-
-
Notifications
You must be signed in to change notification settings - Fork 8.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Broken cjs & esm interop #7973
Comments
Thanks for linking the related issue - I have tried to find one before starting a thread, but failed to do so. This can be closed as duplicate, but the other thread got stale and I would hope this thread to revive the discussion around this because in my opinion this is a serious problem - the users shouldn't worry how their dependencies are structured in this regard. People are adding "module" entries in patch/minor versions and this has a potential of people builds. |
Agreed. This issue is clearer about the problem. |
See this comment: #6584 (comment)
This is not an official babel plugin. They explicit dropped this behavior. If you want to have my recommendation: Avoid using this plugin. see also: babel/babel#2212 (comment)
You don't want this. This will lead to duplicate bundling of a package when referenced with two different style.
Make sure to export the same API via ESM and CJS. Yes, that's not possible when currently exporting a non-object via |
There are still some packages created with babel@5 out there though, and while maybe it shouldnt be a deciding factor in this it should still be considered in making a final decision.
Sure thing, I don't use it and I'm using
Actually I would want this. User safety is IMHO far way important than some bytes added to the bundle - and I'm always trying to help libraries that I use to shave off some bytes by reviewing their build tooling and the code itself. What I wouldn't want is to "swallow" this silently. The best of both worlds would be IMHO to deopt behaviour for safety and at the same time print warnings about duplicated dependencies because of this, allowing people to raise issues in the repositories upstream. |
Sure. The problem is duplicating packages can also lead to problems. This also duplicates module local state and breaks instance equality. i. e. having two versions of react would break things. These bug are probably more difficult to locate than
Changing this would break a lot of valid code. Packages that correctly exposing the same API for ESM and CJS would also be affected. But... the current (experimental) ESM support in node.js seem to use this approach. Here I'm willing to reconsider our approach when node.js settles on an approach. We usually follow node.js. |
This is a valid point - havent thought of that.
IMHO this is not an issue, you could validate the shape of exposed CJS module and use When loading ESM from CJS you could "simply" use similar interop helper to those: // rollup
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
// babel
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } This is a strategy used by both rollup & babel and is working fine for majority of use cases, seems to be a valid approach improving the interoperability and safety. If you do not plan to deopt the loading mechanism I would advocate at least to fail the whole build when detecting differently shaped esm (with default) from cjs. It would still be better to just throw her and prevent guaranteed runtime errors. |
I've check how rollup handles it - you can check out here. It doesnt deopt to main if the requester is a cjs module, but it handles the interop problem gracefully. |
These are the helpers for loading CJS from ESM. Babel doesn't use any interop for loading CJS from ESM. (Resulting in receiving the namespace object, like we do). Rollup seem to use this I don't really like to use this, because it's pretty inconsistent and sloppy. Here is a table how rollup and webpack behave when loading ESM from CJS:
And the biggest problem: Now tell me how to access an additional named export in this module when the default export is truthy? None of the other tools include both CJS and ESM version
We can't do that at compile-time, because CJS shape is a runtime-only value. |
Having been asked to add a few cents, let me note that I have not as much experience as others in this conversation and may be overlooking things. Nevertheless I will try to shed a little more light on how rollup actually does its interop. First let me note, which you might be aware of, that Rollup does not understand CJS internally and handles everything as ESM. If you create a CJS bundle, then Rollup's finalizer adds some interop code which seems to be part of the discussion here. auto mode: IMO, auto mode is pretty consistent and intuitive. It is only concerned with how the exports of a bundle, which as I stated are ESM exports internally, are mapped to
As I said, I think it is intuitive as it enables bundles to easily export non-object results without having to worry about configuration by simply making it a default export. It is also consistent in that a bundle generated with importing into CJS: Now I must admit, this is inconsistent and needs to be changed, though probably not due to the reasons listed above.
Not sure where you go the impression that rollup will behave any different for falsy default exports. I assure you it does not unless you are omitting the module.exports = {
foo: 'bar',
default: 'this is probably not intended to be a default export'
} which would be the output of export default {
foo: 'bar',
default: 'this is probably not intended to be a default export'
} Importing this would not give you the default export but the default export key. I intend to fix this for the 1.0 by changing the interop helper to something more consistent with what Babel does, i.e. looking solely for the bundled modules imported by both ESM and CJS: For rollup, this is handled solely by |
But admittedly, there might be issues with rollup-plugin-commonjs, which is definitely not the most beautiful piece of software out there. |
Just my 2 cents after reading through all the context here: it seems like there's an 80% case that can be accounted for here in order to dramatically easy pains associated with this issue. @lukastaegert described how rollup is implementing such a case: when requiring an ES Module with only a default export, coalesce it to module.exports. On the module authoring side of things, most developers who ship npm packages in combination ESM+CJS avoid mixing default and named exports, because doing so allows the two formats to more closely approximate eachother. It would be nice if Webpack had the special handling like Rollup's in order to optimize for this majority case. I believe the reason the interopRequire() helper was mentioned due to the ramification of the above special-casing, which is that when importing the default export of a module of unknown type (cjs/esm) from an ES Module, it could be returned either as the exports object itself, or a |
Default exports are always a headache for public api. They are hard to use when same module is used for both node and webpack via commonjs. We have this problem webpack/webpack#6584 Mixing named and default exports works even buggier. webpack/webpack#7973 The best solution is using only named exports. They fits perfectly with interops and may work without interop for commonjs. (module.exports = was a mistake, export default IMO too)
Default exports are always a headache for public api. They are hard to use when same module is used for both node and webpack via commonjs. We have this problem webpack/webpack#6584 Mixing named and default exports works even buggier. webpack/webpack#7973 The best solution is using only named exports. They fits perfectly with interops and may work without interop for commonjs. (module.exports = was a mistake, export default IMO too)
Since we have deprecated deep requires we no longer need the module.exports from the individual v*.js files. Since we now ship plain, non-babled deep require files on the top level of the npm package for legacy CommonJS deep-require support we can compensate for the lack of module.exports in there and instead simply access the "default" export of the algorithm-specific modules. This change was inspired by webpack/webpack#7973 (comment)
Since we have deprecated deep requires we no longer need the module.exports from the individual v*.js files. Since we now ship plain, non-babled deep require files on the top level of the npm package for legacy CommonJS deep-require support we can compensate for the lack of module.exports in there and instead simply access the "default" export of the algorithm-specific modules. This change was inspired by webpack/webpack#7973 (comment)
Since we have deprecated deep requires we no longer need the module.exports from the individual v*.js files. Since we now ship plain, non-babled deep require files on the top level of the npm package for legacy CommonJS deep-require support we can compensate for the lack of module.exports in there and instead simply access the "default" export of the algorithm-specific modules. This change was inspired by webpack/webpack#7973 (comment)
Latest answer from the duplicate issue #10971 (comment) |
Webpack 5 support the exports field with |
This issue had no activity for at least three months. It's subject to automatic issue closing if there is no activity in the next 15 days. |
Default exports are always a headache for public api. They are hard to use when same module is used for both node and webpack via commonjs. We have this problem webpack/webpack#6584 Mixing named and default exports works even buggier. webpack/webpack#7973 The best solution is using only named exports. They fits perfectly with interops and may work without interop for commonjs. (module.exports = was a mistake, export default IMO too)
Summary: `react-icomoon` has a quirky CommonJS export with a hack meant to make it work when imported from an ES Modules context. The idea was that would set `exports.__esModule` to enable the hack, and you would export a prop named `default` that would be the "default" import for the ESM. This hack was supported in Webpack 4, but not in Webpack 5. There's a lot of [noise](webpack/webpack#10971) about [this](webpack/webpack#7973) on [GitHub](babel/babel#12363), but the Webpack team points to Node as no longer supported this hack either as justification for pulling it out. The `react-icomoon` package uses this hack. I tried upgrading it, but the latest version still had the same problem. To get around it, I just update the code to access `.default` directly. Note that this means `lib/components/SWMansionIcon.react.js` wouldn't work in a React Native context, despite `react-icomoon` claiming to support React Native. But we don't need it on React Native since we use `createIconSetFromIcoMoon` from `@expo/vector-icons` instead. Depends on D6703 Test Plan: 1. I tested dev build in `web` via `yarn dev` in `web` + `keyserver` and then tested the website 2. I tested prod build by running `yarn prod` in `web` and then tested the website by running keyserver with `yarn prod-build && yarn-prod` Reviewers: atul, tomek Reviewed By: atul Differential Revision: https://phab.comm.dev/D6704
Is there an actual solution for this? I'm experiencing this issue right now. |
Bug report
What is the current behavior?
Many modules published to npm are using "auto" exports (https://rollupjs.org/guide/en#output-exports-exports, but there is also a popular babel plugin which adds this behaviour https://github.com/59naga/babel-plugin-add-module-exports#readme) which is supposed to ease interop with node (removing "pesky"
.default
for CJS consumers when there is only a default export in the module).And with that depending on a package authored solely in CJS (which still is really common) which depends on a package authored using the mentioned "auto" mode is dangerous and broken.
Why? Because webpack is using the "module" entry from package.json (thus using real default export) without checking the requester module type (which is cjs here). CJS requester did not use a
.default
when requiring the package with auto mode, because from its perspective there was no such thing.If the current behavior is a bug, please provide the steps to reproduce.
https://github.com/Andarist/webpack-module-entry-from-cjs-issue . Exported value should be
"foobar42"
instead of"foo[object Module]42"
What is the expected behavior?
Webpack should deopt (ignoring .mjs & "module") its requiring behaviour based on the requester type.
Other relevant information:
webpack version: latest
Node.js version: irrelevant
Operating System: irrelevant
Additional tools: irrelevant
Mentioning rollup team as probably its the tool outputting the most auto mode libraries ( @lukastaegert @guybedford ) and @developit (who I think might be interested in the discussion).
The text was updated successfully, but these errors were encountered: