🧃

Rails + webpack プロジェクトで webpack v4 から v5 にアプデした話

2024/03/08に公開

はじめに

ポート株式会社 サービス開発部 Advent Calendar 2023 4日目の記事です。

フロント開発に携わる半分😼金髪💅の eicho.chin です。この記事は最近行った少し🤏大きいアップデートについてのお話です。

背景

React、TypeScript を最新にあげないと、プロジェクトで使うライブラリーが最新の機能が使えません。でも最新にしたいなら、webpack v5 が必要になります。
また、node を 18 にバージョンアップした時、open-ssl がレガシープロバイダーでした利用できないものがあり、暫定対応などを行いました。
プロジェクトの更新にwebpack をv5 以上にアプデートしないと、いろいろ進めない状況でした🥹
ひっくるめて、一言:上げないとマジでやばくねぇ💕

構成のイメージ

(webpacker を剥がした後)

  1. webpack + babel-loader で app/frontend/packs/ に置かれてる *.jsをコンパイルし、manifest.json にアウトプット。
  2. Rails側でも上記のmanifest.jsonを読み込む。

大まかに実行したこと

  1. 主にマイグレーションガイド手順通りに進む
  2. 主要な webpack ライブラリー周り更新
  3. babel 周り更新、依存関係更新
  4. 設定更新
  5. マイグレーションガイドに沿って更新
  6. yarn run dev 試す
  7. 個別ライブラリー更新
  8. 3~6 のループ
  9. prod 向けの build 調整

実際対応の時の順番🥹

3->5->4->3->6->4->3->~~
得られた教訓にですが、意外と試行錯誤やライブラリーや loaderがごちゃ混ぜ状態なので、仮説を立てつづ更新が重要です。
先に、マイグレーションガイドの手順を実行。
次に、ローカルで devServer を動かすことを目指して DevServer | webpack 周りの設定を更新した方が楽かもしれません。

遭遇したエラー

webpack 設定の変更

brower環境利用のため、node 部分不要のための設定変更

マイグレーションガイドから: https://webpack.js.org/migrate/5/#clean-up-configuration

webpack.common.js
- 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 削除して問題なさそう

https://webpack.js.org/migrate/5/#clean-up-configuration

(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]とマイグレーションガイドの提示にのっとって変更を行いました:

  1. webpack.output に対しても設定

  2. loader 内の設定

[fullhash]、[contenthash]、[chunkhash]

console
<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 は変更なし

webpack.common.js
...
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

webpack.common.js
-{
-  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 18では、file-lodaer 内に サポートしてない OpenSSLバージョンだったので、 NODE_OPTIONS=--openssl-legacy-provider を 環境変数に入れて暫定対応

上記の file-lodaer を移行したため、コマンドなどに NODE_OPTIONS=--openssl-legacy-provider の指定も削除

devSever 設定の変更

devServer.contentBase 廃止、代わりに devServer.static.directory

https://webpack.js.org/configuration/dev-server/#devserverstatic

webpack.dev.js
 devServer: {
- 	path.resolve(__dirname, 'public', 'packs'),
-
+	static: {
+	      directory: path.resolve(__dirname, 'public', 'packs'),
+	    },
}

devServer.disableHostCheck: true 廃止、代わりに devServer.allowedHosts: 'all'

https://github.com/webpack/webpack-dev-server/blob/7add5e5b9ce7967e03a376a07de78763de52bae5/migration-v4.md?plain=1#L371

devServer.watchOptions 廃止、代わりにdevSever.static.watch

https://github.com/webpack/webpack-dev-server/blob/7add5e5b9ce7967e03a376a07de78763de52bae5/migration-v4.md?plain=1#L286

webpack.dev.js
// before
watchOptions: {
-  ignored: /node_modules/,
+  static: {
+    watch: { ignored: /node_modules/ },
+  }
},

devServer.stats のコード階層/場所/位置変更

https://webpack.js.org/configuration/stats/

webpack.dev.js
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 のコード階層/場所/位置変更

https://webpack.js.org/configuration/dev-server/#overlay

webpack.dev.js
module.exports = merge(common, {
  mode: 'development',
  devServer: {
-    overlay: true,
+    client: {
+      overlay: true,
+    },
    }
}

devServer.publicPath: '/packs/' output.publicPath: '/packs/'

参考: https://stackoverflow.com/questions/67926278/webpack-dev-server-path-publicpath-configuration-when-index-html-is-in-different

(prod も同様な設定となります)

devServer.public: 'localhost:3035' v5 廃止

https://github.com/webpack/webpack-dev-server/blob/master/migration-v4.md

他の参考:

webpack.dev.js
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://github.com/webpack/webpack-dev-server/blob/7add5e5b9ce7967e03a376a07de78763de52bae5/migration-v4.md?plain=1#L139

参考: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://github.com/webpack/webpack-dev-server/blob/7add5e5b9ce7967e03a376a07de78763de52bae5/migration-v4.md?plain=1#L195

設定:https://webpack.js.org/configuration/dev-server/#logging

webpack.dev.js
module.exports = {
  devServer: {
-    clientLogLevel: "info",
+    client: {
+      logging: 'info', // 'none'
+    },
    }
}

よくわからんエラー

TypeError: [BABEL]: helperPluginUtils.declarePreset is not a function 調査

グッグってもともて関連性が高い情報が手に入らず、粗療法を試しました:

yarn upgrade

結果:確認が大変な行数の yarn.lock が変更されました🥹
どんくらい大変、ほぼ全 yarn.lock ファイル 書き換えられました。
https://classic.yarnpkg.com/lang/en/docs/cli/upgrade/
調べてみましたら、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

参考にした記事

最後に

🫲🌚🫱 webpack の公式ドキュメント英語と中国語照らし合わせてみられて、いろいろ楽でした。
別対応node-sass から dart-sass 移行途中ですが、そこがネックになってないことが幸いでした。
アプデの過程中、いろんな意味不明はエラーが湧いてきます。焦らず、エラーメッセージからヒントを探してました。案外簡単に情報があります。流石にダメな場合、思い切って全部削除と再インストールをかましても手の一種かなと思います!!

Discussion