Sprocketsの初期化順序問題を解決し、アセット読み込みエラーを修正
ViewComponent 4.0以降で発生していたSprocketsのアセット解決エラーが修正されました。この問題は、RailsエンジンとRailtieの初期化順序に起因しており、eager_load が有効な環境でのみ顕在化していました。
背景
ViewComponent 4.0へのアップグレード後、CI環境など eager_load が有効な環境でのみ、image_tag を使用したコンポーネントが Sprockets::Rails::Helper::AssetNotFound エラーを発生させる問題が報告されました。同じテストをeager loadingなしで実行すると正常に動作するため、初期化のタイミングに問題があることが示唆されていました。
#2433 の調査により、ViewComponentが参照する resolve_assets_with が nil になっていることが判明しました。一方、Rails.application.config.assets.resolve_with は正しく [:manifest, :environment] を保持していました。この不整合は、ViewComponentとSprocketsの after_initialize ブロックの実行順序によって引き起こされていました。
技術的な変更
lib/view_component/engine.rb の初期化フックが after_initialize から after_routes_loaded に変更されました。これにより、Sprocketsの設定が完全に初期化された後にViewComponentの設定がコピーされることが保証されます。
変更前:
config.after_initialize do |app|
ActiveSupport.on_load(:view_component) do
if defined?(Sprockets::Rails)
include Sprockets::Rails::Helper
# Copy relevant config to VC context
self.debug_assets = app.config.assets.debug
変更後:
config.after_routes_loaded do
ActiveSupport.on_load(:view_component) do
if defined?(Sprockets::Rails)
include Sprockets::Rails::Helper
app = Rails.application
# Copy relevant config to VC context
self.debug_assets = app.config.assets.debug
変更に伴い、ブロック引数として渡されていた app は削除され、ブロック内で明示的に Rails.application を参照するようになりました。after_routes_loaded はブロック引数を提供しないため、この調整が必要でした。
設計判断
Railsの初期化ライフサイクルにおいて、after_routes_loaded コールバック が採用されました。
Railsは after_initialize ブロックを登録順に実行します。ViewComponentエンジンは、Sprockets Railtieが先に after_initialize ブロックを登録することを暗黙的に前提としていましたが、登録順序は保証されていませんでした。この問題は、eager loadingが有効な場合により顕著に現れました。
after_routes_loaded は、すべての初期化処理と after_initialize ブロックの実行が完了した後に呼び出されます。このコールバックを使用することで、Sprocketsの設定(config.assets.resolve_with など)が確実に設定された状態でViewComponentの初期化を実行できます。
このアプローチは、他のRailtieやエンジンとの初期化順序の依存関係を明示的に解消し、登録順序に依存しない堅牢な実装を実現しています。
まとめ
本PRは、ViewComponentの初期化フックを after_initialize から after_routes_loaded に移動することで、Sprocketsとの初期化順序問題を解決しました。この変更により、eager loadingが有効な環境でもアセット解決が正常に動作するようになり、CI環境での信頼性が向上しています。Railsの初期化ライフサイクルを正しく理解し活用することで、エンジン間の暗黙的な依存関係を排除した設計です。