Node.jsの新しいモジュール方式の実験的導入
2019 / 03 / 19
EditNode.js の Core へ ESM と CJS の新しい方式が実験的フェイズ(stability: 1)として入ります。
ESM 対応は安定化までのプランとしてステージを 4 つ(0 -3)用意しており、現在が 2 です。
2019 年の 10 月に実験的から安定的へ移行するのが最終目標となります。(stage:3)
内容まとめ
--es-module-specifier-resolution=node|explicitで処理解決方法を決定するexplicitがデフォルト
--entry-type=commonjs|moduleで CJS か ESM かを決定する- デフォルトは近しい親にある package.json の
typeフィールドを参照する
- デフォルトは近しい親にある package.json の
- ESM ではデフォルトで json は読み込めない
--experimental-json-modulesを付ける必要がある
- CJS と ESM の違い
- ESM の場合、拡張子が必須
NODE_PATHがないrequire,exports,module.exports,__filename,__dirnameがないmodule.createRequireFromPath、及びimport.meta.urlを使いましょう
require.extensions,require.cacheの使用不可- URL-based のパス指定
とりあえず、package.json にtypeフィールド追加すると、そのスコープ内の.jsファイルはそのモジュールタイプになるよって覚えておけばいいです。
ESM 事前知識
以下の記事を読んでください。
PR
Core への PR
初期提案実装
—es-module-specifier-resolution
explicit と node が存在し、デフォルトはexplicitです。
違いは以下の通りとなります。
- 拡張子を省略することができない
indexを許容しない
まだ、変更される可能性が高いため注意が必要です。
今までどおりの挙動を望むのであれば、nodeを指定する必要があります。
リゾルバアルゴリズム
typeフラグがmoduleの場合、package.json を軸に次の package.json までにネストされたフォルダとサブフォルダをすべて ESM とみなす仕様(そしてつぎ package.json のフラグがmoduleの場合は続く)
もし package.json がない場合は、デフォルトで commonjs となります。
使用法
実験的なフェイズなため、実行時に--experimental-modulesフラグが必要です。
.mjsがエントリーポイントの場合
この場合は、デフォルトで ESM として読み込みます。
node --experimental-modules index.mjs
また、上記の実行の場合、エントリーポイントから ESM 形式で import するファイルは.mjsである必要があります。
しかし、--entry-typeフラグ及び、package.json のtypeにmoduleを指定すると、.mjsという拡張子を使うことなく、ESM として読み込むことが可能となります。
// package.json
{
"type": "module"
}
—entry-type
このフラグには、commonjsとmoduleの 2 つの設定が存在します。
moduleが指定された場合、.js, .mjs, 拡張子がないファイルは ESM として呼び出されます。
この指定がない場合、デフォルトはcjsです。
$ node --experimental-modules --entry-type=module --eval \
"import { sep } from 'path'; console.log(sep);"
/
$ node --experimental-modules --entry-type=commonjs --eval \
"import { sep } from 'path'; console.log(sep);"
import { sep } from 'path'; console.log(sep);
^
SyntaxError: Unexpected token {
package.json の type フィールド
--entry-typeの package.json に書く版です。
最も近い親の package.json のtypeフィールドを参照し、モジュール方式を決定していきます。
一般的に今までの node_modules の package.json はtypeフィールドを持たないため、commonjs で読み込まれ互換性を保つことが期待されます。
// sample/package.json
{
"type": "module"
}
// ./sample/index.js
// 近しいpackage.jsonのtypeがmoduleなので、このファイルはESMで読み込まれる
import "./sample/setup/init.js";
// ./node_modules/foo/package.jsonにはtypeが書いてないため、CJSで読み込まれる
import "foo";
特定ファイルのモジュール形式をロックしたい場合
ユーザーが表現できる拡張子は、.js, .mjs, .cjsとなります。
type: module|commonjs以下では、.jsはそれに従います。
つまり、特定のファイルに対して拡張子で操作することになります。
type:module以下で commonjs として扱いたいファイルに対しては、.cjsの拡張子にするtype:commonjs以下で esm として扱いたいファイルに対しては、.mjsの拡張子にする
// 常にcommonjsとして読み込む
import "./legacy-file.cjs";
// 常にesmとして読み込む
import "commonjs-package/src/index.mjs";