モジュール: パッケージ#

はじめに#

パッケージとは、package.json ファイルによって記述されるフォルダツリーです。パッケージは、package.json ファイルを含むフォルダと、次の package.json ファイルを含むフォルダ、または node_modules という名前のフォルダまでのすべてのサブフォルダで構成されます。

このページでは、package.json ファイルを記述するパッケージ作者のためのガイダンスと、Node.js によって定義された package.json フィールドのリファレンスを提供します。

モジュールシステムの決定#

はじめに#

Node.js は、node に初期入力として渡された場合、または import 文や import() 式によって参照された場合、以下を ES モジュールとして扱います。

  • .mjs 拡張子を持つファイル。

  • 最も近い親の package.json ファイルが、値が "module" であるトップレベルの "type" フィールドを含む場合の .js 拡張子を持つファイル。

  • --input-type=module フラグを付けて、--eval の引数として渡される文字列、または STDIN を介して node にパイプされる文字列。

  • importexport 文、import.meta など、ES モジュールとしてのみ正常に解析される構文を含むコードで、どのように解釈されるべきかの明示的なマーカーがないもの。明示的なマーカーとは、.mjs または .cjs 拡張子、"module" または "commonjs" の値を持つ package.json"type" フィールド、または --input-type フラグです。動的な import() 式は CommonJS または ES モジュールのどちらでもサポートされており、ファイルを ES モジュールとして扱うことを強制するものではありません。構文検出を参照してください。

Node.js は、node に初期入力として渡された場合、または import 文や import() 式によって参照された場合、以下を CommonJS として扱います。

  • .cjs 拡張子を持つファイル。

  • 最も近い親の package.json ファイルが、値が "commonjs" であるトップレベルの "type" フィールドを含む場合の .js 拡張子を持つファイル。

  • --input-type=commonjs フラグを付けて、--eval または --print の引数として渡される文字列、または STDIN を介して node にパイプされる文字列。

  • 親の package.json ファイルがない、または最も近い親の package.json ファイルに type フィールドがない .js 拡張子のファイルで、かつコードが CommonJS として正常に評価できるもの。言い換えれば、Node.js はそのような「曖昧な」ファイルをまず CommonJS として実行しようとし、パーサーが ES モジュール構文を見つけたために CommonJS としての評価が失敗した場合に ES モジュールとして再評価します。

「曖昧な」ファイルに ES モジュール構文を記述するとパフォーマンスコストが発生するため、作者は可能な限り明示的に記述することが推奨されます。特に、パッケージ作者は、すべてのソースが CommonJS であるパッケージであっても、常に package.json ファイルに "type" フィールドを含めるべきです。パッケージの type を明示的にすることで、将来 Node.js のデフォルトタイプが変更された場合でもパッケージを将来にわたって保証し、ビルドツールやローダーがパッケージ内のファイルをどのように解釈すべきかを判断しやすくなります。

構文検出#

安定性: 1.2 - Release candidate

Node.js は、曖昧な入力のソースコードを検査し、ES モジュール構文が含まれているかどうかを判断します。そのような構文が検出された場合、入力は ES モジュールとして扱われます。

曖昧な入力は次のように定義されます。

  • .js 拡張子を持つ、または拡張子がないファイルで、かつ制御する package.json ファイルがないか、type フィールドがないもの。
  • --input-type が指定されていない場合の文字列入力 (--eval または STDIN)。

ES モジュール構文は、CommonJS として評価された場合にスローされる構文として定義されます。これには以下が含まれます。

  • import 文 (ただし、CommonJS で有効な import() 式は除く)。
  • export 文。
  • import.meta 参照。
  • モジュールのトップレベルでの await
  • CommonJS ラッパー変数 (require, module, exports, __dirname, __filename) のレキシカルな再宣言。

モジュールローダー#

Node.js には、指定子を解決しモジュールをロードするための2つのシステムがあります。

CommonJS モジュールローダーがあります。

  • これは完全に同期的です。
  • require() 呼び出しの処理を担当します。
  • モンキーパッチが可能です。
  • フォルダをモジュールとしてサポートします。
  • 指定子を解決する際、完全一致が見つからない場合、拡張子 (.js, .json, 最後に .node) を追加しようとし、次に フォルダをモジュールとして解決しようと試みます。
  • .json を JSON テキストファイルとして扱います。
  • .node ファイルは、process.dlopen() でロードされるコンパイル済みのアドオンモジュールとして解釈されます。
  • .json または .node 拡張子のないすべてのファイルを JavaScript テキストファイルとして扱います。
  • モジュールグラフが同期的である (トップレベルの await がない) 場合にのみ、CommonJS モジュールから ECMAScript モジュールをロードするために使用できます。ECMAScript モジュールでない JavaScript テキストファイルをロードするために使用されると、そのファイルは CommonJS モジュールとしてロードされます。

ECMAScript モジュールローダーがあります。

  • require() のためにモジュールをロードする場合を除き、非同期的です。
  • import 文と import() 式の処理を担当します。
  • モンキーパッチはできず、ローダーフックを使用してカスタマイズできます。
  • フォルダをモジュールとしてサポートせず、ディレクトリインデックス (例: './startup/index.js') は完全に指定する必要があります。
  • 拡張子の検索は行いません。指定子が相対または絶対ファイル URL の場合、ファイル拡張子を提供する必要があります。
  • JSON モジュールをロードできますが、インポートタイプ属性が必要です。
  • JavaScript テキストファイルには .js, .mjs, .cjs 拡張子のみを受け入れます。
  • JavaScript の CommonJS モジュールをロードするために使用できます。そのようなモジュールは、名前付きエクスポートを特定するために cjs-module-lexer を通過し、静的解析によって決定できる場合は利用可能になります。インポートされた CommonJS モジュールは、URL が絶対パスに変換され、その後 CommonJS モジュールローダーを介してロードされます。

package.json とファイル拡張子#

パッケージ内では、package.json"type" フィールドが、Node.js が .js ファイルをどのように解釈するかを定義します。package.json ファイルに "type" フィールドがない場合、.js ファイルは CommonJS として扱われます。

package.json"type" の値が "module" である場合、Node.js はそのパッケージ内の .js ファイルを ES モジュール構文を使用していると解釈します。

"type" フィールドは、初期エントリーポイント (node my-app.js) だけでなく、import 文や import() 式によって参照されるファイルにも適用されます。

// my-app.js, treated as an ES module because there is a package.json
// file in the same folder with "type": "module".

import './startup/init.js';
// Loaded as ES module since ./startup contains no package.json file,
// and therefore inherits the "type" value from one level up.

import 'commonjs-package';
// Loaded as CommonJS since ./node_modules/commonjs-package/package.json
// lacks a "type" field or contains "type": "commonjs".

import './node_modules/commonjs-package/index.js';
// Loaded as CommonJS since ./node_modules/commonjs-package/package.json
// lacks a "type" field or contains "type": "commonjs". 

.mjs で終わるファイルは、最も近い親の package.json に関係なく、常に ES モジュールとしてロードされます。

.cjs で終わるファイルは、最も近い親の package.json に関係なく、常に CommonJS としてロードされます。

import './legacy-file.cjs';
// Loaded as CommonJS since .cjs is always loaded as CommonJS.

import 'commonjs-package/src/index.mjs';
// Loaded as ES module since .mjs is always loaded as ES module. 

.mjs.cjs 拡張子は、同じパッケージ内でタイプを混在させるために使用できます。

  • "type": "module" パッケージ内では、特定のファイルを .cjs 拡張子で命名することにより、Node.js にそのファイルを CommonJS として解釈するように指示できます ("module" パッケージ内では .js.mjs の両方が ES モジュールとして扱われるため)。

  • "type": "commonjs" パッケージ内では、特定のファイルを .mjs 拡張子で命名することにより、Node.js にそのファイルを ES モジュールとして解釈するように指示できます ("commonjs" パッケージ内では .js.cjs の両方が CommonJS として扱われるため)。

--input-type フラグ#

--eval (または -e) の引数として渡される文字列、または STDIN を介して node にパイプされる文字列は、--input-type=module フラグが設定されている場合、ES モジュールとして扱われます。

node --input-type=module --eval "import { sep } from 'node:path'; console.log(sep);"

echo "import { sep } from 'node:path'; console.log(sep);" | node --input-type=module 

完全を期すために、文字列入力を明示的に CommonJS として実行するための --input-type=commonjs もあります。これは --input-type が指定されていない場合のデフォルトの動作です。

