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

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

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

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

測定

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

  1. サイトをプレビューするには、[アプリを表示] を押し、[全画面表示] 全画面表示 を押します。
  2. `Ctrl+Shift+J`(Mac の場合は `Command+Option+J`)を押して、デベロッパー ツールを開きます。
  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 は、3 つの入力値を並べ替える sortInput メソッドを呼び出す別の .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

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

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

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

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

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

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

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

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

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

画像はアプリケーションの重要な部分を占めることがあります。ファーストビューの下にある画像や、デバイスのビューポートの外にある画像を遅延読み込みすると、ウェブサイトの速度を向上させることができます。詳しくは、Lazysizes ガイドをご覧ください。