-
Notifications
You must be signed in to change notification settings - Fork 30k
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: unflag import assertions #39921
Changes from all commits
8475f0e
ece7a7d
8754a41
debc47f
733deb8
4fa6f58
1e6330a
1ecb35a
180099e
83ff00e
b5ad8b0
d364844
d19ecc4
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 |
---|---|---|
|
@@ -6,11 +6,13 @@ require('internal/modules/cjs/loader'); | |
const { | ||
Array, | ||
ArrayIsArray, | ||
ArrayPrototypeIncludes, | ||
ArrayPrototypeJoin, | ||
ArrayPrototypePush, | ||
FunctionPrototypeBind, | ||
FunctionPrototypeCall, | ||
ObjectCreate, | ||
ObjectFreeze, | ||
ObjectSetPrototypeOf, | ||
PromiseAll, | ||
RegExpPrototypeExec, | ||
|
@@ -20,8 +22,10 @@ const { | |
} = primordials; | ||
|
||
const { | ||
ERR_FAILED_IMPORT_ASSERTION, | ||
ERR_INVALID_ARG_TYPE, | ||
ERR_INVALID_ARG_VALUE, | ||
ERR_INVALID_IMPORT_ASSERTION, | ||
ERR_INVALID_MODULE_SPECIFIER, | ||
ERR_INVALID_RETURN_PROPERTY_VALUE, | ||
ERR_INVALID_RETURN_VALUE, | ||
|
@@ -44,6 +48,10 @@ const { translators } = require( | |
'internal/modules/esm/translators'); | ||
const { getOptionValue } = require('internal/options'); | ||
|
||
const importAssertionTypeCache = new SafeWeakMap(); | ||
const finalFormatCache = new SafeWeakMap(); | ||
const supportedTypes = ObjectFreeze([undefined, 'json']); | ||
|
||
/** | ||
* An ESMLoader instance is used as the main entry point for loading ES modules. | ||
* Currently, this is a singleton -- there is only one used for loading | ||
|
@@ -202,33 +210,66 @@ class ESMLoader { | |
const { ModuleWrap, callbackMap } = internalBinding('module_wrap'); | ||
const module = new ModuleWrap(url, undefined, source, 0, 0); | ||
callbackMap.set(module, { | ||
importModuleDynamically: (specifier, { url }) => { | ||
return this.import(specifier, url); | ||
importModuleDynamically: (specifier, { url }, import_assertions) => { | ||
return this.import(specifier, url, import_assertions); | ||
} | ||
}); | ||
|
||
return module; | ||
}; | ||
const job = new ModuleJob(this, url, evalInstance, false, false); | ||
this.moduleMap.set(url, job); | ||
finalFormatCache.set(job, 'module'); | ||
const { module } = await job.run(); | ||
|
||
return { | ||
namespace: module.getNamespace(), | ||
}; | ||
} | ||
|
||
async getModuleJob(specifier, parentURL) { | ||
async getModuleJob(specifier, parentURL, import_assertions) { | ||
if (!ArrayPrototypeIncludes(supportedTypes, import_assertions.type)) { | ||
throw new ERR_INVALID_IMPORT_ASSERTION('type', import_assertions.type); | ||
} | ||
|
||
const { format, url } = await this.resolve(specifier, parentURL); | ||
let job = this.moduleMap.get(url); | ||
// CommonJS will set functions for lazy job evaluation. | ||
if (typeof job === 'function') this.moduleMap.set(url, job = job()); | ||
|
||
if (job !== undefined) return job; | ||
if (job != null) { | ||
const currentImportAssertionType = importAssertionTypeCache.get(job); | ||
if (currentImportAssertionType === import_assertions.type) return job; | ||
|
||
try { | ||
// To avoid race conditions, wait for previous module to fulfill first. | ||
await job.modulePromise; | ||
aduh95 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} catch { | ||
// If the other job failed with a different `type` assertion, we got | ||
// another chance. | ||
job = undefined; | ||
} | ||
|
||
if (job !== undefined) { | ||
const finalFormat = finalFormatCache.get(job); | ||
if ( | ||
import_assertions.type == null || | ||
(import_assertions.type === 'json' && finalFormat === 'json') | ||
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. shouldn't this always check if the finalFormat is 'json' and not skip it if it is nullish? 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. If
we still want non-JSON modules to load (such as 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. If
we still want non-JSON modules to load (such as 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. Oh I think I get what you mean, indeed because 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 problem is that we have 2 cache entries coalescing to the same module job and that makes me trying to figure out how a userland loader could recreate that behavior very complicated and I am definitely not confident in that collision being safe even with the mitigation I mentioned and requested above. currently we always have 1-1 fully resolved cache key to module instance. The concern is the many-1 model that we introduced by coalescing. |
||
) return job; | ||
throw new ERR_FAILED_IMPORT_ASSERTION( | ||
url, 'type', import_assertions.type, finalFormat); | ||
} | ||
} | ||
|
||
const moduleProvider = async (url, isMain) => { | ||
const { format: finalFormat, source } = await this.load(url, { format }); | ||
|
||
if (import_assertions.type === 'json' && finalFormat !== 'json') { | ||
throw new ERR_FAILED_IMPORT_ASSERTION( | ||
url, 'type', import_assertions.type, finalFormat); | ||
} | ||
finalFormatCache.set(job, finalFormat); | ||
|
||
const translator = translators.get(finalFormat); | ||
|
||
if (!translator) throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat); | ||
|
@@ -249,6 +290,7 @@ class ESMLoader { | |
inspectBrk | ||
); | ||
|
||
importAssertionTypeCache.set(job, import_assertions.type); | ||
this.moduleMap.set(url, job); | ||
|
||
return job; | ||
|
@@ -262,18 +304,19 @@ class ESMLoader { | |
* loader module. | ||
* | ||
* @param {string | string[]} specifiers Path(s) to the module | ||
* @param {string} [parentURL] Path of the parent importing the module | ||
* @returns {object | object[]} A list of module export(s) | ||
* @param {string} parentURL Path of the parent importing the module | ||
* @param {Record<string, Record<string, string>>} import_assertions | ||
* @returns {Promise<object | object[]>} A list of module export(s) | ||
*/ | ||
async import(specifiers, parentURL) { | ||
async import(specifiers, parentURL, import_assertions) { | ||
const wasArr = ArrayIsArray(specifiers); | ||
if (!wasArr) specifiers = [specifiers]; | ||
|
||
const count = specifiers.length; | ||
const jobs = new Array(count); | ||
|
||
for (let i = 0; i < count; i++) { | ||
jobs[i] = this.getModuleJob(specifiers[i], parentURL) | ||
jobs[i] = this.getModuleJob(specifiers[i], parentURL, import_assertions) | ||
.then((job) => job.run()) | ||
.then(({ module }) => module.getNamespace()); | ||
} | ||
|
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.
Shouldn’t
import_assertions
be camelcased everywhere it appears?