// The browser entry (src/browser.js, the "default" export condition) must agree with the Node // entry on every input: it shares src/core.js and swaps only the host hooks (fetch + // instantiateStreaming, and TextDecoder-based isUtf8/isAscii for node:buffer's). Node's fetch // can't read file: URLs, so this stubs fetch to serve build/*.wasm as application/wasm. Also // proves transformSync refuses to run before transform has loaded the engine, then works after. // Usage: node test/browser-entry.mjs import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { readFileSync } from "node:fs"; globalThis.fetch = async (url) => { assert.equal(new URL(url).protocol, "file:", `browser entry fetched ${url}, expected a file: URL next to src/`); return new Response(await readFile(url), { headers: { "content-type": "application/wasm" } }); }; const web = await import("../src/browser.js"); const node = await import("../src/index.js"); assert.throws(() => web.transformSync("let x: number = 1;"), /await transform\(\) once/); await web.transform(""); await web.transform("", { mode: "downlevel" }); const run = (api, src, mode) => { const errors = []; try { const out = typeof src === "string" ? api.transformSync(src, { mode, onError: (e) => errors.push(e.start) }) : [...api.transformSync(src, { mode, onError: (e) => errors.push(e.start) })].join(""); return { out, errors: errors.join() }; } catch (e) { return { threw: e.message }; } }; const encoder = new TextEncoder(); const sources = ["corpus/snippets.jsonl", "corpus/adversarial.jsonl", "corpus/regressions.jsonl", "corpus/downlevel.jsonl"] .flatMap((f) => readFileSync(new URL(`../${f}`, import.meta.url), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l))) .filter((s) => typeof s === "string"); sources.push("const s = '日本語 漢字 🎉🚀 café';", "const u = '😀😀';"); let cases = 0; let fail = 0; for (const mode of ["erase", "downlevel"]) { for (const src of sources) { const bytes = encoder.encode(src); // Whole string, whole bytes, and bytes split at every offset (so multi-byte splits hit // the browser entry's isUtf8/isAscii), plus a leading BOM and an invalid byte. const inputs = [src, [bytes], [Uint8Array.of(0xef, 0xbb, 0xbf, ...bytes)], [Uint8Array.of(0xff, ...bytes)]]; if (bytes.length !== src.length) for (let i = 1; i < bytes.length; i++) inputs.push([bytes.subarray(0, i), bytes.subarray(i)]); for (const input of inputs) { cases++; const a = run(web, input, mode); const b = run(node, input, mode); if (a.threw !== b.threw || a.out !== b.out || a.errors !== b.errors) { if (fail++ < 5) console.error(`mismatch (${mode}):`, JSON.stringify(src).slice(0, 80), { web: a, node: b }); } } } } // A fetch-style Response body, the way a service worker hands it over. const body = new Response("let x: number = 1;\nlet s = '漢字';").body; let streamed = ""; for await (const piece of web.transform(body)) streamed += piece; assert.equal(streamed, node.transformSync("let x: number = 1;\nlet s = '漢字';")); console.log(`browser-entry: ${cases} input/mode pairs, ${fail} fail`); if (fail) process.exit(1);