typescript 无法在Nestjs中导入ESM模块

wh6knrhe  于 2023-01-31  发布在  TypeScript
关注(0)|答案(1)|浏览(423)

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

我的完整环境是:

  • 通过nvm管理的Node.js(19.2.1 LTS)
  • typescript (4.9.4,但我也尝试了4.3.5)
  • @nestjs/常见:9.2.1
  • @嵌套/内核:9.2.1
  • ts-加载程序:“9.4.2”,
  • TS节点:“10.9.1”,
  • tsconfig路径:“4.1.0”,

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

import random from `random`;

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

有人知道怎么修吗?

k10s72fa

k10s72fa1#

由于更多程序包切换为作为ES模块分发,因此该问题似乎更频繁地发生。

    • 摘要**
  • ES模块可以导入CommonJS模块
  • CommonJS模块不能同步导入ES模块
  • NestJS当前不支持编译为ESM-请参阅此PR

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

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

在CommonJS中,为ES模块使用import()的指令随处可见。但是在使用typescript时,缺少了如何防止编译器将import()调用转换为require()的额外提示。我找到了两个选项:

  • tsconfig.json中将moduleResolution设置为nodenextnode16(变体1)
  • 使用eval解决方案-这基于"解决方案2:使用答案https://stackoverflow.com/a/70546326/13839825(变量2和3)中的eval "的解决方案

变体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调用,因为它只被类型脚本编译器使用,而不是运行时环境。
  • 有点混乱,因为包总是隐藏在额外的函数调用后面
  • 仅在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。This Guide有很好的说明,如何为任何类型脚本项目执行此操作。
我用NestJS测试了它,发现这些步骤足够了:

  • "type": "module"添加到您的package.json
  • 在编译器中将module更改为NodeNexttsconfig.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);
  }
}

到目前为止,用jest进行的单元测试没有起作用。我在那里得到了其他导入错误,我敢打赌以后还会有更多的问题。我会避免这种方法,直到NestJS正式支持ES模块。

相关问题