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
moduleResolution: bundler diverges from bundlers following node semantics in regards to default import #54102
When resolving a .d.mts file that proxies to .d.ts in a CJS package with moduleResolution: bundler, TypeScript complains with an error like:
Property 'default' does not exist on type 'string'.ts(2339)
The reason that it complains about it is that TypeScript is resolving default import as if the .d.ts in this CJS package could export the "real" default export. webpack actually follows semantics coined by node and it loads the namespace object as the default export of that "cjs file".
So the type-level reality diverges there from runtime. At runtime, with webpack - it works exactly like it would work in node. TypeScript thinks that the intention was to load module.exports.default there though.
🙂 Expected behavior
I'm not exactly sure what's the expected behavior here because "resolving" like a bundler is quite under-specified and bundlers are not quite consistent when it comes to this. Even when we check this very same repro, we can see that I had to use defaultIsModuleExports: true in Rollup for it to behave the same. By default, Rollup behaves in the same way as TypeScript does.
Yeah, I’m aware of this, but of course it’s not a moduleResolution issue. Really, it’s something that module should handle. I believe behavior differs between different bundlers, which is, in technical terms, a huge bummer. What’s really bad, IMO, is that last I checked, Webpack does the Node behavior for .{m,c}js files, but not the equivalent TS extensions, which violates a pretty core assumption we have about the substitutability of declaration files, TS files, and JS files. This is already on my radar to investigate soon.
Reacted by Bruce Pascoe, Andrew Branch, Ryan Cavanaugh, Ghislain B., Jan Potoms, Krzysztof Zawisła, David Sherret, nyngwang, Brandon Tsang, Aashutosh Rathi and 1 more
Reacted by Martin Johns, John Reilly, Fotis Papadogeorgopoulos, Brandon Tsang, Aashutosh Rathi and Howard
I forked https://sokra.github.io/interop-test to https://andrewbranch.github.io/interop-test, removed a bunch of import and export variations that weren’t super interesting to me (there are still a ton), and added .ts/.mts cases to esbuild and Webpack. I need to add similar TS cases to Rollup, and I’d like to add Parcel, Bun, and perhaps others. But it shows basically what we’ve already established in this issue:
Both esbuild and Webpack give a Node-ESM-like interop treatment to default imports in .mjs files (that is, it always synthesizes a default; it doesn’t care about whether the target module has a __esModule marker).
esbuild extends this same treatment to .mts files since it understands TypeScript out of the box. Webpack does not.
At first, it struck me as odd that these bundlers voluntarily adopted interop restrictions that Node tried very hard to avoid, but couldn’t for technical reasons. But doing so is the best way for them to handle code written for Node—it’s a cross-compatibility strategy. I think it would be best to push all bundlers toward doing this, if they’re not already. But since TypeScript isn’t yet set up to model this behavior (without also adopting nodenext module resolution), it seems inevitable that if we do, it will be toggleable with compiler options somehow. So even if we can’t get bundlers to be consistent between each other on whether they apply this Node-compat behavior, users will be able to configure TypeScript for whatever their bundler does.
However, it’s not possible for us to model bundlers like Webpack that apply the Node-compat behavior to .mjs files, but not to .mts files. That’s something we’ll need to push for across the bundler/TS-runtime ecosystem (I’m assuming Webpack isn’t the only one that will need changes) before any TS option can work.
However, it’s not possible for us to model bundlers like Webpack that apply the Node-compat behavior to .mjs files, but not to .mts files. That’s something we’ll need to push for across the bundler/TS-runtime ecosystem (I’m assuming Webpack isn’t the only one that will need changes) before any TS option can work.
I recall that you mentioned knowing about this issue for quite some time. Is there some tracking issue about this in webpack? Did their team comment on this anyhow in the past?
I don’t know; I’m in the process of figuring out exactly what to propose, and plan to talk to Sean soon. I haven’t mentioned it earlier because it was something I noticed in passing while focusing on module resolution, and needed to double check that I wasn’t just holding it wrong 🥴
Andrew Branch (@andrewbranch) likely it comes from me and Emma Hamilton (@emmatown) trying to implement our library building scheme that is meant to avoid dual package hazard and to provide consistent module shape across formats. We ended up emitting an extra proxy files to work around it. Notice the _default vs default dance introduced here: preconstruct/preconstruct#546
Activity
fatcerberus commentedon May 2, 2023
Isn’t this controlled by
allowSyntheticDefaultImports?andrewbranch commentedon May 2, 2023
Yeah, I’m aware of this, but of course it’s not a
moduleResolutionissue. Really, it’s something thatmoduleshould handle. I believe behavior differs between different bundlers, which is, in technical terms, a huge bummer. What’s really bad, IMO, is that last I checked, Webpack does the Node behavior for.{m,c}jsfiles, but not the equivalent TS extensions, which violates a pretty core assumption we have about the substitutability of declaration files, TS files, and JS files. This is already on my radar to investigate soon.Andarist commentedon May 2, 2023
If you ever need a hug... I'm here for you.
andrewbranch commentedon May 17, 2023
I forked https://sokra.github.io/interop-test to https://andrewbranch.github.io/interop-test, removed a bunch of import and export variations that weren’t super interesting to me (there are still a ton), and added .ts/.mts cases to esbuild and Webpack. I need to add similar TS cases to Rollup, and I’d like to add Parcel, Bun, and perhaps others. But it shows basically what we’ve already established in this issue:
.mjsfiles (that is, it always synthesizes a default; it doesn’t care about whether the target module has a__esModulemarker)..mtsfiles since it understands TypeScript out of the box. Webpack does not.At first, it struck me as odd that these bundlers voluntarily adopted interop restrictions that Node tried very hard to avoid, but couldn’t for technical reasons. But doing so is the best way for them to handle code written for Node—it’s a cross-compatibility strategy. I think it would be best to push all bundlers toward doing this, if they’re not already. But since TypeScript isn’t yet set up to model this behavior (without also adopting
nodenextmodule resolution), it seems inevitable that if we do, it will be toggleable with compiler options somehow. So even if we can’t get bundlers to be consistent between each other on whether they apply this Node-compat behavior, users will be able to configure TypeScript for whatever their bundler does.However, it’s not possible for us to model bundlers like Webpack that apply the Node-compat behavior to .mjs files, but not to .mts files. That’s something we’ll need to push for across the bundler/TS-runtime ecosystem (I’m assuming Webpack isn’t the only one that will need changes) before any TS option can work.
Andarist commentedon May 17, 2023
I recall that you mentioned knowing about this issue for quite some time. Is there some tracking issue about this in webpack? Did their team comment on this anyhow in the past?
andrewbranch commentedon May 17, 2023
I don’t know; I’m in the process of figuring out exactly what to propose, and plan to talk to Sean soon. I haven’t mentioned it earlier because it was something I noticed in passing while focusing on module resolution, and needed to double check that I wasn’t just holding it wrong 🥴
andrewbranch commentedon May 30, 2023
I opened webpack/webpack#17288, and also am showing relevant results at https://andrewbranch.github.io/interop-test/#synthesizing-default-exports-for-cjs-modules
andrewbranch commentedon May 28, 2024
Mateusz Burzyński (@Andarist) do you remember how you originally encountered this issue, i.e. what libraries were involved?
Andarist commentedon May 29, 2024
Andrew Branch (@andrewbranch) likely it comes from me and Emma Hamilton (@emmatown) trying to implement our library building scheme that is meant to avoid dual package hazard and to provide consistent module shape across formats. We ended up emitting an extra proxy files to work around it. Notice the
_defaultvsdefaultdance introduced here: preconstruct/preconstruct#546khallmark commentedon Nov 3, 2024
Andrew Branch (@andrewbranch) what ever came of this change as well as this webpack issue?
andrewbranch commentedon Nov 4, 2024
Our current opinion is that the prevalence of people having problems with this doesn’t merit additional confusing configuration.
remove type:module in relation to microsoft/TypeScript#54102