Figma APIとGitHub Actionsによるデザイントークンとコードベースの同期の試み #techtekt Advent Calendar 2023

この記事はtechtektアドベントカレンダー2023の20日目の記事です🎁

はじめに

こんにちは。
HR forecasterというプロダクトの開発をしている伊藤です。
最近、デザイナーとエンジニアで協力して、Figmaで定義したデザイントークンとGitHubリポジトリ上のTypeScriptコードをFigma Variables REST APIを利用して同期するトライアルを行ったので、エンジニア観点で紹介します。

今回のトライアルの背景

前提として、HR forecasterは複数のサブプロダクトで構成され、開発エンジニアチーム、GitHubリポジトリはサブプロダクト毎(マルチリポジトリ構成)に分かれています。
これらのサブプロダクト間でデザインの一貫性を保つために、カラーを共通npmパッケージで定義して利用できるようにしたいと考えていました。

%%{init:{'theme':'base','themeVariables':{'primaryColor':'#6A7FAB','primaryTextColor':'#FFF','primaryBorderColor':'#6A7FAB','secondaryColor':'#6A7FAB','lineColor':'#FFF','noteTextColor':'#FAFBF9','noteBkgColor':'#6A7FABBB','textColor':'#FFF','fontSize':'14px'},'themeCSS':"text.actor {font-size:16px !important;}"}}%%

graph TB
    A[サブプロダクトA] --> DT[デザイントークン]
    B[サブプロダクトB] --> DT
    C[サブプロダクトC] --> DT

前提知識

まず本記事の前提知識となる要素をいくつか紹介します。

デザインシステムとデザイントークン

デザインシステムは、一貫したデザインや操作性でプロダクトを提供するための重要な仕組みです。
これには、ガイドラインと原則、UIコンポーネント、スタイルガイドなどが含まれます。

次に、デザイントークンは、デザインシステムの構成要素の変数や値を指します。
これには、カラー、タイポグラフィ、余白などが含まれます。
デザイントークンを用いることで、デザイン要素をシステム全体で統一し、一貫性を持たせることができます。

参考: デザインシステム - Figma

HR forecasterのカラートークンのカテゴリ分け

HR forecaster内でのカラートークンは以下のようにカテゴリ分けしています。

  • Component Token: コンポーネントのデザインに関連するトークン
  • System Token: システム全体で使用される抽象化されたトークン
  • Reference Token: カラーコードの分かりやすい参照用トークン

※ 再設計中のため、今後変更される可能性があります。

Figma上でのトークンの参照関係

FigmaのVariable Aliasの機能を用いて、 Component Token -> System Token -> Reference Token の形で参照関係を設定しており、Reference Tokenに定義されているカラーコードを変更すると、Component TokenSystem Tokenのカラーコードも変更されることになります。

Figma VariablesとREST API

Figma Variablesを利用すると、Figma内で変数を使ってデザイン要素を管理できるようになります。
変数の例として、カラー、タイポグラフィ、余白などの要素が挙げられます。

参考: Figmaでのバリアブルに関するガイド - Figma

Figma Variables REST APIは、Variablesを外部のシステムやツールと連携させるためのREST APIです。
今回はVariables取得用エンドポイント GET local variables を利用しています。

Figma Variables - GET local variables | Figma Developers https://www.figma.com/developers/api#variables-endpoints

このAPIを使用することで、Variablesとコードを同期させるような自動化されたワークフローを構築することが可能になります。
今回のトライアルでは、FigmaからGitHubリポジトリで管理するTypeScriptのコードへの流れで同期していますが、逆にGitHubリポジトリで管理するTypeScriptのコードをFigma Variablesに同期させることも可能です。

参考: Syncing design systems using Variables REST API - Figma

REST APIのイメージができるように、以下にAPIレスポンスの例を示します。
各Variable の valuesByModetypeVARIABLE_ALIAS の場合は、valuesByModeid で指定されたVariableの値を参照しています。
name: reference/green/60VARIABLE_ALIAS ではないため、カラーのr,g,b,aのそれぞれがnumber型で含まれています。
このJSONを解析して、TypeScriptのコードを生成します。

