Skip to content

Commit

Permalink
docs(compartment-mapper): Types and docs for makeBundle internals
Browse files Browse the repository at this point in the history
  • Loading branch information
kriskowal committed Jul 17, 2024
1 parent 5a57a61 commit a7c3037
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 24 deletions.
5 changes: 4 additions & 1 deletion packages/compartment-mapper/src/bundle-cjs.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* Provides CommonJS support for `bundle.js`. */
// @ts-nocheck

/** @import {BundlerSupport} from './bundle.js' */

/** quotes strings */
const q = JSON.stringify;

Expand Down Expand Up @@ -52,6 +54,7 @@ const runtime = function wrapCjsFunctor(num) {
/* eslint-enable no-undef */
}.toString();

/** @type {BundlerSupport} */
export default {
runtime,
getBundlerKit({
Expand Down
3 changes: 3 additions & 0 deletions packages/compartment-mapper/src/bundle-mjs.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* Provides ESM support for `bundle.js`. */

/** @import {BundlerSupport} from './bundle.js' */

/** quotes strings */
const q = JSON.stringify;

Expand Down Expand Up @@ -47,6 +49,7 @@ function observeImports(map, importName, importIndex) {
}
`;

/** @type {BundlerSupport} */
export default {
runtime,
getBundlerKit({
Expand Down
103 changes: 83 additions & 20 deletions packages/compartment-mapper/src/bundle.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// @ts-check
/* eslint no-shadow: 0 */

/** @import {StaticModuleType} from 'ses' */

/** @import {ArchiveOptions} from './types.js' */
/** @import {CompartmentDescriptor} from './types.js' */
/** @import {CompartmentSources} from './types.js' */
Expand All @@ -11,6 +13,42 @@
/** @import {Sources} from './types.js' */
/** @import {WriteFn} from './types.js' */

/**
* @typedef {object} BundlerKit
* @property {() => string} getFunctor Produces a JavaScript string consisting of
* a function expression followed by a comma delimiter that will be evaluated in
* a lexical scope with no free variables except the globals.
* In the generated bundle runtime, the function will receive an environment
* record: a record mapping every name of the corresponding module's internal
* namespace to a "cell" it can use to get, set, or observe the linked
* variable.
* @property {() => string} getCells Produces a JavaScript string consisting of
* a JavaScript object and a trailing comma.
* The string is evaluated in a lexical context with a `cell` maker, the `cells`
* array of every module's internal environment record.
* @property {() => string} getFunctorCall Produces a JavaScript string with a
* single statement that will call this module's functor with the calling
* convention appropriate for its language, injecting whatever cells it needs
* to link to other module namespaces.
* @property {() => string} getReexportsWiring Produces a JavaScript string
* consisting of statements that bind the cells reexported by this module.
*/

/**
* @callback GetBundlerKit
* @param {object} args
* @param {number} args.index
* @param {Record<string, number>} args.indexedImports
* @param {Uint8Array} args.bytes
* @returns {BundlerKit}
*/

/**
* @typedef {object} BundlerSupport
* @property {string} runtime
* @property {GetBundlerKit} getBundlerKit
*/

import { resolve } from './node-module-specifier.js';
import { compartmentMapForNodeModules } from './node-modules.js';
import { search } from './search.js';
Expand Down Expand Up @@ -40,8 +78,11 @@ const sortedModules = (
entryCompartmentName,
entryModuleSpecifier,
) => {
/** @type {BundleModule[]} */
const modules = [];
/** @type {Map<string, string>} aliaes */
const aliases = new Map();
/** @type {Set<string>} seen */
const seen = new Set();

/**
Expand All @@ -58,6 +99,8 @@ const sortedModules = (
const source = compartmentSources[compartmentName][moduleSpecifier];
if (source !== undefined) {
const { record, parser, deferredError, bytes } = source;
assert(parser !== undefined);
assert(bytes !== undefined);
if (deferredError) {
throw Error(
`Cannot bundle: encountered deferredError ${deferredError}`,
Expand Down Expand Up @@ -86,6 +129,10 @@ const sortedModules = (
record,
resolvedImports,
bytes,
// @ts-expect-error
index: undefined,
// @ts-expect-error
bundlerKit: null,
});

return key;
Expand Down Expand Up @@ -119,32 +166,51 @@ const sortedModules = (
return { modules, aliases };
};

const implementationPerParser = {
/** @type {Record<string, BundlerSupport>} */
const bundlerSupportForLanguage = {
'pre-mjs-json': mjsSupport,
'pre-cjs-json': cjsSupport,
json: jsonSupport,
};

function getRuntime(parser) {
return implementationPerParser[parser]
? implementationPerParser[parser].runtime
: `/*unknown parser:${parser}*/`;
}
/** @param {string} language */
const getRuntime = language =>
bundlerSupportForLanguage[language]
? bundlerSupportForLanguage[language].runtime
: `/*unknown language:${language}*/`;

function getBundlerKitForModule(module) {
const parser = module.parser;
if (!implementationPerParser[parser]) {
const warning = `/*unknown parser:${parser}*/`;
/**
* @typedef {object} BundleModule
* @property {string} key
* @property {string} compartmentName
* @property {string} moduleSpecifier
* @property {string} parser
* @property {StaticModuleType} record
* @property {Record<string, string>} resolvedImports
* @property {Record<string, number>} indexedImports
* @property {Uint8Array} bytes
* @property {number} index
* @property {BundlerKit} bundlerKit
*/

/** @param {BundleModule} module */
const getBundlerKitForModule = module => {
const language = module.parser;
assert(language !== undefined);
if (bundlerSupportForLanguage[language] === undefined) {
const warning = `/*unknown language:${language}*/`;
// each item is a function to avoid creating more in-memory copies of the source text etc.
/** @type {BundlerKit} */
return {
getFunctor: () => `(()=>{${warning}})`,
getCells: `{${warning}}`,
getFunctorCall: warning,
getCells: () => `{${warning}}`,
getFunctorCall: () => warning,
getReexportsWiring: () => '',
};
}
const { getBundlerKit } = implementationPerParser[parser];
const { getBundlerKit } = bundlerSupportForLanguage[language];
return getBundlerKit(module);
}
};

/**
* @param {ReadFn | ReadPowers | MaybeReadPowers} readPowers
Expand Down Expand Up @@ -269,10 +335,7 @@ export const makeBundle = async (readPowers, moduleLocation, options) => {

const bundle = `\
'use strict';
(() => {
const functors = [
${''.concat(...modules.map(m => m.bundlerKit.getFunctor()))}\
]; // functors end
(functors => {
const cell = (name, value = undefined) => {
const observers = [];
Expand Down Expand Up @@ -300,7 +363,7 @@ ${''.concat(...modules.map(m => m.bundlerKit.getCells()))}\
${''.concat(...modules.map(m => m.bundlerKit.getReexportsWiring()))}\
const namespaces = cells.map(cells => Object.freeze(Object.create(null, {
const namespaces = cells.map(cells => Object.freeze(Object.create(null, {
...cells,
// Make this appear like an ESM module namespace object.
[Symbol.toStringTag]: {
Expand All @@ -320,7 +383,7 @@ ${''.concat(...Array.from(parsersInUse).map(parser => getRuntime(parser)))}
${''.concat(...modules.map(m => m.bundlerKit.getFunctorCall()))}\
return cells[cells.length - 1]['*'].get();
})();
})([${''.concat(...modules.map(m => m.bundlerKit.getFunctor()))}]);
`;

return bundle;
Expand Down
3 changes: 0 additions & 3 deletions packages/compartment-mapper/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@

export {};

/** @import {FinalStaticModuleType} from 'ses' */
/** @import {ThirdPartyStaticModuleInterface} from 'ses' */
/** @import {ImportHook} from 'ses' */
/** @import {StaticModuleType} from 'ses' */
/** @import {Transform} from 'ses' */

Expand Down

0 comments on commit a7c3037

Please sign in to comment.