このWebサイトのココをちょっと直したい…そんな思いからChrome拡張の開発を始めました。 ただやっぱりJavaScriptよりTypeScriptが書きたいと思い、環境を構築してみました。
動作環境
- OS: Windows 11 Home 21H2
$ docker --version Docker version 20.10.14, build a224086 $ docker-compose --version docker-compose version 1.29.2, build 5becea4c
GitHub
当記事で紹介する開発環境のテンプレートを公開してます。ご自由にお使いください!
ファイル構成
今回、最終的に下記のファイル構成になるよう作成をしていきます。
$ tree . |-- docker-compose.yml |-- node_modules |-- package-lock.json |-- package.json |-- public | |-- image | | `-- icon.png | |-- manifest.json | `-- style | `-- styles.css |-- src | |-- background.ts | `-- main.ts |-- tsconfig.json `-- webpack.config.js
webpack、TypeScriptを使いたいので設定ファイルとしてwebpack.config.js、tsconfig.jsonを用意します。また、npm start
でwebpackによるビルド、ファイル変更監視を実現できるよう、package.jsonにも一部追記をします。
Dockerコンテナ内でNode.jsを動かす構成にしていますが、必須ではないのでお好みでどうぞ。
開発環境の準備
docker-compose.yml
下記のdocker-compose.ymlを作成し、コンテナ内でNode.jsを動作させるようにします。
version: '3' services: node: image: node:18.2 working_dir: /chrome_extention volumes: - .:/chrome_extention tty: true
package.json
package.jsonを作成し、開発に必要なパッケージをdevDependencies
に記述しておきます。
- Chromeの型定義
- "@types/chrome"
- Webpack用
- "copy-webpack-plugin"
- "webpack"
- "webpack-cli"
- TypeSciprt用
- "ts-loader"
- "typescript"
{ "name": "chrome extention", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "webpack --watch --mode=development", "build": "webpack --mode=production", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@types/chrome": "^0.0.188", "copy-webpack-plugin": "^11.0.0", "ts-loader": "^9.3.0", "typescript": "^4.6.4", "webpack": "^5.72.1", "webpack-cli": "^4.9.2" } }
記述できたら、docker-compose up -d
でDockerコンテナを立ち上げ、docker-compose exec node bash
でコンテナに入ってからnpm install
します。
scripts
にはnpmコマンドでwebpackを動作させられるよう以下を記述しました。
"start": "webpack --watch --mode=development"
--watch
を付与することでwebpackでビルド後にファイル変更があれば再ビルドさせます。デバッグ時にいちいちビルドのコマンドを叩くのが辛いので。
"build": "webpack --mode=production"
- webpackでビルドする。本番リリース用。
tsconfig.json
TypeScriptの設定ファイルであるtsconfig.jsonを追加します。
{ "compilerOptions": { "target": "es6", "module": "commonjs", "strict": true, "rootDir": "src", "esModuleInterop": true, "typeRoots": [ "node_modules/@types"] }, "exclude": [ "node_modules" ] }
webpack.config.js
webpack.config.jsを作成し、webpackの各種設定を記述します。
const path = require("path"); const CopyPlugin = require("copy-webpack-plugin"); module.exports = { mode: process.env.NODE_ENV || "development", entry: { background: path.join(__dirname, "src/background.ts"), main: path.join(__dirname, "src/main.ts"), }, output: { path: path.join(__dirname, "dist/js"), filename: "[name].js", }, module: { rules: [ { test: /\.ts$/, use: "ts-loader", exclude: /node_modules/, }, ], }, resolve: { extensions: [".ts", ".js"], }, plugins: [ new CopyPlugin({ patterns: [ { from: ".", to: "../", context: "public" } ] }) ], devtool: 'cheap-module-source-map', cache: true, watchOptions:{ poll: true, } };
entry
にトランスパイル対象のtsファイルを記述し、output
にトランスパイルされたjsファイルのパスとファイル名を指定しています。
ビルド後のdistディレクトリ内にtsファイル以外のファイルを含めたいので、plugins
にnew CopyPlugin(...)
を記述しています。これでpublicディレクトリの中身がdistにコピーされます。
devtool: 'cheap-module-source-map'
で、トランスパイルされるjsのソースマッピングの方式を指定しています。
何も指定しない場合、トランスパイルされたjsにeval()
が含まれてしまい、Chromeのデバッグ時に下記エラーが発生し正しく動作しません。
Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' blob: filesystem: chrome-extension-resource:".
watchOptions.poll
をtrueに指定しているのは、WSLでDockerコンテナを動作させたときにwebpackのファイル監視がうまく動作しなかった為です。
Watch and WatchOptions | webpack
Chrome拡張の開発
ここまででTypeScriptとWebpackを動作させる環境は用意できたので、あとはChrome拡張に関する設定(manifest.json)とビジネスロジックを書いたtsファイル、css、アイコン画像等を用意します。 なお、ts, css, アイコン画像についてはここでは詳細を解説しません。
ファイル構成に書いたようにsrcディレクトリ以下にtsファイル、publicディレクトリ以下にアイコン画像、css、manifest.jsonを配置します。
manifest.json
manifest.jsonを作成し、Chrome拡張に関する設定を定義します。ここで記述している以外にもパラメータがあり、実装内容によって適宜変えていく必要があります。 実はまだこの部分は勉強中です…😓
なお、manifest.jsonにはバージョンがあり、V2とV3で大きく仕様の変化があったようです。以下は投稿時点での最新であるV3の記述方法であることにご注意ください。
{ "name": "My Chrome Extention", "description": "this is my extention.", "version": "0.1", "manifest_version": 3, "icons": { "128": "image/icon.png" }, "permissions": [ "activeTab", "scripting", "storage", "declarativeNetRequest", "declarativeNetRequestFeedback", "cookies" ], "action": { "default_icon": "image/icon.png", "default_title": "my-chrome-extention", "default_popup": "popup.html" }, "background": { "service_worker": "js/background.js" }, "content_scripts": [{ "matches": [ "https://hogefuga.com/*" ], "js": [ "js/main.js" ], "css": [ "style/styles.css" ], "run_at": "document_end" }] }
permissions
では開発するChrome拡張で許可する機能を記述します。
- 例えばCookie関連のAPIである
chrome.cookie
を使用したい場合にはpermissions
に"cookies"
を追加しなければエラーにより動作しません。 - API毎に指定すべきpermissionsは、下記ページに列挙されているAPIの詳細ページにて確認できます。
icons
でchrome://extensionsに表示されるアイコン画像のパスを指定しています。
action
でChromeツールバーの拡張機能アイコンの設定を指定しています。
action.default_icon
で拡張機能アイコンの画像パスを指定しています。アドレスバーの右横に表示されるアイコンのことですね。action.default_title
は拡張機能アイコンにマウスを乗せた際に表示されるツールチップの文字列です。action.default_popup
は拡張機能アイコンをクリックした際に表示するhtmlです。
content_scripts
で特定のURLを開いた際に適用するjs, cssを定義しています。
matches
には適用条件となるURLを指定します。URLが一致した場合にjs, cssが適用されます。アスタリスクはワイルドカードとして使用可能です。js
は適用するjsファイル、css
は適用するcssファイルです。run_at
は適用タイミングで、document_end
は「DOMが構築され、画像やフレームなどのサブリソースがロードされる前のタイミングで適用する」ことを指します。他にも種類があるので詳細は下記を参照ください。
background.service_worker
ではバックグラウンドで動作させたいjsファイルを指定しています。今回はトランスパイル後のjsファイルのことを指します。
Chrome拡張ではservice_worker
上でしか動作しないコードが存在する為、これを使わざるを得ないケースがあります。
tsファイル
Chrome拡張では専用のAPIが用意されていますので、下記APIリファレンス等も参考になると思います。
API Reference - Chrome Developers
cssファイル
既にページ側で定義されているCSSプロパティをChrome拡張側で上書きしたい場合には、!important
を付与して適用する優先順位を変えてあげる必要があります。
アイコン画像
良い感じのアイコンを用意してあげましょう!
manifest.jsonではpngを指定しましたが、svgでもOKなようです。サイズ別に指定も可能です。詳細は以下をご覧ください。
Manifest - Icons - Chrome Developers
browser_action - Mozilla | MDN
Chromeで動作確認する
ビルド
Dockerコンテナ内でnpm start
またはnpm run build
しましょう。
distディレクトリ内に各種ファイルが出力されるはずです。
さくっとデバッグしたい時はファイル変更時の自動ビルドが効くnpm start
がおすすめです。
拡張機能の追加
Chromeでアドレスバーにchrome://extensionsを入れ、拡張機能の画面を表示します。
「デベロッパーモード」をONにし、「パッケージ化されていない拡張機能を読み込む」を選択してdistディレクトリを選択します。
自分の作った拡張機能がリストに追加されればあとは実際にWebページを開いて動作確認するだけです。
ソースコードを変更した時
ソースコード変更時のdistディレクトリへのビルドはwebpackがやってくれますが、変更されたdistディレクトリのChromeへの適用は手動でやる必要があります。
ビルド完了後、chrome://extensionsで更新ボタンをクリックすると、最新のdistディレクトリがロードされます。ちょっと面倒ですが…🤤
※スクリーンショットの拡張機能はサンプルです。余談ですがこれはYouTube Liveで画面上にコメントが流れるという便利な機能で個人的におすすめです。 GitHub - fiahfy/youtube-live-chat-flow: Chrome Extension for Flow Chat Messages on YouTube Live.
標準出力が見たい時
manifest.jsonでcontent_scripts
に指定したjsファイルの標準出力は、通常の開発者ツールのConsoleで確認可能です。
manifest.jsonでbackground.service_worker
に指定したjsファイルの場合は少し特殊で、chrome://extensionsの「Service Worker」というリンクをクリックすることでService Worker用のConsoleを確認できます。
また、拡張機能読み込み時のエラーが発生した場合は、「エラー」ボタンが表示されるのでクリックして確認が可能です。