方法 #1:使用 ES 模块封装器
【Approach #1: Use an ES module wrapper】
将包写成 CommonJS,或将 ES 模块源代码转译为 CommonJS,并创建一个定义命名导出的 ES 模块封装文件。使用 条件导出 时,ES 模块封装文件用于 import,CommonJS 入口点用于 require。
【Write the package in CommonJS or transpile ES module sources into CommonJS, and
create an ES module wrapper file that defines the named exports. Using
Conditional exports, the ES module wrapper is used for import and the
CommonJS entry point for require.】
// ./node_modules/pkg/package.json
{
"type": "module",
"exports": {
"import": "./wrapper.mjs",
"require": "./index.cjs"
}
} 前面的示例使用了显式扩展名 .mjs 和 .cjs。如果你的文件使用 .js 扩展名,"type": "module" 将导致这些文件被视为 ES 模块,就像 "type": "commonjs" 会导致它们被视为 CommonJS 模块一样。请参阅 启用。
【The preceding example uses explicit extensions .mjs and .cjs.
If your files use the .js extension, "type": "module" will cause such files
to be treated as ES modules, just as "type": "commonjs" would cause them
to be treated as CommonJS.
See Enabling.】
// ./node_modules/pkg/index.cjs
exports.name = 'value'; // ./node_modules/pkg/wrapper.mjs
import cjsModule from './index.cjs';
export const name = cjsModule.name; 在这个例子中,来自 import { name } from 'pkg' 的 name 与来自 const { name } = require('pkg') 的 name 是同一个单例。因此,在比较两个 name 时,=== 返回 true,并且避免了不同的导入说明符风险。
【In this example, the name from import { name } from 'pkg' is the same
singleton as the name from const { name } = require('pkg'). Therefore ===
returns true when comparing the two names and the divergent specifier hazard
is avoided.】
如果模块不仅仅是一个命名导出列表,而是包含一个独特的函数或对象导出,比如 module.exports = function () { ... },或者如果希望封装器支持 import pkg from 'pkg' 这种模式,那么封装器将会被改写为可选择性地导出默认导出,同时也可以导出任何命名导出:
【If the module is not simply a list of named exports, but rather contains a
unique function or object export like module.exports = function () { ... },
or if support in the wrapper for the import pkg from 'pkg' pattern is desired,
then the wrapper would instead be written to export the default optionally
along with any named exports as well:】
import cjsModule from './index.cjs';
export const name = cjsModule.name;
export default cjsModule; 此方法适用于以下任何用例:
【This approach is appropriate for any of the following use cases:】
- 该包目前是用 CommonJS 编写的,作者不希望将其重构为 ES 模块语法,但希望为 ES 模块使用者提供命名导出。
- 该软件包有其他依赖于它的软件包,终端用户可能会同时安装此软件包和那些其他软件包。例如,一个
utilities软件包直接在应用中使用,而utilities-plus软件包则为utilities添加了更多功能。由于封装器导出了底层的 CommonJS 文件,所以无论utilities-plus是使用 CommonJS 语法还是 ES 模块语法编写的,都可以正常工作。 - 该包存储内部状态,并且包的作者不希望重构该包以隔离其状态管理。请参见下一节。
这种方法的一种变体是不需要为使用者设置条件导出,可以添加一个导出,例如 "./module",指向该包的全 ES 模块语法版本。对于那些确定应用中不会加载 CommonJS 版本的用户(例如通过依赖),或即使加载了 CommonJS 版本也不会影响 ES 模块版本的情况(例如因为该包是无状态的),可以通过 import 'pkg/module' 使用该导出:
【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. This could be used via import 'pkg/module' by users
who are certain that the CommonJS version will not be loaded anywhere in the
application, such as by dependencies; or if the CommonJS version can be loaded
but doesn't affect the ES module version (for example, because the package is
stateless):】
// ./node_modules/pkg/package.json
{
"type": "module",
"exports": {
".": "./index.cjs",
"./module": "./wrapper.mjs"
}
}