動的 import とは何か(まずイメージから)
ES6 以降のモジュールの import には、
「ファイルの先頭で書く静的な import」と「コードの中で呼び出せる動的 import」の2種類があります。
静的 import(いつも見ているやつ):
import { add } from "./math.js";
JavaScript動的 import(今回の主役):
const module = await import("./math.js");
JavaScript動的 import は
「必要になったタイミングで、その場でモジュールを読み込むための仕組み」 です。
ゲームで言えば、
「最初から全部のステージデータを読み込まないで、
プレイヤーがステージ 2 に来たときにステージ 2 のデータだけ読み込む」
みたいなイメージを持つと掴みやすいです。
ここが重要です。
静的 import は「最初に全部読み込む」、
動的 import は「必要になったら後から読み込む」。
これによって、読み込み時間を短くしたり、条件によって読み込むモジュールを変えたりできます。
書き方の基本と「Promise で返ってくる」というポイント
動的 import の基本形
動的 import は「関数のように」書きます。
import("./math.js").then((module) => {
console.log(module.add(2, 3));
});
JavaScriptもしくは async/await を使って:
async function main() {
const module = await import("./math.js");
console.log(module.add(2, 3));
}
main();
JavaScriptポイントは2つあります。
import("./math.js")は Promise を返す- その結果として「モジュールオブジェクト」が取れる
モジュールオブジェクトには、そのモジュールの export が全部入っています。
// math.js
export const PI = 3.14;
export function add(a, b) { return a + b; }
export default function mul(a, b) { return a * b; }
JavaScript// main.js
const module = await import("./math.js");
console.log(module.PI); // 3.14(名前付き export)
console.log(module.add(2, 3)); // 5
console.log(module.default(2, 3)); // 6(default export)
JavaScriptここが重要です。
静的 import は「分割代入」でそのまま名前を取り出しますが、
動的 import は「モジュールオブジェクトを受け取って、そこからプロパティとして使う」形になります。
静的 import との違い(いつ・どこで使えるか)
静的 import は「ファイルの先頭で、一度だけ」
静的 import は、お約束として「トップレベル」でしか使えません。
// OK(ファイルの一番上)
import { add } from "./math.js";
// NG(関数の中では書けない)
function main() {
import { add } from "./math.js"; // 文法エラー
}
JavaScriptこれは、
「どのモジュールが、どのモジュールに依存しているか」を、
JavaScript エンジンが事前に解析しやすくするためです。
動的 import は「どこでも・いつでも呼べる」
一方で動的 import は、
普通の関数呼び出しと同じように、どこでも書けます。
async function main() {
if (Math.random() > 0.5) {
const m1 = await import("./moduleA.js");
m1.doSomething();
} else {
const m2 = await import("./moduleB.js");
m2.doSomethingElse();
}
}
JavaScriptここが重要です。
動的 import の強みは、「条件によって読み込むモジュールを変える」「必要になるまで読み込まない」という柔軟さにあります。
その代わりに、Promise を扱う(await する)必要が出てきます。
代表的な使いどころ(なぜわざわざ動的にするのか)
初期表示を軽くする(遅延読み込み・コード分割)
ブラウザで大きなアプリを作るとき、
全部のコードを最初から読み込むと、表示まで時間がかかります。
そこで、「今すぐは使わない重い部分」を動的 import で後回しにします。
// main.js
document.getElementById("open-settings").addEventListener("click", async () => {
const module = await import("./settingsDialog.js");
module.openSettingsDialog();
});
JavaScript最初のロード時には settingsDialog.js は読み込まれず、
ユーザーが「設定」を開いたタイミングで初めてダウンロードされます。
ここが重要です。
動的 import の一番実用的な使い道は「必要になるまで読み込まない」=遅延読み込みです。
ユーザー体験(初期表示の速さ)を良くするためのテクニックとして覚えておくと強いです。
条件でモジュールを切り替える(環境や設定に応じて)
例えば、設定や環境によって使うモジュールを変えたい場合。
async function loadLogger(env) {
if (env === "production") {
return await import("./loggerProd.js");
} else {
return await import("./loggerDev.js");
}
}
async function main() {
const { log } = await loadLogger("development");
log("アプリ起動");
}
JavaScript静的 import では「どのファイルを import するか」は固定ですが、
動的 import なら、条件によって import 先を変えられます。
実行時に決まるパスを使う(ルーティングなど)
シンプルな例として、画面のパスごとに別モジュールを読み込むルーターを考えてみます。
async function loadPage(path) {
const module = await import(`./pages/${path}.js`);
module.render();
}
loadPage("home"); // ./pages/home.js を読み込む
JavaScriptテンプレートリテラル(バッククォート文字列)を使うことで、
パスの一部を動的に変えることもできます。
ここが重要です。
「静的 import では書けない、変数を使ったパス指定」ができるのが、動的 import の大きな違いです。
具体例で動的 import を体に馴染ませる
例1:ボタンを押したときだけ重いモジュールを読み込む
// heavyTask.js
export function runHeavyTask() {
console.log("重い処理を実行中...");
// 何か時間のかかる処理…
}
JavaScript// main.js
const button = document.getElementById("run-heavy");
button.addEventListener("click", async () => {
const module = await import("./heavyTask.js");
module.runHeavyTask();
});
JavaScript最初のページロードでは heavyTask.js は読み込まれず、
ユーザーがボタンを押して初めて読み込まれます。
例2:ユーザーの選択した言語に応じてメッセージファイルを読み込む
// messages/ja.js
export default {
hello: "こんにちは",
bye: "さようなら"
};
JavaScript// messages/en.js
export default {
hello: "Hello",
bye: "Goodbye"
};
JavaScript// main.js
async function loadMessages(lang) {
const module = await import(`./messages/${lang}.js`);
return module.default;
}
async function main() {
const lang = "ja"; // 本来はブラウザ設定やユーザー設定から取る
const messages = await loadMessages(lang);
console.log(messages.hello);
}
main();
JavaScriptlang の値によって、./messages/ja.js や ./messages/en.js が読み込まれます。
静的 import では、こうしたパターンを素直に書けません。
例3:エラー時にだけデバッグ用モジュールを読み込む
// debugTools.js
export function showDebugInfo(error) {
console.error("デバッグ情報:", error);
}
JavaScript// main.js
async function run() {
try {
// 何かの処理
} catch (err) {
console.error("エラーが起きました");
if (process.env.NODE_ENV !== "production") {
const debugModule = await import("./debugTools.js");
debugModule.showDebugInfo(err);
}
}
}
JavaScript本番環境では debugTools.js を一切読み込まないようにして、
開発環境でだけ読み込む、といった使い方もできます。
注意ポイント(初心者がハマりやすいところ)
import() は「同期じゃない」ので、必ず待つ必要がある
よくあるミスは、こう書いてしまうパターンです。
// よくない例
const module = import("./math.js"); // ここではまだ Promise
console.log(module.add(2, 3)); // module はまだモジュールではない
JavaScriptimport("./math.js") は Promise を返すので、await するか then を使って結果を待たないと、中身にアクセスできません。
// 正しい使い方(async/await)
const module = await import("./math.js");
console.log(module.add(2, 3));
JavaScript// 正しい使い方(then)
import("./math.js").then((module) => {
console.log(module.add(2, 3));
});
JavaScript相対パス・拡張子は静的 import と同様に気をつける
ブラウザの ES モジュールで動的 import を使う場合も、
パス指定は静的 import と同じルールです。
// OK
const module = await import("./math.js");
// NG(ブラウザでは多くの場合エラー)
const module = await import("math");
JavaScript.js までしっかり書く、./ や ../ を忘れない、といった基本は共通です。
まとめ
動的 import の本質は、
「import を“後から・条件付きで”実行できるようにすることで、モジュール読み込みのタイミングと対象を柔軟にコントロールする」 ことです。
押さえておきたいポイントは次の通りです。
動的 import は import("パス") という関数風の書き方で、Promise を返すawait import(...) で「モジュールオブジェクト」を受け取り、module.xxx として export にアクセスする
静的 import はファイル先頭・固定パス、動的 import はどこでも書けて、条件や変数を使える
「必要になるまで読み込まない(遅延読み込み)」「条件によってモジュールを切り替える」場面で力を発揮する
Promise を返すので、必ず await か then で待ってから使う
まずは、小さなサンプルで
「ボタンを押したときだけモジュールを読み込む」コードを書いてみてください。
そこから、「これも後読みできそうだな」「これは条件で切り替えられそうだな」と、
自分のアプリの中で動的 import が活きる場所が見えてきます。
