lit-and-figma について
Lit と TypeScript で Figma プラグインを作る開発者向けのガイドです。AssemblyScript を WASM にコンパイルし、プラグイン UI にインライン化する手順も扱います。
主な機能
- 01Lit による Web コンポーネント作成
- 02TypeScript と Webpack の設定
- 03ローカル Figma プラグインのビルドと読み込み
- 04AssemblyScript の WASM コンパイル
- 05UI コードへの WASM インライン化
活用シーン
- 01キャンバスに矩形を追加するプラグインの作成
- 02Figma プラグイン UI での WASM 計算実行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rodydavis/ skills --skill lit-and-figmaインストール後、Claude Code で/lit-and-figmaを入力して読み込まれたか確認します。
詳細
Lit と Figma
この記事では、Lit の Web コンポーネントをセットアップし、それを使って Figma プラグインを作成する方法を説明します。
TLDR 最終的なソースはこちらにあります:こちら。
前提条件
- Vscode
- Figma Desktop
- Node
- Typescript
はじめに
まず、空のディレクトリを作成し、好きな名前を snake_case で付けます。
mkdir figma_lit_example
cd figma_lit_example
Web のセットアップ
figma_lit_example ディレクトリに移動したら、Figma と Lit をセットアップできます。まずは Node から始めましょう。
npm init -y
これにより、Node プロジェクトの基本設定が行われ、必要なパッケージがインストールされます。次に、設定ファイルを追加します。package.json を開き、以下の内容に置き換えます。
{
"name": "figma_lit_example",
"version": "1.0.0",
"description": "Lit Figma Plugin",
"dependencies": {
"lit": "^2.0.0-rc.1"
},
"devDependencies": {
"@figma/plugin-typings": "^1.23.0",
"html-webpack-inline-source-plugin": "^1.0.0-beta.2",
"html-webpack-plugin": "^4.3.0",
"css-loader": "^5.2.4",
"ts-loader": "^8.0.0",
"typescript": "^4.2.4",
"url-loader": "^4.1.1",
"webpack": "^4.44.1",
"webpack-cli": "^4.6.0"
},
"scripts": {
"dev": "npx webpack --mode=development --watch",
"copy": "mkdir -p lit-plugin && cp ./manifest.json ./lit-plugin/manifest.json && cp ./dist/ui.html ./lit-plugin/ui.html && cp ./dist/code.js ./lit-plugin/code.js",
"build": "npx webpack --mode=production && npm run copy",
"zip": "npm run build && zip -r lit-plugin.zip lit-plugin"
},
"browserslist": [
"last 1 Chrome versions"
],
"keywords": [],
"author": "",
"license": "ISC"
}
これにより、必要なものがすべて追加され、開発環境と本番環境に必要なスクリプトも追加されます。次に、以下を実行します。
npm i
これにより、開始に必要なものがすべてインストールされます。次に、設定ファイルをセットアップします。
touch tsconfig.json
touch webpack.config.ts
これにより、2 つのファイルが作成されます。tsconfig.json を開き、以下の内容を貼り付けます。
{
"compilerOptions": {
"target": "es2017",
"module": "esNext",
"moduleResolution": "node",
"lib": ["es2017", "dom", "dom.iterable"],
"typeRoots": ["./node_modules/@types", "./node_modules/@figma"],
"declaration": true,
"sourceMap": true,
"inlineSources": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"strict": true,
"noImplicitAny": false,
"outDir": "./lib",
"baseUrl": "./packages",
"importHelpers": true,
"plugins": [
{
"name": "ts-lit-plugin",
"rules": {
"no-unknown-tag-name": "error",
"no-unclosed-tag": "error",
"no-unknown-property": "error",
"no-unintended-mixed-binding": "error",
"no-invalid-boolean-binding": "error",
"no-expressionless-property-binding": "error",
"no-noncallable-event-binding": "error",
"no-boolean-in-attribute-binding": "error",
"no-complex-attribute-binding": "error",
"no-nullable-attribute-binding": "error",
"no-incompatible-type-binding": "error",
"no-invalid-directive-binding": "error",
"no-incompatible-property-type": "error",
"no-unknown-property-converter": "error",
"no-invalid-attribute-name": "error",
"no-invalid-tag-name": "error",
"no-unknown-attribute": "off",
"no-unknown-event": "off",
"no-unknown-slot": "off",
"no-invalid-css": "off"
}
}
]
},
"include": ["src/**/*.ts"],
"references": []
}
これは基本的な TypeScript 設定です。次に、webpack.config.ts を開き、以下の内容を貼り付けます。
const HtmlWebpackInlineSourcePlugin = require("html-webpack-inline-source-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = (env, argv) => ({
mode: argv.mode === "production" ? "production" : "development",
devtool: argv.mode === "production" ? false : "inline-source-map",
entry: {
ui: "./src/ui.ts",
code: "./src/code.ts",
app: "./src/my-app.ts",
},
module: {
rules: [
{ test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/ },
{ test: /\.css$/, use: ["style-loader", { loader: "css-loader" }] },
{ test: /\.(png|jpg|gif|webp|svg)$/, loader: "url-loader" },
],
},
resolve: { extensions: [".ts", ".js"] },
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "ui.html"),
filename: "ui.html",
inject: true,
inlineSource: ".(js|css)$",
chunks: ["ui"],
}),
new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin),
],
});
次に、プラグインの UI を作成します。
touch ui.html
/src/ui.html を開き、以下を追加します。
<my-app></my-app>
次に、Figma プラグインのマニフェストファイルが必要です。
touch manifest.json
manifest.json を開き、以下を追加します。
{
"name": "figma_lit_example",
"id": "973668777853442323",
"api": "1.0.0",
"main": "code.js",
"ui": "ui.html"
}
次に、Web コンポーネントを作成します。
mkdir src
cd src
touch my-app.ts
touch code.ts
touch ui.ts
cd ..
/src/ui.ts を開き、以下の内容を貼り付けます。
import "./my-app";
/src/my-app.ts を開き、以下の内容を貼り付けます。
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators.js";
@customElement("my-app")
export class MyApp extends LitElement {
@property() amount = "5";
@query("#count") countInput!: HTMLInputElement;
render() {
return html`
<div>
<h2>Rectangle Creator</h2>
<p>Count: <input id="count" value="${this.amount}" /></p>
<button id="create" @click=${this.create}>Create</button>
<button id="cancel" @click=${this.cancel}>Cancel</button>
</div>
`;
}
create() {
const count = parseInt(this.countInput.value, 10);
this.sendMessage("create-rectangles", { count });
}
cancel() {
this.sendMessage("cancel");
}
private sendMessage(type: string, content: Object = {}) {
const message = { pluginMessage: { type: type, ...content } };
parent.postMessage(message, "*");
}
}
code.ts を開き、以下の内容を貼り付けます。
const options: ShowUIOptions = {
width: 250,
height: 200,
};
figma.showUI(__html__, options);
figma.ui.onmessage = msg => {
switch (msg.type) {
case 'create-rectangles':
const nodes: SceneNode[] = [];
for (let i = 0; i < msg.count; i++) {
const rect = figma.createRectangle();
rect.x = i * 150;
rect.fills = [{ type: 'SOLID', color: { r: 1, g: 0.5, b: 0 } }];
figma.currentPage.appendChild(rect);
nodes.push(rect);
}
figma.currentPage.selection = nodes;
figma.viewport.scrollAndZoomIntoView(nodes);
break;
default:
break;
}
figma.closePlugin();
};
プラグインのビルド
コードの準備ができたので、プラグインをビルドし、Figma でテストできます。
npm run build
ステップ 1
Figma のデスクトップ版をダウンロードして開きます。
https://www.figma.com/downloads/
ステップ 2
メニューを開き、「Plugins > Manage plugins」に移動します。
![]()
ステップ 3
プラスアイコンをクリックして、ローカルプラグインを追加します。
![]()
ボックスをクリックして既存のプラグインにリンクし、ソースコードのビルド後に作成された lit-plugin フォルダーに移動して、manifest.json を選択します。
![]()
ステップ 4
プラグインを実行するには、「Plugins > Development > figma_lit_example」に移動して起動します。
![]()
ステップ 5
これでプラグインが起動し、キャンバス上に 5 つの長方形を作成できます。
![]()
正常に動作すれば、Figma でフォーカスされた状態の新しい長方形がキャンバス上に 5 つ表示されます。
![]()
WASM のサポート
WebAssembly(https://webassembly.org/)で実行すると効果が期待できる負荷の高い計算がある場合、以下の手順により、可能なときはハードウェアアクセラレーションを利用できます。
AssemblyScript(https://www.assemblyscript.org/)と、WASM を Figma の UI に読み込むために使用する依存関係を追加しましょう。
npm i @assemblyscript/loader
npm i --D assemblyscript js-inline-wasm
npx asinit .
プロンプトで「yes」を選択してプロジェクトファイルを生成し、package.json のスクリプトに以下を追加します。
"asbuild:untouched": "asc assembly/index.ts --target debug",
"asbuild:optimized": "asc assembly/index.ts --target release",
"asbuild": "npm run asbuild:untouched && npm run asbuild:optimized",
"inlinewasm": "inlinewasm build/optimized.wasm --output src/wasm.ts",
WASM に使用するコードは /assembly/index.ts にあります。以下のようになっていることを確認してください。
// The entry file of your WebAssembly module.
export function add(a: i32, b: i32): i32 {
return a + b;
}
では、WASM モジュールをビルドしましょう。
npm run asbuild
WASM のビルド成果物を git add の対象外にするため、以下を .gitignore に追加します。
build
これにより、build ディレクトリに wasm ファイルと wat ファイルが生成されます。ただし、Figma の UI に読み込むにはインライン化する必要があるため、以下のコマンドを実行して wasm ファイルから js を生成します。
npm run inlinewasm
これにより、以下の内容の src/wasm.ts が生成されます。
const encoded = 'AGFzbQEAAAABBwFgAn9/AX8DAgEABQMBAAAHEAIDYWRkAAAGbWVtb3J5AgAKCQEHACAAIAFqCwAmEHNvdXJjZU1hcHBpbmdVUkwULi9vcHRpbWl6ZWQud2FzbS5tYXA=';
export default new Promise(resolve => {
const decoded = atob(encoded);
const len = decoded.length;
const bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = decoded.charCodeAt(i);
}
resolve(new Response(bytes, { status: 200, headers: { "Content-Type": "application/wasm" } }));
});
次に、/src/my-app.ts を開き、以下の内容に更新します。
import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators.js";
@customElement("my-app")
export class MyApp extends LitElement {
@property() amount = "5"; // <-- Pass in a value for the number of rectangles to create
@query("#count") countInput!: HTMLInputElement;
render() {
return html`
<div>
<h2>Rectangle Creator</h2>
<p>Count: <input id="count" value="${this.amount}" /></p>
...
</div>
`;
}
...
}
これにより、作成するボックスの数を外部から渡せるようになります。
次に、/src/ui.ts を開き、以下の内容に更新します。
import "./my-app";
import wasm from "./wasm"; // <-- Our WASM file to load
WebAssembly.instantiateStreaming(wasm as Promise<Response>).then((obj) => {
// @ts-ignore
const value: number = obj.instance.exports.add(2, 4);
console.log("return from wasm", value);
const elem = document.querySelector('my-app')! as HTMLElement;
elem.setAttribute('amount', `${value}`);
});
これで、プラグインをビルドして Figma で実行すると、WASM の呼び出し結果に応じた数のボックスが作成されます。
まとめ
Figma プラグインの作成について詳しく知りたい場合は、こちら(https://www.figma.com/plugin-docs/intro/)をご覧ください。Lit については、こちら(https://lit.dev/)のドキュメントをご覧ください。