@endo/compartment-mapper 0.8.0 → 0.8.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@endo/compartment-mapper",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "The compartment mapper assembles Node applications in a sandbox",
5
5
  "keywords": [
6
6
  "node",
@@ -41,14 +41,14 @@
41
41
  "test": "ava"
42
42
  },
43
43
  "dependencies": {
44
- "@endo/cjs-module-analyzer": "^0.2.28",
45
- "@endo/static-module-record": "^0.7.15",
46
- "@endo/zip": "^0.2.28",
47
- "ses": "^0.18.0"
44
+ "@endo/cjs-module-analyzer": "^0.2.29",
45
+ "@endo/static-module-record": "^0.7.16",
46
+ "@endo/zip": "^0.2.29",
47
+ "ses": "^0.18.1"
48
48
  },
49
49
  "devDependencies": {
50
- "@endo/eslint-config": "^0.5.1",
51
- "ava": "^3.12.1",
50
+ "@endo/eslint-config": "^0.5.2",
51
+ "ava": "^5.1.0",
52
52
  "babel-eslint": "^10.0.3",
53
53
  "c8": "^7.7.3",
54
54
  "eslint": "^7.32.0",
@@ -57,7 +57,7 @@
57
57
  "eslint-plugin-eslint-comments": "^3.1.2",
58
58
  "eslint-plugin-import": "^2.26.0",
59
59
  "eslint-plugin-prettier": "^3.4.1",
60
- "prettier": "^1.19.1",
60
+ "prettier": "^2.8.0",
61
61
  "typescript": "~4.8.4"
62
62
  },
63
63
  "files": [
@@ -76,6 +76,7 @@
76
76
  ]
77
77
  },
78
78
  "prettier": {
79
+ "arrowParens": "avoid",
79
80
  "trailingComma": "all",
80
81
  "singleQuote": true,
81
82
  "overrides": [
@@ -93,5 +94,5 @@
93
94
  ],
94
95
  "timeout": "2m"
95
96
  },
96
- "gitHead": "da16a94856482e36296b7cae16d715aa63344928"
97
+ "gitHead": "ab8d64ae6fc9c628a2d1c02d16bf9ef249f5c8dc"
97
98
  }
package/src/archive.js CHANGED
@@ -252,7 +252,10 @@ const digestLocation = async (powers, moduleLocation, options) => {
252
252
  moduleTransforms,
253
253
  modules: exitModules = {},
254
254
  dev = false,
255
+ tags = new Set(),
255
256
  captureSourceLocation = undefined,
257
+ searchSuffixes = undefined,
258
+ commonDependencies = undefined,
256
259
  } = options || {};
257
260
  const { read, computeSha512 } = unpackReadPowers(powers);
258
261
  const {
@@ -262,8 +265,6 @@ const digestLocation = async (powers, moduleLocation, options) => {
262
265
  moduleSpecifier,
263
266
  } = await search(read, moduleLocation);
264
267
 
265
- /** @type {Set<string>} */
266
- const tags = new Set();
267
268
  tags.add('endo');
268
269
  tags.add('import');
269
270
  tags.add('default');
@@ -278,7 +279,7 @@ const digestLocation = async (powers, moduleLocation, options) => {
278
279
  tags,
279
280
  packageDescriptor,
280
281
  moduleSpecifier,
281
- { dev },
282
+ { dev, commonDependencies },
282
283
  );
283
284
 
284
285
  const {
@@ -296,6 +297,7 @@ const digestLocation = async (powers, moduleLocation, options) => {
296
297
  compartments,
297
298
  exitModules,
298
299
  computeSha512,
300
+ searchSuffixes,
299
301
  );
300
302
 
301
303
  // Induce importHook to record all the necessary modules to import the given module specifier.
@@ -0,0 +1,55 @@
1
+ /** quotes strings */
2
+ const q = JSON.stringify;
3
+
4
+ const exportsCellRecord = exportsList =>
5
+ ''.concat(
6
+ ...exportsList.map(
7
+ exportName => `\
8
+ ${exportName}: cell(${q(exportName)}),
9
+ `,
10
+ ),
11
+ );
12
+
13
+ // This function is serialized and references variables from its destination scope.
14
+ const runtime = function wrapCjsFunctor(num) {
15
+ /* eslint-disable no-undef */
16
+ return ({ imports = {} }) => {
17
+ const cModule = Object.freeze(
18
+ Object.defineProperty({}, 'exports', cells[num].default),
19
+ );
20
+ // TODO: specifier not found handling
21
+ const requireImpl = specifier => cells[imports[specifier]].default.get();
22
+ functors[num](Object.freeze(requireImpl), cModule.exports, cModule);
23
+ Object.keys(cells[num])
24
+ .filter(k => k !== 'default' && k !== '*')
25
+ .map(k => cells[num][k].set(cModule.exports[k]));
26
+ };
27
+ /* eslint-enable no-undef */
28
+ }.toString();
29
+
30
+ export default {
31
+ runtime,
32
+ getBundlerKit({
33
+ index,
34
+ indexedImports,
35
+ record: { cjsFunctor, exports: exportsList = {} },
36
+ }) {
37
+ const importsMap = JSON.stringify(indexedImports);
38
+
39
+ return {
40
+ getFunctor: () => `\
41
+ // === functors[${index}] ===
42
+ ${cjsFunctor},
43
+ `,
44
+ getCells: () => `\
45
+ {
46
+ ${exportsCellRecord(exportsList)}\
47
+ },
48
+ `,
49
+ getReexportsWiring: () => '',
50
+ getFunctorCall: () => `\
51
+ wrapCjsFunctor(${index})({imports: ${importsMap}});
52
+ `,
53
+ };
54
+ },
55
+ };
@@ -0,0 +1,120 @@
1
+ /** quotes strings */
2
+ const q = JSON.stringify;
3
+
4
+ const exportsCellRecord = exportMap =>
5
+ ''.concat(
6
+ ...Object.keys(exportMap).map(
7
+ exportName => `\
8
+ ${exportName}: cell(${q(exportName)}),
9
+ `,
10
+ ),
11
+ );
12
+
13
+ const importsCellSetter = (exportMap, index) =>
14
+ ''.concat(
15
+ ...Object.entries(exportMap).map(
16
+ ([exportName, [importName]]) => `\
17
+ ${importName}: cells[${index}].${exportName}.set,
18
+ `,
19
+ ),
20
+ );
21
+
22
+ const adaptReexport = reexportMap => {
23
+ if (!reexportMap) {
24
+ return {};
25
+ }
26
+ const ret = Object.fromEntries(
27
+ Object.values(reexportMap)
28
+ .flat()
29
+ .map(([local, exported]) => [exported, [local]]),
30
+ );
31
+ return ret;
32
+ };
33
+
34
+ export const runtime = `\
35
+ function observeImports(map, importName, importIndex) {
36
+ for (const [name, observers] of map.get(importName)) {
37
+ const cell = cells[importIndex][name];
38
+ if (cell === undefined) {
39
+ throw new ReferenceError(\`Cannot import name \${name}\`);
40
+ }
41
+ for (const observer of observers) {
42
+ cell.observe(observer);
43
+ }
44
+ }
45
+ }
46
+ `;
47
+
48
+ export default {
49
+ runtime,
50
+ getBundlerKit({
51
+ index,
52
+ indexedImports,
53
+ record: {
54
+ __syncModuleProgram__,
55
+ __fixedExportMap__ = {},
56
+ __liveExportMap__ = {},
57
+ __reexportMap__ = {},
58
+ reexports,
59
+ },
60
+ }) {
61
+ return {
62
+ getFunctor: () => `\
63
+ // === functors[${index}] ===
64
+ ${__syncModuleProgram__},
65
+ `,
66
+ getCells: () => `\
67
+ {
68
+ ${exportsCellRecord(__fixedExportMap__)}${exportsCellRecord(
69
+ __liveExportMap__,
70
+ )}${exportsCellRecord(adaptReexport(__reexportMap__))}\
71
+ },
72
+ `,
73
+ getReexportsWiring: () => {
74
+ const mappings = reexports.map(
75
+ importSpecifier => `\
76
+ Object.defineProperties(cells[${index}], Object.getOwnPropertyDescriptors(cells[${indexedImports[importSpecifier]}]));
77
+ `,
78
+ );
79
+ // Create references for export name as newname
80
+ const namedReexportsToProcess = Object.entries(__reexportMap__);
81
+ if (namedReexportsToProcess.length > 0) {
82
+ mappings.push(`
83
+ Object.defineProperties(cells[${index}], {${namedReexportsToProcess.map(
84
+ ([specifier, renames]) => {
85
+ return renames.map(
86
+ ([localName, exportedName]) =>
87
+ `${q(exportedName)}: { value: cells[${
88
+ indexedImports[specifier]
89
+ }][${q(localName)}] }`,
90
+ );
91
+ },
92
+ )} });
93
+ `);
94
+ }
95
+ return mappings.join('');
96
+ },
97
+ getFunctorCall: () => `\
98
+ functors[${index}]({
99
+ imports(entries) {
100
+ const map = new Map(entries);
101
+ ${''.concat(
102
+ ...Object.entries(indexedImports).map(
103
+ ([importName, importIndex]) => `\
104
+ observeImports(map, ${q(importName)}, ${importIndex});
105
+ `,
106
+ ),
107
+ )}\
108
+ },
109
+ liveVar: {
110
+ ${importsCellSetter(__liveExportMap__, index)}\
111
+ },
112
+ onceVar: {
113
+ ${importsCellSetter(__fixedExportMap__, index)}\
114
+ },
115
+ importMeta: {},
116
+ });
117
+ `,
118
+ };
119
+ },
120
+ };
package/src/bundle.js CHANGED
@@ -24,10 +24,10 @@ import parserArchiveCjs from './parse-archive-cjs.js';
24
24
  import parserArchiveMjs from './parse-archive-mjs.js';
25
25
  import { parseLocatedJson } from './json.js';
26
26
 
27
- const textEncoder = new TextEncoder();
27
+ import mjsSupport from './bundle-mjs.js';
28
+ import cjsSupport from './bundle-cjs.js';
28
29
 
29
- /** quotes strings */
30
- const q = JSON.stringify;
30
+ const textEncoder = new TextEncoder();
31
31
 
32
32
  /** @type {Record<string, ParserImplementation>} */
33
33
  const parserForLanguage = {
@@ -70,13 +70,16 @@ const sortedModules = (
70
70
 
71
71
  const resolve = compartmentResolvers[compartmentName];
72
72
  const source = compartmentSources[compartmentName][moduleSpecifier];
73
- if (source) {
74
- const { record, parser } = source;
73
+ if (source !== undefined) {
74
+ const { record, parser, deferredError } = source;
75
+ if (deferredError) {
76
+ throw new Error(
77
+ `Cannot bundle: encountered deferredError ${deferredError}`,
78
+ );
79
+ }
75
80
  if (record) {
76
- const {
77
- imports = [],
78
- reexports = [],
79
- } = /** @type {PrecompiledStaticModuleInterface} */ (record);
81
+ const { imports = [], reexports = [] } =
82
+ /** @type {PrecompiledStaticModuleInterface} */ (record);
80
83
  const resolvedImports = Object.create(null);
81
84
  for (const importSpecifier of [...imports, ...reexports]) {
82
85
  const resolvedSpecifier = resolve(importSpecifier, moduleSpecifier);
@@ -124,15 +127,53 @@ const sortedModules = (
124
127
  return modules;
125
128
  };
126
129
 
130
+ const implementationPerParser = {
131
+ 'pre-mjs-json': mjsSupport,
132
+ 'pre-cjs-json': cjsSupport,
133
+ };
134
+
135
+ function getRuntime(parser) {
136
+ return implementationPerParser[parser]
137
+ ? implementationPerParser[parser].runtime
138
+ : `/*unknown parser:${parser}*/`;
139
+ }
140
+
141
+ function getBundlerKitForModule(module) {
142
+ const parser = module.parser;
143
+ if (!implementationPerParser[parser]) {
144
+ const warning = `/*unknown parser:${parser}*/`;
145
+ // each item is a function to avoid creating more in-memory copies of the source text etc.
146
+ return {
147
+ getFunctor: () => `(()=>{${warning}})`,
148
+ getCells: `{${warning}}`,
149
+ getFunctorCall: warning,
150
+ };
151
+ }
152
+ const getBundlerKit = implementationPerParser[parser].getBundlerKit;
153
+ return getBundlerKit(module);
154
+ }
155
+
127
156
  /**
128
157
  * @param {ReadFn} read
129
158
  * @param {string} moduleLocation
130
159
  * @param {Object} [options]
131
160
  * @param {ModuleTransforms} [options.moduleTransforms]
161
+ * @param {boolean} [options.dev]
162
+ * @param {Set<string>} [options.tags]
163
+ * @param {Array<string>} [options.searchSuffixes]
164
+ * @param {Object} [options.commonDependencies]
132
165
  * @returns {Promise<string>}
133
166
  */
134
167
  export const makeBundle = async (read, moduleLocation, options) => {
135
- const { moduleTransforms } = options || {};
168
+ const {
169
+ moduleTransforms,
170
+ dev,
171
+ tags: tagsOption,
172
+ searchSuffixes,
173
+ commonDependencies,
174
+ } = options || {};
175
+ const tags = new Set(tagsOption);
176
+
136
177
  const {
137
178
  packageLocation,
138
179
  packageDescriptorText,
@@ -140,9 +181,6 @@ export const makeBundle = async (read, moduleLocation, options) => {
140
181
  moduleSpecifier,
141
182
  } = await search(read, moduleLocation);
142
183
 
143
- /** @type {Set<string>} */
144
- const tags = new Set();
145
-
146
184
  const packageDescriptor = parseLocatedJson(
147
185
  packageDescriptorText,
148
186
  packageDescriptorLocation,
@@ -153,6 +191,7 @@ export const makeBundle = async (read, moduleLocation, options) => {
153
191
  tags,
154
192
  packageDescriptor,
155
193
  moduleSpecifier,
194
+ { dev, commonDependencies },
156
195
  );
157
196
 
158
197
  const {
@@ -167,6 +206,9 @@ export const makeBundle = async (read, moduleLocation, options) => {
167
206
  packageLocation,
168
207
  sources,
169
208
  compartments,
209
+ undefined,
210
+ undefined,
211
+ searchSuffixes,
170
212
  );
171
213
 
172
214
  // Induce importHook to record all the necessary modules to import the given module specifier.
@@ -194,6 +236,7 @@ export const makeBundle = async (read, moduleLocation, options) => {
194
236
  module.index = index;
195
237
  modulesByKey[module.key] = module;
196
238
  }
239
+ const parsersInUse = new Set();
197
240
  for (const module of modules) {
198
241
  module.indexedImports = Object.fromEntries(
199
242
  Object.entries(module.resolvedImports).map(([importSpecifier, key]) => [
@@ -201,142 +244,52 @@ export const makeBundle = async (read, moduleLocation, options) => {
201
244
  modulesByKey[key].index,
202
245
  ]),
203
246
  );
247
+ parsersInUse.add(module.parser);
248
+ module.bundlerKit = getBundlerKitForModule(module);
204
249
  }
205
250
 
206
- // Only support mjs format.
207
- const problems = modules
208
- .filter(module => module.parser !== 'pre-mjs-json')
209
- .map(
210
- ({ moduleSpecifier, compartmentName, parser }) =>
211
- `module ${moduleSpecifier} in compartment ${compartmentName} in language ${parser}`,
212
- );
213
- if (problems.length) {
214
- throw new Error(
215
- `Can only bundle applications that only have ESM (.mjs-type) modules, got ${problems.join(
216
- ', ',
217
- )}`,
218
- );
219
- }
220
-
221
- const exportsCellRecord = exportMap =>
222
- ''.concat(
223
- ...Object.keys(exportMap).map(
224
- exportName => `\
225
- ${exportName}: cell(${q(exportName)}),
226
- `,
227
- ),
228
- );
229
- const importsCellSetter = (exportMap, index) =>
230
- ''.concat(
231
- ...Object.entries(exportMap).map(
232
- ([exportName, [importName]]) => `\
233
- ${importName}: cells[${index}].${exportName}.set,
234
- `,
235
- ),
236
- );
237
-
238
251
  const bundle = `\
239
252
  'use strict';
240
253
  (() => {
241
254
  const functors = [
242
- ${''.concat(
243
- ...modules.map(
244
- ({ record: { __syncModuleProgram__ } }, i) =>
245
- `\
246
- // === functors[${i}] ===
247
- ${__syncModuleProgram__},
248
- `,
249
- ),
250
- )}\
255
+ ${''.concat(...modules.map(m => m.bundlerKit.getFunctor()))}\
251
256
  ]; // functors end
252
257
 
253
- function cell(name, value = undefined) {
258
+ const cell = (name, value = undefined) => {
254
259
  const observers = [];
255
- function set(newValue) {
256
- value = newValue;
257
- for (const observe of observers) {
260
+ return Object.freeze({
261
+ get: Object.freeze(() => {
262
+ return value;
263
+ }),
264
+ set: Object.freeze((newValue) => {
265
+ value = newValue;
266
+ for (const observe of observers) {
267
+ observe(value);
268
+ }
269
+ }),
270
+ observe: Object.freeze((observe) => {
271
+ observers.push(observe);
258
272
  observe(value);
259
- }
260
- }
261
- function get() {
262
- return value;
263
- }
264
- function observe(observe) {
265
- observers.push(observe);
266
- observe(value);
267
- }
268
- return { get, set, observe, enumerable: true };
269
- }
273
+ }),
274
+ enumerable: true,
275
+ });
276
+ };
270
277
 
271
278
  const cells = [
272
- ${''.concat(
273
- ...modules.map(
274
- ({ record: { __fixedExportMap__, __liveExportMap__ } }) => `\
275
- {
276
- ${exportsCellRecord(__fixedExportMap__)}${exportsCellRecord(__liveExportMap__)}\
277
- },
278
- `,
279
- ),
280
- )}\
279
+ ${''.concat(...modules.map(m => m.bundlerKit.getCells()))}\
281
280
  ];
282
281
 
283
- ${''.concat(
284
- ...modules.flatMap(({ index, indexedImports, record: { reexports } }) =>
285
- reexports.map(
286
- importSpecifier => `\
287
- Object.defineProperties(cells[${index}], Object.getOwnPropertyDescriptors(cells[${indexedImports[importSpecifier]}]));
288
- `,
289
- ),
290
- ),
291
- )}\
282
+ ${''.concat(...modules.map(m => m.bundlerKit.getReexportsWiring()))}\
292
283
 
293
- const namespaces = cells.map(cells => Object.create(null, cells));
284
+ const namespaces = cells.map(cells => Object.freeze(Object.create(null, cells)));
294
285
 
295
286
  for (let index = 0; index < namespaces.length; index += 1) {
296
287
  cells[index]['*'] = cell('*', namespaces[index]);
297
288
  }
298
289
 
299
- function observeImports(map, importName, importIndex) {
300
- for (const [name, observers] of map.get(importName)) {
301
- const cell = cells[importIndex][name];
302
- if (cell === undefined) {
303
- throw new ReferenceError(\`Cannot import name \${name}\`);
304
- }
305
- for (const observer of observers) {
306
- cell.observe(observer);
307
- }
308
- }
309
- }
290
+ ${''.concat(...Array.from(parsersInUse).map(parser => getRuntime(parser)))}
310
291
 
311
- ${''.concat(
312
- ...modules.map(
313
- ({
314
- index,
315
- indexedImports,
316
- record: { __liveExportMap__, __fixedExportMap__ },
317
- }) => `\
318
- functors[${index}]({
319
- imports(entries) {
320
- const map = new Map(entries);
321
- ${''.concat(
322
- ...Object.entries(indexedImports).map(
323
- ([importName, importIndex]) => `\
324
- observeImports(map, ${q(importName)}, ${importIndex});
325
- `,
326
- ),
327
- )}\
328
- },
329
- liveVar: {
330
- ${importsCellSetter(__liveExportMap__, index)}\
331
- },
332
- onceVar: {
333
- ${importsCellSetter(__fixedExportMap__, index)}\
334
- },
335
- importMeta: {},
336
- });
337
- `,
338
- ),
339
- )}\
292
+ ${''.concat(...modules.map(m => m.bundlerKit.getFunctorCall()))}\
340
293
 
341
294
  return cells[cells.length - 1]['*'].get();
342
295
  })();
@@ -19,7 +19,7 @@ const moduleLanguages = [
19
19
 
20
20
  /** @type {(a: string, b: string) => number} */
21
21
  // eslint-disable-next-line no-nested-ternary
22
- export const stringCompare = (a, b) => ((a === b ? 0 : a < b ? -1 : 1));
22
+ export const stringCompare = (a, b) => (a === b ? 0 : a < b ? -1 : 1);
23
23
 
24
24
  /**
25
25
  * @param {number} length
@@ -195,14 +195,8 @@ const assertModule = (allegedModule, path, url) => {
195
195
  `${path} must be an object, got ${allegedModule} in ${q(url)}`,
196
196
  );
197
197
 
198
- const {
199
- compartment,
200
- module,
201
- location,
202
- parser,
203
- exit,
204
- deferredError,
205
- } = moduleDescriptor;
198
+ const { compartment, module, location, parser, exit, deferredError } =
199
+ moduleDescriptor;
206
200
  if (compartment !== undefined || module !== undefined) {
207
201
  assertCompartmentModule(moduleDescriptor, path, url);
208
202
  } else if (location !== undefined || parser !== undefined) {
@@ -374,16 +368,8 @@ const assertCompartment = (allegedCompartment, path, url) => {
374
368
  `${path} must be an object, got ${allegedCompartment} in ${q(url)}`,
375
369
  );
376
370
 
377
- const {
378
- location,
379
- name,
380
- label,
381
- parsers,
382
- types,
383
- scopes,
384
- modules,
385
- ...extra
386
- } = compartment;
371
+ const { location, name, label, parsers, types, scopes, modules, ...extra } =
372
+ compartment;
387
373
 
388
374
  assertEmptyObject(
389
375
  extra,
@@ -96,6 +96,13 @@ const makeArchiveImportHookMaker = (
96
96
  const importHook = async moduleSpecifier => {
97
97
  // per-module:
98
98
  const module = modules[moduleSpecifier];
99
+ if (module === undefined) {
100
+ throw new Error(
101
+ `Cannot find module ${q(moduleSpecifier)} in package ${q(
102
+ packageLocation,
103
+ )} in archive ${q(archiveLocation)}`,
104
+ );
105
+ }
99
106
  if (module.deferredError !== undefined) {
100
107
  return postponeErrorToExecute(module.deferredError);
101
108
  }
@@ -132,10 +139,7 @@ const makeArchiveImportHookMaker = (
132
139
 
133
140
  let sourceLocation = `file:///${moduleLocation}`;
134
141
  if (packageName !== undefined) {
135
- const base = packageName
136
- .split('/')
137
- .slice(-1)
138
- .join('/');
142
+ const base = packageName.split('/').slice(-1).join('/');
139
143
  sourceLocation = `.../${join(base, moduleSpecifier)}`;
140
144
  }
141
145
  if (computeSourceLocation !== undefined) {