Contents
Figmaプラグインの概要とスレッド分離
Figmaプラグイン開発ではUIスレッドとmainスレッドの責務を理解することが最優先です。設計ミスやビルド不整合は動作不良や公開時の差戻しにつながります。
できること
簡潔に説明します。プラグインで代表的に可能な操作を列挙します。
- ドキュメント内ノードの作成・編集(矩形・テキスト・コンポーネント等)。
- ローカルスタイルやテキストスタイルの取得・適用。
- UIスレッド経由での外部API呼び出し(ユーザー同意がある場合)。
- 設計ルールチェックやテンプレート/バッチ生成などのワークフロー自動化。
できないこと・注意点
制約と守るべきルールを短く説明します。
- ユーザーの同意なくデータを外部送信してはいけません。
- 機密情報(APIキー等)をUIやバンドルにハードコーディングしてはいけません。
- mainスレッドはDOMを持たないため、ブラウザAPI(fetch 等)は使えません。ブラウザAPIはUIスレッドで行います。
スレッドの役割(UIスレッド/mainスレッド)
ここで両者の責務を整理します。
- mainスレッド:Figmaドキュメントを直接操作します。同期的なノード作成・編集が可能です。長時間処理は応答性に影響するため分割実行が必要です。
- UIスレッド:ブラウザ環境(DOMあり)でユーザー操作、外部通信、プレビュー表示を担当します。CORSや認証を扱う処理はこちらで行います。
Figmaプラグインのmanifest.jsonとプロジェクト構成
manifest.json はプラグインのメタ情報とエントリを定義します。正確なパス、JSON の構文、公開時の ID などに注意が必要です。
manifest の要点と公式仕様参照
簡単に要点をまとめます。詳細は公式ドキュメントを必ず確認してください。
- manifest.json はコメントを許しません。正しい JSON を出力してください。
- main は main スクリプトへのパス、ui は HTML への相対パスを指します。両方ともビルド出力と一致させてください。
- permissions 等の項目は最小限に留めてください。利用できるキーや意味は公式のマニフェスト仕様を参照してください: https://www.figma.com/plugin-docs/manifest/
id の扱い:開発時と公開時の差分
開発中の取り扱いと公開時の挙動を説明します。
- 開発時は任意の一意な id で動作させられますが、Figma Community に公開すると Figma 側で安定した id が割り当てられます。
- id はユーザー設定やストレージの結びつきに影響します。公開後に id が変わると古い環境と紐付けられたデータが参照できなくなる場合があります。
- ローカル開発用と公開用で manifest を切り替える運用(CIで差し替え)を検討してください。
ui フィールドと permissions の実務的な扱い
ui の指し示す HTML と permissions の扱い方を整理します。
- manifest.ui は HTML ファイルへのパスである必要があります。公開時に Figma がそのファイルを参照するため、dist/ui.html を出力するのが実務的です。
- 一方で開発中は bundler が HTML を JS に埋め込んで html を提供することがあります。この場合でも dist/ui.html を出力して manifest と整合させておくことを推奨します。
- permissions は必要最低限だけを列挙してください。利用可能な権限や意味は公式ドキュメントに従ってください。
例:実務的な manifest.json(説明用。公開前に公式仕様を確認してください)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "name": "My Figma Plugin", "id": "com.example.my-plugin", "api": "1.0.0", "main": "dist/main.js", "ui": "dist/ui.html", "menu": [ { "name": "Open UI", "command": "open-ui" } ], "editorType": ["figma"], "permissions": [] } |
FigmaプラグインのTypeScriptとビルド設定(main と UI を分離)
main と UI はターゲット環境が異なるため、型設定やビルド設定は分けるべきです。ここでは実務で使える分離パターンを示します。
main と UI で別々の tsconfig を使う理由と例
理由とサンプル設定を示します。main は DOM を含めない設定にします。
tsconfig.main.json(main スレッド向け)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "compilerOptions": { "target": "ES2019", "module": "ESNext", "lib": ["ES2019"], "strict": true, "esModuleInterop": true, "skipLibCheck": true, "types": ["@figma/plugin-typings"] }, "include": ["src/main.ts"] } |
tsconfig.ui.json(UIスレッド向け)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "compilerOptions": { "target": "ES2019", "module": "ESNext", "lib": ["DOM", "ES2019"], "jsx": "react-jsx", "strict": true, "esModuleInterop": true, "skipLibCheck": true }, "include": ["src/ui/**/*"] } |
bundlerパターン:webpack の html と esbuild の dist/ui.html の違い
ここでは二つの定番パターンと実務上の注意点を示します。
-
webpack/html-loader パターン
webpack の html-loader や html-webpack-plugin を使うと、src/ui.html の中身を html として main バンドルに注入できます。main 側で figma.showUI(html, { width, height }) と書くのが一般的です。ただし manifest.ui に対応する dist/ui.html を別途出力しておく方が公開時に安全です。 -
esbuild + HTML テンプレート パターン
esbuild では UI を dist/ui.js にバンドルし、src/ui.html(テンプレート)を dist/ui.html にコピーして参照する運用が実務的です。main のバンドルで HTML をインラインに取り込みたい場合は esbuild の build API で loader: { ".html": "text" } を指定して import uiHtml from './ui.html' する方法があります。
main での showUI の例(webpack の html を使う場合)
|
1 2 3 4 |
// src/main.ts figma.showUI(__html__, { width: 360, height: 480 }); figma.ui.onmessage = (msg) => { /* 受信処理 */ }; |
esbuild での例(UI を別ファイルとして出力し、HTML を文字列として取り込む場合)
|
1 2 3 4 |
// src/main.ts import uiHtml from './ui.html'; figma.showUI(uiHtml, { width: 360, height: 480 }); |
パッケージスクリプト例(実務的で manifest と整合する)
|
1 2 3 4 5 6 7 8 |
{ "scripts": { "build:ui": "node scripts/build-ui.js", "build:main": "esbuild --bundle src/main.ts --outfile=dist/main.js --platform=node --loader:.html=text", "build": "npm run build:ui && npm run build:main" } } |
scripts/build-ui.js の役割は src/ui.tsx を dist/ui.js にバンドルし、src/ui.html を加工して dist/ui.html を生成することです。
FigmaプラグインのUIスレッド⇄mainスレッドのメッセージングとセキュリティ
UIスレッドとmainスレッド間はメッセージで連携します。ここでは型安全化とセキュリティ対策を示します。
型安全なメッセージ設計
最小で型を定義して使うと保守性が上がります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// src/types.ts export type PluginMessage = | { type: 'create-text'; text: string } | { type: 'replace-text'; find: string; replace: string } | { type: 'progress'; value: number }; // src/main.ts figma.ui.onmessage = async (msg: PluginMessage) => { if (msg.type === 'create-text') { // 処理 } }; |
UI 側から送る例:
|
1 2 3 |
// src/ui.js parent.postMessage({ pluginMessage: { type: 'create-text', text: 'Hello' } }, '*'); |
実務的なセキュリティ対策
parent.postMessage の第二引数 '*' は便利ですが潜在的リスクがあります。実務的には以下を併用してください。
- UI 側の受信で必ずメッセージの型を検証する(event.data の形と type をチェック)。
- 送信側で可能なら targetOrigin を限定する。Figma の UI は origin が環境により異なるため完全一致指定が難しいケースがあります。その場合は受信側のバリデーションを厳密にします。
- 受信処理で任意のコード実行をしない。受信メッセージはホワイトリスト方式で処理する。
- 機密情報は main 側でも UI 側でも保持しない。外部 API キーはサーバー側で管理し、必要なトークンは一時的に発行する設計にします。
例:UI 側での堅牢な受信ハンドラ
|
1 2 3 4 5 6 7 8 |
window.onmessage = (event) => { const data = event.data; if (!data || typeof data !== 'object') return; const msg = data.pluginMessage; if (!msg || typeof msg.type !== 'string') return; // 型に応じた処理 }; |
Figmaプラグインのフォント処理と大量ノードの対処法
文字列編集やフォントロードはエラーが出やすい箇所です。大量ノード処理は分割実行で応答性を保ちます。
フォントの安全なロードと MIXED の扱い
TextNode.fontName が 'MIXED'(複数フォント混在)になる場合があります。安全にフォントをロードする手順例を示します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
async function loadFontsForTextNode(node: TextNode) { const fonts = new Map<string, FontName>(); const len = node.characters.length; for (let i = 0; i < len; i++) { const f = node.getRangeFontName(i, i + 1); if (f === "MIXED") continue; const key = (f as FontName).family + '|' + (f as FontName).style; fonts.set(key, f as FontName); } for (const font of fonts.values()) { try { await figma.loadFontAsync(font); } catch (err) { // フォントが環境にない場合のフォールバック例 try { await figma.loadFontAsync({ family: 'Inter', style: 'Regular' }); // 必要なら setRangeFontName で代替フォントを適用 } catch (e) { // 最終的にはユーザーに通知 figma.notify('フォントの読み込みに失敗しました。環境を確認してください。'); throw e; } } } } |
ポイント:
- getRangeFontName をキャラクタ範囲で走査してユニークなフォントセットを作る。
- loadFontAsync は失敗することがあるため try/catch で代替フォントを用意する。
- プラグイン側からシステムに新しいフォントを追加できない点に注意する。
大量ノード処理の具体的チャンク化と進捗報告
処理を小さなチャンクに分け、UI に進捗を返すことで応答性を確保します。Undo の挙動にも注意が必要です(同一プラグイン実行内の変更はひとつの Undo 操作になることが多いです)。途中でプラグインを再起動すると Undo ステップが分かれるので注意してください。
サンプル:チャンク処理(setTimeout / requestIdleCallback フォールバック)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
async function processNodesInChunks(nodes: SceneNode[], chunkSize = 50) { let index = 0; const total = nodes.length; while (index < total) { const end = Math.min(total, index + chunkSize); for (let i = index; i < end; i++) { const node = nodes[i]; // ここでノード処理(例: テキスト置換) } index = end; const progress = Math.round((index / total) * 100); figma.ui.postMessage({ type: 'progress', value: progress }); await new Promise<void>((resolve) => { if (typeof (globalThis as any).requestIdleCallback === 'function') { (globalThis as any).requestIdleCallback(() => resolve()); } else { setTimeout(() => resolve(), 0); } }); } } |
Undo に関する注意:
- 可能な限り同一プラグイン実行内で処理を完結させると、ユーザーは一度の Undo で処理を取り消せます。
- 処理を分けて複数回プラグインを実行する運用は、Undo が複数に分かれる点に注意してください。
Figmaプラグインの公開前チェックと運用・CI
公開前にチェックすべき項目と、簡単な CI テンプレートを示します。公開は信頼獲得のプロセスです。
必須メタ情報とスクリーンショット・アイコンの推奨
公開リストに必要な資産と推奨例を示します。最終的には Figma のアップロード画面のルールに従ってください。
- 名前・短い説明(サムネイル的に目を引く説明を短めに)・詳細説明(使い方、権限の説明)。
- スクリーンショット:推奨サイズの一例は 1200×675(16:9)で高解像度の PNG/JPEG を用意すると見栄えが良いです。UI の操作手順が分かる画像を複数枚用意します。
- アイコン:512×512 PNG(透過可)。小さいサイズ(96×96)も生成しておくと安心です。
- サンプル .fig ファイル:プラグインの効果を見せる最小限のサンプルを添付します。
(注)推奨サイズは変更される場合があるため、公開時は Figma の最新ガイドラインを確認してください。
公開審査とプライバシーポリシーのサンプル
公開審査でよくチェックされる点と簡易的なプライバシーポリシーの例を示します。
- 審査での主な着眼点:内容が説明に一致しているか、ユーザーのデータを外部に送る場合は説明と同意があるか、マルウェア的な挙動がないか。
- プライバシーポリシー(簡易テンプレート):
- 「このプラグインはユーザーのデザインデータを外部に送信しません。」または
- 「このプラグインは選択中のノードを外部API(例:画像生成)に送信します。送信はユーザーが明示的に操作した場合のみ行われます。送信先と利用目的は〜」という形で明記し、ホスティングするページへのURLを Community ページに記載してください。
運用・CI・テストの基本テンプレート(GitHub Actions 例)
最小限の CI ワークフロー例を示します。push/PR 時にビルドと型チェック、lint を実行します。
.github/workflows/ci.yml
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
name: CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npm run lint - run: npm run build - run: npm run typecheck |
テストやリリースへの連携(manifest 差し替えや ZIP 生成)はプロジェクト規模に応じて追加してください。
まとめ
- FigmaプラグインはUIスレッドとmainスレッドの責務分離が肝です。適切な manifest 設定とビルド出力の整合で公開トラブルを減らせます。
- TypeScript は main/UI で別々の設定にし、webpack の html や esbuild の HTML テンプレートなど、ビルド方式を選んで実装してください。
- メッセージは型で制約し、UI 側では event.data の形と type を厳密に検証してください。parent.postMessage('*') を使う場合は受信側のバリデーションを徹底します。
- フォントは getRangeFontName を走査して必要なフォントを列挙してから loadFontAsync を行い、失敗時はフェールバックを用意します。大量ノードはチャンク化して進捗通知し、Undo の挙動に注意してください。
- 公開前は manifest/ui の整合、スクリーンショット・アイコン、プライバシーポリシーを用意し、CI でビルド・型チェック・lint を自動化してください。
参考リンク(実務で必ず確認してください)
- Figma Plugins ドキュメント(manifest 等): https://www.figma.com/plugin-docs/
- 公式サンプルリポジトリ: https://github.com/figma/plugin-samples
- @figma/plugin-typings(型定義、npm): https://www.npmjs.com/package/@figma/plugin-typings