diff --git a/packages/ses/NEWS.md b/packages/ses/NEWS.md index 49604228ad..10a93aa9af 100644 --- a/packages/ses/NEWS.md +++ b/packages/ses/NEWS.md @@ -25,6 +25,12 @@ User-visible changes in SES: the stacktrace line-numbers point back into the original source, as they do on Node without SES. +- Adds a `__noNamespaceBox__` option that aligns the behavior of the `import` + method on SES `Compartment` with the behavior of XS and the behavior we will + champion for compartment standards. + All use of `Compartment` should migrate to use this option as the standard + behavior will be enabled by default with the next major version of SES. + # v1.5.0 (2024-05-06) - Adds `importNowHook` to the `Compartment` options. The compartment will invoke the hook whenever it encounters a missing dependency while running `compartmentInstance.importNow(specifier)`, which cannot use an asynchronous `importHook`. diff --git a/packages/ses/src/compartment.js b/packages/ses/src/compartment.js index 8728a0b11f..270c8203cb 100644 --- a/packages/ses/src/compartment.js +++ b/packages/ses/src/compartment.js @@ -104,6 +104,8 @@ export const CompartmentPrototype = { }, async import(specifier) { + const { noNamespaceBox } = weakmapGet(privateFields, this); + if (typeof specifier !== 'string') { throw TypeError('first argument of import() must be a string'); } @@ -117,6 +119,11 @@ export const CompartmentPrototype = { /** @type {Compartment} */ (this), specifier, ); + if (noNamespaceBox) { + return namespace; + } + // Legacy behavior: box the namespace object so that thenable modules + // do not get coerced into a promise accidentally. return { namespace }; }, ); @@ -172,7 +179,11 @@ export const makeCompartmentConstructor = ( markVirtualizedNativeFunction, parentCompartment = undefined, ) => { - function Compartment(endowments = {}, moduleMap = {}, options = {}) { + function Compartment( + endowmentsOption = {}, + moduleMapOption = {}, + options = {}, + ) { if (new.target === undefined) { throw TypeError( "Class constructor Compartment cannot be invoked without 'new'", @@ -189,8 +200,11 @@ export const makeCompartmentConstructor = ( importNowHook, moduleMapHook, importMetaHook, + __noNamespaceBox__: noNamespaceBox = false, } = options; const globalTransforms = [...transforms, ...__shimTransforms__]; + const endowments = { __proto__: null, ...endowmentsOption }; + const moduleMap = { __proto__: null, ...moduleMapOption }; // Map const moduleRecords = new Map(); @@ -249,6 +263,7 @@ export const makeCompartmentConstructor = ( deferredExports, instances, parentCompartment, + noNamespaceBox, }); } diff --git a/packages/ses/test/compartment-transforms.test.js b/packages/ses/test/compartment-transforms.test.js index 04115b5158..2e18ff7081 100644 --- a/packages/ses/test/compartment-transforms.test.js +++ b/packages/ses/test/compartment-transforms.test.js @@ -52,9 +52,13 @@ test('transforms do not apply to imported modules', async t => { const resolveHook = () => ''; const importHook = () => new ModuleSource('export default "Farewell, World!";'); - const c = new Compartment({}, {}, { transforms, resolveHook, importHook }); + const c = new Compartment( + {}, + {}, + { transforms, resolveHook, importHook, __noNamespaceBox__: true }, + ); - const { namespace } = await c.import('any-string-here'); + const namespace = await c.import('any-string-here'); const { default: greeting } = namespace; t.is(greeting, 'Farewell, World!'); @@ -82,10 +86,15 @@ test('__shimTransforms__ do apply to imported modules', async t => { const c = new Compartment( {}, {}, - { __shimTransforms__: transforms, resolveHook, importHook }, + { + __shimTransforms__: transforms, + __noNamespaceBox__: true, + resolveHook, + importHook, + }, ); - const { namespace } = await c.import('any-string-here'); + const namespace = await c.import('any-string-here'); const { default: greeting } = namespace; t.is(greeting, 'Hello, World!'); diff --git a/packages/ses/test/import-cjs.test.js b/packages/ses/test/import-cjs.test.js index efea862a4e..b80b47e92f 100644 --- a/packages/ses/test/import-cjs.test.js +++ b/packages/ses/test/import-cjs.test.js @@ -86,11 +86,13 @@ test('import a CommonJS module with exports assignment', async t => { ); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); const module = compartment.module('.'); - const { - namespace: { meaning }, - } = await compartment.import('.'); + const { meaning } = await compartment.import('.'); t.is(meaning, 42, 'exports seen'); t.is(module.meaning, 42, 'exports seen through deferred proxy'); @@ -109,11 +111,13 @@ test('import a CommonJS module with exports replacement', async t => { ); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); const module = compartment.module('.'); - const { - namespace: { default: meaning }, - } = await compartment.import('.'); + const { default: meaning } = await compartment.import('.'); t.is(meaning, 42, 'exports seen'); t.is(module.default, 42, 'exports seen through deferred proxy'); @@ -144,10 +148,12 @@ test('CommonJS module imports CommonJS module by name', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -178,8 +184,12 @@ test('CommonJS module imports CommonJS module as default', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { namespace } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const namespace = await compartment.import('./odd'); const { default: odd } = namespace; t.is(odd(1), true); @@ -211,10 +221,12 @@ test('ESM imports CommonJS module as default', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { default: odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -245,10 +257,12 @@ test('ESM imports CommonJS module as star', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { default: odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -281,10 +295,12 @@ test('ESM imports CommonJS module with replaced exports as star', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { default: odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -315,10 +331,12 @@ test('ESM imports CommonJS module by name', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { default: odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -349,10 +367,12 @@ test('CommonJS module imports ESM as default', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { default: odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -385,10 +405,12 @@ test('CommonJS module imports ESM by name', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -440,7 +462,11 @@ test('cross import ESM and CommonJS modules', async t => { throw Error(`Cannot load module for specifier ${specifier}`); }; - const compartment = new Compartment({ t }, {}, { resolveHook, importHook }); + const compartment = new Compartment( + { t }, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); await compartment.import('./src/main.js'); }); diff --git a/packages/ses/test/import-gauntlet.test.js b/packages/ses/test/import-gauntlet.test.js index 0135fd546e..14bdc61dad 100644 --- a/packages/ses/test/import-gauntlet.test.js +++ b/packages/ses/test/import-gauntlet.test.js @@ -49,10 +49,11 @@ test('import all from module', async t => { { resolveHook: resolveNode, importHook: makeImportHook('https://example.com'), + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import('./main.js'); + const namespace = await compartment.import('./main.js'); t.is(namespace.default.a, 10); t.is(namespace.default.b, 20); @@ -78,10 +79,11 @@ test('import named exports from me', async t => { { resolveHook: resolveNode, importHook: makeImportHook('https://example.com'), + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import('./main.js'); + const namespace = await compartment.import('./main.js'); t.is(namespace.default.fizz, 10); t.is(namespace.default.buzz, 20); @@ -106,10 +108,11 @@ test('import color from module', async t => { { resolveHook: resolveNode, importHook: makeImportHook('https://example.com'), + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import('./main.js'); + const namespace = await compartment.import('./main.js'); t.is(namespace.color, 'blue'); }); @@ -132,10 +135,11 @@ test('import and reexport', async t => { { resolveHook: resolveNode, importHook: makeImportHook('https://example.com'), + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import('./main.js'); + const namespace = await compartment.import('./main.js'); t.is(namespace.qux, 42); }); @@ -159,10 +163,11 @@ test('import and export all', async t => { { resolveHook: resolveNode, importHook: makeImportHook('https://example.com'), + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import('./main.js'); + const namespace = await compartment.import('./main.js'); t.is(namespace.alpha, 0); t.is(namespace.omega, 23); @@ -189,10 +194,11 @@ test('live binding', async t => { { resolveHook: resolveNode, importHook: makeImportHook('https://example.com'), + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import('./main.js'); + const namespace = await compartment.import('./main.js'); t.is(namespace.default, 'Hello, World!'); }); diff --git a/packages/ses/test/import-hook.test.js b/packages/ses/test/import-hook.test.js index 6a3cb4c8cf..12b5870008 100644 --- a/packages/ses/test/import-hook.test.js +++ b/packages/ses/test/import-hook.test.js @@ -23,9 +23,10 @@ test('import hook returns module source descriptor with precompiled module sourc } return undefined; }, + __noNamespaceBox__: true, }, ); - const { namespace: index } = await compartment.import('./index.js'); + const index = await compartment.import('./index.js'); t.is(index.default, 42); }); @@ -52,9 +53,10 @@ test('import hook returns module source descriptor with virtual module source', } return undefined; }, + __noNamespaceBox__: true, }, ); - const { namespace: index } = await compartment.import('./index.js'); + const index = await compartment.import('./index.js'); t.is(index.default, 42); }); @@ -100,9 +102,10 @@ test('import hook returns parent compartment module source descriptor with strin } return undefined; }, + __noNamespaceBox__: true, }, ); - const { namespace: index } = await compartment.import('./index.js'); + const index = await compartment.import('./index.js'); t.is(index.default, 42); }); @@ -182,9 +185,10 @@ test('import hook returns parent compartment module source reference with differ } return undefined; }, + __noNamespaceBox__: true, }, ); - const { namespace: index } = await compartment.import('./index.js'); + const index = await compartment.import('./index.js'); t.is(index.default, 42); }); @@ -205,12 +209,11 @@ test('import hook returns module source descriptor for parent compartment with s } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: parentObject }, - } = await parent.import('./object.js'); + const { default: parentObject } = await parent.import('./object.js'); t.is(parentObject.meaning, 42); const compartment = new parent.globalThis.Compartment( @@ -230,12 +233,11 @@ test('import hook returns module source descriptor for parent compartment with s } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: childObject }, - } = await compartment.import('./index.js'); + const { default: childObject } = await compartment.import('./index.js'); t.is(childObject.meaning, 42); // Separate instances t.not(childObject, parentObject); @@ -258,12 +260,11 @@ test('import hook returns parent compartment module namespace descriptor', async } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: parentObject }, - } = await parent.import('./object.js'); + const { default: parentObject } = await parent.import('./object.js'); t.is(parentObject.meaning, 42); const compartment = new parent.globalThis.Compartment( @@ -283,12 +284,11 @@ test('import hook returns parent compartment module namespace descriptor', async } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: childObject }, - } = await compartment.import('./index.js'); + const { default: childObject } = await compartment.import('./index.js'); t.is(childObject.meaning, 42); // Same instances t.is(childObject, parentObject); @@ -311,12 +311,11 @@ test('import hook returns module source descriptor with string reference to pare } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: object1 }, - } = await compartment1.import('./object.js'); + const { default: object1 } = await compartment1.import('./object.js'); t.is(object1.meaning, 42); const compartment2 = new Compartment( @@ -336,12 +335,11 @@ test('import hook returns module source descriptor with string reference to pare } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: object2 }, - } = await compartment2.import('./index.js'); + const { default: object2 } = await compartment2.import('./index.js'); t.is(object2.meaning, 42); // Separate instances t.not(object1, object2); @@ -364,12 +362,11 @@ test('import hook returns other compartment module namespace descriptor', async } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: object1 }, - } = await compartment1.import('./object.js'); + const { default: object1 } = await compartment1.import('./object.js'); t.is(object1.meaning, 42); const compartment2 = new Compartment( @@ -389,12 +386,11 @@ test('import hook returns other compartment module namespace descriptor', async } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: object2 }, - } = await compartment2.import('./index.js'); + const { default: object2 } = await compartment2.import('./index.js'); t.is(object2.meaning, 42); // Same instances t.is(object1, object2); @@ -413,9 +409,10 @@ test('import hook returns module namespace descriptor and namespace object', asy } return undefined; }, + __noNamespaceBox__: true, }, ); - const { namespace: namespace1 } = await compartment1.import('a'); + const namespace1 = await compartment1.import('a'); const compartment2 = new Compartment( {}, {}, @@ -426,9 +423,10 @@ test('import hook returns module namespace descriptor and namespace object', asy } return undefined; }, + __noNamespaceBox__: true, }, ); - const { namespace: namespace2 } = await compartment2.import('z'); + const namespace2 = await compartment2.import('z'); t.is(namespace2.default, 42); t.is(namespace1, namespace2); }); @@ -444,9 +442,10 @@ test('import hook returns module namespace descriptor and non-namespace object', } return undefined; }, + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import('1'); + const namespace = await compartment.import('1'); t.is(namespace.meaning, 42); }); @@ -471,16 +470,13 @@ test('import hook returns module source descriptor for specifier in own compartm } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: object1 }, - } = await compartment.import('./object.js'); + const { default: object1 } = await compartment.import('./object.js'); t.is(object1.meaning, 42); - const { - namespace: { default: object2 }, - } = await compartment.import('./index.js'); + const { default: object2 } = await compartment.import('./index.js'); t.is(object2.meaning, 42); // Separate instances t.not(object1, object2); @@ -508,16 +504,13 @@ test('import hook returns module source descriptor for specifier in own compartm } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: object1 }, - } = await compartment.import('./object.js'); + const { default: object1 } = await compartment.import('./object.js'); t.is(object1.meaning, 42); - const { - namespace: { default: object2 }, - } = await compartment.import('./index.js'); + const { default: object2 } = await compartment.import('./index.js'); t.is(object2.meaning, 42); // Fails to obtain separate instance due to specifier collison. t.is(object1, object2); @@ -544,16 +537,13 @@ test('import hook returns module namespace descriptor for specifier in own compa } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: object1 }, - } = await compartment.import('./object.js'); + const { default: object1 } = await compartment.import('./object.js'); t.is(object1.meaning, 42); - const { - namespace: { default: object2 }, - } = await compartment.import('./index.js'); + const { default: object2 } = await compartment.import('./index.js'); t.is(object2.meaning, 42); // Same instances t.is(object1, object2); @@ -580,11 +570,10 @@ test('module map hook precedes import hook', async t => { importHook() { throw new Error('not reached'); }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: meaning }, - } = await compartment.import('./index.js'); + const { default: meaning } = await compartment.import('./index.js'); t.is(meaning, 42); }); diff --git a/packages/ses/test/import-legacy.test.js b/packages/ses/test/import-legacy.test.js new file mode 100644 index 0000000000..fa08cb231f --- /dev/null +++ b/packages/ses/test/import-legacy.test.js @@ -0,0 +1,651 @@ +// These tests exercise the Compartment import interface and linkage +// between compartments, and Compartment endowments. + +/* eslint max-lines: 0 */ + +import test from 'ava'; +import '../index.js'; +import { resolveNode, makeNodeImporter } from './node.js'; +import { makeImporter, makeStaticRetriever } from './import-commons.js'; + +// This test demonstrates a system of modules in a single Compartment +// that uses fully qualified URLs as module specifiers and module locations, +// not distinguishing one from the other. +test('import within one compartment, web resolution', async t => { + t.plan(1); + + const retrieve = makeStaticRetriever({ + 'https://example.com/packages/example/half.js': ` + export default 21; + `, + 'https://example.com/packages/example/': ` + import half from 'half.js'; + export const meaning = double(half); + `, + }); + const locate = moduleSpecifier => moduleSpecifier; + const resolveHook = (spec, referrer) => new URL(spec, referrer).toString(); + const importHook = makeImporter(locate, retrieve); + + const compartment = new Compartment( + // endowments: + { + double: n => n * 2, + }, + // module map: + {}, + // options: + { + resolveHook, + importHook, + }, + ); + + const { namespace } = await compartment.import( + 'https://example.com/packages/example/', + ); + + t.is(namespace.meaning, 42, 'dynamically imports the meaning'); +}); + +// This case demonstrates the same arrangement except that the Compartment uses +// Node.js module specifier resolution. +test('import within one compartment, node resolution', async t => { + t.plan(1); + + const makeImportHook = makeNodeImporter({ + 'https://example.com/packages/example/half.js': ` + export default 21; + `, + 'https://example.com/packages/example/main.js': ` + import half from './half.js'; + export const meaning = double(half); + `, + }); + + const compartment = new Compartment( + // endowments: + { + double: n => n * 2, + }, + // module map: + {}, + // options: + { + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/example'), + }, + ); + + const { namespace } = await compartment.import('./main.js'); + + t.is(namespace.meaning, 42, 'dynamically imports the meaning'); +}); + +// This demonstrates a pair of linked Node.js compartments. +test('two compartments, three modules, one endowment', async t => { + t.plan(1); + + const makeImportHook = makeNodeImporter({ + 'https://example.com/packages/example/half.js': ` + if (typeof double !== 'undefined') { + throw Error('Unexpected leakage of double(n) endowment: ' + typeof double); + } + export default 21; + `, + 'https://example.com/packages/example/main.js': ` + import half from './half.js'; + import double from 'double'; + export const meaning = double(half); + `, + 'https://example.com/packages/double/main.js': ` + export default double; + `, + }); + + const doubleCompartment = new Compartment( + // endowments: + { + double: n => n * 2, + }, + // module map: + {}, + // options: + { + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/double'), + }, + ); + + const compartment = new Compartment( + // endowments: + {}, + // module map: + { + // Notably, this is the first case where we thread a depencency between + // two compartments, using the sigil of one's namespace to indicate + // linkage before the module has been loaded. + double: doubleCompartment.module('./main.js'), + }, + // options: + { + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/example'), + }, + ); + + const { namespace } = await compartment.import('./main.js'); + + t.is(namespace.meaning, 42, 'dynamically imports the meaning'); +}); + +test('module exports namespace as an object', async t => { + t.plan(7); + + const makeImportHook = makeNodeImporter({ + 'https://example.com/packages/meaning/main.js': ` + export const meaning = 42; + `, + }); + + const compartment = new Compartment( + // endowments: + {}, + // module map: + {}, + // options: + { + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/meaning'), + }, + ); + + const { namespace } = await compartment.import('./main.js'); + + t.is( + namespace.meaning, + 42, + 'exported constant must have a namespace property', + ); + + t.throws( + () => { + namespace.alternateMeaning = 10; + }, + { message: /^Cannot set property/ }, + ); + + // The first should not throw. + t.truthy( + Reflect.preventExtensions(namespace), + 'extensions must be preventable', + ); + // The second should agree. + t.truthy( + Reflect.preventExtensions(namespace), + 'preventing extensions must be idempotent', + ); + + const desc = Object.getOwnPropertyDescriptor(namespace, 'meaning'); + t.is( + typeof desc, + 'object', + 'property descriptor for defined export must be an object', + ); + t.is(desc.set, undefined, 'constant export must not be writeable'); + + t.is( + Object.getPrototypeOf(namespace), + null, + 'module exports namespace prototype must be null', + ); +}); + +test('modules are memoized', async t => { + t.plan(1); + + const makeImportHook = makeNodeImporter({ + 'https://example.com/packages/example/c-s-lewis.js': ` + export const entity = {}; + `, + 'https://example.com/packages/example/clive-hamilton.js': ` + import { entity } from './c-s-lewis.js'; + export default entity; + `, + 'https://example.com/packages/example/n-w-clerk.js': ` + import { entity } from './c-s-lewis.js'; + export default entity; + `, + 'https://example.com/packages/example/main.js': ` + import clive from './clive-hamilton.js'; + import clerk from './n-w-clerk.js'; + export default { clerk, clive }; + `, + }); + + const compartment = new Compartment( + // endowments: + {}, + // module map: + {}, + // options: + { + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/example'), + }, + ); + + const { namespace } = await compartment.import('./main.js'); + const { clive, clerk } = namespace; + + t.truthy(clive === clerk, 'diamond dependency must refer to the same module'); +}); + +test('compartments with same sources do not share instances', async t => { + t.plan(1); + + const makeImportHook = makeNodeImporter({ + 'https://example.com/packages/arm/main.js': ` + export default {}; + `, + }); + + const leftCompartment = new Compartment( + {}, // endowments + {}, // module map + { + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/arm'), + }, + ); + + const rightCompartment = new Compartment( + {}, // endowments + {}, // module map + { + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/packages/arm'), + }, + ); + + const [ + { + namespace: { default: leftArm }, + }, + { + namespace: { default: rightArm }, + }, + ] = await Promise.all([ + leftCompartment.import('./main.js'), + rightCompartment.import('./main.js'), + ]); + + t.truthy( + leftArm !== rightArm, + 'different compartments with same sources do not share instances', + ); +}); + +const trimModuleSpecifierPrefix = (moduleSpecifier, prefix) => { + if (moduleSpecifier === prefix) { + return './index.js'; + } + if (moduleSpecifier.startsWith(`${prefix}/`)) { + return `./${moduleSpecifier.slice(prefix.length + 1)}`; + } + return undefined; +}; + +test('module map hook', async t => { + t.plan(2); + + const makeImportHook = makeNodeImporter({ + 'https://example.com/main.js': ` + import dependency from 'dependency'; + import utility from 'dependency/utility.js'; + + t.is(dependency, "dependency"); + t.is(utility, "utility"); + `, + 'https://example.com/dependency/index.js': ` + export default "dependency"; + `, + 'https://example.com/dependency/utility.js': ` + export default "utility"; + `, + }); + + const dependency = new Compartment( + {}, + {}, + { + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/dependency'), + }, + ); + + const compartment = new Compartment( + { t }, + {}, + { + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + moduleMapHook: moduleSpecifier => { + const remainder = trimModuleSpecifierPrefix( + moduleSpecifier, + 'dependency', + ); + if (remainder) { + return dependency.module(remainder); + } + return undefined; + }, + }, + ); + + await compartment.import('./main.js'); +}); + +test('mutual dependency between compartments', async t => { + t.plan(12); + + const makeImportHook = makeNodeImporter({ + 'https://example.com/main.js': ` + import isEven from "even"; + import isOdd from "odd"; + + for (const n of [0, 2, 4]) { + t.truthy(isEven(n), \`\${n} should be even\`); + t.truthy(!isOdd(n), \`\${n} should not be odd\`); + } + for (const n of [1, 3, 5]) { + t.truthy(isOdd(n), \`\${n} should be odd\`); + t.truthy(!isEven(n), \`\${n} should not be even\`); + } + `, + 'https://example.com/even/index.js': ` + import isOdd from "odd"; + export default n => n === 0 || isOdd(n - 1); + `, + 'https://example.com/odd/index.js': ` + import isEven from "even"; + export default n => n !== 0 && isEven(n - 1); + `, + }); + + const moduleMapHook = moduleSpecifier => { + // Mutual dependency ahead: + // eslint-disable-next-line no-use-before-define + for (const [prefix, compartment] of Object.entries({ even, odd })) { + const remainder = trimModuleSpecifierPrefix(moduleSpecifier, prefix); + if (remainder) { + return compartment.module(remainder); + } + } + return undefined; + }; + + const even = new Compartment( + {}, + {}, + { + name: 'https://example.com/even', + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/even'), + moduleMapHook, + }, + ); + + const odd = new Compartment( + {}, + {}, + { + name: 'https://example.com/odd', + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/odd'), + moduleMapHook, + }, + ); + + const compartment = new Compartment( + { t }, + {}, + { + name: 'https://example.com', + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + moduleMapHook, + }, + ); + + await compartment.import('./main.js'); +}); + +test('import redirect shorthand', async t => { + // The following use of Math.random() is informative but does not + // affect the outcome of the test, just makes the nature of the error + // obvious in test output. + // The containing objects should be identical. + // The contained value should incidentally be identical. + // The test depends on the former. + + const makeImportHook = makeNodeImporter({ + 'https://example.com/main/index.js': ` + export const unique = {n: Math.random()}; + export const meaning = 42; + `, + }); + + const wrappedImportHook = makeImportHook('https://example.com'); + + const importHook = async specifier => { + await null; + const candidates = [specifier, `${specifier}.js`, `${specifier}/index.js`]; + for (const candidate of candidates) { + // eslint-disable-next-line no-await-in-loop + const record = await wrappedImportHook(candidate).catch(_ => undefined); + // return a RedirectStaticModuleInterface with an explicit record + if (record !== undefined) { + return { record, specifier }; + } + } + throw Error(`Cannot find module ${specifier}`); + }; + + const compartment = new Compartment( + { + Math, + }, + {}, + { + resolveHook: resolveNode, + importHook, + }, + ); + + const { namespace } = await compartment.import('./main'); + t.is( + namespace.meaning, + 42, + 'dynamically imports the meaning through a redirect', + ); + + // TODO The following commented test does not pass, and might not be valid. + // Web browsers appear to have taken the stance that they will load a static + // module record once per *response url* and create unique a unique module + // instance per *request url*. + // + // const { namespace: aliasNamespace } = await compartment.import( + // './main/index.js', + // ); + // t.strictEqual( + // namespace.unique, + // aliasNamespace.unique, + // 'alias modules have identical instance', + // ); +}); + +test('import reflexive module alias', async t => { + t.plan(1); + + const makeImportHook = makeNodeImporter({ + 'https://example.com/index.js': ` + import self from 'self'; + export default 10; + t.is(self, 10); + `, + }); + + const wrappedImportHook = makeImportHook('https://example.com'); + + const importHook = async specifier => { + await null; + const candidates = [specifier, `${specifier}.js`, `${specifier}/index.js`]; + for (const candidate of candidates) { + // eslint-disable-next-line no-await-in-loop + const record = await wrappedImportHook(candidate).catch(_ => undefined); + if (record !== undefined) { + // return a RedirectStaticModuleInterface with an explicit record + return { record, specifier }; + } + } + throw Error(`Cannot find module ${specifier}`); + }; + + const moduleMapHook = specifier => { + if (specifier === 'self') { + // eslint-disable-next-line no-use-before-define + return compartment.module('./index.js'); + } + return undefined; + }; + + const compartment = new Compartment( + { + t, + }, + {}, + { + resolveHook: resolveNode, + importHook, + moduleMapHook, + }, + ); + + await compartment.import('./index.js'); +}); + +test('child compartments are modular', async t => { + t.plan(1); + + const makeImportHook = makeNodeImporter({ + 'https://example.com/index.js': ` + export default 42; + `, + }); + + const parent = new Compartment(); + const compartment = new parent.globalThis.Compartment( + {}, // endowments + {}, // module map + { + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com/'), + }, + ); + + const { + namespace: { default: meaning }, + } = await compartment.import('./index.js'); + + t.is(meaning, 42, 'child compartments have module support'); +}); + +test('import.meta populated from module record', async t => { + t.plan(1); + + const makeImportHook = makeNodeImporter({ + 'https://example.com/index.js': ` + const myloc = import.meta.url; + export default myloc; + `, + }); + + const compartment = new Compartment( + { t }, + {}, + { + name: 'https://example.com', + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com', { + meta: { url: 'https://example.com/index.js' }, + }), + }, + ); + + const { + namespace: { default: metaurl }, + } = await compartment.import('./index.js'); + t.is(metaurl, 'https://example.com/index.js'); +}); + +test('importMetaHook', async t => { + t.plan(1); + + const makeImportHook = makeNodeImporter({ + 'https://example.com/index.js': ` + const myloc = import.meta.url; + export default myloc; + `, + }); + + const compartment = new Compartment( + { t }, + {}, + { + name: 'https://example.com', + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com'), + importMetaHook: (_moduleSpecifier, meta) => { + meta.url = 'https://example.com/index.js'; + }, + }, + ); + + const { + namespace: { default: metaurl }, + } = await compartment.import('./index.js'); + t.is(metaurl, 'https://example.com/index.js'); +}); + +test('importMetaHook and meta from record', async t => { + t.plan(1); + + const makeImportHook = makeNodeImporter({ + 'https://example.com/index.js': ` + const myloc = import.meta.url; + export default myloc; + `, + }); + + const compartment = new Compartment( + { t }, + {}, + { + name: 'https://example.com', + resolveHook: resolveNode, + importHook: makeImportHook('https://example.com', { + meta: { url: 'https://example.com/index.js' }, + }), + importMetaHook: (_moduleSpecifier, meta) => { + meta.url += '?foo'; + meta.isStillMutableHopefully = 1; + }, + }, + ); + + const { + namespace: { default: metaurl }, + } = await compartment.import('./index.js'); + t.is(metaurl, 'https://example.com/index.js?foo'); +}); diff --git a/packages/ses/test/import-non-esm.test.js b/packages/ses/test/import-non-esm.test.js index 6cf0fe7d14..bb9e5211f4 100644 --- a/packages/ses/test/import-non-esm.test.js +++ b/packages/ses/test/import-non-esm.test.js @@ -17,11 +17,13 @@ test('import a non-ESM', async t => { }; }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); const module = compartment.module('.'); - const { - namespace: { meaning }, - } = await compartment.import('.'); + const { meaning } = await compartment.import('.'); t.is(meaning, 42, 'exports seen'); t.is(module.meaning, 42, 'exports seen through deferred proxy'); @@ -54,10 +56,12 @@ test('non-ESM imports non-ESM by name', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -90,10 +94,12 @@ test('non-ESM imports non-ESM as default', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { default: odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -125,10 +131,12 @@ test('ESM imports non-ESM as default', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { default: odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -160,10 +168,12 @@ test('ESM imports non-ESM by name', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -195,10 +205,12 @@ test('non-ESM imports ESM as default', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { default: odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { default: odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -230,10 +242,12 @@ test('non-ESM imports ESM by name', async t => { throw Error(`Cannot load module ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); - const { - namespace: { odd }, - } = await compartment.import('./odd'); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); + const { odd } = await compartment.import('./odd'); t.is(odd(1), true); t.is(odd(2), false); @@ -290,6 +304,10 @@ test('cross import ESM and non-ESMs', async t => { throw Error(`Cannot load module for specifier ${specifier}`); }; - const compartment = new Compartment({}, {}, { resolveHook, importHook }); + const compartment = new Compartment( + {}, + {}, + { resolveHook, importHook, __noNamespaceBox__: true }, + ); await compartment.import('./src/main.js'); }); diff --git a/packages/ses/test/import.test.js b/packages/ses/test/import.test.js index fa08cb231f..d2bc5365a9 100644 --- a/packages/ses/test/import.test.js +++ b/packages/ses/test/import.test.js @@ -28,20 +28,18 @@ test('import within one compartment, web resolution', async t => { const importHook = makeImporter(locate, retrieve); const compartment = new Compartment( - // endowments: { double: n => n * 2, }, - // module map: {}, - // options: { resolveHook, importHook, + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import( + const namespace = await compartment.import( 'https://example.com/packages/example/', ); @@ -64,20 +62,18 @@ test('import within one compartment, node resolution', async t => { }); const compartment = new Compartment( - // endowments: { double: n => n * 2, }, - // module map: {}, - // options: { resolveHook: resolveNode, importHook: makeImportHook('https://example.com/packages/example'), + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import('./main.js'); + const namespace = await compartment.import('./main.js'); t.is(namespace.meaning, 42, 'dynamically imports the meaning'); }); @@ -104,13 +100,10 @@ test('two compartments, three modules, one endowment', async t => { }); const doubleCompartment = new Compartment( - // endowments: { double: n => n * 2, }, - // module map: {}, - // options: { resolveHook: resolveNode, importHook: makeImportHook('https://example.com/packages/double'), @@ -118,23 +111,24 @@ test('two compartments, three modules, one endowment', async t => { ); const compartment = new Compartment( - // endowments: {}, - // module map: { // Notably, this is the first case where we thread a depencency between // two compartments, using the sigil of one's namespace to indicate // linkage before the module has been loaded. - double: doubleCompartment.module('./main.js'), + double: { + namespace: './main.js', + compartment: doubleCompartment, + }, }, - // options: { resolveHook: resolveNode, importHook: makeImportHook('https://example.com/packages/example'), + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import('./main.js'); + const namespace = await compartment.import('./main.js'); t.is(namespace.meaning, 42, 'dynamically imports the meaning'); }); @@ -149,18 +143,16 @@ test('module exports namespace as an object', async t => { }); const compartment = new Compartment( - // endowments: {}, - // module map: {}, - // options: { resolveHook: resolveNode, importHook: makeImportHook('https://example.com/packages/meaning'), + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import('./main.js'); + const namespace = await compartment.import('./main.js'); t.is( namespace.meaning, @@ -224,18 +216,16 @@ test('modules are memoized', async t => { }); const compartment = new Compartment( - // endowments: {}, - // module map: {}, - // options: { resolveHook: resolveNode, importHook: makeImportHook('https://example.com/packages/example'), + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import('./main.js'); + const namespace = await compartment.import('./main.js'); const { clive, clerk } = namespace; t.truthy(clive === clerk, 'diamond dependency must refer to the same module'); @@ -251,31 +241,26 @@ test('compartments with same sources do not share instances', async t => { }); const leftCompartment = new Compartment( - {}, // endowments - {}, // module map + {}, + {}, { resolveHook: resolveNode, importHook: makeImportHook('https://example.com/packages/arm'), + __noNamespaceBox__: true, }, ); const rightCompartment = new Compartment( - {}, // endowments - {}, // module map + {}, + {}, { resolveHook: resolveNode, importHook: makeImportHook('https://example.com/packages/arm'), + __noNamespaceBox__: true, }, ); - const [ - { - namespace: { default: leftArm }, - }, - { - namespace: { default: rightArm }, - }, - ] = await Promise.all([ + const [{ default: leftArm }, { default: rightArm }] = await Promise.all([ leftCompartment.import('./main.js'), rightCompartment.import('./main.js'), ]); @@ -321,6 +306,7 @@ test('module map hook', async t => { { resolveHook: resolveNode, importHook: makeImportHook('https://example.com/dependency'), + __noNamespaceBox__: true, }, ); @@ -336,10 +322,14 @@ test('module map hook', async t => { 'dependency', ); if (remainder) { - return dependency.module(remainder); + return { + namespace: remainder, + compartment: dependency, + }; } return undefined; }, + __noNamespaceBox__: true, }, ); @@ -379,7 +369,10 @@ test('mutual dependency between compartments', async t => { for (const [prefix, compartment] of Object.entries({ even, odd })) { const remainder = trimModuleSpecifierPrefix(moduleSpecifier, prefix); if (remainder) { - return compartment.module(remainder); + return { + compartment, + namespace: remainder, + }; } } return undefined; @@ -393,6 +386,7 @@ test('mutual dependency between compartments', async t => { resolveHook: resolveNode, importHook: makeImportHook('https://example.com/even'), moduleMapHook, + __noNamespaceBox__: true, }, ); @@ -404,6 +398,8 @@ test('mutual dependency between compartments', async t => { resolveHook: resolveNode, importHook: makeImportHook('https://example.com/odd'), moduleMapHook, + [Symbol.for('options')]: true, + __noNamespaceBox__: true, }, ); @@ -415,6 +411,7 @@ test('mutual dependency between compartments', async t => { resolveHook: resolveNode, importHook: makeImportHook('https://example.com'), moduleMapHook, + __noNamespaceBox__: true, }, ); @@ -453,17 +450,16 @@ test('import redirect shorthand', async t => { }; const compartment = new Compartment( - { - Math, - }, + { Math }, {}, { resolveHook: resolveNode, importHook, + __noNamespaceBox__: true, }, ); - const { namespace } = await compartment.import('./main'); + const namespace = await compartment.import('./main'); t.is( namespace.meaning, 42, @@ -514,21 +510,23 @@ test('import reflexive module alias', async t => { const moduleMapHook = specifier => { if (specifier === 'self') { - // eslint-disable-next-line no-use-before-define - return compartment.module('./index.js'); + return { + // eslint-disable-next-line no-use-before-define + compartment, + namespace: './index.js', + }; } return undefined; }; const compartment = new Compartment( - { - t, - }, + { t }, {}, { resolveHook: resolveNode, importHook, moduleMapHook, + __noNamespaceBox__: true, }, ); @@ -580,12 +578,11 @@ test('import.meta populated from module record', async t => { importHook: makeImportHook('https://example.com', { meta: { url: 'https://example.com/index.js' }, }), + __noNamespaceBox__: true, }, ); - const { - namespace: { default: metaurl }, - } = await compartment.import('./index.js'); + const { default: metaurl } = await compartment.import('./index.js'); t.is(metaurl, 'https://example.com/index.js'); }); @@ -609,12 +606,11 @@ test('importMetaHook', async t => { importMetaHook: (_moduleSpecifier, meta) => { meta.url = 'https://example.com/index.js'; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: metaurl }, - } = await compartment.import('./index.js'); + const { default: metaurl } = await compartment.import('./index.js'); t.is(metaurl, 'https://example.com/index.js'); }); @@ -641,11 +637,10 @@ test('importMetaHook and meta from record', async t => { meta.url += '?foo'; meta.isStillMutableHopefully = 1; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: metaurl }, - } = await compartment.import('./index.js'); + const { default: metaurl } = await compartment.import('./index.js'); t.is(metaurl, 'https://example.com/index.js?foo'); }); diff --git a/packages/ses/test/module-map-hook.test.js b/packages/ses/test/module-map-hook.test.js index 90eab3e45f..38c1238195 100644 --- a/packages/ses/test/module-map-hook.test.js +++ b/packages/ses/test/module-map-hook.test.js @@ -21,9 +21,10 @@ test('module map hook returns module source descriptor with precompiled module s } return undefined; }, + __noNamespaceBox__: true, }, ); - const { namespace: index } = await compartment.import('./index.js'); + const index = await compartment.import('./index.js'); t.is(index.default, 42); }); @@ -50,9 +51,10 @@ test('module map hook returns module source descriptor with virtual module sour } return undefined; }, + __noNamespaceBox__: true, }, ); - const { namespace: index } = await compartment.import('./index.js'); + const index = await compartment.import('./index.js'); t.is(index.default, 42); }); @@ -98,9 +100,10 @@ test('module map hook returns parent compartment module source descriptor with s } return undefined; }, + __noNamespaceBox__: true, }, ); - const { namespace: index } = await compartment.import('./index.js'); + const index = await compartment.import('./index.js'); t.is(index.default, 42); }); @@ -180,9 +183,10 @@ test('module map hook returns parent compartment module source reference with di } return undefined; }, + __noNamespaceBox__: true, }, ); - const { namespace: index } = await compartment.import('./index.js'); + const index = await compartment.import('./index.js'); t.is(index.default, 42); }); @@ -203,12 +207,11 @@ test('module map hook returns module source descriptor for parent compartment wi } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: parentObject }, - } = await parent.import('./object.js'); + const { default: parentObject } = await parent.import('./object.js'); t.is(parentObject.meaning, 42); const compartment = new parent.globalThis.Compartment( @@ -228,12 +231,11 @@ test('module map hook returns module source descriptor for parent compartment wi } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: childObject }, - } = await compartment.import('./index.js'); + const { default: childObject } = await compartment.import('./index.js'); t.is(childObject.meaning, 42); // Separate instances t.not(childObject, parentObject); @@ -256,12 +258,11 @@ test('module map hook returns parent compartment module namespace descriptor', a } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: parentObject }, - } = await parent.import('./object.js'); + const { default: parentObject } = await parent.import('./object.js'); t.is(parentObject.meaning, 42); const compartment = new parent.globalThis.Compartment( @@ -281,12 +282,11 @@ test('module map hook returns parent compartment module namespace descriptor', a } return undefined; }, + __noNamespaceBox__: true, }, ); - const { - namespace: { default: childObject }, - } = await compartment.import('./index.js'); + const { default: childObject } = await compartment.import('./index.js'); t.is(childObject.meaning, 42); // Same instances t.is(childObject, parentObject); diff --git a/packages/ses/test/module-map.test.js b/packages/ses/test/module-map.test.js index fc4de21cb0..e69ce3d27e 100644 --- a/packages/ses/test/module-map.test.js +++ b/packages/ses/test/module-map.test.js @@ -17,9 +17,10 @@ test('module map primed with module source descriptor with precompiled module so // options: { resolveHook: specifier => specifier, + __noNamespaceBox__: true, }, ); - const { namespace: index } = await compartment.import('./index.js'); + const index = await compartment.import('./index.js'); t.is(index.default, 42); }); @@ -42,9 +43,10 @@ test('module map primed with module source descriptor with virtual module source // options: { resolveHook: specifier => specifier, + __noNamespaceBox__: true, }, ); - const { namespace: index } = await compartment.import('./index.js'); + const index = await compartment.import('./index.js'); t.is(index.default, 42); }); @@ -82,9 +84,10 @@ test('module map primed with parent compartment module source descriptor with st // options: { resolveHook: specifier => specifier, + __noNamespaceBox__: true, }, ); - const { namespace: index } = await compartment.import('./index.js'); + const index = await compartment.import('./index.js'); t.is(index.default, 42); }); @@ -154,9 +157,10 @@ test('module map primed with parent compartment module source reference with dif t.is(specifier, './lib/meaning.js'); return './lib/meaningful.js'; }, + __noNamespaceBox__: true, }, ); - const { namespace: index } = await compartment.import('./index.js'); + const index = await compartment.import('./index.js'); t.is(index.default, 42); }); @@ -173,12 +177,11 @@ test('module map primed with module source descriptor for parent compartment wit // options: { name: 'parent', + __noNamespaceBox__: true, }, ); - const { - namespace: { default: parentObject }, - } = await parent.import('./object.js'); + const { default: parentObject } = await parent.import('./object.js'); t.is(parentObject.meaning, 42); const compartment = new parent.globalThis.Compartment( @@ -194,12 +197,11 @@ test('module map primed with module source descriptor for parent compartment wit // options: { name: 'child', + __noNamespaceBox__: true, }, ); - const { - namespace: { default: childObject }, - } = await compartment.import('./index.js'); + const { default: childObject } = await compartment.import('./index.js'); t.is(childObject.meaning, 42); // Separate instances t.not(childObject, parentObject); @@ -218,12 +220,11 @@ test('module map primed with parent compartment module namespace descriptor', as // options: { name: 'parent', + __noNamespaceBox__: true, }, ); - const { - namespace: { default: parentObject }, - } = await parent.import('./object.js'); + const { default: parentObject } = await parent.import('./object.js'); t.is(parentObject.meaning, 42); const compartment = new parent.globalThis.Compartment( @@ -239,12 +240,11 @@ test('module map primed with parent compartment module namespace descriptor', as // options: { name: 'child', + __noNamespaceBox__: true, }, ); - const { - namespace: { default: childObject }, - } = await compartment.import('./index.js'); + const { default: childObject } = await compartment.import('./index.js'); t.is(childObject.meaning, 42); // Same instances t.is(childObject, parentObject); @@ -263,12 +263,11 @@ test('module map primed with module source descriptor with string reference to p // options: { name: 'compartment1', + __noNamespaceBox__: true, }, ); - const { - namespace: { default: object1 }, - } = await compartment1.import('./object.js'); + const { default: object1 } = await compartment1.import('./object.js'); t.is(object1.meaning, 42); const compartment2 = new Compartment( @@ -284,12 +283,11 @@ test('module map primed with module source descriptor with string reference to p // options: { name: 'child', + __noNamespaceBox__: true, }, ); - const { - namespace: { default: object2 }, - } = await compartment2.import('./index.js'); + const { default: object2 } = await compartment2.import('./index.js'); t.is(object2.meaning, 42); // Separate instances t.not(object1, object2); @@ -308,12 +306,11 @@ test('module map primed with other compartment module namespace descriptor', asy // options: { name: 'compartment1', + __noNamespaceBox__: true, }, ); - const { - namespace: { default: object1 }, - } = await compartment1.import('./object.js'); + const { default: object1 } = await compartment1.import('./object.js'); t.is(object1.meaning, 42); const compartment2 = new Compartment( @@ -329,12 +326,11 @@ test('module map primed with other compartment module namespace descriptor', asy // options: { name: 'child', + __noNamespaceBox__: true, }, ); - const { - namespace: { default: object2 }, - } = await compartment2.import('./index.js'); + const { default: object2 } = await compartment2.import('./index.js'); t.is(object2.meaning, 42); // Same instances t.is(object1, object2); @@ -348,17 +344,21 @@ test('module map primed with module namespace descriptor and namespace object', source: new ModuleSource(`export default 42`), }, }, - {}, + { + __noNamespaceBox__: true, + }, ); - const { namespace: namespace1 } = await compartment1.import('a'); + const namespace1 = await compartment1.import('a'); const compartment2 = new Compartment( {}, { z: { namespace: namespace1 }, }, - {}, + { + __noNamespaceBox__: true, + }, ); - const { namespace: namespace2 } = await compartment2.import('z'); + const namespace2 = await compartment2.import('z'); t.is(namespace2.default, 42); t.is(namespace1, namespace2); }); @@ -369,9 +369,11 @@ test('module map primed with module namespace descriptor and non-namespace objec { 1: { namespace: { meaning: 42 } }, }, - {}, + { + __noNamespaceBox__: true, + }, ); - const { namespace } = await compartment.import('1'); + const namespace = await compartment.import('1'); t.is(namespace.meaning, 42); });