perf: accelerate production request hot paths
Quality / quality (ubuntu-latest) (push) Failing after 12m23s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-03 01:08:43 +05:30
parent fed1d5d3f4
commit 1a1d2e9d08
4 changed files with 164 additions and 8 deletions
+36 -6
View File
@@ -148,6 +148,10 @@ export interface ProdOptions {
port?: number; port?: number;
hostname?: string; hostname?: string;
maxBodyBytes?: number; maxBodyBytes?: number;
/** Keep-alive idle timeout in seconds. Defaults to 30. */
idleTimeout?: number;
/** Allow multiple Bun workers to share the listening port. */
reusePort?: boolean;
/** Enable only for the CLI's supervised exact-production development mode. */ /** Enable only for the CLI's supervised exact-production development mode. */
developmentRuntime?: boolean; developmentRuntime?: boolean;
} }
@@ -205,6 +209,19 @@ function buildProdRouter(manifest: ProdManifest): {
const api = toRoutes(manifest.api); const api = toRoutes(manifest.api);
const realtime = toRoutes(manifest.realtime); const realtime = toRoutes(manifest.realtime);
const optimizedMatcher = (routes: Route[]) => {
const exact = new Map<string, Route>();
const dynamic: Route[] = [];
for (const route of routes) {
if (route.paramNames.length === 0) exact.set(route.raw, route);
else dynamic.push(route);
}
return (pathname: string) => {
const route = exact.get(pathname);
return route ? { route, params: {} } : matchRoute(dynamic, pathname);
};
};
// Components are keyed by name; the runtime resolves them via loadModule(name). // Components are keyed by name; the runtime resolves them via loadModule(name).
for (const c of manifest.components) modules.set(c.name, c.mod); for (const c of manifest.components) modules.set(c.name, c.mod);
// Layouts share the module map under a `layout:` prefix (no name collisions). // Layouts share the module map under a `layout:` prefix (no name collisions).
@@ -219,19 +236,30 @@ function buildProdRouter(manifest: ProdManifest): {
layouts: manifest.layouts.map((l) => ({ name: l.name, file: `layout:${l.name}` })), layouts: manifest.layouts.map((l) => ({ name: l.name, file: `layout:${l.name}` })),
stores: [], stores: [],
schemas: [], // descriptors are pre-baked into schemasJs; not needed at runtime schemas: [], // descriptors are pre-baked into schemasJs; not needed at runtime
matchPage: (p) => matchRoute(pages, p), matchPage: optimizedMatcher(pages),
matchApi: (p) => matchRoute(api, p), matchApi: optimizedMatcher(api),
matchRealtime: (p) => matchRoute(realtime, p), matchRealtime: optimizedMatcher(realtime),
}; };
return { router, modules }; return { router, modules };
} }
/** Serve a pre-built asset file from disk, or 404 if it is absent. */ const productionFileCache = new Map<string, ReturnType<typeof Bun.file>>();
const missingProductionFiles = new Set<string>();
/** Serve a pre-built asset file from disk, caching stable build file handles. */
async function serveFile(path: string | undefined, headers: Record<string, string>) { async function serveFile(path: string | undefined, headers: Record<string, string>) {
if (!path) return new Response("Not Found", { status: 404 }); if (!path) return new Response("Not Found", { status: 404 });
const file = Bun.file(path); if (missingProductionFiles.has(path)) return new Response("Not Found", { status: 404 });
if (!(await file.exists())) return new Response("Not Found", { status: 404 }); let file = productionFileCache.get(path);
if (!file) {
file = Bun.file(path);
if (!(await file.exists())) {
missingProductionFiles.add(path);
return new Response("Not Found", { status: 404 });
}
productionFileCache.set(path, file);
}
return new Response(file, { headers }); return new Response(file, { headers });
} }
@@ -410,6 +438,8 @@ export async function createProductionServer(manifest: ProdManifest, opts: ProdO
hostname: resolveProductionHostname(opts.hostname), hostname: resolveProductionHostname(opts.hostname),
development: false, development: false,
maxRequestBodySize: opts.maxBodyBytes ?? 10 * 1024 * 1024, maxRequestBodySize: opts.maxBodyBytes ?? 10 * 1024 * 1024,
idleTimeout: opts.idleTimeout ?? 30,
reusePort: opts.reusePort ?? false,
fetch: handlers.fetch, fetch: handlers.fetch,
websocket: handlers.websocket, websocket: handlers.websocket,
}); });
+33 -1
View File
@@ -41,6 +41,11 @@ function cachePolicy(filePath: string, mode: Mode): string {
/** Cache the public-dir existence check so it isn't a sync stat on every request. */ /** Cache the public-dir existence check so it isn't a sync stat on every request. */
const publicDirExistsCache = new Map<string, boolean>(); const publicDirExistsCache = new Map<string, boolean>();
const productionPublicFileCache = new Map<
string,
{ body: Uint8Array; contentType: string; cacheControl: string }
>();
const PRODUCTION_PUBLIC_FILE_CACHE_MAX = 256;
function publicDirExists(dir: string): boolean { function publicDirExists(dir: string): boolean {
let exists = publicDirExistsCache.get(dir); let exists = publicDirExistsCache.get(dir);
if (exists === undefined) { if (exists === undefined) {
@@ -73,6 +78,21 @@ export async function servePublicAsset(
let filePath = resolve(base, rel); let filePath = resolve(base, rel);
if (!isInside(base, filePath)) return fallback; if (!isInside(base, filePath)) return fallback;
if (mode === "production") {
const cached = productionPublicFileCache.get(filePath);
if (cached) {
productionPublicFileCache.delete(filePath);
productionPublicFileCache.set(filePath, cached);
return new Response(cached.body.slice(), {
headers: {
"content-type": cached.contentType,
"cache-control": cached.cacheControl,
"x-content-type-options": "nosniff",
},
});
}
}
try { try {
const info = await stat(filePath); const info = await stat(filePath);
if (info.isDirectory()) { if (info.isDirectory()) {
@@ -86,10 +106,22 @@ export async function servePublicAsset(
const body = await readFile(filePath); const body = await readFile(filePath);
const contentType = const contentType =
CONTENT_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream"; CONTENT_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream";
const cacheControl = cachePolicy(filePath, mode);
if (mode === "production") {
productionPublicFileCache.set(filePath, {
body: new Uint8Array(body),
contentType,
cacheControl,
});
if (productionPublicFileCache.size > PRODUCTION_PUBLIC_FILE_CACHE_MAX) {
const oldest = productionPublicFileCache.keys().next().value;
if (oldest) productionPublicFileCache.delete(oldest);
}
}
return new Response(body, { return new Response(body, {
headers: { headers: {
"content-type": contentType, "content-type": contentType,
"cache-control": cachePolicy(filePath, mode), "cache-control": cacheControl,
"x-content-type-options": "nosniff", "x-content-type-options": "nosniff",
}, },
}); });
+46 -1
View File
@@ -2073,6 +2073,23 @@ const COMPRESSIBLE_TYPE =
/^(?:text\/|application\/(?:json|xml|javascript|manifest\+json)|image\/svg\+xml)/i; /^(?:text\/|application\/(?:json|xml|javascript|manifest\+json)|image\/svg\+xml)/i;
/** Below this size gzip's overhead isn't worth it. */ /** Below this size gzip's overhead isn't worth it. */
const COMPRESS_MIN_BYTES = 1024; const COMPRESS_MIN_BYTES = 1024;
const COMPRESSED_RESPONSE_CACHE_MAX = 256;
const compressedResponseCache = new Map<
string,
{ body: Uint8Array; headers: [string, string][]; status: number; statusText: string }
>();
function rememberCompressedResponse(
key: string,
value: { body: Uint8Array; headers: [string, string][]; status: number; statusText: string },
) {
compressedResponseCache.delete(key);
compressedResponseCache.set(key, value);
if (compressedResponseCache.size > COMPRESSED_RESPONSE_CACHE_MAX) {
const oldest = compressedResponseCache.keys().next().value;
if (oldest) compressedResponseCache.delete(oldest);
}
}
/** /**
* Gzip a response when the client accepts it and the body is a compressible, * Gzip a response when the client accepts it and the body is a compressible,
@@ -2080,6 +2097,7 @@ const COMPRESS_MIN_BYTES = 1024;
* `Cache-Control: no-transform`, so they are never buffered here. * `Cache-Control: no-transform`, so they are never buffered here.
*/ */
async function compressResponse(req: Request, res: Response): Promise<Response> { async function compressResponse(req: Request, res: Response): Promise<Response> {
if (req.method.toUpperCase() === "HEAD") return res;
const accept = (req.headers.get("accept-encoding") ?? "").toLowerCase(); const accept = (req.headers.get("accept-encoding") ?? "").toLowerCase();
const acceptsBrotli = /(?:^|,)\s*br(?:\s*;|\s*,|$)/.test(accept); const acceptsBrotli = /(?:^|,)\s*br(?:\s*;|\s*,|$)/.test(accept);
const acceptsGzip = /(?:^|,)\s*gzip(?:\s*;|\s*,|$)/.test(accept); const acceptsGzip = /(?:^|,)\s*gzip(?:\s*;|\s*,|$)/.test(accept);
@@ -2089,6 +2107,25 @@ async function compressResponse(req: Request, res: Response): Promise<Response>
if (!COMPRESSIBLE_TYPE.test(res.headers.get("content-type") ?? "")) return res; if (!COMPRESSIBLE_TYPE.test(res.headers.get("content-type") ?? "")) return res;
if ((res.headers.get("cache-control") ?? "").includes("no-transform")) return res; if ((res.headers.get("cache-control") ?? "").includes("no-transform")) return res;
const immutable = (res.headers.get("cache-control") ?? "").includes("immutable");
// Brotli is ideal for immutable assets because the result is cached. Prefer
// substantially faster gzip for per-request HTML/JSON when the client allows
// both, avoiding synchronous Brotli work on the request hot path.
const preferredEncoding = acceptsBrotli && (immutable || !acceptsGzip) ? "br" : "gzip";
const cacheKey = immutable ? `${req.url}\n${preferredEncoding}` : "";
if (cacheKey) {
const cached = compressedResponseCache.get(cacheKey);
if (cached) {
compressedResponseCache.delete(cacheKey);
compressedResponseCache.set(cacheKey, cached);
return new Response(cached.body.slice(), {
status: cached.status,
statusText: cached.statusText,
headers: cached.headers,
});
}
}
const body = new Uint8Array(await res.arrayBuffer()); const body = new Uint8Array(await res.arrayBuffer());
if (body.length < COMPRESS_MIN_BYTES) { if (body.length < COMPRESS_MIN_BYTES) {
return new Response(body, { return new Response(body, {
@@ -2100,7 +2137,7 @@ async function compressResponse(req: Request, res: Response): Promise<Response>
let encoded: Uint8Array; let encoded: Uint8Array;
let encoding: "br" | "gzip"; let encoding: "br" | "gzip";
if (acceptsBrotli) { if (preferredEncoding === "br") {
encoded = new Uint8Array( encoded = new Uint8Array(
brotliCompressSync(body, { brotliCompressSync(body, {
params: { [zlibConstants.BROTLI_PARAM_QUALITY]: 4 }, params: { [zlibConstants.BROTLI_PARAM_QUALITY]: 4 },
@@ -2120,6 +2157,14 @@ async function compressResponse(req: Request, res: Response): Promise<Response>
else if (!/\baccept-encoding\b/i.test(vary)) headers.set("Vary", `${vary}, Accept-Encoding`); else if (!/\baccept-encoding\b/i.test(vary)) headers.set("Vary", `${vary}, Accept-Encoding`);
const responseBody = new ArrayBuffer(encoded.byteLength); const responseBody = new ArrayBuffer(encoded.byteLength);
new Uint8Array(responseBody).set(encoded); new Uint8Array(responseBody).set(encoded);
if (cacheKey) {
rememberCompressedResponse(cacheKey, {
body: encoded.slice(),
headers: [...headers.entries()],
status: res.status,
statusText: res.statusText,
});
}
return new Response(responseBody, { return new Response(responseBody, {
status: res.status, status: res.status,
statusText: res.statusText, statusText: res.statusText,
@@ -1,4 +1,7 @@
import { expect, test } from "bun:test"; import { expect, test } from "bun:test";
import { mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createProductionHandlers, type ProdManifest } from "../src/prod.ts"; import { createProductionHandlers, type ProdManifest } from "../src/prod.ts";
const manifest: ProdManifest = { const manifest: ProdManifest = {
@@ -72,3 +75,49 @@ test("production client-load endpoint returns only the requested named result",
expect(response.headers.get("cache-control")).toBe("private, no-store"); expect(response.headers.get("cache-control")).toBe("private, no-store");
expect(await response.json()).toEqual({ data: [{ id: 1, name: "Ada" }] }); expect(await response.json()).toEqual({ data: [{ id: 1, name: "Ada" }] });
}); });
test("production prefers fast gzip for dynamic HTML and cached Brotli for immutable assets", async () => {
const largeManifest: ProdManifest = {
...manifest,
pages: [
{
raw: "/large",
mod: { default: () => `<main>${"fast production ".repeat(500)}</main>` },
},
],
};
const directory = mkdtempSync(join(tmpdir(), "wrnexus-prod-assets-"));
const assetPath = join(directory, "app.js");
writeFileSync(assetPath, "const production = true;\n".repeat(500));
const handlers = createProductionHandlers(largeManifest, {
pluginAssets: {
"/assets/app.js": {
path: assetPath,
contentType: "text/javascript; charset=utf-8",
},
},
});
const headers = { "accept-encoding": "br, gzip" };
const page = (await handlers.fetch(
new Request("http://localhost/large", { headers }),
server,
)) as Response;
expect(page.headers.get("content-encoding")).toBe("gzip");
const firstAsset = (await handlers.fetch(
new Request("http://localhost/assets/app.js", { headers }),
server,
)) as Response;
const secondAsset = (await handlers.fetch(
new Request("http://localhost/assets/app.js", { headers }),
server,
)) as Response;
expect(firstAsset.headers.get("content-encoding")).toBe("br");
expect(await secondAsset.arrayBuffer()).toEqual(await firstAsset.arrayBuffer());
const head = (await handlers.fetch(
new Request("http://localhost/assets/app.js", { method: "HEAD", headers }),
server,
)) as Response;
expect(head.headers.get("content-encoding")).toBeNull();
});