node、TS下CJS模块引入ESM注意事项
使用ESM的条件
-
模块化检测
- .mts、.mjs、.d.mts始终表示ESM
- .cts、.cjs、.d.cts始终表示CJS
- 最近的
package.json内type: module则所有的.ts、.js、.d.ts都表示ESM,反之为CJS
-
ESM导入CJS时
- ESM用默认导入可操作CJS的"module.exports" 👍
- ESM用命名导入CJS时,TS类型推断默认会成功,但可能运行时失败,所以仍然推荐默认导入
-
CJS导入ESM时
- CJS无法通过
import语句导入ESM,因为TS编译后import -> require() - CJS只能通过动态导入:import(),去动态异步加载ESM!⚠️
- CJS无法通过
-
将
"type": "module"添加到您的package.json中。 -
(可选)将package.json中的
"main": "index.js"替换为"exports": "./index.js"。 -
将package.json中的"engines"字段更新为"node": ">=16" 。
-
移除所有"use strict"声明。
-
全部使用完整的导入路径。
import x from '.'; // 替换为如下 import x from './index.js'; // 即使你要导入的是ts文件,这里也必须写.js,编译代码不会改变后缀 -
使用node包则使用"node:*"导入。
import fs from "node:fs"; -
tsconfig.json设置。
{ "module": "node16", "moduleResolution": "node16" } -
如果TS类型声明文件有
namespace的使用替换为export导出。
详细参考地址: Pure ESM package
默认TS下node的模块化规则
⚠️ 综上所述,TS默认情况下是CJS模块的!!!
强烈推荐
- 强烈推荐模块化使用ESM,因为ESM可以直接导入CJS/ESM。而CJS只能通过动态导入import()去导入ESM模块
😡 nodejs双模块真该死啊!没办法毕竟ESM才是官方主流啊~ 😡
坑:当你使用动态导入import()报错require() of ES Module
- 报错
Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/xiaoqinvar/Desktop/project/xqv-solution/node_modules/got/dist/source/index.js not supported.
Instead change the require of index.js in null to a dynamic import() which is available in all CommonJS modules.
-
发现问题流程:
-
最直接的想法:因为网上有太多讨论TS CJS使用动态导入import(),因为TS编译成require()的问题,所以第一步想到的是TS编译的产物。
-
我的TS是v 5.2.2(2023-11-11,当时可以说是最新版本了),在TS Playground测试发现TS的编译结果没有问题
// 代码 import foo from "./foo"; console.log(foo); const foo = await import("./foo")// 编译产物 console.log(foo); const foo = await import("./foo"); export {}; -
也就是不是TS问题,接下来我查看了build构建的产物(因为我项目使用的是nx,内置的使用的是webpack构建)
// 原始代码 async function gotLoader() { /*webpackIgnore: true*/ const { got } = await import("got"); return got; }// build产物 async function gotLoader() { /*webpackIgnore: true*/ const { got } = await Promise.resolve(/* import() */).then(__webpack_require__.t.bind(__webpack_require__, 52, 23)); return got; } -
很显然这里肯定是webpack修改了TS构建产物之后的最终产物,而且
__webpack_require__这个变量想都不用想与require()肯定是一致的东西
-
-
解决起来相对容易,忽略编译即可
async function gotLoader() {
const { got } = await import(/*webpackIgnore: true*/ "got"); // 忽略对import()动态导入的编译转换
return got;
}