Unable to import ESM .ts module in node

Viewed 11827

I have been trying to import an ESM module written in typescript in nodejs. But I am getting the following error:

An import path cannot end with a '.ts' extension.

Util.ts

 export class Util {
    constructor ( ) {
       
    }
      log(msg) {
        console.log(msg) 
    }
  }

index.ts

import {log} from './Util.ts'
log(task.query.criteria, payload.parameters)

I have also added "type":"module" inside package.json

I changes .ts to .js just to see if it works and then I got :

Object.defineProperty(exports, "__esModule", { value: true });                         ^

ReferenceError: exports is not defined
at file:///C:/Users/abc/NestJsPOC/NestPOC/dist/main.js:2:23

tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true
  }
}

EDIT

I have also tried:

 var log = require('../utility/util.js');

Util.js

    function log(msg) {
      console.log(msg)
     
  }
    module.exports= { log}

index.ts

    log('hello')

Error:

TypeError: log is not a function
1 Answers

Update fron 14.09.2022

I've created a repository with the necessary settings to run esm directly via ts-node or using tsc and running the transpiled esm file with NodeJS. https://github.com/felipeplets/esm-examples

Keep in mind that you need to use ts-node 10+ and NodeJS 12+ in order to use the setup below.

The updated settings to make it by the time I'm answering your question (updated on 29.05.2022) is:

Original answer

It seems you are looking to use ESM with Node and TS.

tsconfig.json (tested with TypeScript 4.7 and 4.8)

On your tsconfig.json file make sure to have the following settings:

{
    "compilerOptions": {
        "lib": ["ES2022"], // ES2020 in earlier versions
        "module": "ES2022", //ESNext 
        "moduleResolution": "Node",
        "target": "ES2022", // ES2020 in earlier versions
        "esModuleInterop": true,
        ...
    },
    ... 
}

package.json

Make sure to have the following attribute on your package.json file.

{ 
    ...
    "type":"module",
    ...
}

Running it with transpiled files

You can't import .ts files direct in NodeJS, you need to first transpile it using tsc to then import it in the NodeJS in runtime, so in the end you will be importing the .js and not the .ts files. (To run it as .ts please make sure to read the next section of my answer. Running it with ts-node)

When running it you need to use the flag --experimental-specifier-resolution=node as it will enable you to have imports with no extensions as it happens in TypeScript:

node --experimental-specifier-resolution=node index

Note that the flag order matters — you must put it before the file path.

You can find more details about the flag on https://nodejs.org/api/esm.html#esm_customizing_esm_specifier_resolution_algorithm

Running it with ts-node

ts-node is a runtime transpiler so it allow you to import typescript files in runtime when using node.

There is a feature in ts-node that allows you to run it using ESM, to use it you will need to install TS Node 9.1+. For more details on the implementation and possible issues check: https://github.com/TypeStrong/ts-node/issues/1007

To install it you will need to run:

npm i -D ts-node 

And to run the service supporting the new resolution mode and the TSNode loader you will need to run:

ts-node 10.8+

You can add esm and experimentalSpecifierResolution in your tsconfig file.

{
    ...
    "ts-node": {
        "esm": true,
        "experimentalSpecifierResolution": "node"
    }
}

Now you can simply run:

ts-node index

Alternatively you could set ts-node --esm or ts-node-esm to achieve the same without setting it on the the tsconfig.json file.

ts-node up to 10.7

node --experimental-specifier-resolution=node --loader ts-node/esm index.ts

After this you will be able to use TS and ESM in your project in runtime and this statement will be possible:

import { helloWorld } from './Util'

Where helloWorld is an exported member from Util.ts

Important! I don't recommend using ts-node in production, so this is very handy and useful for a development environment but make sure to transpile and use the resulting code in production to avoid possible memory issues with TS Node.

Related