The resolved types use export default
where the JavaScript file appears to use module.exports =
. This will cause TypeScript under the node16
module mode to think an extra .default
property access is required, but that will likely fail at runtime. These types should use export =
instead of export default
.
This problem occurs when a CommonJS JavaScript file appears to use an assignment pattern like module.exports = something
where the corresponding type declarations say export default something
. The correct syntax for the type declarations to use would be export = something
. As shown in the following table, when export default something
describes a CommonJS module, it describes the type of module.exports.default
, not module.exports
itself.
JavaScript syntax | Type declaration syntax |
---|---|
module.exports = x |
export = x |
exports.default = x; exports.__esModule = true |
export default x |
export default x |
export default x |
Consumers using --moduleResolution node16
or nodenext
will see incorrect TypeScript errors when importing the module from an ES module:
import mod from "pkg";
mod();
// ^^ This expression is not callable.
// Type 'typeof import("pkg")' has no call signatures.
This error appears even though the code is correct at runtime. The TypeScript error can be resolved by changing the code to access the .default
property of the import:
import mod from "pkg";
mod.default(); // 💥
However, this likely crashes at runtime. The types misrepresent the runtime module in a way that makes it impossible to satisfy both Node and TypeScript at the same time.
This problem is typically only apparent to users under --moduleResolution node16
or nodenext
in ESM files. In many bundlers, as well as in files that will be transformed to CommonJS with esModuleInterop
enabled, a default import can be used to access a CommonJS module’s exports.default
provided it exposes an exports.__esModule
, so the inaccuracy of the typings is not easily observable. But in ES modules in Node, an imported CommonJS module’s __esModule
export has no special meaning, so accessing its exports.default
will always require an additional .default
property access on the default import. This lack of special interop behavior in Node is only reflected in TypeScript’s node16
and nodenext
modes, so users in other modes may be unaffected.
This problem is usually caused by library authors incorrectly hand-authoring declaration files to match existing JavaScript rather than generating JavaScript and type declarations from TypeScript with tsc
.