WXTとSvelteでChrome拡張機能の開発

· 10 min read

Chrome拡張機能の開発でフレームワークWXTを使ったので備忘録です。今回は拡張機能のフロントエンドではあまり例のないSvelteを選びました。

WXTってこんなもの

WXTの特徴をざっと書いていきます。

要約:素で作るよりも書きやすい&ビルド時の辛みが解消できる。

  • TypeScriptに対応
  • React、Vue 3、Svelte、SolidJSに対応
  • HMRあり
  • エントリーポイントは指定のディレクトリ以下に配置(後述)
    • どこに何の機能を書くのかはっきりしている
  • 多くのブラウザをサポート
    • ChromeやEdge、Firefox、Safariも!
    • もちろんChromium系のブラウザ(=VivaldiやArc Browserなど)も対応
  • ビルドツールはVite
  • zip化やデプロイも可能
    • GitHub Actionの例も載っている

対応しているフロントエンドフレームワークが多く、無料です。開発も活発なようで安心です。

WXTのインストール

公式ドキュメントでは、ご丁寧にpnpm、npm、bunの3つからテンプレートを作成できる例があります。筆者はpnpmで作りました。

Terminal window
pnpm dlx wxt@latest init wxt-sample

実行するといくつか質問されるので選定に合わせて回答し、指示どおりインストールします。

ℹ Initalizing new project
✔ Choose a template › svelte
✔ Package Manager › pnpm
✔ Downloading template
✨ WXT project created with the svelte template.
Next steps:
1. cd wxt-sample
2. pnpm install

素のTypeScript、Vue 3、React、SolidJS、Svelteのテンプレートから選べます。

テンプレート選び

パッケージマネージャーも選べます。ここ数年で選択肢が増えてきたのでありがたいですね。

パッケージマネージャー選び

scripts

package.jsonにscriptsが用意されています。

package.json
"scripts": {
"dev": "wxt",
"dev:firefox": "wxt -b firefox",
"build": "wxt build",
"build:firefox": "wxt build -b firefox",
"zip": "wxt zip",
"zip:firefox": "wxt zip -b firefox",
"check": "svelte-check --tsconfig ./tsconfig.json",
"postinstall": "wxt prepare"
},

開発時はdev、ビルドはbuildといった具合です。zip化もコマンドで一発なのはうれしいですね

たとえばpnpmで開発するなら次のコマンドです。

Terminal window
pnpm run dev

いちいち入力するのが面倒な人向けに、fzfを使った効率化の記事を書きました。よければどうぞ。
fzfでpackage.jsonのscriptsを選んで実行

ディレクトリ構成

初期のディレクトリ構成を見てみましょう。特徴的な部分をハイライトしています。

|-- .gitignore
|-- .vscode
| `-- extensions.json
|-- .wxt
| `-- ...
|-- README.md
|-- package.json
|-- pnpm-lock.yaml
|-- src
| |-- assets
| | `-- ...
| |-- entrypoints
| | `-- ...
| |-- lib
| | `-- ...
| `-- public
| | `-- ...
|-- tsconfig.json
|-- wxt-env.d.ts
`-- wxt.config.ts

次の項目から、省略している箇所も含めて個別に解説します。

wxt.config.ts

WXTでは設定をwxt.config.tsに書きます。Svelteの場合、最初は次のようになっています。

wxt.config.ts
import { defineConfig } from 'wxt';
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte';
// See https://wxt.dev/api/config.html
export default defineConfig({
srcDir: 'src',
vite: () => ({
plugins: [
svelte({
// Using a svelte.config.js file causes a segmentation fault when importing the file
configFile: false,
preprocess: [vitePreprocess()],
}),
],
}),
});

vite

見てのとおりvite.config.tsに書く内容はwxt.config.tsviteに書きます。

たとえばimportのエイリアスを使いたいなら次のように設定します。

wxt.config.ts
export default defineConfig({
// ...
vite: () => ({
resolve: {
alias: {
$lib: path.resolve("./src/lib"),
},
},
// ...
}),
});

manifest

manifest.jsonの中身はwxt.config.tsに書きます。型が効いているので書きやすいです。

wxt.config.ts
export default defineConfig({
manifest: {
name: "拡張機能の名前~",
description: "拡張機能の説明~",
permissions: ["tabs"],
},
srcDir: 'src',
// ...
});

tabsscriptingを使う人は注意です。
開発中はリロードの都合上permissionstabsscriptingが自動で追加されます。
自動で追加されるがゆえに書き忘れると、ビルド時に「動かないぞ!」となります。tabsscriptingが必要なら明示的に指定するのを忘れないようにしましょう。

src

メインとなるのがsrcディレクトリで、特に特徴的なのがentrypointsです。

|-- src
| |-- assets
| | `-- svelte.svg
| |-- entrypoints
| | |-- background.ts
| | |-- content.ts
| | `-- popup
| | |-- App.svelte
| | |-- app.css
| | |-- index.html
| | `-- main.ts
| |-- lib
| | `-- Counter.svelte
| `-- public
| |-- icon
| | |-- 128.png
| | |-- 16.png
| | |-- 32.png
| | |-- 48.png
| | `-- 96.png
| `-- wxt.svg

entrypoints

WXTでは、Popup、Background、Content Scriptsのような機能ごとにエントリーポイントを分けます。

エントリーポイントは次のようにディレクトリを切っても切らなくてもよいです。

  • entrypoints/background/index.ts
  • entrypoints/background.ts

テンプレートにはよく使われるPopup、Background、Content Scriptsが用意されています。もちろんブックマークやオプション画面のほか、サイドパネルなど一通り用意されています。

publicとassets

画像などは基本assetsディレクトリ、次のような条件を満たすのであればpublicディレクトリに入れます。

  • ソースコードから参照されない
    • 例:拡張機能のアイコン
  • ファイル名を変更されたくない(ハッシュ化されたくない)

publicに入れたファイルはビルドするとそのままコピーされます。それ以外のファイルはViteを通してバンドルされる、ということですね。

.wxt

.wxtはWXTで用意した型情報などがあります。基本的に開発者が触ることはないと思うのでスルーで問題ありません。.gitignoreにも指定されています。

.output

ビルドすると.outputの下にchrome-mv3のようなディレクトリが作成されます。デフォルトはChromeですが、その他のブラウザにも対応しています

|-- .output
| `-- chrome-mv3
| |-- background.js
| |-- chunks
| | `-- popup-Cx96etly.js
| |-- content-scripts
| | `-- content.js
| |-- icon
| | |-- 128.png
| | |-- 16.png
| | |-- 32.png
| | |-- 48.png
| | `-- 96.png
| |-- manifest.json
| |-- popup.html
| `-- wxt.svg

WXTは開発中に専用のブラウザが立ち上がるようですが、筆者の環境は未対応のWSLなので未確認です。便利そうだなぁ。
そんなわけで開発用のブラウザが立ち上がらない諸君は.output/chrome-mv3のようなディレクトリを手動で読み込みましょう。

Svelteを使うなら

拡張機能には関係ありませんが、デフォルトのままだとテキストエディタのLSPで次のようなエラーがログファイルに残りました。

No svelte.config.js found.

これはSvelteの設定をwxt.config.tsで書いているためです。

wxt.config.ts
// ...
vite: () => ({
plugins: [
svelte({
// Using a svelte.config.js file causes a segmentation fault when importing the file
configFile: false,
preprocess: [vitePreprocess()],
}),
],
}),

そんなわけでsvelte.config.jsを用意してあげます。上記のコメントではsegmentationについて書かれていますが特にエラーは出ませんでした(要調査)。

svelte.config.mjs
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
const config = {
preprocess: [vitePreprocess()],
};
export default config;
wxt.config.ts
import { defineConfig } from "wxt";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import path from "node:path";
export default defineConfig({
// ...
vite: () => ({
plugins: [
svelte({
configFile: path.resolve("./svelte.config.mjs"),
}),
],
}),
});

Content ScriptsでSvelteを使う例も公式ドキュメントにいろいろ書いてあります。実用的な書き方は後日記事にしようかなと考えてます。


実際に作った拡張機能は現在Chrome Web Storeの審査の準備中です。通ったら諸々のリンクを貼る予定です。

WXTって何の略だろう。Web Extensionの略っぽいです。公式ドキュメントREADMEを見ると、Nuxtベースだよ、Nuxtみたいに~と書いてあるのでそこと引っ掛けた部分もあるかもしれませんね。

WXT is based of Nuxt,
“It’s like Nuxt, but for Chrome Extensions”

他の拡張機能のフレームワークはSvelteに対応していなかったり部分的に有料であるため、WXTに感謝です。