例えばサンプルコードを同梱したい場合や, コンパイラのセルフホストのような場合など, Node.js パッケージの内部でそのパッケージ自身を参照したくなることがあります.
あるか?
このときの方法がいくつかあるので軽くまとめます.
ここでは例として以下のようなディレクトリ構造になっている mypackage
パッケージを仮定して, examples/
以下のサンプルコードからパッケージ自身を参照することを考えます.
CommonJS と ES modules で挙動が異なるので, どちらの形式であるかを明示するために拡張子には常に .cjs
か .mjs
を使います.
+-- package.json
+-- lib/
| \-- index.cjs
\-- examples/
+-- greet.cjs
\-- greet.mjs
package.json
と lib/index.cjs
はひとまずこういう感じ.
{
"name": "mypackage",
"main": "lib/index.cjs"
}
"use strict";
function greet(name) {
return `Hello, ${name}!`;
}
module.exports = { greet };
CommonJS の場合は package.json
のあるディレクトリを require
すると, そのパッケージ自身 (より正確には main
フィールドに指定されたファイル) を参照できます (参考: Node.js のドキュメント, 特に LOAD_AS_DIRECTORY
あたり).
"use strict";
const { greet } = require("..");
console.log(greet("Alice"));
$ node examples/greet.cjs
Hello, Alice!
ただし ES modules の場合は, このようにディレクトリを import
することは禁止されています.
import { greet } from "..";
console.log(greet("Alice"));
$ node examples/greet.mjs
node:internal/errors:497
ErrorCaptureStackTrace(err);
^
Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '/path/to/mypackage/' is not supported resolving ES modules imported from /path/to/mypackage/examples/greet.mjs
自分自身の名前で参照する
package.json
に exports
フィールドがあれば, 自身のパッケージ名を使って自分を参照できます (参考: Node.js のドキュメント).
この方法であれば CommonJS と ES modules の両方で動作します.
{
"name": "mypackage",
"main": "lib/index.cjs",
"exports": "./lib/index.cjs"
}
import { greet } from "mypackage";
console.log(greet("Alice"));
import { greet } from "mypackage";
console.log(greet("Alice"));
$ node examples/greet.cjs
Hello, Alice!
$ node examples/greet.mjs
Hello, Alice!
exports
を追加することに問題がなければ1, パッケージをユーザーと同じ方法で参照できてわかりやすいので, 基本的にはこれがおすすめ.
自分自身の別のバージョンを参照する
コンパイラが自身をセルフホストする場合など, ビルドツールの開発では依存関係に自身の安定版を追加して, それを使って自身のビルドを行うといったことがあります (microsoft/TypeScript が好例).
package.json
に exports
フィールドがない場合は, 以下のように自分自身を依存関係に追加するだけで, mypackage
という名前で自身の安定版を参照できるようになります.
{
"name": "mypackage",
"version": "1.1.0",
"main": "lib/index.cjs",
"devDependencies": {
"mypackage": "^1.0.0"
}
}
ところが exports
フィールドがある場合は, 上で説明した自分自身を名前で参照する挙動が優先されて, mypackage
という名前では安定版ではなく開発中の自分自身を参照することになってしまいます.
この場合は npm, yarn, pnpm といった各種パッケージマネージャーが共通して提供しているエイリアス機能を使って, 安定版を別の名前 (例えば mypackage-stable
など) でインストールして, 名前の重複を回避してやるとよいです.
{
"name": "mypackage",
"version": "1.1.0",
"main": "lib/index.cjs",
"exports": "./lib/index.cjs",
"devDependencies": {
"mypackage-stable": "npm:mypackage@^1.0.0"
}
}