Skip to content

Instantly share code, notes, and snippets.

@kriskowal
Created May 15, 2020 00:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kriskowal/cd26728d04ccf32dcf0abdb8ec294706 to your computer and use it in GitHub Desktop.
Save kriskowal/cd26728d04ccf32dcf0abdb8ec294706 to your computer and use it in GitHub Desktop.
Compartment assembler demonstration.
import 'ses';
import { resolveNode, makeNodeImporter } from './node.js';
const { values, entries } = Object;
// gathers all the packages in a lockfile into a flat representation keyed by
// name and version.
const gather = (pkg, map, location) => {
const key = `${pkg.name}/${pkg.version}`;
if (pkg.dependencies != null) {
for (const dep of values(pkg.dependencies)) {
gather(dep, map, `${location}/node_modules/${dep.name}`);
}
}
pkg.location = location;
delete pkg.dependencies;
map[key] = pkg;
};
// depth first traversal of the package graph to construct compartments from
// the leaves and roll up their exported modules into the imported modules of
// their dependees on the return.
const link = (pkg, map, makeImportHook) => {
let imports = {};
if (pkg.requires != null) {
for (const [name, version] of entries(pkg.requires)) {
const key = `${name}/${version}`;
const dep = link(map[key], map, makeImportHook);
imports = {
...imports,
...dep.exports,
};
}
}
const compartment = new Compartment({}, imports, {
resolveHook: resolveNode,
importHook: makeImportHook(pkg.location),
});
const exports = {};
if (pkg.main != null) {
exports[pkg.name] = compartment.module(pkg.main);
}
return {
compartment,
exports,
};
};
// assemble takes a lockfile, a location, and an import hook maker (which
// curries on the location of a specific package) and returns the root
// compartment.
const assemble = (lock, location, makeImportHook) => {
const key = `${lock.name}/${lock.version}`;
const map = {};
gather(lock, map, location);
return link(map[key], map, makeImportHook).compartment;
};
(async () => {
const sources = {
'file:///my-app/src/main.js': `
import avery from 'avery';
import brooke from 'brooke';
import clarke from 'clarke';
export default avery * brooke * clarke; // 3 * 2 * 1 = 6
`,
'file:///my-app/node_modules/avery/src/main.js': `
import brooke from 'brooke';
import clarke from 'clarke';
export default brooke + clarke; // 2 + 1 = 3
`,
'file:///my-app/node_modules/avery/node_modules/brooke/src/main.js': `
import clarke from 'clarke';
export default clarke * 2; // 2
`,
'file:///my-app/node_modules/clarke/src/main.js': `
export default 1;
`,
};
const lock = {
name: 'my-app',
version: '1.0.0',
main: './src/main.js',
requires: {
avery: '1.0.0',
brooke: '1.0.0',
clarke: '1.0.0',
},
dependencies: {
avery: {
name: 'avery',
version: '1.0.0',
main: './src/main.js',
requires: {
brooke: '1.0.0',
clarke: '1.0.0',
},
dependencies: {
brooke: {
name: 'brooke',
version: '1.0.0',
main: './src/main.js',
requires: {
clarke: '1.0.0',
},
},
},
},
clarke: {
name: 'clarke',
version: '1.0.0',
main: './src/main.js',
},
},
};
const makeImportHook = makeNodeImporter(sources);
console.log(makeImportHook);
const compartment = assemble(lock, 'file:///my-app', makeImportHook);
const { namespace: avery } = await compartment.import('./src/main.js');
console.log(avery.default);
})();
@coder-mike
Copy link

Is a "Compartment assembler" something that automatically instantiates the compartment graph based on metadata?

Where can I find the implementation of ./node.js?

@kriskowal
Copy link
Author

Yeah, this assembler or composer (name TBD) builds a compartment graph and returns the main compartment, based on metadata. The lock variable above is loosely based on the content of a package-lock.json that npm would drop after messing with the node_modules tree. There’s a similar yarn.lock that could be used instead. Worst case, one can crawl the file system and aggregate package.json files for the same input. I’m starting with the lockfile because it would work with a web client, eliminating a build step.

There’s an example ./node.js in the SES tests, now on master. My intention is to move that to a plugin package.

@coder-mike
Copy link

Ok, thanks. This is a really useful demonstration. I'm going to take some time to digest it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment