Node.js 支持 ECMAScript Module

过去几年,Node.js 一直致力支持于支持ECMAScript modules(ESM)。这是一项非常困难的功能,因为,Node.js的生态系统是建立在一个名为CommonJS(CJS)的不同于ESM模块之上的。这两个模块的互相操作带来了巨大的挑战,有许多新的功能要去兼顾。可是,Node.js在12或者更新的版本已经基本实现ESM。在TypeScript4.5左右,我们已经推出夜间版本在Node.js去支持ESM去为了从用户那里获取一些反馈,并且让库作者为更广泛的支持ESM做好准备。
TypeScript4.7通过在tsconfig.json中的module配置中添加了两个新的module设置来支持这个新的功能(Node.js 支持 ECMAScript Module)。这两个配置值就是:node16nodenext

  1. {
  2. "compilerOptions": {
  3. "module": "node16" | "nodenext",
  4. }
  5. }

这些新的modes将带来一些高级功能,我们将在这里进行探讨。

在package.json中的type字段和新的扩展

Node.js支持在package.json配置type字段。type可以被设置为module或者commonjs

  1. {
  2. "name": "my-package",
  3. "type": "module",
  4. "//": "...",
  5. "dependencies": {
  6. }
  7. }

这个配置用于控制.js文件是被解释成ESM还是CommonJS,如果没有配置,默认是CommonJS,如果一个文件是被认为成ESM的时候,相比于CommonJS有一些不同的规则会起作用。

  • import/expirt可以被使用
  • 顶层 await 可以被使用。
  • 相对路径的导入必须写完成的扩展(我们不得不写**import "./foo.js"**而不是**import "./foo"**
  • 导入的解析可能与node_modules中的依赖不同。比如,导入的包是ESM,但是这个包中的依赖使用的是CommonJS。
  • 类似require和module的一些global-like值不可以直接使用。
  • CommonJS模块在某些特殊规则下可以被导入。

我们来回顾其中的一些。
为了覆盖 TypeScript 在此系统中的工作方式,.ts 和 .tsx 文件现在以相同的方式工作。当 TypeScript 找到 .ts、.tsx、.js 或 .jsx 文件时,它会查找 package.json 以查看该文件是否是 ES 模块,并使用它来确定:

  • 如何找到该文件导入的其他模块
  • 以及如果产生输出如何转换该文件

如果.ts文件被编译成ESM,这个时候,import/export会保留在生成的.js文件中,当编译成CommonJS时,会生成CommonJS的的导入导出方式。

这也说明,.ts文件被编译成ESM和CJS模块采取的路径处理的。主要的原因就是上面加粗的那段文字。


tsconfig的module是ESM tsconfig的module是CJS
package.json的type是ESM 相对路径的模块导入必须要完整路径 会报错,type是ESM不支持require等一些全局变量
package.json的type是CJS 会报错,type是CJS不支持import/export等语法 可以正常的导入,不用加上完整路径。

这种方式可能一开始会觉得有点麻烦,但是TS的工具:自动导入和路径补全会为我们执行此操作。
同时,这也适用于d.ts文件。当 TypeScript 在包中找到一个 .d.ts 文件时,它会根据包含的包进行解释。

新的扩展文件

package.json 中的 type 字段很好,因为它允许我们继续使用 .ts 和 .js 文件扩展名,这很方便;但是,您有时需要编写与指定类型不同的文件。你也可能更喜欢总是明确的。
Node.js 支持两个扩展来帮助解决这个问题:.mjs 和 .cjs。 .mjs 文件始终是 ES 模块,而 .cjs 文件始终是 CommonJS 模块,没有办法覆盖它们。
反过来,TypeScript 支持两个新的源文件扩展名:.mts 和 .cts。当 TypeScript 将这些发送到 JavaScript 文件时,它会分别将它们发送到 .mjs 和 .cjs。
此外,TypeScript 还支持两个新的声明文件扩展名:.d.mts 和 .d.cts。当 TypeScript 为 .mts 和 .cts 生成声明文件时,它们对应的扩展名将是 .d.mts 和 .d.cts。
使用这些扩展是完全可选的,但即使您选择不将它们用作主要工作流程的一部分,它们通常也会很有用。

CommonJS Interoperability

Node.js 允许 ES 模块导入 CommonJS 模块,就好像它们是具有默认导出的 ES 模块一样。

  1. // ./foo.cts
  2. export function helper() {
  3. console.log("hello world!");
  4. }
  5. // ./bar.mts
  6. import foo from "./foo.cjs";
  7. // prints "hello world!"
  8. foo.helper();