ツクリンク プロダクト部 Advent Calendar 2023 の2日目です。
概要
webpacker はもうメンテナンスされないので shakapacker に移行した。手順通りやればうまくいくけど、ハマったポイントもある。
webpacker はもうメンテされない
Webpacker has been retired 🌅
webpacker はもうメンテナンスされません。最後のコミットから1年くらい経っていて、READMEにも大きく書いてありました。理由は DHH さんが詳しく語っています。もう2年前ですね。
ひいては webpacker が依存している webpack もアプデできないことになります。 webpack に脆弱性があった場合こまったことになる!
そして webpacker の正規最新バージョンが使用している webpack は、サポート中の Node.js だと OpenSSL に関連したエラーが出て使えません。 具体的なバージョン情報を箇条書きでまとめると以下のようになります。
- webpacker の正規の最新バージョンは 5.4.4
- https://rubygems.org/gems/webpacker/versions
- rc版の 6.x 系はリリースされているけど正式な 6.0 はもう出ない
- webpacker 5.4.4 に含まれている webpack のバージョンは 4.46.0
- webpack 4 は Node.js 17 以上と組み合わせると OpenSSL のエラーになる
- 参考: https://zenn.dev/niccari/articles/ffdd621eb6be5a
- 参考: https://github.com/webpack/webpack/issues/14532
- (と思ってたけど 4.47.0 だと対応されてるっぽい。とりあえず Node.js に対応させるには webpack のアプデだけでいいかも。未検証)
- Node.js のメンテナンス対象の最低バージョンは 18
- https://github.com/nodejs/Release
- 16 は 2023-09-11 に EOL になった
移行先
webpacker が示した移行先は3つあります。
特に推奨されているのは上2つですが、 shakapacker は webpacker の正式の後継 gem なのでほぼコードそのままで移行でき、 shakapacker が示す手順ドキュメント通りやればだいたいは動きます。とりあえず webpack の最新に追従しておきたい、って思った時はこの手段を取れると思います。今回は shakapacker への乗り換えで行ってみましょう。
Official, actively maintained successor to rails/webpacker.ShakaCode stands behind the long-term maintenance and development of this project for the Rails community.
手順
テスト書く
webpacker の移行にかぎらない一般的な手順ですがいちばん重要なことなので。
webpacker の使用箇所のテストケースを網羅的に書きます。新機能は何もないはずなのでリグレッションテストになるはず。 E2E の自動テストがあれば最高だけど、 E2E テストをちゃんと書くのは超大変かつ結構不安定になりがちなので、手動のテストケースも準備しておくといいと思います。
特に古いブラウザでも動かしたいって時はこの手順がいちばん大変かもしれません。反対に最新のブラウザで動けばいい場合は適当でもなんとかなるかも。
webpacker 5 -> shakapacker 6 に移行する
shakapacker が示す移行手順書があるので、この通りやります。
Upgrading from Webpacker v5 to Shakapacker v6
丁寧なドキュメントなのでこれで大体動くようになりますが、いくつかハマったのでそれらの解決方法を書きます。
javascript_pack_tag
は一度しか呼べなくなる
javascript_pack_tag
はひとつのリクエストにつき(1つのHTMLにつき)一度しか呼べなくなります。二つ以上の webpacker のエントリーポイントある場合は append_javascript_pack_tag
をつかって統合することになりますが、それぞれのエントリーポイント(<script>
)に別々の属性をつけることができなくなっています。 なので、以下のようなレガシーブラウザに対応した type=module
nomodule
による読み替えがあった場合動かなくなります(参考: ES Modules への橋渡しとしての nomodule 属性)。
<% # こういうコードはあきらめるしかない %>
<%= javascript_pack_tag 'some_script_for_modern_browsers', type: 'module' %>
<%= javascript_pack_tag 'some_script_for_legacy_browsers', nomodule: true %>
shakapacker のコード確認しても <script>
タグの属性値としてセットされる options
を複数保持する方法がなさそうなので正攻法ではあきらめるしかなさそうです
def javascript_pack_tag(*names, defer: true, **options)
if @javascript_pack_tag_loaded
raise "To prevent duplicated chunks on the page, you should call javascript_pack_tag only once on the page. " \
"Please refer to https://github.com/shakacode/shakapacker/blob/master/README.md#view-helpers-javascript_pack_tag-and-stylesheet_pack_tag for the usage guide"
end
append_javascript_pack_tag(*names, defer: defer)
non_deferred = sources_from_manifest_entrypoints(javascript_pack_tag_queue[:non_deferred], type: :javascript)
deferred = sources_from_manifest_entrypoints(javascript_pack_tag_queue[:deferred], type: :javascript) - non_deferred
@javascript_pack_tag_loaded = true
capture do
concat javascript_include_tag(*deferred, **options.tap { |o| o[:defer] = true })
concat "\n" if non_deferred.any? && deferred.any?
concat javascript_include_tag(*non_deferred, **options.tap { |o| o[:defer] = false })
end
end
lib/shakapacker/helper.rb#L98-L115
javascript_pack_tag_queue
にはいったエントリーポイント名を受け取った options
で出力している。 一度しか呼べないチェックがあるので options
を使い分けることはできなそう
def append_javascript_pack_tag(*names, defer: true)
update_javascript_pack_tag_queue(defer: defer) do |hash_key|
javascript_pack_tag_queue[hash_key] |= names
end
end
lib/shakapacker/helper.rb#L182-L186
append_javascript_pack_tag
はそもそも options
をうけとらない
babel.config.js
をいじっている場合は削除しちゃダメ
Remove
babel.config.js
if you never changed it. Configure yourpackage.json
to use the default:"babel": { "presets": [ "./node_modules/@rails/webpacker/package/babel/preset.js" ] }
See customization example the Customizing Babel Config for React configuration.
if you never changed
を見落としていて動かなくなりました。たとえば独自に staging 環境などを追加してる場合にハマると思います。元のファイルを shakapcaker のデフォルト preset.js に則って直しましょう。
node_modules 以下がデフォルトでコンパイル対象から除外される
どうしても古いブラウザの対応が必要なときなど、 node_modules 以下の npm package のファイル群もコンパイルしたくなると思いますが、 shakapacker はデフォルトの設定で node_modules 以下をコンパイルの対象から外しています。
const { resolve } = require('path')
const { realpathSync } = require('fs')
const {
source_path: sourcePath,
additional_paths: additionalPaths
} = require('../config')
const inclusions = [sourcePath, ...additionalPaths].map(p => {
try {
return realpathSync(p)
} catch (e) {
return resolve(p)
}
})
module.exports = {
include: inclusions,
exclude: [
{
// exclude all node_modules from running through babel-loader
and: [resolve('node_modules')],
// Do not exclude inclusions, as otherwise these won't be transpiled
not: [...inclusions]
}
]
}
なので、以下のような設定を webpacker.yml に書いておく必要があります。 additional_paths
は上記の not: [...inclusion]
になるので、こっちの設定の方が強いです。
additional_paths: [
'node_modules'
]
style-loader が mini-css-extract-loader で置き換えられた
style-loader が mini-css-extract-loader で置き換えられたので、 JS ファイル中で import
していた CSS が JS として読めなくなっています。 dev_server 設定だとどちらかを選べますが、 本番環境と開発環境でコードが大きく変わるのも面倒が多いので stylesheet_pack_tag
を適宜呼ぶようにしました。
開発環境でリロードしてもスクリプトが新しくならないんだけど
shakapacker 6 だと開発環境でもスーパーリロードしないとキャッシュが効いてしまう問題があります。これはスクリプトの URL に hash がつかなくなったためで、これは shakapacker 7 の useContentHash
オプションで修正できるようになってます。
see also https://github.com/shakacode/shakapacker/issues/88
Slow setup for development って警告が出るんだけど
Rails のログを見てると以下のような遅すぎ警告ログが流れるようになってます
Shakapacker::Compiler - Slow setup for development
Prepare JS assets with either:
1. Running `bin/shakapacker-dev-server`
2. Set `compile` to false in shakapacker.yml and run `bin/shakapacker -w`
これは webpacker の時から元々遅かったのを警告出すようにしたみたいです。なので無視しててもいいですが、言われた通りに bin/shakapacker -w
とか使うと高速です。
see also https://github.com/shakacode/shakapacker/pull/6
webpacker の方の時にすでに指摘されていたのを shakapacker で巻き取ったものみたい
https://github.com/rails/webpacker/pull/3233
がんばろう
ほかにもアプリケーションによって色々はまりどころが出ると思いますが、 shakapacker のドキュメントとコードを読んで地道に解決していきましょう。 shakapacker 自体がどんどん薄くなる方針っぽいので、割と解決しやすいと思います。
テストする
まずは rails assets:precompile
が通る状態になることです。
precompile できるようになったら最初に作ったテストケースを開発環境と本番に準ずる環境で実施します。テストが落ちたら移行ガイドと設定を見直します。古いブラウザをサポートしている場合はそのテストも忘れずに(というか古いブラウザをサポートしなくていい場合はコンパイルが通ってる時点で大体ちゃんと動く)。
あとは開発環境のコンパイルが通るか、 dev-server 周りの設定がうまくいっているか確認します。
終わり
中期的には Rails に乗るという意味で shakapacker をはがすのが良さそうですね。今回の手順でテストケースができたので、他の手を取るのも簡単になったはずです!