💯

Chrome拡張の開発フレームワークをCRXJSからWXTに乗り換えた話

2024/10/24に公開

はじめに

Chrome拡張の開発を便利にするフレームワークをCRXJSからWXTに乗り換えたのですが、Chrome拡張を初めて開発する人にも読めそうな内容になりました。お役に立てば幸いです。

Chrome拡張の原始的なつくりかた

Chrome拡張は、下記の手順だけで作れます。

  1. 開発ディレクトリ内にJavaScriptやCSS、アイコンなどを置く
  2. 開発ディレクトリにmanifest.jsonを置く
    • JavaScriptやCSSをどこのサイトで読み込むかなどの定義
  3. Chromeの「拡張機能の管理」を「デベロッパーモード」にして開発ディレクトリを読み込ませて動作確認
  4. 全部をzipで固める
  5. Chrome開発者サイトにアップロードして、説明文を書いて、審査が通れば公開

https://developer.chrome.com/docs/extensions/reference/manifest?hl=ja
https://developer.chrome.com/docs/extensions?hl=ja
とても簡単なので、特にフレームワークとか使わなくてもできます。

Before: CRXJSで開発、それなりに便利だった

ブラウザ拡張を作るのに便利なフレームワークは有名なのが3つほどあって、どれも以下のようなお世話をしてくれます。

  • TypeScriptのトランスパイル
  • コードを書き換えるたびにChromeの「拡張機能の管理」で「再読み込み」しなくても瞬時に反映される
  • manifestをTypeScriptファイルに書くのでコード補完が効く

僕は、「小説家になろう」と「カクヨム」の本文を縦書きにして、UIをちょっといじったりキーボードショートカットを使えるようにする、ちょっとした拡張を公開しています。
https://chromewebstore.google.com/detail/縦書きになろう/eaokpigogcoahjjgkdgcaldgcbdfbdei?hl=ja

この開発に CRXJS Vite Plugin を便利に使っていました。
https://crxjs.dev/vite-plugin

脆弱性アラート…

ある日Githubさんが「CRXJSの依存ライブラリに脆弱性があるぞ」と言ってきました。CRXJSは2年近く開発が止まっており、対応を待ってもダメそうでした。

リポジトリに動きがない様子 →CRXJSのCode frequency

公開している拡張に影響するような脆弱性じゃないので放っておいてもよかったのですが、メールが何度も来るので、面倒ですが他に乗り換えることにしました。

After: 気軽にWXTに乗り換え

CRXJSの代替は主に2つです。

https://docs.plasmo.com/framework
https://wxt.dev/
Plasmoのほうが先発で最近人気があるっぽいです。


PlasmoとWXTのダウンロード数比較。Plasmoが5月に爆伸びしてるの何があったんだろう。

小さい拡張だし、とにかく手間をかけたくないので、ドキュメントがなんとなく簡単そうに見えたWXTを試してみることにしました。

乗り換え手順

公式ドキュメントの頭から追っていってある程度作ってから、元のコードをコピーして持ってくる方針です。まずは最小コードで動くところまで。

1. プロジェクト作成

npx wxt@latest init