パッケージのエントリーポイント#

パッケージの package.json ファイルには、パッケージのエントリーポイントを定義できる2つのフィールドがあります: "main""exports" です。両方のフィールドは、ES モジュールと CommonJS モジュールの両方のエントリーポイントに適用されます。

"main" フィールドは Node.js のすべてのバージョンでサポートされていますが、その機能は限られています。これはパッケージのメインエントリーポイントのみを定義します。

"exports" は、"main" のモダンな代替手段を提供し、複数のエントリーポイントの定義、環境間の条件付きエントリー解決のサポート、そして "exports" で定義されたもの以外のエントリーポイントをすべて禁止することを可能にします。このカプセル化により、モジュールの作者はパッケージの公開インターフェースを明確に定義できます。

現在サポートされているバージョンの Node.js を対象とする新しいパッケージには、"exports" フィールドが推奨されます。Node.js 10 以前をサポートするパッケージには、"main" フィールドが必要です。"exports""main" の両方が定義されている場合、サポートされているバージョンの Node.js では "exports" フィールドが "main" よりも優先されます。

条件付きエクスポートは、"exports" 内で使用して、パッケージが require または import を介して参照されるかどうかを含む、環境ごとに異なるパッケージエントリーポイントを定義できます。1つのパッケージで CommonJS と ES モジュールの両方をサポートする方法の詳細については、デュアル CommonJS/ES モジュールパッケージのセクションを参照してください。

"exports" フィールドを導入する既存のパッケージは、パッケージの利用者が package.json (例: require('your-package/package.json')) を含む、定義されていないエントリーポイントを使用できなくなります。これは破壊的変更になる可能性が高いです。

"exports" の導入を破壊的変更にしないためには、以前にサポートされていたすべてのエントリーポイントがエクスポートされるようにしてください。パッケージの公開 API が明確に定義されるように、エントリーポイントを明示的に指定するのが最善です。たとえば、以前に main, lib, feature, package.json をエクスポートしていたプロジェクトは、次の package.exports を使用できます。

{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    "./lib/index": "./lib/index.js",
    "./lib/index.js": "./lib/index.js",
    "./feature": "./feature/index.js",
    "./feature/index": "./feature/index.js",
    "./feature/index.js": "./feature/index.js",
    "./package.json": "./package.json"
  }
} 

あるいは、プロジェクトはエクスポートパターンを使用して、拡張子付きと拡張子なしのサブパスの両方でフォルダ全体をエクスポートすることを選択できます。

{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./lib": "./lib/index.js",
    "./lib/*": "./lib/*.js",
    "./lib/*.js": "./lib/*.js",
    "./feature": "./feature/index.js",
    "./feature/*": "./feature/*.js",
    "./feature/*.js": "./feature/*.js",
    "./package.json": "./package.json"
  }
} 

上記でマイナーパッケージバージョンの後方互換性を提供した後、パッケージの将来のメジャー変更では、公開される特定の機能エクスポートのみにエクスポートを適切に制限できます。

{
  "name": "my-package",
  "exports": {
    ".": "./lib/index.js",
    "./feature/*.js": "./feature/*.js",
    "./feature/internal/*": null
  }
} 

メインエントリーポイントのエクスポート#

新しいパッケージを作成する際は、"exports" フィールドを使用することが推奨されます。

{
  "exports": "./index.js"
} 

"exports" フィールドが定義されると、パッケージのすべてのサブパスがカプセル化され、インポートする側からは利用できなくなります。たとえば、require('pkg/subpath.js')ERR_PACKAGE_PATH_NOT_EXPORTED エラーをスローします。

このエクスポートのカプセル化は、ツールにとってより信頼性の高いパッケージインターフェースの保証を提供し、パッケージの semver アップグレードを扱う際に役立ちます。ただし、require('/path/to/node_modules/pkg/subpath.js') のようにパッケージの絶対サブパスを直接 require すると subpath.js がロードされるため、これは強力なカプセル化ではありません。

現在サポートされているすべてのバージョンの Node.js とモダンなビルドツールは、"exports" フィールドをサポートしています。古いバージョンの Node.js や関連するビルドツールを使用しているプロジェクトでは、同じモジュールを指す "main" フィールドを "exports" と共に含めることで互換性を達成できます。

{
  "main": "./index.js",
  "exports": "./index.js"
} 

サブパスのエクスポート#

"exports" フィールドを使用する場合、メインエントリーポイントを "." サブパスとして扱うことで、メインエントリーポイントと共にカスタムサブパスを定義できます。

{
  "exports": {
    ".": "./index.js",
    "./submodule.js": "./src/submodule.js"
  }
} 

これで、"exports" で定義されたサブパスのみが利用者によってインポートできます。

import submodule from 'es-module-package/submodule.js';
// Loads ./node_modules/es-module-package/src/submodule.js 

他のサブパスはエラーになります。

import submodule from 'es-module-package/private-module.js';
// Throws ERR_PACKAGE_PATH_NOT_EXPORTED 
サブパスの拡張子#

パッケージの作者は、エクスポートに拡張子付き (import 'pkg/subpath.js') または拡張子なし (import 'pkg/subpath') のサブパスのどちらかを提供すべきです。これにより、エクスポートされる各モジュールに対してサブパスが1つだけになり、すべての依存関係が一貫した指定子をインポートし、利用者のためのパッケージ契約を明確に保ち、パッケージのサブパス補完を簡素化します。

従来、パッケージは拡張子なしのスタイルを使用する傾向がありました。これには、読みやすさやパッケージ内のファイルの実際のパスを隠すという利点があります。

インポートマップがブラウザや他の JavaScript ランタイムでパッケージ解決の標準を提供するようになった現在、拡張子なしのスタイルを使用すると、インポートマップの定義が肥大化する可能性があります。明示的なファイル拡張子を使用すると、インポートマップがパッケージのサブパスエクスポートごとに個別のマップエントリを持つ代わりに、可能な限り複数のサブパスをマッピングするために パッケージフォルダマッピングを利用できるため、この問題を回避できます。これは、相対および絶対インポート指定子で完全な指定子パスを使用する必要があることとも一致します。

エクスポートターゲットのパスルールとバリデーション#

"exports" フィールドでターゲットとしてパスを定義する際、Node.js はセキュリティ、予測可能性、および適切なカプセル化を保証するためにいくつかのルールを強制します。これらのルールを理解することは、パッケージを公開する作者にとって非常に重要です。

ターゲットは相対URLでなければならない#

"exports" マップ内のすべてのターゲットパス(エクスポートキーに関連付けられた値)は、./ で始まる相対 URL 文字列でなければなりません。

// package.json
{
  "name": "my-package",
  "exports": {
    ".": "./dist/main.js",          // Correct
    "./feature": "./lib/feature.js", // Correct
    // "./origin-relative": "/dist/main.js", // Incorrect: Must start with ./
    // "./absolute": "file:///dev/null", // Incorrect: Must start with ./
    // "./outside": "../common/util.js" // Incorrect: Must start with ./
  }
} 

この振る舞いの理由には以下が含まれます。

  • セキュリティ: パッケージ自身のディレクトリ外から任意のファイルをエクスポートするのを防ぎます。
  • カプセル化: エクスポートされたすべてのパスがパッケージルートからの相対で解決されることを保証し、パッケージを自己完結させます。
パストラバーサルや無効なセグメントの禁止#

エクスポートターゲットは、パッケージのルートディレクトリ外の場所に解決されてはなりません。さらに、. (シングルドット)、.. (ダブルドット)、または node_modules (およびそれらのURLエンコードされた同等物) のようなパスセグメントは、通常、最初の ./ の後の target 文字列内、およびターゲットパターンに代入される任意の subpath 部分内で許可されていません。

// package.json
{
  "name": "my-package",
  "exports": {
    // ".": "./dist/../../elsewhere/file.js", // Invalid: path traversal
    // ".": "././dist/main.js",             // Invalid: contains "." segment
    // ".": "./dist/../dist/main.js",       // Invalid: contains ".." segment
    // "./utils/./helper.js": "./utils/helper.js" // Key has invalid segment
  }
} 

exports のシンタックスシュガー#

"." エクスポートが唯一のエクスポートである場合、"exports" フィールドは、このケースのためのシンタックスシュガーとして、直接 "exports" フィールドの値になることを提供します。

{
  "exports": {
    ".": "./index.js"
  }
} 

は次のように書くことができます。

{
  "exports": "./index.js"
} 

サブパスのインポート#

"exports" フィールドに加えて、パッケージ自体の中からのみのインポート指定子に適用されるプライベートなマッピングを作成するためのパッケージ "imports" フィールドがあります。

"imports" フィールドのエントリは、外部パッケージ指定子と区別するために、常に # で始まらなければなりません。

たとえば、imports フィールドは、内部モジュールに対して条件付きエクスポートの利点を得るために使用できます。

// package.json
{
  "imports": {
    "#dep": {
      "node": "dep-node-native",
      "default": "./dep-polyfill.js"
    }
  },
  "dependencies": {
    "dep-node-native": "^1.0.0"
  }
} 

ここで、import '#dep' は外部パッケージ dep-node-native (そのエクスポートも含む) の解決を得るのではなく、他の環境ではパッケージに関連するローカルファイル ./dep-polyfill.js を取得します。

"exports" フィールドとは異なり、"imports" フィールドは外部パッケージへのマッピングを許可します。

imports フィールドの解決ルールは、それ以外は exports フィールドと類似しています。

サブパスパターン#

少数のエクスポートまたはインポートを持つパッケージの場合、各エクスポートのサブパスエントリを明示的にリストアップすることをお勧めします。しかし、多数のサブパスを持つパッケージの場合、これは package.json の肥大化やメンテナンスの問題を引き起こす可能性があります。

これらのユースケースでは、代わりにサブパスのエクスポートパターンを使用できます。

// ./node_modules/es-module-package/package.json
{
  "exports": {
    "./features/*.js": "./src/features/*.js"
  },
  "imports": {
    "#internal/*.js": "./src/internal/*.js"
  }
} 

* マップは、文字列置換構文のみであるため、ネストされたサブパスを公開します。

右側のすべての * のインスタンスは、この値に置き換えられます。これには / セパレータが含まれている場合も含まれます。

import featureX from 'es-module-package/features/x.js';
// Loads ./node_modules/es-module-package/src/features/x.js

import featureY from 'es-module-package/features/y/y.js';
// Loads ./node_modules/es-module-package/src/features/y/y.js

import internalZ from '#internal/z.js';
// Loads ./node_modules/es-module-package/src/internal/z.js 

これは、ファイル拡張子に対する特別な処理なしの直接的な静的マッチングと置換です。マッピングの両側に "*.js" を含めることで、公開されるパッケージのエクスポートをJSファイルのみに制限します。

エクスポートが静的に列挙可能であるという特性は、エクスポートパターンでも維持されます。なぜなら、パッケージの個々のエクスポートは、右側のターゲットパターンをパッケージ内のファイルリストに対する ** グロブとして扱うことで決定できるからです。node_modules パスはエクスポートターゲットで禁止されているため、この展開はパッケージ自身のファイルにのみ依存します。

プライベートなサブフォルダをパターンから除外するには、null ターゲットを使用できます。

// ./node_modules/es-module-package/package.json
{
  "exports": {
    "./features/*.js": "./src/features/*.js",
    "./features/private-internal/*": null
  }
} 
import featureInternal from 'es-module-package/features/private-internal/m.js';
// Throws: ERR_PACKAGE_PATH_NOT_EXPORTED

import featureX from 'es-module-package/features/x.js';
// Loads ./node_modules/es-module-package/src/features/x.js 

条件付きエクスポート#

条件付きエクスポートは、特定の条件に応じて異なるパスにマッピングする方法を提供します。これらは CommonJS と ES モジュールの両方のインポートでサポートされています。

たとえば、require()import で異なる ES モジュールエクスポートを提供したいパッケージは、次のように記述できます。

// package.json
{
  "exports": {
    "import": "./index-module.js",
    "require": "./index-require.cjs"
  },
  "type": "module"
} 

Node.js は以下の条件を実装しており、条件が定義されるべき最も具体的なものから最も具体的でないものの順にリストされています。

  • "node-addons" - "node" と同様で、任意の Node.js 環境にマッチします。この条件は、よりユニバーサルでネイティブアドオンに依存しないエントリーポイントとは対照的に、ネイティブ C++ アドオンを使用するエントリーポイントを提供するために使用できます。この条件は --no-addons フラグで無効にできます。
  • "node" - 任意の Node.js 環境にマッチします。CommonJS または ES モジュールファイルにすることができます。ほとんどの場合、Node.js プラットフォームを明示的に指定する必要はありません。
  • "import" - パッケージが import または import() を介してロードされたとき、またはECMAScript モジュールローダーによる任意のトップレベルのインポートまたは解決操作を介してロードされたときにマッチします。ターゲットファイルのモジュール形式に関係なく適用されます。常に "require" と相互に排他的です。
  • "require" - パッケージが require() を介してロードされたときにマッチします。参照されるファイルは require() でロード可能であるべきですが、条件はターゲットファイルのモジュール形式に関係なくマッチします。想定される形式には、CommonJS、JSON、ネイティブアドオン、ES モジュールが含まれます。常に "import" と相互に排他的です。
  • "module-sync" - パッケージが importimport()require() のいずれでロードされたかに関わらずマッチします。形式は ES モジュールであることが期待され、モジュールグラフにトップレベルの await を含まないものであるべきです。もし含まれている場合、モジュールが require() されると ERR_REQUIRE_ASYNC_MODULE がスローされます。
  • "default" - 常にマッチする汎用のフォールバック。CommonJS または ES モジュールファイルにすることができます。この条件は常に最後にくるべきです。

"exports" オブジェクト内では、キーの順序が重要です。条件マッチング中、前のエントリが高い優先度を持ち、後のエントリよりも優先されます。一般的なルールは、条件はオブジェクトの順序で最も具体的なものから最も具体的でないものへと並べるべきであるということです

"import""require" 条件を使用すると、いくつかの危険が生じる可能性があり、これについては デュアル CommonJS/ES モジュールパッケージのセクションでさらに説明されています。

"node-addons" 条件は、ネイティブ C++ アドオンを使用するエントリーポイントを提供するために使用できます。ただし、この条件は --no-addons フラグで無効にできます。"node-addons" を使用する場合、"default" をよりユニバーサルなエントリーポイントを提供する拡張機能として扱うことが推奨されます。たとえば、ネイティブアドオンの代わりに WebAssembly を使用するなどです。

条件付きエクスポートは、エクスポートのサブパスにも拡張できます。例えば、

{
  "exports": {
    ".": "./index.js",
    "./feature.js": {
      "node": "./feature-node.js",
      "default": "./feature.js"
    }
  }
} 

require('pkg/feature.js')import 'pkg/feature.js' が Node.js と他の JS 環境とで異なる実装を提供できるパッケージを定義します。

環境分岐を使用する場合は、可能な限り常に "default" 条件を含めてください。"default" 条件を提供することで、未知の JS 環境がこのユニバーサルな実装を使用できるようになり、これらの JS 環境が条件付きエクスポートを持つパッケージをサポートするために既存の環境になりすます必要がなくなります。このため、通常は "node""browser" の条件分岐を使用するよりも、"node""default" の条件分岐を使用する方が好ましいです。

ネストされた条件#

直接マッピングに加えて、Node.js はネストされた条件オブジェクトもサポートしています。

たとえば、Node.js での使用のみを目的とし、ブラウザでは使用しないデュアルモードのエントリーポイントのみを持つパッケージを定義するには、

{
  "exports": {
    "node": {
      "import": "./feature-node.mjs",
      "require": "./feature-node.cjs"
    },
    "default": "./feature.mjs"
  }
} 

条件は、フラットな条件と同様に順にマッチングされ続けます。ネストされた条件にマッピングがない場合、親条件の残りの条件をチェックし続けます。このようにして、ネストされた条件はネストされた JavaScript の if 文と類似した振る舞いをします。

ユーザー条件の解決#

Node.js を実行する際、--conditions フラグでカスタムユーザー条件を追加できます。

node --conditions=development index.js 

これにより、パッケージのインポートとエクスポートで "development" 条件が解決され、同時に既存の "node""node-addons""default""import""require" 条件が適切に解決されます。

フラグを繰り返すことで、任意の数のカスタム条件を設定できます。

典型的な条件は英数字のみを含むべきで、必要に応じてセパレータとして ":"、"-"、または "=" を使用します。それ以外のものは、node の外部で互換性の問題に遭遇する可能性があります。

node では、条件にはほとんど制限がありませんが、具体的には以下のものが含まれます。

  1. 少なくとも1文字を含まなければなりません。
  2. 相対パスも許可される場所に現れる可能性があるため、"." で始めることはできません。
  3. 一部の CLI ツールによってカンマ区切りのリストとして解析される可能性があるため、"," を含むことはできません。
  4. JS オブジェクトのプロパティキーの順序に予期しない影響を与える可能性があるため、「10」のような整数プロパティキーであってはなりません。

コミュニティによる条件の定義#

"import", "require", "node", "module-sync", "node-addons", "default" というNode.js コアに実装されている条件以外の条件文字列は、デフォルトで無視されます。

他のプラットフォームは他の条件を実装する可能性があり、ユーザー条件は --conditions / -C フラグを介して Node.js で有効にすることができます。

カスタムパッケージ条件は正しい使用法を保証するために明確な定義が必要なため、エコシステムの協調を支援するために、一般的に知られているパッケージ条件とその厳密な定義のリストを以下に提供します。

  • "types" - 型付けシステムが特定のエクスポートの型付けファイルを解決するために使用できます。この条件は常に最初に含めるべきです。
  • "browser" - あらゆるウェブブラウザ環境。
  • "development" - 開発専用の環境エントリーポイントを定義するために使用できます。例えば、開発モードで実行する際により良いエラーメッセージなどの追加のデバッグコンテキストを提供します。常に "production" と相互に排他的でなければなりません。
  • "production" - 本番環境のエントリーポイントを定義するために使用できます。常に "development" と相互に排他的でなければなりません。

他のランタイムについては、プラットフォーム固有の条件キーの定義は WinterCG によって Runtime Keys 提案仕様で維持されています。

新しい条件の定義は、このセクションの Node.js ドキュメントにプルリクエストを作成することで、このリストに追加できます。ここに新しい条件の定義をリストするための要件は以下の通りです。

  • 定義はすべての実装者にとって明確で曖昧さがないこと。
  • その条件が必要な理由についてのユースケースが明確に正当化されていること。
  • 十分な既存の実装使用例が存在すること。
  • 条件名が他の条件定義や広く使用されている条件と競合しないこと。
  • 条件定義のリストアップが、そうでなければ不可能だったエコシステムへの協調的な利益を提供すること。例えば、これは企業固有またはアプリケーション固有の条件には必ずしも当てはまりません。
  • その条件は、Node.js ユーザーが Node.js コアのドキュメントにあると期待するようなものであるべきです。"types" 条件は良い例です。それは Runtime Keys 提案にはあまり属しませんが、ここ Node.js のドキュメントにはよく合っています。

上記の定義は、いずれ専用の条件レジストリに移動される可能性があります。

パッケージ名を使い自己参照する#

パッケージ内では、パッケージの package.json"exports" フィールドで定義された値を、パッケージの名前を介して参照できます。たとえば、package.json が以下のようになっていると仮定します。

// package.json
{
  "name": "a-package",
  "exports": {
    ".": "./index.mjs",
    "./foo.js": "./foo.js"
  }
} 

すると、そのパッケージ内のどのモジュールも、パッケージ自体のエクスポートを参照できます。

// ./a-module.mjs
import { something } from 'a-package'; // Imports "something" from ./index.mjs. 

自己参照は、package.json"exports" がある場合にのみ利用可能で、その "exports" (package.json内) が許可するもののみをインポートできます。したがって、前のパッケージを前提とすると、以下のコードはランタイムエラーを生成します。

// ./another-module.mjs

// Imports "another" from ./m.mjs. Fails because
// the "package.json" "exports" field
// does not provide an export named "./m.mjs".
import { another } from 'a-package/m.mjs'; 

自己参照は、ES モジュール内と CommonJS モジュール内の両方で require を使用する場合にも利用できます。たとえば、このコードも機能します。

// ./a-module.js
const { something } = require('a-package/foo.js'); // Loads from ./foo.js. 

最後に、自己参照はスコープ付きパッケージでも機能します。たとえば、このコードも機能します。

// package.json
{
  "name": "@my/package",
  "exports": "./index.js"
} 
// ./index.js
module.exports = 42; 
// ./other.js
console.log(require('@my/package')); 
$ node other.js
42 

デュアル CommonJS/ES モジュールパッケージ#

詳細はパッケージのサンプルリポジトリを参照してください。

Node.js の package.json フィールド定義#

このセクションでは、Node.js ランタイムが使用するフィールドについて説明します。npm などの他のツールは、Node.js では無視され、ここでは文書化されていない追加のフィールドを使用します。

package.json ファイルの以下のフィールドが Node.js で使用されます。

  • "name" - パッケージ内で名前付きインポートを使用する際に関連します。パッケージマネージャーによってパッケージ名としても使用されます。
  • "main" - exports が指定されていない場合、および exports が導入される前のバージョンの Node.js でパッケージをロードする際のデフォルトモジュール。
  • "type" - .js ファイルを CommonJS または ES モジュールとしてロードするかを決定するパッケージタイプ。
  • "exports" - パッケージのエクスポートと条件付きエクスポート。存在する場合、パッケージ内からロードできるサブモジュールを制限します。
  • "imports" - パッケージ内のモジュールが使用するためのパッケージインポート。

"name"#

{
  "name": "package-name"
} 

"name" フィールドは、パッケージの名前を定義します。npm レジストリに公開するには、特定の要件を満たす名前が必要です。

"name" フィールドは、"exports" フィールドに加えて、パッケージをその名前で 自己参照するために使用できます。

"main"#

{
  "main": "./index.js"
} 

"main" フィールドは、node_modules のルックアップを介して名前でインポートされたときのパッケージのエントリーポイントを定義します。その値はパスです。

パッケージに "exports" フィールドがある場合、パッケージを名前でインポートする際には "main" フィールドよりも優先されます。

また、パッケージディレクトリが require() を介してロードされる際に使用されるスクリプトも定義します。

// This resolves to ./path/to/directory/index.js.
require('./path/to/directory'); 

"type"#

"type" フィールドは、その package.json ファイルを最も近い親として持つすべての .js ファイルに対して Node.js が使用するモジュール形式を定義します。

.js で終わるファイルは、最も近い親の package.json ファイルに値が "module" であるトップレベルのフィールド "type" が含まれている場合、ES モジュールとしてロードされます。

最も近い親の package.json は、現在のフォルダ、そのフォルダの親、というように node_modules フォルダまたはボリュームルートに達するまで検索して最初に見つかった package.json と定義されます。

// package.json
{
  "type": "module"
} 
# In same folder as preceding package.json
node my-app.js # Runs as ES module 

最も近い親の package.json"type" フィールドがないか、"type": "commonjs" が含まれている場合、.js ファイルは CommonJS として扱われます。ボリュームルートに達しても package.json が見つからない場合、.js ファイルは CommonJS として扱われます。

.js ファイルの import 文は、最も近い親の package.json"type": "module" が含まれている場合、ES モジュールとして扱われます。

// my-app.js, part of the same example as above
import './startup.js'; // Loaded as ES module because of package.json 

"type" フィールドの値に関わらず、.mjs ファイルは常に ES モジュールとして扱われ、.cjs ファイルは常に CommonJS として扱われます。

"exports"#

{
  "exports": "./index.js"
} 

"exports" フィールドは、node_modules ルックアップまたは自身の名前への 自己参照を介してロードされたパッケージのエントリーポイントを定義することを可能にします。これは Node.js 12+ で、内部のエクスポートされていないモジュールをカプセル化しつつ、サブパスのエクスポート条件付きエクスポートの定義をサポートできる"main"の代替としてサポートされています。

条件付きエクスポートは、"exports" 内で、パッケージが require または import を介して参照されるかどうかを含む、環境ごとの異なるパッケージのエントリーポイントを定義するためにも使用できます。

"exports" で定義されるすべてのパスは、./ で始まる相対ファイル URL でなければなりません。

"imports"#

// package.json
{
  "imports": {
    "#dep": {
      "node": "dep-node-native",
      "default": "./dep-polyfill.js"
    }
  },
  "dependencies": {
    "dep-node-native": "^1.0.0"
  }
} 

imports フィールドのエントリは、# で始まる文字列でなければなりません。

パッケージのインポートは、外部パッケージへのマッピングを許可します。

このフィールドは、現在のパッケージのサブパスのインポートを定義します。