-
Notifications
You must be signed in to change notification settings - Fork 31.2k
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
esm: syncify default path of ModuleLoader.load
#57419
base: main
Are you sure you want to change the base?
esm: syncify default path of ModuleLoader.load
#57419
Conversation
Review requested:
|
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #57419 +/- ##
==========================================
- Coverage 90.23% 90.21% -0.02%
==========================================
Files 630 630
Lines 185074 185502 +428
Branches 36221 36379 +158
==========================================
+ Hits 166994 167350 +356
Misses 11036 11036
- Partials 7044 7116 +72
🚀 New features to boost your workflow:
|
I investigated a bit on the weird failure and apparently there is some scheduling issue. |
Sweet, thanks! I was gonna take a look this evening. I'll apply it as soon as I finish work 🙂 |
0bcfae3
to
e7941fe
Compare
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 test's setup is the actual issue (I'm not sure why it was ever working 🤔):
global-hooks.js
contains a non-await
ed dynamic import at the root:import('node:test').then((test) => …)
, which left a dangling promise (so node can't guarantee the global file has finished executing before starting with the entry-point). It was probably done that way because TLA is not available in CJS, and the file is shared by both test cases.- The global hooks setup file
--import
/--require
'd itself is indeed run before the test files (addedconsole.log('\n==global hooks==\n')
at line 1, and it prints first).
I suspect it was set up this way in order to re-use for both cjs and esm. @avivkeller it looks like you authored this test ~6 months ago. Would you perchance remember whether there was another reason? I can't think of one, but if there is, my change could inadvertently cause something to no-longer be covered.
Splitting global-hooks.js
into global-hooks.cjs
+ require & global-hooks.mjs
+ static import, the sequence issue appears to be fixed.
However, a few of the global test hooks are now just not printing at all for the --require
case (red lines are missing in actual vs expected):
before(): global
before one: <root>
suite one
before two: <root>
suite two
- beforeEach(): global
beforeEach one: suite one - test
beforeEach two: suite one - test
suite one - test
- afterEach(): global
afterEach one: suite one - test
afterEach two: suite one - test
before suite two: suite two
- beforeEach(): global
beforeEach one: suite two - test
beforeEach two: suite two - test
suite two - test
- afterEach(): global
afterEach one: suite two - test
afterEach two: suite two - test
- after(): global
after one: <root>
after two: <root>
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.
Sorry I didn't see this earlier!
I suspect it was set up this way in order to re-use for both cjs and esm.
Exactly—using a single test file for both ESM and CJS helped keep things simpler, as I could simply flip-flop the --import
vs --require
call.
That’s definitely an odd issue you’re encountering. I have a few theories (though no concrete evidence to back them up):
- It’s possible that an error occurs in the chain for all but
before
, though that seems unlikely. - Another possibility is a race condition. Could it be that
before
runs successfully simply because it’s the first one executed in the file?
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.
Thanks!
Race condition seems the most likely candidate since this PR is affecting a/sync behaviour. Strange that only this test is failing though—I would have expected a bunch of failures.
PTAL @nodejs/test_runner We're encountering an issue where a global hook script loaded via @JakobJingleheimer and I investigated the root cause, but since I haven’t worked on the test runner in a while, we could use some help. We've determined that the issue stems from the test runner's loading of CJS files. When modifying the following code in const userImports = getOptionValue('--import').concat(getOptionValue('--require'));
for (let i = 0; i < userImports.length; i++) {
await cascadedLoader.import(userImports[i], parentURL, kEmptyObject);
} (This would need primordials, but we are only testing right now) If I run a test with a
This suggests that the initial CJS load (which we haven’t yet pinpointed) runs and executes only the first We need help identifying where the CJS load originally happens and whether we can remove it in favor of the snippet above or modify it to use the ESM loader. |
thanks @avivkeller! The issue actually is actually present is a simpler change (merely the dangling promise fix) #57595 So that means it's unrelated to this change 😁 Let's move the discussion over to that 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.
lgtm
Co-Authored-By: Marco Ippolito <[email protected]>
a2d0fcc
to
f60650f
Compare
Failed to start CI⚠ Commits were pushed since the last approving review: ⚠ - esm: syncify default path of `ModuleLoader.load` ⚠ - !fixup: don't force `ModuleLoader.load` to be async (can via hooks) ⚠ - !fixup: remove obsolete `getSource` in favour of `getSourceSync` ⚠ - !fixup: account for `node:internal` in `isMain` check ✘ Refusing to run CI on potentially unsafe PRhttps://github.com/nodejs/node/actions/runs/14236594120 |
lib/internal/modules/esm/resolve.js
Outdated
@@ -965,7 +965,7 @@ function defaultResolve(specifier, context = {}) { | |||
if (protocol === 'node:') { return { __proto__: null, url: specifier }; } | |||
|
|||
|
|||
const isMain = parentURL === undefined; | |||
const isMain = parentURL === undefined || StringPrototypeStartsWith(parentURL, 'node:internal/'); |
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.
Could you add a comment explaining why we want isMain
to be true for internal modules?
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 came from here: #57419 (comment)
TLDR: we don't know.
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.
What is the weird failure exactly? This addition does look strange, or could be painting over a bug...
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
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.
Ahh, this looks familiar.
https://github.com/nodejs/node/actions/runs/14280143011/job/40028680516?pr=57419#step:11:5939
Path: es-module/test-vm-main-context-default-loader
Error: --- stderr ---
(node:66638) ExperimentalWarning: vm.USE_MAIN_CONTEXT_DEFAULT_LOADER is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
node:internal/process/promises:394
triggerUncaughtException(err, true /* fromPromise */);
^
AssertionError [ERR_ASSERTION]: function should not have been called at /Users/runner/work/node/node/node/test/es-module/test-vm-main-context-default-loader.js:146
called with arguments: AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
Comparison {
+ code: 'ERR_UNSUPPORTED_RESOLVE_REQUEST'
- code: 'ERR_MODULE_NOT_FOUND'
}
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
at async main (/Users/runner/work/node/node/node/test/es-module/test-vm-main-context-default-loader.js:124:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [TypeError],
expected: [Object],
operator: 'rejects'
}
at mustNotCall (/Users/runner/work/node/node/node/test/common/index.js:493:12)
at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: undefined,
operator: 'fail'
}
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.
@joyeecheung does this look like anything to you? I'm not familiar with the VM module.
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 tried to look into this a bit, but the test is written for someone with deep understanding of what is happening, and I unfortunately don't. And there is no stack-trace to follow.
node/test/es-module/test-vm-main-context-default-loader.js
Lines 115 to 125 in 86bfdb5
// createContext() with null referrer also resolves to cwd. | |
{ | |
const options = { | |
importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER, | |
}; | |
const ctx = createContext({}, options); | |
const s = new Script('Promise.resolve("import(\'./message.mjs\')").then(eval)', { | |
importModuleDynamically: common.mustNotCall(), | |
}); | |
await assert.rejects(s.runInContext(ctx), { code: 'ERR_MODULE_NOT_FOUND' }); | |
} |
If there is a bug being papered over, I'm pretty sure it is not caused by this change (but is perhaps uncovered by it). In which case, it should be fixed in a different PR; and since it already exists, it shouldn't be a blocker here.
This reverts commit f60650f.
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.
lgtm
context = { __proto__: context, source }; | ||
} | ||
|
||
if (format == null) { | ||
// Now that we have the source for the module, run `defaultGetFormat` to detect its format. | ||
format = await defaultGetFormat(urlInstance, context); | ||
format = defaultGetFormat(urlInstance, context); | ||
|
||
if (format === '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.
I guess it might happen from the branch here and the "reset source to null and then resolve twice - the second time resolving a full path - if it's commonjs" quirk of the sync loading path. Which might mean that this sync version may not be used as-is without getting rid of that quirk or patched here somehow to not trigger that quirk.
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.
🤔 what makes you think it's that quirk? and why is it only exposed when going through that specific VM scenario?
In #57390 I somehow inadvertently created a branch on the node repo instead of my fork, and then got stuck.
Parent issue: #55782