{
    "status": 200,
    "error": false,
    "meta": {
      "variableCollections": {
        "VariableCollectionId:202:2381": {
          "defaultModeId": "202:0",
          "id": "VariableCollectionId:202:2381",
          "name": "color",
          "modes": [
            {
              "modeId": "202:0",
              "name": "Mode"
            }
          ],
          "key": "476785295cc3f230353e211cd8f98d60d1327055",
          "variableIds": [
            "VariableID:611:24740",
            ...
            "VariableID:611:24731",
            ...
            "VariableID:611:24725",
            ...
          ]
        }
      },
      "variables": {
        "VariableID:611:24740": {
          "id": "VariableID:611:24740",
          "name": "component/button/primary/background-color",
          "key": "1bff2b6b6824828c9cb8d8d70ca27ee0794a37ac",
          "variableCollectionId": "VariableCollectionId:202:2381",
          "resolvedType": "COLOR",
          "valuesByMode": {
            "202:0": {
              "type": "VARIABLE_ALIAS",
              "id": "VariableID:610:24720"
            }
          },
          ...
        },
        ...
        "VariableID:610:24720": {
          "id": "VariableID:610:24720",
          "name": "system/interface/primary",
          "key": "f7259d94c411db19ed50757e894b10e354e9be02",
          "variableCollectionId": "VariableCollectionId:202:2381",
          "resolvedType": "COLOR",
          "valuesByMode": {
            "202:0": {
              "type": "VARIABLE_ALIAS",
              "id": "VariableID:610:24718"
            }
          },
          ...
        },
        ...
        "VariableID:610:24718": {
          "id": "VariableID:610:24718",
          "name": "reference/green/60",
          "key": "c5d6a5bee4b0604109413fb53419e85fa1571af8",
          "variableCollectionId": "VariableCollectionId:202:2381",
          "resolvedType": "COLOR",
          "valuesByMode": {
            "202:0": {
              "r": 0.0784313753247261,
              "g": 0.729411780834198,
              "b": 0.5803921818733215,
              "a": 1
            }
          },
          ...
        }
        ...
      }
    }
  }

設計したFigma VariablesとTypeScriptコードの変換ルール

Figma Variablesから取得したJSONをTypeScriptコードに変換する際に、以下のルールを各Variableのnameに適用しています。

  • / -> . に変換
  • - -> lowerCamelCaseによる連結に変換

変換例:

Figma Variable TypeScript Variable
component/button/primary/background-color component.button.primary.backgroundColor

デザイントークンとコードベースの同期フロー

今回のフローは、GitHub Actionsのワークフローを使用して半自動化しています。
ワークフローは日次で定期的に実行され、FigmaのVariables APIを呼び出してデータを取得します。
その後、Node.jsのスクリプトを使ってTypeScriptのコードを生成します。
生成されたコードに前回との差分がある場合、その差分はGitHubリポジトリにコミットし、新しいPull Requestを作成します。

%%{init:{'theme':'base','themeVariables':{'primaryColor':'#6A7FAB','primaryTextColor':'#FFF','primaryBorderColor':'#6A7FAB','secondaryColor':'#6A7FAB','lineColor':'#FFF','noteTextColor':'#FAFBF9','noteBkgColor':'#6A7FABBB','textColor':'#FFF','fontSize':'14px'},'themeCSS':"text.actor {font-size:16px !important;}"}}%%

sequenceDiagram
    participant GHA as GitHub Actions
    participant FAPI as Figma Variables API
    participant NodeJS as Node.jsスクリプト
    participant Repo as GitHubリポジトリ

    Note over GHA: 日次実行
    GHA->>FAPI: Variables API呼び出し
    FAPI-->>GHA: Variables APIレスポンス取得
    GHA->>NodeJS: TypeScriptコード生成スクリプト実行
    NodeJS-->>GHA: TypeScriptコード生成完了

    alt TypeScriptコードに前回との差分があった場合
        GHA->>Repo: 差分をコミット
        Repo-->>GHA: 
        GHA->>Repo: Pull Requestを作成
        Repo-->>GHA: 
    end

上記のワークフローにより、Pull Requestが作成されると、エンジニアはコードレビューを行い、問題がなければマージします。
最終的には、適切なタイミングでGitHub Packages公開用の別のワークフローを実行し、デザイントークン(npmパッケージ)を公開します。

生成するTypeScriptコードの例

Figma Variablesのレスポンスから生成されるTypeScriptコードの例を紹介します。
デザイントークンのカラーに関するコードを一部抜粋しています。

