たくさんの自由帳

Figma プラグインを作って図形を矢印で繋げられるようにした話(ReactとVite)

投稿日 : | 0 日前

文字数(だいたい) : 28313

どうもこんばんわ。
届きました。こちら

Imgur

特におすすめなのが 恋するMODE(2ndアルバム) と 青い春は君と。(3rdアルバム) です!!!
恋するMODE、ガチ名曲。ずっと聞いてられる。ついでにゲームのD.C.4もいい!!!。
あと後者もめっちゃ好きなんだけどCUFFS 系列全然CD出してくれないからこの値段ならすごく安い!!

Imgur

本題

Figmaで矢印を引くプラグイン、どれがいいんだろう?
選択した2つのアイテム?間を矢印で引いて欲しいんだけどいまいちどれが良いのか分からないのでもういっそのこと作るかになってる。

作った

2つ図形を選んで、その間を矢印で結んでくれます。
ちゃんと右左折が必要な場合は曲がってくれます(複雑じゃなければ

https://www.figma.com/community/plugin/1344710416431362546

Imgur

今回はこの話です。

ソースコード

どぞー

https://github.com/takusan23/yajirushi-mode

公式

https://www.figma.com/plugin-docs/

ざっくり

デスクトップ版Figmaを入れるだけでいいらしい(無料ユーザーでも作れるぽい)。
そしてTypeScriptで作れるらしい。よかった。

ユーザーの入力を受け付けるUIの部分と、実際にFigmaのキャンバス?で図形書いたりする部分はコードレベルで分かれているらしい。(ui.htmlcode.js
UIの部分は<iframe>で読み込まれるらしい。そしてブラウザ用のAPIも叩けるって!
(Webブラウザの技術、localStorageとか<input type="file">fetch()も使えるのかな?)
localStorgeは使えんかった)

UI側じゃない、Figma側の方はブラウザではないJavaScript実行環境らしいので、
JavaScriptの言語機能は使えるけど、ブラウザ由来の機能は使えないとのこと。ちなみに、使える言語機能はES6までです。

コートレベルで分かれてるので、それぞれメッセージを投げ合ってやり取りすればいいらしい。シリアライズ出来ないとだめですね。
これElectronのレンダラープロセス、メインプロセスの関係に似てますね。今のElectron分からんけど;;
https://www.figma.com/plugin-docs/how-plugins-run/

せっかくなので、UI部分はhtml / css直書きよりはReactとかTailwind CSSを使いたいところですが、、、
どうやらui.html以外のファイルを渡すことが出来ないらしい。
Reactは複数の<script>からなるし、CSSもどっかに出てくるので、どうにかして1つのHTMLに全てのJSCSSHTMLを詰め込まないといけない。

というわけでありました。Bundleに関しての話
https://www.figma.com/plugin-docs/libraries-and-bundling/

というか、Reactの例とesbuild (Vite?)の例がありました。今回はこれに乗っかります。

あとあんまり関係ないけどなんかとてもわかりやすく説明してる気がする。なんだこれ?
ご丁寧にGIF画像つかってコマンドラインの操作方法とか書いてる。でもやってることは難しそう。

https://www.figma.com/plugin-docs/plugin-quickstart-guide/

つくる

矢印が引ける、Figma Plugin作ります。
あ、今回はFigma プラグイン開発には必須ではないReactViteesbuildTailwind CSSとかを使うので、最小限の例が見たければ他行ったほうが良いと思います。

環境

必要なものは、Figma デスクトップアプリVSCode(テキストエディタなら何でも良い)Node.jsです。

なまえあたい
OSWindows 10 Pro
Node.jsv20.9.0

Figma デスクトップアプリ版を入れる

https://www.figma.com/ja/downloads/

こちら、私はWindows版をいれます。秒でインストールが終わりました。。。
UACとか求められなかったけどどういう事なの?

プラグインをつくる

https://www.figma.com/plugin-docs/plugin-quickstart-guide/

なにか適当なプロジェクトを開いて(なければ作ります)
↓ デザインの方を押す Imgur

次に、左上のロゴを押して、プラグイン開発プラグインの新規作成を押します。

Imgur

名前は適当につけて、今回はFigmaのデザインの方だけで動けばいいので、デザインの方を選びます。

Imgur

矢印を作るプラグインを作るわけですが、矢印の設定とかをするための画面が必要なので、カスタムUIを選びます。

Imgur

あとは保存先を尋ねてくるので、適当に選びます。

Imgur

これで最小限のプラグインのコードが出来たらしいです。

Imgur

完了を押すとプラグインにありました!

Imgur

VSCode で開く

https://www.figma.com/plugin-docs/plugin-quickstart-guide/#install-project-dependencies

保存先をVSCodeで開きます。
ファイルの中身はこうなってるでしょうか?

Imgur

Node.jsを使ったことがあれば気付くかもしれませんが、node_modulesとか言うのがいませんね。
というわけでnpm iします。

まあコマンドプロンプトとかGitBashでも何でも良いんですが、VSCodeから起動できるのでもうそれで、
ターミナルVSCodeで開きます。Windows以外はしらんけど多分おんなじ感じだと思う。

Imgur

そこでnpm iを打ち込んでエンターです。
ながい。gradleのそれと違ってライブラリはプロジェクト毎にダウンロードするので長いです。

Imgur

これで依存関係もおっけー

git を使う(お好みで)

Gitを使いたい場合は。git initコマンドを叩いてね。
コミットとかしたい場合は。とりあえず動かしたければ要らないね。

gitignoreがすでにあるので便利ですね。

git init

Imgur

Vite + React で UI 部分を開発できるようにする

Next.jsとかはこのWebpackとかのバンドラーの設定をいい感じにやってくれてたから。。。いざ自分がやろうとするときついな。
雰囲気でesbuildサンプルとにらめっこしてみる。

そもそも Vite とか Webpack とか esbuild って何?

ガチでフロントエンド何もわからない。

esbuild

https://esbuild.github.io/

型付きでおなじみTypeScriptのコードはブラウザでは動かせないんですね。ブラウザはJavaScriptしか分からないので。
なので、TypeScriptJavaScriptに変換しないといけないのですが、これはesbuildがやってくれるらしい。

あ、今更ですがTypeScriptJavaScriptに変換される都合上、お硬い言語にあるinstanceofとかはありません。
型情報はJavaScriptに変換する際に消え去るので、実行時にクラスを判別とかは出来ないです。

また、ReactJSX / TSXJavaScriptに直さないといけないのですが、これもJavaScriptにしないといけません。
これもesbuildがやってくれるらしい。

その他、ライブラリのコードを自分の書いたコードといっしょに吐き出して本番で動かせる(node_modules無しで起動)ようにする機能、
古いJavaScript実行環境でも(Figmaのプラグイン(UIじゃない方)はES6+までの言語機能はサポート)新しい言語構文を使えるように、古くても動くように書き直す機能とかもesbuildがやるらしい。

これらはesbuild以外のWebpackとかも同じことをやるらしい。

Viteは何?

Vite

ありざいす

どうやら、esbuildには開発サーバー機能がないらしい?。
Reactとかのフロント開発では、開発サーバーを立ち上げて快適ホットリロードのありがたみを感じつつ開発をすると思いますがないらしい。これがないといちいちビルドし直さないといけない?

→ というより、Figma プラグインUIに必要なJS / HTML / CSSを一つのhtmlにしないといけないので、ホットリロードよりもこのためかも。

そのためにViteを使ってるとかなんですかね?もうよくわからない。

Figma の esbuild サンプルを見る

https://github.com/figma/plugin-samples/blob/master/esbuild-react/package.json

どうやら、UI部分の開発にはViteFigma上で動く方はesbuildにやらせているそうです。

"build": "npm run build:ui && npm run build:main -- --minify",
"build:main": "esbuild plugin-src/code.ts --bundle --outfile=dist/code.js",
"build:ui": "npx vite build --minify esbuild --emptyOutDir=false",
"build:watch": "concurrently -n widget,iframe \"npm run build:main -- --watch\" \"npm run build:ui -- --watch\"",

フロントエンド難しすぎ

てなわけで普通にReactを使ったWebサイトを作りたければ、すぐに使えるNext.jsとかを選ぼうねってReactの人が言ってる。
Figma プラグイン開発でReact使いたければ地道にセットアップするしか無いと思いますが。。。

https://ja.react.dev/learn/start-a-new-react-project

Figma プラグイン開発で esbuild + Vite + React + Tailwind CSS を使うようにセットアップ

ViteじゃなくてWebpackでも良いはずなんだけど、WebpackNext.jsで使ったことあるので今回はViteにしてみる。

フォルダ構成を変更しておく

Reactのコンポーネント置き場ようにsrcみたいなのを置いておくべきかなと思ったので。
src-uisrc-pluginを作りました。src-uiにはReactとかのUI 関連を、src-pluginにはUIじゃないFigma上で動く方を。

src-pluginの方には早速code.tsを移動させました。
また、code.jsと、ui.htmlは要らなくなるので消します。消すのはcode.jsの方で、code.tsの方はsrc-pluginに移動させてください。

それから、tsconfig.jsonも消しちゃいましょう。
そしてsrc-pluginフォルダ内にtsconfig.jsonを追加します。削除せずとも移動させるでも良かったんですが。中身はこんな感じ。
サンプル通りです。https://github.com/figma/plugin-samples/blob/master/esbuild-react/plugin-src/tsconfig.json

{
  "compilerOptions": {
    "target": "es6",
    "lib": ["es6"],
    "strict": true,
    "typeRoots": ["../node_modules/@figma"]
  }
}

最後、変更した都合上、パスが変わってしまったので、manifest.jsonを変更します。
変更点は、main"code.js""dist/code.js"に。ui"ui.html""dist/index.html"になります。

{
  "name": "yajirushi-mode",
  "id": "1344710416431362546",
  "api": "1.0.0",
  "main": "dist/code.js",
  "capabilities": [],
  "enableProposedApi": false,
  "editorType": [
    "figma"
  ],
  "ui": "dist/index.html",
  "networkAccess": {
    "allowedDomains": [
      "none"
    ]
  },
  "documentAccess": "dynamic-page"
}

ここまでのファイル構成と、manifest.jsonです。

Imgur

esbuild

https://esbuild.github.io/getting-started/

まずは簡単そうな、Figma上で動く方を(UIじゃない方)。
VSCodeのターミナルへ戻り、esbuildライブラリを追加します。
開発時しか使わない(Reactみたいにブラウザ側では使わない)ので--save-devですね。

npm install --save-exact --save-dev esbuild

ひとまずこれで、次にVite + React + Tailwind CSSUI側いきます。

Vite と React

https://ja.vitejs.dev/guide/

Viteを入れて、Reactも使えるようにします。
・・・が、npx create以外の方法が書いてないのかな?npm install viteの方法で地道にセットアップしたいんやが?

というわけで手探りでやってみる。
サンプルをみると、vite /vite-plugin-singlefile / plugin-react-refresh の3つを入れているのでまずは入れてみる。

plugin-react-refreshが非推奨になってしまったので、@vitejs/plugin-reactの方を入れます。

npm install --save-dev vite vite-plugin-singlefile @vitejs/plugin-react

次に、Reactを入れます。--save-devは付けません。

npm install react react-dom

TypeScriptなので型定義ファイルも入れます。

npm install --save @types/react @types/react-dom

次に、viteの設定ファイルを作ります。
vite.config.tssrc-uiがあるフォルダと同じところに作成して、サンプルを参考にしながら(というかほぼコピペ)こんな感じ。
https://github.com/figma/plugin-samples/blob/master/esbuild-react/vite.config.ts

多分パスを./src-uiにすればいいはず。

import { defineConfig } from "vite";
import react from '@vitejs/plugin-react'
import { viteSingleFile } from "vite-plugin-singlefile";

// https://vitejs.dev/config/
export default defineConfig({
    root: "./src-ui", // UI のコンポーネントとかがあるパス
    plugins: [react(), viteSingleFile()],
    build: {
        target: "esnext",
        assetsInlineLimit: 100000000,
        chunkSizeWarningLimit: 100000000,
        cssCodeSplit: false,
        outDir: "../dist",
        rollupOptions: {
            output: {
                inlineDynamicImports: true,
            },
        },
    },
});

現時点のファイル構成

Imgur

package.json の編集

Figmaの方はesbuildJavaScriptに、Reactで作るUIの方はViteJavaScriptにできるようにします。
package.jsonを開き、scriptsの部分を変更します。scripts以降は変更ないです。

変更点はscripts内の、buildbuild:pluginbuild:uiコマンドの追加です。

  • buildコマンド
    • 後述する、pluginuiのビルドをやる
  • build:plugin
    • esbuildを使い、code.tsJavaScriptにしてdistに入れるように。
      • この際に、古いバージョンで動かすから、新しい文法を使ってたら動くように書き直すように指示します。これが--target=es2015の部分。
        • optional chainingES6だと使えない
    • ついでに依存してるライブラリもそのJavaScriptに入れてねって。
  • build:ui
    • viteを使ってvite.config.tsを元にJavaScriptにしたのちdistに入れてねってしてるらしい。
{
  "name": "yajirushi-mode",
  "version": "1.0.0",
  "description": "自分のFigmaプラグイン",
  "main": "code.js",
  "scripts": {
    "lint": "eslint --ext .ts,.tsx --ignore-pattern node_modules .",
    "lint:fix": "eslint --ext .ts,.tsx --ignore-pattern node_modules --fix .",
    "build": "npm run build:ui && npm run build:plugin -- --minify",
    "build:plugin": "esbuild src-plugin/code.ts --bundle --target=es2015 --outfile=dist/code.js",
    "build:ui": "npx vite build --minify esbuild --emptyOutDir=false"
  },

React で UI を作る

消してしまったui.htmlReactで再現させましょう。
code.tsの方はそのままなので、ui.htmlの画面をReactで作って、ui.htmlでやっていたJavaScriptを書けば良いはず。

src-uiApp.tsxって名前でファイルを作り、以下のtsxを貼り付けてね。

import { useState } from 'react'

function App() {
    const [count, setCount] = useState(5)

    return (
        <>
            <h2>Rectangle Creator</h2>
            <p>
                Count:
                <input
                    onChange={(ev) => setCount(Number(ev.target.value))}
                    value={count} />
            </p>
            <button
                onClick={() => {
                    parent.postMessage({ pluginMessage: { type: 'create-rectangles', count } }, '*')
                }}
            >
                Create
            </button>

            <button
                onClick={() => {
                    parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*')
                }}
            >
                Cancel
            </button>
        </>
    )
}

export default App

次に、同じフォルダへmain.tsxを作り、App()コンポーネントを呼び出します。

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

ReactDOM.createRoot(document.getElementById('root')!).render(
    <React.StrictMode>
        <App />
    </React.StrictMode>,
)

最後に、また同じフォルダにindex.htmlを作り、以下をコピペします。
Reactを呼び出すやつですね。

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>やじるしモード</title>
</head>

<body>
    <div id="root"></div>
    <script type="module" src="./main.tsx"></script>
</body>

</html>

で、消えないエラーがあると思います。src-plugin/にはtsconfig.jsonがありますが、src-ui/にはtsconfig.jsonが無いからですね。
というわけでsrc-uiフォルダ内にtsconfig.jsonを追加し、以下コピペ。これもサンプル通りです。。。
https://github.com/figma/plugin-samples/blob/master/esbuild-react/ui-src/tsconfig.json

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": false,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  }
}

あと、vite-env.d.tsファイルを置くのが、Vite + Reactアプリのお作法?らしいので置いておきます。
src-ui内にvite-env.d.tsファイルを作って、以下コピペ。

/// <reference types="vite/client" />

ここまでのファイル構成です。
Imgur

ビルドしてみる

npm run buildをターミナルで叩くことで、UI側とプラグイン側のJavaScript吐き出しが行われます。

Imgur

distフォルダが出来て、code.jsindex.htmlがあれば成功!!!
おっめでと~~~~🎉🎉🎉
まだ設定が終わったところなんですけどね初見さん。あとホットリロードとかはscriptsで書いてないので無いです。

Imgur

Figma 側で読み込んでみる

Figmaのデスクトップアプリを開いて、作ってるやつを押してみれば良いはず。

Imgur

はえ~最初に書いてあったui.htmlはこんな姿だったんですね~
Reactで書くため速攻で消したので知らなかった)

