module: fix extensionless CJS files in type:module packages#62083
module: fix extensionless CJS files in type:module packages#62083mcollina wants to merge 2 commits intonodejs:mainfrom
Conversation
|
Review requested:
|
PR nodejs#61600 made the CJS loader respect the package.json type field for extensionless files, but also enforced type: "module" which breaks CJS extensionless files inside ESM packages (e.g. yargs v17). Only enforce type: "commonjs" for extensionless files. For type: "module", leave format undefined so V8's syntax detection determines the correct format, allowing CJS extensionless files in ESM packages to continue working. Fixes: nodejs#61971 Refs: yargs/yargs#2509
3e6aa5a to
6e3ed67
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #62083 +/- ##
=======================================
Coverage 89.64% 89.65%
=======================================
Files 676 676
Lines 206249 206326 +77
Branches 39518 39527 +9
=======================================
+ Hits 184892 184980 +88
+ Misses 13479 13471 -8
+ Partials 7878 7875 -3
🚀 New features to boost your workflow:
|
joyeecheung
left a comment
There was a problem hiding this comment.
Code LGTM with a correction on comment
Co-authored-by: Joyee Cheung <joyeec9h3@gmail.com>
GeoffreyBooth
left a comment
There was a problem hiding this comment.
- PR module: fix extensionless entry with explicit type=commonjs #61600 made the CJS loader respect
package.jsontypefield for extensionless files, but also enforcedtype: "module"which breaks CJS extensionless files inside ESM packages (e.g. yargs v17)- Only enforce
type: "commonjs"for extensionless files; fortype: "module", leave format undefined so V8's syntax detection determines the correct format
I think this proposed behavior is incorrect; according to our spec, extensionless files within a package scope with an explicit type field follow the format of the type field—that is, they behave however .js files would behave. We never do syntax detection when there's an explicit type field.
I read yargs/yargs#2509 and it's unfortunate that they seemed to rely on what was buggy behavior. But this PR seems to make several breaking changes, going against our own documented behaviors for how the type field works and how syntax detection works. I'm not sure what to do instead; perhaps just revert #61600 and re-land it as semver-major?
Marking as request changes so that we discuss this before landing. If others disagree with my analysis here I'm happy to be persuaded I'm wrong; I understand there's probably an eagerness to unbreak a popular library but let's not be so hasty that we cause other bugs.
Can you list them? I don't see what would be breaking about it |
| // Extensionless files skip the .js suffix check above. When type is commonjs, | ||
| // follow it so ESM syntax surfaces as SyntaxError instead of silently | ||
| // delegating to ESM. For type: module, leave format undefined so our syntax | ||
| // detection handles it (allowing CJS extensionless files in ESM packages). |
There was a problem hiding this comment.
fwiw this seems right to me; i remain surprised that extensionless ESM was ever possible, and in all the module group discussions, i thought we concluded it basically wouldn't ever be (unless the "type" default changed)
There was a problem hiding this comment.
This is not recent, it has been possible since v20, so it would be too late to do anything about it.
|
@GeoffreyBooth I didn't put much thought into the spec for this. My main concern was to fix this without having to revert that commit. I'm happy either way. The alternative would be to revert that commit from 25.x and land it after the problematic behavior is fixed in the ecosystem. We should be doing something soon about this. |
This is a case where we have contradicting documentations: while in the esm part of the documentation it says the module format always respect the type field for extension files, in the cjs part of the documentation it has been including this exception that's not present in the spec, as far back as v16 from #41383 - so yargs is technically following a documented exception, the problem is more on the Node.js side that it has conflicting documentation.
#61600 made the extensionless behavior match what's in the ESM spec (so reverting it is widening this exception to ESM, not eliminating it) without counting this exception in the CJS documentation, and this PR only adds that CJS exception back. I agree the saner behavior would be to respect type field always (this is the status quo after #61600 fixed the mismatch), and remove this exception from the CJS documentation, but that should be a semver-major. For existing releases, the best we can do IMO is to combine #61600 and this PR to reduce the exception surface without introducing a regression. |
| // Extensionless files skip the .js suffix check above. When type is commonjs, | ||
| // follow it so ESM syntax surfaces as SyntaxError instead of silently | ||
| // delegating to ESM. For type: module, leave format undefined so our syntax |
There was a problem hiding this comment.
Why would type: commonjs "silently delegate to ESM"? Surely that should be a loud syntax detection case with a warning?
There was a problem hiding this comment.
Surely that should be a loud syntax detection case with a warning?
I think that might be missed in the syntax detection implementation to add a warning in this path, so it never emitted a warning in this case, and the comment was just describing the previous behavior - it should probably be removed because we generally don't talk about "what a previous bug looked like" in comments, only in commit logs.
| // Extensionless files skip the .js suffix check above. When type is commonjs, | |
| // follow it so ESM syntax surfaces as SyntaxError instead of silently | |
| // delegating to ESM. For type: module, leave format undefined so our syntax | |
| // Extensionless files skip the .js suffix check above. When type is commonjs, follow it so ESM | |
| // syntax surfaces as SyntaxError. For type: module, leave format undefined so our syntax |
| if (typeFromPjson === 'commonjs') { | ||
| format = typeFromPjson; | ||
| } |
There was a problem hiding this comment.
Would it be possible to have extensionless files entirely ignore the package.json "type"? That seems like a more pragmatic approach given that they are designed to exist outside of package boundaries in most "binary execution" use cases.
Summary
package.jsontypefield for extensionless files, but also enforcedtype: "module"which breaks CJS extensionless files inside ESM packages (e.g. yargs v17)type: "commonjs"for extensionless files; fortype: "module", leave format undefined so V8's syntax detection determines the correct formatrequire()s a CJS extensionless file from atype: "module"package and verifies exports are correctTest plan
test/es-module/test-extensionless-esm-type-commonjs.jsstill passes (ESM syntax intype: "commonjs"still throws SyntaxError)type: "module"package returns correct exports viarequire()require()of fixture returns{ hello: 'world' }(was returning empty ESM namespace before fix)Fixes: #61971
Refs: yargs/yargs#2509
Refs: #61600