-
Notifications
You must be signed in to change notification settings - Fork 0
ES Modules
最新の JavaScript では import
/ export
構文を使用したモジュールプログラミングが可能になっています。
静的なモジュールローダーの仕様(ES Modules)が ES2015 で追加され、 動的な仕様(Dynamic module import) が ESNext で追加されました。
Dynamic module import は、2017/12 の時点で ES.Stage 3 の仕様です。
このエントリでは、モジュールの仕様について説明します。以下のエントリも合せてご覧ください。
- ES Modules Syntax - import と export の構文について
- ES Modules Path rule - import 構文で使用可能な path の仕様について
- ES Modules Module scope - Module scope について
- ES Modules Runtime support - ランタイム/ツール側のサポート状況について
- ES Modules Migration - 既存の環境から ES Modules に移行する際のマイグレーションパスについて
ES Modules は静的にモジュールを読み込むための仕様です。ESM と略されます。
ES Modules では import
でモジュールを読み込みます。また export
で外部に識別子を公開します。
言葉で説明するよりも、コードを見たほうが理解が早いでしょう。 この例では mod.js で export し、それを main.js で 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"; // 名前付きで import
console.log(foo); // -> 123
console.log(bar(2, 3)); // -> 6
new Buz();
この例では 名前付きで export し、その名前で import しています。これが基本の形になります。 import と export は上記以外にも沢山の書き方が可能です。 詳細は ES Modules Syntax を参照してください。
ES Modules では、従来の <script src="...">
形式のスクリプトの呼び出しを クラシック (または単にスクリプト)と呼び、新しい <script type="module">
や import ...
形式の呼び出しを モジュール と呼びます。このように2つのモードがあるということです。
モジュールの仕様は以下のようになっています
- モジュールは同じファイルを1度しか読み込みません。クラシックでは
<script src="script.js">
が複数あると、その都度読み込みが発生します。 - モジュールは読み込み時に静的に構文チェックが行われます。実際に動作させる前にエラーが判明します。
- モジュールは循環する読み込みであっても問題ありません。
- モジュールは strict mode がデフォルトになります。
- モジュールで読み込まれるファイルは CORS の制限を受けます。
- スクリプトの最上位ブロックで変数を定義するとグローバル変数になりますが、モジュールの最上位ブロックで変数を定義すると、その変数はローカル変数になります。
-
<script type="module">
はデフォルトで defer が設定されている状態になります。つまり<script defer type="module">
と同じ動作になります。 defer の効果により、モジュールの読み込みと実行は HTML をパースし終わった後に非同期で行われます。 - モジュールでは
this
の値はundefined
になります。 - クラシックからモジュールを呼び出す事はできません。つまり
<script src="main.js">
で読み込んだ main.js からimport ...
はできません。構文エラーになります。 -
import
に指定するモジュールのパスは/
,./
,../
で始まる相対パスかURLを指定します。ES Modules Path rule を参照してください。
モジュール と クラシック の違いを纏めると、このようになります。
module | classic | |
---|---|---|
file loading | one time | every time |
Cyclic import | safety | warn |
Strict mode | enable | disable ("use strict" が必要) |
currentScript | null | null or <script>
|
CORS | YES | NO |
Top level variables | local変数 | global変数 |
defer attribute | enable | disable |
HTML element |
<script type="module"> <script nomodule> <link rel="modulepreload">
|
<script> |
this value |
undefined | window |
path rule |
/ , ./ , ../ で始まる相対パス または URL |
相対パス または URL |
大規模なシステムやプラガブルなシステムを構築するには、動的にモジュールを読み込むための仕様も必要です。
このための仕様が Dynamic module import です。import()
とも呼ばれます。
2018/01 時点で import()
をサポートしているブラウザは Chrome と Safari のみです。 Firefox や Edge で利用する場合は polyfill を導入する必要があります。
import ...
は最上位ブロックのみに限定されていましたが import()
は 任意の場所で利用できます。
import()
は、引数で与えられた path を動的に読み込み Promise を返します。path に変数を指定できます。
if (flag) {
const path = `//example.com/module/Foo.js`;
import(path).then(Foo => {
const foo = new Foo();
}).catch(err => console.error(err.message));
}
import()
で読み込んだjsに構文エラーがあった場合は、(従来の)スクリプトと同様に、実行時にSyntaxErrorが発生することになります。
import()
は Promise を返すため async
/ await
と組み合わせて利用することができます。
async function foo() {
if (flag) {
const { stuff } = await import("//example.com/Foo.js?t=123");
}
}
(async () => {
const path = `./modules`;
const { NiceDateClass } = await import(`${path}/DateUtil.js`);
const { NiceTimeFunction } = await import(`${path}/TimeUtil.js`);
})().catch(err => console.error(err.message));
- ES Modules:
- Dynamic import:
- TypeScript: