-
Notifications
You must be signed in to change notification settings - Fork 0
ES Modules Syntax
このエントリでは、ES Modules の Syntax について説明します。
import ...
と export ...
は最上位ブロックに記述する必要があります。
if文や関数の内部、ブロックスコープの中には記述できません。
import { bar } from "./bar.js"; // -> OK
{
export piyo; // -> Syntax error (is not top level block)
}
if (flag) {
import foo from "./foo.js"; // -> Syntax error (is not top level block)
}
import { buz } from "./buz.js"; // -> OK
function bar() {
export let buz = 1; // -> Syntax error (is not top level block)
}
export piyo; // -> OK
-
import ...
は、コードの途中や末尾に記述されていてもコードの先頭に書かれている物として解釈されます。これは JavaScript における hoisting(巻き上げ) に近い振る舞いです。 -
export ...
は、最上位ブロックに記述する限り、ファイルのどこにでも記述することができます。 -
export let num = 123
をimport { num } from ...
で読み込むと、変数num
は読み込み専用になります。
import には 名前付き( named import ) と、名前なし( default import ) の二種類が存在します。
-
import { member, ... } from path
(named import) -
import * from path
(default import)
export にも named export と default export が存在します。
名前を特に指定せず、1つだけキーワードを export するのが default export です。
復数のキーワードを名前付きで export するのが named export です。これらの混在も可能です。
-
export ...
(named export) -
export default ...
(default export)
キーワードを export せず副作用のみを期待するモジュールも記述できます(Re-exporting)。
export default ...
構文を使用した export を default export と呼びます。
default export を import するには import foo from "..."
や import { default: foo } from "..."
構文を使用します。
-
export default ...
で名前を省略したまま export できます。// foo.js export default 123; // 値123 を default export
// main.js import foo from "./foo.js"; // 123 を fooという名前で参照 console.log(foo); // -> 123
import foo from "./foo.js";
は
import { default as foo } from "./foo.js";
でも同じ結果になります。 -
値の他に関数やクラスも export できます。
export default
はちょっと特殊な式となっておりlet
const
var
を併記できません。直感的ではありませんが、なにやら理由があるようです。const value = 123; class Foo {} function fn() {} // export default value; // -> OK export default Foo; // -> OK export default fn; // -> OK export default 1; // -> OK export default `current time: ${Date.now()}`; // -> OK export default class {} // -> OK export default function() {} // -> OK export default let 1; // -> SyntaxError (let) export default const 1; // -> SyntaxError (const) export default var 1; // -> SyntaxError (var)
export ...
構文を使用し、名前を付けた export を named export と呼びます。
-
export ...
で名前を付けて export できます。// mod.js export let foo = 1; export function bar() {}
// main.js import { foo, bar } from "./mod.js"; console.log(foo, bar); // -> 1, function bar
-
export { member, ... }
の形で、まとめて export もできます。// mod.js let foo = 1; function bar() {} export { foo, bar };
-
import * as ... from ...
で名前を省略して import できます。// mod.js export const foo = 123; // foo という名前で export export function bar(x) { return x * x; } // bar という名前で export export class Buz {} // Buz という名前で export
// main.js import * as mod from "./mod.js"; console.log(mod.foo); // -> 123
-
import { member, ... } from ...
で名前を指定して import できます。// mod.js export const foo = 123; // foo という名前で export export function bar(x) { return x * x; } // bar という名前で export export class Buz {} // Buz という名前で export
// main.js import { foo, bar, Buz } from "./mod.js"; console.log(foo); // -> 123
-
まとめて import することもできますし、何度かに分けて import することもできます。
import { foo, bar } from "./mod.js"; import { Buz } from "./mod.js";
-
as キーワードを使い、別名(local name)で import できます。名前の衝突を回避できます。
import { foo, bar, Buz } from "./mod.js"; import { Buz as Buz2 } from "./mod.js"; // -> OK
-
既に存在する名前で import するとSyntaxErrorになります。
import { foo, bar, Buz } from "./mod.js"; import { foo, bar } from "./mod.js"; // -> Uncaught SyntaxError: Identifier 'foo' has already been declared import { Buz } from "./mod.js"; // -> Uncaught SyntaxError: Identifier 'Buz' has already been declared
何も export しないモジュールも定義できます。また、副作用を期待した import もできます。
// mod.js
document.body.style.backgroundColor = "red"; // 画面を赤に染める
console.log("mod3.js called"); // コンソールに文字列を出力する
// main.js
import "./mod.js"; // mod.js を import すると画面が赤くなり、コンソールに文字列がでる
mod1.js を mod2.js で丸ごと export できます。
// mod1.js
export function foo() {}
export function bar() {}
// mod2.js
export * from "./mod1.js";
mod1.js で定義された名前をそのまま export せず、 as で名前を変更して export することもできます。
// mod2.js
export { foo as foo2, bar } from "./mod1.js";
Named export, Default export と import()
を活用することで、未知のプラグインを動的に読み込むコードを記述することが可能です。
以下のコードは、GoogleMapsProvider
を Named export と Default export の両方で記述しており、このように記述することで、 import ...
による静的な読み込みと import()
による動的な読み込みの両方から利用可能となります。
このような記述の仕方をプラガブルモジュールパターン(またはプラグインパターン)と呼んでいます。
export class GoogleMapsProvider { // [!] Named export
load(apiKey) { // @arg GoogleMapsAPIKey - "AIzaSyBez8zjDlp3oOzy_1cVpkIHwdyH_Yrt..."
// @ret Promise
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.onload = resolve;
script.src = `//maps.googleapis.com/maps/api/js?key=${apiKey}&v=3&language=jp`;
document.head.appendChild(script);
});
}
}
export default GoogleMapsProvider; // [!] Default export
静的に読み込む場合は以下のようにします。
import { GoogleMapsProvider } from "./plugin-dir/GoogleMapsProvider.js";
GoogleMapsProvider.load(apiKey);
プラグインとして動的に読み込む場合は、以下のようにします。
async function loadPlugin(pluginDir, pluginName) { // @arg PluginNameString - "Foo"
// @ret Promise
const { default: Provider } = await import(`${pluginDir}/${pluginName}Provider.js`);
return Promise.resolve(Provider);
}
const pluginDir = `./plugin-dir`;
loadPlugin(pluginDir, "GoogleMaps").then(Provider => {
Provider.load(apiKey);
});
-
import name from ...
は default export 用の構文で、
import { name } from ...
が named export 用の構文です。 -
import name from ...
は
import { default as name } from ...
の shorthand です。 -
export default
は module の中に一つだけ記述できます。復数あるとエラーになります
基本的な考え方として、
- モジュールが1つの class のみを export する場合は
export default
を使います。 - 1つの関数を export する場合も同様に
export default
を使用します。 - プラグイン機構を組み込む場合は
export ...
とexport default ...
を併用します。 - 上記以外なら named export を使用します。
ES Modules はまだ新しくツールのサポートは改善途中にあります、 export default を使うと、IDEやトランスパイラといった関連ツールが上手く動作しないケースがあります。
また トランスパイルを必要とする CommomJS のコードが多く存在している状況においては、 default にこだわらず named を選択するという柔軟な思考も必要になるでしょう。
このエントリで紹介した構文以外でも、 default import と named import を組み合わせた書き方などが存在します。 詳しく知りたい方は、以下のエントリを参照してください。