方法二:隔离状态
【Approach #2: Isolate state】
package.json 文件可以直接定义独立的 CommonJS 和 ES 模块入口点:
【A package.json file can define the separate CommonJS and ES module entry
points directly:】
// ./node_modules/pkg/package.json
{
"type": "module",
"exports": {
"import": "./index.mjs",
"require": "./index.cjs"
}
} 如果软件包的 CommonJS 版本和 ES 模块版本是等效的,例如因为其中一个是另一个的转译输出;并且软件包的状态管理被仔细隔离(或者软件包是无状态的),那么这是可行的。
【This can be done if both the CommonJS and ES module versions of the package are equivalent, for example because one is the transpiled output of the other; and the package's management of state is carefully isolated (or the package is stateless).】
之所以状态会成为问题,是因为一个应用可能同时使用该包的 CommonJS 和 ES 模块版本;例如,用户的应用代码可能会 import ES 模块版本,而某个依赖可能会 require CommonJS 版本。如果发生这种情况,该包将会被加载两次到内存中,因此会存在两个独立的状态。这很可能会导致难以排查的错误。
【The reason that state is an issue is because both the CommonJS and ES module
versions of the package might get used within an application; for example, the
user's application code could import the ES module version while a dependency
requires the CommonJS version. If that were to occur, two copies of the
package would be loaded in memory and therefore two separate states would be
present. This would likely cause hard-to-troubleshoot bugs.】
除了编写无状态的包(例如,如果 JavaScript 的 Math 是一个包,它将是无状态的,因为它的所有方法都是静态的),还有一些方法可以隔离状态,以便在包的可能加载的 CommonJS 和 ES 模块实例之间共享状态:
【Aside from writing a stateless package (if JavaScript's Math were a package,
for example, it would be stateless as all of its methods are static), there are
some ways to isolate state so that it's shared between the potentially loaded
CommonJS and ES module instances of the package:】
-
如果可能的话,将所有状态包含在实例化对象中。例如,JavaScript 的
Date需要被实例化才能包含状态;如果它是一个包,它的使用方式如下:import Date from 'date'; const someDate = new Date(); // someDate contains state; Date does notnew关键字不是必须的;一个包的函数可以返回一个新对象,或者修改传入的对象,以保持状态在包外部。 -
将状态隔离到一个或多个在 CommonJS 和 ES 模块版本的包之间共享的 CommonJS 文件中。例如,如果 CommonJS 和 ES 模块的入口点分别是
index.cjs和index.mjs:// ./node_modules/pkg/index.cjs const state = require('./state.cjs'); module.exports.state = state;// ./node_modules/pkg/index.mjs import state from './state.cjs'; export { state };即使在应用中既通过
require又通过import使用pkg(例如,在应用代码中通过import,而在依赖中通过require),每个对pkg的引用都将包含相同的状态;并且从任一模块系统修改该状态都会对两者生效。
任何附加到该包单例的插件都需要分别附加到 CommonJS 和 ES 模块单例上。
【Any plugins that attach to the package's singleton would need to separately attach to both the CommonJS and ES module singletons.】
此方法适用于以下任何用例:
【This approach is appropriate for any of the following use cases:】
- 该软件包目前是用 ES 模块语法编写的,软件包作者希望在支持这种语法的地方使用该版本。
- 该软件包是无状态的,或者其状态可以在不太困难的情况下被隔离。
- 该包不太可能有其他依赖于它的公共包,或者即使有,该包也是无状态的,或者其状态不需要在依赖之间或与整个应用共享。
即使在隔离状态下,仍然存在在包的 CommonJS 版本和 ES 模块版本之间可能额外执行代码的成本。
【Even with isolated state, there is still the cost of possible extra code execution between the CommonJS and ES module versions of a package.】
与之前的方法一样,这种方法的一个变体是不需要为使用者提供条件导出的,可以添加一个导出,例如 "./module",指向该包的全 ES 模块语法版本:
【As with the previous approach, a variant of this approach not requiring
conditional exports for consumers could be to add an export, e.g.
"./module", to point to an all-ES module-syntax version of the package:】
// ./node_modules/pkg/package.json
{
"type": "module",
"exports": {
".": "./index.cjs",
"./module": "./index.mjs"
}
}