@masknet/compartment
Advanced tools
Comparing version 0.3.3 to 0.3.4
@@ -74,3 +74,8 @@ import { all, ambiguous, empty, namespace, PromiseCapability, } from './utils/spec.js'; | ||
/** Callback to update live exports */ | ||
#ExportCallback = new Set(); | ||
#ExportCallback = new Map(); | ||
#AddLiveExportCallback(name, callback) { | ||
if (!this.#ExportCallback.has(name)) | ||
this.#ExportCallback.set(name, new Set()); | ||
this.#ExportCallback.get(name).add(callback); | ||
} | ||
//#endregion | ||
@@ -202,30 +207,40 @@ //#region VirtualModuleRecord methods | ||
module.#Environment = env; | ||
const propertiesToBeDefined = { | ||
__proto__: null, | ||
}; | ||
for (const i of module.#ImportEntries) { | ||
const importedModule = Module.#HostResolveImportedModule(module, i.ModuleRequest); | ||
// import * as ns from '..' | ||
if (i.ImportName === namespace) { | ||
const namespaceObject = Module.#GetModuleNamespace(importedModule); | ||
Object.defineProperty(env, i.LocalName, { value: namespaceObject, enumerable: true }); | ||
propertiesToBeDefined[i.LocalName] = { value: namespaceObject, enumerable: true }; | ||
} | ||
else { | ||
const resolution = importedModule.#ResolveExport(i.ImportName); | ||
if (resolution === null || resolution === ambiguous) { | ||
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); | ||
Object.defineProperty(env, i.LocalName, { value: namespaceObject, enumerable: true }); | ||
propertiesToBeDefined[i.LocalName] = { value: namespaceObject, enumerable: true }; | ||
} | ||
else { | ||
const { bindingName, module } = resolution; | ||
const f = () => Object.defineProperty(env, i.LocalName, { | ||
value: module.#LocalExportValues.get(bindingName), | ||
configurable: true, | ||
enumerable: true, | ||
resolution.module.#AddLiveExportCallback(i.LocalName, (newValue) => { | ||
Object.defineProperty(env, i.LocalName, { | ||
value: newValue, | ||
configurable: true, | ||
enumerable: true, | ||
}); | ||
}); | ||
resolution.module.#ExportCallback.add(f); | ||
if (resolution.module.#LocalExportValues.has(bindingName)) { | ||
f(); | ||
if (resolution.module.#LocalExportValues.has(resolution.bindingName)) { | ||
propertiesToBeDefined[i.LocalName] = { | ||
configurable: true, | ||
enumerable: true, | ||
value: resolution.module.#LocalExportValues.get(resolution.bindingName), | ||
}; | ||
} | ||
else { | ||
Object.defineProperty(env, i.LocalName, { | ||
propertiesToBeDefined[i.LocalName] = { | ||
get() { | ||
@@ -236,3 +251,3 @@ throw new ReferenceError(`Cannot access '${i.LocalName}' before initialization`); | ||
enumerable: true, | ||
}); | ||
}; | ||
} | ||
@@ -244,7 +259,7 @@ } | ||
assert(ModuleRequest === null && typeof ExportName === 'string' && ImportName === null); | ||
Object.defineProperty(env, ExportName, { | ||
propertiesToBeDefined[ExportName] = { | ||
get: () => this.#LocalExportValues.get(ExportName), | ||
set: (value) => { | ||
this.#LocalExportValues.set(ExportName, value); | ||
this.#ExportCallback.forEach((f) => f(ExportName)); | ||
this.#ExportCallback.get(ExportName)?.forEach((callback) => callback(value)); | ||
return true; | ||
@@ -255,4 +270,5 @@ }, | ||
enumerable: true, | ||
}); | ||
}; | ||
} | ||
Object.defineProperties(env, propertiesToBeDefined); | ||
for (const exports of module.#GetExportedNames()) { | ||
@@ -567,44 +583,52 @@ if (module.#ResolveExport(exports) === ambiguous) { | ||
static #GetModuleNamespace(module) { | ||
assert(module.#Status !== ModuleStatus.unlinked); | ||
if (module.#Namespace) | ||
return module.#Namespace; | ||
assert(module.#Status !== ModuleStatus.unlinked); | ||
const exportedNames = module.#GetExportedNames(); | ||
const unambiguousNames = []; | ||
const namespaceObject = { __proto__: null }; | ||
const propertiesToBeDefined = { | ||
__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 (typeof resolution === 'object' && resolution !== null) { | ||
unambiguousNames.push(name); | ||
if (resolution === ambiguous || resolution === null) | ||
continue; | ||
const { bindingName, module: targetModule } = resolution; | ||
if (bindingName === namespace) { | ||
propertiesToBeDefined[name] = { enumerable: true, value: Module.#GetModuleNamespace(targetModule) }; | ||
} | ||
} | ||
const namespace = { __proto__: null }; | ||
Object.defineProperty(namespace, Symbol.toStringTag, { value: 'Module' }); | ||
for (const name of exportedNames) { | ||
if (module.#LocalExportValues.has(name)) { | ||
Object.defineProperty(namespace, name, { | ||
enumerable: true, | ||
writable: true, | ||
value: module.#LocalExportValues.get(name), | ||
}); | ||
} | ||
else { | ||
Object.defineProperty(namespace, 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, | ||
if (targetModule.#LocalExportValues.has(bindingName)) { | ||
propertiesToBeDefined[name] = { | ||
enumerable: true, | ||
// Note: this should not be configurable, but it's a trade-off for DX. | ||
configurable: true, | ||
value: targetModule.#LocalExportValues.get(bindingName), | ||
}; | ||
} | ||
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, | ||
}; | ||
} | ||
module.#AddLiveExportCallback(name, (newValue) => { | ||
Object.defineProperty(namespaceObject, name, { | ||
enumerable: true, | ||
writable: true, | ||
value: newValue, | ||
}); | ||
}); | ||
} | ||
} | ||
module.#ExportCallback.add((name) => { | ||
Object.defineProperty(namespace, name, { | ||
enumerable: true, | ||
writable: true, | ||
value: module.#LocalExportValues.get(name), | ||
}); | ||
}); | ||
const proxy = new Proxy(namespace, moduleNamespaceExoticMethods); | ||
module.#Namespace = proxy; | ||
return proxy; | ||
Object.defineProperties(namespaceObject, propertiesToBeDefined); | ||
return namespaceProxy; | ||
} | ||
@@ -611,0 +635,0 @@ //#endregion |
@@ -1,2 +0,2 @@ | ||
import { unreachable } from './assert.js'; | ||
import { assert, unreachable } from './assert.js'; | ||
import { hasFromField, isExportAllBinding, isExportBinding, isImportAllBinding, isImportBinding } from './shapeCheck.js'; | ||
@@ -120,3 +120,2 @@ import { all, allButDefault, namespace } from './spec.js'; | ||
if (isImportBinding(binding)) { | ||
requestedModules.push(binding.from); | ||
importEntries.push({ | ||
@@ -129,3 +128,2 @@ ImportName: binding.import, | ||
else if (isImportAllBinding(binding)) { | ||
requestedModules.push(binding.importAllFrom); | ||
importEntries.push({ | ||
@@ -138,3 +136,2 @@ ImportName: namespace, | ||
} | ||
const importedBoundNames = importEntries.map((x) => x.LocalName); | ||
const indirectExportEntries = []; | ||
@@ -144,43 +141,28 @@ const localExportEntries = []; | ||
for (const binding of bindings) { | ||
if (isExportBinding(binding)) { | ||
if (hasFromField(binding)) { | ||
requestedModules.push(binding.from); | ||
// step 10.a: if ee.ModuleRequest is null, then append ee to localExportEntries | ||
// export { ... } | ||
if (isExportBinding(binding) && !hasFromField(binding)) { | ||
localExportEntries.push({ | ||
ExportName: binding.as ?? binding.export, | ||
ImportName: null, | ||
ModuleRequest: null, | ||
}); | ||
} | ||
// step 10.b: else if ee.ImportName is all-but-default, then append ee to starExportEntries | ||
// export * from '...' | ||
else if (isExportAllBinding(binding) && binding.as === undefined) { | ||
starExportEntries.push({ | ||
// LocalName: null, | ||
ExportName: null, | ||
ImportName: allButDefault, | ||
ModuleRequest: binding.exportAllFrom, | ||
}); | ||
} | ||
// step 10.c: else, append ee to indirectExportEntries | ||
// export * as T from '...' | ||
// export { T } from '...' | ||
else { | ||
if (isExportAllBinding(binding)) { | ||
assert(binding.as !== undefined); | ||
indirectExportEntries.push({ | ||
ExportName: binding.as ?? binding.export, | ||
ImportName: binding.export, | ||
// LocalName: null, | ||
ModuleRequest: binding.from, | ||
}); | ||
} | ||
else { | ||
const ee = { | ||
ExportName: binding.as ?? binding.export, | ||
// LocalName: binding.export, | ||
ImportName: null, | ||
ModuleRequest: null, | ||
}; | ||
if (!importedBoundNames.includes(binding.export)) { | ||
localExportEntries.push(ee); | ||
} | ||
else { | ||
const ie = importEntries.find((x) => x.LocalName === binding.export); | ||
if (ie.ImportName === namespace) { | ||
localExportEntries.push(ee); | ||
} | ||
else { | ||
indirectExportEntries.push({ | ||
ModuleRequest: ie.ModuleRequest, | ||
ImportName: ie.ImportName, | ||
ExportName: ee.ExportName, | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
else if (isExportAllBinding(binding)) { | ||
requestedModules.push(binding.exportAllFrom); | ||
if (typeof binding.as === 'string') { | ||
// export * as name from 'mod' | ||
starExportEntries.push({ | ||
// LocalName: binding.as, | ||
ExportName: binding.as, | ||
@@ -191,9 +173,7 @@ ImportName: all, | ||
} | ||
else { | ||
// export * from 'mod' | ||
starExportEntries.push({ | ||
// LocalName: null, | ||
ExportName: null, | ||
ImportName: allButDefault, | ||
ModuleRequest: binding.exportAllFrom, | ||
else if (isExportBinding(binding)) { | ||
indirectExportEntries.push({ | ||
ExportName: binding.as ?? binding.export, | ||
ImportName: binding.export, | ||
ModuleRequest: binding.from, | ||
}); | ||
@@ -200,0 +180,0 @@ } |
{ | ||
"name": "@masknet/compartment", | ||
"version": "0.3.3", | ||
"version": "0.3.4", | ||
"type": "module", | ||
@@ -12,3 +12,3 @@ "main": "./dist/index.js", | ||
"devDependencies": { | ||
"@swc/core": "^1.2.223" | ||
"@swc/core": "^1.2.245" | ||
}, | ||
@@ -15,0 +15,0 @@ "files": [ |
@@ -98,3 +98,7 @@ import type { ModuleSource } from './ModuleSource.js' | ||
/** Callback to update live exports */ | ||
#ExportCallback = new Set<(name: string) => void>() | ||
#ExportCallback = new Map<string, Set<(newValue: any) => void>>() | ||
#AddLiveExportCallback(name: string, callback: (newValue: any) => void) { | ||
if (!this.#ExportCallback.has(name)) this.#ExportCallback.set(name, new Set()) | ||
this.#ExportCallback.get(name)!.add(callback) | ||
} | ||
//#endregion | ||
@@ -233,29 +237,38 @@ | ||
const propertiesToBeDefined: PropertyDescriptorMap = { | ||
__proto__: null!, | ||
} | ||
for (const i of module.#ImportEntries) { | ||
const importedModule = Module.#HostResolveImportedModule(module, i.ModuleRequest) | ||
// import * as ns from '..' | ||
if (i.ImportName === namespace) { | ||
const namespaceObject = Module.#GetModuleNamespace(importedModule) | ||
Object.defineProperty(env, i.LocalName, { value: namespaceObject, enumerable: true }) | ||
propertiesToBeDefined[i.LocalName] = { value: namespaceObject, enumerable: true } | ||
} else { | ||
const resolution = importedModule.#ResolveExport(i.ImportName) | ||
if (resolution === null || resolution === ambiguous) { | ||
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) | ||
Object.defineProperty(env, i.LocalName, { value: namespaceObject, enumerable: true }) | ||
propertiesToBeDefined[i.LocalName] = { value: namespaceObject, enumerable: true } | ||
} else { | ||
const { bindingName, module } = resolution | ||
const f = () => | ||
resolution.module.#AddLiveExportCallback(i.LocalName, (newValue) => { | ||
Object.defineProperty(env, i.LocalName, { | ||
value: module.#LocalExportValues.get(bindingName), | ||
value: newValue, | ||
configurable: true, | ||
enumerable: true, | ||
}) | ||
resolution.module.#ExportCallback.add(f) | ||
}) | ||
if (resolution.module.#LocalExportValues.has(bindingName)) { | ||
f() | ||
if (resolution.module.#LocalExportValues.has(resolution.bindingName)) { | ||
propertiesToBeDefined[i.LocalName] = { | ||
configurable: true, | ||
enumerable: true, | ||
value: resolution.module.#LocalExportValues.get(resolution.bindingName), | ||
} | ||
} else { | ||
Object.defineProperty(env, i.LocalName, { | ||
propertiesToBeDefined[i.LocalName] = { | ||
get() { | ||
@@ -266,3 +279,3 @@ throw new ReferenceError(`Cannot access '${i.LocalName}' before initialization`) | ||
enumerable: true, | ||
}) | ||
} | ||
} | ||
@@ -272,9 +285,10 @@ } | ||
} | ||
for (const { ModuleRequest, ExportName, ImportName } of module.#LocalExportEntries) { | ||
assert(ModuleRequest === null && typeof ExportName === 'string' && ImportName === null) | ||
Object.defineProperty(env, ExportName, { | ||
propertiesToBeDefined[ExportName] = { | ||
get: () => this.#LocalExportValues.get(ExportName), | ||
set: (value) => { | ||
this.#LocalExportValues.set(ExportName, value) | ||
this.#ExportCallback.forEach((f) => f(ExportName)) | ||
this.#ExportCallback.get(ExportName)?.forEach((callback) => callback(value)) | ||
return true | ||
@@ -285,5 +299,7 @@ }, | ||
enumerable: true, | ||
}) | ||
} | ||
} | ||
Object.defineProperties(env, propertiesToBeDefined) | ||
for (const exports of module.#GetExportedNames()) { | ||
@@ -616,44 +632,51 @@ if (module.#ResolveExport(exports) === ambiguous) { | ||
static #GetModuleNamespace(module: Module): ModuleNamespace { | ||
if (module.#Namespace) return module.#Namespace | ||
assert(module.#Status !== ModuleStatus.unlinked) | ||
if (module.#Namespace) return module.#Namespace | ||
const exportedNames = module.#GetExportedNames() | ||
const unambiguousNames = [] | ||
const namespaceObject: ModuleNamespace = { __proto__: null } | ||
const propertiesToBeDefined: 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 (typeof resolution === 'object' && resolution !== null) { | ||
unambiguousNames.push(name) | ||
} | ||
} | ||
if (resolution === ambiguous || resolution === null) continue | ||
const namespace: ModuleNamespace = { __proto__: null } | ||
Object.defineProperty(namespace, Symbol.toStringTag, { value: 'Module' }) | ||
for (const name of exportedNames) { | ||
if (module.#LocalExportValues.has(name)) { | ||
Object.defineProperty(namespace, name, { | ||
enumerable: true, | ||
writable: true, | ||
value: module.#LocalExportValues.get(name), | ||
}) | ||
const { bindingName, module: targetModule } = resolution | ||
if (bindingName === namespace) { | ||
propertiesToBeDefined[name] = { enumerable: true, value: Module.#GetModuleNamespace(targetModule) } | ||
} else { | ||
Object.defineProperty(namespace, 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, | ||
if (targetModule.#LocalExportValues.has(bindingName)) { | ||
propertiesToBeDefined[name] = { | ||
enumerable: true, | ||
// Note: this should not be configurable, but it's a trade-off for DX. | ||
configurable: true, | ||
value: targetModule.#LocalExportValues.get(bindingName)!, | ||
} | ||
} 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, | ||
} | ||
} | ||
module.#AddLiveExportCallback(name, (newValue) => { | ||
Object.defineProperty(namespaceObject, name, { | ||
enumerable: true, | ||
writable: true, | ||
value: newValue, | ||
}) | ||
}) | ||
} | ||
} | ||
module.#ExportCallback.add((name) => { | ||
Object.defineProperty(namespace, name, { | ||
enumerable: true, | ||
writable: true, | ||
value: module.#LocalExportValues.get(name), | ||
}) | ||
}) | ||
const proxy = new Proxy(namespace, moduleNamespaceExoticMethods) | ||
module.#Namespace = proxy | ||
return proxy | ||
Object.defineProperties(namespaceObject, propertiesToBeDefined) | ||
return namespaceProxy | ||
} | ||
@@ -660,0 +683,0 @@ //#endregion |
import type { VirtualModuleRecord, Binding, ExportAllBinding, ImportBinding } from '../types.js' | ||
import { unreachable } from './assert.js' | ||
import { assert, unreachable } from './assert.js' | ||
import { hasFromField, isExportAllBinding, isExportBinding, isImportAllBinding, isImportBinding } from './shapeCheck.js' | ||
@@ -112,3 +112,2 @@ import { all, allButDefault, namespace, type ModuleExportEntry, type ModuleImportEntry } from './spec.js' | ||
if (isImportBinding(binding)) { | ||
requestedModules.push(binding.from) | ||
importEntries.push({ | ||
@@ -120,3 +119,2 @@ ImportName: binding.import, | ||
} else if (isImportAllBinding(binding)) { | ||
requestedModules.push(binding.importAllFrom) | ||
importEntries.push({ | ||
@@ -129,4 +127,2 @@ ImportName: namespace, | ||
} | ||
const importedBoundNames = importEntries.map((x) => x.LocalName) | ||
const indirectExportEntries: ModuleExportEntry[] = [] | ||
@@ -137,39 +133,28 @@ const localExportEntries: ModuleExportEntry[] = [] | ||
for (const binding of bindings) { | ||
if (isExportBinding(binding)) { | ||
if (hasFromField(binding)) { | ||
requestedModules.push(binding.from) | ||
// step 10.a: if ee.ModuleRequest is null, then append ee to localExportEntries | ||
// export { ... } | ||
if (isExportBinding(binding) && !hasFromField(binding)) { | ||
localExportEntries.push({ | ||
ExportName: binding.as ?? binding.export, | ||
ImportName: null, | ||
ModuleRequest: null, | ||
}) | ||
} | ||
// step 10.b: else if ee.ImportName is all-but-default, then append ee to starExportEntries | ||
// export * from '...' | ||
else if (isExportAllBinding(binding) && binding.as === undefined) { | ||
starExportEntries.push({ | ||
// LocalName: null, | ||
ExportName: null, | ||
ImportName: allButDefault, | ||
ModuleRequest: binding.exportAllFrom, | ||
}) | ||
} | ||
// step 10.c: else, append ee to indirectExportEntries | ||
// export * as T from '...' | ||
// export { T } from '...' | ||
else { | ||
if (isExportAllBinding(binding)) { | ||
assert(binding.as !== undefined) | ||
indirectExportEntries.push({ | ||
ExportName: binding.as ?? binding.export, | ||
ImportName: binding.export, | ||
// LocalName: null, | ||
ModuleRequest: binding.from, | ||
}) | ||
} else { | ||
const ee: ModuleExportEntry = { | ||
ExportName: binding.as ?? binding.export, | ||
// LocalName: binding.export, | ||
ImportName: null, | ||
ModuleRequest: null, | ||
} | ||
if (!importedBoundNames.includes(binding.export)) { | ||
localExportEntries.push(ee) | ||
} else { | ||
const ie = importEntries.find((x) => x.LocalName === binding.export)! | ||
if (ie.ImportName === namespace) { | ||
localExportEntries.push(ee) | ||
} else { | ||
indirectExportEntries.push({ | ||
ModuleRequest: ie.ModuleRequest, | ||
ImportName: ie.ImportName, | ||
ExportName: ee.ExportName, | ||
}) | ||
} | ||
} | ||
} | ||
} else if (isExportAllBinding(binding)) { | ||
requestedModules.push(binding.exportAllFrom) | ||
if (typeof binding.as === 'string') { | ||
// export * as name from 'mod' | ||
starExportEntries.push({ | ||
// LocalName: binding.as, | ||
ExportName: binding.as, | ||
@@ -179,9 +164,7 @@ ImportName: all, | ||
}) | ||
} else { | ||
// export * from 'mod' | ||
starExportEntries.push({ | ||
// LocalName: null, | ||
ExportName: null, | ||
ImportName: allButDefault, | ||
ModuleRequest: binding.exportAllFrom, | ||
} else if (isExportBinding(binding)) { | ||
indirectExportEntries.push({ | ||
ExportName: binding.as ?? binding.export, | ||
ImportName: binding.export, | ||
ModuleRequest: binding.from, | ||
}) | ||
@@ -188,0 +171,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
147162
2541