あとは指示に従うと初期状態ができあがります。(リファレンス -- Installation

2. エントリポイント

僕の拡張は「小説家になろう」と「カクヨム」のページ内でそれぞれのコードを実行するので、entrypoints/ディレクトリ以下に "Content Script" を2つ作ることになります。(リファレンス -- Entrypoints
エントリポイントの名前はファイル名、ディレクトリ名でコントロールするようになっています。僕は複数ファイルをまとめやすいようにディレクトリを作りました。

entrypoints/
   narou.content/  ← 小説家になろう用
      index.ts
   kakuyomu.content/  ← カクヨム用
      index.ts

3. defineContentScript()

index.tsに、どこのサイトでどのタイミングで読み込むか定義します。なるほど、これが後でmanifest.jsonに合成されるんですね。この定義をスクリプト本体のすぐ隣に書けるのはイイですね。(リファレンス -- Content Scripts

index.ts
export default defineContentScript({
  matches: [
    "https://ncode.syosetu.com/*"
  ],
  runAt: "document_start",
  main() {
    console.log('hello, なろう!')
    :
    (実行されるスクリプト本体)
    :
  }
})

4. アイコン

アイコンがないとChromeストアにアップロードしたときに怒られるので、用意します。乗り換えの場合はもともと使ってたアイコンのファイル名を変えて所定のディレクトリに置くだけです。

public/
   icon/
      16.png
      48.png
      128.png

manifest.json"icons"要素を一切自分で書く必要がないの、イケてますね。

5. 開発実行

npm run dev

を実行すると、開発中の拡張だけがインストールされた、まっさらのGoogle Chromeが起動しました。なるほど、他の拡張がインストールされてない状態は大事ですね。ていうかすごいな。

Dev toolを表示させて「小説家になろう」にアクセスしたら、ログがでました。OK、できてる。

6. 拡張名とかバージョンとか

manifest.jsonに書くべきその他の内容は、wxt.config.tsに書きます。TypeScriptで型が定義されてるのでエディタで補完が効いて書きやすいです。(リファレンス -- Configuration/manifest
コメントとか書けるのもいいですよね。

wxt.config.ts
import { defineConfig } from "wxt"

// See https://wxt.dev/api/config.html
export default defineConfig({
  manifest: {
    manifest_version: 3,
    name: "縦書きになろう",
    version: "2.1.5",
    description: "「小説家になろう」と「カクヨム」を縦書きで読みやすくします。",
    web_accessible_resources: [
      // 画面上にアイコンを表示するのに必要
      {
        resources: ["icon/48.png"],
        matches: ["<all_urls>"],
      }
    ]
  }
})

7. 粛々と移植

あとは、もともと使っていたライブラリをインストールしたり、TypeScriptファイルを移植したりするだけでした。

注意点

移植作業ではほとんど困らなかったのですが、ひとつだけハマりました。

index.tsファイル内のmain()以外の場所に書いた関数をmain()から呼んだらブラウザ上で「そんな関数はない」エラーになりました。

index.ts
function someFunction() {
   なにかする
}

export default defineContentScript({
   :
   main() {
      someFunction()  // ← エラーになる
   }
})

別ファイルにしてimportしたら解決しました。

someFunction.ts
export function someFunction() {
   なにかする
}
index.ts
import {someFunction} from './someFunction'

export default defineContentScript({
   :
   main() {
      someFunction()  // ← エラーにならない
   }
})

乗り換えて良かったこと

CSS!SCSS!

僕の拡張では、小説を縦書きにするためにスクリプトと一緒にcssも読み込みます。Extensionの仕様的にはmanifest.jsonにcssファイル名を書くのですが、WXTではなんと、Content Scriptに

index.ts
import "./index.css"

と書くだけです。えー便利。(リファレンス -- Content Scripts/CSS

しかも。

npm install -D sass-embedded

sass-embeddedをインストールしてから、

index.ts
import "./index.scss"

と書けば、scssがすんなり使えました。CRXJSではscssが使えなかったので、めっちゃ嬉しいです!
ビルドするとcssファイルができてて、manifest.jsonもちゃんとこうなってました。すごーい。

manifest.json
"content_scripts": [
  {
    "matches": ["https://ncode.syosetu.com/*"],
    "run_at": "document_start",
    "css": ["content-scripts/narou.css"],
    "js": ["content-scripts/narou.js"]
  }
]

ZIP!

CRXJSはディレクトリへのファイル配置まではしてくれましたが、zipに固める機能はありませんでした。
以前はMacだけ使ってたのでnpmスクリプトで

package.json
"zip": "cd dist && zip ../tategaki-ni-narou.zip -r *"

とか書いてたのですが、最近Windowsを使うことが多くて、zipコマンドが無くてエラーになるのが面倒でした。WSLでもいいんですが、やっぱりネイティブのほうがなにかと便利なんですよね。

WXTに乗り換えたら、npmスクリプトがすでに用意されてました。

package.json
"zip": "wxt zip",
"zip:firefox": "wxt zip -b firefox",

ああ自分で工夫しなくても勝手にzipファイルができてくれる。しかもFirefox用のコマンドもある。(使ってないけど)

これで、Windowsネイティブでも快適に開発できるようになりました。(そしてautoCrlfの罠にハマってアワアワする)

開発用ブラウザ

開発中の拡張だけがインストールされたChromeが立ち上がる機能は、一瞬「普段自分が使ってるChromeでやらせてくれよ」と思いましたが、やっぱ便利です。

僕の拡張はページのHTMLやCSSに手を加える系なのですが、広告ブロッカーが入った普段使いのChromeで試したときには気づかなかった不具合に気づくことができました。

さいごに

WXTを使ったChrome拡張の作り方を紹介しました。WXTはシンプルで、やってほしいことはやってくれるけど、余計なことはしない、変なブラックボックスがないという印象です。気に入りました。すんなり乗り換えられて良かったです。

あと、manifest.jsonは拡張機能のすべてを規定するファイルです。フレームワークを利用する場合でも理解しておくほうが困りごとが減るのでおすすめです。
https://developer.chrome.com/docs/extensions/reference/manifest?hl=ja

GitHubで編集を提案

Discussion