🎨

デザイントークンを定義してパッケージとして公開する

2023/08/12に公開
1

はじめに

この記事ではデザイントークンの定義から、CIでの各フォーマットへの変換(CSSのカスタムプロパティやJSのモジュール等)、npmパッケージの公開、コード利用のフロー構築について私なりの実装案を紹介します。

使用するツール

Tokens Studio for Figma(Figma Tokens)
https://tokens.studio/
token-transformer
https://github.com/tokens-studio/figma-plugin/tree/main/token-transformer
Style Dictionary
https://amzn.github.io/style-dictionary/#/
Github Packages(npm)
https://github.co.jp/features/packages

Tokens Studio for Figmaでトークンを定義する

Tokens Studio for Figma(旧Figma Tokens)はFigmaのpluginです。
Tokens Studioを使うメリットは以下。

  • 基本無料
  • Githubと連携してPull Requestが出せる
  • ColorやSpacing,Font Size等20以上のトークンタイプがプリセットされている
  • 作成したトークンをStylesやVariables(有料)に書き出せる

最近Figma本体がVariablesという機能を出してきましたが、APIはエンタープライズプランのみ。
エンタープライズプランが使える恵まれた環境でも、現状beta版で変更可能性があるものだと公式にアナウンスされているので手が出しづらい。
こちらも変更可能性はありますが、VariablesはColor,Number,String,Booleanという大きなカテゴリ4つなので、リストで全てを取得するか、上記4カテゴリのどれかでfilterするかなので(あまりユースケースとして無いと思うが個々のVariableをid指定して取ることもできる)現状エンジニアにとって扱いやすいデータ形式とはいいづらい。
現実的には命名を工夫してリストで取得したものを成型するんだと思いますが、個人的にはTokens Studioのようにあらかじめよしなに成型されたJSONを吐き出してくれる方が嬉しいです。
よってVariables登場でTokens Studio終了?みたいな言説を見かけることがありますが、しばらくは使っていくことになると思うし、個人的にはUIや使い勝手の良さが気に入っているのでずっと使っていきたいと思っています。
ちなみに有料プランなら定義したトークンをVariablesとして吐き出すこともできるので共存していく道もあると思います。

セットアップ

基本的には公式ドキュメント通りに進めて頂ければと思います。
Tokens Studio

  1. インストール
    FigmaのPluginsからFind more pluginsで検索するか、コミュニティのページからインストールするかです
  2. トークンの定義
    GUIから直感的に定義ができます。カラーのグローバルトークンを定義するならcolors.blue.500のようにドットでつないで定義します。ドットを起点にUI上でネストされ、吐き出されるJSONもドットごとにネストされます。
  3. Githubと連携する
    ここは若干公式ドキュメントが古いようです(執筆時点)。
    Syncタブというものがあるように書かれていますが、SettingsタブからSync providersの欄に行き、Add newと書かれたドロップダウンメニューを選択してGithubを選びます。
    認証にはGithubのPersonal Access Tokenを使います。 Classicならrepoのスコープを選択し、Fine-grained tokensならRepository permissionsでContentsのRead and write権限を付与します。
    生成したトークンをAdd new credentialsのフォームに入力し、その他の必要情報を入力してSaveすれば連携完了です。
  4. Pull Requestを出す
    連携が完了するとpushとpullができるようになります。
    何かしら更新されたデータがあればpushができるようになるので、pushのアイコンを選択し、コミットメッセージやブランチ名を指定します。mainに直接pushすることもできるし、ブランチ名を指定すればPush Changesを実行したタイミングでそれまで行った変更が作業ブランチに退避され、mainからは分離されます。この辺のブランチの切り替えは有料プランであれば任意のタイミングで行うことができます。Pushした後にPRを作るかのダイアログが表示されるのでCreate Pull Requestと書かれたボタンをクリックすればそのままGithubのPR作成ページが表示されます。

リポジトリの設定

