// One compile, in a worker (a Web Worker in pages, a worker_thread in Node): asc calls process.exit(1) // when it crashes (a stack overflow on deep generics does), which here ends only this worker, and a // compile that runs long is terminated by index.js. Gets { code, urls? }, posts { binary } or { error }. import { ABI, ENTRY } from "./abi.js"; const FLAGS = ["--runtime", "stub", "--use", "abort=", "--optimizeLevel", "1", "--noAssert", "--maximumMemory", "1024"]; // A page's worker sees no import map: the page resolves asc and its dependencies (index.js `urls`) and // asc's own bare imports are rewritten to them, loaded as blob modules. Node resolves them itself. async function loadAsc(urls) { if (!urls) return import("assemblyscript/asc"); const rewrite = async (url, to) => { const source = (await (await fetch(url)).text()).replace(/from"(assemblyscript|binaryen|long)"/g, (all, name) => (to[name] ? `from"${to[name]}"` : all)); return URL.createObjectURL(new Blob([source], { type: "text/javascript" })); }; const deps = { binaryen: urls.binaryen, long: urls.long }; return import(await rewrite(urls["assemblyscript/asc"], { ...deps, assemblyscript: await rewrite(urls.assemblyscript, deps) })); } async function compile(code, urls) { // the prelude's names without a line of their own, so the compiler's line numbers are the agent's const files = { "abi.ts": ABI, "module.ts": ENTRY, "code.ts": (code.includes('"./abi"') ? "" : 'import { Json, Kind, Run, print } from "./abi"; ') + code }; const asc = await loadAsc(urls); let binary; let log = ""; const stream = { write: (chunk) => ((log += typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk)), true) }; const { error } = await asc.main(["module.ts", "--outFile", "module.wasm", ...FLAGS], { stdout: stream, stderr: stream, // only these files and asc's own standard library: an import of anything else is a compile error readFile: (name) => (Object.hasOwn(files, name.replace(/^\.\//, "")) ? files[name.replace(/^\.\//, "")] : null), writeFile: (name, contents) => { if (name.endsWith("module.wasm") && typeof contents !== "string") binary = new Uint8Array(contents); }, listFiles: () => [], }); if (error || !binary) return { error: (log || error?.message || "no output").trim() }; // a module that declares imports compiles; refuse it here, with its imports named (the host would refuse it too) const imports = WebAssembly.Module.imports(await WebAssembly.compile(binary)); if (imports.length) return { error: `imports are not allowed: ${imports.map((i) => `${i.module}.${i.name}`).join(", ")}` }; return { binary }; } const port = typeof self === "undefined" ? (await import("node:worker_threads")).parentPort : self; const reply = async ({ code, urls }) => port.postMessage(await compile(code, urls).catch((error) => ({ error: String(error?.message ?? error) }))); if (typeof self === "undefined") port.once("message", reply); else self.onmessage = ({ data }) => reply(data);