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

2019 / 03 / 19

Edit
🚨 This article hasn't been updated in over a year
💁‍♀️ This post was copied from Hatena Blog

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

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

modules/plan-for-new-modules-implementation.md at main · nodejs/modules Node.js Modules Team. Contribute to nodejs/modules development by creating an account on GitHub.

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

内容まとめ

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

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

ESM 事前知識

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

Node.jsとECMAScript Modules - 技術探し 覚えておくべきこと ESMを使いたい場合は、拡張子を.mjsにする .mjsの拡張子は省略可能である ESMのファイルをトップレベルではCJSでインポート出来ない CJSのファイルをインポートするのに...

PR

Core への PR


new ESM implementation by MylesBorins · Pull Request #26745 · nodejs/node This PR updates the current --experimental-modules implementation based on the work of the modules t...

初期提案実装


Entry points proposal spec and implementation by guybedford · Pull Request #32 · nodejs/ecmascript-modules This implements a top-level --type flag for --experimental-modules with the following behaviours. In...

—es-module-specifier-resolution

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

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

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

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

リゾルバアルゴリズム

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

Node.js Package Mode について - 技術探し Node.js Package Mode について

使用法

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

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

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

node --experimental-modules index.mjs

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

しかし、--entry-typeフラグ及び、package.json のtypemoduleを指定すると、.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.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";