目次
静的インポート vs 動的インポート
静的インポート(おさらい)
これまで使ってきたのが 静的インポート です:
// mathUtil.js
export function add(a, b) {
return a + b;
}// main.js
import { add } from "./mathUtil.js";
console.log(add(1, 2));特徴:
- ファイルの先頭レベル(top-level)でしか書けない
if文の中や関数の中では書けない(構文エラー)
- モジュール解析時に、依存関係がすべてわかる(静的解析可能)
- 実行前に、「どのモジュールを読み込むか」が確定している
動的インポート
一方、実行時の状況に応じてモジュールを読みたいことがあります。
- ユーザーがボタンを押したときにだけ、重いライブラリを読み込みたい
- 特定の条件に当てはまるときだけ、別モジュールに処理を任せたい
- プラグイン名を文字列で受け取って、その名前のモジュールを読み込みたい
このようなときに使うのが 動的インポート です:
// 関数っぽい構文だが、実は特殊構文
const promise = import("./mathUtil.js");特徴:
import("./path.js")は Promise を返す- どこでも書ける(関数の中 / if の中 / イベントハンドラなど)
- 文字列でパスを組み立てられる(
import(./plugins/${name}.js)など)
import() の基本構文と使い方
then / catch で使う
import("./mathUtil.js")
.then((module) => {
console.log(module.add(1, 2));
})
.catch((err) => {
console.error("モジュール読み込み失敗:", err);
});moduleは「モジュールオブジェクト」
→exportされたものがプロパティとしてぶら下がっているイメージ
// mathUtil.js がこうだとすると:
export const PI = 3.14;
export function add(a, b) { ... }
export default function mul(a, b) { ... }// import() 結果の module には
module.PI; // 3.14
module.add; // 関数
module.default; // デフォルトエクスポート(mul)await import(...) で使う(よく使う形式)
async 関数の中では、await を使って同期っぽく書けます:
async function main() {
try {
const module = await import("./mathUtil.js");
console.log(module.add(1, 2));
} catch (e) {
console.error("読み込み失敗:", e);
}
}構造分割で直接取り出す書き方もよく使われます:
async function main() {
const { add, PI } = await import("./mathUtil.js");
console.log(add(1, 2), PI);
}- 静的インポートと違い、「実行時に読み込む」ことを強く意識する必要があります
- エラー処理は
try/catchで OK(awaitなので reject → 例外化)
動的インポートの主な用途
コード分割(遅延読み込み)
例:ボタンを押したときだけ、重いグラフ描画ライブラリを読み込みたい。
document.getElementById("showChart").addEventListener("click", async () => {
const { drawChart } = await import("./chart.js");
drawChart();
});- 初期表示では
chart.jsを読み込まないので、初回ロードが軽くなる - 実際のプロダクションでは、バンドラ(webpack / Vite など)が
import()を手掛かりに JS ファイルを分割してくれます(ここではイメージだけでOK)
条件付きで別実装を読み込む
async function loadStorageImpl() {
if ("indexedDB" in window) {
return import("./storage-indexeddb.js");
} else {
return import("./storage-fallback.js");
}
}
async function main() {
const storageModule = await loadStorageImpl();
// storageModule.save(), storageModule.load() などを使う
}- 環境に応じて「どの実装を使うか」を切り替えたいときに便利
- プラグイン構造 にも応用しやすい
「名前でモジュールを指定」するプラグイン的な使い方
async function loadPlugin(name) {
const module = await import(`./plugins/${name}.js`);
return module;
}
async function runPlugin(name, context) {
const plugin = await loadPlugin(name);
await plugin.run(context); // 各プラグインは run() をエクスポートしている想定
}- パスを動的に組み立てられるのは静的 import にはない強み
- バンドラ使用時は、「どのパスがビルド対象か」を設定で制限する場合があります
→ ここは将来的に現場で設定ファイルを触るときに意識すればOK
動的インポートのエラー処理とキャッシュ
読み込み失敗時の挙動
import() が失敗する主なケース:
- ネットワークエラー(ファイルが取得できない)
- 404 などでモジュールが存在しない
- モジュールの構文エラー(パースに失敗)
これらは Promise の reject として扱われます。
async function main() {
try {
const module = await import("./not-exist.js");
} catch (e) {
console.error("読み込みエラー:", e);
}
}キャッシュ挙動(静的インポートと同じ)
同じ URL(パス)に対しての import() は、
- 最初の1回だけ実際に読み込まれて評価される
- 2回目以降は キャッシュされたモジュールオブジェクトが返る
async function loadTwice() {
const m1 = await import("./counter.js");
const m2 = await import("./counter.js");
console.log(m1 === m2); // true(同じモジュールインスタンス)
}- 静的インポートの場合と同様、「モジュールは1度だけ評価→以降は共有」というイメージでOKです。
import.meta:モジュールに関するメタ情報
import.meta とは
モジュールの内部で import.meta と書くと、
そのモジュールに関するメタデータを持つオブジェクトにアクセスできます。
代表的なのは:
import.meta.url:そのモジュール自身のURL
// someModule.js(モジュール内)
console.log(import.meta); // { url: "..." } など(環境依存で他のプロパティも)
console.log(import.meta.url); // このファイルのURL文字列※ import.meta は モジュール内でしか使えません。
従来の <script>(非モジュール)では未定義です。
import.meta.url の使いどころ(ブラウザ)
ブラウザでは、import.meta.url は大体こんな値になります:
https://example.com/js/modules/someModule.jsこれを基準に new URL を使うことで、
「このモジュールファイルから見た相対パス」を解決できます。
// someModule.js
const jsonUrl = new URL("./data/config.json", import.meta.url);
// → https://example.com/js/modules/data/config.json など
fetch(jsonUrl)
.then(res => res.json())
.then(config => {
console.log("設定:", config);
});- HTML ファイルからの相対パスではなく、
「このJSモジュールからの相対パス」 を扱えるのがポイント - 複数の場所からモジュールが読み込まれても、自モジュールに付随するファイルを確実に参照できる
Node.js での import.meta.url
Node.js では import.meta.url は、だいたいこんな文字列になります:
file:///Users/you/project/src/someModule.mjsこれを fileURLToPath で OS のパスに変換して、
__dirname/__filename相当の値を得る- 同じディレクトリにあるファイルを読み込む
といった用途で使います。
「Node でもモジュールパスの基準として使える」ぐらいの認識でOKです。
例:ダイアログ機能を遅延ロードする
簡単な実用イメージを一つ紹介します。
構成
/project
index.html
main.js
dialog.jsdialog.js:
export function showDialog(message) {
alert("ダイアログ: " + message);
}main.js:
document.getElementById("openDialog").addEventListener("click", async () => {
const { showDialog } = await import("./dialog.js");
showDialog("こんにちは!");
});index.html:
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>Dynamic Import Example</title>
</head>
<body>
<button id="openDialog">ダイアログを開く</button>
<script type="module" src="./main.js"></script>
</body>
</html>ポイント:
- ページ表示時点では
dialog.jsはまだ読み込まれていない(=初期ロードが軽い) - ボタンが押された瞬間にだけ
import("./dialog.js")が走り、
そのモジュールがネットワーク越しに取得される - バンドラを噛ませると、自動で「チャンク分割」される
コメント