Chrome拡張機能「Amazing Searcher」を作った時の備忘録
2022/07/14追記
改良版のChoomameについての記事も書いています。ぜひご覧ください。
概要
Googleの検索結果に期間や言語の絞り込みボタン、検索ワードに関連したリンクを表示するChrome拡張機能「Amazing Searcher」を作りました。この記事はその際の備忘録です。
↓公開先
↓紹介編
Manifest V3
以前作ったChrome拡張機能「Mr.Sagasu (紹介記事)」や「Mr.Jimaku (紹介記事)」は Manifest V3 の登場前だったため、Manifest V2で作成しましたが、今回作成した Amazing Searcher はV3です。
V3での変更点については、Manifest V3 への移行ガイド やManifest V3 チェックリスト を参考にしました。
自動リロード対応
background pages
が Service worker になった影響で、rubenspgcavalcante/webpack-extension-reloaderやxpl/crx-hotreloadといった、ファイル保存時に自動でビルドやリロードを行うプラグインが使えなくなりました(本当に便利だった…)。
そこで、自作プラグインを作りました。Service Worker そのものの機能を考えずにとりあえずWebSocket
で作ったため、折りたたみの中に簡単に書きます。
次に拡張機能を作るときには大胆に変更するかもしれません。
自動リロードについて
機能は以下です。
- 拡張機能の
content_scripts
かoptions_page
が開かれていれば、拡張機能のスクリプト変更時に拡張機能を再読み込み -
content_scripts
が開かれていれば、拡張機能再読み込み時に関連タブを自動リロード -
options_page
が開かれていれば、拡張機能再読み込み時にオプションページを自動で開く
options_page
は拡張機能再読み込み時に自動で閉じてしまうため、リロードではなく再び「開く」ことにしました。
extension-reloader.js
がプラグインとして読み込むファイルです。
content_scripts
, options_page
, background
それぞれのjsファイルに、WebSocketで通信するコードをビルド時に差し込みます。
画像を読み込みたいとき
拡張機能のフォルダに含まれている画像ファイルなどにアクセスする場合、web_accessible_resources
とchrome.extension.getURL
を使う必要があります。
V2までは "web_accessible_resources": ["imgs/*.png"]
のように書きました。V3からはアクセス範囲を限定するために、以下のようにresources
とmatches
をセットで書くようになりました。
"web_accessible_resources": [{
"resources": [ "hoge.png", "foo.png" ],
"matches": [ "https://example.com/*" ]
}],
パスの指定は"imgs/hoge.svg"
ではなく、chrome.extension.getURL("imgs/hoge.svg")
のように書きます。これを指定しないと、Chrome拡張機能のスクリプトが差し込まれる側のURLからリソースを探そうとしてしまいます。
最初はSVGを画像として読み込んでいたのでこの方法を使っていましたが、途中からはSVGをVueのコンポーネントとして読み込むことにしたため、現在のAmazing Searcherでこの方法は使っていません。
参考:Manifest - Web Accessible Resources - Chrome Developers
Webpackの設定
webpackの設定は、 共通のwebpack.common.js
, 本番用webpack.prod.js
, 開発用webpack.dev.js
の3つに分けました。
この構成は、Production | webpackに書かれているものです。
webpack.common.js
splitChunks
の設定
background
で指定するファイルは1つだけのため、他のエントリポイントと共通化をしないようchunks
を設定しました。
optimization: {
splitChunks: {
name: 'chunk',
chunks(chunk) {
return chunk.name !== 'background';
},
}
},
参考:webpack.js.org/split-chunks-plugin.md at master · webpack/webpack.js.org
webpack.dev.js
前述のManifest V3
の自動リロード対応
で書いた自作プラグインを読み込んでいます。
また、Webpackのビルドの表示を抑えるため、以下のようにstats
を設定しました。
stats: {
colors: true,
hash: false,
version: false,
timings: false,
assets: false,
chunks: false,
modules: false,
reasons: false,
children: false,
source: false,
publicPath: false
},
stats
に文字列を指定してプリセットを使用する方法もあります。
参考
webpack.prod.js
minifyの設定
こちらのブログによると、
Ordinary minification, on the other hand, typically speeds up code execution as it reduces code size, and is much more straightforward to review. Thus, minification will still be allowed, including the following techniques:
* Removal of whitespace, newlines, code comments, and block delimiters
* Shortening of variable and function names
* Collapsing the number of JavaScript files
と書かれており、以下がOKでそれ以外がだめなようです。
- 「ホワイトスペース、改行、コメント、ブロックを区切る文字」の削除
- 変数と関数の名前を短くすること
- ファイルの数を減らすこと
デフォルトでも問題ないですが、デフォルトがアップデート等で変わったら困るため、一応こちらのブログのようにterserを使ってascii_only
をfalse
に設定しました。
VueのCLIを使うときでもデフォルトで問題ないですが、自分で設定したい場合はVue側で既に設定している内容
const TerserPlugin = require('terser-webpack-plugin')
const terserOptions = require('./terserOptions')
webpackConfig.optimization
.minimizer('terser')
.use(TerserPlugin, [terserOptions(options)])
})
をいじるので、以下のようになると思います。
config.optimization.minimizer('terser').tap(args => {
const { terserOptions } = args[0];
terserOptions.output.ascii_only = false;
return args
}
参考
- webpack & Babel を使って Chrome 拡張機能を開発するためのテンプレート(Hot Reload 付き) - to-me-mo-rrow - 未来の自分に残すメモ -
- terser/terser Format options
- vuejs/vue-cli/base.js
Zipにする
erikdesjardins/zip-webpack-pluginを使い、ビルド時にZipファイルを生成するようにしました。
const ZipPlugin = require('zip-webpack-plugin');
// ...
plugins: [
new ZipPlugin({
filename: path.basename(__dirname) + ".zip",
pathPrefix: path.basename(__dirname)
}),
]
//...
content scripts
Tailwind CSS適用時にVueで差し込まれる側の要素の表示が崩れる問題
Amazing Searcher では、Googleの検索結果(https://www.google.com/search?*
)に当てはまるページへ content scripts で作ったものを差し込んでいます。そこで、差し込まれる側(Googleの検索結果)に差し込む側(拡張機能)のCSSが混じらないように、以下のようにprefixを付けることにしました。
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'postcss-prefix-selector': {prefix: '#amzSchRoot'}
},
}
#amzSchRoot
は差し込む側のルートのidです。
Tailwind CSSで訪問済みリンクの色を変える
visitedはTailwind CSSにもありますが、デフォルトではオフになっているため設定が必要です。
module.exports = {
variants: {
extend: {
textColor: ['visited'],
}
},
}
設定すれば、以下のように使えます(結局 Amazing Searcher では使いませんでした)。
<a class="text-blue-600 visited:text-purple-600">hoge</a>
参考:https://tailwindcss.com/docs/hover-focus-and-other-states#visited
createApp
の段階でデータを渡したい
第2引数に指定してあげれば良いです。
createApp(App, {nowURL: "hoge", paramTbm: "fuga"}).mount('#amzSchRoot');
options
chrome.storage.local.setに配列を保存&取り出し
chrome.storage.local.set({ key: array });
で配列を保存しても、chrome.storage.local.get(key、(hoge)=>{fuga});
するときには配列ではなくObjectとして取り出されてしまいます。
保存する値はJSON化できる必要があるため、JSON.stringify(array)
やJSON.parse(data)
を使う必要があります。
chrome.storage.local.get("terms", (result) => {
if (typeof result.terms !== "undefined") {
termRef.value.push(...JSON.parse(result.terms));
}
});
Vueでの値の受け渡しが悪いのかと思ってめちゃくちゃ時間食った部分でした。
参考
Vueでformの検証を使いたい
formだけではなく、イベント修飾子を使います。preventへ渡すonSubmit
でリアクティブなデータを処理することにしました。
<form @submit.prevent="onSubmit">
<!-- なにかinputタグ書く -->
<button type="submit">Add</button>
</form>
VueでNumberをpropsで渡したい
Javascriptの式であると伝えなければ文字列だと思われてしまうので、動的にv-bind
として渡す必要があります。
<blog-post :likes="42"></blog-post>
最後に
スクラップのまとめなんですが、もう少し文脈を入れて書くべきだったなぁと思いました。逆に、文脈を添えて書いたものはマークダウンをコピペして整えるだけなので楽でした。
Discussion
Manifest V3 で、自身もrubenspgcavalcante/webpack-extension-reloaderやxpl/crx-hotreloadのプラグインが使えなくで困っているのですが、掲載内容だと自身には、ちょっとわからなくて、すいません。
このWebSocketの、外部サイトに拡張する意味合いでCORSを超えれるのでしょうか?
それから、node.jsのようなWebSocketを受けるサーバサイドjsの仕組みが必要なのでしょうか?
kondomodoruさん
記事を読んでいただきありがとうございます。
まず、この記事を書いたときとは状況が違い、現在は Manfiest v3では CRXJS Vite Plugin を使ってみるのがいいかもしれません。
実際に、僕もいくつかの拡張機能ではCRXJS Vite Pluginを使っています。良ければ以下の記事もご覧ください。
node.jsのwsとブラウザのAPIのWebSocketの両方を使っていたと記憶しています。ただ、現在はこの記事の拡張機能の開発は停止し、改良版ではCRXJS Vite Pluginを使っているのでこれ以上のことは覚えておりません🙇♂️