🖼️

Figma MCP × GitHub CopilotでReactコンポーネントを自動生成してみた

に公開

はじめに

近年生成AIの利用により、要件を入力することでデザインやコードの生成が高速に行われるようになってきました。ですが、すでに作成されたデザインを忠実にコードに起こすことは一般的な生成AIではまだ苦手な様に感じます。
そこで今回は、Figma REST APIと MCP(Model Context Protocol) を活用して、デザイン情報をJSON形式で直接取得し、それをGitHub Copilotにインプットすることで、デザインに忠実なコンポーネントコードの自動生成を試してみました。

実行環境

Figma APIを実行するにあたって以下の準備が必要です。

Personal Access Tokenの発行

  1. Figma左上のアイコンからSettingsを選択。
  2. Securityタブを選択。
  3. Personal access tokensのGenerate new tokenを選択。
  4. 名前と有効期限を指定し、スコープとしてfile_content:read(取得用の権限)を付与してトークンを発行します。
  5. 発行すると画面上にトークンが表示されるので表示されたトークンを取得する(表示後同じものをWeb上から再取得できないため注意が必要)。

ファイルキー(FILE_KEY)の取得

  1. Figmaのデザインテンプレートを取得
  2. Draftに保存されたテンプレートファイルからfile_keyを取得
    • URLは https://www.figma.com/design/:file_key/:file_name の形式となっているので:file_key部分を抜き出して利用します。

実行

Figma MCPを利用して、デザインデータをJSONとして保存する処理を実装します。以下は処理の一部抜粋です。

server_params = StdioServerParameters(
    command="docker",
    args=[
        "run",
        "-i",
        "--rm",
        "-e", f"HTTPS_PROXY={https_proxy}",
        "-e", f"FIGMA_API_KEY={Config.FIGMA_TOKEN}",
        "-e", f"FIGMA_FILE_KEY={Config.FIGMA_FILE_KEY}",
        Config.DOCKER_IMAGE,
    ],
)


