メインコンテンツまでスキップ

動的`import()`

· 約6分
Mathias Bynens ([@mathias](https://twitter.com/mathias))

動的import()は、静的importと比べて新しい可能性を引き出す、関数のような形式の新しいimportを導入します。この記事では両者を比較し、新しい内容の概要を説明します。

静的import(おさらい)

Chrome 61では、モジュール内でのES2015 importステートメントのサポートが出荷されました。

./utils.mjsにある次のモジュールを考えてみましょう:

// デフォルトエクスポート
export default () => {
console.log('デフォルトエクスポートからこんにちは!');
};

// 名前付きエクスポート`doStuff`
export const doStuff = () => {
console.log('作業中…');
};

./utils.mjsモジュールを静的にインポートして使用する方法は次の通りです:

<script type="module">
import * as module from './utils.mjs';
module.default();
// → 'デフォルトエクスポートからこんにちは!'をログに出力
module.doStuff();
// → '作業中…'をログに出力
</script>
注記

注記: 上記の例では.mjs拡張子を使用して、それが通常のスクリプトではなくモジュールであることを示しています。ただし、Webではファイル拡張子はあまり重要ではなく、ファイルが正しいMIMEタイプ(例: JavaScriptファイルの場合text/javascript)でContent-Type HTTPヘッダーで提供されていれば問題ありません。

.mjs拡張子は、Node.jsd8など他のプラットフォームで特に有用です。これらのプラットフォームでは、MIMEタイプやtype="module"のようなスクリプトとモジュールを区別するための必須フックの概念がありません。一貫性を持たせるため、そしてモジュールと通常のスクリプトを明確に区別するため、この拡張子を使用しています。

モジュールをインポートするこの構文形式は静的宣言です: モジュール指定子として文字列リテラルのみを受け入れ、実行前の「リンク」プロセスを介してローカルスコープにバインディングを作成します。静的import構文はファイルのトップレベルでのみ使用できます。

静的importは、静的解析、バンドルツール、ツリーシェイキングなど、重要なユースケースを可能にします。

ただし、次の場合に役立つことがあります:

  • オンデマンド(または条件付き)でモジュールをインポートする
  • 実行時にモジュール指定子を計算する
  • 通常のスクリプト(モジュールではなく)内からモジュールをインポートする

これらのどれも静的importで実現することはできません。

動的import() 🔥

動的import()はこれらのユースケースに対応する新しい関数のような形式のimportを導入します。import(moduleSpecifier)は、依存モジュールすべておよびそのモジュール自体をフェッチ、インスタンス化、評価した後に作成される、要求されたモジュールの名前空間オブジェクトのPromiseを返します。

./utils.mjsモジュールを動的にインポートして使用する方法を以下に示します:

<script type="module">
const moduleSpecifier = './utils.mjs';
import(moduleSpecifier)
.then((module) => {
module.default();
// → 'デフォルトエクスポートからこんにちは!'をログに出力
module.doStuff();
// → '作業中…'をログに出力
});
</script>

import()はPromiseを返すため、thenベースのコールバックスタイルの代わりにasync/awaitを使用することも可能です:

<script type="module">
(async () => {
const moduleSpecifier = './utils.mjs';
const module = await import(moduleSpecifier)
module.default();
// → 'デフォルトエクスポートからこんにちは!'をログに出力
module.doStuff();
// → '作業中…'をログに出力
})();
</script>
注記

注記: import()は関数呼び出しのように_見えます_が、カッコを使用する単なる構文として指定されています(super()に似ています)。したがって、importFunction.prototypeから継承していないため、callapplyをすることはできず、const importAlias = importのようなことも動作しません。そもそもimportはオブジェクトですらないのです。しかしこれは実際には問題になりません。

動的import()を使用することで、小規模なシングルページアプリケーション内でナビゲーション時にモジュールを遅延ロードする例を以下に示します:

<!DOCTYPE html>
<meta charset="utf-8">
<title>私のライブラリ</title>
<nav>
<a href="books.html" data-entry-module="books"></a>
<a href="movies.html" data-entry-module="movies">映画</a>
<a href="video-games.html" data-entry-module="video-games">ビデオゲーム</a>
</nav>
<main>オンデマンドでロードされるコンテンツ用のプレースホルダです。</main>
<script>
const main = document.querySelector('main');
const links = document.querySelectorAll('nav > a');
for (const link of links) {
link.addEventListener('click', async (event) => {
event.preventDefault();
try {
const module = await import(`/${link.dataset.entryModule}.mjs`);
// モジュールは`loadPageInto`という名前の関数をエクスポートしています。
module.loadPageInto(main);
} catch (error) {
main.textContent = error.message;
}
});
}
</script>

動的import()で有効化される遅延読み込み機能は、正しく適用されると非常に強力です。デモとして、Addyは、すべての依存関係を初回読み込み時に静的にインポートしていた例のHacker News PWAを変更しました。更新されたバージョンでは、動的import()を使用してコメントを遅延ロードし、ユーザーが実際に必要とするまでのロード、解析、およびコンパイルコストを回避しています。

注記

注: アプリが別のドメインからスクリプトをインポートする場合(静的または動的)、スクリプトには有効なCORSヘッダー(例: Access-Control-Allow-Origin: *)が含まれる必要があります。これは、通常のスクリプトとは異なり、モジュールスクリプト(およびそのインポート)はCORSでフェッチされるためです。

推奨事項

静的importと動的import()はどちらも有用です。それぞれ非常に異なる使用ケースがあります。静的importは初回描画の依存関係、特に画面上部のコンテンツに利用してください。それ以外の場合は、要求に応じて動的import()で依存関係を読み込むことを検討してください。

動的import()のサポート