javascriptnode.jstypescriptnestjses6-modules

Unable to import ESM module in Nestjs


I am having a problem with importing ESM modules in my project based on Nest.js. As far as I understand, this problem is relevant not just to Nest.js but typescript as well.

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:

{
  "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:

But it still gives me an error when I am trying to import any ESM module in any of my services. 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?


Solution

  • This Problem seems to occur more frequently since more packages are switching over to be distributed as ES module.

    Summary

    There are two approaches for this problem.

    Import package asynchronously using the dynamic import() function

    The instruction to use import() for ES modules in CommonJS can be found everywhere. But when using typescript the additional hint is missing how to prevent the compiler to transform the import() call to a require(). I found two options for this:

    Variant 1: await import() whenever needed

    This solution is often suggested on the official NestJS Discord

    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

    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.

    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

    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)

    Although not supported, it seems possible to setup NestJS to compile to ESM. This Guide has good instructions how to do this for any typescript project.

    I tested it with NestJS and found these steps sufficient:

    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.