esm アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

Railsアプリケーションでフロントエンドのパフォーマンス改善:初歩編

この記事は ESM Advent Calendar 2021 - Adventar の21日目の記事です。

こんにちは、swamp09です。

とあるRailsアプリケーション開発プロジェクトで、フロントエンドのパフォーマンスを計測するのに Lighthouse を使いました。パフォーマンスの項目を見てみると「レンダリングを妨げるリソースの除外」が指摘されており中身をみるとapplication.jsで多くの無駄が発生しているようです。Railsアプリケーションでは application.js が肥大化してしまうケースがたまに見られますね。 Eliminate render-blocking resources を読むと、まずはカバレッジを見て必要なリソースだけを読み込むこと、次いでスクリプトがレンダリングブロックしないようにするとのことです。ある画面ではGoogle ChromeのDevToolsのカバレッジを見るとapplication.jsは91%使用していないという結果になっていました、なんと…。未使用のコードが多すぎるという問題は画面ごとに必要なJSだけ読み込むように地道にリファクタリングしていくしかないですね…。

レンダリングブロックを除く

スクリプトがレンダリングブロックしないようにするには、application.jsを読み込みをasyncdeferにします。 これらがないとJavaScriptがHTMLパーサーをブロックしてしまうのでパフォーマンスに悪影響があります。asyncdeferの違いは、 ブラウザの仕組み: 最新ウェブブラウザの内部構造 の スクリプトとスタイル シートの処理順序 によると、asyncがHTMLのパースとは別スレッドで処理され、deferはHTMLのパースが終わってから順番に処理されるということのようです。application.jsではライブラリの読み込みなどを行っており、読み込む順序を意識する必要があったのでdeferを使うことにして、application.jsの読み込みを javascript_include_tag 'application', defer: true のように変更しました。

ちなみにパーサーブロックとレンダリングブロックは微妙に違うようなのですが、MDNではパーサーブロックと書いてあり Eliminate render-blocking resources ではレンダリングブロックと書いてあります。あんまり気にしなくて良いのかな…。

インラインスクリプトの対応が必要だった

さておき、よしこれでパーサー|レンダリング ブロックしなくなって良くなったな!!と思いきや問題が起き、application.jsで読み込んでいるライブラリに依存しているコードがインラインスクリプトにありそれらが動作しなくなってしまいました。じゃあインラインじゃなくして全部JSファイルにしてdeferで読み込めばいいか!!!と思ったのですがそんなことはなく、レンダリングを妨げる JavaScript を削除する によると、小さいコードならインラインが最適なケースがありなんでもかんでもJSファイルに切り出すのは良くないようです。

インラインスクリプトのままでJSファイルにあるライブラリ読み込みの遅延を待つために、DOMContentLoadedでライブラリの読み込みを待ったあとで処理を開始するようにします。defer によると「defer 属性の付いたスクリプトは、スクリプトが読み込まれて評価が完了するまで、 DOMContentLoaded イベントの発生が抑制されます。」とのことなのでdeferでライブラリを読み込み DOMContentLoadedイベントを待てば安心ですね。

おまけ

余談なのですが、最近リリースされたRails 7.0ではデフォルトでimportmap を使用しています。importmapはES Modulesを読み込むもので、ES moduleの読み込みはクラシックスクリプトとは別でモジュールスクリプトとなっており、defer は「defer 属性はモジュールスクリプトには効果がありません。」とのことなのでasyncしか使えないようです。deferとの違いを意識していないとimportmapを使うとき処理順序が起因のエラーが起きるのかもしれません。