src/colors.ts

import { Colors } from './types';

export const colors: Colors = {
  component: {
    button: {
      primary: {
        backgroundColor: '#14ba94',
        hoverBackgroundColor: '#00a78d',
      ...
      },
    ...
  },
  system: {
    interface: {
      primary: '#14ba94',
      primaryHover: '#00a78d',
      ...
    },
    ...
  },
  reference: {
    green: {
      ...
      50: '#00a78d',
      60: '#14ba94',
      ...
    },
   ...
  },
};

TypeScriptから扱いやすいように、型定義も生成します。

src/types.ts

type Hex = string;

export type Colors = {
  component: {
    button: {
      primary: {
        backgroundColor: Hex;
        hoverBackgroundColor: Hex;
        ...
      };
    ...
  };
  system: {
    interface: {
      primary: Hex;
      primaryHover: Hex;
      ...
    };
    ...
  };
  reference: {
    green: {
      10: Hex;
      20: Hex;
      ...
    };
   ...
  };
};

src/index.ts

export * from './colors';
// export * from './typography';
// export * from './spacing';

さいごに

今後、デザイナーとエンジニアがさらに効率的なデザインと開発プロセスを実現できるように、デザイントークンやワークフローを改善していきたいと考えています。

最後に、今回、Figma Variables REST APIのレスポンスからTypeScriptコードを生成する仕組みを開発しましたが、気を付けるポイントが2点ありました。
それぞれのポイントに対して関数を定義して利用したため、同じような仕組みを開発する方の参考になればと思います。

  • Figma Variableから別のVariableへAliasによる参照ができるため、TypeScriptのコードを生成する際に、Aliasを再帰的に辿ってVariableのValueを取得する必要がある
/**
 * Figma VariableがAliasの場合は、参照先のVariableを取得する。
   * Aliasの参照先がAliasの場合は、再帰的に参照先を取得する。
   *
   * @param variable 対象のVariable
   * @param variables 探索対象のVariableのリスト
   * @param modeId Variableの対象モード。Figma VariableはDarkやLightのようなモードを複数持つので、どのモードの値を取得するかを指定する。
   * @returns AliasでないVariable
   */
function resolveAlias(variable: Variable, variables: Variables, modeId: string): Variable {
   if (isAlias(variable, modeId)) {
      return resolveAlias(
         variables[(variable.valuesByMode[modeId] as VariableAlias).id],
         variables,
         modeId,
      );
   }

   return variable;
}

/**
 * Figma VariableがAliasかどうかを判定する。
   * Aliasの場合は、VariableAlias型であるため、参照先のVariableから値を取得する必要がある。
   *
   * @param variable Figma Variable
   * @param modeId Variableの対象モード。Figma VariableはDarkやLightのようなモードを複数持つので、どのモードの値を取得するかを指定する。
   * @returns Aliasならtrue、そうでなければfalse
   */
function isAlias(variable: Variable, modeId: string): boolean {
   return (
      variable.valuesByMode[modeId].hasOwnProperty('type') &&
      (variable.valuesByMode[modeId] as VariableAlias).type === 'VARIABLE_ALIAS'
   );
}
  • Figma上ではカラーは16進数カラーコード(#FFFのように)だが、APIレスポンスではr,g,b,aのそれぞれがnumber型で含まれているため、TypeScriptのコードを生成する際に、r,g,b,aから16進数カラーコードに変換する必要がある
function rgbToHex({ r, g, b }: { r: number; g: number; b: number }): string {
   const toHex = (value: number): string => {
      const hex = Math.round(value * 255).toString(16);

      return hex.length === 1 ? '0' + hex : hex;
   };

   const hex = [toHex(r), toHex(g), toHex(b)].join('');
   return `#${hex}`;
}

補足

  • Figma Variables機能は2023年12月20日時点では、オープンベータ版の機能であるため、今後のリリースでは変更があるかもしれません
  • Figma Variables REST APIは2023年12月20日時点では、エンタープライズプランで利用可能です。

参考: Figmaベータ版の機能 - Figma

伊藤 祥 Sho Ito

テクノロジー本部 エンジニアリング統括部 クライアントサービス開発部所属 シニアエンジニア

2023年6月にパーソルキャリアに入社。現在はHR forecasterの開発に従事。

※2023年12月現在の情報です。