コード分割で 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 と連結されています。プロミス チェーンの最後で、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 は、import ステートメントでマジック コメントを使用して、両方のヒントをサポートしています。詳しくは、重要なチャンクをプリロードするガイドをご覧ください。

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

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