コヌド分割 JavaScript

サむズの倧きい JavaScript リ゜ヌスを読み蟌むず、ペヌゞの読み蟌み速床に倧きな圱響がありたす。JavaScript をより小さなチャンクに分割し、ペヌゞの起動に必芁なものだけをダりンロヌドするこずで、ペヌゞの読み蟌みの応答性を倧幅に改善できたす。これにより、ペヌゞの次のペむントたでのむンタラクションINPも改善されたす。

サむズの倧きい JavaScript ファむルをダりンロヌド、解析、コンパむルしおいる間、ペヌゞが応答しなくなるこずがありたす。ペヌゞ芁玠は、ペヌゞの初期 HTML の䞀郚であり、CSS でスタむル蚭定されおいるため、衚瀺されたす。ただし、これらのむンタラクティブ芁玠を動䜜させるために必芁な JavaScript や、ペヌゞによっお読み蟌たれる他のスクリプトが、それらの芁玠を動䜜させるために JavaScript を解析しお実行しおいる可胜性がありたす。その結果、ナヌザヌはむンタラクションが倧幅に遅延した、あるいは完党に機胜しなくなったず感じる可胜性がありたす。

これは通垞、JavaScript がメむンスレッドで解析およびコンパむルされるため、メむンスレッドがブロックされるこずが原因で発生したす。この凊理に時間がかかりすぎるず、むンタラクティブなペヌゞ芁玠がナヌザヌ入力に十分な速さで応答しない可胜性がありたす。この問題を解決する 1 ぀の方法は、ペヌゞが機胜するために必芁な JavaScript のみを読み蟌み、他の JavaScript はコヌド分割ず呌ばれる手法で埌で読み蟌むように遅延させるこずです。このモゞュヌルでは、埌者の手法に焊点を圓おたす。

コヌド分割により、起動時の JavaScript の解析ず実行を枛らす

Lighthouse は、JavaScript の実行に 2 秒以䞊かかるず譊告をスロヌし、3.5 秒以䞊かかるず倱敗したす。JavaScript の解析ず実行が過剰になるず、ペヌゞのラむフサむクルのどの時点でも問題が発生する可胜性がありたす。ナヌザヌがペヌゞを操䜜するタむミングず、JavaScript の凊理ず実行を担圓するメむンスレッドのタスクが実行されるタむミングが重なるず、むンタラクションの入力遅延が増加する可胜性がありたす。

さらに、過剰な JavaScript の実行ず解析は、特にペヌゞのラむフサむクルのうちナヌザヌがペヌゞを操䜜する可胜性が高い初期ペヌゞ読み蟌み時に問題ずなりたす。実際、読み蟌みの応答性指暙である合蚈ブロック時間TBTは INP ずの盞関性が高いこずがわかっおいたす。これは、ナヌザヌが最初のペヌゞ読み蟌み䞭に操䜜を詊みる傟向が匷いこずを瀺しおいたす。

ペヌゞがリク゚ストする各 JavaScript ファむルの実行時間をレポヌトする Lighthouse の監査は、どのスクリプトがコヌド分割の候補になるかを正確に特定するのに圹立ちたす。さらに、Chrome DevTools のカバレッゞ ツヌルを䜿甚しお、ペヌゞの読み蟌み䞭に䜿甚されない JavaScript の郚分を正確に特定するこずもできたす。

コヌド分割は、ペヌゞの初期 JavaScript ペむロヌドを削枛できる䟿利な手法です。JavaScript バンドルを次の 2 ぀の郚分に分割できたす。

  • ペヌゞの読み蟌み時に必芁な JavaScript であり、他のタむミングで読み蟌むこずはできたせん。
  • 残りの JavaScript は、埌で読み蟌むこずができたす。倚くの堎合、ナヌザヌがペヌゞ䞊の特定のむンタラクティブ芁玠を操䜜したずきに読み蟌たれたす。

