@endo/compartment-mapper 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +292 -111
  2. package/bundle.d.ts +1 -1
  3. package/bundle.js +4 -1
  4. package/functor-lite.d.ts +3 -0
  5. package/functor-lite.d.ts.map +1 -0
  6. package/functor-lite.js +4 -0
  7. package/functor.d.ts +3 -0
  8. package/functor.d.ts.map +1 -0
  9. package/functor.js +4 -0
  10. package/import-archive-all-parsers.d.ts +2 -0
  11. package/import-archive-all-parsers.d.ts.map +1 -0
  12. package/import-archive-all-parsers.js +1 -0
  13. package/index.d.ts +1 -1
  14. package/index.js +4 -1
  15. package/package.json +14 -5
  16. package/script-lite.d.ts +3 -0
  17. package/script-lite.d.ts.map +1 -0
  18. package/script-lite.js +4 -0
  19. package/script.d.ts +3 -0
  20. package/script.d.ts.map +1 -0
  21. package/script.js +4 -0
  22. package/src/archive-lite.d.ts +2 -4
  23. package/src/archive-lite.d.ts.map +1 -1
  24. package/src/archive-lite.js +16 -192
  25. package/src/archive.d.ts.map +1 -1
  26. package/src/archive.js +8 -0
  27. package/src/bundle-cjs.d.ts +1 -1
  28. package/src/bundle-cjs.d.ts.map +1 -1
  29. package/src/bundle-cjs.js +57 -28
  30. package/src/bundle-json.d.ts.map +1 -1
  31. package/src/bundle-json.js +2 -3
  32. package/src/bundle-lite.d.ts +91 -0
  33. package/src/bundle-lite.d.ts.map +1 -0
  34. package/src/bundle-lite.js +668 -0
  35. package/src/bundle-mjs.d.ts +2 -2
  36. package/src/bundle-mjs.d.ts.map +1 -1
  37. package/src/bundle-mjs.js +36 -19
  38. package/src/bundle.d.ts +48 -10
  39. package/src/bundle.d.ts.map +1 -1
  40. package/src/bundle.js +399 -127
  41. package/src/capture-lite.d.ts.map +1 -1
  42. package/src/capture-lite.js +10 -188
  43. package/src/digest.d.ts +5 -0
  44. package/src/digest.d.ts.map +1 -0
  45. package/src/digest.js +235 -0
  46. package/src/import-archive-all-parsers.d.ts +11 -0
  47. package/src/import-archive-all-parsers.d.ts.map +1 -0
  48. package/src/import-archive-all-parsers.js +29 -0
  49. package/src/import-archive-lite.d.ts.map +1 -1
  50. package/src/import-archive-lite.js +4 -0
  51. package/src/import-hook.d.ts +3 -16
  52. package/src/import-hook.d.ts.map +1 -1
  53. package/src/import-hook.js +11 -18
  54. package/src/import-lite.d.ts.map +1 -1
  55. package/src/import-lite.js +7 -2
  56. package/src/import.d.ts.map +1 -1
  57. package/src/import.js +2 -0
  58. package/src/link.d.ts.map +1 -1
  59. package/src/link.js +2 -0
  60. package/src/map-parser.d.ts.map +1 -1
  61. package/src/map-parser.js +4 -1
  62. package/src/node-modules.d.ts +4 -47
  63. package/src/node-modules.d.ts.map +1 -1
  64. package/src/node-modules.js +157 -131
  65. package/src/parse-archive-cjs.d.ts.map +1 -1
  66. package/src/parse-archive-cjs.js +8 -3
  67. package/src/parse-cjs-shared-export-wrapper.d.ts.map +1 -1
  68. package/src/parse-cjs-shared-export-wrapper.js +2 -10
  69. package/src/parse-cjs.js +1 -1
  70. package/src/parse-mjs.js +2 -2
  71. package/src/policy.d.ts.map +1 -1
  72. package/src/policy.js +4 -7
  73. package/src/search.d.ts +6 -12
  74. package/src/search.d.ts.map +1 -1
  75. package/src/search.js +29 -12
  76. package/src/types/compartment-map-schema.d.ts +5 -0
  77. package/src/types/compartment-map-schema.d.ts.map +1 -1
  78. package/src/types/compartment-map-schema.ts +5 -0
  79. package/src/types/external.d.ts +159 -12
  80. package/src/types/external.d.ts.map +1 -1
  81. package/src/types/external.ts +180 -12
  82. package/src/types/internal.d.ts +86 -13
  83. package/src/types/internal.d.ts.map +1 -1
  84. package/src/types/internal.ts +107 -13
  85. package/src/types/node-modules.d.ts +79 -0
  86. package/src/types/node-modules.d.ts.map +1 -0
  87. package/src/types/node-modules.ts +89 -0
  88. package/src/types/node-powers.d.ts +4 -4
  89. package/src/types/node-powers.d.ts.map +1 -1
  90. package/src/types/node-powers.ts +4 -4
  91. package/src/types/powers.d.ts +2 -2
  92. package/src/types/powers.d.ts.map +1 -1
  93. package/src/types/powers.ts +2 -2
