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)。这两个配置值就是:node16
和nodenext
。
{
"compilerOptions": {
"module": "node16" | "nodenext",
}
}
这些新的modes将带来一些高级功能,我们将在这里进行探讨。
在package.json中的type字段和新的扩展
Node.js支持在package.json配置type
字段。type
可以被设置为module
或者commonjs
。
{
"name": "my-package",
"type": "module",
"//": "...",
"dependencies": {
}
}
这个配置用于控制.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 模块一样。
// ./foo.cts
export function helper() {
console.log("hello world!");
}
// ./bar.mts
import foo from "./foo.cjs";
// prints "hello world!"
foo.helper();