How to import an es module in Node without an extension

1.4k views Asked by At

I've heard that when importing ES modules, you're supposed to include a file extension. However, I came across some Node projects that import ES modules without an extension, like in this example.

I'm wondering how they do that.

1

There are 1 answers

0
nktnet On BEST ANSWER

Regarding your statement

I've heard that when importing ES modules, you're supposed to include a file extension

Yes, this is covered in the documentation for NodeJS ESM, under import-specifiers:

Relative specifiers like './startup.js' or '../config.mjs'. They refer to a path relative to the location of the importing file. The file extension is always necessary for these.

and under the Mandatory File Extensions section:

A file extension must be provided when using the import keyword to resolve relative or absolute specifiers. Directory indexes (e.g. './startup/index.js') must also be fully specified.

This behaviour matches how import behaves in browser environments, assuming a typically configured server.


As for your next statement,

However, I came across some Node projects that import ES modules without an extension, like in this example.

The example you linked is for a TypeScript project, not a regular ESM JavaScript/NodeJS project.

Looking at lines 4 and 5 of the project's tsconfig.json, we can see that there is

        "module": "CommonJS",
        "moduleResolution": "Node",

A good place to start is TypeScript's documentation on Module Resolution. which explains how the extensions can be omitted.

For example, under the description for "node10" (which aliases to node for backwards compatibility), the following is stated:

It supports looking up packages from node_modules, loading directory index.js files, and omitting .js extensions in relative module specifiers.

and again in the Module - References: Extensionless Relative Paths section, we see that:

In some cases, the runtime or bundler allows omitting a .js file extension from a relative path. TypeScript supports this behavior where the moduleResolution setting and the context indicate that the runtime or bundler supports it:

// @Filename: a.ts
export {};
// @Filename: b.ts
import {} from "./a";

If TypeScript determines that the runtime will perform a lookup for ./a.js given the module specifier "./a", then ./a.js will undergo extension substitution, and resolve to the file a.ts in this example.

Hope this helps :).