Skip to content

ES Modules Runtime support

uupaa edited this page Dec 26, 2018 · 13 revisions

このエントリでは、ES Modules のランタイム側のサポート状況について説明しています。

This entry is will be continually updated.

Current state

以下は、執筆当時(2018/03)の各ランタイムの ES Modules 関連仕様のサポート状況になります。

Browser / Runtime ES Modules
import / export
import() import.meta
Chrome 61 63 64
Chrome for Android 61 63 64
Mac Safari 10.1 11
Mobile Safari 10.3 11
Firefox 54 + flag ❌ (no signal)
Edge 16 ❌ (no signal)
Node.js 9.3 + flag WIP WIP
Babel plugin
Rollup 0.56 + flag
Webpack
TypeScript 2.4

HTML関連仕様の状況です。

Browser <script type="module"> <script nomodule> <link rel="modulepreload">
Chrome 61 61 64 + flag
Chrome for Android 61 61 64 + flag
Mac Safari 10.1 10.3
Mobile Safari 10.3 11.0
Firefox 55 55
Edge 16 16

Situation of the runtime side

ES Modules は ECMAScript の範囲内で振る舞いを定義しています。 モジュールとは何であり、どうやって解釈されるべきか… といった重要な部分は ES Modules で標準化されましたが、 モジュールの読み込み方などは、例により各実装にまかされています。

ブラウザや Node.js などのランタイム, トランスパイラなどのツールは、それぞれでバックエンドや状況が異なるといった事情を抱えています。

Browser support

ES Modules をサポートするブラウザは <script type="module"> をサポートします。

<script type="module" src="..."></script>
<script type="module"> inline module </script>

<script type="module"> は ES Modules のために追加された仕様です。 ES Modules をサポートするブラウザは type="module" 属性が設定されている <script> を読み込み実行します。

  • <script type="nomodule">defer 属性が設定されているものとして動作します
  • <script> はどこからでもファイルを読み込めますが、<script type="module"> はCORSの影響を受けます
  • <script> は cookie 付きでサーバにアクセスしますが、 <script type="module"> は cookie を付与しません(No credentials)
  • 適切なMimeTypeがレスポンスヘッダで与えられない場合は実行に失敗します

また、ES Modules では <script type="modue"> が利用できない古いブラウザにむけたフォールバックの仕組みが用意されています。それが nomodule です。

<script type="module" src="main.js"></script>
<script nomodule src="bundle.js"></script>
<script nomodule> inline script </script>
  • ES Modules をサポートしているブラウザは <script type="module"> を読み込みますが、 <script nomodule> は無視します。
  • ES Modules をサポートしていない古いブラウザは <script type="module"> を恐らく無視します。そして <script nomodule> を読み込み実行します。

<script type="module"><script nomodule> の実行タイミングをあわせる場合は、<script nomodule>defer 属性を追加し、以下のようにすると良いでしょう。

<script type="module" src="main.js"></script>
<script nomodule defer src="bundle.js"></script>

読み込むモジュールが明らかな場合は、プリロードする仕組みが欲しくなります。 それが <link rel="modulepreload" href="module.js"> です。

<link rel="modulepreload" href="module.js" onload="onloaded()"> とすると、module.js 読み込み後に onloaded() がコールバックされます。

TypeScript support

TypeScript は JavaScript のスーパーセット(上位互換)であり、ES Modules (import / export) をそのまま利用できます。 また、import() も利用できます。

https://www.typescriptlang.org/docs/handbook/modules.html

Node.js support

Node.js v9.3.0 から以下の条件付きで ES Modules のサポートが入りました。

https://nodejs.org/api/esm.html

  • Node.js v9.3.0 の時点では node --experimental-modules index.mjs のようにフラグ付きで起動する必要があります。
    • v10.0.0 で --experimental-modules が廃止され、ES Modules がデフォルトで利用可能になる予定です。
  • Node.js の import / export で使用するモジュールの拡張子を .mjs にする必要があります。 .js は動作しません。
  • Node.js v9.3.0 では import()import.meta は利用できません。これらは将来の実装を待つ必要があります。
  • 2020年ごろに --experimental-modules フラグを就けなくても動作するようになるとの情報もあります

require() で読み込まれるスクリプトと import で読み込まれるモジュールには以下の相違点があります。

  • モジュールでは NODE_PATH を使用できません。
  • モジュールでは Node.js 固有のキーワード(require, module, exports, __filename, __dirname)は利用できません。
  • モジュールのパスは、URL のルールに基づいている必要があります。URLで使用できない記号はエスケープする必要があります。
  • モジュールとして、CommonJS で書かれたコード、拡張子がJSONのテキストファイル、C++で書かれたアドオンを読み込む事ができます。
  • モジュールを読み込む際のフックを設定できます。フックの詳細は https://nodejs.org/api/esm.html#esm_loader_hooks を参照してください。

__filename__dirname が利用できないと困ってしまう状況もあるため、 環境依存のメタ情報を格納するためのプロパティ import.meta 以下に import.meta.url を用意するなどの代替案も検討されています。

拡張子 .mjs については注意すべきでしょう。 単に .js.mjs にリネームするだけでは既存のツールが利用できなくなります。

  • サーバ側に MimeType の設定を追加しないと、ブラウザ側では利用できないファイルになります。 ES Modules Path rule を参照してください。
  • 既存のシンタックスハイライトツールは .mjs を JavaScript のコードとして認識しません。
  • /\.js$/.test(path) という既存のjsにマッチする正規表現はそのままでは .mjs にマッチしません。
    /\.(js|mjs)$/.test(path) に変更する必要があります。

以下は古い情報です

Node.js では require 構文が長年利用されてきた関係で、import へのスムーズな移行が難しい状況にあります。 2018/Q1 頃までになんらかの方向性を提案する予定とありますが、現在有力視されているのは、 従来の CommonJS(requre/module.exports)スタイルのコードの拡張子を *.js とし、 ES Modules スタイルで書かれたコードの拡張子を *.mjs にする方法です。

https://medium.com/the-node-js-collection/an-update-on-es6-modules-in-node-js-42c958b890c

このmjs拡張子(マイケルジャクソン拡張子)は、npm に蓄積された資産的な都合から提案されているのですが、 どちらかと言うと否定的な反応が多く観測されており、どうなるか揺れているようです。

Issues

実用上の課題が残っているブラウザも存在します。

Flags

ES Modules に関するフラグの一覧です。

  • Chrome
    • Enable experimental features
      • chrome://flags/#enable-experimental-web-platform-features
    • Enable/Disable <script type="module"> <script nomodule>, import, export
      • chrome://flags/#enable-module-scripts
      • Chrome 60 で追加、 63 で廃止になり disable 不能に
    • Enable/Disable import()
      • chrome://flags/#enable-module-scripts-dynamic-import
      • Chrome 63 で追加
    • Enable/Disable import.meta.url
      • chrome://flags/#enable-module-scripts-import-meta-url
      • Chrome 63 で追加
  • Node.js
    • ES Modules
      • node --experimental-modules
      • v9.3.0 で追加
  • Rollup.js
    • Dynamic import
      • rollup --experimentalDynamicImport
      • v0.56.0 で追加