こんにちは、システムエンジニアの kouki です
WESEEK では Rails と webpacker を使ったプロジェクトを多数担当しています。今回はその中で起こったトラブルと対処法についてご紹介します
最近では
- webpacker が公式に引退宣言を表明したり
- Rails 7 になり、jsbundling-rails, cssbundling-rails が台頭してきたり
と assets 関連が盛り上がっていますが、まだ webpacker が現役なため、今回は webpacker にフォーカスを当てています
(esbuild のビルドが速いと聞いて興味は沸いているので機会があればまた別の記事にて)
おさらい: webpacker で minify を効かせる方法
Rails + webpacker を利用しているアプリにて production で assets (JS, CSS etc) を提供する際には下記のようなコマンドを実行します
$ RAILS_ENV=production bin/rails assets:precompile
これは webpacker が assets:precompile rake タスクに特定のタスクを挟み込むことで production build を実現しています
また、RAILS_ENV=production
を指定するだけで、よしなに minify を働かせてくれるのは webpacker が下記のようなコードを提供してくれているからです
- TerserPlugin と CompressionPlugin を使って minify をしている個所 (
environments/production.js
)
利用しているプロジェクトの環境 (ハマった環境)
では、今回トラブルに遭遇した環境を紹介します
- Ruby on Rails
- Rails
'~> 6.0.3', '>= 6.0.3.2'
- webpacker
~5.4
- Rails
- webpacker (もとい JavaScript 側で利用しているライブラリ)
- React
- TypeScript
- (ポイント)
devDependencies
にtypescript
や@types/*
を追加している
- (ポイント)
production build をしてみたが ... エラー発生
ここから遭遇したエラーを紹介します
「さて、assets:precompile
を production でビルドするぞ」と意気込んで、下記コマンドを実行しました (実際は Dockerfile 内に記載しています)
$ RAILS_ENV=production NODE_ENV=production bundle exec rake assets:precompile
そうすると下記のようなエラーになってしまいました
TS2580: Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i @types/node`.
(略)
TS7016: Could not find a declaration file for module ...
@types
は devDependencies
に追加済みのはずなのにおかしいですね
調査フェーズ
上記エラーに対処するために色々と調査をしたところ、下記のような状況になっていることが分かりました
assets:precompile
実行後、node_modules
配下を見ると@types
npm パッケージが追加されていない- それ以外にも
devDependencies
に記載しているパッケージがインストールされていない - どうやら
NODE_ENV=production
の指定が入っていることにより、dependencies
のみ npm パッケージが install されている
次にそれぞれ (rails, webpacker) のコードを見て挙動を確認していきました
- webpacker の
yarn install
を実行するタスクはrails/rails
のyarn:install
を enhance している - どうやら rails の中に含まれる
yarn:install
タスクはRAILS_ENV=production
を指定するとNODE_ENV=production
が指定されることになっている
それ以外にも GitHub の issue で下記のようなコメントを見つけました
https://github.com/rails/webpacker/issues/117#issuecomment-282798638
Yes let's fix the root issue and move away from errant use of devDependencies.
dhh (28 Feb 2017)
2017 時点でのコメントなので現在では意見が異なると思いますが、「devDependencies の誤った利用 (errant use of devDependencies.)」 と言われてしまうと困ってしまいますね
解決案候補 & 採用した解決方法
ある程度情報が揃ったので解決案の候補を出していきます
ここでのやりたいことは 「yarn install 時には devDependencies のパッケージもインストールして、ビルド時にはNODE_ENV=production でビルドできること」 ということにしています
ボツ案も含め、一旦はザッと思い浮かんだものをリストアップし、最終的に 5.
の案にすることにしました
それぞれの案の却下理由は下記の通りです
devDependencies
に記載しているパッケージをdependencies
に移す- 先述した GitHub issue にて提示されている方法
- フロントエンド側からすると直感的ではない
- 各 npm パッケージのサイトでも
devDependencies
に追加する例示もあって、プロジェクトメンバーにそれを意識させたくない
- 各 npm パッケージのサイトでも
NODE_ENV=development
でassets:precompile
を実行- minify が効かない
- minify を効かせることもできるが手間がかかるし、トリッキー
assets:precompile
タスクを実行せず、rake タスクを分割して実行- こちらもトリッキーすぎる
- 「このプロジェクトでは
assets:precompile
は実行しないようにしてください」という注意書きにしたくない
- webpacker をアップデートしてみて、
assets:precompile
を実行してみる- integrity チェックが無くなった、という話もあるのでこれは有効な案
- 調査に時間がかかりそうだったため、今回は見送り
- [採用]
yarn:install
task を上書きする
最終的に案5を採用することにしました
自然な形で導入できる、かつ上書きをするタスクが局所的である、という点が採用ポイントです
下記のような rake タスクをプロジェクトの lib/tasks/yarn.rake
というファイルパスで追加しました
# assets:precompile 時に yarn install を NODE_ENV=development で実行させるモンキーパッチ
#
# refs: https://github.com/rails/webpacker/issues/405#issuecomment-750384173
if ENV['FORCE_YARN_INSTALL_IN_DEVELOPMENT']
Rails.application.config.after_initialize do
Rake::Task['yarn:install'].clear
# rails/rails v6.0.4.1 から引用
# https://github.com/rails/rails/blob/v6.0.4.1/railties/lib/rails/tasks/yarn.rake
namespace :yarn do
desc "Install all JavaScript dependencies as specified via Yarn"
task :install do
# 強制的に NODE_ENV=development で yarn install を実行させる
node_env = 'development'
system({ "NODE_ENV" => node_env }, "#{Rails.root}/bin/yarn install --no-progress --frozen-lockfile")
end
end
# Run Yarn prior to Sprockets assets precompilation, so dependencies are available for use.
if Rake::Task.task_defined?("assets:precompile")
Rake::Task["assets:precompile"].enhance [ "yarn:install" ]
end
end
end
そして実行するコマンドは下記のようになりました
$ FORCE_YARN_INSTALL_IN_DEVELOPMENT=true NODE_ENV=production bundle exec rake assets:precompile
これであれば他の人が見たときに「FORCE_YARN_INSTALL_IN_DEVELOPMENT
って何?」ってなった時にコードを検索することで辿ることができます
オプションを指定しない場合は先述したとおりエラーになるのですが、それは標準的な動きからあまり挙動を変えたくなかったためです
まとめ
今回は「webpacker で TypeScript を使った時にハマった話」からの各種調査についてご紹介させていただきました
「あなたの webpacker、minify 効いてますか?」という文言はただ単に「皆さん、deploy されている環境で assets が minify されているか確認してますよね?」というお話でした
こういった記事でも参考になる方がいらっしゃれば幸いです