Plasmoで簡単！ブラウザ拡張機能開発

## はじめに

最近ブラウザの拡張機能を作りたいと思うようになり、いい感じのツールないかな？と思って探したところPlasmoというツールが良さそうだったので記事にしました！
なお、筆者はフロントエンドフレームワーク・TypeScript初心者で、ReactとVueのチュートリアルをかじった程度の人間です笑

## Plasmoとは

- 公式サイト：[Supercharge your browser extension development – Plasmo](https://www.plasmo.com/)
- Plasmoはブラウザの拡張機能を簡単に開発できるフレームワークで、TypeScriptをサポート
- `First-class support for React, Svelte, Vue`と書かれており、既存のフロントエンドフレームワークとの相性も良いらしい？
- Plasmoで開発した拡張機能からWebページに何かUIコンポーネントを追加する場合、Shadow DOMによってCSSがWebページに漏れることがなく、CSSの影響範囲が拡張機能に閉じられることも魅力の１つ
    - [Life Cycle of Plasmo CSUI – Plasmo](https://docs.plasmo.com/framework/content-scripts-ui/life-cycle)
- ディレクトリ構造・ファイル名のルール、CLIのオプションによってChrome向け、Firefox向けのmanifest.jsonを自動生成してくれる。これが大変ありがたい！
- ドキュメントも充実しており、サンプルコード集のリポジトリもあるので、やりたいことをどう実現できるかのヒントが見つけやすい

## 早速使ってみる

### インストール

- 今回の開発環境
    - Ubuntu 24.04 LTS（WSL2）
    - pnpm（Plasmoが推奨）

まずはpnpmをインストール。インストール手順はpnpm公式のものを参照してください。

- [Installation | pnpm](https://pnpm.io/installation)

```bash
$ sudo apt update && sudo apt upgrade
$ curl -fsSL https://get.pnpm.io/install.sh | sh -
$ curl -fsSL https://get.pnpm.io/install.sh | sh -
==> Downloading pnpm binaries 9.15.0
 WARN  using --force I sure hope you know what you are doing
Copying pnpm CLI from /tmp/tmp.ABo2bh01ys/pnpm to /home/username/.local/share/pnpm/pnpm
Appended new lines to /home/username/.bashrc

Next configuration changes were made:
export PNPM_HOME="/home/username/.local/share/pnpm"
case ":$PATH:" in
  *":$PNPM_HOME:"*) ;;
  *) export PATH="$PNPM_HOME:$PATH" ;;
esac

To start using pnpm, run:
source /home/username/.bashrc
$ source ~/.bashrc
$ pnpm --version
9.15.0
$ pnpm env add --global lts
Fetching Node.js 22.12.0 ...
Node.js 22.12.0 was installed
  /home/username/.local/share/pnpm/nodejs/22.12.0
All specified Node.js versions were installed
$ pnpm env use --global lts
Node.js 22.12.0 was installed
  /home//.local/share/pnpm/nodejs/22.12.0
Node.js 22.12.0 was activated
/home/username/.local/share/pnpm/node -> /home/username/.local/share/pnpm/nodejs/22.12.0/bin/node
$ node --version
v22.12.0
```

pnpmとnodeのインストールは完了。

次はplasmoのプロジェクトを作成する。

- 手順：[Plasmo Framework – Plasmo](https://docs.plasmo.com/framework)

```bash
$　pnpm create plasmo
🟣 Plasmo v0.89.4
🔴 The Browser Extension Framework
🟡 Extension name: sample-extension
🟡 Extension description: A sample extension.
🟡 Author name: username
🔵 INFO   | Creating new project with popup
🔵 INFO   | Installing dependencies...
...
dependencies:
+ plasmo 0.89.4
+ react 18.2.0 (19.0.0 is available)
+ react-dom 18.2.0 (19.0.0 is available)

devDependencies:
+ @ianvs/prettier-plugin-sort-imports 4.1.1 (4.4.0 is available)
+ @types/chrome 0.0.258 (0.0.287 is available)
+ @types/node 20.11.5 (22.10.2 is available)
+ @types/react 18.2.48 (19.0.1 is available)
+ @types/react-dom 18.2.18 (19.0.2 is available)
+ prettier 3.2.4 (3.4.2 is available)
+ typescript 5.3.3 (5.7.2 is available)

 WARN  Issues with peer dependencies found
.
└─┬ plasmo 0.89.4
  └─┬ @plasmohq/parcel-config 0.41.1
    └─┬ @parcel/config-default 2.9.3
      └─┬ @parcel/optimizer-htmlnano 2.9.3
        └─┬ htmlnano 2.1.1
          └── ✕ unmet peer svgo@^3.0.2: found 2.8.0

Done in 17.6s
🔵 INFO   | Initializing git project...
🟠 WARN   | git commit -m Created a new Plasmo extension exited with non-zero code: 128
🟢 DONE   | Your extension is ready in:  /home/username/sample-extension

    To start hacking, run:

       cd sample-extension
       pnpm dev

    Visit https://docs.plasmo.com for documentation and more examples.
$ ls -al
drwxr-xr-x 6 username username 4096 Dec 16 07:47 sample-extension
```

コマンド１つ実行するだけでプロジェクトが作成されます
中身はこのような感じ

```bash
$ ls -al
total 252
drwxr-xr-x 6 username username   4096 Dec 16 07:47 .
drwxr-x--- 6 username username   4096 Dec 16 07:47 ..
drwxr-xr-x 7 username username   4096 Dec 16 07:47 .git
drwxr-xr-x 3 username username   4096 Dec 16 07:46 .github
-rw-r--r-- 1 username username    344 Dec 16 07:46 .gitignore
-rw-r--r-- 1 username username    558 Dec 16 07:46 .prettierrc.mjs
-rw-r--r-- 1 username username   1520 Dec 16 07:46 README.md
drwxr-xr-x 2 username username   4096 Dec 16 07:46 assets
drwxr-xr-x 6 username username   4096 Dec 16 07:47 node_modules
-rw-r--r-- 1 username username    691 Dec 16 07:47 package.json
-rw-r--r-- 1 username username 207117 Dec 16 07:47 pnpm-lock.yaml
-rw-r--r-- 1 username username    528 Dec 16 07:46 popup.tsx
-rw-r--r-- 1 username username    274 Dec 16 07:46 tsconfig.json
```

### 動作確認

```bash
$ pnpm dev
```

これを実行するとプロジェクト内のbuildディレクトリにビルド対象ごとのディレクトリが作成されます。

```bash
$ ls build
chrome-mv3-dev
```

デフォルトではChrome向けにビルドされるようなので、生成されたディレクトリをChromeで読み込みます。
すると、今回作成した拡張機能がChromeで識別されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3724444/affb2a31-6e25-5f5d-e33b-30c688186441.png)

ピン留めして拡張機能のアイコンをクリックすると・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3724444/5508df9a-28b0-8271-4af8-badd9da1cc66.png)

表示された！
これはプロジェクトディレクトリ内に生成された`popup.tsx `のおかげです。

```typescript
$ cat popup.tsx 
import { useState } from "react"

function IndexPopup() {
  const [data, setData] = useState("")

  return (
    <div
      style={{
        padding: 16
      }}>
      <h2>
        Welcome to your{" "}
        <a href="https://www.plasmo.com" target="_blank">
          Plasmo
        </a>{" "}
        Extension!
      </h2>
      <input onChange={(e) => setData(e.target.value)} value={data} />
      <a href="https://docs.plasmo.com" target="_blank">
        View Docs
      </a>
    </div>
  )
}

export default IndexPopup
```

ちなみに生成されたmanifest.jsonはこんな感じ。

```json
{"icons":{"16":"icon16.plasmo.9f44d99c.png","32":"icon32.plasmo.83dbbbab.png","48":"icon48.plasmo.a78c509e.png","64":"icon64.plasmo.15206795.png","128":"icon128.plasmo.c11f39af.png"},"manifest_version":3,"action":{"default_icon":{"16":"icon16.plasmo.9f44d99c.png","32":"icon32.plasmo.83dbbbab.png","48":"icon48.plasmo.a78c509e.png","64":"icon64.plasmo.15206795.png","128":"icon128.plasmo.c11f39af.png"},"default_popup":"popup.html"},"version":"0.0.1","author":"username","name":"DEV | Sample extension","description":"A sample extension","host_permissions":["https://*/*"],"background":{"service_worker":"plasmo-default-background.5fdd9bcb.js"},"content_security_policy":{"extension_pages":"script-src 'self' http://localhost;object-src 'self';"},"web_accessible_resources":[{"matches":["<all_urls>"],"resources":["__plasmo_hmr_proxy__"]}]}
```

プロジェクトを生成してビルドしただけなのにここまでやってくれる！
めちゃくちゃ簡単に作れますね！

## おわりに

日常の作業をいい感じにできるといいですね！