0
# Module System
1
2
Advanced module loading system with hooks for custom resolution, transformation, and cross-compartment module sharing. The SES module system enables secure module loading with fine-grained control over how modules are resolved, loaded, and shared between compartments.
3
4
## Capabilities
5
6
### Module Loading Hooks
7
8
Hook functions that customize how modules are resolved and loaded within compartments.
9
10
```javascript { .api }
11
/**
12
* Resolves import specifiers to full module specifiers
13
* @param importSpecifier - The specifier used in import statement
14
* @param referrerSpecifier - The specifier of the importing module
15
* @returns Full module specifier for loading
16
*/
17
type ResolveHook = (importSpecifier: string, referrerSpecifier: string) => string;
18
19
/**
20
* Asynchronously loads modules by specifier
21
* @param moduleSpecifier - Full module specifier to load
22
* @returns Promise resolving to module descriptor
23
*/
24
type ImportHook = (moduleSpecifier: string) => Promise<ModuleDescriptor>;
25
26
/**
27
* Synchronously loads modules by specifier
28
* @param moduleSpecifier - Full module specifier to load
29
* @returns Module descriptor or undefined if not found
30
*/
31
type ImportNowHook = (moduleSpecifier: string) => ModuleDescriptor | undefined;
32
33
/**
34
* Dynamically maps module specifiers to descriptors
35
* @param moduleSpecifier - Module specifier to lookup
36
* @returns Module descriptor or undefined if not found
37
*/
38
type ModuleMapHook = (moduleSpecifier: string) => ModuleDescriptor | undefined;
39
40
/**
41
* Customizes import.meta objects for modules
42
* @param moduleSpecifier - The module's specifier
43
* @param importMeta - The import.meta object to customize
44
*/
45
type ImportMetaHook = (moduleSpecifier: string, importMeta: ImportMeta) => void;
46
```
47
48
**Usage Examples:**
49
50
```javascript
51
import 'ses';
52
53
lockdown();
54
55
// Custom module resolution
56
const resolveHook = (importSpecifier, referrerSpecifier) => {
57
if (importSpecifier.startsWith('./')) {
58
// Resolve relative imports
59
const referrerPath = referrerSpecifier.split('/').slice(0, -1);
60
return [...referrerPath, importSpecifier.slice(2)].join('/');
61
}
62
if (importSpecifier.startsWith('virtual:')) {
63
// Handle virtual modules
64
return importSpecifier;
65
}
66
// Default resolution
67
return importSpecifier;
68
};
69
70
// Async module loading
71
const importHook = async (moduleSpecifier) => {
72
if (moduleSpecifier.startsWith('virtual:math')) {
73
return {
74
source: `
75
export const add = (a, b) => a + b;
76
export const multiply = (a, b) => a * b;
77
export const PI = 3.14159;
78
`
79
};
80
}
81
if (moduleSpecifier.startsWith('http://')) {
82
const response = await fetch(moduleSpecifier);
83
const source = await response.text();
84
return { source };
85
}
86
throw new Error(`Cannot load module: ${moduleSpecifier}`);
87
};
88
89
// Sync module loading
90
const importNowHook = (moduleSpecifier) => {
91
if (moduleSpecifier === 'builtin:console') {
92
return {
93
namespace: { log: console.log, error: console.error }
94
};
95
}
96
// Return undefined for modules that can't be loaded synchronously
97
};
98
99
// Dynamic module mapping
100
const moduleMapHook = (moduleSpecifier) => {
101
const moduleRegistry = {
102
'app/config': {
103
namespace: { apiUrl: 'https://api.example.com', debug: true }
104
},
105
'app/utils': {
106
source: './utils/index.js',
107
compartment: utilsCompartment
108
}
109
};
110
return moduleRegistry[moduleSpecifier];
111
};
112
113
// Create compartment with hooks
114
const compartment = new Compartment({
115
resolveHook,
116
importHook,
117
importNowHook,
118
moduleMapHook,
119
__options__: true
120
});
121
```
122
123
### Module Descriptors
124
125
Various descriptor formats for specifying how modules should be loaded and initialized.
126
127
```javascript { .api }
128
/**
129
* Descriptor using source code or compiled module
130
*/
131
interface SourceModuleDescriptor {
132
/** Module source code or compiled module source */
133
source: string | ModuleSource;
134
/** Optional specifier for the module */
135
specifier?: string;
136
/** Optional import.meta properties */
137
importMeta?: any;
138
/** Optional compartment to load/initialize in */
139
compartment?: Compartment;
140
}
141
142
/**
143
* Descriptor using pre-existing namespace
144
*/
145
interface NamespaceModuleDescriptor {
146
/** Namespace object or specifier in another compartment */
147
namespace: string | ModuleExportsNamespace;
148
/** Optional compartment containing the namespace */
149
compartment?: Compartment;
150
}
151
152
/**
153
* Legacy descriptor format (being deprecated)
154
*/
155
interface RecordModuleDescriptor {
156
/** Module specifier */
157
specifier: string;
158
/** Optional compiled module record */
159
record?: ModuleSource;
160
/** Optional import.meta properties */
161
importMeta?: any;
162
/** Compartment to use (defaults to self) */
163
compartment?: Compartment;
164
}
165
166
/**
167
* Union type of all module descriptor formats
168
*/
169
type ModuleDescriptor =
170
| SourceModuleDescriptor
171
| NamespaceModuleDescriptor
172
| RecordModuleDescriptor
173
| ModuleExportsNamespace
174
| VirtualModuleSource
175
| PrecompiledModuleSource
176
| string;
177
```
178
179
**Usage Examples:**
180
181
```javascript
182
import 'ses';
183
184
lockdown();
185
186
const compartment = new Compartment({
187
modules: {
188
// Source descriptor with string source
189
'math/basic': {
190
source: `
191
export const add = (a, b) => a + b;
192
export const subtract = (a, b) => a - b;
193
`
194
},
195
196
// Namespace descriptor with direct object
197
'config/app': {
198
namespace: {
199
apiEndpoint: 'https://api.example.com',
200
version: '1.0.0'
201
}
202
},
203
204
// Cross-compartment namespace reference
205
'utils/shared': {
206
namespace: 'utils/index',
207
compartment: sharedUtilsCompartment
208
},
209
210
// With custom import.meta
211
'app/main': {
212
source: `
213
console.log('Module URL:', import.meta.url);
214
export const init = () => 'initialized';
215
`,
216
importMeta: {
217
url: 'file:///app/main.js',
218
env: 'production'
219
}
220
}
221
},
222
__options__: true
223
});
224
```
225
226
### Module Source Types
227
228
Different types of module source representations for advanced use cases.
229
230
```javascript { .api }
231
/**
232
* Pre-compiled module source with analysis metadata
233
*/
234
interface PrecompiledModuleSource {
235
/** Array of import specifiers */
236
imports: Array<string>;
237
/** Array of export names */
238
exports: Array<string>;
239
/** Array of re-export specifiers */
240
reexports: Array<string>;
241
/** Compiled module program as string */
242
__syncModuleProgram__: string;
243
/** Map of live (mutable) export bindings */
244
__liveExportMap__: __LiveExportMap__;
245
/** Map of fixed (immutable) export bindings */
246
__fixedExportMap__: __FixedExportMap__;
247
/** Map of re-export bindings */
248
__reexportMap__: __ReexportMap__;
249
}
250
251
/**
252
* Virtual module source with custom execution logic
253
*/
254
interface VirtualModuleSource {
255
/** Array of import specifiers this module needs */
256
imports: Array<string>;
257
/** Array of names this module exports */
258
exports: Array<string>;
259
260
/**
261
* Custom execution function for the module
262
* @param exportsTarget - Object to populate with exports
263
* @param compartment - The compartment executing the module
264
* @param resolvedImports - Map of import names to resolved specifiers
265
*/
266
execute(
267
exportsTarget: Record<string, any>,
268
compartment: Compartment,
269
resolvedImports: Record<string, string>
270
): void;
271
}
272
273
/** Union type of module source formats */
274
type ModuleSource = PrecompiledModuleSource | VirtualModuleSource;
275
```
276
277
**Usage Examples:**
278
279
```javascript
280
import 'ses';
281
282
lockdown();
283
284
// Virtual module source example
285
const createDatabaseModule = (connectionString) => ({
286
imports: [],
287
exports: ['connect', 'query', 'disconnect'],
288
execute(exportsTarget, compartment, resolvedImports) {
289
let connection = null;
290
291
exportsTarget.connect = async () => {
292
connection = await createConnection(connectionString);
293
return connection;
294
};
295
296
exportsTarget.query = async (sql, params) => {
297
if (!connection) throw new Error('Not connected');
298
return await connection.query(sql, params);
299
};
300
301
exportsTarget.disconnect = async () => {
302
if (connection) {
303
await connection.close();
304
connection = null;
305
}
306
};
307
}
308
});
309
310
// Use virtual module
311
const compartment = new Compartment({
312
modules: {
313
'db/connection': createDatabaseModule('postgresql://localhost/mydb')
314
},
315
__options__: true
316
});
317
318
const db = compartment.importNow('db/connection');
319
await db.connect();
320
const results = await db.query('SELECT * FROM users WHERE active = ?', [true]);
321
```
322
323
## Advanced Module Patterns
324
325
### Module Federation
326
327
Sharing modules across multiple compartments:
328
329
```javascript
330
import 'ses';
331
332
lockdown();
333
334
// Shared library compartment
335
const libraryCompartment = new Compartment({
336
modules: {
337
'lodash': {
338
source: `
339
export const map = (array, fn) => array.map(fn);
340
export const filter = (array, fn) => array.filter(fn);
341
export const reduce = (array, fn, initial) => array.reduce(fn, initial);
342
`
343
},
344
'validator': {
345
source: `
346
export const isEmail = (str) => /\\S+@\\S+\\.\\S+/.test(str);
347
export const isURL = (str) => /^https?:\\/\\//.test(str);
348
`
349
}
350
},
351
__options__: true
352
});
353
354
// Application compartments that use shared libraries
355
const createAppCompartment = (name) => new Compartment({
356
name,
357
modules: {
358
// Reference modules from library compartment
359
'lodash': {
360
namespace: 'lodash',
361
compartment: libraryCompartment
362
},
363
'validator': {
364
namespace: 'validator',
365
compartment: libraryCompartment
366
}
367
},
368
__options__: true
369
});
370
371
const userAppCompartment = createAppCompartment('user-app');
372
const adminAppCompartment = createAppCompartment('admin-app');
373
374
// Both apps can use the same shared modules
375
const userLodash = userAppCompartment.importNow('lodash');
376
const adminValidator = adminAppCompartment.importNow('validator');
377
console.log(userLodash === adminValidator); // false (different namespaces)
378
```
379
380
### Dynamic Module Loading
381
382
Loading modules based on runtime conditions:
383
384
```javascript
385
import 'ses';
386
387
lockdown();
388
389
const createPluginSystem = () => {
390
const plugins = new Map();
391
392
return new Compartment({
393
moduleMapHook: (specifier) => {
394
if (specifier.startsWith('plugin:')) {
395
const pluginName = specifier.slice(7);
396
return plugins.get(pluginName);
397
}
398
},
399
400
importHook: async (specifier) => {
401
if (specifier.startsWith('dynamic:')) {
402
const moduleName = specifier.slice(8);
403
404
// Load module based on environment
405
if (process.env.NODE_ENV === 'development') {
406
return {
407
source: `export default { name: '${moduleName}', env: 'dev' };`
408
};
409
} else {
410
return {
411
source: `export default { name: '${moduleName}', env: 'prod' };`
412
};
413
}
414
}
415
416
throw new Error(`Unknown module: ${specifier}`);
417
},
418
419
globals: {
420
registerPlugin: harden((name, descriptor) => {
421
plugins.set(name, descriptor);
422
}),
423
console: harden(console)
424
},
425
426
__options__: true
427
});
428
};
429
430
const pluginSystem = createPluginSystem();
431
432
// Register a plugin
433
pluginSystem.globalThis.registerPlugin('auth', {
434
source: `
435
export const authenticate = (token) => token === 'valid';
436
export const authorize = (user, resource) => user.role === 'admin';
437
`
438
});
439
440
// Use the plugin
441
const auth = pluginSystem.importNow('plugin:auth');
442
console.log(auth.authenticate('valid')); // true
443
444
// Load dynamic module
445
const dynModule = await pluginSystem.import('dynamic:feature-a');
446
console.log(dynModule.default); // { name: 'feature-a', env: 'dev' or 'prod' }
447
```
448
449
### Module Transformation Pipeline
450
451
Transforming modules during loading:
452
453
```javascript
454
import 'ses';
455
456
lockdown();
457
458
const createTransformingCompartment = () => {
459
const transforms = [
460
// Add logging to all functions
461
(source) => {
462
return source.replace(
463
/export\s+(?:const|function)\s+(\w+)/g,
464
(match, name) => {
465
return match + `\nconsole.log('Calling ${name}');`;
466
}
467
);
468
},
469
470
// Replace development assertions
471
(source) => {
472
if (process.env.NODE_ENV === 'production') {
473
return source.replace(/assert\([^)]+\);?/g, '');
474
}
475
return source;
476
}
477
];
478
479
return new Compartment({
480
importHook: async (specifier) => {
481
let source = await fetchModuleSource(specifier);
482
483
// Apply transformations
484
for (const transform of transforms) {
485
source = transform(source);
486
}
487
488
return { source };
489
},
490
491
globals: { console: harden(console) },
492
__options__: true
493
});
494
};
495
496
const fetchModuleSource = async (specifier) => {
497
// Mock module source
498
if (specifier === 'math-utils') {
499
return `
500
export const add = (a, b) => {
501
assert(typeof a === 'number');
502
assert(typeof b === 'number');
503
return a + b;
504
};
505
506
export const multiply = (a, b) => {
507
assert(typeof a === 'number');
508
assert(typeof b === 'number');
509
return a * b;
510
};
511
`;
512
}
513
throw new Error(`Module not found: ${specifier}`);
514
};
515
516
const compartment = createTransformingCompartment();
517
const mathUtils = await compartment.import('math-utils');
518
console.log(mathUtils.namespace.add(2, 3)); // Logs: "Calling add" then returns 5
519
```
520
521
### Module Sandboxing
522
523
Creating isolated environments for different types of modules:
524
525
```javascript
526
import 'ses';
527
528
lockdown();
529
530
const createSandboxedModule = (source, permissions = {}) => {
531
const sandbox = new Compartment({
532
globals: {
533
// Only provide explicitly allowed capabilities
534
...(permissions.console && { console: harden(console) }),
535
...(permissions.setTimeout && { setTimeout: harden(setTimeout) }),
536
...(permissions.fetch && { fetch: harden(fetch) }),
537
538
// Custom API surface
539
...(permissions.storage && {
540
storage: harden({
541
get: (key) => localStorage.getItem(`sandbox:${key}`),
542
set: (key, value) => localStorage.setItem(`sandbox:${key}`, value)
543
})
544
})
545
},
546
__options__: true
547
});
548
549
return sandbox.evaluate(`
550
${source}
551
// Return the module's exports
552
(typeof exports !== 'undefined' ? exports :
553
typeof module !== 'undefined' && module.exports ? module.exports :
554
{})
555
`);
556
};
557
558
// Load user-provided plugin with minimal permissions
559
const userPlugin = createSandboxedModule(`
560
exports.processData = (data) => {
561
console.log('Processing:', data);
562
return data.map(x => x * 2);
563
};
564
`, {
565
console: true // Only allow console access
566
});
567
568
// Load trusted module with more permissions
569
const trustedModule = createSandboxedModule(`
570
exports.saveData = async (data) => {
571
storage.set('lastData', JSON.stringify(data));
572
const response = await fetch('/api/save', {
573
method: 'POST',
574
body: JSON.stringify(data)
575
});
576
return response.json();
577
};
578
`, {
579
console: true,
580
storage: true,
581
fetch: true
582
});
583
584
// Use the sandboxed modules
585
const processedData = userPlugin.processData([1, 2, 3]); // [2, 4, 6]
586
await trustedModule.saveData(processedData);
587
```