import()
import()
構文は、よくダイナミックインポートと呼ばれますが、非同期かつ動的に、 ECMAScript モジュールを、潜在的にモジュールではない環境に読み込めるようにする関数風の式です。
宣言スタイルのものとは異なり、動的インポートは必要なときだけ評価され、より柔軟な構文が可能になります。
構文
import(moduleName)
import()
呼び出しは関数呼び出しによく似た構文ですが、import
自体はキーワードであり、関数ではありません。const myImport = import
のようなエイリアスを作成することはできず、SyntaxError
が発生します。
引数
moduleName
インポート元モジュール。指定子の評価はホスト次第ですが、常に静的なインポート宣言と同じアルゴリズムに従います。
返値
モジュール名前空間オブジェクト、すなわちmoduleName
からのすべてのエクスポートを格納したオブジェクトで履行されるプロミスを返します。
import()
の評価で同期的にエラーが発生することはありません。moduleName
は文字列に強制されるので、強制が発生した場合、そのエラーとともにプロミスが拒否されます。
解説
import宣言の構文(import something from "somewhere"
)は静的で、常にモジュールが 読み込まれた時点で評価される結果となります。ダイナミックインポートを使用すると、import宣言の構文の硬直性を回避し、条件付きまたはオンデマンドでモジュールを読み込むことができます。以下のような理由で、動的インポートが使用することがあります。
- 静的にインポートするとコードの読み込みが著しく遅くなる場合で、インポートしているコードが必要になる可能性が低い、または後日必要になる可能性がある場合。
- 静的にインポートすると、プログラムのメモリー使用量が大幅に増える場合で、インポートするコードが必要になる可能性が低い場合。
- 読み込むモジュールが読み込む時点で存在しない場合。
- インポート指定子文字列を動的に構築する必要がある場合。(静的インポートでは静的指定のみ対応しています。)
- インポートされるモジュールに副作用があり、ある条件を満たさない限りその副作用を望まない場合。 (モジュールに副作用を持たないことを推奨しますが、モジュールの依存関係でこれを制御できないこともあります)。
- モジュール以外の環境(例:
eval
やスクリプトファイル)にいるとき。
動的インポートは必要なときだけ使用してください。静的な形は、初期の依存関係を読み込むのに適しており、静的解析ツールやツリーシェイキングの恩恵を受けやすくなります。
ファイルがモジュールとして実行されていない場合(HTML ファイルで参照される場合、script タグにtype="module"
が必要)、静的な import 宣言は使用できませんが、非同期の動的 import 構文は常に利用できるので、モジュール以外の環境にもモジュールをインポートすることができます。
モジュール名前空間オブジェクト
モジュール名前空間オブジェクト は、モジュールからのすべてのエクスポートを記述しているオブジェクトです。これは静的なオブジェクトで、モジュールが評価されるときに作成されます。モジュールの名前空間オブジェクトにアクセスする方法は 2 つあります。名前空間インポート (import * as name from moduleName
)、または動的インポートの履行値を通じてです。
モジュール名前空間オブジェクトは封印された、プロトタイプがnull
のオブジェクトです。これは、オブジェクトのすべての文字列キーが、モジュールのエクスポートに対応し、余分なキーがないことを意味しています。すべてのキーは辞書順に列挙可能で(すなわちArray.prototype.sort()
の既定の動作)、デフォルトエクスポートはdefault
というキーで利用できます。さらに、モジュール名前空間オブジェクトには@@toStringTag
プロパティがあり、値は"Module"
で、Object.prototype.toString()
で使用されるものです。
文字列プロパティは構成不可能で、Object.getOwnPropertyDescriptors()
を使用してその記述子を取得すると書き込みが可能になります。しかし、プロパティを新しい値に割り当てることはできないため、実質的に読み取り専用となります。この動作は、静的インポートが「ライブ結合」- 値をエクスポートするモジュールは再割り当てることができるが、インポートするモジュールはできない - を作成するという事実を反映したものです。構成不可、書き込み不可のプロパティは一定でなければならないため、プロパティの書き込み可否は、値が変化する可能性を反映します。例えば、変数のエクスポートされた値を再代入することができ、新しい値はモジュール名前空間オブジェクトで監視することができます。
それぞれのモジュール指定子は固有のモジュール名前空間オブジェクトに対応するため、一般的には以下のようになります。
import * as mod from "/my-module.js";import("/my-module.js").then((mod2) => { console.log(mod === mod2); // true});
ただし、1つだけ奇妙な場合があります。プロミスは決してthenable に履行されることはないので、もしmy-module.js
モジュールがthen()
という関数をエクスポートすると、その関数はダイナミックインポートのプロミスが履行されると自動的に呼ばれることになります。
// my-module.jsexport function then() { console.log("then() called");}
// main.jsimport * as mod from "/my-module.js";import("/my-module.js").then((mod2) => { // Logs "then() called" console.log(mod === mod2); // false});
例
副作用のあるモジュールだけをインポート
(async () => { if (somethingIsTrue) { // 副作用のあるモジュールをインポート await import("/modules/my-module.js"); }})();
自分のプロジェクトで ESM をエクスポートするパッケージを使用する場合、副作用がある場合のみインポートすることもできます。この場合、パッケージのエントリーポイントファイル(およびこのファイルがインポートするすべてのファイル)内のコードのみが実行されます。
デフォルトをインポート
返されたオブジェクトから "default" のキーを再構築し、名前を変更する必要があります。
(async () => { if (somethingIsTrue) { const { default: myDefault, foo, bar, } = await import("/modules/my-module.js"); }})();
ユーザー操作に応じたオンデマンドのインポート
この例では、ユーザーの操作(この場合はボタンのクリック)に基づいてページに機能を読み込ませ、そのモジュール内の関数を呼び出す方法を示しています。この機能を実装するための唯一の方法というわけではありません。import()
関数はawait
にも対応しています。
const main = document.querySelector("main");for (const link of document.querySelectorAll("nav > a")) { link.addEventListener("click", (e) => { e.preventDefault(); import("/modules/my-module.js") .then((module) => { module.loadPageInto(main); }) .catch((err) => { main.textContent = err.message; }); });}
環境に応じて様々な形のモジュールをインポート
サーバーサイドレンダリングなどの処理では、サーバーとブラウザーで異なるグローバルやモジュールと対話するため、異なるロジックを読み込む必要がある場合があります(例えば、ブラウザーのコードはdocument
やnavigator
などのウェブ API に、サーバーコードはサーバーファイルシステムにアクセスすることができます)。条件付きの動的インポートによって、このようなことが可能になります。
let myModule;if (typeof window === "undefined") { myModule = await import("module-used-on-server");} else { myModule = await import("module-used-in-browser");}
リテラルでない指定子を持つモジュールのインポート
動的インポートでは、モジュール指定子として、文字列リテラルに限らず、任意の式で指定することができます。
ここでは、/modules/module-0.js
、/modules/module-1.js
など、10 個のモジュールを並列に読み込んで、それぞれがエクスポートするload
関数を呼び出しています。
Promise.all( Array.from({ length: 10 }).map( (_, index) => import(`/modules/module-${index}.js`), ),).then((modules) => modules.forEach((module) => module.load()));
仕様書
Specification |
---|
ECMAScript® 2025 Language Specification # sec-import-calls |
ブラウザーの互換性
BCD tables only load in the browser