-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
module: ensure successful dynamic import returns the same result #46662
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
Changes from 1 commit
0f1fe00
3bf7e02
9f36bf7
aabe393
be69edd
dd06864
631d7c6
c279d18
56a8a07
abf105a
00ea14c
a791dc9
3638858
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,60 +5,115 @@ const { | |
| ArrayPrototypeMap, | ||
| ArrayPrototypeSort, | ||
| JSONStringify, | ||
| ObjectDefineProperty, | ||
| ObjectKeys, | ||
| SafeMap, | ||
| } = primordials; | ||
| const { kImplicitAssertType } = require('internal/modules/esm/assert'); | ||
| const { setOwnProperty } = require('internal/util'); | ||
| let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { | ||
| debug = fn; | ||
| }); | ||
| const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes; | ||
| const { validateString } = require('internal/validators'); | ||
|
|
||
| class ModuleResolveMap extends SafeMap { | ||
| /** | ||
| * Cache the results of the `resolve` step of the module resolution and loading process. | ||
|
aduh95 marked this conversation as resolved.
|
||
| * Future resolutions of the same input (specifier, parent URL and import assertions) | ||
| * must return the same result if the first attempt was successful, per | ||
| * https://tc39.es/ecma262/#sec-HostLoadImportedModule. | ||
| */ | ||
|
aduh95 marked this conversation as resolved.
|
||
| class ResolveCache extends SafeMap { | ||
| constructor(i) { super(i); } // eslint-disable-line no-useless-constructor | ||
|
|
||
| /** | ||
| * Generates the internal serialized cache key and returns it along the actual cache object. | ||
| * | ||
| * It is exposed to allow more efficient read and overwrite a cache entry. | ||
| * @param {string} specifier | ||
| * @param {string} parentURL | ||
| * @param {Record<string,string>} importAssertions | ||
| * @returns {{ | ||
| * serializedModuleRequest: string, | ||
| * internalCache: Record<string, Promise<import('./loader').ModuleExports> | import('./loader').ModuleExports>, | ||
|
Contributor
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 might be worth entirely separating the serialization process from the lookup process? Something like: const requestKey = resolveCache.serializeKey(specifier, attributes);
const absoluteResolvedURL = await resolve(specifier, parentURL, attributes);
const resolvedKey = resolveCache.serializeKey(absoluteResolvedURL, attributes);
resolveCache.set(requestKey, parentKey, resolvedKey);Ideally we should also be using Does that make sense? Let me know if I can clarify any of the above.
Contributor
Author
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.
That's a good remark, I didn't consider this. I don't think we have access to the
That's probably a good idea. The current implementation is probably not going to stay as Jacob is trying to refactor all that is his PR, so I'm hesitant on spending too much time crafting an implementation that we want to get rid of anyway. What I'm mostly interested in is to get the tests to land, so we can refactor with more confidence.
Member
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. What is |
||
| * }} | ||
| */ | ||
| getSerialized(specifier, parentURL, importAssertions) { | ||
|
aduh95 marked this conversation as resolved.
Outdated
|
||
| let cache = super.get(parentURL); | ||
| let specifierCache; | ||
| if (cache == null) { | ||
| super.set(parentURL, cache = new SafeMap()); | ||
| } else { | ||
| specifierCache = cache.get(specifier); | ||
| let internalCache = super.get(parentURL); | ||
| if (internalCache == null) { | ||
| super.set(parentURL, internalCache = { __proto__: null }); | ||
| } | ||
|
|
||
| if (specifierCache == null) { | ||
| cache.set(specifier, specifierCache = { __proto__: null }); | ||
| } | ||
|
|
||
| const serializedAttributes = ArrayPrototypeJoin( | ||
| // To serialize the ModuleRequest (specifier + list of import attributes), | ||
| // we need to sort the attributes by key, then stringifying, | ||
| // so that different import statements with the same attributes are always treated | ||
|
aduh95 marked this conversation as resolved.
Outdated
|
||
| // as identical. | ||
|
aduh95 marked this conversation as resolved.
Outdated
|
||
| const serializedModuleRequest = specifier + ArrayPrototypeJoin( | ||
| ArrayPrototypeMap( | ||
| ArrayPrototypeSort(ObjectKeys(importAssertions)), | ||
| (key) => JSONStringify(key) + JSONStringify(importAssertions[key])), | ||
| ','); | ||
|
aduh95 marked this conversation as resolved.
|
||
|
|
||
| return { specifierCache, serializedAttributes }; | ||
| return { internalCache, serializedModuleRequest }; | ||
| } | ||
|
|
||
| /** | ||
| * @param {string} specifier | ||
| * @param {string} parentURL | ||
| * @param {Record<string, string>} importAttributes | ||
| * @returns {import('./loader').ModuleExports | Promise<import('./loader').ModuleExports>} | ||
| */ | ||
| get(specifier, parentURL, importAttributes) { | ||
|
Member
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. Let’s please not mix and match
Contributor
Author
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'm using the updated spec though, this is following what import attributes are, not import assertions. The inconsistency is regretable, but it would still be inconsistent to call the variable
Member
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. What would be different here if you weren't following the new spec? |
||
| const { specifierCache, serializedAttributes } = this.getSerialized(specifier, parentURL, importAttributes); | ||
| return specifierCache[serializedAttributes]; | ||
| const { internalCache, serializedModuleRequest } = this.getSerialized(specifier, parentURL, importAttributes); | ||
| return internalCache[serializedModuleRequest]; | ||
| } | ||
|
|
||
| /** | ||
| * @param {string} specifier | ||
| * @param {string} parentURL | ||
| * @param {Record<string, string>} importAttributes | ||
| * @param {import('./loader').ModuleExports | Promise<import('./loader').ModuleExports>} job | ||
| */ | ||
| set(specifier, parentURL, importAttributes, job) { | ||
| const { specifierCache, serializedAttributes } = this.getSerialized(specifier, parentURL, importAttributes); | ||
| specifierCache[serializedAttributes] = job; | ||
| const { internalCache, serializedModuleRequest } = this.getSerialized(specifier, parentURL, importAttributes); | ||
| internalCache[serializedModuleRequest] = job; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Adds a cache entry that won't be evaluated until the first time someone tries to read it. | ||
| * | ||
| * Static imports need to be lazily cached as the link step is done before the | ||
| * module exports are actually available. | ||
| * @param {string} specifier | ||
| * @param {string} parentURL | ||
| * @param {Record<string, string>} importAttributes | ||
| * @param {() => import('./loader').ModuleExports} getJob | ||
| */ | ||
| setLazy(specifier, parentURL, importAttributes, getJob) { | ||
| const { internalCache, serializedModuleRequest } = this.getSerialized(specifier, parentURL, importAttributes); | ||
| ObjectDefineProperty(internalCache, serializedModuleRequest, { | ||
| __proto__: null, | ||
| configurable: true, | ||
| get() { | ||
| const val = getJob(); | ||
|
Contributor
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 should be possible to avoid this indirection by having a double-level cache - firstly making this explicitly a resolve cache ( The global registry itself likely will want to be keyed by the serialized absolute module anyway too in future. |
||
| setOwnProperty(internalCache, serializedModuleRequest, val); | ||
| return val; | ||
| }, | ||
| }); | ||
| return this; | ||
| } | ||
|
|
||
| has(specifier, parentURL, importAttributes) { | ||
| const { specifierCache, serializedAttributes } = this.getSerialized(specifier, parentURL, importAttributes); | ||
| return serializedAttributes in specifierCache; | ||
| const { internalCache, serializedModuleRequest } = this.getSerialized(specifier, parentURL, importAttributes); | ||
| return serializedModuleRequest in internalCache; | ||
| } | ||
| } | ||
|
|
||
| // Tracks the state of the loader-level module cache | ||
| class ModuleLoadMap extends SafeMap { | ||
| /** | ||
| * Cache the results of the `load` step of the module resolution and loading process. | ||
| */ | ||
| class LoadCache extends SafeMap { | ||
| constructor(i) { super(i); } // eslint-disable-line no-useless-constructor | ||
| get(url, type = kImplicitAssertType) { | ||
| validateString(url, 'url'); | ||
|
|
@@ -89,6 +144,6 @@ class ModuleLoadMap extends SafeMap { | |
| } | ||
|
|
||
| module.exports = { | ||
| ModuleLoadMap, | ||
| ModuleResolveMap, | ||
| LoadCache, | ||
| ResolveCache, | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.