Skip to content

Commit

Permalink
feat(ses): Add moduleMapHook (#419)
Browse files Browse the repository at this point in the history
Adds a `moduleMapHook` option to the `Compartment` constructor options.  The module map hook can return values for the `moduleMap` for any given module specifier, or return `undefined` to fall back to the `importHook`.  This allows for wildcard linkage to other compartments.
  • Loading branch information
kriskowal committed Aug 18, 2020
1 parent f6bf1c6 commit f053ba4
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 3 deletions.
6 changes: 5 additions & 1 deletion packages/ses/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ User-visible changes in SES:

## Next release

* No changes yet.
* Adds a `moduleMapHook` option to the `Compartment` constructor options.
The module map hook can return values for the `moduleMap` for
any given module specifier, or return `undefined` to fall back to the
`importHook`.
This allows for wildcard linkage to other compartments.

## Release 0.10.1 (13-August-2020)

Expand Down
2 changes: 2 additions & 0 deletions packages/ses/src/compartment-shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export const makeCompartmentConstructor = (intrinsics, nativeBrander) => {
globalLexicals = {},
resolveHook,
importHook,
moduleMapHook,
} = options;
const globalTransforms = [...transforms];

Expand Down Expand Up @@ -266,6 +267,7 @@ export const makeCompartmentConstructor = (intrinsics, nativeBrander) => {
resolveHook,
importHook,
moduleMap,
moduleMapHook,
moduleRecords,
deferredExports,
instances,
Expand Down
8 changes: 6 additions & 2 deletions packages/ses/src/module-load.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,15 @@ export const load = async (
resolveHook,
importHook,
moduleMap,
moduleMapHook,
moduleRecords,
} = compartmentPrivateFields.get(compartment);

// Follow moduleMap.
const aliasNamespace = moduleMap[moduleSpecifier];
// Follow moduleMap, or moduleMapHook if present.
let aliasNamespace = moduleMap[moduleSpecifier];
if (aliasNamespace === undefined && moduleMapHook !== undefined) {
aliasNamespace = moduleMapHook(moduleSpecifier);
}
if (typeof aliasNamespace === 'string') {
throw new TypeError(
`Cannot map module ${q(moduleSpecifier)} to ${q(
Expand Down
132 changes: 132 additions & 0 deletions packages/ses/test/import.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,135 @@ test('compartments with same sources do not share instances', async t => {
'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.equal(dependency, "dependency");
t.equal(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.ok(isEven(n), \`\${n} should be even\`);
t.ok(!isOdd(n), \`\${n} should not be odd\`);
}
for (const n of [1, 3, 5]) {
t.ok(isOdd(n), \`\${n} should be odd\`);
t.ok(!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(
{},
{},
{
resolveHook: resolveNode,
importHook: makeImportHook('https://example.com/even'),
moduleMapHook,
},
);

const odd = new Compartment(
{},
{},
{
resolveHook: resolveNode,
importHook: makeImportHook('https://example.com/odd'),
moduleMapHook,
},
);

const compartment = new Compartment(
{ t },
{},
{
resolveHook: resolveNode,
importHook: makeImportHook('https://example.com'),
moduleMapHook,
},
);

await compartment.import('./main.js');
});

0 comments on commit f053ba4

Please sign in to comment.