@masknet/compartment
Advanced tools
Comparing version 0.4.2 to 0.5.0
@@ -6,2 +6,3 @@ export type { Binding, ImportBinding, ImportAllBinding, ExportBinding, ExportAllBinding, VirtualModuleRecord, VirtualModuleRecordExecuteContext, ModuleNamespace, ImportHook, ImportMetaHook, ModuleHandler, } from './types.js'; | ||
export { makeGlobalThis } from './utils/makeGlobalThis.js'; | ||
export { type CommonJSExportReflect, type CommonJSGlobalLexicals, type CommonJSHandler, type CommonJSInitialize, CommonJSModule, type RequireHook as CommonJSRequireHook, requires as commonjsRequires, } from './commonjs/runtime.js'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -6,5 +6,11 @@ import { ModuleSource } from './ModuleSource.js'; | ||
#private; | ||
/** | ||
* https://tc39.es/proposal-compartments/0-module-and-module-source.html#sec-module | ||
*/ | ||
constructor(moduleSource: ModuleSource<T> | VirtualModuleRecord, handler: ModuleHandler); | ||
/** | ||
* https://tc39.es/proposal-compartments/0-module-and-module-source.html#sec-module.prototype.source | ||
*/ | ||
get source(): ModuleSource | VirtualModuleRecord | null; | ||
} | ||
//# sourceMappingURL=Module.d.ts.map |
{ | ||
"name": "@masknet/compartment", | ||
"version": "0.4.2", | ||
"version": "0.5.0", | ||
"type": "module", | ||
@@ -8,2 +8,6 @@ "main": "./dist/index.js", | ||
"sideEffects": false, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/DimensionDev/aot-secure-ecmascript" | ||
}, | ||
"exports": { | ||
@@ -17,3 +21,3 @@ "types": "./dist/index.d.ts", | ||
"devDependencies": { | ||
"@swc/core": "^1.3.11" | ||
"@swc/core": "^1.3.53" | ||
}, | ||
@@ -20,0 +24,0 @@ "files": [ |
@@ -9,4 +9,3 @@ # @masknet/compartment | ||
- [ECMA262 Normative PR: Layering: Add HostLoadImportedModule hook](https://github.com/tc39/ecma262/pull/2905/) | ||
- [Module Block proposal](https://tc39.es/proposal-js-module-blocks/) | ||
- [Module Expressions proposal](https://tc39.es/proposal-module-expressions/) | ||
- [Compartment proposal: Layer 0: Module and ModuleSource constructor][layer-0] | ||
@@ -73,3 +72,3 @@ - [Compartment proposal: Layer 1: ModuleSource reflection][layer-1] | ||
} | ||
const module = new Evaluators.Module(virtualModule, import.meta.url, () => null) | ||
const module = new evaluators.Module(virtualModule, import.meta.url, () => null) | ||
const moduleNamespace = await imports(module) | ||
@@ -76,0 +75,0 @@ moduleNamespace.x // 42 |
@@ -75,3 +75,3 @@ import { Module, setParentGlobalThis, setParentImportHook, setParentImportMetaHook } from './Module.js' | ||
} | ||
#GetImportMeta(): ImportMeta | null { | ||
#GetImportMeta(): object | null { | ||
if (this.#AssignedImportMeta) return this.#AssignedImportMeta | ||
@@ -78,0 +78,0 @@ if (this.#ParentEvaluator) return this.#ParentEvaluator.#GetImportMeta() |
@@ -19,1 +19,11 @@ export type { | ||
export { makeGlobalThis } from './utils/makeGlobalThis.js' | ||
export { | ||
type CommonJSExportReflect, | ||
type CommonJSGlobalLexicals, | ||
type CommonJSHandler, | ||
type CommonJSInitialize, | ||
CommonJSModule, | ||
type RequireHook as CommonJSRequireHook, | ||
requires as commonjsRequires, | ||
} from './commonjs/runtime.js' |
1051
src/Module.ts
@@ -36,14 +36,15 @@ import { ModuleSource } from './ModuleSource.js' | ||
export class Module<T extends ModuleNamespace = any> { | ||
// The constructor is equivalent to ParseModule in SourceTextModuleRecord | ||
// https://tc39.es/ecma262/#sec-parsemodule | ||
/** | ||
* https://tc39.es/proposal-compartments/0-module-and-module-source.html#sec-module | ||
*/ | ||
constructor(moduleSource: ModuleSource<T> | VirtualModuleRecord, handler: ModuleHandler) { | ||
// Note: we act as CSP enabled, therefore no ModuleSource instance can be created. | ||
if (typeof moduleSource !== 'object') throw new TypeError('moduleSource must be an object') | ||
// impossible to create a ModuleSource instance | ||
if (moduleSource instanceof ModuleSource) assertFailed('ModuleSource instance cannot be created') | ||
// At this point, the only possible & valid module source is a VirtualModuleRecord. | ||
const module = normalizeVirtualModuleRecord(moduleSource) | ||
if (handler === null) throw new TypeError('handler must not be null') | ||
let importHook: ImportHook | undefined | ||
let importMetaHook: ImportMetaHook | undefined | ||
if (typeof handler === 'object') { | ||
if (typeof handler === 'object' && handler) { | ||
importHook = handler.importHook | ||
@@ -55,13 +56,16 @@ if (typeof importHook !== 'function' && importHook !== undefined) | ||
throw new TypeError('importMetaHook must be a function') | ||
} | ||
} else if (handler === undefined) { | ||
importHook = undefined | ||
importMetaHook = undefined | ||
} else throw new TypeError('handler must be an object or undefined') | ||
this.#VirtualModuleSource = moduleSource | ||
this.#Execute = module.execute | ||
this.#NeedsImport = module.needsImport | ||
this.#NeedsImportMeta = module.needsImportMeta | ||
this.#Layer2_VirtualModuleSource = moduleSource | ||
this.#Layer2_Execute = module.execute | ||
this.#Layer2_NeedsImport = module.needsImport | ||
this.#Layer2_NeedsImportMeta = module.needsImportMeta | ||
this.#HasTLA = !!module.isAsync | ||
this.#ImportHook = importHook | ||
this.#ImportMetaHook = importMetaHook | ||
this.#HandlerValue = handler | ||
this.#Layer0_ImportHook = importHook | ||
this.#Layer0_ImportMetaHook = importMetaHook | ||
this.#Layer0_HandlerValue = handler | ||
@@ -76,139 +80,83 @@ const { importEntries, indirectExportEntries, localExportEntries, requestedModules, starExportEntries } = | ||
} | ||
/** | ||
* https://tc39.es/proposal-compartments/0-module-and-module-source.html#sec-module.prototype.source | ||
*/ | ||
get source(): ModuleSource | VirtualModuleRecord | null { | ||
return this.#VirtualModuleSource | ||
return this.#Layer2_VirtualModuleSource as VirtualModuleRecord | ||
} | ||
//#region ModuleRecord fields https://tc39.es/ecma262/#table-module-record-fields | ||
/** first argument of execute() */ | ||
//#region ModuleRecord | ||
/** | ||
* The Environment Record containing the top level bindings for this module. | ||
* This field is set when the module is linked. | ||
* https://tc39.es/ecma262/#table-module-record-fields | ||
*/ | ||
#Environment: object | undefined | ||
/** result of await import(mod) */ | ||
/** | ||
* The Module Namespace Object (28.3) if one has been created for this module. | ||
* https://tc39.es/ecma262/#table-module-record-fields | ||
*/ | ||
#Namespace: ModuleNamespace | undefined | ||
//#endregion | ||
//#region VirtualModuleRecord fields | ||
// *this value* when calling #Execute. | ||
#VirtualModuleSource: VirtualModuleRecord | ||
#Execute: VirtualModuleRecord['execute'] | ||
#NeedsImportMeta: boolean | undefined | ||
#NeedsImport: boolean | undefined | ||
#ContextObject: VirtualModuleRecordExecuteContext | undefined | ||
#ImportHook: ImportHook | undefined | ||
#ImportMetaHook: ImportMetaHook | undefined | ||
#HandlerValue: ModuleHandler | ||
/** the global environment this module binds to */ | ||
#GlobalThis: object = globalThis | ||
#ParentImportHook: ImportHook = defaultImportHook | ||
#ParentImportMetaHook: ImportMetaHook | undefined | ||
/** imported module cache */ | ||
#ImportEntries: ModuleImportEntry[] | ||
#LocalExportEntries: ModuleExportEntry[] | ||
#IndirectExportEntries: ModuleExportEntry[] | ||
#StarExportEntries: ModuleExportEntry[] | ||
/** Where local export stored */ | ||
#LocalExportedValues = new Map<string, unknown>() | ||
/** Callback to update live exports */ | ||
#non_std_ExportCallback = new Map<string, Set<(newValue: any) => void>>() | ||
//#endregion | ||
//#region VirtualModuleRecord methods | ||
#non_std_AddLiveExportCallback(name: string, callback: (newValue: any) => void) { | ||
if (!this.#non_std_ExportCallback.has(name)) this.#non_std_ExportCallback.set(name, new Set()) | ||
this.#non_std_ExportCallback.get(name)!.add(callback) | ||
} | ||
//#endregion | ||
//#region ModuleRecord methods https://tc39.es/ecma262/#table-abstract-methods-of-module-records | ||
// https://tc39.es/ecma262/#sec-getexportednames | ||
#GetExportedNames(exportStarSet: Module[] = []): string[] { | ||
const module = this | ||
if (!(module.#Status !== ModuleStatus.new)) assertFailed() | ||
if (exportStarSet.includes(module)) return [] | ||
exportStarSet.push(module) | ||
const exportedNames: string[] = [] | ||
for (const e of module.#LocalExportEntries) { | ||
if (!(e.ExportName !== null)) assertFailed() | ||
exportedNames.push(e.ExportName) | ||
} | ||
for (const e of module.#IndirectExportEntries) { | ||
if (!(e.ExportName !== null)) assertFailed() | ||
exportedNames.push(e.ExportName) | ||
} | ||
for (const e of module.#StarExportEntries) { | ||
if (!(e.ModuleRequest !== null)) assertFailed() | ||
const requestedModule = Module.#GetImportedModule(module, e.ModuleRequest) | ||
const starNames = requestedModule.#GetExportedNames(exportStarSet) | ||
for (const n of starNames) { | ||
if (n === 'default') continue | ||
if (exportedNames.includes(n)) continue | ||
exportedNames.push(n) | ||
} | ||
} | ||
return exportedNames | ||
} | ||
// https://tc39.es/ecma262/#sec-resolveexport | ||
#ResolveExport( | ||
exportName: string, | ||
resolveSet: { module: Module; exportName: string }[] = [], | ||
): typeof ambiguous | { module: Module; bindingName: string | typeof namespace } | null { | ||
const module = this | ||
if (!(module.#Status !== ModuleStatus.new)) assertFailed() | ||
for (const r of resolveSet) { | ||
if (module === r.module && exportName === r.exportName) { | ||
// Assert: This is a circular import request. | ||
return null | ||
} | ||
} | ||
resolveSet.push({ module, exportName }) | ||
for (const e of module.#LocalExportEntries) { | ||
if (exportName === e.ExportName) { | ||
// if (!(e.LocalName !== null)) assertFailed() | ||
// return { module, bindingName: e.LocalName } | ||
return { module, bindingName: e.ExportName } | ||
} | ||
} | ||
for (const e of module.#IndirectExportEntries) { | ||
if (exportName === e.ExportName) { | ||
if (!(e.ModuleRequest !== null)) assertFailed() | ||
const importedModule = Module.#GetImportedModule(module, e.ModuleRequest) | ||
if (e.ImportName === all) { | ||
// Assert: module does not provide the direct binding for this export. | ||
return { module: importedModule, bindingName: namespace } | ||
} else { | ||
if (!(typeof e.ImportName === 'string')) assertFailed() | ||
return importedModule.#ResolveExport(e.ImportName, resolveSet) | ||
} | ||
} | ||
} | ||
if (exportName === 'default') { | ||
// Assert: A default export was not explicitly provided by this module. | ||
// Note: A default export cannot be provided by an export * from "mod" declaration. | ||
return null | ||
} | ||
let starResolution: null | { module: Module; bindingName: string | typeof namespace } = null | ||
for (const e of module.#StarExportEntries) { | ||
if (!(e.ModuleRequest !== null)) assertFailed() | ||
const importedModule = Module.#GetImportedModule(module, e.ModuleRequest) | ||
let resolution = importedModule.#ResolveExport(exportName, resolveSet) | ||
if (resolution === ambiguous) return ambiguous | ||
if (resolution !== null) { | ||
if (starResolution === null) starResolution = resolution | ||
else { | ||
// Assert: There is more than one * import that includes the requested name. | ||
if (resolution.module !== starResolution.module) return ambiguous | ||
if ( | ||
(resolution.bindingName === namespace && starResolution.bindingName !== namespace) || | ||
(resolution.bindingName !== namespace && starResolution.bindingName === namespace) | ||
) | ||
return ambiguous | ||
if ( | ||
typeof resolution.bindingName === 'string' && | ||
typeof starResolution.bindingName === 'string' && | ||
resolution.bindingName !== starResolution.bindingName | ||
) { | ||
return ambiguous | ||
} | ||
} | ||
} | ||
} | ||
return starResolution | ||
} | ||
//#region CyclicModuleRecord | ||
/** | ||
* Initially new. Transitions to unlinked, linking, linked, evaluating, possibly evaluating-async, evaluated (in that order) as the module progresses throughout its lifecycle. evaluating-async indicates this module is queued to execute on completion of its asynchronous dependencies or it is a module whose [[HasTLA]] field is true that has been executed and is pending top-level completion. | ||
* https://tc39.es/ecma262/#sec-cyclic-module-records | ||
*/ | ||
#Status = ModuleStatus.new | ||
/** | ||
* A throw completion representing the exception that occurred during evaluation. undefined if no exception occurred or if [[Status]] is not evaluated. | ||
* https://tc39.es/ecma262/#sec-cyclic-module-records | ||
*/ | ||
#EvaluationError: unknown | empty = empty | ||
/** | ||
* Auxiliary field used during Link and Evaluate only. If [[Status]] is linking or evaluating, this non-negative number records the point at which the module was first visited during the depth-first traversal of the dependency graph. | ||
* https://tc39.es/ecma262/#sec-cyclic-module-records | ||
*/ | ||
#DFSIndex: number | empty = empty | ||
/** | ||
* Auxiliary field used during Link and Evaluate only. If [[Status]] is linking or evaluating, this is either the module's own [[DFSIndex]] or that of an "earlier" module in the same strongly connected component. | ||
* https://tc39.es/ecma262/#sec-cyclic-module-records | ||
*/ | ||
#DFSAncestorIndex: number | empty = empty | ||
/** | ||
* A List of all the ModuleSpecifier strings used by the module represented by this record to request the importation of a module. The List is in source text occurrence order. | ||
* https://tc39.es/ecma262/#sec-cyclic-module-records | ||
*/ | ||
#RequestedModules: string[] | ||
/** | ||
* A map from the specifier strings used by the module represented by this record to request the importation of a module to the resolved Module Record. The list does not contain two different Records with the same [[Specifier]]. | ||
* https://tc39.es/ecma262/#sec-cyclic-module-records | ||
*/ | ||
#LoadedModules = new Map<string, Module>() | ||
/** | ||
* The first visited module of the cycle, the root DFS ancestor of the strongly connected component. For a module not in a cycle this would be the module itself. Once Evaluate has completed, a module's [[DFSAncestorIndex]] is equal to the [[DFSIndex]] of its [[CycleRoot]]. | ||
* https://tc39.es/ecma262/#sec-cyclic-module-records | ||
*/ | ||
#CycleRoot: Module | undefined | ||
/** | ||
* Whether this module is individually asynchronous (for example, if it's a Source Text Module Record containing a top-level await). Having an asynchronous dependency does not mean this field is true. This field must not change after the module is parsed. | ||
* https://tc39.es/ecma262/#sec-cyclic-module-records | ||
*/ | ||
#HasTLA: boolean | ||
/** | ||
* Whether this module is either itself asynchronous or has an asynchronous dependency. Note: The order in which this field is set is used to order queued executions, see 16.2.1.5.3.4. | ||
* https://tc39.es/ecma262/#sec-cyclic-module-records | ||
*/ | ||
#AsyncEvaluation = false | ||
/** | ||
* If this module is the [[CycleRoot]] of some cycle, and Evaluate() was called on some module in that cycle, this field contains the PromiseCapability Record for that entire evaluation. It is used to settle the Promise object that is returned from the Evaluate() abstract method. This field will be empty for any dependencies of that module, unless a top-level Evaluate() has been initiated for some of those dependencies. | ||
* https://tc39.es/ecma262/#sec-cyclic-module-records | ||
*/ | ||
#TopLevelCapability: PromiseCapability<void> | undefined | ||
/** | ||
* If this module or a dependency has [[HasTLA]] true, and execution is in progress, this tracks the parent importers of this module for the top-level execution job. These parent modules will not start executing before this module has successfully completed execution. | ||
* https://tc39.es/ecma262/#sec-cyclic-module-records | ||
*/ | ||
#AsyncParentModules: Module[] = [] | ||
/** | ||
* If this module has any asynchronous dependencies, this tracks the number of asynchronous dependency modules remaining to execute for this module. A module with asynchronous dependencies will be executed when this field reaches 0 and there are no execution errors. | ||
* https://tc39.es/ecma262/#sec-cyclic-module-records | ||
*/ | ||
#PendingAsyncDependencies: number | empty = empty | ||
/** https://tc39.es/ecma262/#sec-LoadRequestedModules */ | ||
#LoadRequestedModules(HostDefined: Task) { | ||
@@ -227,2 +175,3 @@ const module = this | ||
} | ||
/** https://tc39.es/ecma262/#sec-InnerModuleLoading */ | ||
static #InnerModuleLoading(state: GraphLoadingState, module: Module) { | ||
@@ -235,2 +184,4 @@ if (!state.IsLoading) assertFailed() | ||
for (const required of module.#RequestedModules) { | ||
// i. If module.[[LoadedModules]] contains a Record whose [[Specifier]] is required, then | ||
// 1. Let record be that Record. | ||
const record = module.#LoadedModules.get(required) | ||
@@ -240,3 +191,4 @@ if (record) { | ||
} else { | ||
Module.#LoadImportedModule(module, required, state.HostDefined, state) | ||
Module.#Layer0_LoadImportedModule(module, required, state.HostDefined, state) | ||
// 2. NOTE: HostLoadImportedModule will call FinishLoadingImportedModule, which re-enters the graph loading process through ContinueModuleLoading. | ||
} | ||
@@ -256,2 +208,3 @@ if (!state.IsLoading) return | ||
} | ||
/** https://tc39.es/ecma262/#sec-ContinueModuleLoading */ | ||
static #ContinueModuleLoading(state: GraphLoadingState, moduleCompletion: Completion<Module>) { | ||
@@ -265,181 +218,3 @@ if (!state.IsLoading) return | ||
} | ||
//#endregion | ||
//#region CyclicModuleRecord fields https://tc39.es/ecma262/#sec-cyclic-module-records | ||
#Status = ModuleStatus.new | ||
#EvaluationError: unknown | empty = empty | ||
#DFSIndex: number | empty = empty | ||
#DFSAncestorIndex: number | empty = empty | ||
#RequestedModules: string[] | ||
#LoadedModules = new Map<string, Module>() | ||
#LoadingModules = new Map<string, Set<GraphLoadingState | PromiseCapability<ModuleNamespace>>>() | ||
#LoadStates = new Set<GraphLoadingState | PromiseCapability<ModuleNamespace>>() | ||
#CycleRoot: Module | undefined | ||
#HasTLA: boolean | ||
#AsyncEvaluation = false | ||
#__AsyncEvaluationPreviouslyTrue = false | ||
#TopLevelCapability: PromiseCapability<void> | undefined | ||
#AsyncParentModules: Module[] = [] | ||
#PendingAsyncDependencies: number | empty = empty | ||
//#endregion | ||
//#region CyclicModuleRecord methods https://tc39.es/ecma262/#table-cyclic-module-methods | ||
// https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment | ||
#InitializeEnvironment() { | ||
const module = this | ||
for (const e of module.#IndirectExportEntries) { | ||
if (!(e.ExportName !== null)) assertFailed() | ||
const resolution = module.#ResolveExport(e.ExportName) | ||
if (resolution === null || resolution === ambiguous) { | ||
throw new SyntaxError(`Module '${e.ModuleRequest}' does not provide an export ${e.ExportName}`) | ||
} | ||
} | ||
// Assert: All named exports from module are resolvable. | ||
const env = { __proto__: null } | ||
module.#ContextObject = createContextObject() | ||
module.#Environment = env | ||
const propertiesToBeDefined: PropertyDescriptorMap = { | ||
__proto__: null!, | ||
} | ||
for (const i of module.#ImportEntries) { | ||
const importedModule = Module.#GetImportedModule(module, i.ModuleRequest) | ||
// import * as ns from '..' | ||
if (i.ImportName === namespace) { | ||
const namespaceObject = Module.#GetModuleNamespace(importedModule) | ||
propertiesToBeDefined[i.LocalName] = { value: namespaceObject, enumerable: true } | ||
} else { | ||
const resolution = importedModule.#ResolveExport(i.ImportName) | ||
if (resolution === null) | ||
throw new SyntaxError(`${i.ModuleRequest} does not provide export ${i.ImportName}`) | ||
if (resolution === ambiguous) | ||
throw new SyntaxError(`${i.ModuleRequest} does not provide an unambiguous export ${i.ImportName}`) | ||
// import { x } from '...' where x is a "export * as ns from '...'" | ||
if (resolution.bindingName === namespace) { | ||
const namespaceObject = Module.#GetModuleNamespace(resolution.module) | ||
propertiesToBeDefined[i.LocalName] = { value: namespaceObject, enumerable: true } | ||
} else { | ||
resolution.module.#non_std_AddLiveExportCallback(i.ImportName, (newValue) => { | ||
Object.defineProperty(env, i.LocalName, { | ||
value: newValue, | ||
configurable: true, | ||
enumerable: true, | ||
}) | ||
}) | ||
if (resolution.module.#LocalExportedValues.has(resolution.bindingName)) { | ||
propertiesToBeDefined[i.LocalName] = { | ||
configurable: true, | ||
enumerable: true, | ||
value: resolution.module.#LocalExportedValues.get(resolution.bindingName), | ||
} | ||
} else { | ||
propertiesToBeDefined[i.LocalName] = { | ||
get() { | ||
throw new ReferenceError(`Cannot access '${i.LocalName}' before initialization`) | ||
}, | ||
configurable: true, | ||
enumerable: true, | ||
} | ||
} | ||
} | ||
} | ||
} | ||
for (const { ModuleRequest, ExportName, ImportName } of module.#LocalExportEntries) { | ||
if (!(ModuleRequest === null && typeof ExportName === 'string' && ImportName === null)) assertFailed() | ||
propertiesToBeDefined[ExportName] = { | ||
get: () => this.#LocalExportedValues.get(ExportName), | ||
set: (value) => { | ||
this.#LocalExportedValues.set(ExportName, value) | ||
this.#non_std_ExportCallback.get(ExportName)?.forEach((callback) => callback(value)) | ||
return true | ||
}, | ||
// Note: export property should not be enumerable? | ||
// but it will crash Chrome devtools. See: https://bugs.chromium.org/p/chromium/issues/detail?id=1358114 | ||
enumerable: true, | ||
} | ||
} | ||
Object.defineProperties(env, propertiesToBeDefined) | ||
for (const exports of module.#GetExportedNames()) { | ||
if (module.#ResolveExport(exports) === ambiguous) { | ||
throw new SyntaxError(`Module has multiple exports named '${exports}'`) | ||
} | ||
} | ||
// TODO: https://github.com/tc39/proposal-compartments/issues/70 | ||
// prevent access to global env until [[ExecuteModule]] | ||
Object.setPrototypeOf(env, opaqueProxy) | ||
} | ||
/** All call to ExecuteModule must use Task.run to keep the call stack continue */ | ||
#ExecuteModule(promise?: PromiseCapability<void>) { | ||
const execute = this.#Execute | ||
if (!execute) return | ||
this.#Execute = undefined | ||
// prepare context | ||
this.#ContextObject!.globalThis = this.#GlobalThis as any | ||
if (this.#NeedsImportMeta) { | ||
const importMeta = { __proto__: null } | ||
if (this.#ImportMetaHook) Reflect.apply(this.#ImportMetaHook, this.#HandlerValue, [importMeta]) | ||
else if (this.#ParentImportMetaHook) Reflect.apply(this.#ParentImportMetaHook, undefined, [importMeta]) | ||
this.#ContextObject!.importMeta = importMeta | ||
} | ||
if (this.#NeedsImport) { | ||
this.#ContextObject!.import = async ( | ||
specifier: string | Module<ModuleNamespace>, | ||
options?: ImportCallOptions, | ||
) => { | ||
const referrer = this | ||
const promiseCapability = PromiseCapability<ModuleNamespace>() | ||
let hasModuleInternalSlot = false | ||
try { | ||
;(specifier as Module).#HandlerValue | ||
hasModuleInternalSlot = true | ||
} catch {} | ||
if (hasModuleInternalSlot) { | ||
const hostDefined = createTask(`import(<module block>)`) | ||
Module.#ContinueDynamicImport(promiseCapability, NormalCompletion(specifier as Module), hostDefined) | ||
} else { | ||
specifier = String(specifier) | ||
const hostDefined = createTask(`import("${specifier}")`) | ||
if (referrer.#LoadedModules.has(specifier)) { | ||
Module.#ContinueDynamicImport( | ||
promiseCapability, | ||
NormalCompletion(referrer.#LoadedModules.get(specifier)!), | ||
hostDefined, | ||
) | ||
} else { | ||
Module.#LoadImportedModule(referrer, specifier, hostDefined, promiseCapability) | ||
} | ||
} | ||
return promiseCapability.Promise as any | ||
} | ||
} | ||
if (!this.#Environment) assertFailed() | ||
const env = new Proxy(this.#Environment, moduleEnvExoticMethods) | ||
if (!this.#HasTLA) { | ||
if (promise) assertFailed() | ||
const result = Reflect.apply(execute, this.#VirtualModuleSource, [env, this.#ContextObject]) | ||
if (result) | ||
throw new TypeError( | ||
'Due to specification limitations, in order to support Async Modules (modules that use Top Level Await or a Virtual Module that has an execute() function that returns a Promise), the Virtual Module record must be marked with `isAsync: true`. The `isAsync` property is non-standard, and it is being tracked in https://github.com/tc39/proposal-compartments/issues/84.', | ||
) | ||
} else { | ||
if (!promise) assertFailed() | ||
Promise.resolve(Reflect.apply(execute, this.#VirtualModuleSource, [env, this.#ContextObject])).then( | ||
promise.Resolve, | ||
promise.Reject, | ||
) | ||
} | ||
} | ||
// https://tc39.es/ecma262/#sec-moduledeclarationlinking | ||
/** https://tc39.es/ecma262/#sec-moduledeclarationlinking */ | ||
#Link() { | ||
@@ -471,41 +246,3 @@ const module = this | ||
} | ||
// https://tc39.es/ecma262/#sec-moduleevaluation | ||
#Evaluate(HostDefined: Task) { | ||
let module: Module = this | ||
// TODO: Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent. | ||
if (![ModuleStatus.linked, ModuleStatus.evaluatingAsync, ModuleStatus.evaluated].includes(module.#Status)) | ||
assertFailed() | ||
if ([ModuleStatus.evaluatingAsync, ModuleStatus.evaluated].includes(module.#Status)) { | ||
module = module.#CycleRoot! | ||
if (!module) assertFailed() // TODO: https://github.com/tc39/ecma262/issues/2823 | ||
} | ||
if (module.#TopLevelCapability) return module.#TopLevelCapability.Promise | ||
const stack: Module[] = [] | ||
const capability = PromiseCapability<void>() | ||
module.#TopLevelCapability = capability | ||
try { | ||
Module.#InnerModuleEvaluation(module, stack, 0, HostDefined) | ||
} catch (err) { | ||
for (const m of stack) { | ||
if (!(m.#Status === ModuleStatus.evaluating)) assertFailed() | ||
m.#Status = ModuleStatus.evaluated | ||
m.#EvaluationError = err | ||
} | ||
if (!(module.#Status === ModuleStatus.evaluated)) assertFailed() | ||
if (!(module.#EvaluationError === err)) assertFailed() | ||
capability.Reject(err) | ||
return capability.Promise | ||
} | ||
if (![ModuleStatus.evaluatingAsync, ModuleStatus.evaluated].includes(module.#Status)) assertFailed() | ||
if (!(module.#EvaluationError === empty)) assertFailed() | ||
if (module.#AsyncEvaluation === false) { | ||
if (!(module.#Status === ModuleStatus.evaluated)) assertFailed() | ||
capability.Resolve() | ||
} | ||
if (!(stack.length === 0)) assertFailed() | ||
return capability.Promise | ||
} | ||
// https://tc39.es/ecma262/#sec-InnerModuleLinking | ||
/** https://tc39.es/ecma262/#sec-InnerModuleLinking */ | ||
static #InnerModuleLinking(module: Module, stack: Module[], index: number) { | ||
@@ -562,4 +299,39 @@ if ( | ||
} | ||
// https://tc39.es/ecma262/#sec-InnerModuleEvaluation | ||
/** https://tc39.es/ecma262/#sec-moduleevaluation */ | ||
#Evaluate(HostDefined: Task) { | ||
let module: Module = this | ||
// TODO: Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent. | ||
if (![ModuleStatus.linked, ModuleStatus.evaluatingAsync, ModuleStatus.evaluated].includes(module.#Status)) | ||
assertFailed() | ||
if ([ModuleStatus.evaluatingAsync, ModuleStatus.evaluated].includes(module.#Status)) { | ||
module = module.#CycleRoot! | ||
if (!module) assertFailed() // TODO: https://github.com/tc39/ecma262/issues/2823 | ||
} | ||
if (module.#TopLevelCapability) return module.#TopLevelCapability.Promise | ||
const stack: Module[] = [] | ||
const capability = PromiseCapability<void>() | ||
module.#TopLevelCapability = capability | ||
try { | ||
Module.#InnerModuleEvaluation(module, stack, 0, HostDefined) | ||
} catch (err) { | ||
for (const m of stack) { | ||
if (!(m.#Status === ModuleStatus.evaluating)) assertFailed() | ||
m.#Status = ModuleStatus.evaluated | ||
m.#EvaluationError = err | ||
} | ||
if (!(module.#Status === ModuleStatus.evaluated)) assertFailed() | ||
if (!(module.#EvaluationError === err)) assertFailed() | ||
capability.Reject(err) | ||
return capability.Promise | ||
} | ||
if (![ModuleStatus.evaluatingAsync, ModuleStatus.evaluated].includes(module.#Status)) assertFailed() | ||
if (!(module.#EvaluationError === empty)) assertFailed() | ||
if (module.#AsyncEvaluation === false) { | ||
if (!(module.#Status === ModuleStatus.evaluated)) assertFailed() | ||
capability.Resolve() | ||
} | ||
if (!(stack.length === 0)) assertFailed() | ||
return capability.Promise | ||
} | ||
/** https://tc39.es/ecma262/#sec-InnerModuleEvaluation */ | ||
static #InnerModuleEvaluation(module: Module, stack: Module[], index: number, HostDefined: Task) { | ||
@@ -579,4 +351,4 @@ if ([ModuleStatus.evaluatingAsync, ModuleStatus.evaluated].includes(module.#Status)) { | ||
for (const required of module.#RequestedModules) { | ||
let requiredModule = this.#GetImportedModule(module, required) | ||
index = this.#InnerModuleEvaluation(requiredModule, stack, index, HostDefined) | ||
let requiredModule = Module.#GetImportedModule(module, required) | ||
index = Module.#InnerModuleEvaluation(requiredModule, stack, index, HostDefined) | ||
if ( | ||
@@ -611,11 +383,11 @@ ![ModuleStatus.evaluating, ModuleStatus.evaluatingAsync, ModuleStatus.evaluated].includes( | ||
if (!(module.#AsyncEvaluation === false)) assertFailed() | ||
if (!(module.#__AsyncEvaluationPreviouslyTrue === false)) assertFailed() | ||
if (!(module.#NON_SPEC_AsyncEvaluationPreviouslyTrue === false)) assertFailed() | ||
module.#AsyncEvaluation = true | ||
module.#__AsyncEvaluationPreviouslyTrue = true | ||
module.#NON_SPEC_AsyncEvaluationPreviouslyTrue = true | ||
// Note: The order in which module records have their [[AsyncEvaluation]] fields transition to true is significant. (See 16.2.1.5.2.4.) | ||
if (module.#PendingAsyncDependencies === 0) { | ||
this.#ExecuteAsyncModule(module, HostDefined) | ||
Module.#ExecuteAsyncModule(module, HostDefined) | ||
} | ||
} else { | ||
HostDefined.run(() => module.#ExecuteModule()) | ||
HostDefined.run(() => module.#Layer2_ExecuteModule()) | ||
} | ||
@@ -639,4 +411,3 @@ if (!(stack.filter((x) => x === module).length === 1)) assertFailed() | ||
} | ||
// https://tc39.es/ecma262/#sec-execute-async-module | ||
/** https://tc39.es/ecma262/#sec-execute-async-module */ | ||
static #ExecuteAsyncModule(module: Module, HostDefined: Task) { | ||
@@ -654,6 +425,5 @@ if (![ModuleStatus.evaluating, ModuleStatus.evaluatingAsync].includes(module.#Status)) assertFailed() | ||
) | ||
HostDefined.run(() => module.#ExecuteModule(capability)) | ||
HostDefined.run(() => module.#Layer2_ExecuteModule(capability)) | ||
} | ||
// https://tc39.es/ecma262/#sec-gather-available-ancestors | ||
/** https://tc39.es/ecma262/#sec-gather-available-ancestors */ | ||
static #GatherAvailableAncestors(module: Module, execList: Module[]) { | ||
@@ -669,3 +439,3 @@ for (const m of module.#AsyncParentModules) { | ||
execList.push(m) | ||
if (!m.#HasTLA) this.#GatherAvailableAncestors(m, execList) | ||
if (!m.#HasTLA) Module.#GatherAvailableAncestors(m, execList) | ||
} | ||
@@ -675,4 +445,3 @@ } | ||
} | ||
// https://tc39.es/ecma262/#sec-async-module-execution-fulfilled | ||
/** https://tc39.es/ecma262/#sec-async-module-execution-fulfilled */ | ||
static #AsyncModuleExecutionFulfilled(module: Module, HostDefined: Task) { | ||
@@ -693,3 +462,3 @@ if (module.#Status === ModuleStatus.evaluated) { | ||
const execList: Module[] = [] | ||
this.#GatherAvailableAncestors(module, execList) | ||
Module.#GatherAvailableAncestors(module, execList) | ||
// TODO: Let sortedExecList be a List whose elements are the elements of execList, in the order in which they had their [[AsyncEvaluation]] fields set to true in InnerModuleEvaluation. | ||
@@ -707,6 +476,6 @@ const sortedExecList = execList | ||
} else if (m.#HasTLA) { | ||
this.#ExecuteAsyncModule(m, HostDefined) | ||
Module.#ExecuteAsyncModule(m, HostDefined) | ||
} else { | ||
try { | ||
HostDefined.run(() => m.#ExecuteModule()) | ||
HostDefined.run(() => m.#Layer2_ExecuteModule()) | ||
} catch (err) { | ||
@@ -724,4 +493,3 @@ this.#AsyncModuleExecutionRejected(m, err) | ||
} | ||
// https://tc39.es/ecma262/#sec-async-module-execution-rejected | ||
/** https://tc39.es/ecma262/#sec-async-module-execution-rejected */ | ||
static #AsyncModuleExecutionRejected = (module: Module, error: unknown) => { | ||
@@ -745,63 +513,270 @@ if (module.#Status === ModuleStatus.evaluated) { | ||
} | ||
static #GetModuleNamespace(module: Module): ModuleNamespace { | ||
if (module.#Namespace) return module.#Namespace | ||
if (!(module.#Status !== ModuleStatus.new && module.#Status !== ModuleStatus.unlinked)) assertFailed() | ||
const exportedNames = module.#GetExportedNames() | ||
//#endregion | ||
//#region SourceTextModuleRecord | ||
/** | ||
* A List of ImportEntry records derived from the code of this module. | ||
* https://tc39.es/ecma262/#table-additional-fields-of-source-text-module-records | ||
*/ | ||
#ImportEntries: ModuleImportEntry[] | ||
/** | ||
* A List of ExportEntry records derived from the code of this module that correspond to declarations that occur within the module. | ||
* https://tc39.es/ecma262/#table-additional-fields-of-source-text-module-records | ||
*/ | ||
#LocalExportEntries: ModuleExportEntry[] | ||
/** | ||
* A List of ExportEntry records derived from the code of this module that correspond to reexported imports that occur within the module or exports from export * as namespace declarations. | ||
* https://tc39.es/ecma262/#table-additional-fields-of-source-text-module-records | ||
*/ | ||
#IndirectExportEntries: ModuleExportEntry[] | ||
/** | ||
* A List of ExportEntry records derived from the code of this module that correspond to export * declarations that occur within the module, not including export * as namespace declarations. | ||
* https://tc39.es/ecma262/#table-additional-fields-of-source-text-module-records | ||
*/ | ||
#StarExportEntries: ModuleExportEntry[] | ||
/** https://tc39.es/ecma262/#sec-getexportednames */ | ||
#GetExportedNames(exportStarSet: Module[] = []): string[] { | ||
const module = this | ||
if (!(module.#Status !== ModuleStatus.new)) assertFailed() | ||
if (exportStarSet.includes(module)) { | ||
// a. Assert: We've reached the starting point of an export * circularity. | ||
return [] | ||
} | ||
exportStarSet.push(module) | ||
const exportedNames: string[] = [] | ||
for (const e of module.#LocalExportEntries) { | ||
// a. Assert: module provides the direct binding for this export. | ||
if (!(e.ExportName !== null)) assertFailed() | ||
exportedNames.push(e.ExportName) | ||
} | ||
for (const e of module.#IndirectExportEntries) { | ||
// a. Assert: module imports a specific binding for this export. | ||
if (!(e.ExportName !== null)) assertFailed() | ||
exportedNames.push(e.ExportName) | ||
} | ||
for (const e of module.#StarExportEntries) { | ||
// this assert does not appear in the spec. | ||
if (!(e.ModuleRequest !== null)) assertFailed() | ||
const requestedModule = Module.#GetImportedModule(module, e.ModuleRequest) | ||
const starNames = requestedModule.#GetExportedNames(exportStarSet) | ||
for (const n of starNames) { | ||
if (n === 'default') continue | ||
if (exportedNames.includes(n)) continue | ||
exportedNames.push(n) | ||
} | ||
} | ||
return exportedNames | ||
} | ||
/** https://tc39.es/ecma262/#sec-resolveexport */ | ||
#ResolveExport( | ||
exportName: string, | ||
resolveSet: { Module: Module; ExportName: string }[] = [], | ||
): typeof ambiguous | ResolvedBindingRecord | null { | ||
const module = this | ||
if (!(module.#Status !== ModuleStatus.new)) assertFailed() | ||
for (const r of resolveSet) { | ||
if (module === r.Module && exportName === r.ExportName) { | ||
// Assert: This is a circular import request. | ||
return null | ||
} | ||
} | ||
resolveSet.push({ Module: module, ExportName: exportName }) | ||
for (const e of module.#LocalExportEntries) { | ||
if (exportName === e.ExportName) { | ||
// i. Assert: module provides the direct binding for this export. | ||
// if (!(e.LocalName !== null)) assertFailed() | ||
// return { module, bindingName: e.LocalName } | ||
const namespaceObject: ModuleNamespace = { __proto__: null } | ||
const propertiesToBeDefined: PropertyDescriptorMap = { | ||
__proto__: null!, | ||
[Symbol.toStringTag]: { value: 'Module' }, | ||
// ? why we use this alternative step? | ||
return { Module: module, BindingName: e.ExportName } | ||
} | ||
} | ||
const namespaceProxy = new Proxy(namespaceObject, moduleNamespaceExoticMethods) | ||
// set it earlier in case of circular dependency | ||
module.#Namespace = namespaceProxy | ||
for (const e of module.#IndirectExportEntries) { | ||
if (exportName === e.ExportName) { | ||
// this assert does not appear in the spec. | ||
if (!(e.ModuleRequest !== null)) assertFailed() | ||
const importedModule = Module.#GetImportedModule(module, e.ModuleRequest) | ||
if (e.ImportName === all) { | ||
// Assert: module does not provide the direct binding for this export. | ||
return { Module: importedModule, BindingName: namespace } | ||
} else { | ||
// 1. Assert: module imports a specific binding for this export. | ||
if (!(typeof e.ImportName === 'string')) assertFailed() | ||
return importedModule.#ResolveExport(e.ImportName, resolveSet) | ||
} | ||
} | ||
} | ||
if (exportName === 'default') { | ||
// Assert: A default export was not explicitly provided by this module. | ||
// Note: A default export cannot be provided by an export * from "mod" declaration. | ||
return null | ||
} | ||
let starResolution: null | ResolvedBindingRecord = null | ||
for (const e of module.#StarExportEntries) { | ||
// this assert does not appear in the spec. | ||
if (!(e.ModuleRequest !== null)) assertFailed() | ||
const importedModule = Module.#GetImportedModule(module, e.ModuleRequest) | ||
let resolution = importedModule.#ResolveExport(exportName, resolveSet) | ||
if (resolution === ambiguous) return ambiguous | ||
if (resolution !== null) { | ||
// i. Assert: resolution is a ResolvedBinding Record. | ||
if (starResolution === null) starResolution = resolution | ||
else { | ||
// Assert: There is more than one * import that includes the requested name. | ||
if (resolution.Module !== starResolution.Module) return ambiguous | ||
if ( | ||
(resolution.BindingName === namespace && starResolution.BindingName !== namespace) || | ||
(resolution.BindingName !== namespace && starResolution.BindingName === namespace) | ||
) | ||
return ambiguous | ||
if ( | ||
typeof resolution.BindingName === 'string' && | ||
typeof starResolution.BindingName === 'string' && | ||
resolution.BindingName !== starResolution.BindingName | ||
) { | ||
return ambiguous | ||
} | ||
} | ||
} | ||
} | ||
return starResolution | ||
} | ||
/** https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment */ | ||
#InitializeEnvironment() { | ||
const module = this | ||
for (const e of module.#IndirectExportEntries) { | ||
// this assert does not appear in the spec. | ||
if (!(e.ExportName !== null)) assertFailed() | ||
const resolution = module.#ResolveExport(e.ExportName) | ||
if (resolution === null || resolution === ambiguous) { | ||
throw new SyntaxError(`Module '${e.ModuleRequest}' does not provide an export ${e.ExportName}`) | ||
} | ||
} | ||
for (const name of exportedNames) { | ||
const resolution = module.#ResolveExport(name) | ||
if (resolution === ambiguous || resolution === null) continue | ||
// 2. Assert: All named exports from module are resolvable. | ||
const { bindingName, module: targetModule } = resolution | ||
if (bindingName === namespace) { | ||
propertiesToBeDefined[name] = { enumerable: true, value: Module.#GetModuleNamespace(targetModule) } | ||
// 5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). | ||
const env = { __proto__: null } | ||
// 6. Set module.[[Environment]] to env. | ||
module.#Environment = env | ||
module.#Layer2_ContextObject = createContextObject() | ||
const envBindings: PropertyDescriptorMap = { | ||
__proto__: null!, | ||
} | ||
// 7. For each ImportEntry Record in of module.[[ImportEntries]], do | ||
for (const i of module.#ImportEntries) { | ||
const importedModule = Module.#GetImportedModule(module, i.ModuleRequest) | ||
// import * as ns from '..' | ||
if (i.ImportName === namespace) { | ||
const namespaceObject = Module.#GetModuleNamespace(importedModule) | ||
envBindings[i.LocalName] = { value: namespaceObject, enumerable: true } | ||
} else { | ||
if (targetModule.#LocalExportedValues.has(bindingName)) { | ||
propertiesToBeDefined[name] = { | ||
enumerable: true, | ||
// Note: this should not be configurable, but it's a trade-off for DX. | ||
configurable: true, | ||
value: targetModule.#LocalExportedValues.get(bindingName)!, | ||
} | ||
const resolution = importedModule.#ResolveExport(i.ImportName) | ||
if (resolution === null) | ||
throw new SyntaxError(`${i.ModuleRequest} does not provide export ${i.ImportName}`) | ||
if (resolution === ambiguous) | ||
throw new SyntaxError(`${i.ModuleRequest} does not provide an unambiguous export ${i.ImportName}`) | ||
// import { x } from '...' where x is a "export * as ns from '...'" | ||
if (resolution.BindingName === namespace) { | ||
const namespaceObject = Module.#GetModuleNamespace(resolution.Module) | ||
envBindings[i.LocalName] = { value: namespaceObject, enumerable: true } | ||
} else { | ||
propertiesToBeDefined[name] = { | ||
get() { | ||
throw new ReferenceError(`Cannot access '${name}' before initialization`) | ||
}, | ||
// Note: this should not be configurable, but it's a trade-off for DX. | ||
configurable: true, | ||
enumerable: true, | ||
// 1. Perform env.CreateImportBinding(in.[[LocalName]], resolution.[[Module]], resolution.[[BindingName]]). | ||
resolution.Module.#NON_SPEC_AddLiveExportCallback(i.ImportName, (newValue) => { | ||
Object.defineProperty(env, i.LocalName, { | ||
value: newValue, | ||
configurable: true, | ||
enumerable: true, | ||
}) | ||
}) | ||
if (resolution.Module.#NON_SPEC_LocalExportedValues.has(resolution.BindingName)) { | ||
envBindings[i.LocalName] = { | ||
configurable: true, | ||
enumerable: true, | ||
value: resolution.Module.#NON_SPEC_LocalExportedValues.get(resolution.BindingName), | ||
} | ||
} else { | ||
envBindings[i.LocalName] = { | ||
get() { | ||
throw new ReferenceError(`Cannot access '${i.LocalName}' before initialization`) | ||
}, | ||
configurable: true, | ||
enumerable: true, | ||
} | ||
} | ||
} | ||
targetModule.#non_std_AddLiveExportCallback(name, (newValue) => { | ||
Object.defineProperty(namespaceObject, name, { | ||
enumerable: true, | ||
writable: true, | ||
value: newValue, | ||
}) | ||
}) | ||
} | ||
} | ||
Object.defineProperties(namespaceObject, propertiesToBeDefined) | ||
return namespaceProxy | ||
// non-spec: set up env for exported bindings | ||
for (const { ModuleRequest, ExportName, ImportName } of module.#LocalExportEntries) { | ||
if (!(ModuleRequest === null && typeof ExportName === 'string' && ImportName === null)) assertFailed() | ||
envBindings[ExportName] = { | ||
get: () => this.#NON_SPEC_LocalExportedValues.get(ExportName), | ||
set: (value) => { | ||
this.#NON_SPEC_LocalExportedValues.set(ExportName, value) | ||
this.#NON_SPEC_ExportCallback.get(ExportName)?.forEach((callback) => callback(value)) | ||
return true | ||
}, | ||
// Note: export property should not be enumerable? | ||
// but it will crash Chrome devtools. See: https://bugs.chromium.org/p/chromium/issues/detail?id=1358114 | ||
enumerable: true, | ||
} | ||
} | ||
Object.defineProperties(env, envBindings) | ||
// ? no spec reference? | ||
for (const exports of module.#GetExportedNames()) { | ||
if (module.#ResolveExport(exports) === ambiguous) { | ||
throw new SyntaxError(`Module has multiple exports named '${exports}'`) | ||
} | ||
} | ||
// TODO: https://github.com/tc39/proposal-compartments/issues/70 | ||
// prevent access to global env until [[ExecuteModule]] | ||
Object.setPrototypeOf(env, opaqueProxy) | ||
} | ||
//#endregion | ||
//#region Non-spec things (needed for this impl) | ||
/** | ||
* This is used as an assertion in the spec but spec does not contain it. | ||
*/ | ||
#NON_SPEC_AsyncEvaluationPreviouslyTrue = false | ||
/** | ||
* A map that map the exported name to it's current value. | ||
*/ | ||
#NON_SPEC_LocalExportedValues = new Map<string, unknown>() | ||
/** | ||
* A callback map that stores all listeners will be notified when the requested export name has been updated. | ||
*/ | ||
#NON_SPEC_ExportCallback = new Map<string, Set<(newValue: any) => void>>() | ||
#NON_SPEC_AddLiveExportCallback(name: string, callback: (newValue: any) => void) { | ||
if (!this.#NON_SPEC_ExportCallback.has(name)) this.#NON_SPEC_ExportCallback.set(name, new Set()) | ||
this.#NON_SPEC_ExportCallback.get(name)!.add(callback) | ||
} | ||
//#endregion | ||
//#region Layer 0 features | ||
/** | ||
* Defaults to undefined. The function can return a module instance to resolve module dependencies. | ||
* https://tc39.es/proposal-compartments/0-module-and-module-source.html#table-internal-slots-of-module-instances | ||
*/ | ||
#Layer0_ImportHook: ImportHook | undefined | ||
/** | ||
* Defaults to undefined. The function can augment the import.meta object provided as the first argument. | ||
* https://tc39.es/proposal-compartments/0-module-and-module-source.html#table-internal-slots-of-module-instances | ||
*/ | ||
#Layer0_ImportMetaHook: ImportMetaHook | undefined | ||
/** | ||
* This is the *this* value used for invocation of the hook functions. | ||
* https://tc39.es/proposal-compartments/0-module-and-module-source.html#table-internal-slots-of-module-instances | ||
*/ | ||
#Layer0_HandlerValue: ModuleHandler | ||
//#region Module refactor methods https://github.com/tc39/ecma262/pull/2905/ | ||
static #GetImportedModule(module: Module, spec: string) { | ||
const record = module.#LoadedModules.get(spec) | ||
if (!record) assertFailed() | ||
return record | ||
} | ||
static #LoadImportedModule( | ||
/** | ||
* A map from the specifier strings imported by this module to the states of the loading processes that are waiting for the resolved module record. It is used to avoid multiple calls to the loading hook with the same (specifier, referrer) pair. The list does not contain two different Records with the same [[Specifier]]. | ||
* https://tc39.es/proposal-compartments/0-module-and-module-source.html#table-cyclic-module-fields | ||
*/ | ||
#Layer0_LoadingModules = new Map<string, Set<GraphLoadingState | PromiseCapability<ModuleNamespace>>>() | ||
/** https://tc39.es/proposal-compartments/0-module-and-module-source.html#sec-LoadImportedModule */ | ||
static #Layer0_LoadImportedModule( | ||
referrer: Module, | ||
@@ -814,10 +789,10 @@ specifier: string, | ||
const module = referrer.#LoadedModules.get(specifier)! | ||
this.#FinishLoadingImportedModule(referrer, specifier, NormalCompletion(module), hostDefined) | ||
this.#Layer0_FinishLoadingImportedModule(referrer, specifier, NormalCompletion(module), hostDefined) | ||
return | ||
} | ||
if (referrer.#LoadingModules.has(specifier)) { | ||
referrer.#LoadingModules.get(specifier)!.add(state) | ||
if (referrer.#Layer0_LoadingModules.has(specifier)) { | ||
referrer.#Layer0_LoadingModules.get(specifier)!.add(state) | ||
return | ||
} | ||
referrer.#LoadingModules.set(specifier, new Set([state])) | ||
referrer.#Layer0_LoadingModules.set(specifier, new Set([state])) | ||
// Skipped spec: | ||
@@ -829,5 +804,5 @@ // 4. If referrer is not a Source Text Module Record, referrer.[[ModuleInstance]] is undefined, or referrer.[[ModuleInstance]].[[ImportHook]] is undefined, then | ||
try { | ||
const importHookResult = referrer.#ImportHook | ||
? Reflect.apply(referrer.#ImportHook, referrer.#HandlerValue, [specifier]) | ||
: Reflect.apply(referrer.#ParentImportHook, undefined, [specifier]) | ||
const importHookResult = referrer.#Layer0_ImportHook | ||
? Reflect.apply(referrer.#Layer0_ImportHook, referrer.#Layer0_HandlerValue, [specifier]) | ||
: Reflect.apply(referrer.#Layer3_ParentImportHook, undefined, [specifier]) | ||
// unwrap importHookResult here | ||
@@ -839,3 +814,3 @@ const importHookPromise = Promise.resolve(importHookResult) | ||
try { | ||
;(result as Module).#HandlerValue | ||
;(result as Module).#Layer0_HandlerValue | ||
completion = NormalCompletion(result) | ||
@@ -845,14 +820,17 @@ } catch (error) { | ||
} | ||
this.#FinishLoadingImportedModule(referrer, specifier, completion, hostDefined) | ||
Module.#Layer0_FinishLoadingImportedModule(referrer, specifier, completion, hostDefined) | ||
} | ||
const onRejected = (error: any) => { | ||
this.#FinishLoadingImportedModule(referrer, specifier, ThrowCompletion(error), hostDefined) | ||
this.#Layer0_FinishLoadingImportedModule(referrer, specifier, ThrowCompletion(error), hostDefined) | ||
} | ||
importHookPromise.then(onFulfilled, onRejected) | ||
} catch (error) { | ||
this.#FinishLoadingImportedModule(referrer, specifier, ThrowCompletion(error), hostDefined) | ||
this.#Layer0_FinishLoadingImportedModule(referrer, specifier, ThrowCompletion(error), hostDefined) | ||
} | ||
} | ||
static #FinishLoadingImportedModule( | ||
/** | ||
* https://tc39.es/ecma262/#sec-FinishLoadingImportedModule | ||
* https://tc39.es/proposal-compartments/0-module-and-module-source.html#sec-FinishLoadingImportedModule | ||
*/ | ||
static #Layer0_FinishLoadingImportedModule( | ||
referrer: Module, | ||
@@ -871,11 +849,113 @@ specifier: string, | ||
} | ||
const loading = referrer.#LoadingModules.get(specifier)! | ||
const loading = referrer.#Layer0_LoadingModules.get(specifier) | ||
if (!loading) assertFailed() | ||
referrer.#LoadingModules.delete(specifier) | ||
referrer.#Layer0_LoadingModules.delete(specifier) | ||
for (const state of loading) { | ||
if ('Visited' in state) this.#ContinueModuleLoading(state, result) | ||
else this.#ContinueDynamicImport(state, result, hostDefined) | ||
if ('PromiseCapability' in state) { | ||
Module.#ContinueModuleLoading(state, result) | ||
} else { | ||
Module.#ContinueDynamicImport(state, result, hostDefined) | ||
} | ||
} | ||
} | ||
//#endregion | ||
//#region Layer 2 features | ||
#Layer2_VirtualModuleSource: unknown | ||
#Layer2_Execute: VirtualModuleRecord['execute'] | empty = empty | ||
#Layer2_NeedsImportMeta: boolean | undefined | ||
#Layer2_NeedsImport: boolean | undefined | ||
#Layer2_ContextObject: VirtualModuleRecordExecuteContext | undefined | ||
/** | ||
* All call to ExecuteModule must use Task.run to keep the call stack continue | ||
* https://tc39.es/ecma262/#sec-source-text-module-record-execute-module | ||
* https://tc39.es/proposal-compartments/0-module-and-module-source.html | ||
* https://github.com/tc39/proposal-compartments/blob/master/2-virtual-module-source.md | ||
*/ | ||
#Layer2_ExecuteModule(promise?: PromiseCapability<void>) { | ||
if (!this.#Layer2_ContextObject) assertFailed() | ||
if (!this.#Environment) assertFailed() | ||
// a virtual module might have no execute function available (a purely re-export module) | ||
const execute = this.#Layer2_Execute | ||
if (execute === empty) assertFailed() | ||
this.#Layer2_Execute = empty | ||
if (!execute) { | ||
promise?.Resolve() | ||
return | ||
} | ||
// prepare context | ||
this.#Layer2_ContextObject.globalThis = this.#Layer3_GlobalThis as any | ||
if (this.#Layer2_NeedsImportMeta) { | ||
// https://tc39.es/proposal-compartments/0-module-and-module-source.html#sec-meta-properties-runtime-semantics-evaluation | ||
const importMeta = { __proto__: null } | ||
if (this.#Layer0_ImportMetaHook) | ||
Reflect.apply(this.#Layer0_ImportMetaHook, this.#Layer0_HandlerValue, [importMeta]) | ||
else if (this.#Layer3_ParentImportMetaHook) | ||
Reflect.apply(this.#Layer3_ParentImportMetaHook, undefined, [importMeta]) | ||
this.#Layer2_ContextObject.importMeta = importMeta | ||
} | ||
if (this.#Layer2_NeedsImport) { | ||
// https://tc39.es/proposal-compartments/0-module-and-module-source.html#sec-import-calls | ||
this.#Layer2_ContextObject.import = async ( | ||
specifier: string | Module<ModuleNamespace>, | ||
options?: ImportCallOptions, | ||
) => { | ||
// 1. Let referrer be GetActiveScriptOrModule(). | ||
const referrer = this | ||
// 5. Let promiseCapability be ! NewPromiseCapability(%Promise%). | ||
const promiseCapability = PromiseCapability<ModuleNamespace>() | ||
let hasModuleInternalSlot = false | ||
try { | ||
;(specifier as Module).#Layer0_HandlerValue | ||
hasModuleInternalSlot = true | ||
} catch {} | ||
// 6. If Type(specifierOrModule) is Object that has a [[ModuleSourceInstance]] internal slot, then | ||
if (hasModuleInternalSlot) { | ||
const hostDefined = createTask(`import(<module block>)`) | ||
Module.#ContinueDynamicImport(promiseCapability, NormalCompletion(specifier as Module), hostDefined) | ||
} else { | ||
specifier = String(specifier) | ||
const hostDefined = createTask(`import("${specifier}")`) | ||
Module.#Layer0_LoadImportedModule(referrer, specifier, hostDefined, promiseCapability) | ||
} | ||
return promiseCapability.Promise as any | ||
} | ||
} | ||
const env = new Proxy(this.#Environment, moduleEnvExoticMethods) | ||
// https://tc39.es/ecma262/#sec-source-text-module-record-execute-module | ||
// 9. If module.[[HasTLA]] is false, then | ||
if (!this.#HasTLA) { | ||
// a. Assert: capability is not present. | ||
if (promise) assertFailed() | ||
// c. Let result be Completion(Evaluation of module.[[ECMAScriptCode]]). | ||
// f. If result is an abrupt completion, then | ||
// i. Return ? result. | ||
const result = Reflect.apply(execute, this.#Layer2_VirtualModuleSource, [env, this.#Layer2_ContextObject]) | ||
if (result) { | ||
throw new TypeError( | ||
'Due to specification limitations, in order to support Async Modules (modules that use Top Level Await or a Virtual Module that has an execute() function that returns a Promise), the Virtual Module record must be marked with `isAsync: true`. The `isAsync` property is non-standard, and it is being tracked in https://github.com/tc39/proposal-compartments/issues/84.', | ||
) | ||
} | ||
} else { | ||
// a. Assert: capability is a PromiseCapability Record. | ||
if (!promise) assertFailed() | ||
// b. Perform AsyncBlockStart(capability, module.[[ECMAScriptCode]], moduleContext). | ||
Promise.resolve() | ||
.then(() => Reflect.apply(execute, this.#Layer2_VirtualModuleSource, [env, this.#Layer2_ContextObject])) | ||
.then(promise.Resolve, promise.Reject) | ||
} | ||
} | ||
//#endregion | ||
//#region Layer 3 features | ||
#Layer3_GlobalThis: object = globalThis | ||
#Layer3_ParentImportHook: ImportHook = defaultImportHook | ||
#Layer3_ParentImportMetaHook: ImportMetaHook | undefined | ||
//#endregion | ||
/** https://tc39.es/ecma262/#sec-ContinueDynamicImport */ | ||
static #ContinueDynamicImport( | ||
@@ -910,4 +990,60 @@ promiseCapability: PromiseCapability<ModuleNamespace>, | ||
} | ||
//#endregion | ||
/** @internal */ | ||
/** https://tc39.es/ecma262/#sec-GetImportedModule */ | ||
static #GetImportedModule(module: Module, spec: string) { | ||
const record = module.#LoadedModules.get(spec) | ||
if (!record) assertFailed() | ||
return record | ||
} | ||
/** https://tc39.es/ecma262/#sec-modulenamespacecreate */ | ||
static #GetModuleNamespace(module: Module): ModuleNamespace { | ||
if (module.#Namespace) return module.#Namespace | ||
if (!(module.#Status !== ModuleStatus.new && module.#Status !== ModuleStatus.unlinked)) assertFailed() | ||
const exportedNames = module.#GetExportedNames() | ||
const namespaceObject: ModuleNamespace = { __proto__: null } | ||
const namespaceObjectBindings: PropertyDescriptorMap = { | ||
__proto__: null!, | ||
[Symbol.toStringTag]: { value: 'Module' }, | ||
} | ||
const namespaceProxy = new Proxy(namespaceObject, moduleNamespaceExoticMethods) | ||
// set it earlier in case of circular dependency | ||
module.#Namespace = namespaceProxy | ||
for (const name of exportedNames) { | ||
const resolution = module.#ResolveExport(name) | ||
if (resolution === ambiguous || resolution === null) continue | ||
const { BindingName, Module: targetModule } = resolution | ||
if (BindingName === namespace) { | ||
namespaceObjectBindings[name] = { enumerable: true, value: Module.#GetModuleNamespace(targetModule) } | ||
} else { | ||
if (targetModule.#NON_SPEC_LocalExportedValues.has(BindingName)) { | ||
namespaceObjectBindings[name] = { | ||
enumerable: true, | ||
// Note: this should not be configurable, but it's a trade-off for DX. | ||
configurable: true, | ||
value: targetModule.#NON_SPEC_LocalExportedValues.get(BindingName)!, | ||
} | ||
} else { | ||
namespaceObjectBindings[name] = { | ||
get() { | ||
throw new ReferenceError(`Cannot access '${name}' before initialization`) | ||
}, | ||
// Note: this should not be configurable, but it's a trade-off for DX. | ||
configurable: true, | ||
enumerable: true, | ||
} | ||
} | ||
targetModule.#NON_SPEC_AddLiveExportCallback(name, (newValue) => { | ||
Object.defineProperty(namespaceObject, name, { | ||
enumerable: true, | ||
writable: true, | ||
value: newValue, | ||
}) | ||
}) | ||
} | ||
} | ||
Object.defineProperties(namespaceObject, namespaceObjectBindings) | ||
return namespaceProxy | ||
} | ||
static { | ||
@@ -921,5 +1057,5 @@ imports = async (module, options) => { | ||
} | ||
setParentGlobalThis = (module, global) => (module.#GlobalThis = global) | ||
setParentImportHook = (module, hook) => (module.#ParentImportHook = hook) | ||
setParentImportMetaHook = (module, hook) => (module.#ParentImportMetaHook = hook) | ||
setParentGlobalThis = (module, global) => (module.#Layer3_GlobalThis = global) | ||
setParentImportHook = (module, hook) => (module.#Layer3_ParentImportHook = hook) | ||
setParentImportMetaHook = (module, hook) => (module.#Layer3_ParentImportMetaHook = hook) | ||
} | ||
@@ -996,1 +1132,6 @@ } | ||
} | ||
interface ResolvedBindingRecord { | ||
Module: Module | ||
BindingName: string | typeof namespace | ||
} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
172490
39
3674
0
92