You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
TypeScript generated invalid types if esModuleInterop is enabled #52779
When a package is typed using module.exports =, the correct way to type it is export =. The correct way to import this, in either .ts or .d.ts files, is import identifier = require('module'). For example import React = require('react').
If a user enables esModuleInterop, they are allowed to use import React from 'react'. When compiling to CJS, this will output JavaScript that used either module.exports.default or module.exports.
"use strict";var__importDefault=(this&&this.__importDefault)||function(mod){return(mod&&mod.__esModule) ? mod : {"default": mod};};Object.defineProperty(exports,"__esModule",{value: true});constreact_1=__importDefault(require("react"));
IMO This isn’t great, but it works.
What is bad, is that it generates a default import in the type definitions.
importReactfrom'react';
Whereas it should be typed as this:
importReact= require('react');
If this is library code, that means the user of the library now is also enforced to use esModuleInterop, whether they like it or not.
This is correct only if type checking assumes that the target module has a synthetic default (i.e., does not have the __esModule marker). allowSyntheticDefaultImports (implied by esModuleInterop) means that the type of a default import from one CJS module to another varies depending on whether the target module was originally written in ESM syntax. If it was, the default import accesses the target’s module.exports.default. If it wasn’t, the default import accesses the target’s module.exports. The former case can’t be represented by import x = require("./y"), but the latter case can. Heurisitics are used during type checking to guess which of these cases will happen at runtime (i.e., which branch of the conditional return (mod && mod.__esModule) ? mod : { "default": mod } will be taken). We could pull this same heuristic check during declaration emit, and emit an import assignment when the false branch is taken and a default import when the true branch is taken. This would improve the portability of the declaration file produced, but it would be a problem for #47947.
Once this problem exists in one library, this is likely to “infect” all its dependants too. I think this is partly because TypeScript gives improper hints on how to fix it. Let’s say someone writes a library my-component, and they don’t enable esModuleInterop. TypeScript will show the following error.
MyComponent.tsx:1:8 - error TS1259: Module '"node_modules/@types/react/index"' can only be default-imported using the 'esModuleInterop' flag
1 import React from 'react'
~~~~~
node_modules/@types/react/index.d.ts:63:1
63 export = React;
~~~~~~~~~~~~~~~
This module is declared with 'export =', and can only be used with a default import when using the 'esModuleInterop' flag.
As stated in the OP, the correct fix for this is to use either import React = require('react') or import * as React from 'react'. However, TypeScript suggests to enable esModuleInterop.
If the author of my-component would follow TypeScript’s suggestion and enable esModuleInterop, this library can’t be consumed without using esModuleInterop, so consumers of my-component are prompted with the same error and suggestion.
The consumers of my-component have these options:
Follow the suggestion to enable esModuleInterop, delegating the issue to their dependants.
Enable skipLibCheck, meaning usage of my-component results in implicit any types.
Use something like patch-package to fix the types of my-component in their node_modules.
Fix the bug upstream in my-component and hope its author accepts the fix.
I’m not familiar with the details from #47947, but it introduces two new compiler options. As far as I understand, it makes sense to make those options mutually exclusive with allowSyntheticDefaultImports (and therefor esModuleInterop).
Activity
andrewbranch commentedon Feb 17, 2023
This is correct only if type checking assumes that the target module has a synthetic default (i.e., does not have the
__esModulemarker).allowSyntheticDefaultImports(implied byesModuleInterop) means that the type of a default import from one CJS module to another varies depending on whether the target module was originally written in ESM syntax. If it was, the default import accesses the target’smodule.exports.default. If it wasn’t, the default import accesses the target’smodule.exports. The former case can’t be represented byimport x = require("./y"), but the latter case can. Heurisitics are used during type checking to guess which of these cases will happen at runtime (i.e., which branch of the conditionalreturn (mod && mod.__esModule) ? mod : { "default": mod }will be taken). We could pull this same heuristic check during declaration emit, and emit an import assignment when the false branch is taken and a default import when the true branch is taken. This would improve the portability of the declaration file produced, but it would be a problem for #47947.remcohaszing commentedon Feb 20, 2023
Once this problem exists in one library, this is likely to “infect” all its dependants too. I think this is partly because TypeScript gives improper hints on how to fix it. Let’s say someone writes a library
my-component, and they don’t enableesModuleInterop. TypeScript will show the following error.As stated in the OP, the correct fix for this is to use either
import React = require('react')orimport * as React from 'react'. However, TypeScript suggests to enableesModuleInterop.If the author of
my-componentwould follow TypeScript’s suggestion and enableesModuleInterop, this library can’t be consumed without usingesModuleInterop, so consumers ofmy-componentare prompted with the same error and suggestion.The consumers of
my-componenthave these options:esModuleInterop, delegating the issue to their dependants.skipLibCheck, meaning usage ofmy-componentresults in implicitanytypes.patch-packageto fix the types ofmy-componentin theirnode_modules.my-componentand hope its author accepts the fix.I’m not familiar with the details from #47947, but it introduces two new compiler options. As far as I understand, it makes sense to make those options mutually exclusive with
allowSyntheticDefaultImports(and thereforesModuleInterop).andrewbranch commentedon May 10, 2023
I discussed another solution to this in #54212