Skip to content

Commit

Permalink
feat(module-source): Specialize for XS native ModuleSource
Browse files Browse the repository at this point in the history
  • Loading branch information
kriskowal committed Oct 18, 2024
1 parent b399e75 commit 8ca8e06
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/module-source/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tmp
2 changes: 2 additions & 0 deletions packages/module-source/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ User-visible changes in `@endo/module-source`:

- Adds `@endo/module-source/shim.js` to shim `globalThis.ModuleSource`.
The shim currently replaces the native `globalThis.ModuleSource` if present.
- Provides an XS-specific variant of `@endo/module-source` that adapts the
native `ModuleSource` instead of entraining Babel.

# v1.0.0 (2024-07-30)

Expand Down
9 changes: 9 additions & 0 deletions packages/module-source/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ the hook returns a promise, it will be dropped and rejections will go uncaught.
If the hook must do async work, these should be queued up as a job that the
caller can later await.

## XS Specific Variant

With the `xs` condition, `@endo/module-source` will not entrain Babel and will
just adapt the native `ModuleSource` to the older interface presented by this
package.
That is, the XS native `bindings` will be translated to `imports`, `exports`,
and `reexports` getters.
This form of `ModuleSource` ignores all options.

## Bug Disclosure

Please help us practice coordinated security bug disclosure, by using the
Expand Down
8 changes: 6 additions & 2 deletions packages/module-source/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
"type": "module",
"main": "./index.js",
"exports": {
".": "./index.js",
".": {
"xs": "./src-xs/index.js",
"default": "./index.js"
},
"./shim.js": "./shim.js",
"./package.json": "./package.json"
},
Expand All @@ -35,7 +38,8 @@
"lint:types": "tsc",
"lint:eslint": "eslint .",
"lint-fix": "eslint --fix .",
"test": "ava"
"test": "ava",
"test:xs": "node scripts/generate-test-xs.js && xst tmp/test-xs.js"
},
"dependencies": {
"@agoric/babel-generator": "^7.17.6",
Expand Down
41 changes: 41 additions & 0 deletions packages/module-source/scripts/generate-test-xs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* global process */
import 'ses';
import { promises as fs } from 'fs';
// Lerna does not like dependency cycles.
// With an explicit devDependency from module-source to compartment-mapper,
// the build script stalls before running every package's build script.
// yarn lerna run build
// Omitting the dependency from package.json solves the problem and works
// by dint of shared workspace node_modules.
// eslint-disable-next-line import/no-extraneous-dependencies
import { makeBundle } from '@endo/compartment-mapper/bundle.js';
import { fileURLToPath } from 'url';

const read = async location => {
const path = fileURLToPath(location);
return fs.readFile(path);
};
const write = async (location, content) => {
const path = fileURLToPath(location);
await fs.writeFile(path, content);
};

const main = async () => {
const xsPrelude = await makeBundle(
read,
new URL('../test/_xs.js', import.meta.url).href,
{
tags: new Set(['xs']),
},
);

await fs.mkdir(fileURLToPath(new URL('../tmp', import.meta.url)), {
recursive: true,
});
await write(new URL('../tmp/test-xs.js', import.meta.url).href, xsPrelude);
};

main().catch(err => {
console.error('Error running main:', err);
process.exitCode = 1;
});
6 changes: 5 additions & 1 deletion packages/module-source/shim.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/* global globalThis */

import { ModuleSource } from './index.js';
// We are using a reflexive import to make sure we pass through the conditional
// export in package.json.
// Eslint does not yet seem to have a carve-out for package-reflexive imports.
// eslint-disable-next-line import/no-extraneous-dependencies
import { ModuleSource } from '@endo/module-source';

Object.defineProperty(globalThis, 'ModuleSource', {
value: ModuleSource,
Expand Down
59 changes: 59 additions & 0 deletions packages/module-source/src-xs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// @ts-check
/* global globalThis */
/* eslint-disable @endo/no-nullish-coalescing */

/** @typedef {{ import: string, as?: string, from: string } & { importAllFrom: string, as: string, from: string } & { export: string, as?: string, from?: string } & { exportAllFrom: string, as?: string } & {importFrom: string }} Binding */

/** @param {Binding[]} bindings */
function* getImports(bindings) {
for (const binding of bindings) {
if (binding.import !== undefined) {
yield binding.from;
} else if (binding.importFrom !== undefined) {
yield binding.importFrom;
} else if (binding.importAllFrom !== undefined) {
yield binding.importAllFrom;
} else if (binding.exportAllFrom !== undefined) {
yield binding.exportAllFrom;
}
}
}

/** @param {Binding[]} bindings */
function* getExports(bindings) {
for (const binding of bindings) {
if (binding.export !== undefined) {
yield binding.as ?? binding.export;
}
}
}

/** @param {Binding[]} bindings */
function* getReexports(bindings) {
for (const binding of bindings) {
if (binding.exportAllFrom !== undefined) {
yield binding.exportAllFrom;
}
}
}

const ModuleSource = globalThis.ModuleSource;

Object.defineProperties(
ModuleSource.prototype,
Object.getOwnPropertyDescriptors({
get imports() {
return Array.from(new Set(getImports(this.bindings)));
},

get exports() {
return Array.from(new Set(getExports(this.bindings)));
},

get reexports() {
return Array.from(new Set(getReexports(this.bindings)));
},
}),
);

export { ModuleSource };
3 changes: 3 additions & 0 deletions packages/module-source/test/_native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* global globalThis */
export const NativeModuleSource = globalThis.ModuleSource;
export const NativeCompartment = globalThis.Compartment;
41 changes: 41 additions & 0 deletions packages/module-source/test/_xs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// This is a test fixture for XS validation (yarn test:xs with Moddable's xst
// on the PATH)
// This must be bundled with the -C xs condition to produce an artifact
// (tmp/test-xs.js) suitable for running with xst.
import { NativeModuleSource, NativeCompartment } from './_native.js';
// Eslint does not know about package reflexive imports (importing your own
// package), which in this case is necessary to go through the conditional
// export in package.json.
// eslint-disable-next-line import/no-extraneous-dependencies
import '@endo/module-source/shim.js';
import 'ses';

lockdown();

// spot checks
assert(Object.isFrozen(Object));

const source = new ModuleSource(`
import name from 'imported';
export * from 'reexported';
export default 42;
throw new Error('unreached');
`);
assert(source.imports[0] === 'imported');
assert(source.exports[0] === 'default');
assert(source.reexports[0] === 'reexported');

assert(source instanceof NativeModuleSource);

const compartment = new NativeCompartment({
modules: {
'.': {
source: new ModuleSource(`
export default 42;
`),
},
},
});
assert(compartment.importNow('.').default === 42);

// to be continued with XS-specific adapters for Compartment in SES...

0 comments on commit 8ca8e06

Please sign in to comment.