Imgur

ボタンを押してみる。えいっ
オレンジの四角形が5個並びました!おお~

Imgur

とりあえずesbuildFigma プラグイン側ViteFigma UI側両方がちゃんと設定されてうまく動いていそうです。
よかったよかった。

タイプチェックをする

esbuildTypeScriptの型が合っているかまでは見てくれないので、プロパティが足りないとか、型があってないとかのエラーがあってもトランスパイルされちゃいます。
VSCode上でも見れますが、開いてないと分からないので型チェックするようにしましょう。

package.jsonを開いて、tscコマンドを追加します。scriptsに1個追加です。
tscコマンドを足しました。src-pluginsrc-uiの中にあるTypeScriptコードで型があっているかのチェックができるようになりました。

"scripts": {
    "lint": "eslint --ext .ts,.tsx --ignore-pattern node_modules .",
    "lint:fix": "eslint --ext .ts,.tsx --ignore-pattern node_modules --fix .",
    "tsc": "tsc --noEmit -p src-plugin && tsc --noEmit -p src-ui",
    "build": "npm run build:ui && npm run build:plugin -- --minify",
    "build:plugin": "esbuild src-plugin/code.ts --bundle --target=es2015 --outfile=dist/code.js",
    "build:ui": "npx vite build --minify esbuild --emptyOutDir=false"
},

Tailwind CSS を入れる(欲しければ)

あ、要らなければ別にいいです。私は欲しいので...

https://tailwindcss.com/docs/guides/vite

Reactで書けるようになった!!でもCSS書きたくない。
というわけでTailwind CSSを入れます。Vite環境下での導入方法がちゃんとあります!

Tailwind CSSを入れて。

npm install -D tailwindcss postcss autoprefixer

Tailwind CSSの構成ファイルを以下のコマンドで生成します。
tailwind.config.jspostcss.config.jsがでてくるはず。

npx tailwindcss init -p

できたら、tailwind.config.jsを開いて書き換えます。
Tailwind CSSのユーティリティ名の走査対象ファイルをsrc-uiにします。

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src-ui/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

次に、src-uiApp.cssを追加します。
中身はこんな感じ。

@tailwind base;
@tailwind components;
@tailwind utilities;

そして最後、App.tsxを開き、App.cssimportします。
ついでに、味気ないのでApp.tsxTailwind CSSでいい感じに見た目を揃えておきます。

import { useState } from 'react'
import "./App.css"

function App() {
    const [count, setCount] = useState(5)

    return (
        <div className='flex flex-col space-y-2 items-center'>
            <h2 className='text-3xl'>Rectangle Creator</h2>
            <p>
                Count:
                <input
                    className='border-black border-b-2 ml-2'
                    onChange={(ev) => setCount(Number(ev.target.value))}
                    value={count} />
            </p>
            <button
                className='rounded-md border-blue-300 border-2 px-4'
                onClick={() => {
                    parent.postMessage({ pluginMessage: { type: 'create-rectangles', count } }, '*')
                }}
            >
                Create
            </button>

            <button
                className='rounded-md border-red-300 border-2 px-4'
                onClick={() => {
                    parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*')
                }}
            >
                Cancel
            </button>
        </div>
    )
}

export default App

センスが無いのであれですが、とりあえずTailwind CSSのセットアップも出来ました。
Imgur

Figma Plugin API になれよう

本題の矢印を書く前に、手始めに四角形じゃなくて矢印を出せるようにしてみましょう。

code.ts

サンプルコードではcreateRect()してますが、これをcreateLine()にすれば棒が出てくるようになります。
また、strokesで線の色、strokeCapで矢印を出せるようです。

// This plugin will open a window to prompt the user to enter a number, and
// it will then create that many rectangles on the screen.

// This file holds the main code for plugins. Code in this file has access to
// the *figma document* via the figma global object.
// You can access browser APIs in the <script> tag inside "ui.html" which has a
// full browser environment (See https://www.figma.com/plugin-docs/how-plugins-run).

// This shows the HTML page in "index.html".
figma.showUI(__html__);

// Calls to "parent.postMessage" from within the HTML page will trigger this
// callback. The callback will be passed the "pluginMessage" property of the
// posted message.
figma.ui.onmessage =  (msg: {type: string, count: number}) => {
  // One way of distinguishing between different types of messages sent from
  // your HTML page is to use an object with a "type" property like this.
  if (msg.type === 'create-rectangles') {
    const nodes: SceneNode[] = [];
    for (let i = 0; i < msg.count; i++) {
      const line = figma.createLine();
      line.x = i * 150;
      line.strokes = [{ type: 'SOLID', color: { r: 1, g: 0, b: 0 } }]
      line.strokeCap = 'ARROW_LINES'
      figma.currentPage.appendChild(line);
      nodes.push(line);
    }
    figma.currentPage.selection = nodes;
    figma.viewport.scrollAndZoomIntoView(nodes);
  }

  // Make sure to close the plugin when you're done. Otherwise the plugin will
  // keep running, which shows the cancel button at the bottom of the screen.
  figma.closePlugin();
};

こんな感じ。
なるほど。。。

Imgur

座標系

Figmaは上に向けてマイナスになる。(WebGL のそれと違うのか...)
右向かってプラスは他と変わらんかも。

console.log

ここのコンソールを表示/非表示を選ぶことで、見慣れた画面が出てきます。
consoleタブに移動すれば、console.log()の内容が出力されます。これはUI側(React)もFigma で動く側code.ts)もここに出てきます。

Imgur

Imgur

アイテム(ノード)の選択イベント

code.tsでこんな感じに書けば、、、取れます。
選択中アイテムを配列で返してくれます。

// 2つのアイテム(Node)を選択したかどうか
figma.on("selectionchange", () => {
  console.log(figma.currentPage.selection)
})

注意点ですが、selectionの配列の順番は決まっていません。
これは、2個選択した際に、0番目が最初に選択したノードになるかもしれないし、2回目に選択したノードになるかもしれないということです。

https://www.figma.com/plugin-docs/api/properties/PageNode-selection/

片っぽだけ矢印をつける

strokeCap = 'ARROW_LINES'すれば矢印が引けることがわかりました、、、が、
別に両方矢印は要らない。片方ついてたらそれでいい。

↓これ。こんな感じに右側だけ矢印をつけたい。
Imgur

というわけで調査してみた。Figmaで矢印を引いてNodeconsole.logして色々見てみた。
結果、あれは線(createLine())ではなく、ベクターで出来てて(createVector())、
VectorNetworkで、ストロークに矢印をつける(?)ようにすればいいらしい。

