Node.jsライブラリ/ツールをESMに移行する[Node.js 12+]
Node.js 12からECMAScript Modules(ESM)がデフォルトサポートとなったため、Node.js向けのライブラリやツールを書く際にネイティブなESMを使えるようになった。
既存のNode.jsでCommonJS向けに書かれたライブラリなどをESMへ移行する際のやり方についてをまとめる。
追記: いい感じにCJSからESMへの移行を補助するを書いた
あわせて読む
パッケージの移行
パッケージをESMなパッケージとして明示する方法は2つ
-
.mjs
の拡張子を使う -
package.json
のtype
フィールドにmodule
の値を設定する
基本的にパッケージ単位で移行すると思うので、後者の package.json
の "type": "module"
を設定する。
{
"name": "pkg-example",
"main": "index.js",
+ "type": "module"
}
この設定をすると、パッケージ内のJavaScriptがモジュールのコンテキストで実行されるようになる。
モジュールでは常にstrict modeとなり、Node.jsのESMではrequire
、exports
、__dirname
、__filename
などが利用できなくなる。
代わりに、ES2015+のimport
とexport
文がモジュール構文として利用できるようになる。
また、ESMではimport
するファイル名の拡張子(.js
)が必須となる。
そのため、パッケージをESM移行するためには、コードも書き換える必要があるケースがほとんどとなる。
参考:
コードのマイグレーション
CommonJSに対応するESMの書き方
require.main
直接実行されたか、モジュールとして読み込まれたかを判定したいとき
CJS:
if (require.main === module) {
// node script.cjs
} else {
// require("./script.cjs")
}
ESM:
import url from "url";
const self = url.fileURLToPath(import.meta.url);
if (process.argv [1] === self) {
// node script.mjs
} else {
// require("./script.mjs")
}
参考
__dirname
と __filename
CJS:
__dirname; // スクリプトのディレクトリパス
__filename; // スクリプトのファイルパス
ESM:
import url from "url";
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
参考
import
ESMは import
するファイルの拡張子が必須。
- モジュールは拡張子なしで良い
- 相対パスは
.js
をつける必要がある - 2021-06-12現在では、
.ts
内でのimport
も.js
をつける - 2021-06-12現在では、
import type
に関しては.js
は不要
import assert from "assert"; // モジュール
import foo from "./foo.js"; // 相対パス
import type Foo from "./foo"; // import type
require.resolve
モジュールのファイルパスを取得するrequire.resolve
CJS:
const filepath = require.resolve("mod"); // mod モジュールのファイルパス
ESM:
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const filepath = require.resolve("mod"); // mod モジュールのファイルパス
:memo: Experimentalフラグ付きで import.meta.resolve
が直接的な代替となる。
const filepath = import.meta.resolve("mod"); // mod モジュールのファイルパス
参考