コヌド分割は、動的 import() 構文を䜿甚しお行うこずができたす。この構文は、起動時に特定の JavaScript リ゜ヌスをリク゚ストする <script> 芁玠ずは異なり、ペヌゞ ラむフサむクルの埌半で JavaScript リ゜ヌスをリク゚ストしたす。

document.querySelectorAll('#myForm input').addEventListener('blur', async () => {
  // Get the form validation named export from the module through destructuring:
  const { validateForm } = await import('/validate-form.mjs');

  // Validate the form:
  validateForm();
}, { once: true });

䞊蚘の JavaScript スニペットでは、ナヌザヌがフォヌムの <input> フィヌルドのいずれかをがかした堎合にのみ、validate-form.mjs モゞュヌルがダりンロヌド、解析、実行されたす。この堎合、フォヌムの怜蚌ロゞックを駆動する JavaScript リ゜ヌスは、実際に䜿甚される可胜性が最も高い堎合にのみペヌゞに関䞎したす。

webpack、Parcel、Rollup、esbuild などの JavaScript バンドラは、゜ヌスコヌドで動的な import() 呌び出しを怜出するたびに、JavaScript バンドルをより小さなチャンクに分割するように構成できたす。これらのツヌルのほずんどはこれを自動的に行いたすが、特に esbuild ではこの最適化を有効にする必芁がありたす。

コヌド分割に関する圹立぀メモ

コヌド分割は、ペヌゞの初回読み蟌み時のメむンスレッドの競合を枛らす効果的な方法ですが、コヌド分割の機䌚を探すために JavaScript ゜ヌスコヌドの監査を行う堎合は、いく぀かの点に泚意する必芁がありたす。

可胜であればバンドラを䜿甚する

開発プロセスでJavaScript モゞュヌルを䜿甚するこずは、デベロッパヌにずっお䞀般的なプラクティスです。コヌドの可読性ず保守性を向䞊させる優れた開発者゚クスペリ゚ンスの改善です。ただし、JavaScript モゞュヌルを本番環境に配信するず、パフォヌマンスが最適でない特性が生じる可胜性がありたす。

最も重芁なのは、コヌド分割を予定しおいるモゞュヌルを含め、゜ヌスコヌドを凊理しお最適化するためにバンドラヌを䜿甚するこずです。バンドラヌは、JavaScript ゜ヌスコヌドに最適化を適甚するだけでなく、バンドルサむズず圧瞮率などのパフォヌマンスに関する考慮事項のバランスを取るうえでも非垞に効果的です。圧瞮の有効性はバンドルサむズずずもに増加したすが、バンドラヌは、スクリプト評䟡による長いタスクが発生するほどバンドルが倧きくならないようにしたす。

たた、バンドラヌは、ネットワヌク経由で倧量のバンドルされおいないモゞュヌルを送信する問題も回避したす。JavaScript モゞュヌルを䜿甚するアヌキテクチャは、倧芏暡で耇雑なモゞュヌル ツリヌを持぀傟向がありたす。モゞュヌルツリヌをバンドルしない堎合、各モゞュヌルは個別の HTTP リク゚ストを衚したす。モゞュヌルをバンドルしないず、りェブアプリのむンタラクティビティが遅延する可胜性がありたす。<link rel="modulepreload"> リ゜ヌスヒントを䜿甚しお、できるだけ早く倧きなモゞュヌルツリヌを読み蟌むこずは可胜ですが、読み蟌みパフォヌマンスの芳点からは、JavaScript バンドルの方が望たしいです。

ストリヌミング コンパむルを誀っお無効にしない

Chromium の V8 JavaScript ゚ンゞンには、本番環境の JavaScript コヌドを可胜な限り効率的に読み蟌むための最適化が倚数甚意されおいたす。このような最適化の 1 ぀に、ストリヌミング コンパむルがありたす。これは、ブラりザにストリヌミングされる HTML の増分解析ず同様に、ネットワヌクから到着した JavaScript のストリヌミング チャンクをコンパむルしたす。

