Skip to content
uupaa edited this page Feb 16, 2018 · 8 revisions

最新の JavaScript では import / export 構文を使用したモジュールプログラミングが可能になっています。

静的なモジュールローダーの仕様(ES Modules)が ES2015 で追加され、 動的な仕様(Dynamic module import) が ESNext で追加されました。

Dynamic module import は、2017/12 の時点で ES.Stage 3 の仕様です。

このエントリでは、モジュールの仕様について説明します。以下のエントリも合せてご覧ください。

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

Module dynamic import

大規模なシステムやプラガブルなシステムを構築するには、動的にモジュールを読み込むための仕様も必要です。 このための仕様が 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));

Resources