💪

苦しんで覚えるwebpackerのバージョン更新

2021/06/02に公開

webpackerのバージョン更新作業

現代のWeb Applicationのviewを作成する場合はReact・Vue、Angular等のライブラリを利用する事例が増えたと思います。それらのライブラリを利用する場合はビルドツール、多くはデファクトスタンダードになっているwebpackを利用しているかと思います。
Railsも例外ではなく、Railsのversion 6からはRails用にwebpackをwrapしたwebpackerがデフォルトでinstallされるようになっています。

https://github.com/rails/webpacker

webpackerの導入事例はよく耳にしますが、その後の更新作業を耳にすることは少ないです。
Railsを利用するからにはRails Wayに乗ることが重要であり、そのためにはRailsアプリケーションを継続的に更新していく必要はあります。webpackerに関しても例外ではありません。

今回はv4.3.0からv5.0.0へ苦しみながら更新した一連の流れを記載したものを投稿します。
v4.3.0からv5.0-stable へ一度に更新しない理由はversion間の差分を減らし不確実性を減らすためです。

今回使用するコードはGitHubすべてに公開しています。

https://github.com/teitei-tk/webpacker-version-diff-example

果たしてwebpackerは必要なのか

更新の前にそもそもwebpackerは必要なのかという話をします。

フロントエンドの人からはwebpackerは嫌われている印象がありますし、その理由も理解出来ます。個人的にwebpackerの役目はフロントエンドに強い人に任せるまでの繋ぎの役割だと考えています。

webpackerはwebpackの事をあまり知らなくてもいい感じにやってくれる事が最大のメリットだと思っていますが、デメリットとしてRails独自の成約や後述する@rails/webpackerに依存関係が隠れてしまいwebpackerが利用しているwebpackのversionに引きずられてしまいます。

しかし、そのデメリットを上回るほど導入が簡単であり、誰でもいい感じに扱えるメリットが大きいです。誰でも扱えるということは開発のスピード向上に寄与します。

Rails6からはwebpackerがデフォルトでinstallされるようになりましたが、剥がすことはそこまで大変ではありません。事例もいくつもあります。

https://tech.misoca.jp/entry/2019/02/22/110000
https://inside.pixiv.blog/subal/4615

なのでフロントエンドに強い人が複数人いる組織に所属しており、バージョン更新が面倒と思っている場合はこれを気にwebpackerを剥がし、素のwebpackやpacel、esbuildやらvite等を使っても良いと思いますし、そちらの方が良いと思っています。前述の通りwebpackerはあくまでそれまでの繋ぎの役割で良いと個人的には考えています。

その他webpackerは嫌だがもう少しシンプルに利用したいという方にはsimpackerという選択肢もあります。
https://techlife.cookpad.com/entry/2019/07/08/100000

HotwireやSprocketsもありますが、npm packageのエコシステムに乗ることが出来ず、フロントエンドのデファクトスタンダードになりつつあるTypeScriptの型定義の恩恵も受けることが出来ないので、個人的にはwebpackerの採用より筋が悪いと考えています。

CHANGELOGを眺める

今回はv5.0.0への更新なのでそちらを見ます。
https://github.com/rails/webpacker/blob/v5.0.0/CHANGELOG.md#500---2020-03-22

大きくは下記の4つです。

  • Node 8以下のサポート廃止
  • Rails 5.2以下のサポート廃止
  • Ruby 2.4以下のサポート廃止
  • webpackのmultiple file entryのサポート

変更差分を確認する

CHANGELOGを眺めたところで完全に理解した。と言いたいところですが、具体的なファイルの差分はわかりません。変更差分が分からないということはどのファイルがどのように変わるか把握が出来ず、バージョン更新時の不確実性が増し、不具合の温床になるます。
そこで更新前と更新後の空のRailsプロジェクトを作成し更新前と更新後のファイル差分を確かめます。今回の場合はv4.3.0v5.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

https://github.com/babel/babel/blob/main/CHANGELOG.md

core-js

https://github.com/zloirock/core-js/blob/master/CHANGELOG.md

webpack

https://github.com/webpack/webpack/releases/tag/v4.42.0

バージョンの更新

これですべての更新差分を算出することが出来ました。あとは差分を元にwebpackerの更新を行うだけです。更新に関してはREADMEにガイドがあるのでそちらに従います。

https://github.com/rails/webpacker/tree/v5.0.0#upgrading

ガイドには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

https://github.com/rails/webpacker/blob/v6.0.0.beta.7/CHANGELOG.md#600---2021-tbd

ここまでは良いとして、Breaking Chnagesと痺れる一文が載っていますね。

  • Simple webpack config
  • Removed integration installers
  • Splitchunks enabled by default
  • CSS extraction enabled by default
  • Optional CSS support

こちらについてはv6へのアップグレードガイドがあるのでそちらを参照します。

https://github.com/rails/webpacker/blob/v6.0.0.beta.7/docs/v6_upgrade.md

Simpel Webpack config

webpackerのAPIが一部変更となりました。

後述するintegrationを利用して導入している場合は設定を変更する必要があります。こういうコマンドですね。bin/rails webpacker:install:vue

こちらはVueの例になります。

https://github.com/rails/webpacker/tree/v6.0.0.beta.7#other-frameworks

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.0v6.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があるのでそちらを見ると良いです。

https://github.com/webpack/changelog-v5/blob/master/MIGRATION GUIDE.md

依存ライブラリの削除

前述したように integration installers が削除された影響でpostcss, sass, sass-loader, style-loader等のライブラリを自分で導入する必要があります。

最後に

次期バージョンであるv6はある程度大きな変更が入っており、v4からv5への更新に比べると難易度が違ってくる事がおわかりいただけたかと思います。
そこそこ大変なのでこのタイミングでwebpackerを引き剥がすか、このままwebpackerを採用し、Rails wayに乗り続けるかの判断は人や立場、環境によって変わるかと思いますが、どちらにも言えることは更新をし続けることが重要です。

ライブラリのバージョン更新は地道な作業であまり華はありませんが、プロダクトを開発していく中ではとても重要です。放置をしていて脆弱性が発見されたバージョンを利用し続けセキュリティホールになったり、サポートが廃止になり開発の妨げになることも少なくありません。地道で大変な作業ですが少しずつやっていきましょう。

Discussion