Adds parakeet.js 1.4.4 (MIT) as a devDependency bundled into the worker only; a build plugin keeps its own onnxruntime-web copy out of the bundle. Includes an opt-in real-model test (PARAKEET_REAL=1). Co-Authored-By: Claude Opus 5.5 <[email protected]>
111 lines
4.4 KiB
TypeScript
111 lines
4.4 KiB
TypeScript
/**
|
|
* Production build (`bun run build`) — Decisions.md D-007.
|
|
*
|
|
* Emits a self-contained `dist/`:
|
|
* 1. `bun build src/index.html` → hashed app-shell assets
|
|
* 2. inference worker bundle at the stable /inference.worker.js URL
|
|
* 3. manifest + icons + onnxruntime .wasm/.mjs (dist/ort/, offline inference)
|
|
* 4. sw.js with the precache manifest + a content-hash version injected
|
|
*
|
|
* `bun run start` (NODE_ENV=production bun src/index.ts) then serves dist/
|
|
* verbatim, so the URLs the service worker precached are exactly the URLs
|
|
* the server responds to.
|
|
*/
|
|
|
|
import { cp, readdir, rm } from "node:fs/promises";
|
|
import { join, relative } from "node:path";
|
|
import { injectPrecacheManifest, precachePathsFromOutputs } from "../src/pwa/precache.ts";
|
|
import { ensureFonts, FONTS_DIR } from "./fonts.ts";
|
|
import { ensureIcons, ICON_SIZES, ICONS_DIR } from "./generate-icons.ts";
|
|
import { ortAliasPlugin } from "./ortAlias.ts";
|
|
|
|
const ROOT = join(import.meta.dir, "..");
|
|
const DIST = join(ROOT, "dist");
|
|
|
|
await rm(DIST, { recursive: true, force: true });
|
|
|
|
// Icons before the shell build — index.html references src/pwa/icons/ (the
|
|
// HTML loader resolves the file at bundle time). They are checked in, but
|
|
// regenerate any that are missing.
|
|
await ensureIcons(ICONS_DIR);
|
|
// Self-hosted Inter must exist for index.css to resolve (scripts/fonts.ts).
|
|
const fontFiles = await ensureFonts();
|
|
|
|
// 1. App shell (hashed assets).
|
|
const shellBuild = await Bun.build({
|
|
entrypoints: [join(ROOT, "src/index.html")],
|
|
outdir: DIST,
|
|
target: "browser",
|
|
minify: true,
|
|
sourcemap: "linked",
|
|
define: { "process.env.NODE_ENV": '"production"' },
|
|
env: "BUN_PUBLIC_*",
|
|
// Self-hosted fonts stay as plain url(fonts/...) files — see scripts/fonts.ts.
|
|
external: ["*.woff2"],
|
|
});
|
|
|
|
// 2. Inference worker at its stable URL (Bun does not follow `new Worker`
|
|
// graphs — same mechanism as the dev server in src/index.ts).
|
|
const workerBuild = await Bun.build({
|
|
entrypoints: [join(ROOT, "src/worker/inference.worker.ts")],
|
|
target: "browser",
|
|
format: "esm",
|
|
minify: true,
|
|
// One onnxruntime in the bundle (D-WS7a).
|
|
plugins: [ortAliasPlugin],
|
|
});
|
|
const workerScript = await workerBuild.outputs[0]!.text();
|
|
await Bun.write(join(DIST, "inference.worker.js"), workerScript);
|
|
|
|
// 3. Static PWA assets + onnxruntime wasm binaries (offline inference — the
|
|
// worker loads them from /ort/ instead of the jsdelivr CDN). The manifest and
|
|
// icons are also emitted as hashed shell assets (index.html links them), but
|
|
// the manifest's internal icon URLs point at these fixed /icons/ paths.
|
|
await cp(join(ROOT, "src/pwa/manifest.webmanifest"), join(DIST, "manifest.webmanifest"));
|
|
for (const size of ICON_SIZES) {
|
|
await cp(join(ICONS_DIR, `icon-${size}.png`), join(DIST, `icons/icon-${size}.png`));
|
|
}
|
|
const ortDist = join(ROOT, "node_modules/onnxruntime-web/dist");
|
|
const ortFiles = (await readdir(ortDist)).filter((name) =>
|
|
/^ort-wasm-simd-threaded(\.jsep|\.jspi|\.asyncify)?\.(wasm|mjs)$/.test(name),
|
|
);
|
|
for (const name of ortFiles) {
|
|
await cp(join(ortDist, name), join(DIST, "ort", name));
|
|
}
|
|
for (const name of fontFiles) {
|
|
await cp(join(FONTS_DIR, name), join(DIST, "fonts", name));
|
|
}
|
|
|
|
// 4. Service worker with the injected precache manifest (D-007). Icons,
|
|
// manifest, the worker, and fonts are unhashed, so the cache version hashes
|
|
// their contents (the hashed shell assets are covered by their names).
|
|
const swBuild = await Bun.build({
|
|
entrypoints: [join(ROOT, "src/pwa/sw.ts")],
|
|
target: "browser",
|
|
format: "esm",
|
|
// Placeholder string literals must survive verbatim (src/pwa/precache.ts).
|
|
minify: false,
|
|
});
|
|
const swSource = await swBuild.outputs[0]!.text();
|
|
|
|
const precachePaths = precachePathsFromOutputs(
|
|
shellBuild.outputs.map((output) => relative(DIST, output.path)),
|
|
[
|
|
"inference.worker.js",
|
|
"manifest.webmanifest",
|
|
...ICON_SIZES.map((size) => `icons/icon-${size}.png`),
|
|
...fontFiles.map((name) => `fonts/${name}`),
|
|
],
|
|
);
|
|
const fontBytes = await Promise.all(
|
|
fontFiles.map((name) => Bun.file(join(FONTS_DIR, name)).bytes()),
|
|
);
|
|
const version = Bun.hash(
|
|
JSON.stringify(precachePaths) + swSource + workerScript + fontBytes.map((b) => Bun.hash(b)).join(),
|
|
).toString(36);
|
|
await Bun.write(join(DIST, "sw.js"), injectPrecacheManifest(swSource, precachePaths, version));
|
|
|
|
console.log(`Built dist/ (cache version ${version}); precached app shell:`);
|
|
for (const path of precachePaths) console.log(` ${path}`);
|
|
console.log(` + ${ortFiles.length} onnxruntime files in /ort/ (runtime-cached)`);
|