Skip to content

Instantly share code, notes, and snippets.

@nicolo-ribaudo
Last active September 27, 2022 20:20
Show Gist options
  • Save nicolo-ribaudo/81f18db096659ac8447ca94f50f2c37a to your computer and use it in GitHub Desktop.
Save nicolo-ribaudo/81f18db096659ac8447ca94f50f2c37a to your computer and use it in GitHub Desktop.

A simple module bundler that uses Layer 0 of compartments, module blocks and (if you need dynamic import support) import reflection.

API:

function bundle(entryPointURL: string, allowDynamicImport: boolean): string;

where:

  • entryPointURL is the absolute URL of the entry point
  • allowDynamicImport can be set to true to allow the bundled code to use dynamic import to load modules that are not part of the bundle
  • the result is the source code of the bundle, as a string

The resulting source code can then be run directly.

export default async function bundle(entryPointURL, allowDynamicImport) {
const loader = new BundleLoader();
await loader.loadEntryPoint(entryPointURL);
let bundleSource = `
const modules = new Map();
function importHook(specifier) {
const url = new URL(specifier, this.context.url);
if (modules.has(url)) return modules.get(url);
${
allowDynamicImport
? `return import(url, { module: true });`
: `throw new Error(url + " has not been bundled.");`
}
}
function importMetaHook(meta) {
meta.url = this.context.url;
}
`;
for (const [url, sourceText] of loader.iterateSources()) {
const urlStr = JSON.stringify(url);
bundleSource += `
modules.set(
${urlStr},
new Module(module {${sourceText}}.source, {
importHook,
importMetaHook,
context: { url: ${urlStr} },
})
);
`;
}
bundleSource += `
// Run
await import(modules.get(${JSON.stringify(entryPointURL)}));
`;
return bundleSource;
}
class BundleLoader {
#loadedModules = new Map();
#sources = new Map();
iterateSources() {
return this.#sources.entries();
}
async loadEntryPoint(url) {
const bootLoader = new Module(BundleLoader.#bootSource, {
importHook: BundleLoader.#bootImportHook,
context: { entryPointURL, loader: this },
});
await import(bootLoader);
}
static #doNotEvaluate = module {
throw new Error("bundler/do-not-evaluate");
};
static #bootSource = module {
import "do-not-evaluate";
import "entrypoint";
}.source;
static #bootImportHook = function (specifier) {
if (specifier === "do-not-evaluate") {
return BundleLoader.#doNotEvaluate;
}
if (specifier === "entrypoint") {
return this.context.loader.#loadModuleCached(this.context.entryPointURL);
}
throw new Error("Assert: unreachable");
};
#loadModuleCached(url) {
if (loadedModules.has(url)) {
return loadedModules.get(url);
}
const res = this.#loadModule(url);
this.#loadedModules.set(url, res);
return res;
}
async #loadModule(url) {
const response = await fetch(url);
const text = await response.text();
const module = new Module(new ModuleSource(text), {
importHook: this.#importHook,
context: { url, loader: this },
});
this.#sources.set(url, text);
return module;
}
static #importHook = function (specifier) {
const url = new URL(specifier, this.context.url);
return this.context.loader.#loadModuleCached(url);
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment