interface Loaded { map: BuiltAssetMap; buf: Buffer; } let assets: Loaded | null = null; export type StaticPageId = string; export async function reload() { const [map, buf] = await Promise.all([ fs.readFile(".clover/static.json", "utf8"), fs.readFile(".clover/static.blob"), ]); assets = { map: JSON.parse(map), buf, }; } export async function reloadSync() { const map = fs.readFileSync(".clover/static.json", "utf8"); const buf = fs.readFileSync(".clover/static.blob"); assets = { map: JSON.parse(map), buf, }; } export async function assetMiddleware(c: Context, next: Next) { if (!assets) await reload(); const asset = assets!.map[c.req.path]; if (asset) { return assetInner(c, asset, 200); } return next(); } export async function serveAsset( c: Context, id: StaticPageId, status: StatusCode, ) { assets ?? await reload(); return assetInner(c, assets!.map[id], status); } export function hasAsset(id: string) { if (!assets) reloadSync(); return assets!.map[id] !== undefined; } export function etagMatches(etag: string, ifNoneMatch: string) { return ifNoneMatch === etag || ifNoneMatch.split(/,\s*/).indexOf(etag) > -1; } function subarrayAsset([start, end]: View) { return assets!.buf.subarray(start, end); } function assetInner(c: Context, asset: BuiltAsset, status: StatusCode) { const ifnonematch = c.req.header("If-None-Match"); if (ifnonematch) { const etag = asset.headers.ETag; if (etagMatches(etag, ifnonematch)) { c.res = new Response(null, { status: 304, statusText: "Not Modified", headers: { ETag: etag, }, }); return; } } const acceptEncoding = c.req.header("Accept-Encoding") ?? ""; let body; let headers = asset.headers; if (acceptEncoding.includes("zstd") && asset.zstd) { body = subarrayAsset(asset.zstd); headers = { ...asset.headers, "Content-Encoding": "zstd", }; } else if (acceptEncoding.includes("gzip") && asset.gzip) { body = subarrayAsset(asset.gzip); headers = { ...asset.headers, "Content-Encoding": "gzip", }; } else { body = subarrayAsset(asset.raw); } c.res = new Response(body, { headers, status }); } import * as fs from "./fs.ts"; import type { Context, Next } from "hono"; import type { StatusCode } from "hono/utils/http-status"; import type { BuiltAsset, BuiltAssetMap, View } from "./incremental.ts";