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

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

2019 / 03 / 19
Hiroppy

Hiroppy

JavaScript Engineer


この記事は Hatena Blog からの移行記事です

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

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

modules/doc/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.

github.com
modules/doc/plan-for-new-modules-implementation.md at main · nodejs/modules

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 - hiroppy's site

Node.jsに入るECMAScript Mdoulesの特徴を紹介します

blog.hiroppy.me
Node.jsとECMAScript Modules - hiroppy's site

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 team and reflects Phase 2 of our new modules plan. A longer form description of these changes can ...

github.com
new ESM implementation by MylesBorins · Pull Request #26745 · nodejs/node

初期提案実装


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 addition "type": "esm" in the package.json is renamed to "type": &...

github.com
Entry points proposal spec and implementation by guybedford · Pull Request #32 · nodejs/ecmascript-modules

—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 について - hiroppy's site

Node.jsに新しく入る可能性があるpackage modeについて紹介します

blog.hiroppy.me
Node.js Package Mode について - hiroppy's site

使用法

実験的なフェイズなため、実行時に--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";

関連記事