Rails + webpack プロジェクトで webpack v4 から v5 にアプデした話
はじめに
ポート株式会社 サービス開発部 Advent Calendar 2023 4日目の記事です。
フロント開発に携わる半分😼金髪💅の eicho.chin です。この記事は最近行った少し🤏大きいアップデートについてのお話です。
背景
React、TypeScript を最新にあげないと、プロジェクトで使うライブラリーが最新の機能が使えません。でも最新にしたいなら、webpack v5 が必要になります。
また、node を 18 にバージョンアップした時、open-ssl がレガシープロバイダーでした利用できないものがあり、暫定対応などを行いました。
プロジェクトの更新にwebpack をv5 以上にアプデートしないと、いろいろ進めない状況でした🥹
ひっくるめて、一言:上げないとマジでやばくねぇ
💕
構成のイメージ
(webpacker を剥がした後)
- webpack + babel-loader で
app/frontend/packs/
に置かれてる*.js
をコンパイルし、manifest.json にアウトプット。 - Rails側でも上記のmanifest.jsonを読み込む。
大まかに実行したこと
- 主にマイグレーションガイド手順通りに進む
- 主要な webpack ライブラリー周り更新
- babel 周り更新、依存関係更新
- 設定更新
- マイグレーションガイドに沿って更新
-
yarn run dev
試す - 個別ライブラリー更新
- 3~6 のループ
- prod 向けの build 調整
実際対応の時の順番🥹
3->5->4->3->6->4->3->~~
得られた教訓にですが、意外と試行錯誤やライブラリーや loaderがごちゃ混ぜ状態なので、仮説を立てつづ更新が重要です。
先に、マイグレーションガイドの手順を実行。
次に、ローカルで devServer を動かすことを目指して DevServer | webpack 周りの設定を更新した方が楽かもしれません。
- webpack-dev-server/migration-v4.md at master · webpack/webpack-dev-server
-
webpack-dev-server/migration-v5.md at master · webpack/webpack-dev-server
↑Minimum supported Node.js version is 18.12.0.
🤭
遭遇したエラー
webpack 設定の変更
brower環境利用のため、node 部分不要のための設定変更
マイグレーションガイドから: https://webpack.js.org/migrate/5/#clean-up-configuration
- module.exports = {
- node: {
- dgram: 'empty',
- fs: 'empty',
- net: 'empty',
- tls: 'empty',
- child_process: 'empty',
- },
+ module.exports = {
+ resolve: {
+ fallback: { // ブラウザ環境のみに利用
+ dgram: false,
+ fs: false,
+ net: false,
+ tls: false,
+ child_process: false,
+ },
+ }
+ }
yarn pnp-webpack-plugin
削除して問題なさそう
(node:59586) [DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_HASH] DeprecationWarning: [hash] is now [fullhash] (also consider using [chunkhash] or [contenthash], see documentation for details)
(Use node --trace-deprecation ... to show where the warning was created) 警告が出る
参考記事: https://medium.com/@web_developer/hash-vs-chunkhash-vs-contenthash-e94d38a32208
現状の webpack.common.js
に使用している[hash]とマイグレーションガイドの提示にのっとって変更を行いました:
-
webpack.output
に対しても設定
-
loader 内の設定
[fullhash]、[contenthash]、[chunkhash]
<e> [webpack-dev-middleware] HookWebpackError: Path variable [contenthash:8] not implemented in this context: js/[id]-[contenthash:8].hot-update.js
<e> at makeWebpackError (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/HookWebpackError.js:48:9)
<e> at /Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:3075:12
<e> at eval (eval at create (/Users/test.chin/up/sample.test.jp/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:32:1)
<e> at fn (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:481:17)
<e> at Hook.eval [as callAsync] (eval at create (/Users/test.chin/up/sample.test.jp/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:30:1)
<e> at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/Users/test.chin/up/sample.test.jp/node_modules/tapable/lib/Hook.js:18:14)
<e> at cont (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:3072:34)
<e> at /Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:3120:10
<e> at symbolIterator (/Users/test.chin/up/sample.test.jp/node_modules/neo-async/async.js:3485:9)
<e> at process.processTicksAndRejections (node:internal/process/task_queues:77:11)
<e> -- inner error --
<e> Error: Path variable [contenthash:8] not implemented in this context: js/[id]-[contenthash:8].hot-update.js
<e> at fn (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/TemplatedPathPlugin.js:68:11)
<e> at fn (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/TemplatedPathPlugin.js:41:17)
<e> at /Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/TemplatedPathPlugin.js:320:12
<e> at String.replace (<anonymous>)
<e> at replacePathVariables (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/TemplatedPathPlugin.js:313:14)
<e> at Hook.eval [as call] (eval at create (/Users/test.chin/up/sample.test.jp/node_modules/tapable/lib/HookCodeFactory.js:19:10), <anonymous>:7:16)
<e> at Compilation.getAssetPathWithInfo (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:4744:40)
<e> at Compilation.getPathWithInfo (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:4720:15)
<e> at /Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/HotModuleReplacementPlugin.js:662:24
<e> at fn (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:479:10)
<e> at Hook.eval [as callAsync] (eval at create (/Users/test.chin/up/sample.test.jp/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:30:1)
<e> at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/Users/test.chin/up/sample.test.jp/node_modules/tapable/lib/Hook.js:18:14)
<e> at cont (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:3072:34)
<e> at /Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:3120:10
<e> at symbolIterator (/Users/test.chin/up/sample.test.jp/node_modules/neo-async/async.js:3485:9)
<e> at process.processTicksAndRejections (node:internal/process/task_queues:77:11)
<e> caused by plugins in Compilation.hooks.processAssets
<e> Error: Path variable [contenthash:8] not implemented in this context: js/[id]-[contenthash:8].hot-update.js
<e> at fn (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/TemplatedPathPlugin.js:68:11)
<e> at fn (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/TemplatedPathPlugin.js:41:17)
<e> at /Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/TemplatedPathPlugin.js:320:12
<e> at String.replace (<anonymous>)
<e> at replacePathVariables (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/TemplatedPathPlugin.js:313:14)
<e> at Hook.eval [as call] (eval at create (/Users/test.chin/up/sample.test.jp/node_modules/tapable/lib/HookCodeFactory.js:19:10), <anonymous>:7:16)
<e> at Compilation.getAssetPathWithInfo (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:4744:40)
<e> at Compilation.getPathWithInfo (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:4720:15)
<e> at /Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/HotModuleReplacementPlugin.js:662:24
<e> at fn (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:479:10)
<e> at Hook.eval [as callAsync] (eval at create (/Users/test.chin/up/sample.test.jp/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:30:1)
<e> at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/Users/test.chin/up/sample.test.jp/node_modules/tapable/lib/Hook.js:18:14)
<e> at cont (/Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:3072:34)
<e> at /Users/test.chin/up/sample.test.jp/node_modules/webpack/lib/Compilation.js:3120:10
<e> at symbolIterator (/Users/test.chin/up/sample.test.jp/node_modules/neo-async/async.js:3485:9)
<e> at process.processTicksAndRejections (node:internal/process/task_queues:77:11)
解決案:
output のみ変更、他一切[hash]変更なし。🧙
エラー内容自体は、webpack (本体)の [hash]指定、各種 loader は変更なし
...
module.exports = {
mode: 'development',
output: {
filename: 'js/[name]-[contenthash].js',
chunkFilename: 'js/[name]-[contenthash].chunk.js',
hotUpdateChunkFilename: 'js/[id]-[fullhash].hot-update.js', // [hash]のみの指定が廃止のため、[fullhash]で指定
path: path.resolve(__dirname, 'public', 'packs'),
publicPath: '/packs/',
},
file-loaderの代替品の検討:file-loaderはWebpack 5以降では非推奨となり、代わりにAsset Modulesの使用が推奨されています。
参考: https://studist.tech/migration-webpack-from-4-to-5-3df31da8e7a2
-{
- test: /\.(jpg|jpeg|png|gif|tiff|ico|svg|eot|otf|ttf|woff|woff2)$/i,
- use: [
- {
- loader: 'file-loader',
- options: {
- name: '[path][name]-[hash].[ext]',
- esModule: false,
- context: path.resolve(__dirname, 'app', 'frontend'),
- },
- },
- ],
-},
+ {
+ test: /\.(jpg|jpeg|png|gif|tiff|ico|svg)$/i,
+ type: 'asset/resource', // asset/resourceが推奨
+ generator: {
+ filename: 'images/[path][name]-[hash][ext]',
+ },
+ },
+ {
+ test: /\.(woff|woff2|woff(2)?|eot|ttf|otf)$/i,
+ type: 'asset/resource',
+ generator: {
+ filename: 'fonts/[path][name]-[hash][ext]',
+ },
+ },
NODE_OPTIONS=--openssl-legacy-provider
を 環境変数に入れて暫定対応
node 18では、file-lodaer 内に サポートしてない OpenSSLバージョンだったので、 上記の file-lodaer を移行したため、コマンドなどに NODE_OPTIONS=--openssl-legacy-provider
の指定も削除
devSever 設定の変更
devServer.contentBase
廃止、代わりに devServer.static.directory
devServer: {
- path.resolve(__dirname, 'public', 'packs'),
-
+ static: {
+ directory: path.resolve(__dirname, 'public', 'packs'),
+ },
}
devServer.disableHostCheck: true
廃止、代わりに devServer.allowedHosts: 'all'
devServer.watchOptions
廃止、代わりにdevSever.static.watch
// before
watchOptions: {
- ignored: /node_modules/,
+ static: {
+ watch: { ignored: /node_modules/ },
+ }
},
devServer.stats
のコード階層/場所/位置変更
module.exports = merge(common, {
mode: 'development',
devServer: {
...someCode,
- stats: { // devSever の中
- entrypoints: false,
- errorStack: true,
- errorDetails: true,
- modules: false,
- moduleTrace: false,
- },
+ stats: { // devSever と同じ階層
+ entrypoints: false,
+ errorStack: true,
+ errorDetails: true,
+ modules: false,
+ moduleTrace: false,
+ },
},
}
devServer.overlay
のコード階層/場所/位置変更
module.exports = merge(common, {
mode: 'development',
devServer: {
- overlay: true,
+ client: {
+ overlay: true,
+ },
}
}
devServer.publicPath: '/packs/'
を output.publicPath: '/packs/'
(prod も同様な設定となります)
devServer.public: 'localhost:3035'
v5 廃止
他の参考:
module.exports = merge(common, {
mode: 'development',
devServer: {
- public: 'localhost:3035',
+ client: {
+ webSocketURL: 'auto://localhost:3035', // 公式の記載:auto://myapp.test:80/ws, wsはwebSocket、'/ws'がなくでも動作する
+ },
}
}
devServer.useLocalIp: false
廃止、削除して問題なさそう
参考:https://webpack.js.org/configuration/dev-server/#local-ip
devServer.inline
削除されました
エラーメッセージは以下:
The inline (iframe live mode) option was removed without replacement.
devServer.clientLogLevel: 'none'
も場所移動、client 内に移動とkeyName変更
設定:https://webpack.js.org/configuration/dev-server/#logging
module.exports = {
devServer: {
- clientLogLevel: "info",
+ client: {
+ logging: 'info', // 'none'
+ },
}
}
よくわからんエラー
TypeError: [BABEL]: helperPluginUtils.declarePreset is not a function 調査
グッグってもともて関連性が高い情報が手に入らず、粗療法を試しました:
yarn upgrade
結果:確認が大変な行数の yarn.lock が変更されました🥹
どんくらい大変、ほぼ全 yarn.lock ファイル 書き換えられました。
調べてみましたら、package.json で指定したバージョン情報の元最大限バージョンアップを行いました。
いい結果として:エラーメッセージが変わりました!
一旦変更全部戻して、BABEL を中心にアプデしたい
yarn upgrade -scope @babel
// yarn upgrade -S @babel
今回の対応で結果論として、これでいい TRY になりました。
npmのパッケージがscoped packagesであることを示しています。これはパッケージ名の前にスコープ(つまり、特定のユーザーや組織)を追加し、名前空間を区別する方法です。これにより、開発者は自分のパッケージ群を他の人や組織から区別できます。
例えば、@babel/coreというパッケージは、babelというスコープ(すなわち、@babelという名前の組織)に属します。同様に、@vue/cliはvueという組織のパッケージであり、それぞれのパッケージは、そのスコープ内で一意の名称を持ちます。
詳細なドキュメンテーションは、npmの公式サイトで提供されています: npm-scope
@
指定で更新ちゃんとできるかと疑ってましたので、もう一回 remove と add をしてもいいかもしれません。
筆者はやりました😀 ついでに、packcage.json の 整理もしました。
yarn.lock の確認の中 微妙に依存関係が違和感が感じました。(本当に感でしかない
yarn remove @babel/core @babel/runtime babel-loader
再インストール:
yarn add @babel/core@latest @babel/runtime@latest babel-loader@latest
そして、devDependenciesに対して:
アンインストール:
yarn remove @babel/helper-environment-visitor @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread @babel/plugin-syntax-dynamic-import @babel/plugin-transform-destructuring @babel/plugin-transform-regenerator @babel/plugin-transform-runtime @babel/preset-env babel-plugin-dynamic-import-node babel-plugin-macros
再インストール:
yarn add --dev @babel/helper-environment-visitor@latest @babel/plugin-proposal-class-properties@latest @babel/plugin-proposal-object-rest-spread@latest @babel/plugin-syntax-dynamic-import@latest @babel/plugin-transform-destructuring@latest @babel/plugin-transform-regenerator@latest @babel/plugin-transform-runtime@latest @babel/preset-env@latest babel-plugin-dynamic-import-node@latest babel-plugin-macros@latest
各パッケージは適切な依存関係セクションに再配置されます。<- 信じます🐈
fiber.call$1 is not a function 調査
おそらくsass-loaderとsassバージョン間の非互換性によるものかと思われます。
バージョン情報
- sass: "^1.38.0",
- sass-lodaer: "14"
sassのバージョンに合わせる必要があるかも
yarn remove sass-loader
yarn add sass-loader@10 -D
エラーが出なくなりました!
(yarn upgrade sass
、sass をアプデの方法も TRY してみましたが、ダメでした)
今後の対応案の検討
esbuild-loader
babel から脱却したい〜〜
開発環境で試しに導入してみました。
babel-lodaer の設定を書き換えます
詳細な設定は esbuild-loader のgithub に記載があるため、省略させてください。
(babel.config.js) などの具体的な設定は es2015 だけ反映しました。
上記の変更だけで、開発環境の起動成功しています。
現状:
esbuild-lodaer 後:
プロジェクトによって大きく変わりますが、コンパイルの時間が 2000ms から 1800ms 程度早くなります。
他の事例もありますが、2~3 倍早くなるとかもあります。
(上記は簡単な導入の試しのみとなります。
しっかりやる場合、TSファイルの型チェック周りの整理・babal-lodaer 周りの削除などが必要です。🙇♂️)
vite
利用できる ライブラーがあります:https://vite-ruby.netlify.app/guide/rails.html
ライブラリーなし、Vite から導入
こちらの紹介から https://vitejs.dev/guide/backend-integration.html
参考にした記事
- https://github.com/webpack/webpack-dev-server/blob/master/migration-v4.md
- https://medium.com/@web_developer/hash-vs-chunkhash-vs-contenthash-e94d38a32208
最後に
🫲🌚🫱 webpack の公式ドキュメント英語と中国語照らし合わせてみられて、いろいろ楽でした。
別対応node-sass から dart-sass 移行途中ですが、そこがネックになってないことが幸いでした。
アプデの過程中、いろんな意味不明はエラーが湧いてきます。焦らず、エラーメッセージからヒントを探してました。案外簡単に情報があります。流石にダメな場合、思い切って全部削除と再インストールをかましても手の一種かなと思います!!
Discussion