Webpack Package exports

2023-06-03 14:21 更新

?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ò)誤。

General syntax

一般情況下,?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

Alternatives

與提供單個(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? 中找到。

條件語(yǔ)法 

與直接在 ?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)和工具。

Abbreviation

當(dāng)僅支持對(duì)包中的單個(gè)條目(?"."?)的訪問(wèn)時(shí),可以省略 ?{ ".": ... }? 對(duì)象嵌套:

{
  "exports": "./index.mjs"
}
{
  "exports": {
    "red": "./stop.js",
    "green": "./drive.js"
  }
}

關(guān)于排序的注意事項(xiàng)

在一個(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?或自定義字段。

Support

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, esinstall(4)
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ò)誤。

條件

引用語(yǔ)法

根據(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í)。

import

以下語(yǔ)法將設(shè)置導(dǎo)入條件:

  • ESM ESM 中的 import 申報(bào)

  • JS ?import()? 表達(dá)式
  • HTML ?<script type="module">? 在 HTML 中
  • HTML ?<link rel="preload/prefetch">? 在 HTML 中
  • JS ?new Worker(..., { type: "module" }) ?
  • WASM import 部分
  • ESM HMR (webpack) ?import.hot.accept/decline([...]) ?
  • JS ?Worklet.addModule ?
  • 使用 javascript 作為入口點(diǎn)

require

以下語(yǔ)法將設(shè)置 require 條件:

  • CommonJs require(...)
  • AMD define()
  • AMD require([...])
  • CommonJs ?require.resolve() ?
  • CommonJs (webpack) ?require.ensure([...])?
  • CommonJs (webpack) ?require.context ?
  • CommonJs HMR (webpack) ?module.hot.accept/decline([...]) ?
  • HTML ?<script src="...">?

style

以下語(yǔ)法將設(shè)置樣式條件:

  • CSS @import
  • HTML ?<link rel="stylesheet">?

asset

The following syntax will set the asset condition:

  • CSS ?url()?
  • ESM ?new URL(..., import.meta.url)?
  • HTML ?<img src="...">?

script

以下語(yǔ)法將設(shè)置腳本條件:

  • HTML ?<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)使用此條件。

Optimizations

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è)。

Target environment

根據(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)原則:

  • node:請(qǐng)參閱引擎字段以了解兼容性。

瀏覽器:在發(fā)布包時(shí)兼容當(dāng)前的規(guī)范和第4階段建議。Polyfilling職責(zé)。編譯必須在消費(fèi)者端處理。不可能填充或轉(zhuǎn)譯的特性應(yīng)該小心使用,因?yàn)樗拗屏丝赡艿氖褂谩?

  • ?deno?: TBD
  • ?react-native?: TBD

Conditions: Preprocessor and runtimes

根據(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)建異常。

Conditions: Custom

以下工具支持自定義條件:

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?。

Common patterns

所有模式都用包中的單個(gè)?"."?條目來(lái)解釋,但是它們也可以從多個(gè)條目擴(kuò)展,通過(guò)為每個(gè)條目重復(fù)模式。

這些模式應(yīng)該用作指南,而不是嚴(yán)格的規(guī)則集。它們可以適應(yīng)不同的軟件包。

這些模式基于以下目標(biāo)/假設(shè)列表:

  • 我們假設(shè)在某些時(shí)候不再維護(hù)包,但是它們繼續(xù)被使用。應(yīng)該編寫導(dǎo)出,以便為未來(lái)未知的情況使用回退。可以使用 default 條件。由于未來(lái)是未知的,我們假設(shè)一個(gè)類似瀏覽器的環(huán)境和類似ESM的模塊系統(tǒng)。
  • 并非所有工具都支持所有條件。應(yīng)該使用回退來(lái)處理這些情況。我們假設(shè)下面的回退通常是有意義的:
  1. ESM > CommonJs
  2. Production > Development
  3. Browser > node.js

根據(jù)package的意圖,可能有些其他的東西是有意義的,在這種情況下應(yīng)該采用模式。示例:對(duì)于命令行工具來(lái)說(shuō),類似瀏覽器的未來(lái)和回退并沒有多大意義,在這種情況下,應(yīng)該使用類似node.js的環(huán)境和回退。

對(duì)于復(fù)雜的用例,需要通過(guò)嵌套這些條件來(lái)組合多個(gè)模式。

Target environment independent packages

這些模式對(duì)于不使用特定于環(huán)境的api的包是有意義的。

Providing only an ESM version

{
  "type": "module",
  "exports": "./index.js"
}

Note: 

只提供ESM對(duì)node.js來(lái)說(shuō)是有限制的。這樣的包只能在Node.js >= 14中工作,并且只能在使用 ?import? 時(shí)工作。它不能與 ?require()? 一起工作。

Providing CommonJs and ESM version (stateless)

{
  "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è)不同的類。

Providing CommonJs and ESM version (stateful)

{
  "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)化。

Providing only a CommonJs version

{
  "type": "commonjs",
  "exports": "./index.js"
}

提供?"type": "commonjs"?有助于靜態(tài)檢測(cè)commonjs文件。

Providing a bundled script version for direct browser consumption

{
  "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)記。

Providing devtools or production optimizations

當(dāng)一個(gè)package包含兩個(gè)版本,一個(gè)用于開發(fā),一個(gè)用于生產(chǎn)時(shí),這些模式是有意義的。例如,開發(fā)版本可以包含額外的代碼,以提供更好的錯(cuò)誤信息或額外的警告。

Without Node.js runtime detection

{
  "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)化版本。

With Node.js runtime detection

{
  "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)版本。

Providing different versions depending on target environment

應(yīng)該選擇一個(gè)對(duì)包支持未來(lái)環(huán)境有意義的回退環(huán)境。通常應(yīng)該假設(shè)一個(gè)類似瀏覽器的環(huán)境。

Providing Node.js, WebWorker and browser versions

{
  "type": "module",
  "exports": {
    "node": "./index-node.js",
    "worker": "./index-worker.js",
    "default": "./index.js"
  }
}

Providing Node.js, browser and electron versions

{
  "type": "module",
  "exports": {
    "electron": {
      "node": "./index-electron-node.js",
      "default": "./index-electron.js"
    },
    "node": "./index-node.js",
    "default": "./index.js"
  }
}

Combining patterns

Example 1

這是一個(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"
  }
}

Example 2

這是一個(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ā)。

Guidelines

  • 避免 default 導(dǎo)出。不同工具之間的處理方式不同。只使用命名的導(dǎo)出。

  • 永遠(yuǎn)不要為不同的條件提供不同的api或語(yǔ)義。
  • 將源代碼寫成ESM,然后通過(guò)babel、typescript或類似的工具編譯成CJS。
  • 在package中使用 ?.cjs? 或 ?type:"commonjs"?。將源代碼清晰地標(biāo)記為CommonJs。這使得如果使用CommonJs或ESM,工具可以靜態(tài)地檢測(cè)到它。這對(duì)于只支持ESM而不支持CommonJs的工具來(lái)說(shuō)非常重要。
  • 包中使用的ESM支持以下類型的請(qǐng)求:支持模塊請(qǐng)求,指向其他包,使用package.json.relative請(qǐng)求,指向包中的其他文件。它們不能指向包外的文件。數(shù)據(jù):支持url請(qǐng)求。默認(rèn)情況下不支持其他絕對(duì)請(qǐng)求或服務(wù)器相對(duì)請(qǐng)求,但某些工具或環(huán)境可能支持它們。

Further Reading


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)