-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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: clarify CJS global-like variables not defined error message #37852
Conversation
b732c67
to
5a26936
Compare
I've added detection of other global-like CJS variables ( |
'. It seems you are trying to load a file using `.js` extension ' + | ||
'inside a folder containing a `package.json` that includes ' + | ||
'`"type": "module"`; you need to use the `.cjs` extension to ' + | ||
'load the file as a CommonJS module, or add a `package.json` ' + | ||
'file with ``"type": "commonjs"`.'; |
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.
This is way too long and reminds me of clippy. Something more short and to the point would be better:
'. It seems you are trying to load a file using `.js` extension ' + | |
'inside a folder containing a `package.json` that includes ' + | |
'`"type": "module"`; you need to use the `.cjs` extension to ' + | |
'load the file as a CommonJS module, or add a `package.json` ' + | |
'file with ``"type": "commonjs"`.'; | |
'CommonJS files can be loaded using the package.json type option or .cjs' |
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.
Note that it's not much longer than the error message when using named import with CJS:
node/lib/internal/modules/esm/module_job.js
Lines 128 to 134 in 5318e53
e.message = `Named export '${name}' not found. The requested module` + | |
` '${childSpecifier}' is a CommonJS module, which may not support` + | |
' all module.exports as named exports.\nCommonJS modules can ' + | |
'always be imported via the default export, for example using:' + | |
`\n\nimport pkg from '${childSpecifier}';\n${ | |
destructuringAssignment ? | |
`const ${destructuringAssignment} = pkg;\n` : ''}`; |
In the linked issue, we have a user reporting they got such error without realising they were under a "module"
package scope, having a very explicit error message would help I think. (and also everyone loved Clippy anyway, right? right? 🙃). Anyway, if the consensus is to have a shorter message, I'll take your suggestion.
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 was just about to say, we should make this similar to the “named export” error message, which includes the path to the controlling package.json
file and things like that. An error message that includes the context to explain what’s going on seems worth the added length. Something like:
your-file-here.js cannot be parsed as an ES module, but it is being treated as an ES module
because it has a .js extension and /Users/devsnek/projects/foo/package.json contains
"type": "module". To treat it as a CommonJS file, rename it to your-file-here.cjs.
I’m not sure we want to recommend changing the relevant "type"
value, since that would likely affect other files that might not be CommonJS. We could also do without the second sentence in my example above, though then we’re leaving the user to Google the error message to figure out what to do to fix this. (The other potential solution that comes to mind is to refactor the offending file into ESM, but that seems a bit much for an error message to suggest.)
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.
Alternatively, we could link to the docs rather than including the second sentence, and the docs are able to be much more precise without having to be quite so brief.
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 don't think we do link to our docs in any error message so far, maybe there's a good reason for not doing it? If there's none, that seems ideal indeed.
if (e.name === 'ReferenceError' && | ||
StringPrototypeEndsWith(e.message, ' is not defined') && | ||
isCommonJSGlobalLikeNotDefinedError(e.message)) { | ||
e.message += ' in ES module scope'; |
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.
more than one of these if statements can be true at the same time. we should ensure every combination is a valid sentence or sentences.
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.
Well I think it is already, isn't it? Do you want me to add a test with all conditions being true at the same time?
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.
Instead of in ES module scope
perhaps we can just say in the ES module ${url}
.
Do you want me to add a test with all conditions being true at the same time?
It sounds to me like that is the request here.
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.
we can just say
in the ES module ${url}
.
The url
is already provided in the first line of the stack trace, is it useful to repeat it here?
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.
Good poin - if we definitely have an error frame for all of these then we should work to that.
@devsnek Are you still -1 on the latest version of this PR? |
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.
This is a great one, thank you!
if (e.name === 'ReferenceError' && | ||
StringPrototypeEndsWith(e.message, ' is not defined') && | ||
isCommonJSGlobalLikeNotDefinedError(e.message)) { | ||
e.message += ' in ES module scope'; |
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.
Instead of in ES module scope
perhaps we can just say in the ES module ${url}
.
Do you want me to add a test with all conditions being true at the same time?
It sounds to me like that is the request here.
e.message += ' in ES module scope'; | ||
|
||
if (StringPrototypeStartsWith(e.message, 'require ')) { | ||
e.message += ', you can use import instead'; |
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.
Perhaps - Use "import" instead or
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.
Is your comment incomplete or? 😅
For reference, the e.message
at this point is ReferenceError: require is not defined in ES module scope
.
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.
Was working to ReferenceError: require is not defined in the ES module ${url}
per the previous comment.
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.
The or
was supposed to compose with the .cjs
extension suggestion, but we could keep that at the end too.
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.
Oh I see! Note that the second part is printed out only if the module is .js
, we need to take into account the case where the module is using a different extension or is a data:
URL.
I came across something really annoying yesterday where I had to use |
@devsnek that's a good point, but I don't think now is the time rework long error messages in Node.js that provide context. Rather that should ideally be a separate PR process. |
@guybedford sure, I'm saying at the very least, hold off on landing this until we figure that out. I'm personally willing to put in that effort (this weekend perhaps). |
@devsnek that could work, can we at least set a rough time frame on such a hold to ensure this doesn't go stale? |
We could use the |
@devsnek would it be OK to land this now?
BTW if you want/need help on that, please reach out. I agree with Jordan, adding a |
I'm planning to land this in a few days if no one objects. |
Fixes: nodejs#33741 PR-URL: nodejs#37852 Reviewed-By: Guy Bedford <guybedford@gmail.com>
46bdb40
to
7b2bad4
Compare
Landed in 7b2bad4 |
Error message on Node.js v15.x:
Error message with this PR:
@nodejs/modules I'm not sure we should keep the
.cjs
advice because it doesn't always apply – sometimes there are no files (E.G.:data:
URL), and this behaviour could be overridden by a custom loader. On the other hand, for a user stumbling over this issue, the suggestion could be helpful. wdyt?Fixes: #33741
In the issue, it's also been discussed to add a message explaining why the file was parsed as ESM rather than CJS, but I couldn't find how to do that.EDIT: When the module is a file with
.js
extension, the following error message is shown: