パッケージの`package.json`内の`exports`フィールドを使用すると、`import "package"`または`import "package/sub/path"`のようなモジュールリクエストを使用する場合に使用するモジュールを宣言できます。これは、`main`フィールドまたは`index.js`ファイルを`package`に対して返し、`package/sub/path`に対してファイルシステムのルックアップを行うデフォルトの実装に取って代わります。
`exports`フィールドが指定されている場合、これらのモジュールリクエストのみが使用可能です。その他のリクエストは、ModuleNotFoundエラーになります。
一般的に、`exports`フィールドには、各プロパティがモジュールリクエストのサブパスを指定するオブジェクトが含まれている必要があります。上記の例では、`import "package"`には`"."`、`import "package/sub/path"`には`"./sub/path"`というプロパティを使用できます。`/`で終わるプロパティは、このプレフィックスを持つリクエストを古いファイルシステムのルックアップアルゴリズムに転送します。`*`で終わるプロパティの場合、`*`は任意の値を取ることができ、プロパティ値内の`*`は取得した値に置き換えられます。
例
{
"exports": {
".": "./main.js",
"./sub/path": "./secondary.js",
"./prefix/": "./directory/",
"./prefix/deep/": "./other-directory/",
"./other-prefix/*": "./yet-another/*/*.js"
}
}| モジュールリクエスト | 結果 |
|---|---|
package | .../package/main.js |
package/sub/path | .../package/secondary.js |
package/prefix/some/file.js | .../package/directory/some/file.js |
package/prefix/deep/file.js | .../package/other-directory/file.js |
package/other-prefix/deep/file.js | .../package/yet-another/deep/file/deep/file.js |
package/main.js | エラー |
単一の結果を提供する代わりに、パッケージ作成者は結果のリストを提供できます。このようなシナリオでは、このリストが順番に試され、最初の有効な結果が使用されます。
注:最初の有効な結果のみが使用され、すべての有効な結果が使用されるわけではありません。
例
{
"exports": {
"./things/": ["./good-things/", "./bad-things/"]
}
}ここでは、`package/things/apple`は`.../package/good-things/apple`または`.../package/bad-things/apple`で見つかる可能性があります。
`exports`フィールドに直接結果を提供する代わりに、パッケージ作成者はモジュールシステムが環境に関する条件に基づいて結果を選択できるようにすることができます。
この場合、条件と結果をマッピングするオブジェクトを使用する必要があります。条件はオブジェクトの順序で試されます。無効な結果を含む条件はスキップされます。条件はネストして論理ANDを作成できます。オブジェクトの最後の条件は特別な`default`条件にすることができ、これは常に一致する条件です。
例
{
"exports": {
".": {
"red": "./stop.js",
"yellow": "./stop.js",
"green": {
"free": "./drive.js",
"default": "./wait.js"
},
"default": "./drive-carefully.js"
}
}
}これは次のようなものになります。
if (red && valid('./stop.js')) return './stop.js';
if (yellow && valid('./stop.js')) return './stop.js';
if (green) {
if (free && valid('./drive.js')) return './drive.js';
if (valid('./wait.js')) return './wait.js';
}
if (valid('./drive-carefully.js')) return './drive-carefully.js';
throw new ModuleNotFoundError();使用可能な条件は、使用されるモジュールシステムとツールによって異なります。
パッケージへの単一のエントリ(`.`)のみをサポートする必要がある場合、`{ ".": ... }`オブジェクトのネストを省略できます。
{
"exports": "./index.mjs"
}{
"exports": {
"red": "./stop.js",
"green": "./drive.js"
}
}各キーが条件であるオブジェクトでは、プロパティの順序は重要です。条件は指定された順序で処理されます。
例:`{ "red": "./stop.js", "green": "./drive.js" }` != `{ "green": "./drive.js", "red": "./stop.js" }` (`red`と`green`の両方の条件が設定されている場合、最初のプロパティが使用されます)
各キーがサブパスであるオブジェクトでは、プロパティ(サブパス)の順序は重要ではありません。より具体的なパスの方が、それほど具体的なパスよりも優先されます。
例:`{ "./a/": "./x/", "./a/b/": "./y/", "./a/b/c": "./z" }` == `{ "./a/b/c": "./z", "./a/b/": "./y/", "./a/": "./x/" }` (順序は常に`./a/b/c` > `./a/b/` > `./a/`となります)
`exports`フィールドは、`main`、`module`、`browser`、またはカスタムなどの他のパッケージエントリフィールドよりも優先されます。
| 機能 | サポート対象 |
|---|---|
| `"."`プロパティ | Node.js、webpack、rollup、esinstall、wmr |
| 通常のプロパティ | Node.js、webpack、rollup、esinstall、wmr |
| `/`で終わるプロパティ | Node.js(1)、webpack、rollup、esinstall(2)、wmr(3) |
*で終わるプロパティ | Node.js、webpack、rollup、esinstall |
| 代替案 | Node.js、webpack、rollup、 |
| 省略形のみのパス | Node.js、webpack、rollup、esinstall、wmr |
| 省略形のみの条件 | Node.js、webpack、rollup、esinstall、wmr |
| 条件付き構文 | Node.js、webpack、rollup、esinstall、wmr |
| ネストされた条件付き構文 | Node.js、webpack、rollup、wmr(5) |
| 条件の順序 | Node.js、webpack、rollup、wmr(6) |
"default"条件 | Node.js、webpack、rollup、esinstall、wmr |
| パスの順序 | Node.js、webpack、rollup |
| マップされていない場合のエラー | Node.js、webpack、rollup、esinstall、wmr(7) |
| 条件とパスの混合時のエラー | Node.js、webpack、rollup |
(1) Node.jsでは非推奨。*を推奨します。
(2) "./"はキーとして意図的に無視されます。
(3) プロパティの値は無視され、プロパティキーがターゲットとして使用されます。キーと値が同一であるマッピングのみを有効に許可します。
(4) この構文はサポートされていますが、常に最初のエントリが使用されるため、実用的なユースケースでは使用できません。
(5) 代替の兄弟親条件へのフォールバックが正しく処理されていません。
(6) require条件オブジェクトの順序は正しく処理されていません。これは、wmrが参照構文を区別しないため、意図的なものです。
(7) "exports": "./file.js" の省略形を使用する場合、例:package/not-existing などのリクエストはそれに解決されます。省略形を使用しない場合、例:package/file.js の直接ファイルアクセスはエラーになりません。
モジュールを参照するために使用される構文に応じて、これらの条件のいずれかが設定されます。
| 条件 | 説明 | サポート対象 |
|---|---|---|
import | ESM構文または同様のものからリクエストが発行されます。 | Node.js、webpack、rollup、esinstall(1)、wmr(1) |
require | CommonJs/AMD構文または同様のものからリクエストが発行されます。 | Node.js、webpack、rollup、esinstall(1)、wmr(1) |
style | スタイルシート参照からリクエストが発行されます。 | |
sass | sassスタイルシート参照からリクエストが発行されます。 | |
asset | アセット参照からリクエストが発行されます。 | |
script | モジュールシステムを使用しない通常のスクリプトタグからリクエストが発行されます。 |
これらの条件は追加で設定される可能性もあります。
| 条件 | 説明 | サポート対象 |
|---|---|---|
module | JavaScriptを参照できるすべてのモジュール構文はESMをサポートしています。 ( importまたはrequireとのみ組み合わせる) | webpack、rollup、wmr |
esmodules | サポートされているツールによって常に設定されます。 | wmr |
types | 型宣言に関心のあるTypeScriptからリクエストが発行されます。 |
(1) importとrequireは、参照構文に関係なく独立して設定されます。requireは常に優先順位が低くなります。
次の構文により、import条件が設定されます。
import宣言import()式<script type="module"><link rel="preload/prefetch">new Worker(..., { type: "module" })importセクションimport.hot.accept/decline([...])Worklet.addModule次の構文により、require条件が設定されます。
require(...)define()require([...])require.resolve()require.ensure([...])require.contextmodule.hot.accept/decline([...])<script src="...">次の構文により、style条件が設定されます。
@import<link rel="stylesheet">次の構文により、asset条件が設定されます。
url()new URL(..., import.meta.url)<img src="...">次の構文により、script条件が設定されます。
<script src="...">scriptは、モジュールシステムがサポートされていない場合にのみ設定する必要があります。スクリプトがCommonJsをサポートするシステムによってプリプロセスされる場合、代わりにrequireを設定する必要があります。
この条件は、追加のプリプロセスなしでHTMLページにスクリプトタグとして挿入できるJavaScriptファイルを探す場合に使用します。
さまざまな最適化のために、次の条件が設定されます。
| 条件 | 説明 | サポート対象 |
|---|---|---|
production | 本番環境です。 デバッグツールを含めるべきではありません。 | webpack |
development | 開発環境です。 デバッグツールを含めるべきです。 | webpack |
注:productionとdevelopmentはすべてでサポートされているわけではないため、これらのいずれも設定されていない場合は、想定すべきではありません。
ターゲット環境に応じて、次の条件が設定されます。
| 条件 | 説明 | サポート対象 |
|---|---|---|
browser | コードはブラウザで実行されます。 | webpack、esinstall、wmr |
electron | コードはElectronで実行されます。(1) | webpack |
worker | コードは(Web)ワーカーで実行されます。(1) | webpack |
worklet | コードはWorkletで実行されます。(1) | - |
node | コードはNode.jsで実行されます。 | Node.js、webpack、wmr(2) |
deno | コードはDenoで実行されます。 | - |
react-native | コードはReact Nativeで実行されます。 | - |
(1) electron、worker、workletは、コンテキストに応じてnodeまたはbrowserのいずれかと組み合わせて使用されます。
(2) これはブラウザのターゲット環境に対して設定されます。
各環境には複数のバージョンがあるため、次のガイドラインが適用されます。
node: 互換性についてはenginesフィールドを参照してください。browser: パッケージの公開時点での現在の仕様とステージ4の提案と互換性があります。ポリフィルまたはトランスパイルは、利用者側で処理する必要があります。deno: 未定react-native: 未定ソースコードをプリプロセスするツールに応じて、次の条件が設定されます。
| 条件 | 説明 | サポート対象 |
|---|---|---|
webpack | webpackによって処理されます。 | webpack |
残念なことに、Node.jsをランタイムとして使用するNode.js用のnode-js条件はありません。これにより、Node.jsの例外の作成が簡素化されます。
次のツールはカスタム条件をサポートしています。
| ツール | サポート | 注記 |
|---|---|---|
| Node.js | はい | --conditions CLI引数を使用します。 |
| webpack | はい | resolve.conditionNames 設定オプションを使用します。 |
| rollup | はい | @rollup/plugin-node-resolve の exportConditions オプションを使用します。 |
| esinstall | いいえ | |
| wmr | いいえ |
カスタム条件には、次の命名スキームをお勧めします。
<会社名>:<条件名>
例:example-corp:beta、google:internal。
すべてのパターンは、パッケージへの単一の"."エントリで説明されていますが、各エントリに対してパターンを繰り返すことで、複数のエントリから拡張することもできます。
これらのパターンは、厳格なルールセットではなく、ガイドとして使用する必要があります。個々のパッケージに合わせて調整できます。
これらのパターンは、次の目標/前提に基づいています。
exportsは、未知の将来のケースのためにフォールバックを使用するように記述する必要があります。default条件はそれのために使用できます。パッケージの意図によっては、別のものが理にかなう場合があり、この場合、パターンをそれに合わせて調整する必要があります。例:コマンドラインツールの場合は、ブラウザのような未来とフォールバックはあまり意味がなく、この場合は、代わりにNode.jsのような環境とフォールバックを使用する必要があります。
複雑なユースケースでは、これらの条件をネストすることで、複数のパターンを組み合わせる必要があります。
これらのパターンは、環境固有のAPIを使用しないパッケージに適しています。
{
"type": "module",
"exports": "./index.js"
}注:ESMのみを提供することは、Node.jsに制限があります。そのようなパッケージは、Node.js >= 14 でのみ、importを使用する場合にのみ機能します。require()では機能しません。
{
"type": "module",
"exports": {
"node": {
"module": "./index.js",
"require": "./index.cjs"
},
"default": "./index.js"
}
}ほとんどのツールはESMバージョンを取得します。Node.jsはここでの例外です。require()を使用する場合は、CommonJsバージョンを取得します。これにより、require()とimportで参照する場合に、これらのパッケージの2つのインスタンスが作成されますが、パッケージに状態がないため、問題ありません。
module条件は、require()に対してESMをサポートするツール(Node.jsの場合のバンダラなど)を使用して、Nodeをターゲットとするコードをプリプロセスする場合の最適化として使用されます。このようなツールでは、例外はスキップされます。これは技術的にはオプションですが、そうでない場合はバンダラがパッケージのソースコードを2回含めることになります。
パッケージの状態をJSONファイルに隔離できる場合は、ステートレスパターンを使用することもできます。JSONは、他のモジュールシステムでグラフを汚染することなく、CommonJsとESMから利用できます。
ここでステートレスとは、二重モジュールインスタンス化のため2つの異なるクラスが存在する可能性があるため、instanceofを使用してクラスインスタンスをテストしないことを意味します。
{
"type": "module",
"exports": {
"node": {
"module": "./index.js",
"import": "./wrapper.js",
"require": "./index.cjs"
},
"default": "./index.js"
}
}// wrapper.js
import cjs from './index.cjs';
export const A = cjs.A;
export const B = cjs.B;ステートフルパッケージでは、パッケージが二度とインスタンス化されないようにする必要があります。
これはほとんどのツールでは問題ではありませんが、Node.jsはここでも例外です。Node.jsでは、常にCommonJsバージョンを使用し、ESMラッパーでESMに名前付きエクスポートを公開します。
最適化として再びmodule条件を使用します。
{
"type": "commonjs",
"exports": "./index.js"
}"type": "commonjs"を指定すると、CommonJsファイルを静的に検出できます。
{
"type": "module",
"exports": {
"script": "./dist-bundle.js",
"default": "./index.js"
}
}"type": "module"と.jsをdist-bundle.jsに使用しているにもかかわらず、このファイルはESM形式ではありません。スクリプトタグとして直接使用できるように、グローバル変数を使用する必要があります。
これらのパターンは、パッケージに開発用と本番用の2つのバージョンが含まれている場合に有効です。たとえば、開発バージョンには、より良いエラーメッセージや追加の警告のための追加コードを含めることができます。
{
"type": "module",
"exports": {
"development": "./index-with-devtools.js",
"default": "./index-optimized.js"
}
}development条件がサポートされている場合、開発用に拡張されたバージョンを使用します。それ以外の場合は、本番環境またはモードが不明な場合は、最適化されたバージョンを使用します。
{
"type": "module",
"exports": {
"development": "./index-with-devtools.js",
"production": "./index-optimized.js",
"node": "./wrapper-process-env.cjs",
"default": "./index-optimized.js"
}
}// wrapper-process-env.cjs
if (process.env.NODE_ENV !== 'development') {
module.exports = require('./index-optimized.cjs');
} else {
module.exports = require('./index-with-devtools.cjs');
}productionまたはdevelopment条件を使用して、本番/開発モードの静的検出を優先します。
Node.jsでは、process.env.NODE_ENVを使用してランタイムで本番/開発モードを検出できるため、Node.jsではそれをフォールバックとして使用します。ESMの同期条件付きインポートは不可能であり、パッケージを2回ロードしたくありません。そのため、ランタイム検出にはCommonJsを使用する必要があります。
モードを検出できない場合は、本番バージョンにフォールバックします。
将来の環境をサポートするために、パッケージにとって適切なフォールバック環境を選択する必要があります。一般的に、ブラウザのような環境を想定する必要があります。
{
"type": "module",
"exports": {
"node": "./index-node.js",
"worker": "./index-worker.js",
"default": "./index.js"
}
}{
"type": "module",
"exports": {
"electron": {
"node": "./index-electron-node.js",
"default": "./index-electron.js"
},
"node": "./index-node.js",
"default": "./index.js"
}
}これは、process.envのランタイム検出による本番と開発の使用のための最適化を持ち、CommonJsとESMの両方のバージョンを提供するパッケージの例です。
{
"type": "module",
"exports": {
"node": {
"development": {
"module": "./index-with-devtools.js",
"import": "./wrapper-with-devtools.js",
"require": "./index-with-devtools.cjs"
},
"production": {
"module": "./index-optimized.js",
"import": "./wrapper-optimized.js",
"require": "./index-optimized.cjs"
},
"default": "./wrapper-process-env.cjs"
},
"development": "./index-with-devtools.js",
"production": "./index-optimized.js",
"default": "./index-optimized.js"
}
}これは、Node.js、ブラウザ、Electronをサポートし、process.envのランタイム検出による本番と開発の使用のための最適化を持ち、CommonJsとESMの両方のバージョンを提供するパッケージの例です。
{
"type": "module",
"exports": {
"electron": {
"node": {
"development": {
"module": "./index-electron-node-with-devtools.js",
"import": "./wrapper-electron-node-with-devtools.js",
"require": "./index-electron-node-with-devtools.cjs"
},
"production": {
"module": "./index-electron-node-optimized.js",
"import": "./wrapper-electron-node-optimized.js",
"require": "./index-electron-node-optimized.cjs"
},
"default": "./wrapper-electron-node-process-env.cjs"
},
"development": "./index-electron-with-devtools.js",
"production": "./index-electron-optimized.js",
"default": "./index-electron-optimized.js"
},
"node": {
"development": {
"module": "./index-node-with-devtools.js",
"import": "./wrapper-node-with-devtools.js",
"require": "./index-node-with-devtools.cjs"
},
"production": {
"module": "./index-node-optimized.js",
"import": "./wrapper-node-optimized.js",
"require": "./index-node-optimized.cjs"
},
"default": "./wrapper-node-process-env.cjs"
},
"development": "./index-with-devtools.js",
"production": "./index-optimized.js",
"default": "./index-optimized.js"
}
}複雑に見えますね。私たちは、次のような仮定をすることで、複雑さを軽減することができました。NodeのみがCommonJsバージョンを必要とし、process.envで本番/開発を検出できます。
defaultエクスポートは避けてください。ツール間で異なる方法で処理されます。名前付きエクスポートのみを使用してください。.cjsまたはpackage.jsonのtype: "commonjs"を使用して、ソースコードをCommonJsとして明確にマークします。これにより、CommonJsとESMのどちらが使用されているかをツールが静的に検出できるようになります。これは、ESMのみをサポートし、CommonJsをサポートしていないツールにとって重要です。data: URL要求がサポートされています。