// Figma の矢印は LineNode じゃなくて VectorNode で出来てる
// SVG で線を書く必要がある
// M 0 0 L 900 0
// https://www.figma.com/plugin-docs/api/properties/VectorPath-data/
const lineVector: VectorNode = figma.createVector()
lineVector.strokeWeight = 5
lineVector.cornerRadius = 20

// 矢印を追加するためには、VectorPath ではなく、VectorNetwork を使って、最後(or 最初)のストロークに矢印をつける必要があるらしい。
// が、SVG の data を VectorNetwork にするのは面倒なので、
// vectorPaths に入れたあとに出てくる、vectorNetwork をディープコピーして矢印をつけることにする
lineVector.vectorPaths = [{
    windingRule: 'NONE',
    data: `M 0 0 L 900 0`
}]

// VectorNetwork を使ってストロークに矢印をつける
const vertices: VectorVertex[] = lineVector.vectorNetwork.vertices
    .map((stroke, index) => {
        if (index === lineVector.vectorNetwork.vertices.length - 1) {
            // 終了側につける
            return { ...stroke, strokeCap: 'ARROW_LINES' }
        } else {
            return stroke
        }
    })

// VectorNetwork をセットする
lineVector.setVectorNetworkAsync({
    ...lineVector.vectorNetwork,
    vertices: vertices
})

figma.currentPage.appendChild(lineVector)

vectorPathsした後に、またsetVectorNetworkAsyncしてて何がしたいんだって話ですが、
SVGのパスを書くのが一番速く、簡単なのですが、矢印をストロークに付けたい場合は、
VectorNetworkオブジェクト内のverticesに入ってるストロークを変化させる必要がある、、、が、VectorNetworkオブジェクトを作るのが多分とてもつらいので、
SVGのパスをvectorPathsで書いてから、vectorNetworkプロパティに反映されたオブジェクトから最後のストロークだけ変化させて、setVectorNetworkAsyncで反映させる。
が一番いいのかなと思いました。

こんな感じにかたっぽだけ矢印が付きます。
Imgur

PluginAPI['mixed'] ←こいつ何?

たまにこの、numberPluginAPI['mixed']unionで定義されているプロパティーがあります。
で、このPluginAPI['mixed']JavaScriptSymbolらしい、、、Symbol、はて何なんだ?

interface CornerMixin {
  cornerRadius: number | PluginAPI['mixed']
  cornerSmoothing: number
}

というわけで見てみたけど、どうやら表現できない値の時にnumberではなくPluginAPI['mixed']を入れているらしい。
symbolの言語機能を使っているとかではなく、ただ表現できない時にsymbolを入れてるだけだった。symbol使ったことないから助かる(?)。

https://www.figma.com/plugin-docs/api/properties/figma-mixed/

だからunionなんですね、で、その表現できない値が何?
ってわけなんですが、どうやら角を丸くするやつとかは、右上だけ丸くする。など部分的に丸めたい時、1つのプロパティでは表現出来ないため、
代わりになる別のプロパティに値を入れて、元のプロパティにはPluginAPI['mixed']で表現出来ないよって表しているらしい。

