Open9

Node.jsライブラリ/ツールをESMに移行する[Node.js 12+]

azuazu

Node.js 12からECMAScript Modules(ESM)がデフォルトサポートとなったため、Node.js向けのライブラリやツールを書く際にネイティブなESMを使えるようになった。

既存のNode.jsでCommonJS向けに書かれたライブラリなどをESMへ移行する際のやり方についてをまとめる。

追記: いい感じにCJSからESMへの移行を補助するを書いた

あわせて読む

azuazu

パッケージの移行

パッケージをESMなパッケージとして明示する方法は2つ

  • .mjs の拡張子を使う
  • package.jsontype フィールドに module の値を設定する

基本的にパッケージ単位で移行すると思うので、後者の package.json"type": "module"を設定する。

{
   "name": "pkg-example",
   "main": "index.js",
+  "type": "module"
}

この設定をすると、パッケージ内のJavaScriptがモジュールのコンテキストで実行されるようになる。
モジュールでは常にstrict modeとなり、Node.jsのESMではrequireexports__dirname__filename などが利用できなくなる。
代わりに、ES2015+のimportexport文がモジュール構文として利用できるようになる。

また、ESMではimportするファイル名の拡張子(.js)が必須となる。
そのため、パッケージをESM移行するためには、コードも書き換える必要があるケースがほとんどとなる。

参考:

azuazu

コードのマイグレーション

CommonJSに対応するESMの書き方

azuazu

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")
}

参考

azuazu

__dirname__filename

CJS:

__dirname; // スクリプトのディレクトリパス
__filename; // スクリプトのファイルパス

ESM:

import url from "url";
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

参考

azuazu

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
azuazu

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

参考