rails asset pipeline JSが、developmentでは動いてproductionでは動かないということはあり得る

滅多にないとは思うが、実際そういうことが昨日起きて、しばらく悩んだのでメモしておく。
「コレが原因でこういう現象が起こるケースがありました」という報告なので、そのつもりで読んで欲しい。

まず前提知識

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.jstetherが未ロードだと例外を投げる。

したがって、developmentでは、bootstrap.jsの一部の機能が動かない状態になっていた。しかし、まだこのアプリケーションでは、bootstrap.jsの機能をゴリッと使っている場所がなかったため、問題なく動いているように見えた。しかし、productionでは、先に述べたように後続のコードが一切実行されなくなってしまうので、全く動かなくなっていた。

注記

※ 記事中では「production」と言っていますが、業務上ではユーザに影響与える前にstagingで食い止めています