无法在Nestjs中导入ESM模块

问题描述 投票:0回答:2

我在基于 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,
  }
}

我的完整环境是:

  • Node.js (19.2.1 LTS) 通过 nvm 管理
  • Typescript(4.9.4,但我也尝试过4.3.5)
  • @nestjs/common:9.2.1
  • @nestjs/核心:9.2.1
  • ts-loader:“9.4.2”,
  • ts 节点:“10.9.1”,
  • tsconfig-paths:“4.1.0”,

但是当我尝试在任何服务中导入任何 ESM 模块时,它仍然会给我一个错误。例如:

import random from `random`;

export class AppService implements OnApplicationBootstrap {
  async test() {
     const r = random.int(1, 5);
     console.log(r);
  }
}

有人知道如何修复它吗?

javascript node.js typescript nestjs es6-modules
2个回答
42
投票

这个问题似乎更频繁地发生,因为更多的包正在切换为以 ES 模块的形式分发。

总结

  • ES模块可以导入CommonJS模块
  • CommonJS 模块无法同步导入 ES 模块
  • NestJS 目前不支持编译为 ESM - 请参阅此 PR

解决这个问题有两种方法。

使用动态
import()
函数异步导入包

CommonJS 中 ES 模块使用

import()
的指令随处可见。但是,当使用打字稿时,缺少额外的提示,如何防止编译器将
import()
调用转换为
require()
。我找到了两个选择:

  • moduleResolution
    设置为
    nodenext
    node16
    中的
    tsconfig.json
    (变体 1)
  • 使用
    eval
    解决方法 - 这是基于答案中的“解决方案 2:使用 eval 的解决方法”部分https://stackoverflow.com/a/70546326/13839825(变体 2 和 3)

变体 1:
await import()
每当需要时

官方经常建议使用此解决方案 NestJS Discord

  • "moduleResolution": "nodenext"
    "moduleResolution": "node16"
    添加到您的
    tsconfig.json
  • 需要时使用
    await import()
    电话
  • 简单直接
  • 软件包未与其他导入一起列在顶部
  • 你不能从导入的 ES 模块中导入/使用类型(至少到目前为止我发现没有可能性)
  • 仅适用于
    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);
  }
}

变体 2:辅助函数

注意:从 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);
  }
}

变体 3:导入并保存到局部变量

  • 无需额外的函数调用
  • 导入的模块可能在运行时未定义(不太可能,但仍然是不好的做法)
  • 您可以导入/使用类型
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 项目转换为 ES 模块(不推荐)

虽然不支持,但似乎可以设置 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 模块。


0
投票

使用 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"
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.