FigmaのプラグインをWebpackで構築する

# はじめに
Figmaのプラグインを作成する環境をWebpackを使って構築する方法は、下記の公式ドキュメントで案内されてます。
[Bundling with Webpack · Figma Developers](https://www.figma.com/plugin-docs/bundling-webpack/)

ただ、上記のドキュメントの通りにやってみたところエラーが出てしまい一部手を加えたので、備忘録的に記事に残しておきます。

# 1. デスクトップアプリから新しいプラグイン作成
Figmaデスクトップアプリのメニュー、plugins > Development > New plugin から新規プラグイン作成。
今回は「Figma design」の「With UI browser APIs」を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37716/19e4a0f0-b373-5166-95ca-e28a058f09fb.png)

# 2. パッケージのインストール

pluginのディレクトリに移動し、使用するパッケージをインストール。


```bash
npm install --save-dev css-loader html-inline-script-webpack-plugin html-webpack-plugin style-loader ts-loader typescript url-loader webpack webpack-cli
```

ドキュメントでは、`html-webpack-inline-source-plugin` を使用してるのですが、これがエラーで動かなかったので代わりに `html-inline-script-webpack-plugin` を使用してます。

`@figma/plugin-typings` もインストール。

```bash
npm install --save-dev @figma/plugin-typings
```

# 3. プロジェクト用にファイルを設置
`src` ディレクトリを作成し、下記のファイルを設置。


`src/ui.html`

```html:src/ui.html
<img src="<%= require('./logo.svg') %>">
<h2>Rectangle Creator</h2>
<p>Count: <input id="count" value="5"></p>
<button id="create">Create</button>
<button id="cancel">Cancel</button>
```

`src/logo.svg`

```html:src/logo.svg
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
  <path d="M27 5V27H5M31 9V31H9M1 1H23V23H1V1Z" stroke="black" stroke-width="2"/>
</svg>
```

`src/ui.ts`

```tsx:src/ui.ts
import './ui.css'

window.addEventListener('load', () => {

  document.getElementById('create').onclick = () => {
    const textbox = document.getElementById('count') as HTMLInputElement
    const count = parseInt(textbox.value, 10)
    parent.postMessage({ pluginMessage: { type: 'create-rectangles', count } }, '*')
  }
  
  document.getElementById('cancel').onclick = () => {
    parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*')
  }
})
```

`src/ui.css`

```css:src/ui.css
body {
  font: 12px sans-serif;
  text-align: center;
  margin: 20px;
}
button {
  border-radius: 5px;
  background: white;
  color: black;
  border: none;
  padding: 8px 15px;
  margin: 0 5px;
  box-shadow: inset 0 0 0 1px black;
  outline: none;
}
#create {
  box-shadow: none;
  background: #18A0FB;
  color: white;
}
input {
  border: none;
  outline: none;
  padding: 8px;
}
input:hover { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1); }
button:focus { box-shadow: inset 0 0 0 2px #18A0FB; }
#create:focus { box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.3); }
input:focus { box-shadow: inset 0 0 0 2px #18A0FB; }
```

`src/code.ts`

```tsx:src/code.ts
figma.showUI(__html__)

figma.ui.onmessage = msg => {
  if (msg.type === 'create-rectangles') {
    const nodes = []

    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)
  }

  figma.closePlugin()
}
```

# 4. manifest.jsでバンドルされたファイルをポイント


バンドルされたコードを `dist`ディレクトリに出力するようにWebpackをセットアップするので、これらのバンドルファイルを指すように `manifest.json` を変更する。


```javascript:manifest.js
{
  ...
  "main": "dist/code.js",
  "ui": "dist/ui.html",
  ...
}
```

# 5. webpackの設定


`webpack.config.js` を作成し、下記のように設定をする。

```javascript:webpack.config.js
const HtmlInlineScriptWebpackPlugin = 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',

  // This is necessary because Figma's 'eval' works differently than normal eval
  devtool: argv.mode === 'production' ? false : 'inline-source-map',

  entry: {
    ui: './src/ui.ts', // The entry point for your UI code
    code: './src/code.ts', // The entry point for your plugin code
  },

  module: {
    rules: [
      // Converts TypeScript code to JavaScript
      { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ },

      // Enables including CSS by doing "import './file.css'" in your TypeScript code
      { test: /\.css$/, use: ['style-loader', { loader: 'css-loader' }] },

      // Allows you to use "<%= require('./file.svg') %>" in your HTML code to get a data URI
      { test: /\.(png|jpg|gif|webp|svg)$/, use: [{ loader: 'url-loader', options: { esModule: false }}] },
    ],
  },

  // Webpack tries these extensions for you if you omit the extension like "import './file'"
  resolve: { extensions: ['.tsx', '.ts', '.jsx', '.js'] },

  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist'), // Compile into a folder called "dist"
  },

  // Tells Webpack to generate "ui.html" and to inline "ui.ts" into it
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/ui.html',
      filename: 'ui.html',
      inlineSource: '.(js)$',
      chunks: ['ui'],
    }),
    new HtmlInlineScriptWebpackPlugin([
      /ui.js$/
    ]),
  ],
})
```

# 6. ts.configの設定

`.ts.config` の `lib` に `dom` を追加

```json:.ts.config
"lib": ["es6", "dom"],
```


# 7. 完了
以上で完了。


開発時は、

```bash
npx webpack --mode=development --watch
```

ビルドは下記です。

```bash
npx webpack --mode=production
```

公開する前には、必ずビルドを実行してください。


成功していればFigma上でRectangleを作成するサンプルプラグインが動きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37716/61282614-770d-17b2-f63c-79958f0dc879.png)