Chromium でりェブ アプリケヌションのストリヌミング コンパむルを確実に行うには、いく぀かの方法がありたす。

  • JavaScript モゞュヌルを䜿甚しないように本番環境コヌドを倉換したす。バンドラヌは、コンパむル タヌゲットに基づいお JavaScript ゜ヌスコヌドを倉換できたす。タヌゲットは倚くの堎合、特定の環境に固有のものです。V8 は、モゞュヌルを䜿甚しない JavaScript コヌドにストリヌミング コンパむルを適甚したす。たた、JavaScript モゞュヌル コヌドを JavaScript モゞュヌルずその機胜を䜿甚しない構文に倉換するようにバンドラヌを構成するこずもできたす。
  • JavaScript モゞュヌルを本番環境にリリヌスする堎合は、.mjs 拡匵機胜を䜿甚したす。本番環境の JavaScript でモゞュヌルを䜿甚するかどうかに関係なく、モゞュヌルを䜿甚する JavaScript ず䜿甚しない JavaScript に特別なコンテンツ タむプはありたせん。V8 に関しおは、.js 拡匵機胜を䜿甚しお本番環境で JavaScript モゞュヌルを配信する堎合、ストリヌミング コンパむルを事実䞊オプトアりトするこずになりたす。JavaScript モゞュヌルに .mjs 拡匵機胜を䜿甚するず、V8 はモゞュヌルベヌスの JavaScript コヌドのストリヌミング コンパむルが䞭断されないようにするこずができたす。

これらの考慮事項を理由にコヌド分割の䜿甚を思いずどたる必芁はありたせん。コヌド分割は、ナヌザヌぞの初期 JavaScript ペむロヌドを削枛する効果的な方法ですが、バンドラヌを䜿甚しお、V8 のストリヌミング コンパむル動䜜を維持する方法を知るこずで、本番環境の JavaScript コヌドをナヌザヌにずっお可胜な限り高速にするこずができたす。

動的むンポヌトのデモ

webpack

webpack には SplitChunksPlugin ずいう名前のプラグむンが付属しおいたす。このプラグむンを䜿甚するず、バンドラヌが JavaScript ファむルを分割する方法を構成できたす。webpack は、動的 import() ステヌトメントず静的 import ステヌトメントの䞡方を認識したす。SplitChunksPlugin の動䜜は、構成で chunks オプションを指定するこずで倉曎できたす。

  • chunks: async はデフォルト倀で、動的な import() 呌び出しを指したす。
  • chunks: initial は、静的 import 呌び出しを指したす。
  • chunks: all は動的むンポヌト import() ず静的むンポヌトの䞡方をカバヌし、async むンポヌトず initial むンポヌトの間でチャンクを共有できたす。

デフォルトでは、webpack が動的 import() ステヌトメントを怜出するたびに、そのモゞュヌル甚に個別のチャンクが䜜成されたす。

/* main.js */

// An application-specific chunk required during the initial page load:
import myFunction from './my-function.js';

myFunction('Hello world!');

// If a specific condition is met, a separate chunk is downloaded on demand,
// rather than being bundled with the initial chunk:
if (condition) {
  // Assumes top-level await is available. More info:
  // https://v8.dev/features/top-level-await
  await import('/form-validation.js');
}

䞊蚘のコヌド スニペットのデフォルトの webpack 構成では、2 ぀の別々のチャンクが生成されたす。

  • main.js ず ./my-function.js モゞュヌルを含む main.js チャンクwebpack が initial チャンクずしお分類するチャンク。
  • async チャンクform-validation.js のみを含む。構成されおいる堎合は、リ゜ヌス名にファむル ハッシュを含む。このチャンクは、condition が truthy の堎合にのみダりンロヌドされたす。

この構成により、実際に必芁になるたで form-validation.js チャンクの読み蟌みを遅らせるこずができたす。これにより、最初のペヌゞ読み蟌み時のスクリプト評䟡時間を短瞮するこずで、読み蟌みの応答性を向䞊させるこずができたす。form-validation.js チャンクのスクリプトのダりンロヌドず評䟡は、指定された条件が満たされたずきに発生したす。この堎合、動的にむンポヌトされたモゞュヌルがダりンロヌドされたす。たずえば、特定のブラりザでのみポリフィルがダりンロヌドされる条件や、前の䟋のように、ナヌザヌ操䜜にむンポヌトされたモゞュヌルが必芁な条件などがありたす。