async with stdio_client(server_params) as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()

        args = {
            # ここに前述で取得したfileKeyを設定
            "fileKey": Config.FIGMA_FILE_KEY
        }

        res = await session.call_tool(
            "get_figma_data",
            arguments=args,
        )

        text = res.content[0].text
        os.makedirs("output", exist_ok=True) 

        data = json.loads(text)
        with open("output/figma_data.json", "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2, ensure_ascii=False)

取得したJSON(一部抜粋)は以下のようになります。テキストの色やサイズ、構造が詳細に記述されていることがわかります。

{
  "document": {
    "id": "0:0",
    "name": "Document",
    "type": "DOCUMENT",
    "scrollBehavior": "SCROLLS",
    "children": [
      {
        "id": "1:2",
        "name": "Style Guide",
        "type": "CANVAS",
        "scrollBehavior": "SCROLLS",
        "children": [
          {
            "id": "1:15",
            "name": "Colors",
            "type": "FRAME",
            "scrollBehavior": "SCROLLS",
            "children": [
              {
                "id": "1:18",
                "name": "Primary",
                "type": "TEXT",
                "scrollBehavior": "SCROLLS",
                "blendMode": "PASS_THROUGH",
                "fills": [
                  {
                    "blendMode": "NORMAL",
                    "type": "SOLID",
                    "color": {
                      "r": 0.0235294122248888,
                      "g": 0.4431372582912445,
                      "b": 0.8784313797950745,
                      "a": 1.0
                    }
                  }
                ],
・・・

取得したJSONはデータ量が膨大なため、AIが処理しやすいようコンポーネント単位のファイルに分割します。

with open(INPUT_PATH, "r", encoding="utf-8") as f:
    data = json.load(f)
    components = data["document"]["children"][0]["children"]

    name_count = {}
    for comp in components:
        name = comp.get("name", "unnamed")
        file_name = f"{name}.json"
        out_path = os.path.join(OUTPUT_DIR, file_name)
        with open(out_path, "w", encoding="utf-8") as out_f:
            json.dump(comp, out_f, ensure_ascii=False, indent=2)

分割したJSONファイルをプロンプトとともにGitHub Copilotなどの生成AIに渡します。
今回はTypographyのコンポーネントを利用し、HeadlinesとBodyの情報を生成します。

# Typography Components Generation Prompt (Atomic Design / Co-location)
`output/components/Typography.json` の定義に基づき、Atomic Designの「Atoms」として、再利用可能な Typography コンポーネント群を作成してください。

## 1. ディレクトリ構成(Co-locationルール)
各コンポーネントは独立したディレクトリに配置し、ロジックとスタイルを物理的に分離して構成してください。
構成イメージ:
`src/components/atoms/Typography/[ComponentName]/`
  ├── `[ComponentName].tsx`
  ├── `[ComponentName].module.css`
  └── `index.ts`

## 2. 実装対象のコンポーネント(計12個)
以下の各スタイルを、独立したコンポーネントとして作成してください。
- Headlines: `Headline1`, `Headline2`, `Headline3`, `Headline4`
- Body Regular: `Body1Regular`, `Body2Regular`, `Body3Regular`, `Body4Regular`
- Body Medium: `Body1Medium`, `Body2Medium`, `Body3Medium`, `Body4Medium`

## 3. スタイル定義(CSS Modules)
各コンポーネントに対応する `.module.css` を作成し、JSONから以下の数値を正確に抽出してください。
- Size: Headline(64, 36, 28, 20px), Body(18, 16, 14, 12px)
- Weight: Regular(400), Medium(500) を `fontWeight` に反映。
- Detail: `letterSpacing`, `lineHeightPx` を正確に反映。
- Color: 文字色は `var(--color-text-primary, #212121)` を使用。

## 4. コンポーネント要件(React/TypeScript)
- BaseTextの抽出: 共通ロジック(ポリモーフィックな `as` プロップス、className結合)を受け持つ `InternalBaseText` コンポーネントを内部的に作成し、各Atomで利用すること。
- Polymorphic: `as` プロップスにより、HTMLタグ(h1-h6, p, span等)を動的に変更可能にすること。
  - デフォルトタグ:Headline系は `h1`〜`h4`、Body系は `p`。
- Flexibility: `clsx` または `tailwind-merge` を使用し、外部からの `className` 拡張を許容すること。
- 型安全: `React.ComponentPropsWithRef` を継承し、`ref` や標準的なHTML属性をサポートすること。

## 5. 出力形式
1. 各ディレクトリごとの `.tsx` と `.module.css` のコード
2. これらを外部から利用しやすくするための `index.ts` (Barrel file)

実行結果

AIによって生成されたReactコードの一例が以下になります。
基幹コンポーネントであるInternalBaseTextと意匠コンポーネントであるBody1Regularに分けて生成されています。

import React from "react";
import { twMerge } from "tailwind-merge";
import clsx from "clsx";

// 型安全なポリモーフィックコンポーネント型定義
export type InternalBaseTextProps<C extends React.ElementType> = {
  as?: C;
  className?: string;
  children: React.ReactNode;
} & Omit<React.ComponentPropsWithRef<C>, "as" | "className" | "children">;

export const InternalBaseText = React.forwardRef(
  <C extends React.ElementType = "span">(
    { as, className, children, ...rest }: InternalBaseTextProps<C>,
    ref: React.Ref<any>
  ) => {
    const Component = as || "span";
    return (
      <Component ref={ref} className={className} {...rest}>
        {children}
      </Component>
    );
  }
);

InternalBaseText.displayName = "InternalBaseText";
import { InternalBaseText } from "./InternalBaseText";
import { twMerge } from "tailwind-merge";
import clsx from "clsx";
import React from "react";

const STYLES = twMerge(
  "text-[18px] font-[400] leading-[28px] tracking-[0em]",
  "text-[#212121] text-[var(--color-text-primary),#212121]"
);

export type Body1RegularProps<C extends React.ElementType = "p"> = {
  as?: C;
  className?: string;
  children: React.ReactNode;
} & Omit<React.ComponentPropsWithRef<C>, "as" | "className" | "children">;

export const Body1Regular = React.forwardRef(
  <C extends React.ElementType = "p">(
    { as = "p", className, children, ...rest }: Body1RegularProps<C>,
    ref: React.Ref<any>
  ) => (
    <InternalBaseText
      as={as}
      ref={ref}
      className={twMerge(clsx(STYLES, className))}
      {...rest}
    >
      {children}
    </InternalBaseText>
  )
);

Body1Regular.displayName = "Body1Regular";

生成されたReactコードを基に画面を構成した結果、以下のようになりました。

まとめ

Figma MCPを利用してデザインデータを構造化データ(JSON)として取り出すことで、従来の見た目から推測するAI生成とは異なり、定義に基づいた正確なコード化が可能になりました。

今回はTypographyという比較的シンプルなコンポーネントで検証しました。
設計データそのものをAIにインプットしているため、プロンプトやコンポーネントの設計次第で、より複雑なUIや動的なレイアウトにも十分対応できるポテンシャルを感じます。

株式会社メンバーズ AIフォーオールカンパニー

Discussion