技術探し

JavaScriptを中心に記事を書いていきます :;(∩´﹏`∩);:

Node.jsの新しいモジュール方式の実験的導入


Node.jsのCoreへESMとCJSの新しい方式が実験的フェイズ(stability: 1)として入ります。

ESM対応は安定化までのプランとしてステージを4つ(0 -3)用意しており、現在が2です。

github.com

2019年の10月に実験的から安定的へ移行するのが最終目標となります。(stage:3)

内容まとめ

github.com

  • --es-module-specifier-resolution=node|explicitで処理解決方法を決定する
    • explicit がデフォルト
  • --entry-type=commonjs|moduleでCJSかESMかを決定する
    • デフォルトは近しい親にあるpackage.jsontypeフィールドを参照する
  • ESMではデフォルトでjsonは読み込めない
    • --experimental-json-modulesを付ける必要がある
  • CJSとESMの違い
    • ESMの場合、拡張子が必須
    • NODE_PATHがない
    • require, exports, module.exports, __filename, __dirnameがない
    • require.extensions , require.cache の使用不可
    • URL-basedのパス指定

とりあえず、package.jsontypeフィールド追加すると、そのスコープ内の.jsファイルはそのモジュールタイプになるよって覚えておけばいいです。

ESM事前知識

以下の記事を読んでください。

blog.hiroppy.me

PR

CoreへのPR

github.com

初期提案実装

github.com

--es-module-specifier-resolution

explicitnode が存在し、デフォルトはexplicitです。

違いは以下の通りとなります。

  • 拡張子を省略することができない
  • indexを許容しない

まだ、変更される可能性が高いため注意が必要です。
今までどおりの挙動を望むのであれば、nodeを指定する必要があります。

ゾルアルゴリズム

typeフラグがmoduleの場合、package.jsonを軸に次の package.json までにネストされたフォルダとサブフォルダをすべて ESM とみなす仕様(そしてつぎpackage.jsonのフラグがmoduleの場合は続く)
もしpackage.jsonがない場合は、デフォルトでcommonjsとなります。

blog.hiroppy.me

使用法

実験的なフェイズなため、実行時に--experimental-modulesフラグが必要です。

.mjsがエントリーポイントの場合

この場合は、デフォルトでESMとして読み込みます。

node --experimental-modules index.mjs

また、上記の実行の場合、エントリーポイントからESM形式でimportするファイルは.mjsである必要があります。

しかし、--entry-typeフラグ及び、package.jsontypemoduleを指定すると、.mjsという拡張子を使うことなく、ESMとして読み込むことが可能となります。

// package.json

{
  "type": "module"
}

--entry-type

このフラグには、commonjsmoduleの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.jsontypeフィールドを参照し、モジュール方式を決定していきます。

一般的に今までのnode_modulesのpackage.jsontypeフィールドを持たないため、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';