ES 模块脚本的 CommonJS 包装器

CommonJS wrapper for ES module scripts

Node documentation on ECMAScript Modules 为 "dual packages" 提出建议,向用户提供 CommonJS 脚本和 ES 模块脚本。

具体来说,他们建议将 ES 模块脚本转译为 CommonJS,然后使用 "ES module wrapper",一个从 CJS 导入的 ES 模块脚本,并将其名为 exports 的 CJS 重新导出为名为 exports 的 ESM。

// ./node_modules/pkg/wrapper.mjs
import cjsModule from './index.cjs';
export const name = cjsModule.name;

我的问题是:是否可以以相反的方式执行此操作,以 ES 模块格式提供我的脚本,但在 CommonJS 中添加一个简单的包装文件以允许 CJS 用户 require 它?

就我而言,我看不到在同步代码中执行此操作的方法。在我看来,CommonJS 脚本只能通过异步动态导入 ES 模块 import().

(async () => {
    const {foo} = await import('./foo.mjs');
})();

但这意味着我的 CJS 脚本无法导出依赖于 foo 的任何内容。

There is no way to synchronously import ESM modules. CJS 导出是同步的,但 ESM 导入本质上是异步的。

In CommonJS, require() is synchronous; it doesn't return a promise or call a callback. require() reads from the disk (or perhaps even from the network), and then immediately runs the script, which may itself do I/O or other side effects, and then returns whatever values were set on module.exports.

In ESM, the module loader runs in asynchronous phases. In the first phase, it parses the script to detect calls to import and export without running the imported script. In the parsing phase, the ESM loader can immediately detect a typo in named imports and throw an exception without ever actually running the dependency code.

The ESM module loader then asynchronously downloads and parses any scripts that you imported, and then scripts that your scripts imported, building out a “module graph” of dependencies, until eventually it finds a script that doesn’t import anything. Finally, that script is allowed to execute, and then scripts that depend on that are allowed to run, and so on.

甚至不一定在所有情况下都可以转译 ESM 到 CJS。例如,ESM 可以使用 top-level await,而 CJS 模块不能。因此,无法将此 ESM 代码转换为 CJS:

export const foo = await fetch('./data.json');

CJS和ESM是完全不同的动物。