Skip to content

Commit

Permalink
feat(ses): Support dynamic import
Browse files Browse the repository at this point in the history
  • Loading branch information
kriskowal committed Nov 26, 2024
1 parent 25414d6 commit e56cc04
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 4 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 version

- Specifying the long discontinued `mathTaming` or `dateTaming` options logs a warning.
- Adds support for dynamic `import` in conjunction with an update to
`@endo/module-source`.

- Specifying the long-discontinued `mathTaming` or `dateTaming` options logs a
warning.

# v1.10.0 (2024-11-13)

Expand Down
42 changes: 40 additions & 2 deletions packages/ses/src/compartment.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ import {
setGlobalObjectMutableProperties,
setGlobalObjectEvaluators,
} from './global-object.js';
import { assert, assertEqual } from './error/assert.js';
import { assert, assertEqual, q } from './error/assert.js';
import { sharedGlobalPropertyNames } from './permits.js';
import { load, loadNow } from './module-load.js';
import { link } from './module-link.js';
import { getDeferredExports } from './module-proxy.js';
import { compartmentEvaluate } from './compartment-evaluate.js';
import { makeSafeEvaluator } from './make-safe-evaluator.js';

/** @import {ModuleDescriptor} from '../types.js' */
/** @import {ModuleDescriptor, ModuleExportsNamespace} from '../types.js' */

// moduleAliases associates every public module exports namespace with its
// corresponding compartment and specifier so they can be used to link modules
Expand Down Expand Up @@ -297,6 +297,43 @@ export const makeCompartmentConstructor = (

assign(globalObject, endowments);

/**
* In support dynamic import in a module source loaded by this compartment,
* like `await import(importSpecifier)`, induces this compartment to import
* a module, returning a promise for the resulting module exports
* namespace.
* Unlike `compartment.import`, never creates a box object for the
* namespace as that behavior is deprecated and inconsistent with the
* standard behavior of dynamic import.
* Obliges the caller to resolve import specifiers to their corresponding
* full specifier.
* That is, every module must have its own dynamic import function that
* closes over the surrounding module's full module specifier and calls
* through to this function.
* @param {string} fullSpecifier - A full specifier is a key in the
* compartment's module memo.
* The method `compartment.import` accepts a full specifier, but dynamic
* import accepts an import specifier and resolves it to a full specifier
* relative to the calling module's full specifier.
* @returns {Promise<ModuleExportsNamespace>}
*/
const compartmentImport = async fullSpecifier => {
if (typeof resolveHook !== 'function') {
throw new TypeError(
`Compartment does not support dynamic import: no configured resolveHook for compartment ${q(name)}`,
);
}
await load(privateFields, moduleAliases, this, fullSpecifier);
const { execute, exportsProxy } = link(
privateFields,
moduleAliases,
this,
fullSpecifier,
);
execute();
return exportsProxy;
};

weakmapSet(privateFields, this, {
name: `${name}`,
globalTransforms,
Expand All @@ -314,6 +351,7 @@ export const makeCompartmentConstructor = (
instances,
parentCompartment,
noNamespaceBox,
compartmentImport,
});
}

Expand Down
15 changes: 14 additions & 1 deletion packages/ses/src/module-instance.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/** @import {ModuleExportsNamespace} from '../types.js' */

import { assert } from './error/assert.js';
import { getDeferredExports } from './module-proxy.js';
import {
Expand Down Expand Up @@ -131,13 +133,15 @@ export const makeModuleInstance = (
__fixedExportMap__: fixedExportMap = {},
__liveExportMap__: liveExportMap = {},
__reexportMap__: reexportMap = {},
__needsImport__: needsImport = false,
__needsImportMeta__: needsImportMeta = false,
__syncModuleFunctor__,
} = moduleSource;

const compartmentFields = weakmapGet(privateFields, compartment);

const { __shimTransforms__, importMetaHook } = compartmentFields;
const { __shimTransforms__, resolveHook, importMetaHook, compartmentImport } =
compartmentFields;

const { exportsProxy, exportsTarget, activate } = getDeferredExports(
compartment,
Expand Down Expand Up @@ -171,6 +175,14 @@ export const makeModuleInstance = (
importMetaHook(moduleSpecifier, importMeta);
}

/** @type {(fullSpecifier: string) => Promise<ModuleExportsNamespace>} */
let dynamicImport;
if (needsImport) {
/** @param {string} importSpecifier */
dynamicImport = async importSpecifier =>
compartmentImport(resolveHook(importSpecifier, moduleSpecifier));
}

// {_localName_: [{get, set, notify}]} used to merge all the export updaters.
const localGetNotify = create(null);

Expand Down Expand Up @@ -462,6 +474,7 @@ export const makeModuleInstance = (
imports: freeze(imports),
onceVar: freeze(onceVar),
liveVar: freeze(liveVar),
import: dynamicImport,
importMeta,
}),
);
Expand Down
19 changes: 19 additions & 0 deletions packages/ses/test/import.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/* eslint max-lines: 0 */

import test from 'ava';
import { ModuleSource } from '@endo/module-source';
import '../index.js';
import { resolveNode, makeNodeImporter } from './_node.js';
import { makeImporter, makeStaticRetriever } from './_import-commons.js';
Expand Down Expand Up @@ -600,3 +601,21 @@ test('importMetaHook and meta from record', async t => {
const { default: metaurl } = await compartment.import('./index.js');
t.is(metaurl, 'https://example.com/index.js?foo');
});

test('dynamic import from source', async t => {
const c = new Compartment({
__options__: true,
__noNamespaceBox__: true,
resolveHook: s => s,
modules: {
'-': {
source: new ModuleSource(`
export const dynamic = import('-');
`),
},
},
});
const namespace = await c.import('-');
const namespace2 = await namespace.dynamic;
t.is(namespace, namespace2);
});

0 comments on commit e56cc04

Please sign in to comment.