Transform Module
Transform-module is a transformation from a JS module source to a corresponding
JS program that, when evaluated, produces a function that can execute a module
and orchestrate its imports and exports.
The static module record includes the static analysis of the module: what it
imports and exports and how it refers to imported modules.
The workflow for executing a module, as conducted by a Compartment,
is to analyze the module source, link with other modules based on the static
record's metadata, evaluate the functor, call the functor with linkage.
import { makeModuleAnalyzer } from './src/main.js';
import * as babel from '@agoric/babel-standalone';
const analyze = makeModuleAnalyzer(babel.default);
const moduleStaticRecord = analyze(moduleSource);
const moduleFunctor = evaluateModuleFunctor(moduleStaticRecord.functorSource, );
moduleFunctor({
imports(importedVariableUpdaters, exportAlls) { },
liveVar: exportedVariableUpdaters,
onceVar: exportedConstantEmitters,
});
So, for example, the following module uses import and export quite thoroughly.
import foo from 'import-default-export-from-me.js';
import * as bar from 'import-all-from-me.js';
import { fizz, buzz } from 'import-named-exports-from-me.js';
import { color as colour } from 'import-named-export-and-rename.js';
export let quuux = null;
export { qux } from 'import-and-reexport-name-from-me.js';
export * from 'import-and-export-all.js';
export default 42;
export const quux = 'Hello, World!';
quuux = 'Hello, World!';
From this, the analyzer produces a static module record that shows what modules
the module needs to be linked to and how to link their imports and exports.
{
"exportAlls": [ "import-and-export-all.js" ],
"imports": {
"import-default-export-from-me.js": [ "default" ],
"import-all-from-me.js": [ "*" ],
"import-named-exports-from-me.js": [ "fizz", "buzz" ],
"import-named-export-and-rename.js": [ "color" ],
"import-and-reexport-name-from-me.js": [ "qux" ],
"import-and-export-all.js": []
},
"liveExportMap": {
"qux": [ "qux", false ],
"quuux": [ "quuux", true ],
},
"fixedExportMap": {
"default": [ "default" ],
"quux": [ "quux" ],
}
}
The functor source, the module transformed into a program, has the following
shape.
The names are additionally obscured with Unicode zero-width-joiners to avoid
collisions with sensibly constructed modules, and the transformation preserves
line numbers.
(({
imports: $h_imports,
liveVar: $h_live,
onceVar: $h_once,
}) => {
let foo, bar, fizz, buzz, colour;
$h_imports(
new Map([
["import-default-export-from-me.js", new Map([
["default", [$h_a => (foo = $h_a)]],
])],
["import-all-from-me.js", new Map([
["*", [$h_a => (bar = $h_a)]]
])],
["import-named-exports-from-me.js", new Map([
["fizz", [$h_a => (fizz = $h_a)]],
["buzz", [$h_a => (buzz = $h_a)]],
])],
["import-named-export-and-rename.js", new Map([
["color", [$h_a => (colour = $h_a)]],
])],
["import-and-reexport-name-from-me.js", new Map([
["qux", [$h_live["qux"]]]
])],
["import-and-export-all.js", new Map([])]
]),
["import-and-export-all.js"]
);
let $c_quuux = null;
$h_live.quuux($c_quuux);
const { default: $c_default } = { default: 42 };
$h_once.default($c_default);
const quux = 'Hello, World!';
$h_once.quux(quux);
quuux = 'Sorry for binding late!';
})
For the final live binding to quuux
, we depend on the evaluator to put a
Proxy on the scope chain to intercept the assignment and effect an update
to all modules that import the value.
type Analyzer = ({string: ModuleSource}) => StaticModuleRecord
type ModuleSource = string
type StaticModuleRecord = {
exportAlls: ExportAlls,
imports: Imports,
liveExportMap: LiveExportMap,
fixedExportMap: FixedExportMap,
functorSource: string,
};
type ExportAlls = Array<RelativeModuleSpecifier>
type Imports = Object<RelativeModuleSpecifier, Array<ImportNames>)
type ImportName = string
type LiveExportMap = Object<ExportName, [ExportName, SetProxyTrap]>
type FixedExportMap = Object<ExportName, [ExportName]>
type ExportName = string
type SetProxyTrap = bool
type ModuleFunctor = (UpdaterArgument):void
type UpdaterArgument = {
imports(Updaters, ExportAlls) => void,
liveVar(Exporters) => void,
onceVar(Exporters) => void,
};
type UpdateFunction = (value:any) => void
type Updaters = Map<RelativeModuleSpecifier, ModuleUpdaters>>
type ModuleUpdaters = Map<ImportName, Array<UpdateFunction>>
type ImportName = string
type RelativeModuleSpecifier = string
type Exporters = Object<ExportName, UpdateFunction>