diff --git a/packages/compartment-mapper/src/bundle-cjs.js b/packages/compartment-mapper/src/bundle-cjs.js index 25dbb64321..22decc7fb7 100644 --- a/packages/compartment-mapper/src/bundle-cjs.js +++ b/packages/compartment-mapper/src/bundle-cjs.js @@ -1,5 +1,10 @@ /* Provides CommonJS support for `bundle.js`. */ -// @ts-nocheck + +/** @import {VirtualModuleSource} from 'ses' */ +/** @import {BundlerSupport} from './bundle.js' */ + +/** @typedef {VirtualModuleSource & {cjsFunctor: string}} CjsModuleSource */ + /** quotes strings */ const q = JSON.stringify; @@ -15,7 +20,8 @@ const exportsCellRecord = exportsList => ); // This function is serialized and references variables from its destination scope. -const runtime = function wrapCjsFunctor(num) { +const runtime = `\ +function wrapCjsFunctor(num) { /* eslint-disable no-undef */ return ({ imports = {} }) => { const moduleCells = cells[num]; @@ -50,8 +56,9 @@ const runtime = function wrapCjsFunctor(num) { moduleCells['*'].set(Object.freeze(starExports)); }; /* eslint-enable no-undef */ -}.toString(); +}`; +/** @type {BundlerSupport} */ export default { runtime, getBundlerKit({ diff --git a/packages/compartment-mapper/src/bundle-mjs.js b/packages/compartment-mapper/src/bundle-mjs.js index 28cd8591a0..83aa7ed380 100644 --- a/packages/compartment-mapper/src/bundle-mjs.js +++ b/packages/compartment-mapper/src/bundle-mjs.js @@ -1,5 +1,8 @@ /* Provides ESM support for `bundle.js`. */ +/** @import {PrecompiledModuleSource} from 'ses' */ +/** @import {BundlerSupport} from './bundle.js' */ + /** quotes strings */ const q = JSON.stringify; @@ -47,6 +50,7 @@ function observeImports(map, importName, importIndex) { } `; +/** @type {BundlerSupport} */ export default { runtime, getBundlerKit({ diff --git a/packages/compartment-mapper/src/bundle.js b/packages/compartment-mapper/src/bundle.js index 205dc85314..bbee7f3360 100644 --- a/packages/compartment-mapper/src/bundle.js +++ b/packages/compartment-mapper/src/bundle.js @@ -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' */ @@ -11,6 +13,56 @@ /** @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 may + * be a statement that calls 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 + * that may include statements that bind the cells reexported by this module. + */ + +/** + * @template {unknown} SpecificModuleSource + * @typedef {object} BundleModule + * @property {string} key + * @property {string} compartmentName + * @property {string} moduleSpecifier + * @property {string} parser + * @property {StaticModuleType & SpecificModuleSource} record + * @property {Record} resolvedImports + * @property {Record} indexedImports + * @property {Uint8Array} bytes + * @property {number} index + * @property {BundlerKit} bundlerKit + */ + +/** + * @template {unknown} SpecificModuleSource + * @callback GetBundlerKit + * @param {BundleModule} module + * @returns {BundlerKit} + */ + +/** + * @template {unknown} SpecificModuleSource + * @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'; @@ -40,8 +92,11 @@ const sortedModules = ( entryCompartmentName, entryModuleSpecifier, ) => { + /** @type {BundleModule[]} */ const modules = []; + /** @type {Map} aliaes */ const aliases = new Map(); + /** @type {Set} seen */ const seen = new Set(); /** @@ -58,6 +113,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}`, @@ -86,6 +143,10 @@ const sortedModules = ( record, resolvedImports, bytes, + // @ts-expect-error + index: undefined, + // @ts-expect-error + bundlerKit: null, }); return key; @@ -119,32 +180,37 @@ const sortedModules = ( return { modules, aliases }; }; -const implementationPerParser = { +/** @type {Record>} */ +const bundlerSupportForLanguage = { 'pre-mjs-json': mjsSupport, 'pre-cjs-json': cjsSupport, json: jsonSupport, }; -function getRuntime(parser) { - return implementationPerParser[parser] - ? implementationPerParser[parser].runtime - : `/*unknown parser:${parser}*/`; -} - -function getBundlerKitForModule(module) { - const parser = module.parser; - if (!implementationPerParser[parser]) { - const warning = `/*unknown parser:${parser}*/`; +/** @param {string} language */ +const getRuntime = language => + bundlerSupportForLanguage[language] + ? bundlerSupportForLanguage[language].runtime + : `/*unknown language:${language}*/`; + +/** @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, + getFunctor: () => `(()=>{${warning}}),`, + getCells: () => `{${warning}},`, + getFunctorCall: () => warning, + getReexportsWiring: () => '', }; } - const { getBundlerKit } = implementationPerParser[parser]; + const { getBundlerKit } = bundlerSupportForLanguage[language]; return getBundlerKit(module); -} +}; /** * @param {ReadFn | ReadPowers | MaybeReadPowers} readPowers @@ -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 = []; @@ -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]: { @@ -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; diff --git a/packages/compartment-mapper/src/types.js b/packages/compartment-mapper/src/types.js index 64df74e2fe..ff0ca25b9d 100644 --- a/packages/compartment-mapper/src/types.js +++ b/packages/compartment-mapper/src/types.js @@ -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' */