Skip to content

Commit e56cc04

Browse files
committed
feat(ses): Support dynamic import
1 parent 25414d6 commit e56cc04

File tree

4 files changed

+78
-4
lines changed

4 files changed

+78
-4
lines changed

packages/ses/NEWS.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ User-visible changes in `ses`:
22

33
# Next version
44

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

711
# v1.10.0 (2024-11-13)
812

packages/ses/src/compartment.js

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ import {
1919
setGlobalObjectMutableProperties,
2020
setGlobalObjectEvaluators,
2121
} from './global-object.js';
22-
import { assert, assertEqual } from './error/assert.js';
22+
import { assert, assertEqual, q } from './error/assert.js';
2323
import { sharedGlobalPropertyNames } from './permits.js';
2424
import { load, loadNow } from './module-load.js';
2525
import { link } from './module-link.js';
2626
import { getDeferredExports } from './module-proxy.js';
2727
import { compartmentEvaluate } from './compartment-evaluate.js';
2828
import { makeSafeEvaluator } from './make-safe-evaluator.js';
2929

30-
/** @import {ModuleDescriptor} from '../types.js' */
30+
/** @import {ModuleDescriptor, ModuleExportsNamespace} from '../types.js' */
3131

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

298298
assign(globalObject, endowments);
299299

300+
/**
301+
* In support dynamic import in a module source loaded by this compartment,
302+
* like `await import(importSpecifier)`, induces this compartment to import
303+
* a module, returning a promise for the resulting module exports
304+
* namespace.
305+
* Unlike `compartment.import`, never creates a box object for the
306+
* namespace as that behavior is deprecated and inconsistent with the
307+
* standard behavior of dynamic import.
308+
* Obliges the caller to resolve import specifiers to their corresponding
309+
* full specifier.
310+
* That is, every module must have its own dynamic import function that
311+
* closes over the surrounding module's full module specifier and calls
312+
* through to this function.
313+
* @param {string} fullSpecifier - A full specifier is a key in the
314+
* compartment's module memo.
315+
* The method `compartment.import` accepts a full specifier, but dynamic
316+
* import accepts an import specifier and resolves it to a full specifier
317+
* relative to the calling module's full specifier.
318+
* @returns {Promise<ModuleExportsNamespace>}
319+
*/
320+
const compartmentImport = async fullSpecifier => {
321+
if (typeof resolveHook !== 'function') {
322+
throw new TypeError(
323+
`Compartment does not support dynamic import: no configured resolveHook for compartment ${q(name)}`,
324+
);
325+
}
326+
await load(privateFields, moduleAliases, this, fullSpecifier);
327+
const { execute, exportsProxy } = link(
328+
privateFields,
329+
moduleAliases,
330+
this,
331+
fullSpecifier,
332+
);
333+
execute();
334+
return exportsProxy;
335+
};
336+
300337
weakmapSet(privateFields, this, {
301338
name: `${name}`,
302339
globalTransforms,
@@ -314,6 +351,7 @@ export const makeCompartmentConstructor = (
314351
instances,
315352
parentCompartment,
316353
noNamespaceBox,
354+
compartmentImport,
317355
});
318356
}
319357

packages/ses/src/module-instance.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/** @import {ModuleExportsNamespace} from '../types.js' */
2+
13
import { assert } from './error/assert.js';
24
import { getDeferredExports } from './module-proxy.js';
35
import {
@@ -131,13 +133,15 @@ export const makeModuleInstance = (
131133
__fixedExportMap__: fixedExportMap = {},
132134
__liveExportMap__: liveExportMap = {},
133135
__reexportMap__: reexportMap = {},
136+
__needsImport__: needsImport = false,
134137
__needsImportMeta__: needsImportMeta = false,
135138
__syncModuleFunctor__,
136139
} = moduleSource;
137140

138141
const compartmentFields = weakmapGet(privateFields, compartment);
139142

140-
const { __shimTransforms__, importMetaHook } = compartmentFields;
143+
const { __shimTransforms__, resolveHook, importMetaHook, compartmentImport } =
144+
compartmentFields;
141145

142146
const { exportsProxy, exportsTarget, activate } = getDeferredExports(
143147
compartment,
@@ -171,6 +175,14 @@ export const makeModuleInstance = (
171175
importMetaHook(moduleSpecifier, importMeta);
172176
}
173177

178+
/** @type {(fullSpecifier: string) => Promise<ModuleExportsNamespace>} */
179+
let dynamicImport;
180+
if (needsImport) {
181+
/** @param {string} importSpecifier */
182+
dynamicImport = async importSpecifier =>
183+
compartmentImport(resolveHook(importSpecifier, moduleSpecifier));
184+
}
185+
174186
// {_localName_: [{get, set, notify}]} used to merge all the export updaters.
175187
const localGetNotify = create(null);
176188

@@ -462,6 +474,7 @@ export const makeModuleInstance = (
462474
imports: freeze(imports),
463475
onceVar: freeze(onceVar),
464476
liveVar: freeze(liveVar),
477+
import: dynamicImport,
465478
importMeta,
466479
}),
467480
);

packages/ses/test/import.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
/* eslint max-lines: 0 */
55

66
import test from 'ava';
7+
import { ModuleSource } from '@endo/module-source';
78
import '../index.js';
89
import { resolveNode, makeNodeImporter } from './_node.js';
910
import { makeImporter, makeStaticRetriever } from './_import-commons.js';
@@ -600,3 +601,21 @@ test('importMetaHook and meta from record', async t => {
600601
const { default: metaurl } = await compartment.import('./index.js');
601602
t.is(metaurl, 'https://example.com/index.js?foo');
602603
});
604+
605+
test('dynamic import from source', async t => {
606+
const c = new Compartment({
607+
__options__: true,
608+
__noNamespaceBox__: true,
609+
resolveHook: s => s,
610+
modules: {
611+
'-': {
612+
source: new ModuleSource(`
613+
export const dynamic = import('-');
614+
`),
615+
},
616+
},
617+
});
618+
const namespace = await c.import('-');
619+
const namespace2 = await namespace.dynamic;
620+
t.is(namespace, namespace2);
621+
});

0 commit comments

Comments
 (0)