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

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

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

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

測定

いつものように、最適化を行う前に、まずウェブサイトのパフォーマンスを測定することが重要です。

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

71.2 KB の JavaScript バンドルを示すネットワーク パネル。

シンプルなアプリケーションで数値を並べ替えるためだけに、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 から単一のメソッドのみをインポートするように、いくつかのファイルを変更する必要があります。まず、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 を開いて、[ネットワーク] パネルをもう一度確認します。

15.2 KB の JavaScript バンドルを示すネットワーク パネル。

このアプリでは、ほとんど作業をせずにバンドルサイズを 4 倍以上削減できましたが、まだ改善の余地があります。

コードの分割

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

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

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

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

まず、src/index.js の並べ替えメソッドの上位レベルのインポートを削除します。

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 バンドルを示すネットワーク パネル。

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

2.7 KB の JavaScript バンドルと、それに続く 13.9 KB の JavaScript バンドルを示すネットワーク パネル。

引き続き数字が並べ替えられていることに注目してください。

まとめ

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

遅延読み込み UI

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

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

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

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

webpack を使用する一般的なフレームワークやライブラリの多くは、アプリケーションの途中で動的インポートを使用するよりも簡単に遅延読み込みを行えるように抽象化を提供しています。

動的インポートの仕組みを理解することは有用ですが、特定のモジュールを遅延読み込みする場合は、フレームワーク / ライブラリで推奨されているメソッドを常に使用してください。

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

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

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

画像はアプリの重要な部分を占めることがあります。アバブ ザ フォールドまたはデバイスのビューポートの外側にあるものを遅延読み込みすると、ウェブサイトの速度を上げることができます。詳しくは、Lazysizes ガイドをご覧ください。