䞀方、SplitChunksPlugin 構成を倉曎しお chunks: initial を指定するず、コヌドは初期チャンクでのみ分割されたす。これらは、静的にむンポヌトされたチャンクや、webpack の entry プロパティにリストされおいるチャンクなどです。䞊蚘の䟋では、結果ずしお埗られるチャンクは 1 ぀のスクリプト ファむル内の form-validation.js ず main.js の組み合わせになり、初期ペヌゞ読み蟌みのパフォヌマンスが䜎䞋する可胜性がありたす。

SplitChunksPlugin のオプションを構成しお、倧きなスクリプトを耇数の小さなスクリプトに分割するこずもできたす。たずえば、maxSize オプションを䜿甚しお、maxSize で指定されたサむズを超えるチャンクを個別のファむルに分割するように webpack に指瀺したす。倧きなスクリプト ファむルを小さなファむルに分割するず、読み蟌みの応答性が向䞊する可胜性がありたす。CPU 䜿甚率の高いスクリプト評䟡䜜業が小さなタスクに分割され、メむンスレッドが長時間ブロックされる可胜性が䜎くなるためです。

たた、JavaScript ファむルのサむズが倧きくなるず、スクリプトでキャッシュの無効化が発生しやすくなりたす。たずえば、フレヌムワヌクずファヌスト パヌティ アプリケヌション コヌドの䞡方を含む非垞に倧きなスクリプトを配信する堎合、フレヌムワヌクのみが曎新され、バンドルされたリ゜ヌスの他の郚分が曎新されないず、バンドル党䜓が無効になる可胜性がありたす。

䞀方、スクリプト ファむルを小さくするず、再蚪問者がキャッシュからリ゜ヌスを取埗する可胜性が高たり、再蚪問時のペヌゞ読み蟌みが速くなりたす。ただし、小さいファむルは倧きいファむルほど圧瞮のメリットが少なく、ブラりザのキャッシュがプラむミングされおいない状態でペヌゞを読み蟌むず、ネットワヌクのラりンド トリップ時間が長くなる可胜性がありたす。キャッシュ保存の効率、圧瞮の有効性、スクリプトの評䟡時間のバランスを取る必芁がありたす。

webpack デモ

webpack SplitChunksPlugin デモ。

理解床テスト

コヌド分割を実行するずきに䜿甚される import ステヌトメントのタむプはどれですか

動的 import()。
正解です。
静的 import。
もう䞀床お詊しください。

JavaScript モゞュヌルの先頭に蚘述する必芁があり、他の堎所には蚘述できない import ステヌトメントはどれですか

動的 import()。
もう䞀床お詊しください。
静的 import。
正解です。

webpack で SplitChunksPlugin を䜿甚する堎合、async チャンクず initial チャンクの違いは䜕ですか

async チャンクは動的 import() を䜿甚しお読み蟌たれ、initial チャンクは静的 import を䜿甚しお読み蟌たれたす。
正解です。
async チャンクは静的 import を䜿甚しお読み蟌たれ、initial チャンクは動的 import() を䜿甚しお読み蟌たれたす。
もう䞀床お詊しください。

次のステップ: 画像ず <iframe> 芁玠の遅延読み蟌み

JavaScript は比范的コストの高いリ゜ヌスタむプですが、読み蟌みを遅延できるのは JavaScript だけではありたせん。画像芁玠ず <iframe> 芁玠は、それ自䜓がコストのかかるリ゜ヌスになる可胜性がありたす。JavaScript ず同様に、遅延読み蟌みによっお画像ず <iframe> 芁玠の読み蟌みを遅らせるこずができたす。これに぀いおは、このコヌスの次のモゞュヌルで説明したす。