Node.js 同时支持 ES 模块和 CJS 模块,但是每个文件的具体模块格式是由文件的扩展名
以及在文件所在目录及其所有上级目录中找到的第一个 package.json
文件里的 type
字段决定的:
-
.mjs
文件会被当作 ES 模块处理,.cjs
文件会被当作 CJS 模块处理。 -
对于
.js
文件:-
如果它所在的目录或其上级目录中最近的
package.json
文件里有type
字段,并且这个字段的值是"module"
,那么这个.js
文件就会被当作 ES 模块。 -
如果找不到
package.json
文件,或者package.json
里面没有type
字段,或者是其他任何值,那么.js
文件就会被当作 CJS 模块处理。
-
如果根据这些规则,Node.js 判断一个文件是 ES 模块,那么在执行这个文件时,Node.js 不会自动把 CommonJS 的 module
和 require
对象注入到文件的作用域中。所以,如果文件里尝试使用 module
或 require
,就会导致程序崩溃。
相反地,如果一个文件被判断为 CJS 模块,那么在这个文件里使用 import
和 export
语句就会引起语法错误,同样也会导致程序崩溃。
TypeScript 也使用了相同的算法
当 tsconfig 中 module
编译选项设置为 node16
或 nodenext
时,TypeScript 会使用相同的算法来判断项目输入文件的模块类型,从而确定每个对应输出文件的模块类型。
下面的表格,说明了当 tsconfig 在 module
编译选项设置为 nodenext
时,代码的模块格式(即模块类型)是如何被检测的:
输入文件 | 内容 | 输出文件 | 模块类型 | 原因 |
---|---|---|---|---|
/package.json | {} | |||
/main.mts | /main.mjs | ESM | File extension | |
/utils.cts | /utils.cjs | CJS | File extension | |
/example.ts | /example.js | CJS | No "type": "module" in package.json | |
/node_modules/pkg/package.json | { "type": "module" } | |||
/node_modules/pkg/index.d.ts | ESM | "type": "module" in package.json | ||
/node_modules/pkg/index.d.cts | CJS | File extension |
当输入文件的扩展名是 .mts
或 .cts
时,TypeScript 会将这些文件分别视为 ES 模块或 CJS 模块,因为 Node.js 会将编译后的 .mjs
文件视为 ES 模块,将 .cjs
文件视为 CJS 模块。
当输入文件的扩展名是 .ts
时,TypeScript 会查看最近的 package.json
文件来决定模块格式,因为这也是 Node.js 在遇到编译后的 .js
文件时会做的事情。如果找不到 package.json
文件,或package.json
文件的 type
不为 module
,则 .ts
会当做 CJS 模块处理。
对于依赖包中的 .d.cts
和 .d.ts
声明文件(虽然它们不会产生编译输出),规则也是一样的。存在一个 .d.ts
文件意味着有一个对应的 .js
文件——可能是当 pkg 库的作者运行 tsc 编译他们自己的 .ts
文件时生成的。Node.js 会根据 js 文件的拓展名和 /node_modules/pkg/package.json
中的 type
字段判断其模块类型。
TypeScript 会根据检测到的输入文件的模块格式(commonjs
、es6
等),确保它生成的输出文件中的语法是 Node.js 所期望的。
TypeScript 在 --module node16
和 --module nodenext
模式下, 尽量模仿 Node.js 的行为,以确保生成的代码在 Node.js 中能正确运行。由于 TypeScript 的目标是在编译时捕获潜在的运行时错误,他使用复杂的规则来检测模块类型,目的是提前发现潜在的问题。
总结
Node.js 同时支持 ES 模块和 CJS 模块,但是每个文件的具体模块格式是由文件的扩展名
以及在文件所在目录及其所有上级目录中找到的第一个 package.json
文件里的 type
字段决定的。
当 TypeScript 项目中的 tsconfig 中的 module
设置为 node16
或 nodenext
时,TypeScript 会尽量模仿 Node.js 的行为,采用相同的算法检测代码的模块格式,以确保生成的代码在 Node.js 中能正确运行。