-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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
module: proxy require('esm') to import('esm') #39064
Conversation
@nodejs/modules |
One thing we could possibly do here to improve the error message currently would be to use a parser to determine if the target file looks like CommonJS or ESM, then alter the message based on those two cases.
Would something like that help to remove this confusion? |
Wouldn't this mean that by writing ESM, i'm automatically forced to support an API that's both async behind a Promise, and sync-ish via |
'Replace `require` with `import`', | ||
'Warning', 'WARN_REQUIRE_ESM'); | ||
const ESMLoader = asyncESM.ESMLoader; | ||
const exports = ESMLoader.import(filename); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to be converted to a URL for windows paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It also makes sense to expose the inners of getModuleJob
directly and use that rather than import
(namely all the bits after the specifier is resolved, like in the other PR) - the cjs loader has already resolved the specifier at this point so there's no reason to have the esm loader double resolve it. (That's just more unneeded disk hits, and potential format confusion bugs if the two loaders disagree on the format)
This just confuses the problem further imo. The vast majority of situations where CJS will try to require ESM is existing code tracking dependency updates on packages that have decided to go from CJS to pure ESM (perhaps unknowingly or via dependabot or whatever). This change (afaict) simply disguises #30891 actually attempts to solve a sync resolution of an esm module inside of a require statement which would be a much better solution if we can agree on a way to make it work. Finally, replacing an error with behavior that is already served fine with |
throw new ERR_REQUIRE_ESM(filename); | ||
if (StringPrototypeEndsWith(filename, '.mjs') && | ||
!Module._extensions['.mjs']) { | ||
process.emitWarning('Attempting to require and ESModule.' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
process.emitWarning('Attempting to require and ESModule.' + | |
process.emitWarning('Attempting to require and ESModule. ' + |
const parentPath = parent?.filename; | ||
const packageJsonPath = path.resolve(pkg.path, 'package.json'); | ||
throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath); | ||
process.emitWarning('Attempting to require and ESModule.' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
process.emitWarning('Attempting to require and ESModule.' + | |
process.emitWarning('Attempting to require and ESModule. ' + |
This is a dangerous PR to even propose - I don't wanna start seeing The problem was never |
Hi! I asked for this change on twitter. I'm interested in addressing the following user flow:
Now, notably, I find "make Sam read the error message and grok dynamic import()" unacceptable as an answer, personally. So that's a no-go for me, at least. I'm basing this on a similar story posted on Twitter. As for sync resolution of modules, Node needs to make a call one way or another. Like, it's within the realm of possibility that such a thing could be made to work, but is that desirable? I don't know. It seems like a pretty big divergence in behavior from how modules work in other environments, though it does bring it closer to ES module syntax as transpiled to CJS. (At that point, why not transpile optimistically?) In order to make a call on this PR, we need to be able to make a call on that PR. Speaking to it being dangerous, well, I disagree. It looks like folks are already seeking out this behavior, according to that code search. |
There's no difference in usage between requiring IMO, for now, @guybedford is probably right - there's probably room to improve the error message in CJS to directly include a copy-pasteable dynamic import statement that can be used instead of |
I don't know if I agree with this take on the goals of this. I'm not asking for the complexity of the loader to be hidden, I'm saying that Node has enough context at this point to know how to appropriately load the target module, and the current messaging is sending users down a path that leads to frustration. Whether users use Better error messaging would be an improvement, but in this case, the suggestion in the error seems like it'd always be adopted by the user. In which case, why not just make the thing the user expected to work... work? (Leaving aside the fact that that approach closes the door to synchronously requiring ESM modules for now.) |
Moreover, at least in that story, if this behavior was in already, you'd have seen the same story posted, since none of the lines given would have worked still, except the error in the require cases would have been |
A bunch of those esm error handling examples don't actually handle the error and simply recontextualize it to some form of "cjs only sorry". This change would break that error handling in exactly the way you describe, in addition to preventing a real require('foo.mjs') solution in the future. Is that the intention here as well? I refuse to accept giving that possibility up (because it would solve the whole mess), even if it never materializes 🥲 Maybe someone will figure it out. For the case of a beginner learning about the world of node module types, improving the error message would be the only option here that doesn't inadvertently introduce many more issues. |
@MylesBorins this is a breaking change, and will break the ESM handling in Mocha v7 & v8, because we rely on the exception BTW, this won't break Mocha v9 and onward, because in v9 we first |
Here’s how I would imagine this playing out if I were the user:
|
Except it still doesn't work, because there is no top-level await in CommonJS |
Does anyone want to explicitly block this change? If people would rather see this behind a flag (at least to start) that could be an option too. Please let me know as I don't want to kick off additional work on this if it isn't going to land. I don't personally see there being a future where we can actually support synchronous require of ESM and I don't think it is benefiting our users continuing to throw here to carve out the future design space at the cost of today's UX |
I won't block, but I really don't see how this benefits our users. |
i am too tired to block but i am not a fan |
The common scenario that I imagine with this change is:
const { someFunction } = require('X')
module.exports = function() {
someFunction(someArgument)
}
|
I get that the many comments in the other thread are hard to follow, but the TL;DR at the end of the discussion in it is that we can avoid the event loop chicanery once loaders are moved off-thread (which is still planned to be done...? @bmeck ) by using some |
I challenge the assertion that this change improves user experience. It confuses and obfuscate whats going on. We already have people confused at capabilities this change hints at in this very thread , even though it fails to provide whats hinted at. (e.g. people thinking that they can simply prefix
Blowing the opportunity to land sync loading of esm from cjs in #30891 in favor of this solution would really be an incredible loss. The possibility of writing a single export esm module that still works in a cjs context (even with caveats) that doesn't prescribe significantly rewriting the cjs side would provide massive value to the mountain of Node.js code in existence in addition to new code being written that still has a high likelihood of interacting with older cjs codebases (by not requiring the high maintenance cost of transforming and exporting two separate code bases, and allowing for the consumption of other ESM only modules without violating module boundaries as dual export requires in that scenario). Unless @weswigham, who graciously provided a working implementation of sync loading esm in cjs, is giving up on the problem, I am not giving up on it either. @MylesBorins Because I'm criticizing this change, I would also clarify I'm appreciative of your time and energy overall and this criticism is limited to the idea. |
The loaders WG is actively talking about it still but are doing other refactors first. I've made 2 PRs in the past and am not actively working on a new one but would still think this is the only viable path given I'm also adding |
Yes . . . I think really what users want is |
So it seems to me that the general sentiment is:
I think with all of the above it might make sense to abandon this change today and re-explore prior to Node.js 18 if we want to really do this. Thoughts? |
This PR changes the behavior when someone attempts to require and ESM module.
Rather than throwing, which we currently do, the module that was attempted to be required will be proxied to dynamic import and require will return the promise to resolve the module (the same behavior as dynamic import).
This works for both packages and standalone files.
This is an alternative to #30891
One thing worth mentioning is that it would appear that the majority of folks that are wrapping this error right now are doing so for the exact behavior that this PR implements
https://github.com/search?q=err.code+%3D%3D+ERR_REQUIRE_ESM&type=code
There are currently no docs, assuming we are open to landing this PR I'll update documentation