Chrome用のブラウザ拡張を開発するのに create-chrome-ext (の vue-ts テンプレート)をボイラープレートとして使用していたときの躓き箇所の備忘録になります。
DockerのNode.jsを利用するとHMRが動作しない
最初はホストPC(Windows)にインストールした Node.js(20.x) を使用していたのですが、途中で複数台のPCで開発できるよう Docker を使った環境構築を行うよう方針転換しました。
Dockerを使った環境構築について
複雑なことはせずにviteのデフォルトのポート(5173)のポートフォワーディングを行うくらいのコンテナ環境を作成しています。
services:
app:
build:
context: ./
dockerfile: ./.docker/app/Dockerfile
volumes:
- .:/app:cached
ports:
- '5173:5173'
tty: true
- compose.yml 作成
FROM node:20-slim
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]
ENV TZ=Asia/Tokyo
RUN { \
echo "alias ll='ls -l --color=auto'"; \
} >> /root/.bashrc
WORKDIR /app
- compose.yml に記載した Dockerfile を作成
今回は WSL2 配下ではなく WindowsPC のボリューム内にプロジェクトを作っているため ホストとコンテナで UID=1000,GID=1000 に揃えるための USER命令(USER node)はなし
import { defineConfig } from 'vite'
import { crx } from '@crxjs/vite-plugin'
import vue from '@vitejs/plugin-vue'
import manifest from './src/manifest'
import Components from 'unplugin-vue-components/vite'
import { PrimeVueResolver } from '@primevue/auto-import-resolver'
export default defineConfig(({ mode }) => {
const production = mode === 'production'
return {
+ server: {
+ host: true,
+ port: 5173,
+ hmr: {
+ port: 5173,
+ },
+ },
build: {
cssCodeSplit: true,
emptyOutDir: true,
outDir: 'build',
rollupOptions: {
output: {
chunkFileNames: 'assets/chunk-[hash].js',
},
},
},
plugins: [
crx({ manifest }),
vue(),
Components({
resolvers: [PrimeVueResolver()],
}),
],
}
})
- devモードで起動するときのviteにブラウザから到達できるよう
server.host
やserver.port
、server.hmr.port
などの設定を実施
ホストPCの Node.js を使用していた頃はサイドパネルのvueファイルを保管時のHMRが正常に動作していました。
Dockerを使用したコンテナ環境ではエディタでファイルを編集した際のhmr update
の出力が行われず、実際にHMRも行われなくなってしまいました。また、サイドパネルを開いた状態で開発者ツールを開いてみてもコンソールにエラーらしいエラーは出ていませんでした。
こちらの件、HMRに関するエラーらしいエラーも出ていなく原因がよくわかっていませんが、以下のように server.watch.usePolling: true
を追記することで動作するようになりました。
import { defineConfig } from 'vite'
import { crx } from '@crxjs/vite-plugin'
import vue from '@vitejs/plugin-vue'
import manifest from './src/manifest'
import Components from 'unplugin-vue-components/vite'
import { PrimeVueResolver } from '@primevue/auto-import-resolver'
export default defineConfig(({ mode }) => {
const production = mode === 'production'
return {
server: {
host: true,
port: 5173,
+ watch: {
+ usePolling: true,
+ },
hmr: {
port: 5173,
},
},
build: {
cssCodeSplit: true,
emptyOutDir: true,
outDir: 'build',
rollupOptions: {
output: {
chunkFileNames: 'assets/chunk-[hash].js',
},
},
},
plugins: [
crx({ manifest }),
vue(),
Components({
resolvers: [PrimeVueResolver()],
}),
],
}
})
manualChunkを設定するとdevサーバーが起動しない
UIフレームワークや自動インポートのためのプラグインを導入していった結果として、デフォルトの閾値(500KB)を超過するチャンクが発生するようになりました。
build実行時のログ詳細
> docker compose exec app npm run build
> [some-ext]@0.0.1 build
> vue-tsc --noEmit && vite build
vite v5.4.1 building for production...
✓ 209 modules transformed.
The emitted file "img/logo-16.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-34.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.
build/assets/index.ts-loader-CvVIyBbC.js 0.34 kB
build/sidepanel.html 0.55 kB │ gzip: 0.33 kB
build/img/logo-16.png 0.73 kB
build/manifest.json 1.09 kB │ gzip: 0.65 kB
build/.vite/manifest.json 1.87 kB │ gzip: 0.45 kB
build/img/logo-34.png 2.04 kB
build/img/logo-48.png 3.11 kB
build/img/logo-128.png 7.80 kB
build/assets/primeicons-C6QP2o4f.woff2 35.15 kB
build/assets/primeicons-MpK4pl85.ttf 84.98 kB
build/assets/primeicons-WjwUDZjB.woff 85.06 kB
build/assets/primeicons-DMOk5skT.eot 85.16 kB
build/assets/primeicons-Dr5RGzOO.svg 342.53 kB │ gzip: 105.26 kB
build/assets/sidepanel-6d3Q9_62.css 350.17 kB │ gzip: 36.56 kB
build/assets/chunk-CPt-ohte.js 0.62 kB │ gzip: 0.26 kB
build/assets/chunk-CrPRWnrr.js 2.23 kB │ gzip: 1.10 kB
build/assets/chunk-D__Qv3Tb.js 722.55 kB │ gzip: 163.71 kB
(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
✓ built in 5.53s
これを解消すべく以下のような manualChunk の設定を施しました。
ちなみにチャンクの分割対象を視覚化するための Bundle Analyzer もこのタイミングで導入しました。
import { defineConfig } from 'vite'
import { crx } from '@crxjs/vite-plugin'
import vue from '@vitejs/plugin-vue'
import manifest from './src/manifest'
import Components from 'unplugin-vue-components/vite'
import { PrimeVueResolver } from '@primevue/auto-import-resolver'
+import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig(({ mode }) => {
const production = mode === 'production'
return {
server: {
host: true,
port: 5173,
watch: {
usePolling: true,
},
hmr: {
port: 5173,
},
},
build: {
cssCodeSplit: true,
emptyOutDir: true,
outDir: 'build',
rollupOptions: {
output: {
chunkFileNames: 'assets/chunk-[hash].js',
+ manualChunks: {
+ vue: ['vue'],
+ datatable: ['primevue/datatable'],
+ },
},
+ plugins: [visualizer()],
},
},
plugins: [
crx({ manifest }),
vue(),
Components({
resolvers: [PrimeVueResolver()],
}),
],
}
})
上記設定を施した後のビルド結果が以下
build実行時のログ詳細
> docker compose exec app npm run build
> [some-ext]@0.0.1 build
> vue-tsc --noEmit && vite build
vite v5.4.1 building for production...
✓ 209 modules transformed.
The emitted file "img/logo-34.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.
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.
build/assets/index.ts-loader-CvVIyBbC.js 0.34 kB
build/sidepanel.html 0.70 kB │ gzip: 0.35 kB
build/img/logo-16.png 0.73 kB
build/manifest.json 1.09 kB │ gzip: 0.65 kB
build/img/logo-34.png 2.04 kB
build/.vite/manifest.json 2.16 kB │ gzip: 0.51 kB
build/img/logo-48.png 3.11 kB
build/img/logo-128.png 7.80 kB
build/assets/primeicons-C6QP2o4f.woff2 35.15 kB
build/assets/primeicons-MpK4pl85.ttf 84.98 kB
build/assets/primeicons-WjwUDZjB.woff 85.06 kB
build/assets/primeicons-DMOk5skT.eot 85.16 kB
build/assets/primeicons-Dr5RGzOO.svg 342.53 kB │ gzip: 105.26 kB
build/assets/sidepanel-6d3Q9_62.css 350.17 kB │ gzip: 36.56 kB
build/assets/chunk-CPt-ohte.js 0.62 kB │ gzip: 0.26 kB
build/assets/chunk-CrPRWnrr.js 2.23 kB │ gzip: 1.10 kB
build/assets/chunk-DilsI5ti.js 67.85 kB │ gzip: 27.28 kB
build/assets/chunk-F0L3Wnrr.js 200.27 kB │ gzip: 38.53 kB
build/assets/chunk-kB2xJbjL.js 454.92 kB │ gzip: 100.18 kB
✓ built in 5.48s
vueとprimevue/datatableについてチャンクを分割したことにより、チャンクの数が2個増え500KBを超えるものが無くなりました
build時のログとして表示されていたワーニングメッセージも抑制されるようになりよかったよかったと思いきや、その後の開発の続きを行う際のdevサーバー起動時に以下のようなエラーが出てサーバーが即落ちするようになってしまってました。
> docker compose exec app npm run dev
> [some-ext]@0.0.1 dev
> vite
VITE v5.4.1 ready in 8149 ms
➜ Local: http://localhost:5173/
➜ Network: http://172.20.0.2:5173/
➜ press h + enter to show help
Error: Could not resolve entry module (vue).
at error (file:///app/node_modules/rollup/dist/es/shared/rollup.js:1858:30)
at ModuleLoader.loadEntryModule (file:///app/node_modules/rollup/dist/es/shared/rollup.js:22369:20)
at async Promise.all (index 2)
at async Promise.all (index 0) {
code: 'UNRESOLVED_ENTRY'
}
こちらの件、エラーメッセージや manualChunk について調べてもこれと言って解決策が見つけられませんでした。
ということで正しい対応か微妙ですが vite.config.ts
の先頭に定義されているproductionビルドかを判定するproduction変数を元にbuild時のみ manualChunk の設定を有効化されるように修正しました。
import { defineConfig } from 'vite'
import { crx } from '@crxjs/vite-plugin'
import vue from '@vitejs/plugin-vue'
import manifest from './src/manifest'
import Components from 'unplugin-vue-components/vite'
import { PrimeVueResolver } from '@primevue/auto-import-resolver'
export default defineConfig(({ mode }) => {
const production = mode === 'production'
return {
server: {
host: true,
port: 5173,
watch: {
usePolling: true,
},
hmr: {
port: 5173,
},
},
build: {
cssCodeSplit: true,
emptyOutDir: true,
outDir: 'build',
rollupOptions: {
output: {
chunkFileNames: 'assets/chunk-[hash].js',
- manualChunks: {
- vue: ['vue'],
- datatable: ['primevue/datatable'],
- },
+ manualChunks: production
+ ? {
+ vue: ['vue'],
+ datatable: ['primevue/datatable'],
+ }
+ : undefined,
},
},
},
plugins: [
crx({ manifest }),
vue(),
Components({
resolvers: [PrimeVueResolver()],
}),
],
}
})
ストアに公開したブラウザ拡張のインストールでエラー
サイドパネルやタブに関するパーミッションを利用しているため審査に1~2日ほど掛かりましたが、無事に公開できました
そして動作確認のため、自分でブラウザ拡張をインストールしようとしてみたところ以下のようなエラーが出てしまいました。。
ダウンロード エラー: 画像をデコードできませんでした: logo-128.png
拡張機能内に含まれるロゴ画像(logo-128.png)はPhotoshop(CS6)で自作しWeb用に書き出したものでこれと言って特別なことをした覚えもありません。。
先に結論を書くとzipを作成する際に使用していたgulpをおもむろに4→5に移行していたことが原因でした。
以下の記事や公式のリリースノートが参考になりました
- (調査メモ)gulp5で画像ファイルが壊れた場合の対処 #Node.js - Qiita
- Release gulp v5.0.0 · gulpjs/gulp · GitHub
- gulp v5.0.0 リリース!およそ5年ぶりのアップデート - コハム
原因特定に至るまでの調査内容
-
ダウンロードした
main.crx
をmain.zip
にリネームして展開 -
展開した中身のlogo-128.pngファイルを確認し確かに破損していることを確認
-
自分の開発環境内に残っていたアップロード元のzipファイルを展開し、中身のlogo-128.pngファイルが破損していることも確認
-
このことからChromeのデベロッパーダッシュボードにアップロードしたタイミングで破損したわけではなく開発環境で作成したzipファイルに原因があるという問題の切り分けが可能となった
-
zip化のためのコードはnpm-scriptsから呼び出し可能なスクリプト化(
src/zip.js
)されており、主にgulpを使用したシンプルなコードとなっていた -
そういえばgulp4について脆弱性のアラートが上がってたからgulp5に更新したな?と思い出した
-
gulp5 + png などでググり自身のアップデートが不具合の原因であったことが判明。。
Chromeのブラウザ拡張開発のボイラープレートとして使用している create-chrome-ext を元に作成したプロジェクトについてnpmで管理するパッケージ周りのバージョンが古くなってしまっているものがあり、その中にgulp: ^4.0.2
も含まれていました。
これを深く考えず npm install -D gulp@latest
で最新の^5.0.0
にアップデートだけしたのがエラーの原因でした。
create-chrome-ext を使用してブラウザ拡張のプロジェクトを作成したときの出力
以下は vanilla-ts テンプレートを使用したプロジェクトを作成したときの出力です
>npm create chrome-ext
> npx
> create-chrome-ext
√ Project name: ... .
√ Author: ... no one
√ Framework: » vanilla
√ Language: » vanilla-ts
Scaffolding project in H:\workspace_browser_ext\test-project...
Done. Now run:
npm install
npm run dev
Suggest you next step:
1. cd
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.
------------------------------
>npm install
npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and
powerful.
npm warn deprecated source-map-url@0.4.1: See https://github.com/lydell/source-map-url#deprecated
npm warn deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm warn deprecated q@1.5.1: You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.
npm warn deprecated
npm warn deprecated (For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
npm warn deprecated source-map-resolve@0.5.3: See https://github.com/lydell/source-map-resolve#deprecated
npm warn deprecated chokidar@2.1.8: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies
npm warn deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm warn deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead
added 457 packages, and audited 458 packages in 23s
44 packages are looking for funding
run `npm fund` for details
11 vulnerabilities (7 moderate, 4 high)
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
gulp: ^4.0.2 を使用していた頃の脆弱性レポート
>npm audit
# npm audit report
braces <3.0.3
Severity: high
Uncontrolled resource consumption in braces - https://github.com/advisories/GHSA-grv7-fg5c-xmjg
fix available via `npm audit fix --force`
Will install gulp@5.0.0, which is a breaking change
node_modules/braces
chokidar 1.3.0 - 2.1.8
Depends on vulnerable versions of anymatch
Depends on vulnerable versions of braces
Depends on vulnerable versions of readdirp
node_modules/chokidar
glob-watcher 5.0.0 - 5.0.5
Depends on vulnerable versions of anymatch
Depends on vulnerable versions of chokidar
node_modules/glob-watcher
gulp 4.0.0 - 4.0.2
Depends on vulnerable versions of glob-watcher
Depends on vulnerable versions of gulp-cli
node_modules/gulp
micromatch <=4.0.7
Depends on vulnerable versions of braces
node_modules/anymatch/node_modules/micromatch
node_modules/findup-sync/node_modules/micromatch
node_modules/matchdep/node_modules/micromatch
node_modules/readdirp/node_modules/micromatch
anymatch 1.2.0 - 2.0.0
Depends on vulnerable versions of micromatch
node_modules/anymatch
findup-sync 0.4.0 - 3.0.0
Depends on vulnerable versions of micromatch
node_modules/findup-sync
node_modules/matchdep/node_modules/findup-sync
liftoff 2.2.3 - 3.1.0
Depends on vulnerable versions of findup-sync
node_modules/liftoff
gulp-cli 1.3.0 - 2.3.0
Depends on vulnerable versions of liftoff
Depends on vulnerable versions of matchdep
node_modules/gulp-cli
matchdep >=1.0.1
Depends on vulnerable versions of findup-sync
Depends on vulnerable versions of micromatch
node_modules/matchdep
readdirp 2.2.0 - 2.2.1
Depends on vulnerable versions of micromatch
node_modules/readdirp
11 vulnerabilities (7 moderate, 4 high)
To address all issues (including breaking changes), run:
npm audit fix --force
gulpを使用したzip化のスクリプトを修正
gulp5ではデフォルトでsrcに指定するGlobパターンのパスに含まれるストリーム形式のファイルについてUTF-8へのエンコードを行うとのこと。
Viteのプロダクションビルド(npm run build
)によって build ディレクトリに出来上がったファイル一式をソースとした zip を作成するコードとなっているため、src のオプションとして encoding: false
を追加することで解決できました。
import gulp from 'gulp'
import zip from 'gulp-zip'
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const manifest = require('../build/manifest.json')
gulp
- .src('build/**')
+ .src('build/**', { encoding: false })
.pipe(zip(`${manifest.name.replaceAll(' ', '-')}-${manifest.version}.zip`))
.pipe(gulp.dest('package'))
参考サイト
-
サーバーオプション | Vite
-
server.watch.usePolling
について調べたときの参考情報(公式) - 今回はWSL2環境下に環境構築しているわけではないので
server.watch.usePolling: true
が関係するとは思ってなかったですが、実際にはWindows環境下でDockerを使って立てたコンテナ環境でも必要だったみたいです?
-
-
(調査メモ)gulp5で画像ファイルが壊れた場合の対処 #Node.js - Qiita
- ブラウザ拡張に含まれるロゴ画像が破損していることが原因でブラウザ拡張のインストールに失敗してしまった際に見たページ
- ブラウザ拡張についてストアに公開した後で発覚したため非常に助かりました
-
gulp v5.0.0 リリース!およそ5年ぶりのアップデート - コハム
- gulp5 の変更点について日本語でまとまっているページ
-
Release gulp v5.0.0 · gulpjs/gulp · GitHub
- gulp v5.0.0 のリリースノート(公式)
- 今回のgulp4→5移行で起きた現象についてはBREAKING CHANGESの2行目
Default stream encoding to UTF-8
に書いてありました
-
Vite製のアプリケーションにBundle Analyzerを設定する
- manualChunk の設定を行う際の設定対象を洗い出すのに使用したBundle Analyzerを導入したときに参考にしたページです
- 今回の躓きと直接関係のあるものではないです