Have a TS 4.7 library using ESM modules.
tsconfig.json:
"target": "ES2020",
"module": "ES2020",
"lib": ["ES2020"],
"moduleResolution": "node",
package.json
"type": "module",
I have a main file with only a one silly export:
index.ts
export { Spig } from './spig';
which is compiled to:
index.js
export { Spig } from './spig';
//# sourceMappingURL=index.js.map
When I use this library from a Node CLI program (with ESM modules enabled as well), I get the following error:
Cannot find module <path>/lib/spig imported from <path>/lib/index.js
When I manually add .js
in the generated index.js
, the issue is gone:
export { Spig } from './spig.js';
How can I force TypeScript compiler to generate the extension, too? What am I missing here?
You cannot omit the file extension anymore in ESM module imports. The extension should be always .js
/.jsx
, not .ts
/.tsx
for a typescript file. So, in the index.ts
you should add the extension to spig
export like the following and every other file imported/exported if using ESM modules.:
index.ts
export { Spig } from './spig.js';
Also, moduleResolution
should be set to Node16
or NodeNext
so ESM modules work as expected.
As stated in the docs (enphasis by me):
Relative import paths need full extensions (e.g we have to write import "./foo.js"
instead of import "./foo"
).
When a .ts
file is compiled as an ES module, ECMAScript import/export syntax is left alone in the .js
output; when it’s compiled as a CommonJS module, it will produce the same output you get today under module: commonjs.
This also means paths resolve differently between .ts
files that are ES modules and ones that are CJS modules. For example, let’s say you have the following code today:
// ./foo.ts
export function helper() {
// ...
}
// ./bar.ts
import { helper } from "./foo"; // only works in CJS
helper();
This code works in CommonJS modules, but will fail in ES modules because relative import paths need to use extensions. As a result, it will have to be rewritten to use the extension of the output of foo.ts
- so bar.ts
will instead have to import from ./foo.js
.
// ./bar.ts
import { helper } from "./foo.js"; // works in ESM & CJS
helper();
This might feel a bit cumbersome at first, but TypeScript tooling like auto-imports and path completion will typically just do this for you.