Chrome拡張機能を作成&開発
プロジェクト作成
npx create chrome-ext
からのプロジェクト作成を行いました。
create chrome-ext
というコマンドは辿っていくと以下のリポジトリにたどり着きます。
- 著名なフロントエンドフレームワーク(React、Vue、Svelte、他多数)&Viteのビルドを想定したプロジェクトが作成できる(バニラも選択可)
- デフォルトの状態でコンテンツスクリプト、ポップアップ、バックグラウンド、サイドパネル等の機能が有効化されたテンプレートとなっている(Vueのテンプレートでのみ確認)
今回はdemo-chrome-ext
という名前でVue.jsを使用したChrome拡張を作成していきます
CLIからプロジェクト作成
$ npx create chrome-ext demo-chrome-ext
√ Framework: » vue
√ Language: » vue-ts
Scaffolding project in C:\workspace_browser_ext\demo-chrome-ext...
Done. Now run:
cd demo-chrome-ext
npm install
npm run dev
Suggest you next step:
1. cd demo-chrome-ext
2. Run npm install
3. Open chrome://extensions/ in your browser
4. Check the box for Developer mode in the top right.
5. Click the Load unpacked extension button.
6. Select the build/ directory that was created.
以下のような形で使用するフレームワークとJavaScript or TypeScriptの選択が行えました。
今回はvue
⇒vue-ts
と選択。
プロジェクト作成時のコンソール出力の内容に従い作成したプロジェクトのディレクトリに移動&npmで管理するパッケージをインストール
$ cd demo-chrome-ext
$ npm install
ひとまずViteの開発サーバーを起動するnpmスクリプトが実行できることを確認
$ npm run dev
> demo-chrome-ext@0.0.0 dev
> vite
VITE v4.5.3 ready in 711 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h to show help
The emitted file "img/logo-34.png" overwrites a previously emitted file of the same name.
The emitted file "img/logo-48.png" overwrites a previously emitted file of the same name.
The emitted file "img/logo-128.png" overwrites a previously emitted file of the same name.
The emitted file "img/logo-16.png" overwrites a previously emitted file of the same name.
パッケージ最新化
npmで管理しているパッケージは全体的にやや古い状態だったので以下のコマンドで最新のものを入れ直しました。
6件見つかった脆弱性に関するアラートが消えました。
$ npm install vue@latest
up to date, audited 479 packages in 1s
43 packages are looking for funding
run `npm fund` for details
6 high severity vulnerabilities
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
$ npm install -D @crxjs/vite-plugin@^2.0.0-beta.23 @types/chrome@latest @vitejs/plugin-vue@latest gulp@latest gulp-zip@latest prettier typescript vite@latest vue-tsc@latest
added 25 packages, removed 256 packages, changed 71 packages, and audited 248 packages in 19s
39 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
package.jsonのアップデート前後の差分は以下の通りとなりました。
{
"name": "demo-chrome-ext",
"displayName": "demo-chrome-ext",
"version": "0.0.0",
"author": "**",
"description": "",
"type": "module",
"license": "MIT",
"keywords": [
"chrome-extension",
"vue",
"vite",
"create-chrome-ext"
],
"engines": {
"node": ">=14.18.0"
},
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"fmt": "prettier --write '**/*.{vue,ts,json,css,scss,md}'",
"zip": "npm run build && node src/zip.js"
},
"dependencies": {
- "vue": "^3.3.4"
+ "vue": "^3.4.27"
},
"devDependencies": {
- "@crxjs/vite-plugin": "^2.0.0-beta.19",
- "@types/chrome": "^0.0.246",
- "@vitejs/plugin-vue": "^4.4.0",
- "gulp": "^4.0.2",
+ "@crxjs/vite-plugin": "^2.0.0-beta.23",
+ "@types/chrome": "^0.0.268",
+ "@vitejs/plugin-vue": "^5.0.5",
+ "gulp": "^5.0.0",
"gulp-zip": "^6.0.0",
- "prettier": "^3.0.3",
- "typescript": "^5.2.2",
- "vite": "^4.4.11",
- "vue-tsc": "^1.8.18"
+ "prettier": "^3.3.1",
+ "typescript": "^5.4.5",
+ "vite": "^5.2.13",
+ "vue-tsc": "^2.0.21"
}
}
Chromeに開発中のChrome拡張をインストール
- Viteの開発サーバーを起動しておく
- Chromeで
chrome://extensions
にアクセス -
パッケージ化されていない拡張機能を読み込む
ボタンをクリック - ダイアログで開発中のChrome拡張のbuildディレクトリを指定
開発中のChrome拡張の動作確認
create chrome-ext
コマンドで作成したVue&TypeScriptのテンプレートではChrome拡張の各機能間で共有可能なカウントアップが行えるような拡張機能となっていました。
POPUP PAGEというポップアップが表示できることを確認
インストールしたChrome拡張のService Worker
をクリックして表示されたdevtoolでカウントアップ時のコンソール出力を確認
Chrome拡張のボタンを右クリック > オプション
からオプションページが表示できることを確認
Chrome拡張のボタンを右クリック > サイドパネルを開く
からサイドパネルが表示できることを確認
新規タブ追加時のページがデフォルトの状態から上書きされていることを確認
ボイラープレートから不要な機能を取り除く
create chrome-ext
コマンドで作成したVue&TypeScriptのボイラープレートに不要な機能がある場合の除外手順をまとめてみました
新規タブ追加時の画面が不要な場合
手順
不要なファイルを削除
$ rm -fr newtab.html src/newtab
src/manifest.tsを編集
import { defineManifest } from '@crxjs/vite-plugin'
import packageData from '../package.json'
//@ts-ignore
const isDev = process.env.NODE_ENV == 'development'
export default defineManifest({
name: `${packageData.displayName || packageData.name}${isDev ? ` ➡️ Dev` : ''}`,
description: packageData.description,
version: packageData.version,
manifest_version: 3,
icons: {
16: 'img/logo-16.png',
32: 'img/logo-34.png',
48: 'img/logo-48.png',
128: 'img/logo-128.png',
},
action: {
default_popup: 'popup.html',
default_icon: 'img/logo-48.png',
},
options_page: 'options.html',
devtools_page: 'devtools.html',
background: {
service_worker: 'src/background/index.ts',
type: 'module',
},
content_scripts: [
{
matches: ['http://*/*', 'https://*/*'],
js: ['src/contentScript/index.ts'],
},
],
side_panel: {
default_path: 'sidepanel.html',
},
web_accessible_resources: [
{
resources: ['img/logo-16.png', 'img/logo-34.png', 'img/logo-48.png', 'img/logo-128.png'],
matches: [],
},
],
permissions: ['sidePanel', 'storage'],
- chrome_url_overrides: {
- newtab: 'newtab.html',
- },
})
(Git管理している場合)VSCodeで見た差分
ポップアップが不要な場合
手順
不要なファイルを削除
$ rm -fr popup.html src/popup
src/manifest.tsを編集
import { defineManifest } from '@crxjs/vite-plugin'
import packageData from '../package.json'
//@ts-ignore
const isDev = process.env.NODE_ENV == 'development'
export default defineManifest({
name: `${packageData.displayName || packageData.name}${isDev ? ` ➡️ Dev` : ''}`,
description: packageData.description,
version: packageData.version,
manifest_version: 3,
icons: {
16: 'img/logo-16.png',
32: 'img/logo-34.png',
48: 'img/logo-48.png',
128: 'img/logo-128.png',
},
- action: {
- default_popup: 'popup.html',
- default_icon: 'img/logo-48.png',
- },
options_page: 'options.html',
devtools_page: 'devtools.html',
background: {
service_worker: 'src/background/index.ts',
type: 'module',
},
content_scripts: [
{
matches: ['http://*/*', 'https://*/*'],
js: ['src/contentScript/index.ts'],
},
],
side_panel: {
default_path: 'sidepanel.html',
},
web_accessible_resources: [
{
resources: ['img/logo-16.png', 'img/logo-34.png', 'img/logo-48.png', 'img/logo-128.png'],
matches: [],
},
],
permissions: ['sidePanel', 'storage'],
chrome_url_overrides: {
newtab: 'newtab.html',
},
})
(Git管理している場合)VSCodeで見た差分
オプション画面が不要な場合
手順
不要なファイルを削除
$ rm -fr options.html src/options
src/manifest.tsを編集
import { defineManifest } from '@crxjs/vite-plugin'
import packageData from '../package.json'
//@ts-ignore
const isDev = process.env.NODE_ENV == 'development'
export default defineManifest({
name: `${packageData.displayName || packageData.name}${isDev ? ` ➡️ Dev` : ''}`,
description: packageData.description,
version: packageData.version,
manifest_version: 3,
icons: {
16: 'img/logo-16.png',
32: 'img/logo-34.png',
48: 'img/logo-48.png',
128: 'img/logo-128.png',
},
action: {
default_popup: 'popup.html',
default_icon: 'img/logo-48.png',
},
- options_page: 'options.html',
devtools_page: 'devtools.html',
background: {
service_worker: 'src/background/index.ts',
type: 'module',
},
content_scripts: [
{
matches: ['http://*/*', 'https://*/*'],
js: ['src/contentScript/index.ts'],
},
],
side_panel: {
default_path: 'sidepanel.html',
},
web_accessible_resources: [
{
resources: ['img/logo-16.png', 'img/logo-34.png', 'img/logo-48.png', 'img/logo-128.png'],
matches: [],
},
],
permissions: ['sidePanel', 'storage'],
chrome_url_overrides: {
newtab: 'newtab.html',
},
})
(Git管理している場合)VSCodeで見た差分
サイドパネルが不要な場合
手順
不要なファイルを削除
$ rm -fr sidepanel.html src/sidePanel
src/manifest.tsを編集
import { defineManifest } from '@crxjs/vite-plugin'
import packageData from '../package.json'
//@ts-ignore
const isDev = process.env.NODE_ENV == 'development'
export default defineManifest({
name: `${packageData.displayName || packageData.name}${isDev ? ` ➡️ Dev` : ''}`,
description: packageData.description,
version: packageData.version,
manifest_version: 3,
icons: {
16: 'img/logo-16.png',
32: 'img/logo-34.png',
48: 'img/logo-48.png',
128: 'img/logo-128.png',
},
action: {
default_popup: 'popup.html',
default_icon: 'img/logo-48.png',
},
options_page: 'options.html',
devtools_page: 'devtools.html',
background: {
service_worker: 'src/background/index.ts',
type: 'module',
},
content_scripts: [
{
matches: ['http://*/*', 'https://*/*'],
js: ['src/contentScript/index.ts'],
},
],
- side_panel: {
- default_path: 'sidepanel.html',
- },
web_accessible_resources: [
{
resources: ['img/logo-16.png', 'img/logo-34.png', 'img/logo-48.png', 'img/logo-128.png'],
matches: [],
},
],
- permissions: ['sidePanel', 'storage'],
+ permissions: ['storage'],
chrome_url_overrides: {
newtab: 'newtab.html',
},
})
(Git管理している場合)VSCodeで見た差分
タグ作成をトリガーとしたGitHub Actionsのワークフローを作成
目的達成に至るまでの備忘録
- vite buildの成果物のアップロード先としてGitHubのリリースを作成しAssetsにアップロードするワークフローを作る必要がある
-
actions/create-releaseアクションでGitHubのリリースを作成しactions/upload-release-assetアクションでリリースのAssetsにファイルをアップロードできそう
-
actions/create-release
、actions/upload-release-asset
の両アクションについて公式のドキュメントを確認したところリポジトリがアーカイブされていた。現在は以下のいずれかのアクションを使用するのが良いとのこと(actions/create-releaseのREADME.mdより) - 上記4アクションのうちactions/upload-release-assetアクションのREADME.mdでも
softprops/action-gh-release
アクションに関する言及があること、4リポジトリの中で最もスター数が多いことなどから今回はsoftprops/action-gh-release
アクションを利用することとしました
-
-
softprops/action-gh-release
アクションについて調べ、リリースの作成とAssetsへのアップロードについて1つのアクションで実行できることを確認
作成したワークフローは以下のようになりました。
name: Create Release
on:
push:
tags:
- "*"
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Build
run: |
npm install
npm run build
mv build demo-chrome-ext
zip -r demo-chrome-ext-${{ github.ref_name }}.zip demo-chrome-ext
- name: Release
uses: softprops/action-gh-release@v2
with:
files: demo-chrome-ext-${{ github.ref_name }}.zip
generate_release_notes: true
-
softprops/action-gh-release
アクションのREADMEにも書いてありますがワークフローを実行するのにcontents: write
の権限が必要です -
generate_release_notes: true
オプションを指定することでリリースノートについて自動生成しています -
${{ github.ref_name }}
はタグ作成をトリガーとしているため0.0.1
のようなバージョンが入ります
リリースノート自動生成用のカテゴリ設定
以下のようなrelease.ymlを作成
changelog:
categories:
- title: 🐛 Bug Fixes
labels:
- bug
- title: 📚 Documentation
labels:
- documentation
- title: 🚫 Duplicates
labels:
- duplicate
- title: 🚀 Enhancements
labels:
- enhancement
- title: 👶 Good First Issues
labels:
- good first issue
- title: 🙋 Help Wanted
labels:
- help wanted
- title: 🚷 Invalid
labels:
- invalid
- title: ❓ Questions
labels:
- question
- title: ⛔ Won't Fix
labels:
- wontfix
- title: Others
labels:
- "*"
-
GitHubリポジトリにデフォルトで設定されている9つのラベルに対応するrelease.ymlを作成しました(大枠はAIに出力してもらった)
- 意味を理解してないラベルがある...
- 必ずしもデフォルトのまま使い続ける必要はないはず。不要なものは削除しても良いかも
- デフォルト以外のラベルのPRは全てOthersというカテゴリに含まれるような設定としています
ワークフローの動作確認
ローカルでタグを作成してリモートにプッシュ(最新のmainブランチにいる状態で以下実施
)
$ git tag 0.0.1
$ git push origin 0.0.1
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/imo-tikuwa/demo-chrome-ext.git
* [new tag] 0.0.1 -> 0.0.1
タグ作成をトリガーとしたワークフローが走ったことを確認
ワークフローの実行によって生成されたリリース内にリリースノートが生えてることを確認
- リリースノートの自動生成について
.github/release.yml
で設定したカテゴリごとにPRがまとめられていることを確認 - vite buildによって出来上がったChrome拡張機能のコード一式を固めたzipファイルをAssets欄にアップロードできていることを確認
備考
- 今回は手動によるタグ作成をワークフローのトリガーとしていますが、ストアへの公開を目標とした正式なChrome拡張を開発する場合はセマンテックバージョニングのルールに基づいたバージョン管理を行える必要があると思います
- 記事の中で書いたVue&TypeScriptのテンプレートではpackage.jsonのversionに記載した値をChrome拡張のバージョンとして設定するようになっているため、前段としてpackage.jsonのバージョンを書き換えるようなワークフローを組めると良いかなと思いました
今回の記事の中で実際に作成したデモのChrome拡張は以下のリポジトリで公開しています
参考サイト
-
Google Chrome 拡張機能 / Firefox アドオン作成ボイラープレート 2024 - なつねこメモ
- Chrome拡張のボイラープレートを探していたときに読んだページ
-
自動生成リリース ノート - GitHub Docs
- リリースノートの自動生成について調べたときに読んだページ(公式)
-
ラベルを管理する - GitHub Docs
- 9つのデフォルトラベルについて意味を調べたときに読んだページ(公式)