|
| 1 | +# ❓ Missing `export =` |
| 2 | + |
| 3 | +The JavaScript appears to set both `module.exports` and `module.exports.default` for improved compatibility, but the types only reflect the latter (by using `export default`). This will cause TypeScript under the `node16` module mode to think an extra `.default` property access is required, which will work at runtime but is not necessary. These types `export =` an object with a `default` property instead of using `export default`. |
| 4 | + |
| 5 | +## Explanation |
| 6 | + |
| 7 | +This problem occurs when a CommonJS JavaScript file appears to use a compatibility pattern like: |
| 8 | + |
| 9 | +```js |
| 10 | +class Whatever { |
| 11 | + /* ... */ |
| 12 | +} |
| 13 | +Whatever.default = Whatever; |
| 14 | +module.exports = Whatever; |
| 15 | +``` |
| 16 | + |
| 17 | +but the corresponding type definitions only reflect the existence of the `module.exports.default` property: |
| 18 | + |
| 19 | +```ts |
| 20 | +declare class Whatever { |
| 21 | + /* ... */ |
| 22 | +} |
| 23 | +export default Whatever; |
| 24 | +``` |
| 25 | + |
| 26 | +The types should declare the existence of the `Whatever` class on both `module.exports` and `module.exports.default`. The method of doing this can vary depending on the kinds of things already being exported from the types. When the `export default` exports a class, and that class is the only export in the file, the `default` can be declared as a static property on the class, and the `export default` swapped for `export =`: |
| 27 | + |
| 28 | +```ts |
| 29 | +declare class Whatever { |
| 30 | + static default: typeof Whatever; |
| 31 | + /* ... */ |
| 32 | +} |
| 33 | +export = Whatever; |
| 34 | +``` |
| 35 | + |
| 36 | +When the file exports additional types, it will be necessary to declare a `namespace` that merges with the class and contains the exported types: |
| 37 | + |
| 38 | +```ts |
| 39 | +declare class Whatever { |
| 40 | + static default: typeof Whatever; |
| 41 | + /* ... */ |
| 42 | +} |
| 43 | +declare namespace Whatever { |
| 44 | + export interface WhateverProps { |
| 45 | + /* ... */ |
| 46 | + } |
| 47 | +} |
| 48 | +export = Whatever; |
| 49 | +``` |
| 50 | + |
| 51 | +This merging namespace can also be used to declare the `default` property when the main export is a function: |
| 52 | + |
| 53 | +```ts |
| 54 | +declare function Whatever(props: Whatever.WhateverProps): void; |
| 55 | +declare namespace Whatever { |
| 56 | + declare const _default: typeof Whatever; |
| 57 | + export { _default as default }; |
| 58 | + |
| 59 | + export interface WhateverProps { |
| 60 | + /* ... */ |
| 61 | + } |
| 62 | +} |
| 63 | +export = Whatever; |
| 64 | +``` |
| 65 | + |
| 66 | +## Consequences |
| 67 | + |
| 68 | +This problem is similar to the [“Incorrect default export”](./FalseExportDefault.md) problem, but in this case, the types are _incomplete_ rather than wholly incorrect. This incompleteness may lead TypeScript users importing from Node.js ESM code, or CommonJS code without `esModuleInterop` enabled, to add an extra `.default` property onto default imports to access the module’s `module.exports.default` property, even though accessing the `module.exports` would have been sufficient. |
| 69 | + |
| 70 | +```ts |
| 71 | +import Whatever from "pkg"; |
| 72 | +Whatever.default(); // Ok, but `Whatever()` would have worked! |
| 73 | +``` |
| 74 | + |
| 75 | +## Common causes |
| 76 | + |
| 77 | +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`, or by using a third-party TypeScript emitter that adds an extra compatibility layer to TypeScript written with `export default`. Libraries compiling to CommonJS should generally avoid writing `export default` as input syntax. |
0 commit comments