1import { Server } from "foldkit/experimental";
2
3import { renderPage } from "./entry.server.ts";
4
5// Minimal binding shape, so the example needs no Workers type package.
6interface Env {
7 readonly ASSETS: { fetch(request: Request): Promise<Response> };
8}
9
10// THE SHELL — the BUILT index.html, read from the assets binding rather than
11// imported from source. The source template names `/src/entry.ts`; the built
12// one names the hashed bundle, and it is the only copy that cannot disagree
13// with what the browser will be asked to load.
14const shell = (env: Env, url: URL): Promise<string> =>
15 env.ASSETS.fetch(new Request(new URL("/index.html", url.origin))).then(
16 (response) => response.text(),
17 );
18
19export default {
20 async fetch(request: Request, env: Env): Promise<Response> {
21 // A method the `Request` constructor rejects can never reach an entry, so
22 // it is refused here rather than becoming a 500 further in.
23 if (Server.isHostSettledMethod(request.method)) {
24 return new Response(null, {
25 status: Server.HOST_METHOD_ANSWERS.refusedStatus,
26 headers: { Allow: Server.HOST_METHOD_ANSWERS.allow },
27 });
28 }
29
30 // A request that matched no file is not automatically a page. Browsers
31 // ask for scripts and images with `Accept: */*`, which accepts HTML, so a
32 // hashed bundle that is no longer deployed would otherwise be answered
33 // with the shell at 200 — a stale client would read that as its own
34 // JavaScript. Classifying the miss answers it as the miss it is.
35 const classification = Server.classifyRequest(
36 request.url,
37 request.headers.get("sec-fetch-dest") ?? undefined,
38 );
39 if (classification !== "Page") {
40 return new Response("Not found", { status: 404 });
41 }
42
43 const url = new URL(request.url);
44 return Server.toResponse(await shell(env, url), await renderPage(request));
45 },
46};