在 Node.js 中使用 require(esm):实现者的故事
In earlier posts, I wrote about reviving require(esm) and its iteration process. The idea seems straightforward once you grasp the ESM semantics, but battle‑testing revealed interop edge cases rooted in existing ecosystem workarounds. Along the way, several escape hatches were devised to address them.
在之前的文章中,我写过关于 复兴 require(esm) 和 它的迭代过程。一旦你掌握了 ESM 语义,这个想法似乎很简单,但实战测试揭示了根植于现有生态系统变通方法的互操作边缘案例。在这个过程中,设计了几个逃生通道来解决这些问题。
As mentioned in the previous post, packages that bundle for browsers often ship transpiled CommonJS on Node.js (also known as “faux ESM”). A typical pattern looks like this:
正如在 上一篇文章 中提到的,针对浏览器打包的包通常在 Node.js 上发布转译的 CommonJS(也称为“伪 ESM”)。一个典型的模式如下:
1 |
|
One might expect that with require(esm), a faux‑ESM package could now simply point main to src/index.js. But here the semantic mismatch between ESM and CommonJS gets in the way. Consider:
人们可能会期望使用 require(esm),一个伪 ESM 包现在可以简单地将 main 指向 src/index.js。但在这里,ESM 和 CommonJS 之间的语义不匹配妨碍了这一点。考虑:
1 |
|
Per the spec, when dynamically import()‑ed, the default export appears under the default property of the returned namespace object:
根据规范,当动态 import() 时,默认导出出现在返回的命名空间对象的 default 属性下:
1 | console.log(await import('faux-esm-package')); |
This differs from CommonJS, where module.exports is returned directly by require(), so tools that transpile ESM into CommonJS needed a way to multiplex ESM exports in CommonJS. Over the years, a de facto convention emerged from Babel and was adopted widely by transpilers and bundlers: add a __esModule sentinel in transpiled exports to signal “was ESM”. The above ESM would transpile to:
这与CommonJS不同,在CommonJS中,module.exports是由require()直接返回的,因此将ESM转译为CommonJS的工具需要一种在CommonJS中复用ESM导出的方法。多年来,从Babel中出现了一种事实上的约定,并被转译器和打包工具广泛采用:在转译的导出中添加__esModule哨兵以表示“曾是ESM”。上述ESM将转译为:
1 |
|
Wh...