简体   繁体   English

无法在 Nestjs 中导入 ESM 模块

[英]Unable to import ESM module in Nestjs

I am having a problem with importing ESM modules in my project based on Nest.js.我在基于 Nest.js 的项目中导入 ESM 模块时遇到问题。 As far as I understand, this problem is relevant not just to Nest.js but typescript as well.据我了解,这个问题不仅与 Nest.js 相关,还与 typescript 相关。

I have tried various things and combinations of Node.js & typescript versions, adding "type":"module" to package.json & changes in the settings of my tsconfig.json file, so it has the following view, which is far from default values:我尝试了 Node.js 和 typescript 版本的各种组合,将"type":"module"添加到package.json并更改了我的tsconfig.json文件的设置,因此它具有以下视图,与默认值相去甚远:

{
  "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,
  }
}

My full environment is:我的完整环境是:

  • Node.js (19.2.1 LTS) managed via nvm Node.js (19.2.1 LTS) 通过 nvm 管理
  • Typescript (4.9.4, but I also tried 4.3.5) Typescript(4.9.4,但我也试过4.3.5)
  • @nestjs/common: 9.2.1 @nestjs/常见:9.2.1
  • @nestjs/core: 9.2.1 @nestjs/核心:9.2.1
  • ts-loader: "9.4.2", ts-加载器:“9.4.2”,
  • ts-node: "10.9.1", ts-节点:“10.9.1”,
  • tsconfig-paths: "4.1.0", tsconfig-路径:“4.1.0”,

But it still gives me an error when I am trying to import any ESM module in any of my services.但是当我尝试在我的任何服务中导入任何 ESM 模块时,它仍然给我一个错误。 For example:例如:

import random from `random`;

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

Does anyone have a clue how to fix it?有谁知道如何解决它?

This Problem seems to occur more frequently since more packages are switching over to be distributed as ES module.这个问题似乎更频繁地发生,因为更多的包正在切换为作为 ES 模块分发。

Summary概括

  • ES Modules can import CommonJS modules ES 模块可以导入 CommonJS 模块
  • CommonJS modules cannot import ES Modules synchronously CommonJS 模块无法同步导入 ES 模块
  • NestJS does currently not support to compile to ESM - see this PR NestJS 当前不支持编译为 ESM - 请参阅此PR

There are two approaches for this problem.这个问题有两种方法。

Import package asynchronously using the dynamic import() function使用动态import()异步导入 package function

The instruction to use import() for ES modules in CommonJS can be found everywhere. CommonJS 中 ES 模块使用import()的说明随处可见。 But when using typescript the additional hint is missing how to prevent the compiler to transform the import() call to a require() .但是当使用 typescript 时,附加提示丢失了如何防止编译器将import()调用转换为require() I found two options for this:我为此找到了两个选择:

  • set moduleResolution to nodenext or node16 in your tsconfig.json (Variant 1)tsconfig.json (变体 1)中将nodenext moduleResolution node16
  • use the eval workaround - this is based on the section "Solution 2: Workaround using eval" from answer https://stackoverflow.com/a/70546326/13839825 (Variant 2 & 3)使用eval解决方法 - 这基于答案https://stackoverflow.com/a/70546326/13839825 (变体 2 和 3)中的“解决方案 2:使用 eval 的解决方法”部分

Variant 1: await import() whenever needed变体 1:在需要时await import()

This solution is also found frequently on the official NestJS Discord这个解决方案在 NestJS 官方Discord上也经常出现

  • add "moduleResolution": "nodenext" or "moduleResolution": "node16" to your tsconfig.json"moduleResolution": "nodenext""moduleResolution": "node16"添加到您的tsconfig.json
  • use an await import() call when needed需要时使用await import()调用
  • simple and straight forward简单明了
  • package is not listed on top with other imports package没有和其他进口一起列在最前面
  • you cannot import/use types from the imported ES module (at least I found no possibility so far)你不能从导入的 ES 模块中导入/使用类型(至少到目前为止我发现不可能)
  • only works in async context仅适用于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);
  }
}

Variant 2: Helper Function变体 2:助手 Function

Note : Importing a type from a ES module will not result in an require call, since it's only used by the typescript compiler and not the runtime environment.注意:从 ES 模块导入类型不会导致require调用,因为它仅由 typescript 编译器而不是运行时环境使用。

  • a bit messy because package is always hidden behind an extra function call有点乱,因为 package 总是隐藏在额外的 function 调用后面
  • only works in async context仅适用于async上下文
  • you can import/use types您可以导入/使用类型
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);
  }
}

Variant 3: Import and save to local variable变体 3:导入并保存到局部变量

  • no extra function call needed无需额外拨打 function
  • imported module can be undefined at runtime (unlikely but still bad practice)导入的模块可以在运行时未定义(不太可能但仍然是不好的做法)
  • you can import/use types您可以导入/使用类型
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);
  }
}

Convert your NestJS project to ES modules (not recommended)将 NestJS 项目转换为 ES 模块(不推荐)

Although not supported, it seems possible to setup NestJS to compile to ESM.尽管不受支持,但似乎可以将 NestJS 设置为编译为 ESM。 This Guide has good general instructions to do this in a typescript project. 本指南有很好的一般说明,可以在 typescript 项目中执行此操作。

I tested it with NestJS and found these steps sufficient:我用 NestJS 测试了它,发现这些步骤足够了:

  • add "type": "module" to your package.json添加"type": "module"到你的package.json
  • change module to NodeNext in your compilerOptions in tsconfig.jsonNodeNext的 compilerOptions 中将module更改为tsconfig.json
  • add the .js extension to all of your relative imports.js扩展名添加到所有相关导入

Now the import should work as expected现在导入应该按预期工作

import { Injectable } from '@nestjs/common';
import random from 'random';

@Injectable()
export class AppService {
  async getHello(): Promise<string> {
    return 'Hello World! ' + random.int(1, 10);
  }
}

What did not work so far were the unit tests with jest.到目前为止没有用的是开玩笑的单元测试。 I got other import errors there and I bet there are more problems down the road.我在那里遇到了其他导入错误,我敢打赌未来还会有更多问题。 I would avoid this approach and wait until NestJS officially supports ES modules.我会避免这种方法,等到 NestJS 正式支持 ES 模块。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM