/** * build.cjs — esbuild script for the PDF viewer TypeScript bundle. * * Produces two output files in ../assets/viewer/: * viewer.bundle.js — main viewer (IIFE, no PDF.js included) * render-worker.bundle.js — render worker (pdf.min.js prepended + IIFE) * * Usage: * node build.cjs — one-shot production build (minified) * node build.cjs --watch — watch mode for development (unminified) */ const esbuild = require("esbuild"); const fs = require("fs"); const path = require("path"); const isWatch = process.argv.includes("--watch"); const minify = !isWatch; const ROOT = __dirname; const PDFJS_WORKER_MIN = path.join(ROOT, "../viewer/pdfjs/pdf.worker.min.js"); const PDFJS_MIN = path.join(ROOT, "../viewer/pdfjs/pdf.min.js"); const OUT_DIR = path.join(ROOT, "../viewer"); // Preamble prepended to the render-worker bundle. // // Sets `globalThis.window = globalThis` before pdf.worker.min.js runs so that: // 1. pdf.worker.min.js does NOT auto-call WorkerMessageHandler.initializeFromPort(self) // (which would hijack our render-worker's own onmessage handler). // 2. pdf.worker.min.js DOES set globalThis.pdfjsWorker.WorkerMessageHandler as usual. // 3. pdf.min.js's _mainThreadWorkerMessageHandler getter then finds the handler via // globalThis.pdfjsWorker and uses it inline — no document.createElement needed. const RENDER_WORKER_PREAMBLE = Buffer.from("globalThis.window=globalThis;\n"); // ── Render-worker plugin: prepend preamble + pdf.worker.min.js + pdf.min.js ─── // // Bundle order matters: // 1. Preamble — sets globalThis.window = globalThis // 2. pdf.worker.min.js — sets globalThis.pdfjsWorker.WorkerMessageHandler // (skips auto-setup because window is now defined) // 3. pdf.min.js — sets globalThis.pdfjsLib; fake-worker path reads pdfjsWorker // 4. Compiled TS — our render-worker code function prependPdfjsPlugin() { const pdfjsWorkerMin = fs.readFileSync(PDFJS_WORKER_MIN); const pdfjsMin = fs.readFileSync(PDFJS_MIN); const NL = Buffer.from("\n"); return { name: "prepend-pdfjs", setup(build) { build.onEnd(result => { if (result.errors.length > 0 || !result.outputFiles) return; const compiled = Buffer.from(result.outputFiles[0].contents); const combined = Buffer.concat([ RENDER_WORKER_PREAMBLE, pdfjsWorkerMin, NL, pdfjsMin, NL, compiled, ]); fs.writeFileSync(path.join(OUT_DIR, "render-worker.bundle.js"), combined); if (!isWatch) console.log("[viewer-src] render-worker.bundle.js written"); }); }, }; } async function main() { const sharedOptions = { bundle: true, format: "iife", target: "es2020", minify }; if (isWatch) { const viewerCtx = await esbuild.context({ ...sharedOptions, entryPoints: ["src/viewer.ts"], outfile: path.join(OUT_DIR, "viewer.bundle.js"), }); const workerCtx = await esbuild.context({ ...sharedOptions, entryPoints: ["src/render-worker.ts"], write: false, plugins: [prependPdfjsPlugin()], }); await viewerCtx.watch(); await workerCtx.watch(); console.log("[viewer-src] watching for changes…"); // Keep the process alive return; } // One-shot build await esbuild.build({ ...sharedOptions, entryPoints: ["src/viewer.ts"], outfile: path.join(OUT_DIR, "viewer.bundle.js"), }); console.log("[viewer-src] viewer.bundle.js written"); // Render worker: build to memory then prepend preamble + worker + pdf.min.js const workerResult = await esbuild.build({ ...sharedOptions, entryPoints: ["src/render-worker.ts"], write: false, }); const pdfjsWorkerMin = fs.readFileSync(PDFJS_WORKER_MIN); const pdfjsMin = fs.readFileSync(PDFJS_MIN); const NL = Buffer.from("\n"); const compiled = Buffer.from(workerResult.outputFiles[0].contents); fs.writeFileSync( path.join(OUT_DIR, "render-worker.bundle.js"), Buffer.concat([RENDER_WORKER_PREAMBLE, pdfjsWorkerMin, NL, pdfjsMin, NL, compiled]), ); console.log("[viewer-src] render-worker.bundle.js written"); } main().catch(e => { console.error(e); process.exit(1); });