ここまでで、トークンの定義からPRの作成まで行えるようになりました。
ここからはPRをマージしてパッケージとして公開するまでのフローをご紹介します。
パッケージとして公開する前提なので、デザイントークン専用のリポジトリを用意する形です。
既存プロジェクトと連携しても良いですが、デザインシステムはプロダクトを横断して利用するようなユースケースが多いので特定のリポジトリと連携するというよりは個別に用意する形です。

ディレクトリ構成

├── .github/
│   ├── workflows/
│       ├── build.yml
	├── publish.yml
├── src/
│    ├── tokens.css
│    ├── tokens.d.ts
│    ├── tokens.js
│    ├── tokens.scss
├── output.json
├── package.json
├── README.md
├── style-dictionary.config.json
├── tokens.json

tokens.json

これはTokens Studioが出力するJSONファイルになります。リポジト連携の際に任意の名前を設定できます。

output.json

tokens.jsonの中身をStyle Dictionaryが読み込める形式に変換するToken Transformerというライブラリが出力するファイルです。

src/*

output.jsonの情報を元にStyle Dictionaryが出力するファイルです。今回はcss, scss, jsを出力するようにしています。

.gitub/workflows/*

Github Actionsのワークフローファイルです。目的と実行タイミングが異なるのでbuildとpublishを分けています。

package.json
{
  "name": "@${Githubのアカウント名もしくは組織名}/design-tokens",
  "version": "0.1.0",
  "description": "My Design Tokens",
  "files": [
    "tokens/**"
  ],
  "publishConfig": {
    "registry": "https://npm.pkg.github.com/@${Githubのアカウント名もしくは組織名}/design-tokens"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com:${Githubのアカウント名もしくは組織名}/design-tokens.git"
  },
  "scripts": {
    "transform": "./node_modules/.bin/token-transformer tokens.json output.json",
    "build": "style-dictionary build --config style-dictionary.config.json",
  },
  "devDependencies": {
    "style-dictionary": "^3.8.0",
    "token-transformer": "^0.0.33"
  }
}

まずscriptsですが、transformでtoken-transformer、buildでstyle-dictionaryのコマンドを実行しています。
あとは最終的なビルド結果をGithub Packages経由でnpmに公開するための設定諸々です。

style-dictionary.config.json
{
  "source": ["output.json"],
  "platforms": {
    "css": {
      "buildPath": "src/",
      "transformGroup": "css",
      "files": [
        {
          "format": "css/variables",
          "destination": "tokens.css"
        }
      ]
    },
    "scss": {
      "buildPath": "src/",
      "transformGroup": "scss",
      "files": [
        {
          "format": "scss/variables",
          "destination": "tokens.scss"
        }
      ]
    },
    "ts": {
      "buildPath": "src/",
      "transformGroup": "js",
      "files": [
        {
          "format": "javascript/module",
          "destination": "tokens.js"
        },
        {
          "format": "typescript/module-declarations",
          "destination": "tokens.d.ts"
        }
      ]
    }
  }
}

設定内容の要点は出力後のパス(buildPath)、ファイル名(destination)、フォーマット(format)です。
フォーマットの形式としてどのようなものがあるかは公式ドキュメントをご確認ください。
ここではcss, scss, jsのモジュールとその型定義ファイルを指定しています。

PRをマージするまでのフロー

Tokens StudioからPRが出されると、Files changedからtokens.jsonの内容が確認できます。
jsonファイルの内容をそのまま確認してレビューしても良いのですが、余計な情報も含まれているので開発者は実際に使うcssのカスタムプロパティやSassの変数といった形式でレビューできると直感的です。そのためにわざわざ手元でビルドのスクリプトを実行するのは面倒なので、PRが上がってきたらビルドまでCIで実行してしまいます。そのためのワークフローが以下になります。

.github/workflows/build.yml
name: build design tokens

on:
  pull_request:
    paths:
      - "tokens.json"
      - ".github/workflows/build.yml"

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: "18.x"

      - name: install node_modules
        run: yarn
	
      - name: transform tokens
        run: yarn transform

      - name: build tokens
        run: yarn build

      - name: get pull request number
        run: echo pr_number=$(echo $GITHUB_REF | sed -e 's/[^0-9]//g') >> $GITHUB_OUTPUT
        id: pr_number

      - name: commit built tokens
        id: "auto-commit-action"
        uses: stefanzweifel/git-auto-commit-action@v4
        with:
          commit_message: "Update design tokens #${{ steps.pr_number.outputs.pr_number }}"

pull_requestをトリガーとします。tokens.jsonもしくはこのymlファイル自身が変更された場合にワークフローが実行されます。

name: get pull request number

ここではシェルコマンドを使用し、PRの番号を取得して他のステップで使用可能な形式で出力しています。

echo pr_number=$(echo $GITHUB_REF | sed -e 's/[^0-9]//g') >> $GITHUB_OUTPUT

$GITHUB_REFはGitHub Actions環境内で自動的に設定される環境変数であり、現在のGitリファレンス(ブランチやタグなど)を表します。PRの場合、一般的には refs/pull/PR番号/merge のような形式となります。
sed -e 's/[^0-9]//g': sed コマンドを使用して、文字列から数字以外の文字を削除、すなわちPR番号だけを抽出しています。抽出したPR番号をpr_numberという変数に格納しています。
最後に、他のステップから出力内容を参照できるようid: pr_numberでこのステップのidを指定します。

name: commit built tokens

デザイントークンのビルドが完了した後、コミットを行うステップです。
GitHub Actionsのstefanzweifel/git-auto-commit-actionアクションを使用してワークフローからコミットを行います。commit_message パラメータには、コミットメッセージを指定します。このメッセージ内で ${{ steps.pr_number.outputs.pr_number }} を使用して、直前に取得したPR番号を挿入しています。

このワークフローファイルによってデザイントークンのビルド処理が自動化され、レビュワーはビルド済みのファイルを確認するという流れになります。

パッケージを公開する

srcディレクトリ配下のビルド成果物をパッケージとして公開します。
今回はGithub Packagesを利用してnpmパッケージとして公開する方法を紹介します。
Github Packagesを利用するメリットは以下です。

  • Githubのアカウントで認証とアクセス権を管理できる
  • npmのAdminアカウントのようなものを作成して運用する必要がない(Organizationを想定)
  • Github Actionsとの統合が用意(secrets.GITHUB_TOKENが使える)
.github/workflows/publish.yml
name: publish design tokens

on:
  push:
    branches:
      - main
    paths:
      - "src/**"
      - "package.json"
      - ".github/workflows/publish.yml"

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: "18.x"
          registry-url: https://npm.pkg.github.com/

      - name: set version to env
        run: node -p -e '`VERSION=${require("./package.json").version}`' >> $GITHUB_ENV
      - run: npm ci
      - name: publish to npm
        run: |
          CURRENT_VERSION=$(npm view @${Githubのアカウント名もしくは組織名}/design-tokens version)
          EXISTING_VERSION=${{ env.VERSION }}
          if [ "$CURRENT_VERSION" != "$EXISTING_VERSION" ]; then
            echo "Current version ($CURRENT_VERSION) does not match existing version ($EXISTING_VERSION), publishing package..."
            npm publish
          else
            echo "Current version ($CURRENT_VERSION) already exists, skipping publish..."
          fi
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: add version tag
        uses: pkgdeps/git-tag-action@v2
        with:
          version: ${{ env.VERSION }}
          github_token: ${{ secrets.GITHUB_TOKEN }}
          github_repo: ${{ github.repository }}
          git_commit_sha: ${{ github.sha }}
          git_tag_prefix: "v"

mainへのマージをトリガーとします。src配下のファイル、package.jsonもしくはこのymlファイル自身が変更された場合にワークフローが実行されます。
基本的には公式ドキュメント通りですが、あくまでパッケージの中身とバージョン情報が更新された場合にのみpublishするようにしています。

- name: set version to env
        run: node -p -e '`VERSION=${require("./package.json").version}`' >> $GITHUB_ENV

node -p -e ...コマンドを使用してパッケージのバージョン情報を環境変数に設定しています。

- name: publish to npm
        run: |
          CURRENT_VERSION=$(npm view @${Githubのアカウント名もしくは組織名}/design-tokens version)
          EXISTING_VERSION=${{ env.VERSION }}
          if [ "$CURRENT_VERSION" != "$EXISTING_VERSION" ]; then
            echo "Current version ($CURRENT_VERSION) does not match existing version ($EXISTING_VERSION), publishing package..."
            npm publish
          else
            echo "Current version ($CURRENT_VERSION) already exists, skipping publish..."
          fi
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

現在公開されているパッケージのバージョンをnpm view ...で取得し、CURRENT_VERSIONに格納した上で、直前のステップで取得したpackage.json記載のバージョン情報と比較し、異なる場合のみpublishするためのシェルを実行しています。ちなみに同じ数値のバージョンをpublishしてしまうとエラーになります。

     - name: add version tag
        uses: pkgdeps/git-tag-action@v2
        with:
          version: ${{ env.VERSION }}
          github_token: ${{ secrets.GITHUB_TOKEN }}
          github_repo: ${{ github.repository }}
          git_commit_sha: ${{ github.sha }}
          git_tag_prefix: "v"

このステップでは、前のステップで取得されたバージョン番号を使ってタグを追加しています。これにより、新しいバージョンのリリースがタグ付けされ、リポジトリ内で確認できるようになります。

以上でデザイントークンのリポジトリでトークンのビルドからパッケージの公開までが出来るようになりました。

デザイントークンをコード内で利用する

公開したパッケージをアプリケーションのコードで利用します。
Github Packagesはpublicなパッケージとして公開してもPersonal Access Tokenが必要になります。PackageのRead権限さえついていればOKです。Github PackagesにおけるpublicとはあくまでGithub内で検索可能になるという程度の意味合いで、認証トークンが不要になるということは現状ありません。
Github CLIを使っている方は、gh auth login --scopes read:packagesでGithubにログインし、gh config get -h github.com oauth_tokenで認証トークンを取得してもOKです。
プロジェクトのルートに.npmrcファイルを作成します

.npmrc
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
@${Githubのアカウント名もしくは組織名}:registry="https://npm.pkg.github.com"

${GITHUB_TOKEN}の欄に先ほどのトークンを指定します。
これでパッケージのinstallが可能になります。
ちなみにCI上でパッケージをinstallする場合には${{ secrets.GITHUB_TOKEN }}が使えます。

your-frontend-ci.yml
...省略

- name: Install dependencies
        run: yarn
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }

privateなパッケージとして公開しているなら、別途リポジトリやOrganization単位でsecretsを定義し、そこにread:packages権限のついたPATを指定して読み込ませる必要があります。

ここまで来たら、あとはトークンを使用したいファイルで読み込むだけです。

hoge.scss
@import "@myaccountname/design-tokens/src/tokens";

.SomeComponentTitleText {
    color: $color-text-primary
}

さいごに

以上で、デザイントークンの定義からアプリケーションコード内での利用まで行えるようになりました。
ここまで来れば、あとはデザイントークンそのものをいかに整備していくか、いかにコード内での利用を浸透させるかです。
トークンにはセマンティックな名前がついているはずなので、実装者はよりそのスタイルがアプリケーション内で持っている意味を意識するようになります。
トークン化しにくい値が使用されていれば、それはスタイルのガイドラインから逸脱している値である可能性があります。
デザイントークンは、何となくスタイルを当てているだけの状態から一歩前進するきっかけを与えてくれるものだと私は考えています。
この記事が、デザイントークン導入の一助となれば幸いです。

Discussion

yKesamaruyKesamaru

素晴らしい記事をありがとうございます😃⭐️

typoありましたのでお知らせします。
FigamのPluginsからFind more pluginsで検索するか