コード分割で JavaScript ペイロードを削減

ほとんどのウェブページとアプリケーションは、さまざまな要素で構成されています。最初のページが読み込まれた直後にアプリケーションを構成する JavaScript をすべて送信する代わりに、JavaScript を複数のチャンクに分割すると、ページのパフォーマンスが向上します。

この Codelab では、コード分割を使用して、3 つの数値を並べ替えるシンプルなアプリのパフォーマンスを改善する方法を説明します。

ブラウザ ウィンドウに Magic Sorter というアプリケーションが表示されている。このアプリケーションには、数値を入力する 3 つのフィールドと並べ替えボタンがある。

測定

これまでと同様に、最適化を追加する前に、まずウェブサイトのパフォーマンスを測定することが重要です。

  1. サイトをプレビューするには、[アプリを表示] を押してから、全画面表示 全画面表示 を押します。
  2. Ctrl+Shift+J キー(Mac の場合は Command+Option+J キー)を押して DevTools を開きます。
  3. [Network] タブをクリックします。
  4. [キャッシュを無効にする] チェックボックスをオンにします。
  5. アプリを再読み込みします。

71.2 KB の JavaScript バンドルが表示されている [Network] パネル。

簡単なアプリケーションで数件の数値を並べ替えるだけでも、71.2 KB 相当の JavaScript で済みます。なぜでしょうか?

ソースコード(src/index.js)では、lodash ライブラリがインポートされ、このアプリで使用されます。Lodash には有用なユーティリティ関数が数多く用意されていますが、ここではパッケージの 1 つのメソッドのみを使用しています。 サードパーティの依存関係のごく一部しか利用されていない状態でインストールしてインポートするのはよくある間違いです。

最適化

バンドルサイズを小さくするには、次のような方法があります。

  1. サードパーティのライブラリをインポートする代わりにカスタムの並べ替え方法を作成する
  2. 組み込みの Array.prototype.sort() メソッドを使用して数値を並べ替える
  3. ライブラリ全体ではなく、lodash から sortBy メソッドのみをインポートしてください。
  4. ユーザーがボタンをクリックしたときにのみ並べ替えるコードをダウンロードする

オプション 1 と 2 は、バンドルサイズを削減するための最適な方法です(実際のアプリではおそらく最も合理的です)。ただし、このチュートリアルでは 推し進める目的では使用しないでください。

オプション 3 と 4 はどちらも、このアプリケーションのパフォーマンスを改善するのに役立ちます。この Codelab の以降のセクションでは、これらのステップについて説明します。他のコーディング チュートリアルと同様に、コードをコピーして貼り付けるのではなく、必ず自分でコードを記述してみてください。

必要なもののみをインポート

lodash から 1 つのメソッドのみをインポートするように、いくつかのファイルを変更する必要があります。まず、package.json でこの依存関係を置き換えます。

"lodash": "^4.7.0",

これを次のように置き換えます。

"lodash.sortby": "^4.7.0",

次に、src/index.js で、次のモジュールをインポートします。

import "./style.css";
import _ from "lodash";
import sortBy from "lodash.sortby";

値の並べ替え方法を更新します。

form.addEventListener("submit", e => {
  e.preventDefault();
  const values = [input1.valueAsNumber, input2.valueAsNumber, input3.valueAsNumber];
  const sortedValues = _.sortBy(values);
  const sortedValues = sortBy(values);

  results.innerHTML = `
    <h2>
      ${sortedValues}
    </h2>
  `
});

アプリケーションを再読み込みして DevTools を開き、[Network] パネルをもう一度確認します。

15.2 KB の JavaScript バンドルが表示されている [Network] パネル。

このアプリでは、ほとんど労力をかけずにバンドルサイズが 4 分の 1 以上削減されましたが、まだ改善の余地があります。

コード分割

webpack は、現在使用されている最も人気のあるオープンソース モジュール バンドラの一つです。つまり、ウェブ アプリケーションを構成するすべての JavaScript モジュール(およびその他のアセット)を、ブラウザで読み取れる静的ファイルにバンドルします。

このアプリケーションで使用される単一のバンドルは、2 つのチャンクに分割できます。

  • 最初のルートを構成するコードの担当者
  • 並べ替えコードを含むセカンダリ チャンク

動的インポートを使用すると、セカンダリ チャンクを遅延読み込みまたはオンデマンドで読み込むことができます。このアプリでは、ユーザーがボタンを押したときにのみ、チャンクを構成するコードを読み込むことができます。

まず、src/index.js の Sort メソッドの最上位のインポートを削除します。

import sortBy from "lodash.sortby";

ボタンが押されたときに起動するイベント リスナー内にインポートします。

form.addEventListener("submit", e => {
  e.preventDefault();
  import('lodash.sortby')
    .then(module => module.default)
    .then(sortInput())
    .catch(err => { alert(err) });
});

import() 機能は、モジュールを動的にインポートする機能を含めるための提案(現在は TC39 プロセスのステージ 3)の一部です。webpack はこの機能をすでにサポートしており、提案で規定されている構文に準拠しています。

import() は Promise を返し、それが解決されると、選択したモジュールが提供され、別のチャンクに分割されます。モジュールが返された後、module.default を使用して、lodash が提供するデフォルトのエクスポートが参照されます。Promise は、sortInput メソッドを呼び出して 3 つの入力値を並べ替える別の .then でチェーンされています。Promise チェーンの最後にはcatch() は、エラーが原因で Promise が拒否されたケースを処理するために使用されます。

最後に行う必要があるのは、ファイルの末尾に sortInput メソッドを記述することです。これは、lodash.sortBy からインポートされたメソッドを受け取る関数を返す関数である必要があります。ネストされた関数は、3 つの入力値を並べ替えて DOM を更新できます。

const sortInput = () => {
  return (sortBy) => {
    const values = [
      input1.valueAsNumber,
      input2.valueAsNumber,
      input3.valueAsNumber
    ];
    const sortedValues = sortBy(values);

    results.innerHTML = `
      <h2>
        ${sortedValues}
      </h2>
    `
  };
}

モニタリング

最後にもう一度アプリケーションを再読み込みし、もう一度 [ネットワーク] パネルを確認します。アプリが読み込まれるとすぐに、小さな初期バンドルのみがダウンロードされます。

2.7 KB の JavaScript バンドルが表示されている [Network] パネル。

ボタンを押して入力番号を並べ替えると、並べ替えコードを含むチャンクが取得されて実行されます。

2.7 KB の JavaScript バンドルに続いて 13.9 KB の JavaScript バンドルが表示されている [Network] パネル。

数値の並び順が変わらないことに注目してください。

まとめ

コード分割と遅延読み込みは、アプリケーションの初期バンドルサイズを削減するための非常に便利な手法です。これにより、ページの読み込み時間が大幅に短縮されます。ただし、この最適化をアプリに組み込む前に、考慮すべき重要な点がいくつかあります。

UI の遅延読み込み

コードの特定のモジュールを遅延読み込みする場合は、ネットワーク接続が脆弱なユーザーにとってどのようなエクスペリエンスになるかを考慮することが重要です。ユーザーがアクションを送信したときに、非常に大きなコードのチャンクを分割して読み込むと、アプリケーションが動作しなくなったように見える可能性があるため、なんらかの読み込みインジケーターを表示することを検討してください。

サードパーティ ノード モジュールの遅延読み込み

アプリケーションでサードパーティの依存関係を遅延読み込みする方法は、必ずしも最適な方法ではなく、使用する場所によって異なります。通常、サードパーティの依存関係は更新頻度が低いため、キャッシュに保存可能な別の vendor バンドルに分割されます。SplitChunksPlugin の活用方法について詳しくは、こちらをご覧ください。

JavaScript フレームワークを使用した遅延読み込み

Webpack を使用する一般的なフレームワークやライブラリの多くは抽象化機能を備えており、アプリケーションの途中で動的インポートを使用するよりも遅延読み込みが容易になります。

動的インポートの仕組みを理解することは役に立ちますが、必ずフレームワークまたはライブラリが推奨する方法を使用して、特定のモジュールを遅延読み込みしてください。

プリロードとプリフェッチ

可能であれば、<link rel="preload"><link rel="prefetch"> などのブラウザのヒントを利用して、重要なモジュールをなるべく早く読み込ませてください。webpack は、インポート ステートメントでマジック コメントを使用することで、両方のヒントをサポートします。詳しくは、重要なチャンクをプリロードするをご覧ください。

コード以外の遅延読み込み

画像は、アプリケーションの大部分を占めている場合があります。スクロールしなければ見えない範囲の下、またはデバイスのビューポートの外側にあるオブジェクトを遅延読み込みすると、ウェブサイトの表示を高速化できます。詳細については、Lazysizes ガイドをご覧ください。