Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I need to include ".js" extension in TypeScript import for custom module?

Very simple test of async/await and I have a module that includes simple class to implement a delay function

mymodule.ts:

export class foo {

    public async delay(t: number) {
                console.log("returning prmise");
                return new Promise( resolve => setTimeout(resolve, t));
    };
};

Simple top-level TypeScript file to call the function:

hello.ts:

import { foo } from './mymodule'

let f = new foo();

console.log("start");
await f.delay(4000);
console.log("done");here

This builds fine but when I run the resulting hello.js with Node (node hello.js) I get:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'E:\tsdev\test\out\mymodule' imported from E:\tsdev\test\out\hello.js
Did you mean to import ../mymodule.js?
    at new NodeError (node:internal/errors:399:5)
    at finalizeResolution (node:internal/modules/esm/resolve:326:11)
    at moduleResolve (node:internal/modules/esm/resolve:945:10)
    at defaultResolve (node:internal/modules/esm/resolve:1153:11)
    at nextResolve (node:internal/modules/esm/loader:163:28)
    at ESMLoader.resolve (node:internal/modules/esm/loader:838:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:424:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:77:40)
    at link (node:internal/modules/esm/module_job:76:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

If I add ".js" to make the import import { foo } from './mymodule.js' it also builds just fine and now the application works as expected:

E:\tsdev\test\out>node hello.js
start
returning prmise
done

I tried various changes to tsconfig.json and package.json files.

like image 756
user2556468 Avatar asked Sep 12 '25 11:09

user2556468


1 Answers

If you want to use the official TypeScript compiler to generate JavaScript, you have to add the extension because

  1. Node.js refuses to allow extensionless relative imports
  2. TypeScript refuses to add import extensions at compile time

If you want or need to write extensionless imports in TypeScript, you need a bundler or an alternative build tool that can add the .js extensions. For example, you can transpile TypeScript to JavaScript using Babel and babel-plugin-add-import-extension.

However, you'll still need TypeScript (tsc) itself to do type checking and generate type declaration files, so you'll need a tsconfig.json with compilerOptions.moduleResolution set to "Bundler".

Here's my configuration for a library that targets Node.js 18.

.babelrc:

{
  "presets": [
    [
      "@babel/env",
      {
        "modules": false,
        "targets": {
          "node": "18"
        }
      }
    ],
    "@babel/typescript"
  ],
  "plugins": ["add-import-extension"]
}

tsconfig.json

{
  "extends": "@tsconfig/strictest/tsconfig.json",
  "compilerOptions": {
    "allowJs": false,
    "checkJs": false,
    "declaration": true,
    "declarationMap": true,
    "emitDeclarationOnly": true,
    "lib": ["ES2022"],
    "module": "ES2022",
    "moduleResolution": "Bundler",
    "sourceMap": true,
    "target": "ES2022"
  }
}

package.json scripts:

{
    "build": "run-p -s build:*",
    "build:main": "babel -x .ts -d dist src",
    "build:types": "tsc",
}

and some devDependencies:

{
    "@babel/cli": "^7.22.15",
    "@babel/preset-typescript": "^7.22.15",
    "babel-plugin-add-import-extension": "^1.6.0",
    "npm-run-all": "^4.1.5",
    "typescript": "^5.2.2"
}
like image 111
Steve Avatar answered Sep 15 '25 02:09

Steve