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
Change esModuleInterop from being an opt-in to instead be an opt-out, taking one step further towards deprecating the non-esModuleInterop mode and make esModuleInterop the only supported mode going forward (which has to be the long term goal).
We highly recommend applying it both to new and existing projects.
esModuleInterop is also a recommended option to set in tsconfig.json and when one runs tsc --init it gets set to true automatically.
However, the fact that it still is opt-in makes that non-esModuleInterop mode is still very prevalent, which kind of works against the very problems it was meant to fix – as rather than having just the pre-esModuleInterop functionality, modules now have to try to make things work across both options – and on top of that increasingly have to work with TS-validated JS.
This wouldn't be a breaking change in existing TypeScript/JavaScript code
It would for all code which haven't yet adopted esModuleInterop, but it would make it easier to get TypeScript and JavaScript code to stay compatible with one another
This wouldn't change the runtime behavior of existing JavaScript code
This could be implemented without emitting different JS based on the types of the expressions
It would emit different JS for all code which haven't yet adopted esModuleInterop, but it would make it easier to get TypeScript and JavaScript code to stay compatible with one another
This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
I'll take this as a possibility but we have historically not changed commandline defaults unless the case for it was overwhelmingly strong. This might have the possibility to break people's programs and we would need some very good evidence that it was worth those people encountering that problem.
Reacted by Pelle Wessman, Lemmingh and MarlonPerez-01
I'm a bit wary of using esModuleInterop since how it interacts with node's ESM implementation of default isn't quite compatible. This leads to various conversations about what to do usually ending up with "don't use default exports" as my go to response. Enabling it by default would remove some issues but it seems like a second migration would have to happen afterward if node compatibility was another configuration option.
My impression was that esModuleInterop was meant to increase compatibility?
It kind of does, but not enough to be completely compatible. Particularly how "default" works still tries to do some magic and things start to get incompatible because of it.
The #44501 seems to partially implement this as both its module types imply esModuleInterop
So anyone setting the 'module' in their tsconfig.json to either node12 and nodenext will get esModuleInterop by default.
A good solution for changing the default without breaking backwards compatibility, but may complicate the uptake of those new types, see eg: sindresorhus/tsconfig#8
the ES6 modules spec states that a namespace import (import * as x) can only be an object, by having TypeScript treating it the same as = require("x") then TypeScript allowed for the import to be treated as a function and be callable. This breaks the spec’s recommendations.
while accurate to the ES6 modules spec, most libraries with CommonJS/AMD/UMD modules didn’t conform as strictly as TypeScript’s implementation.
Turning on esModuleInterop will fix both of these problems in the code transpiled by TypeScript. The first changes the behavior in the compiler, the second is fixed by two new helper functions which provide a shim to ensure compatibility in the emitted JavaScript.
So while the compiler was fixed to comply with the first point, the ESM spec, as you say, two helper functions was also added to increase compatibility (I guess with older TS-code?)
So I guess, what you're getting at is that you think these two helpers should not be there?
I have no opinion on implementation, just that the example above shouldn't behave differently from Node pre/post running TS when trying to match Node. I do not think esModuleInterop preserves behavior as shown above and shouldn't be enabled by default if a 2nd pass of enabling something would create another breakage. I'd rather just have 2 modes of default operation with 2 understood behaviors rather than a long tail mode of legacy prior with esModuleInterop:false default, a second with esModuleInterop:true, and a third with esModuleInterop:true + some flag to make the default export work like Node.
So anyone using "module": "node16" or "module": "nodenext" will have their esModuleInterop default to true (but can override it by actively setting it to false).
I am strongly opposed to using esModuleInterop and I was shocked to find out this is default behaviour when module is set to node16.
Many people don’t understand the difference between export default / import identifier from 'module' and export = / import identifier = require('module'). Many people don’t even know the latter exists, though it is often the correct one for their use case.
esModuleInterop hides this difference for them by generating some interop code. For end user products such as websites or CLI applications, this is not a big deal. For libraries however, this means TypeScript will generate invalid type definitions (#52779).
I believe users need to be more aware of the code they ship (not write) and its correctness. esModuleInterop opposes this view.
Reacted by Dario Fabricio da silva and Marcel Laverdet
Activity
RyanCavanaugh commentedon Oct 19, 2020
I'll take this as a possibility but we have historically not changed commandline defaults unless the case for it was overwhelmingly strong. This might have the possibility to break people's programs and we would need some very good evidence that it was worth those people encountering that problem.
voxpelli commentedon Oct 19, 2020
Ryan Cavanaugh (@RyanCavanaugh) Yeah, it's tricky, right now it's causing resentment among non-TS developers who are trying to cater TS users, so current situation is undesirable as well. (See eg also this Twitter thread with me and Matteo Collina (@mcollina) on top of the above GitHub issues: https://twitter.com/matteocollina/status/1305953416217268226)
voxpelli commentedon Feb 17, 2021
An example where the current default causes confusion and makes people stick with the older non-recommended path: plantain-00/type-coverage#78
Also had this Twitter conversation with Rich Harris (@Rich-Harris): https://twitter.com/rich_harris/status/1361839944453541892?s=21
bmeck commentedon Mar 17, 2021
I'm a bit wary of using
esModuleInteropsince how it interacts with node's ESM implementation ofdefaultisn't quite compatible. This leads to various conversations about what to do usually ending up with "don't use default exports" as my go to response. Enabling it by default would remove some issues but it seems like a second migration would have to happen afterward if node compatibility was another configuration option.voxpelli commentedon Mar 28, 2021
Bradley Farias (@bmeck) Can you elaborate a bit on this or provide a link which describes it?
My impression was that
esModuleInteropwas meant to increase compatibility?bmeck commentedon Mar 30, 2021
Pelle Wessman (@voxpelli)
It kind of does, but not enough to be completely compatible. Particularly how
"default"works still tries to do some magic and things start to get incompatible because of it.Given:
Compiling to CommonJS will result in the following outputs:
Node (after renaming
.tsto.mjs):After TS:
Namely, even with
esModuleInteropit tries to do some hoisting of thedefaultproperty if__esModule:trueexists that Node won't ever do.voxpelli commentedon Aug 25, 2021
The #44501 seems to partially implement this as both its module types imply
esModuleInteropSo anyone setting the
'module'in theirtsconfig.jsonto eithernode12andnodenextwill getesModuleInteropby default.A good solution for changing the default without breaking backwards compatibility, but may complicate the uptake of those new types, see eg: sindresorhus/tsconfig#8
voxpelli commentedon Aug 27, 2021
Bradley Farias (@bmeck) Looking at the documentation page for
esModuleInteropSo while the compiler was fixed to comply with the first point, the ESM spec, as you say, two helper functions was also added to increase compatibility (I guess with older TS-code?)
Those two helpers seems to be these two ones in
tslib:And after compilation your code would roughly look like (assuming
importHelperswould also be true):So I guess, what you're getting at is that you think these two helpers should not be there?
bmeck commentedon Aug 27, 2021
I have no opinion on implementation, just that the example above shouldn't behave differently from Node pre/post running TS when trying to match Node. I do not think
esModuleInteroppreserves behavior as shown above and shouldn't be enabled by default if a 2nd pass of enabling something would create another breakage. I'd rather just have 2 modes of default operation with 2 understood behaviors rather than a long tail mode of legacy prior withesModuleInterop:falsedefault, a second withesModuleInterop:true, and a third withesModuleInterop:true+ some flag to make thedefaultexport work like Node.voxpelli commentedon Oct 30, 2022
Double checked whether this actually shipped, and it did:
TypeScript/src/compiler/utilities.ts
Lines 6428 to 6438 in 2bcfed0
So anyone using
"module": "node16"or"module": "nodenext"will have theiresModuleInteropdefault totrue(but can override it by actively setting it tofalse).Though: This isn't at all mentioned in the docs for
esModuleInterop🤔remcohaszing commentedon Feb 15, 2023
I am strongly opposed to using
esModuleInteropand I was shocked to find out this is default behaviour whenmoduleis set tonode16.Many people don’t understand the difference between
export default/import identifier from 'module'andexport =/import identifier = require('module'). Many people don’t even know the latter exists, though it is often the correct one for their use case.esModuleInterophides this difference for them by generating some interop code. For end user products such as websites or CLI applications, this is not a big deal. For libraries however, this means TypeScript will generate invalid type definitions (#52779).I believe users need to be more aware of the code they ship (not write) and its correctness.
esModuleInteropopposes this view.