package/src/bundle.js CHANGED
@@ -6,8 +6,9 @@
6
6
  * PrecompiledStaticModuleInterface
7
7
  * } from 'ses'
8
8
  * @import {
9
- * ArchiveOptions,
9
+ * BundleOptions,
10
10
  * CompartmentDescriptor,
11
+ * CompartmentMapDescriptor,
11
12
  * CompartmentSources,
12
13
  * MaybeReadPowers,
13
14
  * ReadFn,
@@ -18,6 +19,29 @@
18
19
  */
19
20
 
20
21
  /**
22
+ * The bundler kit defines a language-specific behavior for injecting a module
23
+ * into a bundle.
24
+ * Each module must allocate cells for its imports and exports, link those cells
25
+ * to the cells of dependencies, and provide both the linker and evaluation behavior
26
+ * for the module.
27
+ * The linker behavior gets injected in a lexical scope with the linker runtime
28
+ * and has access to the cells of all modules, whereas the evaluation behavior
29
+ * gets injected in the generated script's top level lexical scope, so has
30
+ * no accidental visibility into the linkage runtime.
31
+ *
32
+ * For example, JSON modules produce a single "default" cell ("getCells"):
33
+ *
34
+ * { default: cell('default') },
35
+ *
36
+ * Then, the JSON gets injected verbatim for the evaluation behavior ("getFunctor").
37
+ * The linker simply sets the cell to the value.
38
+ *
39
+ * functors[0]['default'].set(modules[0]);
40
+ *
41
+ * For an ECMAScript or CommonJS module, the evaluation behavior is a function
42
+ * that the linker runtime can call to inject it with the cells it needs by
43
+ * the names it sees for them.
44
+ *
21
45
  * @typedef {object} BundlerKit
22
46
  * @property {() => string} getFunctor Produces a JavaScript string consisting of
23
47
  * a function expression followed by a comma delimiter that will be evaluated in
@@ -28,12 +52,10 @@
28
52
  * variable.
29
53
  * @property {() => string} getCells Produces a JavaScript string consisting of
30
54
  * a JavaScript object and a trailing comma.
31
- * The string is evaluated in a lexical context with a `cell` maker, the `cells`
32
- * array of every module's internal environment record.
33
- * @property {() => string} getFunctorCall Produces a JavaScript string may
34
- * be a statement that calls this module's functor with the calling convention
35
- * appropriate for its language, injecting whatever cells it needs to link to
36
- * other module namespaces.
55
+ * The string is evaluated in the linker runtime's lexical context.
56
+ * @property {() => string} getFunctorCall Produces a JavaScript string,
57
+ * a statement that effects the module's evaluation behavior using the cells
58
+ * it imports and exports and the evaluated "functor".
37
59
  * @property {() => string} getReexportsWiring Produces a JavaScript string
38
60
  * that may include statements that bind the cells reexported by this module.
39
61
  */
@@ -42,8 +64,10 @@
42
64
  * @template {unknown} SpecificModuleSource
43
65
  * @typedef {object} BundleModule
44
66
  * @property {string} key
67
+ * @property {string} exit
45
68
  * @property {string} compartmentName
46
69
  * @property {string} moduleSpecifier
70
+ * @property {string} sourceDirname
47
71
  * @property {string} parser
48
72
  * @property {StaticModuleType & SpecificModuleSource} record
49
73
  * @property {Record<string, string>} resolvedImports
@@ -53,10 +77,22 @@
53
77
  * @property {BundlerKit} bundlerKit
54
78
  */
55
79
 
80
+ /**
81
+ * @typedef {object} BundleExit
82
+ * @property {string} exit
83
+ * @property {number} index
84
+ * @property {BundlerKit} bundlerKit
85
+ * @property {Record<string, number>} indexedImports
86
+ * @property {Record<string, string>} resolvedImports
87
+ */
88
+
56
89
  /**
57
90
  * @template {unknown} SpecificModuleSource
58
91
  * @callback GetBundlerKit
59
92
  * @param {BundleModule<SpecificModuleSource>} module
93
+ * @param {object} params
94
+ * @param {boolean} [params.useEvaluate]
95
+ * @param {string} [params.sourceUrlPrefix]
60
96
  * @returns {BundlerKit}
61
97
  */
62
98
 
@@ -68,13 +104,10 @@
68
104
  */
69
105
 
70
106
  import { resolve } from './node-module-specifier.js';
71
- import { compartmentMapForNodeModules } from './node-modules.js';
72
- import { search } from './search.js';
107
+ import { mapNodeModules } from './node-modules.js';
73
108
  import { link } from './link.js';
74
- import { unpackReadPowers } from './powers.js';
75
109
  import { makeImportHookMaker } from './import-hook.js';
76
110
  import { defaultParserForLanguage } from './archive-parsers.js';
77
- import { parseLocatedJson } from './json.js';
78
111
 
79
112
  import mjsSupport from './bundle-mjs.js';
80
113
  import cjsSupport from './bundle-cjs.js';
@@ -85,16 +118,48 @@ const textEncoder = new TextEncoder();
85
118
  const { quote: q } = assert;
86
119
 
87
120
  /**
121
+ * @param {BundleExit} source
122
+ * @returns {BundlerKit}
123
+ */
124
+ const makeCjsExitBundlerKit = ({ exit, index }) => ({
125
+ getFunctor: () => `\
126
+ null,
127
+ `,
128
+ getCells: () => `\
129
+ namespaceCells(tryRequire(${JSON.stringify(exit)})),
130
+ `,
131
+ getReexportsWiring: () => '',
132
+ getFunctorCall: () => ``,
133
+ });
134
+
135
+ /**
136
+ * Produces a list of modules in the order they should be evaluated, and
137
+ * a side-table for following aliases.
138
+ * The modules are produce in topological postorder, such that the entry
139
+ * module appears last.
140
+ * The post-order traversal does not revisit modules that appear in cycles.
141
+ *
142
+ * Synthesizes a unique key for each module and translates
143
+ * each module's imports to their corresponding keys.
144
+ * Some import keys are aliased to other keys, such that walking from
145
+ * key to value in the aliases side table will eventually arrive at
146
+ * the key of a module that is present in the modules list.
147
+ *
148
+ * The first modules are place-holders for the modules that exit
149
+ * the compartment map to the host's module system.
150
+ *
88
151
  * @param {Record<string, CompartmentDescriptor>} compartmentDescriptors
89
152
  * @param {Record<string, CompartmentSources>} compartmentSources
90
153
  * @param {string} entryCompartmentName
91
154
  * @param {string} entryModuleSpecifier
155
+ * @param {Array<string>} exitModuleSpecifiers
92
156
  */
93
157
  const sortedModules = (
94
158
  compartmentDescriptors,
95
159
  compartmentSources,
96
160
  entryCompartmentName,
97
161
  entryModuleSpecifier,
162
+ exitModuleSpecifiers,
98
163
  ) => {
99
164
  /** @type {BundleModule<unknown>[]} */
100
165
  const modules = [];
@@ -103,6 +168,17 @@ const sortedModules = (
103
168
  /** @type {Set<string>} seen */
104
169
  const seen = new Set();
105
170
 
171
+ for (const exit of exitModuleSpecifiers) {
172
+ modules.push({
173
+ key: exit,
174
+ exit,
175
+ // @ts-expect-error
176
+ index: undefined,
177
+ // @ts-expect-error
178
+ bundlerKit: null,
179
+ });
180
+ }
181
+
106
182
  /**
107
183
  * @param {string} compartmentName
108
184
  * @param {string} moduleSpecifier
@@ -116,9 +192,23 @@ const sortedModules = (
116
192
 
117
193
  const source = compartmentSources[compartmentName][moduleSpecifier];
118
194
  if (source !== undefined) {
119
- const { record, parser, deferredError, bytes } = source;
120
- assert(parser !== undefined);
121
- assert(bytes !== undefined);
195
+ const { record, parser, deferredError, bytes, sourceDirname, exit } =
196
+ source;
197
+ if (exit !== undefined) {
198
+ return exit;
199
+ }
200
+ assert(
201
+ bytes !== undefined,
202
+ `No bytes for ${moduleSpecifier} in ${compartmentName}`,
203
+ );
204
+ assert(
205
+ parser !== undefined,
206
+ `No parser for ${moduleSpecifier} in ${compartmentName}`,
207
+ );
208
+ assert(
209
+ sourceDirname !== undefined,
210
+ `No sourceDirname for ${moduleSpecifier} in ${compartmentName}`,
211
+ );
122
212
  if (deferredError) {
123
213
  throw Error(
124
214
  `Cannot bundle: encountered deferredError ${deferredError}`,
@@ -143,6 +233,7 @@ const sortedModules = (
143
233
  key,
144
234
  compartmentName,
145
235
  moduleSpecifier,
236
+ sourceDirname,
146
237
  parser,
147
238
  record,
148
239
  resolvedImports,
@@ -197,8 +288,13 @@ const getRuntime = language =>
197
288
  ? bundlerSupportForLanguage[language].runtime
198
289
  : `/*unknown language:${language}*/`;
199
290
 
200
- /** @param {BundleModule<unknown>} module */
201
- const getBundlerKitForModule = module => {
291
+ /**
292
+ * @param {BundleModule<unknown>} module
293
+ * @param {object} params
294
+ * @param {boolean} [params.useEvaluate]
295
+ * @param {string} [params.sourceUrlPrefix]
296
+ */
297
+ const getBundlerKitForModule = (module, params) => {
202
298
  const language = module.parser;
203
299
  assert(language !== undefined);
204
300
  if (bundlerSupportForLanguage[language] === undefined) {
@@ -213,37 +309,36 @@ const getBundlerKitForModule = module => {
213
309
  };
214
310
  }
215
311
  const { getBundlerKit } = bundlerSupportForLanguage[language];
216
- return getBundlerKit(module);
312
+ return getBundlerKit(module, params);
217
313
  };
218
314
 
219
315
  /**
220
316
  * @param {ReadFn | ReadPowers | MaybeReadPowers} readPowers
221
- * @param {string} moduleLocation
222
- * @param {ArchiveOptions} [options]
317
+ * @param {CompartmentMapDescriptor} compartmentMap
318
+ * @param {BundleOptions} [options]
223
319
  * @returns {Promise<string>}
224
320
  */
225
- export const makeBundle = async (readPowers, moduleLocation, options) => {
226
- const { read } = unpackReadPowers(readPowers);
227
-
321
+ export const makeFunctorFromMap = async (
322
+ readPowers,
323
+ compartmentMap,
324
+ options,
325
+ ) => {
228
326
  const {
229
327
  moduleTransforms,
230
- dev,
231
- tags: tagsOption,
232
- conditions: conditionsOption = tagsOption,
328
+ syncModuleTransforms,
233
329
  searchSuffixes,
234
- commonDependencies,
235
330
  sourceMapHook = undefined,
331
+ useEvaluate = false,
332
+ sourceUrlPrefix = undefined,
333
+ format = undefined,
236
334
  parserForLanguage: parserForLanguageOption = {},
237
- languageForExtension: languageForExtensionOption = {},
238
- commonjsLanguageForExtension: commonjsLanguageForExtensionOption = {},
239
- moduleLanguageForExtension: moduleLanguageForExtensionOption = {},
240
- workspaceLanguageForExtension: workspaceLanguageForExtensionOption = {},
241
- workspaceCommonjsLanguageForExtension:
242
- workspaceCommonjsLanguageForExtensionOption = {},
243
- workspaceModuleLanguageForExtension:
244
- workspaceModuleLanguageForExtensionOption = {},
245
335
  } = options || {};
246
- const conditions = new Set(conditionsOption);
336
+
337
+ /** @type {((module: BundleExit) => BundlerKit) | undefined} */
338
+ let makeExitBundlerKit;
339
+ if (format === 'cjs') {
340
+ makeExitBundlerKit = makeCjsExitBundlerKit;
341
+ }
247
342
 
248
343
  const parserForLanguage = Object.freeze(
249
344
  Object.assign(
@@ -252,74 +347,52 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {
252
347
  parserForLanguageOption,
253
348
  ),
254
349
  );
255
- const languageForExtension = Object.freeze(
256
- Object.assign(Object.create(null), languageForExtensionOption),
257
- );
258
- const commonjsLanguageForExtension = Object.freeze(
259
- Object.assign(Object.create(null), commonjsLanguageForExtensionOption),
260
- );
261
- const moduleLanguageForExtension = Object.freeze(
262
- Object.assign(Object.create(null), moduleLanguageForExtensionOption),
263
- );
264
- const workspaceLanguageForExtension = Object.freeze(
265
- Object.assign(Object.create(null), workspaceLanguageForExtensionOption),
266
- );
267
- const workspaceCommonjsLanguageForExtension = Object.freeze(
268
- Object.assign(
269
- Object.create(null),
270
- workspaceCommonjsLanguageForExtensionOption,
271
- ),
272
- );
273
- const workspaceModuleLanguageForExtension = Object.freeze(
274
- Object.assign(
275
- Object.create(null),
276
- workspaceModuleLanguageForExtensionOption,
277
- ),
278
- );
279
350
 
280
- const {
281
- packageLocation,
282
- packageDescriptorText,
283
- packageDescriptorLocation,
284
- moduleSpecifier,
285
- } = await search(readPowers, moduleLocation);
286
-
287
- const packageDescriptor = parseLocatedJson(
288
- packageDescriptorText,
289
- packageDescriptorLocation,
290
- );
291
- const compartmentMap = await compartmentMapForNodeModules(
292
- read,
293
- packageLocation,
294
- conditions,
295
- packageDescriptor,
296
- moduleSpecifier,
297
- {
298
- dev,
299
- commonDependencies,
300
- languageForExtension,
301
- commonjsLanguageForExtension,
302
- moduleLanguageForExtension,
303
- workspaceLanguageForExtension,
304
- workspaceCommonjsLanguageForExtension,
305
- workspaceModuleLanguageForExtension,
306
- },
307
- );
351
+ const bundlerKitParams = {
352
+ useEvaluate,
353
+ sourceUrlPrefix,
354
+ };
308
355
 
309
356
  const {
310
357
  compartments,
311
358
  entry: { compartment: entryCompartmentName, module: entryModuleSpecifier },
312
359
  } = compartmentMap;
360
+ /** @type {string[]} */
361
+ const exitModuleSpecifiers = [];
313
362
  /** @type {Sources} */
314
363
  const sources = Object.create(null);
315
364
 
316
- const makeImportHook = makeImportHookMaker(read, packageLocation, {
365
+ /**
366
+ * @param {string} moduleSpecifier
367
+ * @param {string} compartmentName
368
+ */
369
+ const exitModuleImportHook =
370
+ format !== undefined
371
+ ? /**
372
+ * @param {string} moduleSpecifier
373
+ * @param {string} compartmentName
374
+ */
375
+ async (moduleSpecifier, compartmentName) => {
376
+ const compartmentSources =
377
+ sources[compartmentName] || Object.create(null);
378
+ sources[compartmentName] = compartmentSources;
379
+ compartmentSources[moduleSpecifier] = {
380
+ exit: moduleSpecifier,
381
+ };
382
+ exitModuleSpecifiers.push(moduleSpecifier);
383
+ return { imports: [], exports: [], execute() {} };
384
+ }
385
+ : undefined;
386
+
387
+ const makeImportHook = makeImportHookMaker(readPowers, entryCompartmentName, {
388
+ archiveOnly: true,
317
389
  sources,
318
390
  compartmentDescriptors: compartments,
319
391
  searchSuffixes,
320
392
  entryCompartmentName,
321
393
  entryModuleSpecifier,
322
394
  sourceMapHook,
395
+ importHook: exitModuleImportHook,
323
396
  });
324
397
 
325
398
  // Induce importHook to record all the necessary modules to import the given module specifier.
@@ -327,6 +400,7 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {
327
400
  resolve,
328
401
  makeImportHook,
329
402
  moduleTransforms,
403
+ syncModuleTransforms,
330
404
  parserForLanguage,
331
405
  });
332
406
  await compartment.load(entryModuleSpecifier);
@@ -336,6 +410,7 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {
336
410
  sources,
337
411
  entryCompartmentName,
338
412
  entryModuleSpecifier,
413
+ exitModuleSpecifiers,
339
414
  );
340
415
 
341
416
  // Create an index of modules so we can resolve import specifiers to the
@@ -348,49 +423,93 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {
348
423
  }
349
424
  const parsersInUse = new Set();
350
425
  for (const module of modules) {
351
- module.indexedImports = Object.fromEntries(
352
- Object.entries(module.resolvedImports).map(([importSpecifier, key]) => {
353
- // UNTIL https://github.com/endojs/endo/issues/1514
354
- // Prefer: key = aliases.get(key) ?? key;
355
- const alias = aliases.get(key);
356
- if (alias != null) {
357
- key = alias;
358
- }
359
- const module = modulesByKey[key];
360
- if (module === undefined) {
361
- throw new Error(
362
- `Unable to locate module for key ${q(key)} import specifier ${q(
363
- importSpecifier,
364
- )} in ${q(module.moduleSpecifier)} of compartment ${q(
365
- module.compartmentName,
366
- )}`,
367
- );
368
- }
369
- const { index } = module;
370
- return [importSpecifier, index];
371
- }),
372
- );
373
- parsersInUse.add(module.parser);
374
- module.bundlerKit = getBundlerKitForModule(module);
426
+ if (module.exit !== undefined) {
427
+ if (makeExitBundlerKit === undefined) {
428
+ // makeExitBundlerKit must have been provided to makeImportHookMaker for any modules with an exit property to have been created.
429
+ throw TypeError('Unreachable');
430
+ }
431
+ module.bundlerKit = makeExitBundlerKit(module);
432
+ } else {
433
+ module.indexedImports = Object.fromEntries(
434
+ Object.entries(module.resolvedImports).map(([importSpecifier, key]) => {
435
+ // UNTIL https://github.com/endojs/endo/issues/1514
436
+ // Prefer: key = aliases.get(key) ?? key;
437
+ const alias = aliases.get(key);
438
+ if (alias != null) {
439
+ key = alias;
440
+ }
441
+ const module = modulesByKey[key];
442
+ if (module === undefined) {
443
+ throw new Error(
444
+ `Unable to locate module for key ${q(key)} import specifier ${q(
445
+ importSpecifier,
446
+ )}`,
447
+ );
448
+ }
449
+ const { index } = module;
450
+ return [importSpecifier, index];
451
+ }),
452
+ );
453
+ parsersInUse.add(module.parser);
454
+ module.bundlerKit = getBundlerKitForModule(module, bundlerKitParams);
455
+ }
375
456
  }
376
457
 
377
- const bundle = `\
378
- 'use strict';
379
- (functors => {
458
+ // Some bundles appeal to the host module system appropriate to their format
459
+ // like `require` for bundles used as CommonJS modules.
460
+ // Each module in the modules array is constructed by a language-specific bundler kit,
461
+ // and in the case of an exit module, is a bundler kit made with
462
+ // makeExitBundlerKit, like makeCjsExitBundlerKit.
463
+ // This will generate a module initialization runtime that in turn needs this
464
+ // namespaceCells utility function to take a host module exports namespace
465
+ // and turn it into a bank of cells for importing and exporting the
466
+ // properties of the module exports namespace object.
467
+ const exitNamespaces =
468
+ exitModuleSpecifiers.length === 0
469
+ ? ''
470
+ : `\
471
+ const namespaceCells = namespace => fromEntries(
472
+ getOwnPropertyNames(namespace)
473
+ .map(name => [name, {
474
+ get() {
475
+ return get(namespace, name);
476
+ },
477
+ set() {
478
+ throw new TypeError('Non-writable export');
479
+ },
480
+ observe(observer) {
481
+ observer(get(namespace, name));
482
+ },
483
+ enumerable: true,
484
+ }])
485
+ );
486
+ `;
380
487
 
488
+ // The linkage runtime creates a cell for every value exported by any of the
489
+ // bundled modules.
490
+ // The interface of a cell is very much like a getter/setter property
491
+ // deescriptor, and additionally has a method for registering an observer to
492
+ // notice when a variable is changed in its originating module, to support
493
+ // live bindings.
494
+ // Each module language defines its own behavior for the generation of its
495
+ // exported cells.
496
+ // After all cells are allocated, each language gets a second opportunity
497
+ // to introduce bindings for cells that the module re-exports from another
498
+ // module, but does not itself own.
499
+ const runtimeLinkageCells = `\
381
500
  const cell = (name, value = undefined) => {
382
501
  const observers = [];
383
- return Object.freeze({
384
- get: Object.freeze(() => {
502
+ return freeze({
503
+ get: freeze(() => {
385
504
  return value;
386
505
  }),
387
- set: Object.freeze((newValue) => {
506
+ set: freeze((newValue) => {
388
507
  value = newValue;
389
508
  for (const observe of observers) {
390
509
  observe(value);
391
510
  }
392
511
  }),
393
- observe: Object.freeze((observe) => {
512
+ observe: freeze((observe) => {
394
513
  observers.push(observe);
395
514
  observe(value);
396
515
  }),
@@ -403,8 +522,15 @@ ${''.concat(...modules.map(m => m.bundlerKit.getCells()))}\
403
522
  ];
404
523
 
405
524
  ${''.concat(...modules.map(m => m.bundlerKit.getReexportsWiring()))}\
525
+ `;
406
526
 
407
- const namespaces = cells.map(cells => Object.freeze(Object.create(null, {
527
+ // The linker runtime includes a parallel array of module exports namespace
528
+ // objects for each bundled module, for each respective index of the module
529
+ // functors array.
530
+ // Each namespace has a special '*' property for the namespace object itself,
531
+ // which is what modules obtain with `import * as x from 'x'` notation.
532
+ const moduleNamespaces = `\
533
+ const namespaces = cells.map(cells => freeze(create(null, {
408
534
  ...cells,
409
535
  // Make this appear like an ESM module namespace object.
410
536
  [Symbol.toStringTag]: {
@@ -418,16 +544,162 @@ ${''.concat(...modules.map(m => m.bundlerKit.getReexportsWiring()))}\
418
544
  for (let index = 0; index < namespaces.length; index += 1) {
419
545
  cells[index]['*'] = cell('*', namespaces[index]);
420
546
  }
547
+ `;
421
548
 
422
- ${''.concat(...Array.from(parsersInUse).map(parser => getRuntime(parser)))}
549
+ // Each language in use within the bundle has an opportunity to inject
550
+ // utilities into the bundle runtime that it can use in the shared lexical
551
+ // scope of module execution.
552
+ // CommonJS in particular injects a utility function here, if the script
553
+ // entrains any CommonJS modules.
554
+ const languageRuntimeExtensions = `\
555
+ ${''.concat(...Array.from(parsersInUse).map(parser => getRuntime(parser)))}\
556
+ `;
423
557
 
558
+ // This section of the linker runtime causes each of the modules to execute
559
+ // in topological order, using a language-specific calling convention to
560
+ // link its imports and exports to other modules.
561
+ const moduleExecutionRuntime = `\
424
562
  ${''.concat(...modules.map(m => m.bundlerKit.getFunctorCall()))}\
563
+ `;
564
+
565
+ // The linker runtime receives an array of language-specific representations
566
+ // of each module, which in the simplest case is just a function and a
567
+ // runtime initialization calling convention (a functor).
568
+ // Then, in the style of partial application, it receives runtime options.
569
+ // When driven by makeScript, the script will statically apply the options,
570
+ // but with makeFunctor, the runtime must evaluate and apply runtime options.
571
+ // Scripts are suitable for injection with <script> tags on the web, whereas
572
+ // functors require use of an evaluator at runtime.
573
+ const linkerRuntime = `functors => options => {
574
+ 'use strict';
575
+
576
+ const {
577
+ Map,
578
+ Object,
579
+ ReferenceError,
580
+ Reflect,
581
+ TypeError,
582
+ } = globalThis;
583
+ const {
584
+ create,
585
+ defineProperties,
586
+ defineProperty,
587
+ freeze,
588
+ fromEntries,
589
+ getOwnPropertyDescriptors,
590
+ getOwnPropertyNames,
591
+ keys,
592
+ } = Object;
593
+ const { get, set } = Reflect;
594
+
595
+ const {
596
+ ${
597
+ !useEvaluate
598
+ ? ''
599
+ : `\
600
+ evaluate = eval,
601
+ sourceUrlPrefix = ${JSON.stringify(sourceUrlPrefix)},
602
+ `
603
+ }\
604
+ ${
605
+ format !== 'cjs'
606
+ ? ''
607
+ : `\
608
+ require: tryRequire = typeof require === 'function' ? require : specifier => {
609
+ throw new TypeError('Cannot import host module: ' + specifier);
610
+ },
611
+ `
612
+ }\
613
+ } = options || {};
614
+
615
+ ${
616
+ !useEvaluate
617
+ ? ''
618
+ : `\
619
+ const evaluateSource = (source, sourceUrl) => {
620
+ return evaluate(source + '\\n//# sourceURL=' + sourceUrlPrefix + sourceUrl + '\\n');
621
+ };`
622
+ }\
623
+
624
+ ${exitNamespaces}\
625
+
626
+ ${runtimeLinkageCells}\
627
+
628
+ ${moduleNamespaces}\
629
+
630
+ ${languageRuntimeExtensions}\
631
+
632
+ ${moduleExecutionRuntime}\
425
633
 
426
634
  return cells[cells.length - 1]['*'].get();
427
- })([${''.concat(...modules.map(m => m.bundlerKit.getFunctor()))}]);
428
- `;
635
+ }`;
636
+
637
+ // An array of language-specific representations of each bundled module,
638
+ // which in the simplest case is a function that must be initialized by the
639
+ // linkage runtime using a calling convention.
640
+ // We pass this array into the linkage runtime rather than embedding it in
641
+ // the linkage runtime in order to assure that the runtime's lexical context
642
+ // doesn't overshadow each module's lexical scope.
643
+ const moduleFunctors = `[
644
+ ${''.concat(
645
+ ...modules.map(
646
+ (m, index) => `\
647
+ // === ${index}. ${m.sourceDirname} ${m.moduleSpecifier} ===
648
+ ${m.bundlerKit.getFunctor()}`,
649
+ ),
650
+ )}\
651
+ ]`;
652
+
653
+ // Functors partially apply the linker runtime.
654
+ // Scripts go on to apply static options and execute immediately.
655
+ return `(${linkerRuntime})(${moduleFunctors})`;
656
+ };
657
+
658
+ /**
659
+ * @param {ReadFn | ReadPowers | MaybeReadPowers} readPowers
660
+ * @param {CompartmentMapDescriptor} compartmentMap
661
+ * @param {BundleOptions} [options]
662
+ * @returns {Promise<string>}
663
+ */
664
+ export const makeScriptFromMap = async (
665
+ readPowers,
666
+ compartmentMap,
667
+ options,
668
+ ) => {
669
+ // Functors partially apply the linker runtime.
670
+ // Scripts go on to apply static options and execute immediately.
671
+ const functor = await makeFunctorFromMap(readPowers, compartmentMap, options);
672
+ return `${functor}()`;
673
+ };
674
+
675
+ /**
676
+ * @param {ReadFn | ReadPowers | MaybeReadPowers} readPowers
677
+ * @param {string} moduleLocation
678
+ * @param {BundleOptions} [options]
679
+ * @returns {Promise<string>}
680
+ */
681
+ export const makeFunctor = async (readPowers, moduleLocation, options) => {
682
+ const compartmentMap = await mapNodeModules(
683
+ readPowers,
684
+ moduleLocation,
685
+ options,
686
+ );
687
+ return makeFunctorFromMap(readPowers, compartmentMap, options);
688
+ };
429
689
 
430
- return bundle;
690
+ /**
691
+ * @param {ReadFn | ReadPowers | MaybeReadPowers} readPowers
692
+ * @param {string} moduleLocation
693
+ * @param {BundleOptions} [options]
694
+ * @returns {Promise<string>}
695
+ */
696
+ export const makeScript = async (readPowers, moduleLocation, options) => {
697
+ const compartmentMap = await mapNodeModules(
698
+ readPowers,
699
+ moduleLocation,
700
+ options,
701
+ );
702
+ return makeScriptFromMap(readPowers, compartmentMap, options);
431
703
  };
432
704
 
433
705
  /**
@@ -435,16 +707,16 @@ ${''.concat(...modules.map(m => m.bundlerKit.getFunctorCall()))}\
435
707
  * @param {ReadFn} read
436
708
  * @param {string} bundleLocation
437
709
  * @param {string} moduleLocation
438
- * @param {ArchiveOptions} [options]
710
+ * @param {BundleOptions} [options]
439
711
  */
440
- export const writeBundle = async (
712
+ export const writeScript = async (
441
713
  write,
442
714
  read,
443
715
  bundleLocation,
444
716
  moduleLocation,
445
717
  options,
446
718
  ) => {
447
- const bundleString = await makeBundle(read, moduleLocation, options);
719
+ const bundleString = await makeScript(read, moduleLocation, options);
448
720
  const bundleBytes = textEncoder.encode(bundleString);
449
721
  await write(bundleLocation, bundleBytes);
450
722
  };