?package.json
? 中的 ?exports
? 字段允許聲明在使用模塊請(qǐng)求(如?import "package"
? 或 ?import "package/sub/path"
?)時(shí)應(yīng)使用哪個(gè)模塊。它替代了 default 實(shí)現(xiàn),該實(shí)現(xiàn)返回 "package" 的 ?main
? 字段或index.js文件,以及對(duì)?"package/sub/path"
?進(jìn)行文件系統(tǒng)查找。
當(dāng)指定? exports
? 字段時(shí),只有這些模塊請(qǐng)求可用。任何其他請(qǐng)求將導(dǎo)致ModuleNotFound錯(cuò)誤。
一般情況下,?exports
? 字段應(yīng)該包含一個(gè)對(duì)象,其中每個(gè)屬性指定了模塊請(qǐng)求的子路徑。對(duì)于上面的示例,可以使用以下屬性:對(duì)于import "package",可以使用?"."
?,對(duì)于 ?import "package/sub/path"
? ,可以使用?"./sub/path"
?。以? /
? 結(jié)尾的屬性將將帶有該前綴的請(qǐng)求轉(zhuǎn)發(fā)到舊的文件系統(tǒng)查找算法。對(duì)于以
?*
? 結(jié)尾的屬性,可以取任何值,并且屬性值中的任何都將替換為取到的值。
舉例:
{
"exports": {
".": "./main.js",
"./sub/path": "./secondary.js",
"./prefix/": "./directory/",
"./prefix/deep/": "./other-directory/",
"./other-prefix/*": "./yet-another/*/*.js"
}
}
Module request | Result |
---|---|
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
|
Error |
與提供單個(gè)結(jié)果不同,包作者可以提供一系列結(jié)果。在這種情況下,將按順序嘗試此列表,并使用第一個(gè)有效結(jié)果。
注意:只使用第一個(gè)有效結(jié)果,而不是所有有效結(jié)果。
舉例:
{
"exports": {
"./things/": ["./good-things/", "./bad-things/"]
}
}
在這里,?package/things/apple
? 可能會(huì)在 ?.../package/good-things/apple
? 或者 ?.../package/bad-things/apple
? 中找到。
與直接在 ?exports
? 字段中提供結(jié)果不同,包作者可以根據(jù)環(huán)境條件讓模塊系統(tǒng)選擇一個(gè)結(jié)果。
在這種情況下,應(yīng)使用將條件映射到結(jié)果的對(duì)象。條件按照對(duì)象的順序進(jìn)行嘗試。包含無(wú)效結(jié)果的條件將被跳過(guò)。條件可以嵌套以創(chuàng)建邏輯與(AND)。對(duì)象中的最后一個(gè)條件可以是特殊的 ?"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();
可用的條件取決于所使用的模塊系統(tǒng)和工具。
當(dāng)僅支持對(duì)包中的單個(gè)條目(?"."
?)的訪問(wèn)時(shí),可以省略 ?{ ".": ... }
? 對(duì)象嵌套:
{
"exports": "./index.mjs"
}
{
"exports": {
"red": "./stop.js",
"green": "./drive.js"
}
}
在一個(gè)對(duì)象中,每個(gè)鍵都是一個(gè)條件,屬性的順序是重要的。條件按照指定的順序處理。
示例:?{ "red": "./stop.js", "green": "./drive.js" } != { "green": "./drive.js", "red": "./stop.js" }
?(當(dāng) ?red
? 和 ?green
? 條件都設(shè)置時(shí),將使用第一個(gè)屬性)
在一個(gè)對(duì)象中,每個(gè)鍵都是一個(gè)子路徑,屬性(子路徑)的順序不重要。更具體的路徑優(yōu)先于不太具體的路徑。
示例:?{ "./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
? 字段優(yōu)先于其他包入口字段,如?main
?、?module
?、?browser
?或自定義字段。
Feature | Supported by |
---|---|
"." property |
Node.js, webpack, rollup, esinstall, wmr |
normal property | Node.js, webpack, rollup, esinstall, wmr |
property ending with /
|
Node.js(1), webpack, rollup, esinstall(2), wmr(3) |
property ending with *
|
Node.js, webpack, rollup, esinstall |
Alternatives | Node.js, webpack, rollup, |
Abbreviation only path | Node.js, webpack, rollup, esinstall, wmr |
Abbreviation only conditions | Node.js, webpack, rollup, esinstall, wmr |
Conditional syntax | Node.js, webpack, rollup, esinstall, wmr |
Nested conditional syntax | Node.js, webpack, rollup, wmr(5) |
Conditions Order | Node.js, webpack, rollup, wmr(6) |
"default" condition |
Node.js, webpack, rollup, esinstall, wmr |
Path Order | Node.js, webpack, rollup |
Error when not mapped | Node.js, webpack, rollup, esinstall, wmr(7) |
Error when mixing conditions and paths | Node.js, webpack, rollup |
(1) 在Node.js中已棄用,應(yīng)優(yōu)先使用?*
?。
(2) ?"./"
?被有意忽略作為鍵。
(3) 屬性值被忽略,屬性鍵被用作目標(biāo)。實(shí)際上,只允許鍵和值相同的映射。
(4) 語(yǔ)法是支持的,但始終使用第一個(gè)條目,這使其在任何實(shí)際用例中都無(wú)法使用。
(5) 回退到替代的同級(jí)父級(jí)條件處理錯(cuò)誤。
(6) 對(duì)于?require
?條件對(duì)象,對(duì)象順序處理不正確。這是有意的,因?yàn)閣mr不區(qū)分引用語(yǔ)法。
(7) 當(dāng)使用?"exports"
?: ?"./file.js"
?縮寫時(shí),任何請(qǐng)求,例如?package/not-existing
? 將解析到該文件。當(dāng)不使用縮寫時(shí),直接訪問(wèn)文件,例如 ?package/file.js
? 將不會(huì)導(dǎo)致錯(cuò)誤。
根據(jù)用于引用模塊的語(yǔ)法之一設(shè)置以下條件:
Condition | Description | Supported by |
---|---|---|
import
|
Request is issued from ESM syntax or similar. | Node.js, webpack, rollup, esinstall(1), wmr(1) |
require
|
Request is issued from CommonJs/AMD syntax or similar. | Node.js, webpack, rollup, esinstall(1), wmr(1) |
style
|
Request is issued from a stylesheet reference. | |
sass
|
Request is issued from a sass stylesheet reference. | |
asset
|
Request is issued from a asset reference. | |
script
|
Request is issued from a normal script tag without module system. |
這些條件也可以額外設(shè)置:
Condition | Description | Supported by |
---|---|---|
module
|
All module syntax that allows to reference javascript supports ESM.
(only combined with import or require ) |
webpack, rollup, wmr |
esmodules
|
Always set by supported tools. | wmr |
types
|
Request is issued from typescript that is interested in type declarations. |
(1)?import
? 和 ?require
? 都是獨(dú)立于引用語(yǔ)法設(shè)置的。 ?require
? 始終具有較低的優(yōu)先級(jí)。
以下語(yǔ)法將設(shè)置導(dǎo)入條件:
ESM ESM 中的 import
申報(bào)
import()
? 表達(dá)式
<script type="module">
? 在 HTML 中
<link rel="preload/prefetch">
? 在 HTML 中
new Worker(..., { type: "module" })
?import
部分
import.hot.accept/decline([...])
?Worklet.addModule
?以下語(yǔ)法將設(shè)置 require
條件:
require(...)
define()
require([...])
require.resolve()
?require.ensure([...])
?require.context
?module.hot.accept/decline([...])
?<script src="...">
?以下語(yǔ)法將設(shè)置樣式條件:
@import
<link rel="stylesheet">
?The following syntax will set the asset condition:
url()
?new URL(..., import.meta.url)
?<img src="...">
?以下語(yǔ)法將設(shè)置腳本條件:
<script src="...">
?僅當(dāng)不支持模塊系統(tǒng)時(shí)才應(yīng)設(shè)置 script
。當(dāng) script
由支持 CommonJs 的系統(tǒng)預(yù)處理時(shí),它應(yīng)該改為設(shè)置 ?require
? 。
在尋找可以作為腳本標(biāo)記注入 HTML 頁(yè)面而無(wú)需額外預(yù)處理的 javascript 文件時(shí),應(yīng)使用此條件。
The following conditions are set for various optimizations:
Condition | Description | Supported by |
---|---|---|
production
|
在生產(chǎn)環(huán)境中,不應(yīng)包括開發(fā)工具 | webpack |
development
|
在開發(fā)環(huán)境中,應(yīng)該包括開發(fā)工具 | webpack |
Note:
由于
production
和development
不是每個(gè)人都支持的,所以當(dāng)這些都沒有設(shè)置時(shí),不要做任何假設(shè)。
根據(jù)目標(biāo)環(huán)境設(shè)置以下條件:
Condition | Description | Supported by |
---|---|---|
browser
|
代碼將在瀏覽器中運(yùn)行。 | webpack, esinstall, wmr |
electron | 代碼將在electron中運(yùn)行。 | webpack |
worker | 代碼將在worker中運(yùn)行。 | webpack |
worklet
|
代碼將在worklet中運(yùn)行。 | - |
node
|
代碼將在node中運(yùn)行。 | Node.js, webpack, wmr |
deno
|
代碼將在deno中運(yùn)行。 | - |
react-native | 代碼將在react-native中運(yùn)行。 | - |
(1)根據(jù)上下文,?electron
?、?worker
?和?worklet
?可以與 node
和 browser
結(jié)合使用。
(2)這是為瀏覽器目標(biāo)環(huán)境設(shè)置的。
由于每個(gè)環(huán)境都有多個(gè)版本,因此適用以下指導(dǎo)原則:
瀏覽器:在發(fā)布包時(shí)兼容當(dāng)前的規(guī)范和第4階段建議。Polyfilling職責(zé)。編譯必須在消費(fèi)者端處理。不可能填充或轉(zhuǎn)譯的特性應(yīng)該小心使用,因?yàn)樗拗屏丝赡艿氖褂谩?
deno
?: TBD
react-native
?: TBD根據(jù)對(duì)源代碼進(jìn)行預(yù)處理的工具設(shè)置以下條件。
Condition | Description | Supported by |
---|---|---|
webpack
|
由webpack處理 | webpack |
遺憾的是,Node.js運(yùn)行時(shí)沒有 ?node-js
? 條件。這將簡(jiǎn)化為Node.js創(chuàng)建異常。
以下工具支持自定義條件:
Tool | Supported | Notes |
---|---|---|
Node.js | yes | Use --conditions CLI argument. |
webpack | yes | Use resolve.conditionNames configuration option. |
rollup | yes | Use exportConditions option for @rollup/plugin-node-resolve
|
esinstall | no | |
wmr | no |
對(duì)于自定義條件,建議使用以下命名模式:
?<company-name>:<condition-name>
?
例如:?example-corp:beta
?, ?google:internal
?。
所有模式都用包中的單個(gè)?"."
?條目來(lái)解釋,但是它們也可以從多個(gè)條目擴(kuò)展,通過(guò)為每個(gè)條目重復(fù)模式。
這些模式應(yīng)該用作指南,而不是嚴(yán)格的規(guī)則集。它們可以適應(yīng)不同的軟件包。
這些模式基于以下目標(biāo)/假設(shè)列表:
default
條件。由于未來(lái)是未知的,我們假設(shè)一個(gè)類似瀏覽器的環(huán)境和類似ESM的模塊系統(tǒng)。
根據(jù)package的意圖,可能有些其他的東西是有意義的,在這種情況下應(yīng)該采用模式。示例:對(duì)于命令行工具來(lái)說(shuō),類似瀏覽器的未來(lái)和回退并沒有多大意義,在這種情況下,應(yīng)該使用類似node.js的環(huán)境和回退。
對(duì)于復(fù)雜的用例,需要通過(guò)嵌套這些條件來(lái)組合多個(gè)模式。
這些模式對(duì)于不使用特定于環(huán)境的api的包是有意義的。
{
"type": "module",
"exports": "./index.js"
}
Note:
只提供ESM對(duì)node.js來(lái)說(shuō)是有限制的。這樣的包只能在Node.js >= 14中工作,并且只能在使用 ?
import
? 時(shí)工作。它不能與 ?require()
? 一起工作。
{
"type": "module",
"exports": {
"node": {
"module": "./index.js",
"require": "./index.cjs"
},
"default": "./index.js"
}
}
大多數(shù)工具都是ESM版本。Node.js在這里是個(gè)例外。當(dāng)使用 ?require()
? 時(shí),它得到一個(gè)CommonJs版本。當(dāng)使用 ?require()
? 和 ?import
? 引用包時(shí),這將導(dǎo)致這些包的兩個(gè)實(shí)例,但這不會(huì)造成損害,因?yàn)榘鼪]有狀態(tài)。
當(dāng)使用支持ESM的 ?require()
? 工具預(yù)處理面向節(jié)點(diǎn)的代碼時(shí), module
條件被用作優(yōu)化(就像綁定器一樣,在為Node.js綁定時(shí))。對(duì)于這樣的工具,將跳過(guò)異常。這在技術(shù)上是可選的,但如果不這樣做,打包器將包含兩次包源代碼。
如果能夠在JSON文件中隔離包狀態(tài),也可以使用無(wú)狀態(tài)模式。JSON可以從CommonJs和ESM中使用,而不會(huì)用其他模塊系統(tǒng)污染圖形。
請(qǐng)注意,這里的無(wú)狀態(tài)還意味著不使用instanceof測(cè)試類實(shí)例,因?yàn)橛捎陔p模塊實(shí)例化,可能存在兩個(gè)不同的類。
{
"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;
在有狀態(tài)包中,我們必須確保包不會(huì)被實(shí)例化兩次。
這對(duì)大多數(shù)工具來(lái)說(shuō)都不是問(wèn)題,但Node.js在這里又是一個(gè)例外。對(duì)于Node.js,我們總是使用CommonJs版本,并在ESM中使用ESM包裝器公開命名的導(dǎo)出。
我們?cè)俅问褂?nbsp;module
條件作為優(yōu)化。
{
"type": "commonjs",
"exports": "./index.js"
}
提供?"type": "commonjs"
?有助于靜態(tài)檢測(cè)commonjs文件。
{
"type": "module",
"exports": {
"script": "./dist-bundle.js",
"default": "./index.js"
}
}
Note:
盡管在 ?dist-bundle.js
? 中使用了 ?"type": "module"
?和?.js
?,但這個(gè)文件不是ESM格式的。它應(yīng)該使用全局變量來(lái)允許直接使用腳本標(biāo)記。
當(dāng)一個(gè)package包含兩個(gè)版本,一個(gè)用于開發(fā),一個(gè)用于生產(chǎn)時(shí),這些模式是有意義的。例如,開發(fā)版本可以包含額外的代碼,以提供更好的錯(cuò)誤信息或額外的警告。
{
"type": "module",
"exports": {
"development": "./index-with-devtools.js",
"default": "./index-optimized.js"
}
}
當(dāng)開發(fā)條件得到支持時(shí),我們使用版本增強(qiáng)進(jìn)行開發(fā)。否則,在生產(chǎn)中或模式未知時(shí),我們使用優(yōu)化版本。
{
"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');
}
我們更喜歡通過(guò)production
或 development
條件來(lái)靜態(tài)檢測(cè)生產(chǎn)/開發(fā)模式。
Node.js允許通過(guò)process.env在運(yùn)行時(shí)檢測(cè)生產(chǎn)/開發(fā)模式。NODE_ENV,所以我們?cè)贜ode.js中使用它作為回退。同步條件導(dǎo)入ESM是不可能的,我們不想加載包兩次,所以我們必須使用CommonJs來(lái)進(jìn)行運(yùn)行時(shí)檢測(cè)。
當(dāng)無(wú)法檢測(cè)到模式時(shí),我們會(huì)退回到生產(chǎn)版本。
應(yīng)該選擇一個(gè)對(duì)包支持未來(lái)環(huán)境有意義的回退環(huán)境。通常應(yīng)該假設(shè)一個(gè)類似瀏覽器的環(huán)境。
{
"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"
}
}
這是一個(gè)package的例子,它對(duì)生產(chǎn)和開發(fā)使用進(jìn)行了優(yōu)化,并對(duì) process.env
進(jìn)行了運(yùn)行時(shí)檢測(cè),并且還發(fā)布了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"
}
}
這是一個(gè)支持Node.js、瀏覽器和電子的包的例子,它對(duì)生產(chǎn)和開發(fā)使用進(jìn)行了優(yōu)化,并對(duì) process.env
進(jìn)行了運(yùn)行時(shí)檢測(cè),并且還發(fā)布了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"
}
}
看起來(lái)很復(fù)雜,當(dāng)然,我們已經(jīng)能夠減少一些復(fù)雜性,因?yàn)槲覀兛梢宰鲆粋€(gè)假設(shè):只有 node
需要CommonJs版本,并且可以用?process.env
? 檢測(cè)生產(chǎn)/開發(fā)。
避免 default
導(dǎo)出。不同工具之間的處理方式不同。只使用命名的導(dǎo)出。
.cjs
? 或 ?type:"commonjs"
?。將源代碼清晰地標(biāo)記為CommonJs。這使得如果使用CommonJs或ESM,工具可以靜態(tài)地檢測(cè)到它。這對(duì)于只支持ESM而不支持CommonJs的工具來(lái)說(shuō)非常重要。
更多建議: