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 モジュールのファイルパス
参考