Download the whole app build behind a loading page

In production, a page request without the running build's cookie gets a
small loading page instead of the app. It downloads every file in
.bunext/public (listed at /build-info.json) into that build's cache,
checks they are all there, hands over to a new service worker waiting
behind an older one, sets the cookie and reloads. The service worker
then serves the app's code from the cache, so later visits load it from
the device.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-20 19:33:15 +01:00
co-authored by Claude Opus 5.5
parent 9778d0b9ff
commit b3057119df
16 changed files with 991 additions and 45 deletions
@@ -0,0 +1,55 @@
import { afterAll, describe, expect, test } from "bun:test";
import path from "path";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs";
import { tmpdir } from "os";
import buildPrecacheManifest from "./build-precache-manifest";
const root = mkdtempSync(path.join(tmpdir(), "precache-"));
afterAll(() => rmSync(root, { recursive: true, force: true }));
function writeBuild({ dir, files }: { dir: string; files: string[] }) {
writeFileSync(
path.join(dir, "build-info.json"),
JSON.stringify({ build_id: "build-1" }),
);
for (const file of files) {
const full_path = path.join(dir, "public", file);
mkdirSync(path.dirname(full_path), { recursive: true });
writeFileSync(full_path, "x");
}
}
describe("buildPrecacheManifest", () => {
test("lists every public file under its served path", () => {
const dir = path.join(root, "full");
mkdirSync(dir);
writeBuild({
dir,
files: [
"vendor/react.js",
"pages/chunk-AEHOZS24.js",
"pages/admin/bots/[bot_id]/VXCMP4GM.js",
"pages/auth/login/X4TQCLSA.css",
],
});
expect(buildPrecacheManifest({ bunext_dir: dir })).toEqual({
build_id: "build-1",
files: [
"/.bunext/public/pages/admin/bots/[bot_id]/VXCMP4GM.js",
"/.bunext/public/pages/auth/login/X4TQCLSA.css",
"/.bunext/public/pages/chunk-AEHOZS24.js",
"/.bunext/public/vendor/react.js",
],
});
});
test("is null without a build", () => {
const dir = path.join(root, "empty");
mkdirSync(dir);
expect(buildPrecacheManifest({ bunext_dir: dir })).toBeNull();
});
});
@@ -0,0 +1,55 @@
import path from "path";
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
import type { PrecacheManifest } from "@/src/types";
type Params = {
/**
* bunext's build directory (`.bunext`).
*/
bunext_dir: string;
};
/**
* Lists every file of one bunext build's `public/` directory as the path
* the browser asks for (`/.bunext/public/...`), with the build's id, so
* the service worker can download the whole build before the app runs.
* Null without a build.
*/
export default function buildPrecacheManifest({
bunext_dir,
}: Params): PrecacheManifest | null {
const public_dir = path.join(bunext_dir, "public");
const build_info_path = path.join(bunext_dir, "build-info.json");
if (!existsSync(public_dir) || !existsSync(build_info_path)) return null;
const build_id = JSON.parse(readFileSync(build_info_path, "utf-8"))
?.build_id as string | undefined;
if (!build_id) return null;
const files: string[] = [];
const walk = (dir: string) => {
for (const name of readdirSync(dir)) {
const full_path = path.join(dir, name);
if (statSync(full_path).isDirectory()) {
walk(full_path);
continue;
}
const relative = path
.relative(bunext_dir, full_path)
.split(path.sep)
.join("/");
files.push(`/.bunext/${relative}`);
}
};
walk(public_dir);
files.sort();
return { build_id, files };
}
@@ -0,0 +1,27 @@
import path from "path";
import grabDirNames from "@/src/utils/grab-dir-names";
import type { PrecacheManifest } from "@/src/types";
import buildPrecacheManifest from "./build-precache-manifest";
let manifest: PrecacheManifest | null | undefined;
/**
* The running build's precache manifest, production only (development
* rebuilds on every change and is never cached). Read once per process:
* a build never changes under a running server.
*/
export default function grabPrecacheManifest(): PrecacheManifest | null {
if (process.env.NODE_ENV != "production") return null;
if (manifest !== undefined) return manifest;
try {
manifest = buildPrecacheManifest({
bunext_dir: path.join(grabDirNames().ROOT_DIR, ".bunext"),
});
} catch (error) {
console.log("Couldn't read the build for precaching:", error);
manifest = null;
}
return manifest;
}
@@ -0,0 +1,27 @@
import { PwaBuildInfoPath } from "@/src/data/pwa-data";
import grabPrecacheManifest from "./grab-precache-manifest";
/**
* Serves the running build (`.bunext/build-info.json`'s id) with every
* file in `.bunext/public`, for the loading page to download before the
* app runs. Never cached: a deploy must show at once. Not found in
* development, which is never precached.
*/
export default function handleBuildInfo({
pathname,
}: {
pathname: string;
}): Response | null {
if (pathname != PwaBuildInfoPath) return null;
const manifest = grabPrecacheManifest();
if (!manifest) return new Response("Not Found", { status: 404 });
return Response.json(manifest, {
headers: {
"Cache-Control": "no-store, max-age=0, must-revalidate",
"X-Content-Type-Options": "nosniff",
},
});
}
@@ -0,0 +1,63 @@
import path from "path";
import { AppData } from "@/src/data/app-data";
import { PwaLoadingPath } from "@/src/data/pwa-data";
import { getCookie } from "@/src/utils/cookies";
import needsPrecachePage from "@/src/utils/needs-precache-page";
import grabPrecacheManifest from "./grab-precache-manifest";
const LOADING_PAGE = path.join(
path.resolve(import.meta.dir, "../../../../public"),
PwaLoadingPath,
);
/**
* The placeholder `public/loading.html` carries for the running build.
*/
const BuildPlaceholder = "{{build}}";
/**
* Production only: a browser opening a page without the running build in
* its cookie gets the loading page in its place, at the same address. The
* page downloads the build's `.bunext/public` files into the build's
* cache, checks they are all there, sets the cookie and reloads, and the
* service worker then serves the app's code from the device. Null lets
* the request through.
*/
export default async function handlePrecacheGate({
req,
url,
}: {
req: Request;
url: URL;
}): Promise<Response | null> {
const manifest = grabPrecacheManifest();
const needs_page = needsPrecachePage({
method: req.method,
pathname: url.pathname,
fetch_mode: req.headers.get("Sec-Fetch-Mode"),
fetch_dest: req.headers.get("Sec-Fetch-Dest"),
accept: req.headers.get("Accept"),
cookie_build_id: getCookie(req, AppData["BuildCookieName"]),
build_id: manifest?.build_id,
});
if (!needs_page || !manifest) return null;
const file = Bun.file(LOADING_PAGE);
if (!(await file.exists())) return null;
const build = JSON.stringify({
build_id: manifest.build_id,
total: manifest.files.length,
}).replace(/</g, "\\u003c");
return new Response((await file.text()).replace(BuildPlaceholder, build), {
headers: {
"Content-Type": "text/html; charset=utf-8",
"Cache-Control": "no-store, max-age=0, must-revalidate",
"X-Content-Type-Options": "nosniff",
},
});
}