-
Notifications
You must be signed in to change notification settings - Fork 29.4k
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: add releaseLoadedModule
#50618
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -223,6 +223,25 @@ class ModuleLoader { | |
}; | ||
} | ||
|
||
/** | ||
* Evict saved result of `resolve` and `load` for the given import parameters. | ||
*/ | ||
evict(specifier, parentURL, importAttributes = {}) { | ||
let resolved; | ||
try { | ||
resolved = this.resolveSync(specifier, parentURL, importAttributes); | ||
} catch { | ||
return false; | ||
} | ||
const requestKey = this.#resolveCache.serializeKey(specifier, importAttributes); | ||
let didEvict = this.#resolveCache.delete(requestKey, parentURL); | ||
if (this.loadCache.delete(resolved.url, importAttributes.type)) { | ||
// nb: Careful with short-circuits here, we want to run both deletes unconditionally. | ||
didEvict = true; | ||
} | ||
return didEvict; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we perhaps evict from the load cache first? I know these calls are sync, but the load hook is not; if This raises a broader question: do we need to delete from the resolve cache? What are the use cases for this API, and could they be fulfilled by deleting only from the load cache? As in, when would we need to redo the resolution? Perhaps after new module customization hooks were registered, that would affect resolution? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deleting the resolution is nice because otherwise updates to package.json cannot be hot loaded and would force an app restart. But deleting the resolve cache in the manner in my diff is definitely a half measure. I think a more reliable feature would need to record all the resolutions to a given loaded module, so that when the loaded module is evicted all resolutions to it are deleted as well. The abstract specification method |
||
} | ||
|
||
/** | ||
* Get a (possibly still pending) module job from the cache, | ||
* or create one and return its Promise. | ||
|
@@ -618,8 +637,17 @@ function register(specifier, parentURL = undefined, options) { | |
); | ||
} | ||
|
||
/** | ||
* Release saved results of `resolve` and `load` for the given import parameters. | ||
*/ | ||
function releaseLoadedModule(specifier, parentURL, importAssertions) { | ||
const moduleLoader = require('internal/process/esm_loader').esmLoader; | ||
return moduleLoader.evict(specifier, parentURL, importAssertions); | ||
} | ||
|
||
module.exports = { | ||
createModuleLoader, | ||
getHooksProxy, | ||
register, | ||
releaseLoadedModule, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,10 +2,11 @@ | |
|
||
const { findSourceMap } = require('internal/source_map/source_map_cache'); | ||
const { Module } = require('internal/modules/cjs/loader'); | ||
const { register } = require('internal/modules/esm/loader'); | ||
const { releaseLoadedModule, register } = require('internal/modules/esm/loader'); | ||
const { SourceMap } = require('internal/source_map/source_map'); | ||
|
||
Module.findSourceMap = findSourceMap; | ||
Module.register = register; | ||
Module.releaseLoadedModule = releaseLoadedModule; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This cannot be available to the main thread as its effects are forbidden by ECMAScript spec. The best we can do is to have it available only in the loader thread. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain? Is this just a way of saying that hooks need to be registered before users are allowed to use this? Why? How would this work from the other thread since the caches are on the main thread? Cross thread communication? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For reference, the relevant specification is https://tc39.es/ecma262/#sec-HostLoadImportedModule:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we're aware that this API explicitly violates spec if used. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It means that the spec gives some guarantees that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For instance, as an alternative, we could choose an approach where an explicitly opt-in command line flag is required to enable this kind of non-standard ESM behaviors.
This makes it absolutely clear that the individual running the node.js process is deciding on the non-standard behavior as opposed to some arbitrary library they are depending on. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don’t see the need for this. You might as well call the flag
The third option seems like the obvious choice, and now we’re back to the user not having explicitly opted into this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The built-in
Vite's HMR implementation is a client (browser)-side feature. For SSR you have to use dynohot implements There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Based on experience, I would wager very few users know what most of their dependencies are doing. More importantly, I'm not saying no to this, I'm saying I'd like us to make sure we're we're being careful to not introduce new headaches we might regret later. I guess what I'm missing is a higher level description of the use cases, how these various bits fit in with that, and a example that ties it all together. Has such a thing been written up? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
Module.SourceMap = SourceMap; | ||
module.exports = Module; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import '../common/index.mjs'; | ||
Check failure on line 1 in test/es-module/test-esm-evict.mjs GitHub Actions / test-asan
Check failure on line 1 in test/es-module/test-esm-evict.mjs GitHub Actions / test-linux
Check failure on line 1 in test/es-module/test-esm-evict.mjs GitHub Actions / test-macOS
|
||
import { strictEqual } from 'node:assert'; | ||
import { releaseLoadedModule } from 'node:module'; | ||
|
||
const specifier = 'data:application/javascript,export default globalThis.value;'; | ||
|
||
globalThis.value = 1; | ||
const instance1 = await import(specifier); | ||
strictEqual(instance1.default, 1); | ||
globalThis.value = 2; | ||
const instance2 = await import(specifier); | ||
strictEqual(instance2.default, 1); | ||
|
||
strictEqual(releaseLoadedModule(specifier, import.meta.url), true); | ||
strictEqual(releaseLoadedModule(specifier, import.meta.url), false); | ||
const instance3 = await import(specifier); | ||
strictEqual(instance3.default, 2); |
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.