我在基于 Nest.js 的项目中导入 ESM 模块时遇到问题。据我了解,这个问题不仅与 Nest.js 有关,还与 Typescript 相关。
我尝试了 Node.js 和 typescript 版本的各种事物和组合,将
"type":"module"
添加到 package.json
并更改我的 tsconfig.json
文件的设置,因此它具有以下视图,与 default 相去甚远价值观:
{
"compilerOptions": {
"lib": ["ES2020"],
"esModuleInterop": true,
"module": "NodeNext",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "Node",
"target": "esnext",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false,
}
}
我的完整环境是:
但是当我尝试在任何服务中导入任何 ESM 模块时,它仍然会给我一个错误。例如:
import random from `random`;
export class AppService implements OnApplicationBootstrap {
async test() {
const r = random.int(1, 5);
console.log(r);
}
}
有人知道如何修复它吗?
这个问题似乎更频繁地发生,因为更多的包正在切换为以 ES 模块的形式分发。
总结
解决这个问题有两种方法。
import()
函数异步导入包CommonJS 中 ES 模块使用
import()
的指令随处可见。但是,当使用打字稿时,缺少额外的提示,如何防止编译器将 import()
调用转换为 require()
。我找到了两个选择:
moduleResolution
设置为 nodenext
或 node16
中的 tsconfig.json
(变体 1)eval
解决方法 - 这是基于答案中的“解决方案 2:使用 eval 的解决方法”部分https://stackoverflow.com/a/70546326/13839825(变体 2 和 3)await import()
每当需要时官方经常建议使用此解决方案 NestJS Discord
"moduleResolution": "nodenext"
或 "moduleResolution": "node16"
添加到您的 tsconfig.json
await import()
电话async
上下文import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
async getHello(): Promise<string> {
const random = (await import('random')).default;
return 'Hello World! ' + random.int(1, 10);
}
}
注意:从 ES 模块导入类型不会导致
require
调用,因为它仅由 TypeScript 编译器使用,而不是运行时环境。
async
上下文import { Injectable } from '@nestjs/common';
import { type Random } from 'random';
async function getRandom(): Promise<Random> {
const module = await (eval(`import('random')`) as Promise<any>);
return module.default;
}
@Injectable()
export class AppService {
async getHello(): Promise<string> {
return 'Hello World! ' + (await getRandom()).int(1, 10);
}
}
import { Injectable } from '@nestjs/common';
import { type Random } from 'random';
let random: Random;
eval(`import('random')`).then((module) => {
random = module.default;
});
@Injectable()
export class AppService {
async getHello(): Promise<string> {
return 'Hello World! ' + random.int(1, 10);
}
}
虽然不支持,但似乎可以设置 NestJS 来编译为 ESM。 本指南很好地说明了如何为任何打字稿项目执行此操作。
我用 NestJS 进行了测试,发现这些步骤足够了:
"type": "module"
添加到您的 package.json
module
中的编译器选项中的
NodeNext
更改为
tsconfig.json
.js
扩展添加到所有相关导入中现在导入应该按预期进行。
import { Injectable } from '@nestjs/common';
import random from 'random';
@Injectable()
export class AppService {
async getHello(): Promise<string> {
return 'Hello World! ' + random.int(1, 10);
}
}
到目前为止,没有起作用的是用笑话进行的单元测试。我在那里遇到了其他导入错误,我敢打赌以后还会出现更多问题。 我会避免这种方法并等待 NestJS 正式支持 ES 模块。
使用 Node v22,您可以使用
--experimental-require-module
标志
package.json
看起来与此类似:
{
"scripts": {
"start": "nest start -e 'node --experimental-require-module'",
"start:dev": "nest start --watch -e 'node --experimental-require-module'",
"start:prod": "node --experimental-require-module dist/main"
}
}