TypeScript + Vite で Chrome 拡張機能を作る
この記事は TypeScript Advent Calendar 2022 の19日目です。
今年、簡単な Chrome 拡張機能を開発した。
開発初期は公式チュートリアルに従って JavaScript で開発していたが、 CRXJS という Vite プラグインを使えば TypeScript のコードを簡単に Chrome 拡張機能用にビルドできるということを後から知り、コードの書き換えを行った。次の開発時はこの方法を最初から使えるように、 TypeScript + Vite を使った開発手順について改めてまとめる。
前提
- 今回は最新である Manifest V3 のチュートリアルの内容に合わせた
- 以下の点については特に触れない
- Vite の基本的な使い方
- Chrome 拡張機能の公開申請
- 環境は以下の通り
- MacOS 13.0.1
- Node.js 18.12.1
なお今回書いたコードは以下のリポジトリから確認できる。
インストール
以下のパッケージをインストールする。
$ yarn add --dev typescript vite@^2.9.4 @crxjs/vite-plugin @types/chrome
TypeScript と Vite 以外のパッケージについて補足。
-
@crxjs/vite-plugin
- Chrome 拡張機能向けにビルドしてくれる Vite プラグイン
-
@types/chrome
- Chrome Extension API の型定義
実装
package.json には Vite のビルドのスクリプトを書き足しておく。
{
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"preview": "vite preview"
},
"devDependencies": {
"@crxjs/vite-plugin": "^1.0.14",
"@types/chrome": "^0.0.204",
"typescript": "^4.9.4",
"vite": "^2.9.4"
}
}
tsconfig.json は以下の通り。大抵の拡張機能の処理は DOM 操作を伴うため、 lib
に DOM
も指定しておいた方が良い。
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"strict": true
},
"include": ["scripts"]
}
vite.config.ts にはビルドの設定に加え、通常は manifest.json (Chrome 拡張機能に関する情報を記述したファイル)に書かれる情報を記述する。 manifest.json ファイルをインポートすることもできるが、 vite.config.ts に記述すると型補完が効くのでオススメ。
import { defineConfig } from 'vite'
import { crx, defineManifest } from '@crxjs/vite-plugin'
const manifest = defineManifest({
manifest_version: 3,
name: 'Reading time',
description: 'Add the reading time to Chrome Extension documentation articles',
version: '1.0',
icons: {
'16': 'images/icon-16.png',
'32': 'images/icon-32.png',
'48': 'images/icon-48.png',
'128': 'images/icon-128.png',
},
content_scripts: [
{
js: ['scripts/content.ts'], // 拡張子を .ts に変更する
matches: [
'https://developer.chrome.com/docs/extensions/*',
'https://developer.chrome.com/docs/webstore/*',
]
}
],
})
export default defineConfig({
plugins: [crx({ manifest })],
})
後はチュートリアルに従ってファイルを追加すると、次のようなファイル構造になる。
$ tree -I 'node_modules'
.
├── images
│ ├── icon-128.png
│ ├── icon-16.png
│ ├── icon-32.png
│ └── icon-48.png
├── package.json
├── scripts
│ └── content.ts
├── tsconfig.json
├── vite.config.ts
└── yarn.lock
2 directories, 10 files
content.ts の内容は以下の通り。 TypeScript の型エラーが出る箇所だけ(雑に)修正している。
const article = document.querySelector("article");
// `document.querySelector` may return null if the selector doesn't match anything.
if (article) {
const text = article.textContent;
const wordMatchRegExp = /[^\s]+/g; // Regular expression
const words = text!.matchAll(wordMatchRegExp);
// matchAll returns an iterator, convert to array to get word count
const wordCount = [...words].length;
const readingTime = Math.round(wordCount / 200);
const badge = document.createElement("p");
// Use the same styling as the publish information in an article's header
badge.classList.add("color-secondary-text", "type--caption");
badge.textContent = `⏱️ ${readingTime} min read`;
// Support for API reference docs
const heading = article.querySelector("h1");
// Support for article docs with date
const date = article.querySelector("time")?.parentNode;
((date ?? heading) as Element).insertAdjacentElement("afterend", badge);
}
ビルドと実行
開発時は yarn dev
を実行すると、 dist
ディレクトリ下に manifest.json やスクリプト類が生成される。開発中の拡張機能は Loading an unpacked extension に従ってブラウザで実行できる。この手順の Load Unpacked ボタンを押すステップで dist ディレクトリを指定する。
ブラウザに読み込まれた拡張機能
Chrome 拡張機能の公式ページに N min read
が表示される
ストアに公開する際は yarn build
でビルドし、 dist ディレクトリ以下を zip で固めてアップロードする。
Discussion