苦しんで覚えるwebpackerのバージョン更新
webpackerのバージョン更新作業
現代のWeb Applicationのviewを作成する場合はReact・Vue、Angular等のライブラリを利用する事例が増えたと思います。それらのライブラリを利用する場合はビルドツール、多くはデファクトスタンダードになっているwebpackを利用しているかと思います。
Railsも例外ではなく、Railsのversion 6からはRails用にwebpackをwrapしたwebpackerがデフォルトでinstallされるようになっています。
webpackerの導入事例はよく耳にしますが、その後の更新作業を耳にすることは少ないです。
Railsを利用するからにはRails Wayに乗ることが重要であり、そのためにはRailsアプリケーションを継続的に更新していく必要はあります。webpackerに関しても例外ではありません。
今回はv4.3.0
からv5.0.0
へ苦しみながら更新した一連の流れを記載したものを投稿します。
v4.3.0
からv5.0-stable
へ一度に更新しない理由はversion間の差分を減らし不確実性を減らすためです。
今回使用するコードはGitHubすべてに公開しています。
果たしてwebpackerは必要なのか
更新の前にそもそもwebpackerは必要なのかという話をします。
フロントエンドの人からはwebpackerは嫌われている印象がありますし、その理由も理解出来ます。個人的にwebpackerの役目はフロントエンドに強い人に任せるまでの繋ぎの役割だと考えています。
webpackerはwebpackの事をあまり知らなくてもいい感じにやってくれる事が最大のメリットだと思っていますが、デメリットとしてRails独自の成約や後述する@rails/webpacker
に依存関係が隠れてしまいwebpackerが利用しているwebpackのversionに引きずられてしまいます。
しかし、そのデメリットを上回るほど導入が簡単であり、誰でもいい感じに扱えるメリットが大きいです。誰でも扱えるということは開発のスピード向上に寄与します。
Rails6からはwebpackerがデフォルトでinstallされるようになりましたが、剥がすことはそこまで大変ではありません。事例もいくつもあります。
なのでフロントエンドに強い人が複数人いる組織に所属しており、バージョン更新が面倒と思っている場合はこれを気にwebpackerを剥がし、素のwebpackやpacel、esbuildやらvite等を使っても良いと思いますし、そちらの方が良いと思っています。前述の通りwebpackerはあくまでそれまでの繋ぎの役割で良いと個人的には考えています。
その他webpackerは嫌だがもう少しシンプルに利用したいという方にはsimpackerという選択肢もあります。
HotwireやSprocketsもありますが、npm packageのエコシステムに乗ることが出来ず、フロントエンドのデファクトスタンダードになりつつあるTypeScriptの型定義の恩恵も受けることが出来ないので、個人的にはwebpackerの採用より筋が悪いと考えています。
CHANGELOGを眺める
今回はv5.0.0への更新なのでそちらを見ます。
大きくは下記の4つです。
- Node 8以下のサポート廃止
- Rails 5.2以下のサポート廃止
- Ruby 2.4以下のサポート廃止
- webpackのmultiple file entryのサポート
変更差分を確認する
CHANGELOGを眺めたところで完全に理解した。と言いたいところですが、具体的なファイルの差分はわかりません。変更差分が分からないということはどのファイルがどのように変わるか把握が出来ず、バージョン更新時の不確実性が増し、不具合の温床になるます。
そこで更新前と更新後の空のRailsプロジェクトを作成し更新前と更新後のファイル差分を確かめます。今回の場合はv4.3.0
とv5.0.0
のプロジェクトを作成します。
$ mkdir -p /path/to/repo
$ cd /path/to/repo
$ bundle init
$ vim Gemfile # railsのコメントアウトを外す
$ bundle install
v4.3.0
$ bundle exec rails new v4.3.0 -G --skip-turbolinks --skip-bundle
$ cd v4.3.0
$ vim Gemfile # webpackerを4.3.0に
$ vim package.json # @rails/webpackerを4.3.0に
$ bundle exec rails webpacker:install && bundle exec rails webpacker:install:vue
v5.0.0
$ bundle exec rails new v5.0.0 -G --skip-turbolinks --skip-bundle
$ cd v5.0.0
$ vim Gemfile # webpackerを5.0.0に
$ vim package.json # @rails/webpackerを5.0.0に
$ bundle exec rails webpacker:install && bundle exec rails webpacker:install:vue
diffを確認する
yarn.lockについては後述します。
$ diff -x yarn.lock v4.3.0 v5.0.0
diff -x yarn.lock v4.3.0/Gemfile v5.0.0/Gemfile
15c15
< gem 'webpacker', '4.3.0'
---
> gem 'webpacker', '5.0.0'
$ diff -x yarn.lock v4.3.0/Gemfile.lock v5.0.0/Gemfile.lock
160a161
> semantic_range (3.0.0)
183,184c184,185
< webpacker (4.3.0)
< activesupport (>= 4.2)
---
> webpacker (5.0.0)
> activesupport (>= 5.2)
186c187,188
< railties (>= 4.2)
---
> railties (>= 5.2)
> semantic_range (>= 2.3.0)
213c215
< webpacker (= 4.3.0)
---
> webpacker (= 5.0.0)
Common subdirectories: v4.3.0/app and v5.0.0/app
Common subdirectories: v4.3.0/bin and v5.0.0/bin
Common subdirectories: v4.3.0/config and v5.0.0/config
Common subdirectories: v4.3.0/db and v5.0.0/db
Common subdirectories: v4.3.0/lib and v5.0.0/lib
Common subdirectories: v4.3.0/log and v5.0.0/log
Common subdirectories: v4.3.0/node_modules and v5.0.0/node_modules
$ diff -x yarn.lock v4.3.0/package.json v5.0.0/package.json
2c2
< "name": "v4-3-0",
---
> "name": "v5-0-0",
8c8
< "@rails/webpacker": "4.3.0",
---
> "@rails/webpacker": "5.0.0",
11,13c11
< "vue-template-compiler": "^2.6.12",
< "webpack": "^4.46.0",
< "webpack-cli": "^3.3.12"
---
> "vue-template-compiler": "^2.6.12"
Common subdirectories: v4.3.0/public and v5.0.0/public
Common subdirectories: v4.3.0/storage and v5.0.0/storage
Common subdirectories: v4.3.0/test and v5.0.0/test
Common subdirectories: v4.3.0/tmp and v5.0.0/tmp
Common subdirectories: v4.3.0/vendor and v5.0.0/vendor
Gemfileとpackage.json以外はほぼ変更点が無い事が分かりました。次は@rails/webpacker
の差分を見ます。
@rails/webpacker
yarn.lock
のdiffは膨大な数になるので今回は@rails/webpacker
の差分のみを確認します。
$ diff v4.3.0/yarn.lock v5.0.0/yarn.lock | grep -A 52 webpacker
< "@rails/webpacker@4.3.0":
< version "4.3.0"
< resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-4.3.0.tgz#3793b3aed08ed0b661f1bed9de0739abacb6a834"
< integrity sha512-DmKGjKugLeeytT1TO9fUBBjdA3YwQ19zoWK5JDL8V1rM0bf6WRf1n9DZTiVmuf0WO1gp5ej5pJ9b3NjZwfAz4Q==
---
> "@rails/webpacker@5.0.0":
> version "5.0.0"
> resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-5.0.0.tgz#b63dfd31e514ee6fac021add3e34f1381a1fd152"
> integrity sha512-qFByiHiBUVFmCwIldI8wtyJskVEvBXME6UHkivaMAzrVVXuIta+SPbiAqEoi3qwZSh5qEq9wbIltap5Y1F6Avw==
917,925c910,918
< "@babel/core" "^7.7.2"
< "@babel/plugin-proposal-class-properties" "^7.7.0"
< "@babel/plugin-proposal-object-rest-spread" "^7.6.2"
< "@babel/plugin-syntax-dynamic-import" "^7.2.0"
< "@babel/plugin-transform-destructuring" "^7.6.0"
< "@babel/plugin-transform-regenerator" "^7.7.0"
< "@babel/plugin-transform-runtime" "^7.6.2"
< "@babel/preset-env" "^7.7.1"
< "@babel/runtime" "^7.7.2"
---
> "@babel/core" "^7.8.7"
> "@babel/plugin-proposal-class-properties" "^7.8.3"
> "@babel/plugin-proposal-object-rest-spread" "^7.8.3"
> "@babel/plugin-syntax-dynamic-import" "^7.8.3"
> "@babel/plugin-transform-destructuring" "^7.8.8"
> "@babel/plugin-transform-regenerator" "^7.8.7"
> "@babel/plugin-transform-runtime" "^7.8.3"
> "@babel/preset-env" "^7.8.7"
> "@babel/runtime" "^7.8.7"
928c921
< babel-plugin-macros "^2.6.1"
---
> babel-plugin-macros "^2.8.0"
930,933c923,926
< compression-webpack-plugin "^4.0.0"
< core-js "^3.4.0"
< css-loader "^3.2.0"
< file-loader "^4.2.0"
---
> compression-webpack-plugin "^3.0.1"
> core-js "^3.6.4"
> css-loader "^3.4.1"
> file-loader "^5.0.2"
937,938c930,931
< mini-css-extract-plugin "^0.8.0"
< node-sass "^4.13.0"
---
> mini-css-extract-plugin "^0.9.0"
> node-sass "^4.13.1"
948,951c941,944
< sass-loader "7.3.1"
< style-loader "^1.0.0"
< terser-webpack-plugin "^2.2.1"
< webpack "^4.41.2"
---
> sass-loader "^7.3.1"
> style-loader "^1.1.2"
> terser-webpack-plugin "^2.3.5"
> webpack "^4.42.0"
975,977c968,970
babelの各種version、loaderやmini-css-extract-plugin、webpackのversionが更新されていることが分かると思います。これで実際の変更差分がわかりました。次はこれらのアップデートの内容を確認します。すべて取り上げるのは難しいのでここでは変化が大きいであろうpackageたちを確認します。
@rails/webpacker依存関係にあるライブラリ
- webpackerが依存しているpackageのversionも更新されるので各packageのCHANGELOGを眺めます。ここでは一部のみを抜粋します。
babel
core-js
webpack
バージョンの更新
これですべての更新差分を算出することが出来ました。あとは差分を元にwebpackerの更新を行うだけです。更新に関してはREADMEにガイドがあるのでそちらに従います。
ガイドにはlatestとありますが、今回はwebpack-dev-server以外はすべて5.0.0
を指定します。
$ vim Gemfile # 5.0.0へ
$ bundle update --conservative webpacker
$ yarn upgrade @rails/webpacker@5.0.0
$ yarn upgrade webpack-dev-server --latest
packageの更新に関してはこれにて終わりです。あとはwebpacker.yamlやconfig/webpack/
に書いてある設定を各packageの更新内容に追従してください。
検証について
webpackerの更新に限った話ではありませんが一応。
バージョンを更新しただけで終わりではなく検証をする必要があります。検証用の環境があればユーザの操作を洗い出しリスト化し、一つ一つ画面を確認すると確実でしょう。
欲を言えばカナリアリリースの仕組みがあれば実際に一部のプロダクション環境へ検証することができ、不具合があったとしても影響を一部に抑えることが出来るので時間と作業リソースに余裕があれば強くおすすめします。
差し戻しの想定
最後にこれだけは準備しておいたほうが良い事があります。それはバージョン更新したwebpackerを差し戻す(v5.0.0
からv4.3.0
へ)検証です。
ここまで変更差分の算出や各種CHANGELOGの内容を眺めバージョン更新の不確実な点を極力減らしてきましたが、ソフトウェアのバージョン更新時は得てして不具合が起きることが多いです。特定の画面等が動かなくなることも十分ありえます。バージョン更新後戻せない不可逆的な変更を加えた状態で不具合が起きると修正にかなりの時間が想定されます。そうならないためにも差し戻す準備は必要だと考えます。数日〜1週間ほどは不具合が発生したとしても差し戻しが出来るようにすることを推奨します。
そこまで苦しくないのでは?
前述したv4.3.0
からv5.0.0
に関しては更新内容は差分も少なく、そこまで複雑な物はないのでそう思うかもしれません。では本当に苦しい物を見てみましょう。v5.x-stable
から2021/5/25
現在次期メジャーバージョンv6系の最新であるv6.0.0.beta7
への更新の内容を見ましょう。
6.0.0.beta7のCHANGELOG
-
node_modules
をデフォルトでコンパイルしないように - extract cssがデフォルトで有効に
- webpackのruntimeChunkをsingeで動かすように
- @babel/preset-envをautoに
- yarn version2(berry)を実験的にsupport
- webpack-dev-serverの問題を修正
ここまでは良いとして、Breaking Chnages
と痺れる一文が載っていますね。
- Simple webpack config
- Removed integration installers
- Splitchunks enabled by default
- CSS extraction enabled by default
- Optional CSS support
こちらについてはv6へのアップグレードガイドがあるのでそちらを参照します。
Simpel Webpack config
webpackerのAPIが一部変更となりました。
後述するintegrationを利用して導入している場合は設定を変更する必要があります。こういうコマンドですね。bin/rails webpacker:install:vue
こちらはVueの例になります。
Removed integration installers
今まではwebpackerでcoffescriptやtypescriptを導入する際にはrails commandからのinstallerが存在しましたが、それらが削除されました。これからは自分で導入する必要があります。
v5.3.0
$ cat Gemfile.lock | grep webpacker
webpacker (5.3.0)
webpacker (~> 5.0)
$ bundle exec rails --help | grep webpacker
webpacker
webpacker:binstubs
webpacker:check_binstubs
webpacker:check_node
webpacker:check_yarn
webpacker:clean[keep,age]
webpacker:clobber
webpacker:compile
webpacker:info
webpacker:install
webpacker:install:angular
webpacker:install:coffee
webpacker:install:elm
webpacker:install:erb
webpacker:install:react
webpacker:install:stimulus
webpacker:install:svelte
webpacker:install:typescript
webpacker:install:vue
webpacker:verify_install
webpacker:yarn_install
v6.0.0.beta7
$ cat Gemfile.lock | grep webpacker
webpacker (6.0.0.beta.7)
webpacker (= 6.0.0.beta7)
$ bundle exec rails --help | grep webpacker
webpacker
webpacker:binstubs
webpacker:check_binstubs
webpacker:check_node
webpacker:check_yarn
webpacker:clean[keep,age]
webpacker:clobber
webpacker:compile
webpacker:info
webpacker:install
webpacker:verify_install
webpacker:yarn_install
今まではデフォルト入っていたfile-loaderやcss-loaderも@rails/webpacker
の依存関係から削除されているので個別に導入する必要があります。
$ diff v5.3.0/yarn.lock v6.0.0.beta7/yarn.lock | grep -A 50 webpacker
< "@rails/webpacker@5.3.0":
< version "5.3.0"
< resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-5.3.0.tgz#9d7a615735f850572b9c5e2ad4c57f4af70d70fd"
< integrity sha512-cpzrtrsMVi6WgRUzL63bMF+oa98DiL8cKXUZzDFLeElRu7qArG+cxU8grmaPbTmFqQEqVGsbJT0c6fubbFtwVA==
< dependencies:
< "@babel/core" "^7.13.16"
< "@babel/plugin-proposal-class-properties" "^7.13.0"
< "@babel/plugin-proposal-object-rest-spread" "^7.13.8"
< "@babel/plugin-syntax-dynamic-import" "^7.8.3"
< "@babel/plugin-transform-destructuring" "^7.13.17"
< "@babel/plugin-transform-regenerator" "^7.13.15"
< "@babel/plugin-transform-runtime" "^7.13.15"
< "@babel/preset-env" "^7.13.15"
< "@babel/runtime" "^7.13.17"
---
> "@rails/webpacker@^6.0.0-beta.7":
> version "6.0.0-beta.7"
> resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-6.0.0-beta.7.tgz#5deaadc5bf962c5e031e6d038bbb5d4a591c07a6"
> integrity sha512-ZnKSixM+XKKTjRBL5mR5b2ZZVtqYGdPP5MAn4d8aehTApu51w8DIFV+9dC4WI59mIjAOsc2411rNvkX/eLX5XQ==
> dependencies:
> "@babel/core" "^7.12.9"
> "@babel/plugin-proposal-class-properties" "^7.12.1"
> "@babel/plugin-transform-runtime" "^7.12.1"
> "@babel/preset-env" "^7.12.11"
> "@babel/runtime" "^7.12.5"
928,935c916,919
< babel-plugin-dynamic-import-node "^2.3.3"
< babel-plugin-macros "^2.8.0"
< case-sensitive-paths-webpack-plugin "^2.4.0"
< compression-webpack-plugin "^4.0.1"
< core-js "^3.11.0"
< css-loader "^3.6.0"
< file-loader "^6.2.0"
< flatted "^3.1.1"
---
> babel-plugin-macros "^3.0.1"
> case-sensitive-paths-webpack-plugin "^2.3.0"
> compression-webpack-plugin "^7.1.0"
> core-js "^3.8.0"
937,939c921
< js-yaml "^3.14.1"
< mini-css-extract-plugin "^0.9.0"
< optimize-css-assets-webpack-plugin "^5.0.4"
---
> js-yaml "^3.14.0"
942,946d923
< postcss-flexbugs-fixes "^4.2.1"
< postcss-import "^12.0.1"
< postcss-loader "^3.0.0"
< postcss-preset-env "^6.7.0"
< postcss-safe-parser "^4.0.2"
948,955c925,951
< sass "^1.32.11"
< sass-loader "10.1.1"
< style-loader "^1.3.0"
< terser-webpack-plugin "^4.2.3"
< webpack "^4.46.0"
< webpack-assets-manifest "^3.1.1"
< webpack-cli "^3.3.12"
< webpack-sources "^1.4.3"
---
> terser-webpack-plugin "^5.0.3"
> webpack "^5.11.0"
> webpack-assets-manifest "^5.0.0"
> webpack-cli "^4.2.0"
> webpack-merge "^5.7.2"
> webpack-sources "^2.2.0"
>
Splitchunks enabled by default
CSS extraction enabled by default
Optional CSS support
これらに関してはタイトル通りなので省略します。
@rails/webpackerの依存ライブラリについて
先程v5.3.0
とv6.0.0.beta7
のdiffを少しお見せました。もう一度確認します。
$ diff v5.3.0/yarn.lock v6.0.0.beta7/yarn.lock | grep -A 50 webpacker
< "@rails/webpacker@5.3.0":
< version "5.3.0"
< resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-5.3.0.tgz#9d7a615735f850572b9c5e2ad4c57f4af70d70fd"
< integrity sha512-cpzrtrsMVi6WgRUzL63bMF+oa98DiL8cKXUZzDFLeElRu7qArG+cxU8grmaPbTmFqQEqVGsbJT0c6fubbFtwVA==
< dependencies:
< "@babel/core" "^7.13.16"
< "@babel/plugin-proposal-class-properties" "^7.13.0"
< "@babel/plugin-proposal-object-rest-spread" "^7.13.8"
< "@babel/plugin-syntax-dynamic-import" "^7.8.3"
< "@babel/plugin-transform-destructuring" "^7.13.17"
< "@babel/plugin-transform-regenerator" "^7.13.15"
< "@babel/plugin-transform-runtime" "^7.13.15"
< "@babel/preset-env" "^7.13.15"
< "@babel/runtime" "^7.13.17"
---
> "@rails/webpacker@^6.0.0-beta.7":
> version "6.0.0-beta.7"
> resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-6.0.0-beta.7.tgz#5deaadc5bf962c5e031e6d038bbb5d4a591c07a6"
> integrity sha512-ZnKSixM+XKKTjRBL5mR5b2ZZVtqYGdPP5MAn4d8aehTApu51w8DIFV+9dC4WI59mIjAOsc2411rNvkX/eLX5XQ==
> dependencies:
> "@babel/core" "^7.12.9"
> "@babel/plugin-proposal-class-properties" "^7.12.1"
> "@babel/plugin-transform-runtime" "^7.12.1"
> "@babel/preset-env" "^7.12.11"
> "@babel/runtime" "^7.12.5"
928,935c916,919
< babel-plugin-dynamic-import-node "^2.3.3"
< babel-plugin-macros "^2.8.0"
< case-sensitive-paths-webpack-plugin "^2.4.0"
< compression-webpack-plugin "^4.0.1"
< core-js "^3.11.0"
< css-loader "^3.6.0"
< file-loader "^6.2.0"
< flatted "^3.1.1"
---
> babel-plugin-macros "^3.0.1"
> case-sensitive-paths-webpack-plugin "^2.3.0"
> compression-webpack-plugin "^7.1.0"
> core-js "^3.8.0"
937,939c921
< js-yaml "^3.14.1"
< mini-css-extract-plugin "^0.9.0"
< optimize-css-assets-webpack-plugin "^5.0.4"
---
> js-yaml "^3.14.0"
942,946d923
< postcss-flexbugs-fixes "^4.2.1"
< postcss-import "^12.0.1"
< postcss-loader "^3.0.0"
< postcss-preset-env "^6.7.0"
< postcss-safe-parser "^4.0.2"
948,955c925,951
< sass "^1.32.11"
< sass-loader "10.1.1"
< style-loader "^1.3.0"
< terser-webpack-plugin "^4.2.3"
< webpack "^4.46.0"
< webpack-assets-manifest "^3.1.1"
< webpack-cli "^3.3.12"
< webpack-sources "^1.4.3"
---
> terser-webpack-plugin "^5.0.3"
> webpack "^5.11.0"
> webpack-assets-manifest "^5.0.0"
> webpack-cli "^4.2.0"
> webpack-merge "^5.7.2"
> webpack-sources "^2.2.0"
>
babelのversionが上がっていたりしますが、その他大きく分けて2つあります。
webpackのメジャーバージョンアップ
webpackのverionが4系から5系に更新されました。これに伴い4系の設定を書いている場合は5系への移行作業が必要になります。こちらはmigration guideがあるのでそちらを見ると良いです。
依存ライブラリの削除
前述したように integration installers が削除された影響でpostcss, sass, sass-loader, style-loader等のライブラリを自分で導入する必要があります。
最後に
次期バージョンであるv6
はある程度大きな変更が入っており、v4
からv5
への更新に比べると難易度が違ってくる事がおわかりいただけたかと思います。
そこそこ大変なのでこのタイミングでwebpackerを引き剥がすか、このままwebpackerを採用し、Rails wayに乗り続けるかの判断は人や立場、環境によって変わるかと思いますが、どちらにも言えることは更新をし続けることが重要です。
ライブラリのバージョン更新は地道な作業であまり華はありませんが、プロダクトを開発していく中ではとても重要です。放置をしていて脆弱性が発見されたバージョンを利用し続けセキュリティホールになったり、サポートが廃止になり開発の妨げになることも少なくありません。地道で大変な作業ですが少しずつやっていきましょう。
Discussion