rails asset pipeline JSが、developmentでは動いてproductionでは動かないということはあり得る
滅多にないとは思うが、実際そういうことが昨日起きて、しばらく悩んだのでメモしておく。
「コレが原因でこういう現象が起こるケースがありました」という報告なので、そのつもりで読んで欲しい。
- rails 5.0.2
まず前提知識
rails asset pipelineは、独自のconcat機能を持っている。マジックコメント的に、例えばJavaScriptであれば、
//= require jquery
のように書いておくと、1ファイルにまとめた.js
ファイルを出力してくれるので、たくさんのライブラリを使ったり、アプリケーションコードを複数のファイルで分割管理していても、その分だけユーザにHTTP GETリクエストを送らせる必要がなく、ロード時間が短くなるというもの。機能自体は何も珍しいものではないが、railsは自前で(正確にはsprockets
が)この機能を提供している。
で、この機能はユーザフレンドリーなのだが、物理的にデバッグし難くなるという問題がある。 この問題に対し、rails + sprocketsの場合は、これらが密結合しているという利点を活かした、以下のようなアプローチを取っている。
- development環境で実行しているときは、concatやminifyをせずに、ファイル数分の
<script>
タグを発行し、書かれたコードのままのJSを出力する - production環境では、事前にconcatやminifyしておいたファイルを作成しておき、そのファイルに静的アクセスできるような
<script>
タグを1つ出力する
※ development
<script src="/assets/jquery.self-bd7ddd393353a8d2480a622e80342adf488fb6006d667e8b42e4c0073393abee.js?body=1"></script> <script src="/assets/jquery_ujs.self-784a997f6726036b1993eb2217c9cb558e1cbb801c6da88105588c56f13b466a.js?body=1"></script> <script src="/assets/application.self-0e1103bd72084f20bc17863434595ea99e999bcf530d80110698608f35ff5620.js?body=1"></script>
※ production
<script src="/assets/application-9941e475b91f6ec13adf5f910b2a3c0fbcbc8a5049089243cd544e0b43744b46.js"></script>
この違いがもたらす副作用
つまり、1ファイル1タグの場合と、複数ファイル複数タグの場合で挙動が同じであれば良いが、違うケースがあると、「developmentとproductionで挙動が違う」という事象につながるので注意が必要になる。
で、実際に起ったのが、JS実行時エラーが発生した場合だ。
developmentの場合は実行時エラーがあっても、次以降の<script>
タグは改めて実行されるが、productionの場合は1ファイルにconcatされているので、以後が実行されない。
今回悩む羽目になった理由
「次以降のscriptが実行されても、手前がコケてるなら後ろも巻き込まれてエラーになるから、development環境で気付けるのでは?」と思うかもしれないが、そうとは限らなかった。
bootstrap4を使っていた。導入手順に書いてあるように、本来はtether
を先にロードしておかなければならないのだが、これが漏れている箇所があった。bootstrap.js
はtether
が未ロードだと例外を投げる。
したがって、developmentでは、bootstrap.js
の一部の機能が動かない状態になっていた。しかし、まだこのアプリケーションでは、bootstrap.js
の機能をゴリッと使っている場所がなかったため、問題なく動いているように見えた。しかし、productionでは、先に述べたように後続のコードが一切実行されなくなってしまうので、全く動かなくなっていた。
注記
※ 記事中では「production」と言っていますが、業務上ではユーザに影響与える前にstagingで食い止めています