📚

分かった気になるDynamic Import

2022/01/29に公開

はじめに

Dynamic Import は必要な時に、必要なファイルを読み込みます。

Dynamic Importの使用を検討する必要がある検討箇所を複数述べます。

  • static importすると、コードの読み込みが大幅に遅くなり、インポートするコードが必要になる可能性が低くなるか、後で必要になる可能性が低くなります。
  • static importすると、プログラムのメモリー使用量が大幅に増加し、インポートするコードが必要になる可能性が低くなります。
  • 読み込み時に読み込むファイルが存在しないケース
  • 読み込むファイルに副作用があり、ある条件がtrueではない限り、その副作用を望まない場合。(ファイルに副作用がないことを推奨しますが、ファイルの依存関係でこれを制御できない場合があります。)

すべての場合において、Dynamic import を使用するのは賢い判断ではなく、Dynamic import は必要な場合にのみ使用してください。
初期の依存関係をロードするにはstatic importが適していて、tree shakingの恩恵をよりよく受けることができます。

ですが、この説明だけだと分かった気にはなれませんね。
具体的なケースを挙げてみましょう。

具体的に考えよう

今回のチャットアプリケーションでは、4つの重要なコンポーネントがあります。UserInfoChatListChatInputEmojiPickerです。

しかし、これらのコンポーネントのうち、最初のページの読み込み時に即座に使用されるのは、3つだけでUserInfoChatListChatInputです。

EmojiPicker は直接表示されず、ユーザーが EmojiPicker を切り替えるために Emoji をクリックしない場合は、まったく描画されない可能性もあります。
この場合、EmojiPickerファイルを初期バンドルに追加することになり、読み込み時間が長くなる可能性を高くしてしまいました。

これを解決するために、EmojiPickerコンポーネントを動的に読み込むことができます(Dynamic import)。

静的に読み込むのではなく、EmojiPickerを表示するときだけ読み込むのです。

CSR で Dynamic import

Reactでコンポーネントを動的に読み込む簡単な方法は、React Suspenseを使用することです。
React.Suspensionコンポーネントは、動的に読み込むべきコンポーネントを受け取り、EmojiPickerファイルの読み込みを一時停止することで、Appコンポーネントのコンテンツをより速く描画することをできます。
React.Suspensionコンポーネントは、EmojiPickerコンポーネントの読み込みを一時停止します。
ユーザーが絵文字をクリックすると、EmojiPickerコンポーネントが初めて描画される。
EmojiPickerコンポーネントSuspenseコンポーネントを描画し、このコンポーネントが遅延インポートされたモジュール (この場合は EmojiPicker) を受け取ります。Suspense コンポーネントは、fallback prop を受け取り、suspended コンポーネントのロード中にレンダリングされるべきコンポーネントを受け取ります。

EmojiPicker を不要に初期バンドルに追加する代わりに、独自のバンドルに分割し、初期バンドルのサイズを小さくすることができます!

初期バンドルサイズが小さくなれば、初期ロードが速くなります。ユーザーは、長い間、空白のロード画面を見つめる必要がなくなります。
fallbackコンポーネントは、アプリケーションがフリーズしていないことをユーザーに知らせます:ユーザーは、モジュールが処理され実行されるまで少し待つ必要があるだけです。

Asset                             Size         Chunks            Chunk Names
emoji-picker.bundle.js           1.48 KiB      1    [emitted]    emoji-picker
main.bundle.js                   1.33 MiB      main [emitted]    main
vendors~emoji-picker.bundle.js   171 KiB       2    [emitted]    vendors~emoji-picker

従来は初期バンドルが1.5MiBだったのに対し、EmojiPickerの読み込みをSuspence(停止)することで、1.33MiBに減らすことができました。

コンソールでは、EmojiPickerを toggle するまで実行されないことが分かります

import React, { Suspense, lazy } from "react";
// import Send from "./icons/Send";
// import Emoji from "./icons/Emoji";
const Send = lazy(() =>
import(/*webpackChunkName: "send-icon" */ "./icons/Send")
);
const Emoji = lazy(() =>
import(/*webpackChunkName: "emoji-icon" */ "./icons/Emoji")
);
// Lazy load EmojiPicker  when <EmojiPicker /> renders
const Picker = lazy(() =>
import(/*webpackChunkName: "emoji-picker" */ "./EmojiPicker")
);

const ChatInput = () => {
const [pickerOpen, togglePicker] = React.useReducer(state => !state, false);

    return (
      <Suspense fallback={<p id="loading">Loading...</p>}>
        <div className="chat-input-container">
          <input type="text" placeholder="Type a message..." />
          <Emoji onClick={togglePicker} />
          {pickerOpen && <Picker />}
          <Send />
        </div>
      </Suspense>
    );
};

console.log("ChatInput loaded", Date.now());

export default ChatInput;

アプリケーションをビルドすると、Webpackが作成したさまざまなバンドルが表示されます。

EmojiPickerコンポーネントを動的に読み込むことで、最初のバンドルサイズを 1.5MiB から 1.33MiB に減らすことに成功しました!
EmojiPicker が完全に読み込まれるまで、ユーザーはまだしばらく待たなければなりませんが、ユーザーがコンポーネントの読み込みを待っている間、アプリケーションが描画され、インタラクティブになるようにすることで、UXを向上させることができました。

loadable components

SSR(サーバーサイドレンダリング)はまだReact Suspenseをサポートしていません。
React Suspenseに代わるものとして、loadable-componentsライブラリがあり、SSR で使用できます。

import React from "react";
import loadable from "@loadable/component";

import Send from "./icons/Send";
import Emoji from "./icons/Emoji";

const EmojiPicker = loadable(() => import("./EmojiPicker"), {
  fallback: <div id="loading">Loading...</div>
});

const ChatInput = () => {
  const [pickerOpen, togglePicker] = React.useReducer(state => !state, false);

  return (
    <div className="chat-input-container">
      <input type="text" placeholder="Type a message..." />
      <Emoji onClick={togglePicker} />
      {pickerOpen && <EmojiPicker />}
      <Send />
    </div>
  );
};

export default ChatInput;

React Suspense と同様に、lazy importされたファイルを loadable に渡すことで、EmojiPickerファイルがリクエストされたときだけ、ファイルを読み込みます。
ファイルが読み込みされている間、fallbackコンポーネントを描画することができます。

loadable コンポーネントは SSR で React Suspense に代わる素晴らしい方法ですが、CSR でもファイルの読み込みを一時停止するために有用そうですね。

import React from "react";
  import Send from "./icons/Send";
  import Emoji from "./icons/Emoji";
  import loadable from "@loadable/component";

  const EmojiPicker = loadable(() => import("./components/EmojiPicker"), {
    fallback: <p id="loading">Loading...</p>
  });

  const ChatInput = () => {
    const [pickerOpen, togglePicker] = React.useReducer(state => !state, false);

    return (
      <div className="chat-input-container">
        <input type="text" placeholder="Type a message..." />
        <Emoji onClick={togglePicker} />
        {pickerOpen && <EmojiPicker />}
        <Send />
      </div>
    );
  };

  console.log("ChatInput loaded", Date.now());

  export default ChatInput;

こちらの記事を参考にしております。(とても面白い記事ですので、ぜひ一読してみてください)

GitHubで編集を提案

Discussion