@endo/compartment-mapper 1.5.0 → 1.6.1

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 (143) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +278 -111
  3. package/SECURITY.md +2 -2
  4. package/bundle.d.ts +1 -1
  5. package/bundle.js +4 -1
  6. package/functor-lite.d.ts +3 -0
  7. package/functor-lite.d.ts.map +1 -0
  8. package/functor-lite.js +4 -0
  9. package/functor.d.ts +3 -0
  10. package/functor.d.ts.map +1 -0
  11. package/functor.js +4 -0
  12. package/index.d.ts +1 -1
  13. package/index.js +4 -1
  14. package/package.json +15 -11
  15. package/script-lite.d.ts +3 -0
  16. package/script-lite.d.ts.map +1 -0
  17. package/script-lite.js +4 -0
  18. package/script.d.ts +3 -0
  19. package/script.d.ts.map +1 -0
  20. package/script.js +4 -0
  21. package/src/archive-lite.d.ts +5 -5
  22. package/src/archive-lite.d.ts.map +1 -1
  23. package/src/archive-lite.js +1 -1
  24. package/src/archive.d.ts +5 -5
  25. package/src/archive.d.ts.map +1 -1
  26. package/src/archive.js +3 -1
  27. package/src/bundle-cjs.d.ts +12 -2
  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 +667 -0
  35. package/src/bundle-mjs.d.ts +13 -3
  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 +392 -126
  41. package/src/capture-lite.d.ts +1 -1
  42. package/src/capture-lite.d.ts.map +1 -1
  43. package/src/capture-lite.js +4 -2
  44. package/src/compartment-map.d.ts +1 -1
  45. package/src/compartment-map.d.ts.map +1 -1
  46. package/src/import-archive-lite.d.ts +2 -2
  47. package/src/import-archive-lite.d.ts.map +1 -1
  48. package/src/import-archive-lite.js +3 -1
  49. package/src/import-archive.d.ts +3 -3
  50. package/src/import-archive.d.ts.map +1 -1
  51. package/src/import-archive.js +3 -1
  52. package/src/import-hook.d.ts +3 -16
  53. package/src/import-hook.d.ts.map +1 -1
  54. package/src/import-hook.js +214 -116
  55. package/src/import-lite.d.ts +1 -1
  56. package/src/import-lite.d.ts.map +1 -1
  57. package/src/import-lite.js +7 -3
  58. package/src/import.d.ts.map +1 -1
  59. package/src/import.js +3 -1
  60. package/src/infer-exports.d.ts +5 -7
  61. package/src/infer-exports.d.ts.map +1 -1
  62. package/src/infer-exports.js +23 -8
  63. package/src/link.d.ts.map +1 -1
  64. package/src/link.js +4 -1
  65. package/src/map-parser.d.ts.map +1 -1
  66. package/src/map-parser.js +51 -5
  67. package/src/node-module-specifier.d.ts.map +1 -1
  68. package/src/node-module-specifier.js +3 -1
  69. package/src/node-modules.d.ts +4 -47
  70. package/src/node-modules.d.ts.map +1 -1
  71. package/src/node-modules.js +267 -148
  72. package/src/node-powers.d.ts +1 -1
  73. package/src/node-powers.d.ts.map +1 -1
  74. package/src/node-powers.js +3 -1
  75. package/src/parse-archive-cjs.d.ts +5 -1
  76. package/src/parse-archive-cjs.d.ts.map +1 -1
  77. package/src/parse-archive-cjs.js +11 -4
  78. package/src/parse-archive-mjs.d.ts +5 -1
  79. package/src/parse-archive-mjs.d.ts.map +1 -1
  80. package/src/parse-archive-mjs.js +3 -1
  81. package/src/parse-bytes.d.ts +5 -1
  82. package/src/parse-bytes.d.ts.map +1 -1
  83. package/src/parse-bytes.js +3 -1
  84. package/src/parse-cjs-shared-export-wrapper.d.ts.map +1 -1
  85. package/src/parse-cjs-shared-export-wrapper.js +5 -11
  86. package/src/parse-cjs.d.ts +5 -1
  87. package/src/parse-cjs.d.ts.map +1 -1
  88. package/src/parse-cjs.js +4 -2
  89. package/src/parse-json.d.ts +5 -2
  90. package/src/parse-json.d.ts.map +1 -1
  91. package/src/parse-mjs.d.ts +5 -1
  92. package/src/parse-mjs.d.ts.map +1 -1
  93. package/src/parse-mjs.js +2 -2
  94. package/src/parse-pre-cjs.d.ts +5 -1
  95. package/src/parse-pre-cjs.d.ts.map +1 -1
  96. package/src/parse-pre-cjs.js +3 -1
  97. package/src/parse-pre-mjs.d.ts +5 -1
  98. package/src/parse-pre-mjs.d.ts.map +1 -1
  99. package/src/parse-pre-mjs.js +3 -1
  100. package/src/parse-text.d.ts +5 -1
  101. package/src/parse-text.d.ts.map +1 -1
  102. package/src/parse-text.js +3 -1
  103. package/src/policy-format.d.ts +2 -1
  104. package/src/policy-format.d.ts.map +1 -1
  105. package/src/policy-format.js +5 -2
  106. package/src/policy.d.ts +2 -2
  107. package/src/policy.d.ts.map +1 -1
  108. package/src/policy.js +10 -11
  109. package/src/powers.d.ts +1 -1
  110. package/src/powers.d.ts.map +1 -1
  111. package/src/powers.js +3 -1
  112. package/src/search.d.ts +7 -12
  113. package/src/search.d.ts.map +1 -1
  114. package/src/search.js +32 -13
  115. package/src/types/compartment-map-schema.d.ts +8 -1
  116. package/src/types/compartment-map-schema.d.ts.map +1 -1
  117. package/src/types/compartment-map-schema.ts +8 -1
  118. package/src/types/external.d.ts +127 -17
  119. package/src/types/external.d.ts.map +1 -1
  120. package/src/types/external.ts +142 -17
  121. package/src/types/internal.d.ts +116 -29
  122. package/src/types/internal.d.ts.map +1 -1
  123. package/src/types/internal.ts +144 -31
  124. package/src/types/node-modules.d.ts +79 -0
  125. package/src/types/node-modules.d.ts.map +1 -0
  126. package/src/types/node-modules.ts +89 -0
  127. package/src/types/node-powers.d.ts +7 -5
  128. package/src/types/node-powers.d.ts.map +1 -1
  129. package/src/types/node-powers.ts +7 -5
  130. package/src/types/policy-schema.d.ts +3 -1
  131. package/src/types/policy-schema.d.ts.map +1 -1
  132. package/src/types/policy-schema.ts +3 -1
  133. package/src/types/policy.d.ts +3 -1
  134. package/src/types/policy.d.ts.map +1 -1
  135. package/src/types/policy.ts +3 -1
  136. package/src/types/powers.d.ts +5 -3
  137. package/src/types/powers.d.ts.map +1 -1
  138. package/src/types/powers.ts +5 -3
  139. package/src/types/typescript.d.ts +3 -1
  140. package/src/types/typescript.d.ts.map +1 -1
  141. package/src/types/typescript.ts +3 -1
  142. package/src/url.d.ts.map +1 -1
  143. package/src/url.js +3 -1
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,7 +192,11 @@ const sortedModules = (
116
192
 
117
193
  const source = compartmentSources[compartmentName][moduleSpecifier];
118
194
  if (source !== undefined) {
119
- const { record, parser, deferredError, bytes } = source;
195
+ const { record, parser, deferredError, bytes, sourceDirname, exit } =
196
+ source;
197
+ if (exit !== undefined) {
198
+ return exit;
199
+ }
120
200
  assert(
121
201
  bytes !== undefined,
122
202
  `No bytes for ${moduleSpecifier} in ${compartmentName}`,
@@ -125,6 +205,10 @@ const sortedModules = (
125
205
  parser !== undefined,
126
206
  `No parser for ${moduleSpecifier} in ${compartmentName}`,
127
207
  );
208
+ assert(
209
+ sourceDirname !== undefined,
210
+ `No sourceDirname for ${moduleSpecifier} in ${compartmentName}`,
211
+ );
128
212
  if (deferredError) {
129
213
  throw Error(
130
214
  `Cannot bundle: encountered deferredError ${deferredError}`,
@@ -149,6 +233,7 @@ const sortedModules = (
149
233
  key,
150
234
  compartmentName,
151
235
  moduleSpecifier,
236
+ sourceDirname,
152
237
  parser,
153
238
  record,
154
239
  resolvedImports,
@@ -190,7 +275,7 @@ const sortedModules = (
190
275
  return { modules, aliases };
191
276
  };
192
277
 
193
- /** @type {Record<string, BundlerSupport<unknown>>} */
278
+ /** @type {Record<string, BundlerSupport<any>>} */
194
279
  const bundlerSupportForLanguage = {
195
280
  'pre-mjs-json': mjsSupport,
196
281
  'pre-cjs-json': cjsSupport,
@@ -203,8 +288,13 @@ const getRuntime = language =>
203
288
  ? bundlerSupportForLanguage[language].runtime
204
289
  : `/*unknown language:${language}*/`;
205
290
 
206
- /** @param {BundleModule<unknown>} module */
207
- 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) => {
208
298
  const language = module.parser;
209
299
  assert(language !== undefined);
210
300
  if (bundlerSupportForLanguage[language] === undefined) {
@@ -219,37 +309,36 @@ const getBundlerKitForModule = module => {
219
309
  };
220
310
  }
221
311
  const { getBundlerKit } = bundlerSupportForLanguage[language];
222
- return getBundlerKit(module);
312
+ return getBundlerKit(module, params);
223
313
  };
224
314
 
225
315
  /**
226
316
  * @param {ReadFn | ReadPowers | MaybeReadPowers} readPowers
227
- * @param {string} moduleLocation
228
- * @param {ArchiveOptions} [options]
317
+ * @param {CompartmentMapDescriptor} compartmentMap
318
+ * @param {BundleOptions} [options]
229
319
  * @returns {Promise<string>}
230
320
  */
231
- export const makeBundle = async (readPowers, moduleLocation, options) => {
232
- const { read } = unpackReadPowers(readPowers);
233
-
321
+ export const makeFunctorFromMap = async (
322
+ readPowers,
323
+ compartmentMap,
324
+ options,
325
+ ) => {
234
326
  const {
235
327
  moduleTransforms,
236
- dev,
237
- tags: tagsOption,
238
- conditions: conditionsOption = tagsOption,
328
+ syncModuleTransforms,
239
329
  searchSuffixes,
240
- commonDependencies,
241
330
  sourceMapHook = undefined,
331
+ useEvaluate = false,
332
+ sourceUrlPrefix = undefined,
333
+ format = undefined,
242
334
  parserForLanguage: parserForLanguageOption = {},
243
- languageForExtension: languageForExtensionOption = {},
244
- commonjsLanguageForExtension: commonjsLanguageForExtensionOption = {},
245
- moduleLanguageForExtension: moduleLanguageForExtensionOption = {},
246
- workspaceLanguageForExtension: workspaceLanguageForExtensionOption = {},
247
- workspaceCommonjsLanguageForExtension:
248
- workspaceCommonjsLanguageForExtensionOption = {},
249
- workspaceModuleLanguageForExtension:
250
- workspaceModuleLanguageForExtensionOption = {},
251
335
  } = options || {};
252
- const conditions = new Set(conditionsOption);
336
+
337
+ /** @type {((module: BundleExit) => BundlerKit) | undefined} */
338
+ let makeExitBundlerKit;
339
+ if (format === 'cjs') {
340
+ makeExitBundlerKit = makeCjsExitBundlerKit;
341
+ }
253
342
 
254
343
  const parserForLanguage = Object.freeze(
255
344
  Object.assign(
@@ -258,74 +347,52 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {
258
347
  parserForLanguageOption,
259
348
  ),
260
349
  );
261
- const languageForExtension = Object.freeze(
262
- Object.assign(Object.create(null), languageForExtensionOption),
263
- );
264
- const commonjsLanguageForExtension = Object.freeze(
265
- Object.assign(Object.create(null), commonjsLanguageForExtensionOption),
266
- );
267
- const moduleLanguageForExtension = Object.freeze(
268
- Object.assign(Object.create(null), moduleLanguageForExtensionOption),
269
- );
270
- const workspaceLanguageForExtension = Object.freeze(
271
- Object.assign(Object.create(null), workspaceLanguageForExtensionOption),
272
- );
273
- const workspaceCommonjsLanguageForExtension = Object.freeze(
274
- Object.assign(
275
- Object.create(null),
276
- workspaceCommonjsLanguageForExtensionOption,
277
- ),
278
- );
279
- const workspaceModuleLanguageForExtension = Object.freeze(
280
- Object.assign(
281
- Object.create(null),
282
- workspaceModuleLanguageForExtensionOption,
283
- ),
284
- );
285
350
 
286
- const {
287
- packageLocation,
288
- packageDescriptorText,
289
- packageDescriptorLocation,
290
- moduleSpecifier,
291
- } = await search(readPowers, moduleLocation);
292
-
293
- const packageDescriptor = parseLocatedJson(
294
- packageDescriptorText,
295
- packageDescriptorLocation,
296
- );
297
- const compartmentMap = await compartmentMapForNodeModules(
298
- read,
299
- packageLocation,
300
- conditions,
301
- packageDescriptor,
302
- moduleSpecifier,
303
- {
304
- dev,
305
- commonDependencies,
306
- languageForExtension,
307
- commonjsLanguageForExtension,
308
- moduleLanguageForExtension,
309
- workspaceLanguageForExtension,
310
- workspaceCommonjsLanguageForExtension,
311
- workspaceModuleLanguageForExtension,
312
- },
313
- );
351
+ const bundlerKitParams = {
352
+ useEvaluate,
353
+ sourceUrlPrefix,
354
+ };
314
355
 
315
356
  const {
316
357
  compartments,
317
358
  entry: { compartment: entryCompartmentName, module: entryModuleSpecifier },
318
359
  } = compartmentMap;
360
+ /** @type {string[]} */
361
+ const exitModuleSpecifiers = [];
319
362
  /** @type {Sources} */
320
363
  const sources = Object.create(null);
321
364
 
322
- 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,
323
389
  sources,
324
390
  compartmentDescriptors: compartments,
325
391
  searchSuffixes,
326
392
  entryCompartmentName,
327
393
  entryModuleSpecifier,
328
394
  sourceMapHook,
395
+ importHook: exitModuleImportHook,
329
396
  });
330
397
 
331
398
  // Induce importHook to record all the necessary modules to import the given module specifier.
@@ -333,6 +400,7 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {
333
400
  resolve,
334
401
  makeImportHook,
335
402
  moduleTransforms,
403
+ syncModuleTransforms,
336
404
  parserForLanguage,
337
405
  });
338
406
  await compartment.load(entryModuleSpecifier);
@@ -342,6 +410,7 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {
342
410
  sources,
343
411
  entryCompartmentName,
344
412
  entryModuleSpecifier,
413
+ exitModuleSpecifiers,
345
414
  );
346
415
 
347
416
  // Create an index of modules so we can resolve import specifiers to the
@@ -354,49 +423,93 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {
354
423
  }
355
424
  const parsersInUse = new Set();
356
425
  for (const module of modules) {
357
- module.indexedImports = Object.fromEntries(
358
- Object.entries(module.resolvedImports).map(([importSpecifier, key]) => {
359
- // UNTIL https://github.com/endojs/endo/issues/1514
360
- // Prefer: key = aliases.get(key) ?? key;
361
- const alias = aliases.get(key);
362
- if (alias != null) {
363
- key = alias;
364
- }
365
- const module = modulesByKey[key];
366
- if (module === undefined) {
367
- throw new Error(
368
- `Unable to locate module for key ${q(key)} import specifier ${q(
369
- importSpecifier,
370
- )} in ${q(module.moduleSpecifier)} of compartment ${q(
371
- module.compartmentName,
372
- )}`,
373
- );
374
- }
375
- const { index } = module;
376
- return [importSpecifier, index];
377
- }),
378
- );
379
- parsersInUse.add(module.parser);
380
- 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
+ }
381
456
  }
382
457
 
383
- const bundle = `\
384
- 'use strict';
385
- (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
+ `;
386
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 = `\
387
500
  const cell = (name, value = undefined) => {
388
501
  const observers = [];
389
- return Object.freeze({
390
- get: Object.freeze(() => {
502
+ return freeze({
503
+ get: freeze(() => {
391
504
  return value;
392
505
  }),
393
- set: Object.freeze((newValue) => {
506
+ set: freeze((newValue) => {
394
507
  value = newValue;
395
508
  for (const observe of observers) {
396
509
  observe(value);
397
510
  }
398
511
  }),
399
- observe: Object.freeze((observe) => {
512
+ observe: freeze((observe) => {
400
513
  observers.push(observe);
401
514
  observe(value);
402
515
  }),
@@ -409,8 +522,15 @@ ${''.concat(...modules.map(m => m.bundlerKit.getCells()))}\
409
522
  ];
410
523
 
411
524
  ${''.concat(...modules.map(m => m.bundlerKit.getReexportsWiring()))}\
525
+ `;
412
526
 
413
- 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, {
414
534
  ...cells,
415
535
  // Make this appear like an ESM module namespace object.
416
536
  [Symbol.toStringTag]: {
@@ -424,16 +544,162 @@ ${''.concat(...modules.map(m => m.bundlerKit.getReexportsWiring()))}\
424
544
  for (let index = 0; index < namespaces.length; index += 1) {
425
545
  cells[index]['*'] = cell('*', namespaces[index]);
426
546
  }
547
+ `;
427
548
 
428
- ${''.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
+ `;
429
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 = `\
430
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}\
431
633
 
432
634
  return cells[cells.length - 1]['*'].get();
433
- })([${''.concat(...modules.map(m => m.bundlerKit.getFunctor()))}]);
434
- `;
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
+ };
435
657
 
436
- return bundle;
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
+ };
689
+
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);
437
703
  };
438
704
 
439
705
  /**
@@ -441,16 +707,16 @@ ${''.concat(...modules.map(m => m.bundlerKit.getFunctorCall()))}\
441
707
  * @param {ReadFn} read
442
708
  * @param {string} bundleLocation
443
709
  * @param {string} moduleLocation
444
- * @param {ArchiveOptions} [options]
710
+ * @param {BundleOptions} [options]
445
711
  */
446
- export const writeBundle = async (
712
+ export const writeScript = async (
447
713
  write,
448
714
  read,
449
715
  bundleLocation,
450
716
  moduleLocation,
451
717
  options,
452
718
  ) => {
453
- const bundleString = await makeBundle(read, moduleLocation, options);
719
+ const bundleString = await makeScript(read, moduleLocation, options);
454
720
  const bundleBytes = textEncoder.encode(bundleString);
455
721
  await write(bundleLocation, bundleBytes);
456
722
  };