From 889d40328718ba3b33d121639336dd569f9d210d Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Wed, 17 Jun 2026 14:00:55 +0800 Subject: [PATCH 1/2] feat(npm): ESM module-worker template for @pineforge/codegen-pyodide --- scripts/worker-template.mjs | 49 +++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 scripts/worker-template.mjs diff --git a/scripts/worker-template.mjs b/scripts/worker-template.mjs new file mode 100644 index 0000000..87de323 --- /dev/null +++ b/scripts/worker-template.mjs @@ -0,0 +1,49 @@ +// GENERATED by scripts/build-npm-package.mjs from gate/glue.py — DO NOT EDIT. +// Canonical client-side Pyodide transpile worker, shipped by +// @pineforge/codegen-pyodide. Consumers serve it from /pyodide/ and load it via: +// new Worker("/pyodide/transpile.worker.mjs", { type: "module" }) +// A real ESM module worker (string-URL load bypasses the bundler), so Pyodide +// 314's module-worker requirement is satisfied. Mirrors the old in-app +// transpile.worker.ts; protocol is identical (see app protocol.ts). +import { loadPyodide } from "/pyodide/pyodide.mjs"; + +const GLUE = `__GLUE__`; + +const post = (m) => self.postMessage(m); +let transpileJson = null; + +async function init() { + try { + const pyodide = await loadPyodide({ indexURL: "/pyodide/" }); + const manifestRes = await fetch("/pyodide/manifest.json", { cache: "no-cache" }); + if (!manifestRes.ok) throw new Error(`fetch /pyodide/manifest.json: ${manifestRes.status}`); + const manifest = await manifestRes.json(); + const archiveRes = await fetch(`/pyodide/${manifest.archive}`); + if (!archiveRes.ok) throw new Error(`fetch /pyodide/${manifest.archive}: ${archiveRes.status}`); + const buf = await archiveRes.arrayBuffer(); + pyodide.unpackArchive(buf, "gztar", { extractDir: "/codegen" }); + pyodide.runPython(GLUE); + const fn = pyodide.globals.get("transpile_json"); + transpileJson = (source) => fn(source); + post({ type: "ready", codegenVersion: manifest.codegenVersion }); + } catch (err) { + post({ type: "init-error", error: err instanceof Error ? err.message : String(err) }); + } +} + +const initPromise = init(); + +self.onmessage = async (ev) => { + const { id, source } = ev.data; + await initPromise; + if (!transpileJson) { + post({ type: "crash", id, error: "pyodide failed to initialize" }); + return; + } + try { + const result = JSON.parse(transpileJson(source)); + post({ type: "result", id, result }); + } catch (err) { + post({ type: "crash", id, error: err instanceof Error ? err.message : String(err) }); + } +}; From 8b1982ac7baafa1b91e66f8f5f962deb232218b3 Mon Sep 17 00:00:00 2001 From: luisleo526 Date: Wed, 17 Jun 2026 14:02:31 +0800 Subject: [PATCH 2/2] feat(npm): generate transpile.worker.mjs + ship glue.py; export workerPath + glue --- .gitignore | 2 ++ npm/index.mjs | 10 ++++++++++ npm/package.json | 4 +++- scripts/build-npm-package.mjs | 8 +++++++- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 70c4595..1e527de 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ temp/ /npm/pineforge_codegen/ /npm/tables.json /npm/release.json +/npm/transpile.worker.mjs +/npm/glue.py diff --git a/npm/index.mjs b/npm/index.mjs index f085cd1..bbbef0f 100644 --- a/npm/index.mjs +++ b/npm/index.mjs @@ -1,6 +1,7 @@ // Entry for @pineforge/codegen-pyodide. Resolves the packaged payload paths and // metadata so consumers (the app's build + Node oracle/grammar tooling) read // everything from this one dependency — no git submodule. +import { readFileSync } from "node:fs"; import { createRequire } from "node:module"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -18,3 +19,12 @@ export const archivePath = join(HERE, `pineforge_codegen-${release.codegen}.tar. // so `import pineforge_codegen` resolves (oracle tests, grammar gen). export const sourceRoot = HERE; export const codegenSourceDir = join(HERE, "pineforge_codegen"); + +// Absolute path to the shipped ESM module worker — consumers copy it into their +// served /pyodide/ dir and load it via `new Worker(url, { type: "module" })`. +export const workerPath = join(HERE, "transpile.worker.mjs"); + +// Canonical Pyodide glue source (single source: gate/glue.py) — runPython'd to +// define transpile_json. The worker embeds this verbatim; exported here too so +// Node consumers (parity tests) run the exact same glue. +export const glue = readFileSync(join(HERE, "glue.py"), "utf8"); diff --git a/npm/package.json b/npm/package.json index 8f9c7f1..b5405ba 100644 --- a/npm/package.json +++ b/npm/package.json @@ -14,7 +14,9 @@ "pineforge_codegen-*.tar.gz", "pineforge_codegen/**/*", "tables.json", - "release.json" + "release.json", + "transpile.worker.mjs", + "glue.py" ], "engines": { "node": ">=20" diff --git a/scripts/build-npm-package.mjs b/scripts/build-npm-package.mjs index ec29359..f2e6d12 100644 --- a/scripts/build-npm-package.mjs +++ b/scripts/build-npm-package.mjs @@ -31,7 +31,7 @@ const release = JSON.parse(readFileSync(RELEASE, "utf8")); if (release.codegen !== VERSION) fail(`release.json codegen ${release.codegen} != VERSION ${VERSION}`); // 1. Clean payload (keep tracked manifest + index). -for (const p of ["pineforge_codegen", "tables.json", "release.json"]) { +for (const p of ["pineforge_codegen", "tables.json", "release.json", "glue.py", "transpile.worker.mjs"]) { rmSync(join(NPM, p), { recursive: true, force: true }); } const versionedArchive = `pineforge_codegen-${VERSION}.tar.gz`; @@ -55,6 +55,12 @@ const tablesJson = execFileSync("python3", [join(ROOT, "scripts", "dump-tables.p }); writeFileSync(join(NPM, "tables.json"), tablesJson); +// --- canonical glue + generated module worker (single source: gate/glue.py) --- +const glueSrc = readFileSync(join(ROOT, "gate", "glue.py"), "utf8"); +writeFileSync(join(NPM, "glue.py"), glueSrc); +const workerTemplate = readFileSync(join(ROOT, "scripts", "worker-template.mjs"), "utf8"); +writeFileSync(join(NPM, "transpile.worker.mjs"), workerTemplate.replace("__GLUE__", glueSrc)); + // 5. Sync npm/package.json version from VERSION. const pkgPath = join(NPM, "package.json"); const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));