// 四角形で、角丸の場合
if (rectangle.type === 'RECTANGLE') {
    if (rectangle.cornerRadius === figma.mixed) {
        // 一つのプロパティでは表現出来ない
        // 代わりにそれぞれの角丸プロパティを参照する必要がある
        console.log(rectangle.topLeftRadius)
        console.log(rectangle.bottomRightRadius)
    } else {
        // 一つのプロパティで表現できる。あと型推論されて number になります
        const cornerRadius: number = rectangle.cornerRadius
        console.log(cornerRadius)
    }
}

UI 側のダークモード対応

この辺参照。
https://www.figma.com/plugin-docs/css-variables/#semantic-color-tokens

背景勝手に暗くなるけどFigmaが既に用意してくれてるやつだしそれ使ったほうが良さそう(?)

Figma の UI コンポーネントを使いたい

この辺見て、
このサイト知ったのが作ったあとなので、私は使ってない!(頑張ってTailwind CSSで見た目整えた)

気が向いたらやるかも。もっと早く言って欲しかった

https://www.figma.com/plugin-docs/figma-components/

localStorage はなさそう

使えないっぽい。
代わりに、Figma プラグイン側にfigma.clientStorageっていう、Key-Valueで設定とか保存できるlocalStorageみたいなやつがいるので、それを使えば良さそう。
UIから保存したい場合は、プラグイン側へメッセージを投げて、保存して貰う必要があります。

https://github.com/takusan23/yajirushi-mode/blob/master/src-plugin/code.ts#L41

ところでどうやって線を書こう?

環境構築とFigma Plugin APIちょろっといじって気付いた。
どうやって線を書こう。というか、曲がった線なんて引けなくない?

createLine()の場合は、多分真っ直ぐにしか引けず、しかも傾けるとかは厳しそう雰囲気。
てなわけで調べた。SVGのパスが使えるらしい。

https://www.figma.com/plugin-docs/api/properties/figma-createvector/
どうやら、figma.createVector()とか言うので、VectorNodeが作成できる。
これが、SVGで線がかけるやつらしく、例えばこんな感じなSVGのパスを書くと。。。

const lineVector = figma.createVector()
lineVector.strokeWeight = 5
lineVector.vectorPaths = [{
    windingRule: 'NONE',
    data: `M 5501 -9064 L 5829 -8772 L 5829 -8772`
}]
figma.currentPage.appendChild(lineVector)

線が引ける。
これをいい感じにすれば良さそうね。

Imgur

ちなみに、このSVGのパス(M 5501 -9064 L 5829 -8772 L 5829 -8772)は何を表しているかと言うと、1行毎に分解するとわかりやすいかも。

M 5501 -9064 // M は Move。つまり移動。 X=5501 Y=-9064 に移動
L 5829 -8772 // L は Line。線が引ける。 X=5829 Y=-8772 まで線を引く
L 5829 -8772 // これも同様。X=5829 Y=-8772 まで線を引く

最初と最後だけがあれば良いわけではなく、2つ以上Lが無いといけない?
なんか中間点ないと矢印引けないとかだっけ?

プラグインを作る

もうつかれたので、ここからは端折ります。

線を書く

愚直に線を伸ばして当たるかどうか試してるだけです。
U字で線が書けるか、一回折れ曲がるだけで書けるかなどを試してうまく行けば線を書いてる。

開始点、曲がる点、曲がる点、終了点の座標を入れた配列を返しています。
[ {x: 0, y: 0}, {x: 0, y: 0}, {x: 0, y: 0} ... ] 具体的にはこの辺のコード

https://github.com/takusan23/yajirushi-mode/blob/master/src-plugin/DrawLineAlgorithmTool.ts

矢印を書く

線を書くための点をもらったら、そのとおりにSVGを書きます。
書いたら、VectorNetworkを取得して、ディープコピーした後、最初と最後の線を矢印で装飾します。
この辺です。

https://github.com/takusan23/yajirushi-mode/blob/master/src-plugin/CreateArrowTool.ts

UI とプラグインでやり取りするメッセージ

何となく共通化しといたほうがいいかなと思ってこの辺に。
JSON.stringifyを使って文字列にした後、Figma プラグイン側にメッセージを送るため、シリアライズできる必要があります。

https://github.com/takusan23/yajirushi-mode/blob/master/src-common/MessageTypes.ts

UI 部分

編集画面の各編集項目はコンポーネントに切り出してます。
面倒なので再レンダリングとかはまじで意識してないです、、、

https://github.com/takusan23/yajirushi-mode/blob/master/src-ui/components/ArrowSetting.tsx

そう言えば編集画面はかなり見通しが悪くなっちゃったので、カスタムフックに全部切り出しました。
といっても、位置と、UIで入力したパラメーターくらいしか渡していなく、線をどこまで書けばいいか等は全部プラグイン側なので、、、

https://github.com/takusan23/yajirushi-mode/blob/master/src-ui/hooks/useArrowSetting.ts

ちょっと頑張ったので、縦並びを選択したら縦になるし、横並びで選択したら横になるようにした。
https://github.com/takusan23/yajirushi-mode/blob/master/src-ui/hooks/useArrowSetting.ts#L85

Imgur

Imgur

公開

以下を参考に

プラグイン一覧を開くと、開発中のも表示されるので、ここから公開を押す。

Imgur

リリースビルドする

もしwebpackとかvite等のバンドラーが開発モードだった場合は本番にしてビルドしましょう。

二段階認証を有効にする

Figmaアカウントを二段階認証できるようにしないとだめらしい

Imgur

公開に必要なもの

https://help.figma.com/hc/en-us/articles/360042293394-Publish-plugins-to-the-Figma-Community#Creators_and_contributors

必要な、というか面倒なのは以下ですね。

  • 一番上にでる画像(1920x1080
  • アイコン(128x128

Imgur

説明とかは、まあ一応それっぽい英語と日本語で書いておきました。

Imgur

連絡先とか、誰かと一緒に作った場合はここで追加できます。

Imgur

次にプライバシー?の質問があるけど、これはスキップできるらしい。
サーバーにデータ送るか、ログイン機能があるか、インターネット経由でアセット(Webフォントとか)ダウンロードするか等。
今回作ったプラグインはインターネット切り離しても動くと思う(必要なものは全てバンドルした)からほぼNoだと思う。

Imgur

これで公開できます。
公開ボタンを押しましょう。

Imgur

できた

Imgur

アプリストアみたいに、審査があるらしい。わくてか

Imgur

なんかプライバシーについての質問に答えると、ご協力ありがとうねって言ってくれます。

Imgur

審査通った

はやい。その日のうちに通ってない?
審査結果はメールで貰えます。

まとめ

  • Figmaの座標系は上に向けてマイナスになる
  • 別にUI側HTML / CSS / JavaScriptで書いて、プラグイン側TypeScript (JS)で書いてもいい
    • 今回はesbuildとか使って余計にややこしくなったけど...
  • esbuildがとても速い
    • ES6向けにビルドするよう注意ね
  • Reactやその周辺ツールは動くハズ。(Tailwind CSSi18nextも動いた)
    • ただしindex.html一つにまとめる必要があるのでVite等のバンドラ設定しよう
  • UIFigmaのコンポーネントが使えるかもしれない
  • 公開はそんなに身構えなくていい

メモ

  • 多分code.jsuiindex.htmlは監視してる?
    • npm run buildした後にFigma デスクトップアプリに戻ると、プラグインが再読み込みされてる
  • Error: Syntax error on line 1: Unexpected token .
    • optional chainingを使ったらエラーで進まなくなった。
      • dataOrNull?.nameとかdataOrNull?.getName?.()みたいに、Nullの可能性がある場合でも?.で呼び出せるやつ
    • Figmaプラグインで動く方は、ES6+までの言語機能しか無い
      • esbuildのコマンドで--target=es2015すると、?.の構文をES2015 (ES6)で動かせるようにesbuildが書き直してくれます。
  • ドキュメントをよく見ると結構いろんなパラメーター(というかプロパティーか)公開されている、遊びがいがある
  • 割とTypeScript力が試されている感が
    • Kotlinにはない概念というか、、、TypeScriptの静的解析がとても賢いなと思う
    • いやでも型ガードの組み合わせとか本当にパズルそのものや・・・"型パズル"とはよく言ったものだなあと思います
  • プラグインのアイコンは、プラグイン公開時のが使われるそうです
    • とくにmanifest.jsonに書いたり..とかではない

おわりに

esbuildがめちゃめちゃ速い。以上です。