@rshono/core 1.0.0-rc.19 → 1.0.0-rc.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -2
- package/dist/builder/env-shadow-loader.cjs +41 -1
- package/dist/builder/page-entry-loader.cjs +28 -1
- package/dist/builder/page-files.d.ts +0 -1
- package/dist/builder/public-env.d.ts +0 -1
- package/dist/builder/react-versions.d.ts +0 -1
- package/dist/builder/rspack-config.d.ts +0 -1
- package/dist/builder/rspack-config.js +4 -0
- package/dist/builder/rspack-config.js.map +1 -1
- package/dist/builder/server-only-imports.d.ts +0 -1
- package/dist/cli/build.d.ts +0 -1
- package/dist/cli/build.js +37 -35
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev.d.ts +0 -1
- package/dist/cli/dev.js +15 -1
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/exit.d.ts +12 -0
- package/dist/cli/exit.js +16 -0
- package/dist/cli/exit.js.map +1 -0
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +74 -20
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/start.d.ts +0 -1
- package/dist/cli/start.js +3 -2
- package/dist/cli/start.js.map +1 -1
- package/dist/config.d.ts +0 -1
- package/dist/deploy/aws-lambda/runtime.d.ts +0 -1
- package/dist/deploy/build-marker.d.ts +7 -2
- package/dist/deploy/build-marker.js +6 -0
- package/dist/deploy/build-marker.js.map +1 -1
- package/dist/deploy/cloudflare/build.d.ts +0 -1
- package/dist/deploy/cloudflare/build.js +6 -0
- package/dist/deploy/cloudflare/build.js.map +1 -1
- package/dist/deploy/cloudflare/runtime.d.ts +0 -1
- package/dist/deploy/cloudflare/runtime.js +24 -2
- package/dist/deploy/cloudflare/runtime.js.map +1 -1
- package/dist/deploy/contract.d.ts +20 -2
- package/dist/deploy/contract.js.map +1 -1
- package/dist/deploy/filesystem.d.ts +0 -1
- package/dist/deploy/node/runtime.d.ts +0 -1
- package/dist/deploy/presets.d.ts +7 -1
- package/dist/deploy/presets.js +13 -2
- package/dist/deploy/presets.js.map +1 -1
- package/dist/deploy/public-paths.d.ts +42 -0
- package/dist/deploy/public-paths.js +66 -0
- package/dist/deploy/public-paths.js.map +1 -0
- package/dist/deploy/vercel/build.d.ts +0 -1
- package/dist/deploy/vercel/build.js +6 -0
- package/dist/deploy/vercel/build.js.map +1 -1
- package/dist/deploy/vercel/runtime.d.ts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/router.d.ts +20 -5
- package/dist/router.js.map +1 -1
- package/dist/runtime/boundaries.d.ts +0 -1
- package/dist/runtime/client.d.ts +0 -1
- package/dist/runtime/context.d.ts +15 -2
- package/dist/runtime/context.js +15 -1
- package/dist/runtime/context.js.map +1 -1
- package/dist/runtime/control.d.ts +15 -1
- package/dist/runtime/control.js +17 -0
- package/dist/runtime/control.js.map +1 -1
- package/dist/runtime/dev-protocol.d.ts +0 -1
- package/dist/runtime/empty-server-app.d.ts +0 -1
- package/dist/runtime/entry.client.d.ts +0 -1
- package/dist/runtime/entry.client.js +31 -4
- package/dist/runtime/entry.client.js.map +1 -1
- package/dist/runtime/entry.rsc.d.ts +6 -4
- package/dist/runtime/entry.rsc.js +473 -56
- package/dist/runtime/entry.rsc.js.map +1 -1
- package/dist/runtime/entry.ssr.d.ts +19 -5
- package/dist/runtime/entry.ssr.js +22 -47
- package/dist/runtime/entry.ssr.js.map +1 -1
- package/dist/runtime/failure-document.d.ts +11 -0
- package/dist/runtime/failure-document.js +35 -0
- package/dist/runtime/failure-document.js.map +1 -0
- package/dist/runtime/flight-inject.d.ts +0 -1
- package/dist/runtime/flight-inject.js +15 -9
- package/dist/runtime/flight-inject.js.map +1 -1
- package/dist/runtime/hot-update.d.ts +0 -1
- package/dist/runtime/navigation.d.ts +6 -1
- package/dist/runtime/navigation.js +6 -0
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/request.d.ts +23 -1
- package/dist/runtime/request.js +39 -0
- package/dist/runtime/request.js.map +1 -1
- package/dist/runtime/server.d.ts +0 -1
- package/dist/runtime/validate-entries.d.ts +0 -1
- package/dist/runtime/validate-entries.js +114 -10
- package/dist/runtime/validate-entries.js.map +1 -1
- package/dist/server/headers.d.ts +37 -4
- package/dist/server/headers.js +49 -13
- package/dist/server/headers.js.map +1 -1
- package/dist/server/load-config.d.ts +0 -1
- package/dist/server/load-env.d.ts +0 -1
- package/dist/server/prerendered.d.ts +14 -2
- package/dist/server/prerendered.js +42 -4
- package/dist/server/prerendered.js.map +1 -1
- package/dist/server/server-config.d.ts +0 -1
- package/dist/server/shutdown.d.ts +0 -1
- package/dist/server/ssg.d.ts +6 -1
- package/dist/server/ssg.js +30 -7
- package/dist/server/ssg.js.map +1 -1
- package/dist/server/static.d.ts +0 -1
- package/dist/server/static.js +5 -5
- package/dist/server/static.js.map +1 -1
- package/package.json +1 -1
- package/dist/builder/page-files.d.ts.map +0 -1
- package/dist/builder/public-env.d.ts.map +0 -1
- package/dist/builder/react-versions.d.ts.map +0 -1
- package/dist/builder/rspack-config.d.ts.map +0 -1
- package/dist/builder/server-only-imports.d.ts.map +0 -1
- package/dist/cli/build.d.ts.map +0 -1
- package/dist/cli/dev.d.ts.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/start.d.ts.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/deploy/aws-lambda/runtime.d.ts.map +0 -1
- package/dist/deploy/build-marker.d.ts.map +0 -1
- package/dist/deploy/cloudflare/build.d.ts.map +0 -1
- package/dist/deploy/cloudflare/runtime.d.ts.map +0 -1
- package/dist/deploy/contract.d.ts.map +0 -1
- package/dist/deploy/filesystem.d.ts.map +0 -1
- package/dist/deploy/node/runtime.d.ts.map +0 -1
- package/dist/deploy/presets.d.ts.map +0 -1
- package/dist/deploy/vercel/build.d.ts.map +0 -1
- package/dist/deploy/vercel/runtime.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/router.d.ts.map +0 -1
- package/dist/runtime/boundaries.d.ts.map +0 -1
- package/dist/runtime/client.d.ts.map +0 -1
- package/dist/runtime/context.d.ts.map +0 -1
- package/dist/runtime/control.d.ts.map +0 -1
- package/dist/runtime/dev-protocol.d.ts.map +0 -1
- package/dist/runtime/empty-server-app.d.ts.map +0 -1
- package/dist/runtime/entry.client.d.ts.map +0 -1
- package/dist/runtime/entry.rsc.d.ts.map +0 -1
- package/dist/runtime/entry.ssr.d.ts.map +0 -1
- package/dist/runtime/flight-inject.d.ts.map +0 -1
- package/dist/runtime/hot-update.d.ts.map +0 -1
- package/dist/runtime/navigation.d.ts.map +0 -1
- package/dist/runtime/request.d.ts.map +0 -1
- package/dist/runtime/server.d.ts.map +0 -1
- package/dist/runtime/validate-entries.d.ts.map +0 -1
- package/dist/server/headers.d.ts.map +0 -1
- package/dist/server/load-config.d.ts.map +0 -1
- package/dist/server/load-env.d.ts.map +0 -1
- package/dist/server/prerendered.d.ts.map +0 -1
- package/dist/server/server-config.d.ts.map +0 -1
- package/dist/server/shutdown.d.ts.map +0 -1
- package/dist/server/ssg.d.ts.map +0 -1
- package/dist/server/static.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-entries.js","sourceRoot":"","sources":["../../src/runtime/validate-entries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The two modules an app hands the framework — `src/routes.ts` and `src/server.ts` — checked before\n * anything is built on them.\n *\n * Both arrive through a build-time alias that carries no type the compiler can hold on to, so they used to\n * be cast and the mistake found later, somewhere else: a `routes` array that skipped `defineRoutes` surfaced\n * as `TypeError: nN is not iterable` from a minified bundle, and a `src/server.ts` exporting the wrong thing\n * as `Cannot read properties of undefined (reading 'map')` from inside Hono. Neither names the file to open.\n * The mistakes that produced no error at all were worse: a duplicated `path` whose second entry silently\n * never ran, a `staticPaths` on a route that renders per request.\n *\n * The route *table* is checked at module load — during `rshono build`, which imports the server bundle for\n * the prerender pass; at `rshono dev` startup; and when a deployed server boots. The modules the table points\n * *at* cannot be, because they load lazily: {@link assertPageModule} and {@link assertEndpointModule} run on\n * first request, and {@link assertRouteModules} runs both against every route during `rshono build`.\n */\n\nimport type { Handler, Hono } from 'hono';\nimport type { ServerEntry } from 'react-server-dom-rspack/server';\nimport { isPageRoute, type PageComponent, type Route, type RouteConfig } from '../router.js';\n\n/** Every method an endpoint route can name, `'all'` aside — which is also what `'all'` is expanded to below. */\nconst CONCRETE_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options'];\nconst METHODS = new Set([...CONCRETE_METHODS, 'all']);\n\n/** What a page route's `render` accepts. */\nconst RENDER_MODES = ['static', 'dynamic'];\n\nfunction fail(message: string): never {\n throw new Error(`[rshono] ${message}`);\n}\n\n/**\n * How a route is named in a message. Always by position, because that is what a duplicate `path` needs to be\n * told apart, and by path as well wherever there is one.\n */\nfunction label(route: unknown, index: number): string {\n const path = (route as { path?: unknown } | null)?.path;\n return typeof path === 'string' ? `routes[${index}] (\"${path}\")` : `routes[${index}]`;\n}\n\n/** Refuses a key that belongs to the *other* kind of route — see {@link validateRoute}. */\nfunction refuseForeignKeys(route: Record<string, unknown>, name: string, keys: readonly string[], advice: string): void {\n for (const key of keys) {\n if (route[key] !== undefined) fail(`src/routes.ts: ${name} has \\`${key}\\`, which ${advice}`);\n }\n}\n\n/**\n * Checks one entry of the route table.\n *\n * The cross-kind key checks are the ones TypeScript cannot make: excess-property checking against a union\n * accepts any key present in *some* member, so `staticPaths` on an `{ type: 'endpoint' }` route type-checks\n * and is then ignored at runtime — which is indistinguishable from prerendering that quietly does nothing.\n */\nfunction validateRoute(route: unknown, index: number): void {\n if (route === null || typeof route !== 'object') {\n fail(`src/routes.ts: routes[${index}] is ${route === null ? 'null' : typeof route}, not a route object.`);\n }\n const entry = route as Record<string, unknown>;\n const name = label(route, index);\n\n if (typeof entry.path !== 'string' || !entry.path.startsWith('/')) {\n fail(`src/routes.ts: ${name} needs a \\`path\\` starting with \"/\".`);\n }\n if (entry.type !== undefined && entry.type !== 'endpoint') {\n fail(`src/routes.ts: ${name} has type ${JSON.stringify(entry.type)} — the only \\`type\\` is 'endpoint', and a page route omits it.`);\n }\n\n if (entry.type === 'endpoint') {\n if (typeof entry.server !== 'function') {\n fail(`src/routes.ts: ${name} is an endpoint, so it needs \\`server\\` — a function importing the module that exports \\`handler\\`.`);\n }\n if (entry.method !== undefined) validateMethod(entry.method, name);\n refuseForeignKeys(\n entry,\n name,\n ['component', 'render', 'staticPaths'],\n \"only a page route has — an endpoint's `server` handler decides its own response.\",\n );\n } else {\n if (typeof entry.component !== 'function') {\n fail(`src/routes.ts: ${name} needs \\`component\\` — a function importing the page module, e.g. \\`() => import('./pages/home')\\`.`);\n }\n if (entry.render !== undefined && (typeof entry.render !== 'string' || !RENDER_MODES.includes(entry.render))) {\n fail(`src/routes.ts: ${name} has render ${JSON.stringify(entry.render)} — it is 'static' or 'dynamic'.`);\n }\n if (entry.staticPaths !== undefined) {\n if (typeof entry.staticPaths !== 'function') fail(`src/routes.ts: ${name} has a \\`staticPaths\\` that is not a function.`);\n if (entry.render !== 'static') {\n fail(`src/routes.ts: ${name} has \\`staticPaths\\` but is not \\`render: 'static'\\`, so it renders per request and the paths are never built.`);\n }\n }\n refuseForeignKeys(entry, name, ['server', 'method'], \"only an endpoint route has — add `type: 'endpoint'` if that is what this is.\");\n }\n}\n\n/**\n * Checks an endpoint's `method`, which is one method or a list of them.\n *\n * A list is checked member by member so the message names the bad one rather than printing the array, and\n * `'all'` is refused inside one: a list meaning every method is a list its author did not mean to write.\n */\nfunction validateMethod(method: unknown, name: string): void {\n if (Array.isArray(method)) {\n if (method.length === 0) fail(`src/routes.ts: ${name} has an empty \\`method\\` list — omit it to answer every method.`);\n for (const entry of method as unknown[]) {\n if (entry === 'all') {\n fail(`src/routes.ts: ${name} has 'all' inside a \\`method\\` list — use \\`method: 'all'\\`, or list the methods it answers.`);\n }\n validateMethod(entry, name);\n }\n return;\n }\n if (typeof method !== 'string' || !METHODS.has(method)) {\n const meantHead = typeof method === 'string' && method.toLowerCase() === 'head';\n fail(\n `src/routes.ts: ${name} has method ${JSON.stringify(method)}, which is not one of ${[...METHODS].join(', ')}.` +\n (meantHead ? \" A HEAD is dispatched as a GET, so use 'get' — a route registered for HEAD alone answers neither.\" : ''),\n );\n }\n}\n\n/** The methods a route answers, with `'all'` expanded and a list flattened, so two can be compared. */\nfunction methodsOf(route: Route): readonly string[] {\n // A page is registered for GET and POST: the POST is how a `<form action={serverAction}>` reaches it.\n if (isPageRoute(route)) return ['get', 'post'];\n const methods = [route.method ?? 'all'].flat();\n return methods.includes('all') ? CONCRETE_METHODS : methods;\n}\n\n/**\n * Refuses a route the table already answers in full. Hono matches in registration order, so a later entry\n * whose every method and path is spoken for is dead code — and the build that produced it exits 0 with\n * nothing to say, which is how a duplicated `path` survives.\n *\n * Method by method, and only when *all* of them are taken: one path split between a `'get'` endpoint and a\n * `'post'` one shadows nothing, and neither does a catch-all registered behind a route that claims one\n * method of it — that one still answers the rest.\n */\nfunction assertNothingIsShadowed(routes: readonly Route[]): void {\n /** `\"<method> <path>\"` → the first route answering it. */\n const claimed = new Map<string, string>();\n routes.forEach((route, index) => {\n const name = label(route, index);\n const methods = methodsOf(route);\n const claimants = methods.map((method) => claimed.get(`${method} ${route.path}`));\n if (claimants.every((claimant) => claimant !== undefined)) {\n const by = [...new Set(claimants)].join(' and ');\n const verbs = methods.map((method) => method.toUpperCase()).join(', ');\n fail(`src/routes.ts: ${name} would never run — ${by} already answers ${verbs} ${route.path}, and Hono matches in registration order.`);\n }\n for (const method of methods) {\n const key = `${method} ${route.path}`;\n if (!claimed.has(key)) claimed.set(key, name);\n }\n });\n}\n\nfunction validateFallbackPage(page: unknown, field: 'notFound' | 'error'): void {\n if (page === undefined || page === null) return;\n if (typeof page !== 'object' || typeof (page as { component?: unknown }).component !== 'function') {\n fail(`src/routes.ts: \\`${field}\\` must be a page — \\`{ component: () => import('./pages/${field}') }\\`.`);\n }\n}\n\n/**\n * Normalises and checks what `src/routes.ts` exported as `routes`.\n *\n * A bare array is accepted alongside a {@link RouteConfig}, because the docs present both and\n * `defineRoutes` is an identity function over the array form — which makes leaving it off look equivalent\n * right up to the point the build fails somewhere unrelated.\n */\nexport function validateRoutesModule(exported: unknown): RouteConfig {\n const config = (Array.isArray(exported) ? { routes: exported as Route[] } : exported) as RouteConfig | null | undefined;\n if (config === null || config === undefined || typeof config !== 'object' || !Array.isArray(config.routes)) {\n fail(\n 'src/routes.ts must export `routes` as a route array, or as a { routes, notFound?, error? } object — ' +\n 'wrap it in `defineRoutes(…)` to have TypeScript check it for you.',\n );\n }\n\n config.routes.forEach(validateRoute);\n assertNothingIsShadowed(config.routes);\n validateFallbackPage(config.notFound, 'notFound');\n validateFallbackPage(config.error, 'error');\n return config;\n}\n\n/**\n * The Hono app `src/server.ts` default-exports, or `null` for an app that has no `src/server.ts` — the\n * fallback module the alias resolves to instead default-exports exactly that.\n *\n * Duck-typed rather than `instanceof Hono`: what the framework does with the value is `app.route('/', …)`,\n * which reads `routes` off it and mounts the handlers, so those are what have to be there.\n */\nexport function validateServerApp(exported: unknown): Hono | null {\n const app = (exported as { default?: unknown } | null)?.default;\n if (app === null || app === undefined) return null;\n if (typeof app !== 'object' || typeof (app as Hono).fetch !== 'function' || !Array.isArray((app as Hono).routes)) {\n fail('src/server.ts must `export default` a Hono app — `const server = new Hono(); … export default server;`.');\n }\n return app as Hono;\n}\n\n/**\n * The server component a page module must default-export, checked rather than cast.\n *\n * Two structural mistakes land here, and neither can serve *any* request, which is why they are also checked\n * at build time by {@link assertRouteModules}:\n *\n * - no default export at all — a named `export function Page`, or a file that exports its layout instead;\n * - a `'use client'` page. That module's default export is a client reference, so it carries no\n * `entryJsFiles` — the list of scripts the document has to boot, recorded only for a *server* entry.\n * Rendering it anyway would produce a page with no client runtime, which is worse than a message.\n */\nexport function assertPageModule(mod: { default?: unknown }, label: string): ServerEntry<PageComponent> {\n const Page = mod.default as ServerEntry<PageComponent> | undefined;\n if (typeof Page !== 'function') {\n fail(`The page module for ${label} must default-export a server component.`);\n }\n if (!Page.entryJsFiles) {\n fail(\n `The page component for ${label} is missing its client-asset info ('use server-entry'). ` +\n \"The directive is added automatically for inline `component: () => import('…')` thunks in routes.ts. \" +\n \"If this page is wired up another way, put 'use server-entry' on the first line of the page module yourself — \" +\n \"and make sure the page is a server component (a 'use client' page must be wrapped by a server component instead).\",\n );\n }\n return Page;\n}\n\n/**\n * The Hono handler an endpoint module must export as `handler`.\n *\n * The page half of this fork has been checked since it existed; this half used to destructure and call, which\n * turns the one mistake people actually make — a method-named export, the shape other frameworks ask for —\n * into `TypeError: r is not a function` from a minified frame. That is verbatim the failure mode this file's\n * header names as the reason it exists.\n */\nexport function assertEndpointModule(mod: { handler?: unknown }, label: string): Handler {\n const handler = mod.handler;\n if (typeof handler !== 'function') {\n fail(\n `The endpoint module for ${label} must export \\`handler\\` — \\`export const handler: Handler = (c) => c.json({ ok: true });\\`. ` +\n 'One `handler` answers every method the route declares, so a method-named export (`GET`, `POST`) is never read.',\n );\n }\n return handler as Handler;\n}\n\n/**\n * Resolves every route's own module once and runs the two checks above against it — what `rshono build` does\n * once the bundle exists, so a route that cannot serve *any* request fails the build instead of exiting 0 and\n * answering 500 in production for the life of the deployment.\n *\n * Every route is checked, not just the first that fails: these are all fixed by editing a file, and a build\n * that names one of four broken routes costs three more builds to get through.\n *\n * A module that throws while *evaluating* is warned about rather than raised. Checking a module's shape means\n * importing it, and whether an import succeeds is a question about the environment, not about the module: a\n * page whose module scope reads a secret, opens a connection or touches a file that only exists in production\n * fails here and works per request. Failing the build on that would make \"every route module must be\n * importable during the build\" a new requirement on apps, to catch a mistake this cannot tell from a\n * deliberate one. A `render: 'static'` route is the exception, and needs nothing here: it promised to render\n * at build time, so the prerender pass demands the import and fails the build when it throws.\n */\nexport async function assertRouteModules(config: RouteConfig): Promise<void> {\n const failures: string[] = [];\n\n /**\n * Loads one route's module, or reports why it could not be checked. `null` — never a silent skip: the\n * whole point of running at build time is saying what was and was not looked at.\n */\n const load = async <T>(thunk: () => Promise<T>, label: string): Promise<T | null> => {\n try {\n return await thunk();\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n console.warn(` ⚠ ${label} could not be loaded at build time, so its module was not checked — ${reason}`);\n return null;\n }\n };\n\n /** Keeps a check's message instead of raising it, so every broken route is named rather than the first. */\n const check = (assert: () => void): void => {\n try {\n assert();\n } catch (error) {\n failures.push(error instanceof Error ? error.message : String(error));\n }\n };\n\n for (const route of config.routes) {\n const label = `\"${route.path}\"`;\n if (isPageRoute(route)) {\n const mod = await load(route.component, label);\n if (mod) check(() => assertPageModule(mod, label));\n } else {\n const mod = await load(route.server, label);\n if (mod) check(() => assertEndpointModule(mod, label));\n }\n }\n for (const [page, label] of [\n [config.notFound, 'the notFound page'],\n [config.error, 'the error page'],\n ] as const) {\n if (!page) continue;\n const mod = await load(page.component, label);\n if (mod) check(() => assertPageModule(mod, label));\n }\n\n // One failure — overwhelmingly the common case — is raised as itself, so the message reads the way it\n // does on the request path. Several are gathered under a count, each stripped of the prefix the whole\n // list now carries once.\n if (failures.length === 1) throw new Error(failures[0]);\n if (failures.length > 1) {\n fail(\n `${failures.length} route modules cannot serve a request:\\n` +\n failures.map((message) => ` • ${message.replace(/^\\[rshono\\] /, '')}`).join('\\n'),\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"validate-entries.js","sourceRoot":"","sources":["../../src/runtime/validate-entries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The two modules an app hands the framework — `src/routes.ts` and `src/server.ts` — checked before\n * anything is built on them.\n *\n * Both arrive through a build-time alias that carries no type the compiler can hold on to, so they used to\n * be cast and the mistake found later, somewhere else: a `routes` array that skipped `defineRoutes` surfaced\n * as `TypeError: nN is not iterable` from a minified bundle, and a `src/server.ts` exporting the wrong thing\n * as `Cannot read properties of undefined (reading 'map')` from inside Hono. Neither names the file to open.\n * The mistakes that produced no error at all were worse: a duplicated `path` whose second entry silently\n * never ran, a `staticPaths` on a route that renders per request.\n *\n * The route *table* is checked at module load — during `rshono build`, which imports the server bundle for\n * the prerender pass; at `rshono dev` startup; and when a deployed server boots. The modules the table points\n * *at* cannot be, because they load lazily: {@link assertPageModule} and {@link assertEndpointModule} run on\n * first request, and {@link assertRouteModules} runs both against every route during `rshono build`.\n */\n\nimport type { Handler, Hono } from 'hono';\nimport type { ServerEntry } from 'react-server-dom-rspack/server';\nimport { isPageRoute, type PageComponent, type Route, type RouteConfig } from '../router.js';\n\n/** Every method an endpoint route can name, `'all'` aside — which is also what `'all'` is expanded to below. */\nconst CONCRETE_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'options'];\nconst METHODS = new Set([...CONCRETE_METHODS, 'all']);\n\n/** What a page route's `render` accepts. */\nconst RENDER_MODES = ['static', 'dynamic'];\n\n/**\n * The one path prefix the framework claims on every deploy target: the hashed client bundle, mounted by\n * `runtime.mountStaticAssets` ahead of the route table, ending in a terminal 404 — so nothing under it\n * reaches a page. Written out here rather than imported, because the mounts live in the deploy runtimes\n * (`deploy/filesystem.ts`, `deploy/cloudflare/runtime.ts`) and in `cli/dev.ts`, none of which this module\n * can pull in.\n *\n * It is the only one. `/_rshono/hmr` is dev-only and an exact path rather than a prefix, and Cloudflare's\n * `__ssg` is an asset path no app would name.\n */\nconst RESERVED_PREFIX = '/_static';\n\nfunction fail(message: string): never {\n throw new Error(`[rshono] ${message}`);\n}\n\n/**\n * How a route is named in a message. Always by position, because that is what a duplicate `path` needs to be\n * told apart, and by path as well wherever there is one.\n */\nfunction label(route: unknown, index: number): string {\n const path = (route as { path?: unknown } | null)?.path;\n return typeof path === 'string' ? `routes[${index}] (\"${path}\")` : `routes[${index}]`;\n}\n\n/** Refuses a key that belongs to the *other* kind of route — see {@link validateRoute}. */\nfunction refuseForeignKeys(route: Record<string, unknown>, name: string, keys: readonly string[], advice: string): void {\n for (const key of keys) {\n if (route[key] !== undefined) fail(`src/routes.ts: ${name} has \\`${key}\\`, which ${advice}`);\n }\n}\n\n/**\n * Checks one entry of the route table.\n *\n * The cross-kind key checks are the ones TypeScript cannot make: excess-property checking against a union\n * accepts any key present in *some* member, so `staticPaths` on an `{ type: 'endpoint' }` route type-checks\n * and is then ignored at runtime — which is indistinguishable from prerendering that quietly does nothing.\n */\nfunction validateRoute(route: unknown, index: number): void {\n if (route === null || typeof route !== 'object') {\n fail(`src/routes.ts: routes[${index}] is ${route === null ? 'null' : typeof route}, not a route object.`);\n }\n const entry = route as Record<string, unknown>;\n const name = label(route, index);\n\n if (typeof entry.path !== 'string' || !entry.path.startsWith('/')) {\n fail(`src/routes.ts: ${name} needs a \\`path\\` starting with \"/\".`);\n }\n if (entry.type !== undefined && entry.type !== 'endpoint') {\n fail(`src/routes.ts: ${name} has type ${JSON.stringify(entry.type)} — the only \\`type\\` is 'endpoint', and a page route omits it.`);\n }\n\n if (entry.type === 'endpoint') {\n if (typeof entry.server !== 'function') {\n fail(`src/routes.ts: ${name} is an endpoint, so it needs \\`server\\` — a function importing the module that exports \\`handler\\`.`);\n }\n if (entry.method !== undefined) validateMethod(entry.method, name);\n refuseForeignKeys(\n entry,\n name,\n ['component', 'render', 'staticPaths'],\n \"only a page route has — an endpoint's `server` handler decides its own response.\",\n );\n } else {\n if (typeof entry.component !== 'function') {\n fail(`src/routes.ts: ${name} needs \\`component\\` — a function importing the page module, e.g. \\`() => import('./pages/home')\\`.`);\n }\n if (entry.render !== undefined && (typeof entry.render !== 'string' || !RENDER_MODES.includes(entry.render))) {\n fail(`src/routes.ts: ${name} has render ${JSON.stringify(entry.render)} — it is 'static' or 'dynamic'.`);\n }\n if (entry.staticPaths !== undefined) {\n if (typeof entry.staticPaths !== 'function') fail(`src/routes.ts: ${name} has a \\`staticPaths\\` that is not a function.`);\n if (entry.render !== 'static') {\n fail(`src/routes.ts: ${name} has \\`staticPaths\\` but is not \\`render: 'static'\\`, so it renders per request and the paths are never built.`);\n }\n }\n refuseForeignKeys(entry, name, ['server', 'method'], \"only an endpoint route has — add `type: 'endpoint'` if that is what this is.\");\n }\n}\n\n/**\n * Checks an endpoint's `method`, which is one method or a list of them.\n *\n * A list is checked member by member so the message names the bad one rather than printing the array, and\n * `'all'` is refused inside one: a list meaning every method is a list its author did not mean to write.\n */\nfunction validateMethod(method: unknown, name: string): void {\n if (Array.isArray(method)) {\n if (method.length === 0) fail(`src/routes.ts: ${name} has an empty \\`method\\` list — omit it to answer every method.`);\n for (const entry of method as unknown[]) {\n if (entry === 'all') {\n fail(`src/routes.ts: ${name} has 'all' inside a \\`method\\` list — use \\`method: 'all'\\`, or list the methods it answers.`);\n }\n validateMethod(entry, name);\n }\n return;\n }\n if (typeof method !== 'string' || !METHODS.has(method)) {\n const meantHead = typeof method === 'string' && method.toLowerCase() === 'head';\n fail(\n `src/routes.ts: ${name} has method ${JSON.stringify(method)}, which is not one of ${[...METHODS].join(', ')}.` +\n (meantHead ? \" A HEAD is dispatched as a GET, so use 'get' — a route registered for HEAD alone answers neither.\" : ''),\n );\n }\n}\n\n/** The methods a route answers, with `'all'` expanded and a list flattened, so two can be compared. */\nfunction methodsOf(route: Route): readonly string[] {\n // A page is registered for GET and POST: the POST is how a `<form action={serverAction}>` reaches it.\n if (isPageRoute(route)) return ['get', 'post'];\n const methods = [route.method ?? 'all'].flat();\n return methods.includes('all') ? CONCRETE_METHODS : methods;\n}\n\n/**\n * A parameter's *name*, which is not part of the pattern Hono matches on. Anchored to a segment boundary so\n * a `:` inside a `{regex}` constraint is not mistaken for one, and stopped by `{` and `?` — both of which\n * *are* part of the pattern and have to survive into the key.\n */\nconst PARAM_NAME = /(?<=\\/):[^/{?]+/g;\n\n/**\n * What Hono matches on, rather than what was typed — so two spellings of one pattern hash alike.\n *\n * - **A parameter's name is not part of it.** `/u/:id` and `/u/:name` are one route, and whichever is\n * registered second never runs. A `{regex}` constraint is kept, because that one *is* part of the\n * pattern: `/a/:id{[0-9]+}` and `/a/:name` are genuinely different routes and both can answer.\n * - **A `*` before the last segment matches exactly one non-empty segment** — the same thing an\n * unconstrained parameter matches. Checked against Hono 4.13: `/a/[*]/c` and `/a/:id/c` both answer\n * `/a/b/c`, both 404 `/a//c`, and both 404 `/a/b/x/c`.\n *\n * A *trailing* `*` is left alone: it claims a whole subtree rather than one segment, which is\n * {@link subtreeOf}'s half of the job.\n */\nfunction patternKey(path: string): string {\n const segments = path.replace(PARAM_NAME, ':').split('/');\n return segments.map((segment, index) => (segment === '*' && index < segments.length - 1 ? ':' : segment)).join('/');\n}\n\n/**\n * Every key a route answers. More than one only for a trailing optional parameter: `/a/:id?` answers\n * `/a/x` *and* `/a`, and — checked — neither `/a/` nor `/a/x/y`. Both have to be spoken for before it is\n * dead, which is why this is a list rather than a key.\n */\nfunction keysFor(path: string): readonly string[] {\n const key = patternKey(path);\n if (!key.endsWith('/:?')) return [key];\n return [key.slice(0, -1), key.slice(0, -3) || '/'];\n}\n\n/**\n * The subtree a trailing `*` claims, or `null` for a path without one. `/a/*` answers `/a/b` and\n * `/a/b/c/d`, and the bare `/a` as well — but not `/ab`, so the boundary is the `/` and a prefix test has\n * to respect it.\n */\nfunction subtreeOf(key: string): string | null {\n return key.endsWith('/*') ? key.slice(0, -2) : null;\n}\n\n/** Whether the subtree claimed by a trailing `*` at `prefix` contains `key`. */\nfunction covers(prefix: string, key: string): boolean {\n return key === prefix || key.startsWith(`${prefix}/`);\n}\n\n/**\n * Whether a path sits inside {@link RESERVED_PREFIX}'s subtree, which is the prefix and everything below\n * it. The boundary is the `/`: `/_staticky` is an ordinary path and answers normally.\n */\nfunction isReserved(path: string): boolean {\n return path === RESERVED_PREFIX || path.startsWith(`${RESERVED_PREFIX}/`);\n}\n\n/**\n * Refuses a route the table already answers in full, or that the framework's own mount already answers.\n * Hono matches in registration order, so a later entry whose every method and path is spoken for is dead\n * code — and the build that produced it exits 0 with nothing to say, which is how a duplicated `path`\n * survives.\n *\n * The framework's mount is checked first and by name. The rest of this function compares the app's routes\n * with *each other*, so the prefixes the framework registers ahead of them were invisible to it: a route at\n * `/_static/thing` built clean and then 404'd, on every target and in dev. Only a literal path is refused —\n * a parameterised route that happens to match under the prefix, `/:section/thing` or a root `/*`, still\n * answers everything else and is none of this rule's business.\n *\n * Method by method, and only when *all* of them are taken: one path split between a `'get'` endpoint and a\n * `'post'` one shadows nothing, and neither does a catch-all registered behind a route that claims one\n * method of it — that one still answers the rest.\n *\n * Keyed on the *pattern* rather than on the path as written, because a dead route need not be a duplicated\n * string. Two shapes reached a build this way: `/u/:id` followed by `/u/:name`, which is the same route\n * twice under two spellings, and a wildcard registered *ahead* of a concrete path — `/a/*` then `/a/b` —\n * where the second can never be reached. The doc comment above used to discuss only the opposite order, a\n * catch-all registered behind a route, which is the case that is fine.\n *\n * **Where the line is drawn**, for whoever widens this next. A `{regex}` constraint is kept in the key and\n * compared as text, so `/a/:id{[0-9]+}` then `/a/:n{[0-9]+}` is caught and `/a/:id{[0-9]+}` then\n * `/a/:id{\\d+}` is not — the second pair is equivalent, and the later route is dead, and this accepts it.\n * Deciding regex equivalence in general is undecidable-adjacent and not a route validator's job. The same\n * goes for a constraint containing a `/`, which the key carries through unchanged rather than interpreting.\n * Both leave the error on the safe side: an unreachable route slips through, and a route that can still\n * answer is never refused. Anything added here should keep that asymmetry — a false refusal fails a build\n * that was correct, which is much worse than the hole it would close.\n */\nfunction assertNothingIsShadowed(routes: readonly Route[]): void {\n /** `\"<method> <key>\"` → the first route answering it. */\n const claimed = new Map<string, { name: string; path: string }>();\n /** The trailing-`*` routes already registered, which claim a whole subtree rather than single keys. */\n const subtrees: { method: string; prefix: string; name: string; path: string }[] = [];\n\n const claimantOf = (method: string, key: string) =>\n claimed.get(`${method} ${key}`) ?? subtrees.find((subtree) => subtree.method === method && covers(subtree.prefix, key));\n\n routes.forEach((route, index) => {\n const name = label(route, index);\n if (isReserved(route.path)) {\n fail(\n `src/routes.ts: ${name} would never run — the framework serves the client bundle at ${RESERVED_PREFIX}, ` +\n `mounted ahead of the route table and answering that whole subtree, so ${RESERVED_PREFIX} is reserved on every ` +\n 'deploy target and under `rshono dev`. Give the route a path of its own.',\n );\n }\n const methods = methodsOf(route);\n const keys = keysFor(route.path);\n const claimants = methods.flatMap((method) => keys.map((key) => claimantOf(method, key)));\n if (claimants.every((claimant) => claimant !== undefined)) {\n const by = [...new Set(claimants.map((claimant) => claimant.name))].join(' and ');\n const verbs = methods.map((method) => method.toUpperCase()).join(', ');\n // Only when the two are spelled differently, which is the part that reads as a false alarm: an exact\n // duplicate needs no explaining, and this sentence would only get in its way.\n const why = claimants.some((claimant) => claimant.path === route.path)\n ? ''\n : ' Hono matches on the pattern, not the spelling — a parameter’s name is not part of it, and a trailing `*` answers its whole subtree, the bare prefix included.';\n fail(`src/routes.ts: ${name} would never run — ${by} already answers ${verbs} ${route.path}, and Hono matches in registration order.${why}`);\n }\n for (const method of methods) {\n for (const key of keys) {\n if (!claimed.has(`${method} ${key}`)) claimed.set(`${method} ${key}`, { name, path: route.path });\n }\n const prefix = subtreeOf(patternKey(route.path));\n if (prefix !== null) subtrees.push({ method, prefix, name, path: route.path });\n }\n });\n}\n\nfunction validateFallbackPage(page: unknown, field: 'notFound' | 'error'): void {\n if (page === undefined || page === null) return;\n if (typeof page !== 'object' || typeof (page as { component?: unknown }).component !== 'function') {\n fail(`src/routes.ts: \\`${field}\\` must be a page — \\`{ component: () => import('./pages/${field}') }\\`.`);\n }\n}\n\n/**\n * Normalises and checks what `src/routes.ts` exported as `routes`.\n *\n * A bare array is accepted alongside a {@link RouteConfig}, because the docs present both and\n * `defineRoutes` is an identity function over the array form — which makes leaving it off look equivalent\n * right up to the point the build fails somewhere unrelated.\n */\nexport function validateRoutesModule(exported: unknown): RouteConfig {\n const config = (Array.isArray(exported) ? { routes: exported as Route[] } : exported) as RouteConfig | null | undefined;\n if (config === null || config === undefined || typeof config !== 'object' || !Array.isArray(config.routes)) {\n fail(\n 'src/routes.ts must export `routes` as a route array, or as a { routes, notFound?, error? } object — ' +\n 'wrap it in `defineRoutes(…)` to have TypeScript check it for you.',\n );\n }\n\n config.routes.forEach(validateRoute);\n assertNothingIsShadowed(config.routes);\n validateFallbackPage(config.notFound, 'notFound');\n validateFallbackPage(config.error, 'error');\n return config;\n}\n\n/**\n * The Hono app `src/server.ts` default-exports, or `null` for an app that has no `src/server.ts` — the\n * fallback module the alias resolves to instead default-exports exactly that.\n *\n * Duck-typed rather than `instanceof Hono`: what the framework does with the value is `app.route('/', …)`,\n * which reads `routes` off it and mounts the handlers, so those are what have to be there.\n */\nexport function validateServerApp(exported: unknown): Hono | null {\n const app = (exported as { default?: unknown } | null)?.default;\n if (app === null || app === undefined) return null;\n if (typeof app !== 'object' || typeof (app as Hono).fetch !== 'function' || !Array.isArray((app as Hono).routes)) {\n fail('src/server.ts must `export default` a Hono app — `const server = new Hono(); … export default server;`.');\n }\n return app as Hono;\n}\n\n/**\n * The server component a page module must default-export, checked rather than cast.\n *\n * Two structural mistakes land here, and neither can serve *any* request, which is why they are also checked\n * at build time by {@link assertRouteModules}:\n *\n * - no default export at all — a named `export function Page`, or a file that exports its layout instead;\n * - a `'use client'` page. That module's default export is a client reference, so it carries no\n * `entryJsFiles` — the list of scripts the document has to boot, recorded only for a *server* entry.\n * Rendering it anyway would produce a page with no client runtime, which is worse than a message.\n */\nexport function assertPageModule(mod: { default?: unknown }, label: string): ServerEntry<PageComponent> {\n const Page = mod.default as ServerEntry<PageComponent> | undefined;\n if (typeof Page !== 'function') {\n fail(`The page module for ${label} must default-export a server component.`);\n }\n if (!Page.entryJsFiles) {\n fail(\n `The page component for ${label} is missing its client-asset info ('use server-entry'). ` +\n \"The directive is added automatically for inline `component: () => import('…')` thunks in routes.ts. \" +\n \"If this page is wired up another way, put 'use server-entry' on the first line of the page module yourself — \" +\n \"and make sure the page is a server component (a 'use client' page must be wrapped by a server component instead).\",\n );\n }\n return Page;\n}\n\n/**\n * The Hono handler an endpoint module must export as `handler`.\n *\n * The page half of this fork has been checked since it existed; this half used to destructure and call, which\n * turns the one mistake people actually make — a method-named export, the shape other frameworks ask for —\n * into `TypeError: r is not a function` from a minified frame. That is verbatim the failure mode this file's\n * header names as the reason it exists.\n */\nexport function assertEndpointModule(mod: { handler?: unknown }, label: string): Handler {\n const handler = mod.handler;\n if (typeof handler !== 'function') {\n fail(\n `The endpoint module for ${label} must export \\`handler\\` — \\`export const handler: Handler = (c) => c.json({ ok: true });\\`. ` +\n 'One `handler` answers every method the route declares, so a method-named export (`GET`, `POST`) is never read.',\n );\n }\n return handler as Handler;\n}\n\n/**\n * Resolves every route's own module once and runs the two checks above against it — what `rshono build` does\n * once the bundle exists, so a route that cannot serve *any* request fails the build instead of exiting 0 and\n * answering 500 in production for the life of the deployment.\n *\n * Every route is checked, not just the first that fails: these are all fixed by editing a file, and a build\n * that names one of four broken routes costs three more builds to get through.\n *\n * A module that throws while *evaluating* is warned about rather than raised. Checking a module's shape means\n * importing it, and whether an import succeeds is a question about the environment, not about the module: a\n * page whose module scope reads a secret, opens a connection or touches a file that only exists in production\n * fails here and works per request. Failing the build on that would make \"every route module must be\n * importable during the build\" a new requirement on apps, to catch a mistake this cannot tell from a\n * deliberate one. A `render: 'static'` route is the exception, and needs nothing here: it promised to render\n * at build time, so the prerender pass demands the import and fails the build when it throws.\n */\nexport async function assertRouteModules(config: RouteConfig): Promise<void> {\n const failures: string[] = [];\n\n /**\n * Loads one route's module, or reports why it could not be checked. `null` — never a silent skip: the\n * whole point of running at build time is saying what was and was not looked at.\n */\n const load = async <T>(thunk: () => Promise<T>, label: string): Promise<T | null> => {\n try {\n return await thunk();\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n console.warn(` ⚠ ${label} could not be loaded at build time, so its module was not checked — ${reason}`);\n return null;\n }\n };\n\n /** Keeps a check's message instead of raising it, so every broken route is named rather than the first. */\n const check = (assert: () => void): void => {\n try {\n assert();\n } catch (error) {\n failures.push(error instanceof Error ? error.message : String(error));\n }\n };\n\n for (const route of config.routes) {\n const label = `\"${route.path}\"`;\n if (isPageRoute(route)) {\n const mod = await load(route.component, label);\n if (mod) check(() => assertPageModule(mod, label));\n } else {\n const mod = await load(route.server, label);\n if (mod) check(() => assertEndpointModule(mod, label));\n }\n }\n for (const [page, label] of [\n [config.notFound, 'the notFound page'],\n [config.error, 'the error page'],\n ] as const) {\n if (!page) continue;\n const mod = await load(page.component, label);\n if (mod) check(() => assertPageModule(mod, label));\n }\n\n // One failure — overwhelmingly the common case — is raised as itself, so the message reads the way it\n // does on the request path. Several are gathered under a count, each stripped of the prefix the whole\n // list now carries once.\n if (failures.length === 1) throw new Error(failures[0]);\n if (failures.length > 1) {\n fail(\n `${failures.length} route modules cannot serve a request:\\n` +\n failures.map((message) => ` • ${message.replace(/^\\[rshono\\] /, '')}`).join('\\n'),\n );\n }\n}\n"]}
|
package/dist/server/headers.d.ts
CHANGED
|
@@ -3,10 +3,44 @@
|
|
|
3
3
|
* because getting `Vary` and `ETag` comparison subtly wrong only shows up behind a CDN.
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* What a 404 under the `/_static` mount must carry, on every deploy target.
|
|
7
|
+
*
|
|
8
|
+
* A 404 is heuristically cacheable under RFC 9111, and the miss this answers is the one a **rolling deploy**
|
|
9
|
+
* produces: an old instance 404s a content-hashed chunk the new one has. Without this a shared cache may
|
|
10
|
+
* store that answer against a URL that is about to become valid, and then serve it to everyone.
|
|
11
|
+
*
|
|
12
|
+
* Shared by the two mounts — `createStaticAssetsApp` for the filesystem targets and `mountStaticAssets` in
|
|
13
|
+
* `deploy/cloudflare/runtime.ts` — because it was written down in one of them and inherited by accident in
|
|
14
|
+
* the other, which is how the Workers mount came to have no terminal 404 at all. Here rather than in either,
|
|
15
|
+
* since `server/static.ts` reaches for `@hono/node-server` and a Worker bundle cannot import it.
|
|
16
|
+
*
|
|
17
|
+
* The same string as a page response's default (`PAGE_CACHE_CONTROL` in `entry.rsc.tsx`) and a separate
|
|
18
|
+
* constant on purpose: that one is about a logged-in user's page, this one about a hash that is about to
|
|
19
|
+
* resolve, and a change to either must not move the other.
|
|
9
20
|
*/
|
|
21
|
+
export declare const ASSET_MISS_CACHE_CONTROL = "private, no-cache";
|
|
22
|
+
/**
|
|
23
|
+
* The `Vary` a response needs in order to carry `value`, or `null` when there is nothing to write — because it
|
|
24
|
+
* already lists it, or because it lists `*`, which means "never reuse this" and covers everything.
|
|
25
|
+
*
|
|
26
|
+
* What is already there is kept, spelling and all: a route, a middleware or a proxy may have added an entry of
|
|
27
|
+
* its own, and a plain `set` would drop it, leaving a cache free to serve one variant in place of another.
|
|
28
|
+
*
|
|
29
|
+
* The header is parsed into entries rather than tested as a string, because both of the questions it is asked
|
|
30
|
+
* are about the *list* and neither is about the text. `*` counts wherever it sits, not only alone — a cache
|
|
31
|
+
* told never to reuse a response is not told it harder by another field name. And an existing header with no
|
|
32
|
+
* entries in it (`vary: ''`, which an app writing one from a list that came out empty can produce, and which
|
|
33
|
+
* Hono keeps verbatim) is nothing to append to: `, RSC` is an empty list element, which RFC 9110 tells a
|
|
34
|
+
* sender not to generate, and a cache strict enough to reject the malformed header would drop the `Vary` that
|
|
35
|
+
* is the only thing keeping a page URL's two representations apart. That is the failure this header exists to
|
|
36
|
+
* prevent, reached through the header itself.
|
|
37
|
+
*
|
|
38
|
+
* Pure, and separate from {@link appendVary}, because the two callers cannot write the same way. One holds a
|
|
39
|
+
* `Response` it built itself; the response floor holds one the *app* may have returned, whose header bag can be
|
|
40
|
+
* immutable — see the floor in `entry.rsc.tsx`, which writes this answer through `c.header()`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function varyWith(existing: string | null, value: string): string | null;
|
|
43
|
+
/** {@link varyWith}, written straight back — for a `Headers` the caller knows it can write to. */
|
|
10
44
|
export declare function appendVary(headers: Headers, value: string): void;
|
|
11
45
|
/**
|
|
12
46
|
* True when an `If-None-Match` matches `etag` — the client already holds this body and can be answered with a
|
|
@@ -14,4 +48,3 @@ export declare function appendVary(headers: Headers, value: string): void;
|
|
|
14
48
|
* changes the bytes without changing the representation, and may weaken the validator when it does.
|
|
15
49
|
*/
|
|
16
50
|
export declare function etagMatches(ifNoneMatch: string | undefined, etag: string): boolean;
|
|
17
|
-
//# sourceMappingURL=headers.d.ts.map
|
package/dist/server/headers.js
CHANGED
|
@@ -3,21 +3,57 @@
|
|
|
3
3
|
* because getting `Vary` and `ETag` comparison subtly wrong only shows up behind a CDN.
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* What a 404 under the `/_static` mount must carry, on every deploy target.
|
|
7
|
+
*
|
|
8
|
+
* A 404 is heuristically cacheable under RFC 9111, and the miss this answers is the one a **rolling deploy**
|
|
9
|
+
* produces: an old instance 404s a content-hashed chunk the new one has. Without this a shared cache may
|
|
10
|
+
* store that answer against a URL that is about to become valid, and then serve it to everyone.
|
|
11
|
+
*
|
|
12
|
+
* Shared by the two mounts — `createStaticAssetsApp` for the filesystem targets and `mountStaticAssets` in
|
|
13
|
+
* `deploy/cloudflare/runtime.ts` — because it was written down in one of them and inherited by accident in
|
|
14
|
+
* the other, which is how the Workers mount came to have no terminal 404 at all. Here rather than in either,
|
|
15
|
+
* since `server/static.ts` reaches for `@hono/node-server` and a Worker bundle cannot import it.
|
|
16
|
+
*
|
|
17
|
+
* The same string as a page response's default (`PAGE_CACHE_CONTROL` in `entry.rsc.tsx`) and a separate
|
|
18
|
+
* constant on purpose: that one is about a logged-in user's page, this one about a hash that is about to
|
|
19
|
+
* resolve, and a change to either must not move the other.
|
|
9
20
|
*/
|
|
21
|
+
export const ASSET_MISS_CACHE_CONTROL = 'private, no-cache';
|
|
22
|
+
/**
|
|
23
|
+
* The `Vary` a response needs in order to carry `value`, or `null` when there is nothing to write — because it
|
|
24
|
+
* already lists it, or because it lists `*`, which means "never reuse this" and covers everything.
|
|
25
|
+
*
|
|
26
|
+
* What is already there is kept, spelling and all: a route, a middleware or a proxy may have added an entry of
|
|
27
|
+
* its own, and a plain `set` would drop it, leaving a cache free to serve one variant in place of another.
|
|
28
|
+
*
|
|
29
|
+
* The header is parsed into entries rather than tested as a string, because both of the questions it is asked
|
|
30
|
+
* are about the *list* and neither is about the text. `*` counts wherever it sits, not only alone — a cache
|
|
31
|
+
* told never to reuse a response is not told it harder by another field name. And an existing header with no
|
|
32
|
+
* entries in it (`vary: ''`, which an app writing one from a list that came out empty can produce, and which
|
|
33
|
+
* Hono keeps verbatim) is nothing to append to: `, RSC` is an empty list element, which RFC 9110 tells a
|
|
34
|
+
* sender not to generate, and a cache strict enough to reject the malformed header would drop the `Vary` that
|
|
35
|
+
* is the only thing keeping a page URL's two representations apart. That is the failure this header exists to
|
|
36
|
+
* prevent, reached through the header itself.
|
|
37
|
+
*
|
|
38
|
+
* Pure, and separate from {@link appendVary}, because the two callers cannot write the same way. One holds a
|
|
39
|
+
* `Response` it built itself; the response floor holds one the *app* may have returned, whose header bag can be
|
|
40
|
+
* immutable — see the floor in `entry.rsc.tsx`, which writes this answer through `c.header()`.
|
|
41
|
+
*/
|
|
42
|
+
export function varyWith(existing, value) {
|
|
43
|
+
if (existing === null)
|
|
44
|
+
return value;
|
|
45
|
+
const entries = existing.split(',').map((entry) => entry.trim());
|
|
46
|
+
if (entries.some((entry) => entry === '*' || entry.toLowerCase() === value.toLowerCase()))
|
|
47
|
+
return null;
|
|
48
|
+
// Appended to the original text, not to the parsed entries, so a header this has nothing to say about comes
|
|
49
|
+
// back exactly as its author wrote it.
|
|
50
|
+
return entries.some(Boolean) ? `${existing}, ${value}` : value;
|
|
51
|
+
}
|
|
52
|
+
/** {@link varyWith}, written straight back — for a `Headers` the caller knows it can write to. */
|
|
10
53
|
export function appendVary(headers, value) {
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
13
|
-
headers.set('vary',
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
if (existing.trim() === '*')
|
|
17
|
-
return;
|
|
18
|
-
const already = existing.split(',').some((entry) => entry.trim().toLowerCase() === value.toLowerCase());
|
|
19
|
-
if (!already)
|
|
20
|
-
headers.set('vary', `${existing}, ${value}`);
|
|
54
|
+
const vary = varyWith(headers.get('vary'), value);
|
|
55
|
+
if (vary !== null)
|
|
56
|
+
headers.set('vary', vary);
|
|
21
57
|
}
|
|
22
58
|
/**
|
|
23
59
|
* True when an `If-None-Match` matches `etag` — the client already holds this body and can be answered with a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../../src/server/headers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH
|
|
1
|
+
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../../src/server/headers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,mBAAmB,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,QAAQ,CAAC,QAAuB,EAAE,KAAa;IAC7D,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACpC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACjE,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,GAAG,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACvG,4GAA4G;IAC5G,uCAAuC;IACvC,OAAO,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AACjE,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,UAAU,CAAC,OAAgB,EAAE,KAAa;IACxD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;IAClD,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,WAA+B,EAAE,IAAY;IACvE,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,SAAS,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QAC3C,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QACnC,OAAO,SAAS,KAAK,GAAG,IAAI,SAAS,KAAK,MAAM,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Header utilities shared by the response-header defaults and the prerendered-page cache. Their own module\n * because getting `Vary` and `ETag` comparison subtly wrong only shows up behind a CDN.\n */\n\n/**\n * What a 404 under the `/_static` mount must carry, on every deploy target.\n *\n * A 404 is heuristically cacheable under RFC 9111, and the miss this answers is the one a **rolling deploy**\n * produces: an old instance 404s a content-hashed chunk the new one has. Without this a shared cache may\n * store that answer against a URL that is about to become valid, and then serve it to everyone.\n *\n * Shared by the two mounts — `createStaticAssetsApp` for the filesystem targets and `mountStaticAssets` in\n * `deploy/cloudflare/runtime.ts` — because it was written down in one of them and inherited by accident in\n * the other, which is how the Workers mount came to have no terminal 404 at all. Here rather than in either,\n * since `server/static.ts` reaches for `@hono/node-server` and a Worker bundle cannot import it.\n *\n * The same string as a page response's default (`PAGE_CACHE_CONTROL` in `entry.rsc.tsx`) and a separate\n * constant on purpose: that one is about a logged-in user's page, this one about a hash that is about to\n * resolve, and a change to either must not move the other.\n */\nexport const ASSET_MISS_CACHE_CONTROL = 'private, no-cache';\n\n/**\n * The `Vary` a response needs in order to carry `value`, or `null` when there is nothing to write — because it\n * already lists it, or because it lists `*`, which means \"never reuse this\" and covers everything.\n *\n * What is already there is kept, spelling and all: a route, a middleware or a proxy may have added an entry of\n * its own, and a plain `set` would drop it, leaving a cache free to serve one variant in place of another.\n *\n * The header is parsed into entries rather than tested as a string, because both of the questions it is asked\n * are about the *list* and neither is about the text. `*` counts wherever it sits, not only alone — a cache\n * told never to reuse a response is not told it harder by another field name. And an existing header with no\n * entries in it (`vary: ''`, which an app writing one from a list that came out empty can produce, and which\n * Hono keeps verbatim) is nothing to append to: `, RSC` is an empty list element, which RFC 9110 tells a\n * sender not to generate, and a cache strict enough to reject the malformed header would drop the `Vary` that\n * is the only thing keeping a page URL's two representations apart. That is the failure this header exists to\n * prevent, reached through the header itself.\n *\n * Pure, and separate from {@link appendVary}, because the two callers cannot write the same way. One holds a\n * `Response` it built itself; the response floor holds one the *app* may have returned, whose header bag can be\n * immutable — see the floor in `entry.rsc.tsx`, which writes this answer through `c.header()`.\n */\nexport function varyWith(existing: string | null, value: string): string | null {\n if (existing === null) return value;\n const entries = existing.split(',').map((entry) => entry.trim());\n if (entries.some((entry) => entry === '*' || entry.toLowerCase() === value.toLowerCase())) return null;\n // Appended to the original text, not to the parsed entries, so a header this has nothing to say about comes\n // back exactly as its author wrote it.\n return entries.some(Boolean) ? `${existing}, ${value}` : value;\n}\n\n/** {@link varyWith}, written straight back — for a `Headers` the caller knows it can write to. */\nexport function appendVary(headers: Headers, value: string): void {\n const vary = varyWith(headers.get('vary'), value);\n if (vary !== null) headers.set('vary', vary);\n}\n\n/**\n * True when an `If-None-Match` matches `etag` — the client already holds this body and can be answered with a\n * 304. The header carries a list, and the weak prefix is ignored on both sides: a CDN that gzips on the way out\n * changes the bytes without changing the representation, and may weaken the validator when it does.\n */\nexport function etagMatches(ifNoneMatch: string | undefined, etag: string): boolean {\n if (!ifNoneMatch) return false;\n const normalize = (value: string) => value.trim().replace(/^W\\//, '');\n const wanted = normalize(etag);\n return ifNoneMatch.split(',').some((entry) => {\n const candidate = normalize(entry);\n return candidate === '*' || candidate === wanted;\n });\n}\n"]}
|
|
@@ -26,6 +26,19 @@ export declare const VARIANTS: {
|
|
|
26
26
|
readonly contentType: 'text/x-component';
|
|
27
27
|
};
|
|
28
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* How a build-time document render tells the prerender pass that the app minted a CSP nonce for its path.
|
|
31
|
+
*
|
|
32
|
+
* The other half of the same contract as {@link VARIANTS}, and it exists because the pass cannot see the
|
|
33
|
+
* request it made: it renders through the app's own middleware inside the server bundle, in a module graph of
|
|
34
|
+
* its own, and keeps the body alone. What it needs to know is whether a *request* for this path will mint a
|
|
35
|
+
* nonce, because a document that will is one the framework re-renders per request — so the file the pass is
|
|
36
|
+
* about to write is one no deployment will read, and saying "prerendered" about it is a lie by omission.
|
|
37
|
+
*
|
|
38
|
+
* Set only while `RSHONO_PRERENDER` is in the environment, which is `rshono build`'s own process, so it never
|
|
39
|
+
* reaches a deployed response; and never stored, since only the body is.
|
|
40
|
+
*/
|
|
41
|
+
export declare const PRERENDER_NONCE_HEADER = "x-rshono-prerender-nonce";
|
|
29
42
|
/**
|
|
30
43
|
* The index the build leaves beside the pages, naming every file it wrote — one `files` array of
|
|
31
44
|
* {@link ssgFilePath} names.
|
|
@@ -54,7 +67,7 @@ export declare const SSG_MANIFEST_FILE = "manifest.json";
|
|
|
54
67
|
*
|
|
55
68
|
* `null` is everything that is not one file: a `.` or `..` segment — so traversal is a miss rather than a
|
|
56
69
|
* lookup, which a store addressed by key, with no `resolve()` to fall back on, depends on — an empty segment,
|
|
57
|
-
* and any segment
|
|
70
|
+
* and any segment {@link isStorableSegment} refuses as unportable.
|
|
58
71
|
*/
|
|
59
72
|
export declare function ssgFilePath(path: string, variant?: PrerenderVariant): string | null;
|
|
60
73
|
/**
|
|
@@ -106,4 +119,3 @@ export interface PrerenderedPage {
|
|
|
106
119
|
*/
|
|
107
120
|
etag: string;
|
|
108
121
|
}
|
|
109
|
-
//# sourceMappingURL=prerendered.d.ts.map
|
|
@@ -12,6 +12,19 @@ export const VARIANTS = {
|
|
|
12
12
|
html: { file: 'index.html', headers: {}, contentType: 'text/html' },
|
|
13
13
|
flight: { file: 'index.rsc', headers: { RSC: '1' }, contentType: 'text/x-component' },
|
|
14
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* How a build-time document render tells the prerender pass that the app minted a CSP nonce for its path.
|
|
17
|
+
*
|
|
18
|
+
* The other half of the same contract as {@link VARIANTS}, and it exists because the pass cannot see the
|
|
19
|
+
* request it made: it renders through the app's own middleware inside the server bundle, in a module graph of
|
|
20
|
+
* its own, and keeps the body alone. What it needs to know is whether a *request* for this path will mint a
|
|
21
|
+
* nonce, because a document that will is one the framework re-renders per request — so the file the pass is
|
|
22
|
+
* about to write is one no deployment will read, and saying "prerendered" about it is a lie by omission.
|
|
23
|
+
*
|
|
24
|
+
* Set only while `RSHONO_PRERENDER` is in the environment, which is `rshono build`'s own process, so it never
|
|
25
|
+
* reaches a deployed response; and never stored, since only the body is.
|
|
26
|
+
*/
|
|
27
|
+
export const PRERENDER_NONCE_HEADER = 'x-rshono-prerender-nonce';
|
|
15
28
|
/**
|
|
16
29
|
* The index the build leaves beside the pages, naming every file it wrote — one `files` array of
|
|
17
30
|
* {@link ssgFilePath} names.
|
|
@@ -30,6 +43,24 @@ export const SSG_MANIFEST_FILE = 'manifest.json';
|
|
|
30
43
|
* spelled with `:` and `*`, falls out of the same rule.
|
|
31
44
|
*/
|
|
32
45
|
const UNPORTABLE_SEGMENT = /[\\/:*?"<>|]/;
|
|
46
|
+
/**
|
|
47
|
+
* The DOS device names Windows still reserves, with or without an extension and whatever the case — so
|
|
48
|
+
* `con`, `CON` and `Con.txt` are all the console. `mkdir` on one fails with `EINVAL`, naming a path the
|
|
49
|
+
* author never wrote, which is why they are refused here with the rest.
|
|
50
|
+
*
|
|
51
|
+
* The list is Microsoft's, superscripts included: `CON PRN AUX NUL`, `COM0`–`COM9`, `COM¹ COM² COM³`, and
|
|
52
|
+
* the same three suffixes for `LPT`. A documentation slug of `con` is a plausible thing to write and
|
|
53
|
+
* builds fine on Linux today; refusing it everywhere is the same trade `:` and `*` already make — portable
|
|
54
|
+
* or not at all, rather than a build that works on the author's machine and fails in CI on Windows.
|
|
55
|
+
*/
|
|
56
|
+
const RESERVED_DEVICE_NAME = /^(?:con|prn|aux|nul|com[0-9¹²³]|lpt[0-9¹²³])(?:\..*)?$/i;
|
|
57
|
+
/**
|
|
58
|
+
* A trailing `.` or space, which Win32 **strips** rather than refusing: `docs/x.` is stored as `docs/x`, so
|
|
59
|
+
* the page is written where no reader will look for it — "a page the build reports as prerendered and
|
|
60
|
+
* nothing ever serves", which is the failure {@link ssgFilePath} exists to prevent. The silent half of this
|
|
61
|
+
* rule, and the more important one: the reserved names above at least fail loudly.
|
|
62
|
+
*/
|
|
63
|
+
const TRAILING_STRIPPED = /[. ]$/;
|
|
33
64
|
/**
|
|
34
65
|
* Decodes one path segment the way a URL does, treating a malformed escape as the literal text it is:
|
|
35
66
|
* `decodeURIComponent('%')` throws, and a request path is whatever the client chose to send.
|
|
@@ -43,13 +74,20 @@ function decodeSegment(segment) {
|
|
|
43
74
|
}
|
|
44
75
|
}
|
|
45
76
|
/**
|
|
46
|
-
* Whether one decoded segment can be a directory in the prerender tree
|
|
47
|
-
* marker,
|
|
48
|
-
* characters, which a percent-escape is free to carry into a
|
|
77
|
+
* Whether one decoded segment can be a directory in the prerender tree, on every host rather than on this
|
|
78
|
+
* one: not empty, not a relative-path marker, made only of characters a file name can hold
|
|
79
|
+
* ({@link UNPORTABLE_SEGMENT}, plus the control characters, which a percent-escape is free to carry into a
|
|
80
|
+
* path and no filesystem wants in a name), not a {@link RESERVED_DEVICE_NAME}, and not ending in a
|
|
81
|
+
* character Win32 would {@link TRAILING_STRIPPED | strip}.
|
|
82
|
+
*
|
|
83
|
+
* The last two are Windows rules that no macOS or Linux build can discover for itself — which is exactly
|
|
84
|
+
* why they belong in a check rather than in a caveat.
|
|
49
85
|
*/
|
|
50
86
|
function isStorableSegment(segment) {
|
|
51
87
|
if (segment === '' || segment === '.' || segment === '..')
|
|
52
88
|
return false;
|
|
89
|
+
if (RESERVED_DEVICE_NAME.test(segment) || TRAILING_STRIPPED.test(segment))
|
|
90
|
+
return false;
|
|
53
91
|
return !UNPORTABLE_SEGMENT.test(segment) && ![...segment].some((char) => char < ' ');
|
|
54
92
|
}
|
|
55
93
|
/**
|
|
@@ -69,7 +107,7 @@ function isStorableSegment(segment) {
|
|
|
69
107
|
*
|
|
70
108
|
* `null` is everything that is not one file: a `.` or `..` segment — so traversal is a miss rather than a
|
|
71
109
|
* lookup, which a store addressed by key, with no `resolve()` to fall back on, depends on — an empty segment,
|
|
72
|
-
* and any segment
|
|
110
|
+
* and any segment {@link isStorableSegment} refuses as unportable.
|
|
73
111
|
*/
|
|
74
112
|
export function ssgFilePath(path, variant = 'html') {
|
|
75
113
|
const file = VARIANTS[variant].file;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prerendered.js","sourceRoot":"","sources":["../../src/server/prerendered.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Everything about a prerendered page that doesn't need a filesystem: where the build puts it, and what it\n * looks like once read back.\n *\n * Apart from `ssg.ts`, which does the reading and writing through `node:fs`, because a deploy target without a\n * filesystem reads the same layout out of an asset store — and importing it from there would drag `node:fs`\n * into a bundle that has neither.\n */\n\n/**\n * The two representations of a page, prerendered side by side: a hard load wants the HTML document, a soft\n * navigation asks the same URL for a flight payload. Without both, every in-app click would re-render a page\n * that was already built.\n */\nexport type PrerenderVariant = 'html' | 'flight';\n\nexport const VARIANTS = {\n // `headers` is what the build asks each representation for, so it stays the same request the browser makes:\n // a document is the default and a flight payload is the `RSC` header. See `runtime/request.ts`.\n html: { file: 'index.html', headers: {}, contentType: 'text/html' },\n flight: { file: 'index.rsc', headers: { RSC: '1' }, contentType: 'text/x-component' },\n} as const satisfies Record<PrerenderVariant, { file: string; headers: Record<string, string>; contentType: string }>;\n\n/**\n * The index the build leaves beside the pages, naming every file it wrote — one `files` array of\n * {@link ssgFilePath} names.\n *\n * It exists so a reader can tell \"this store has no page for that path\" without asking the store, which is\n * the difference between a `Map` lookup and a failed `readFile` (or, without a filesystem, a failed store\n * fetch) on **every** request to a static route the build could not prerender — misses are deliberately not\n * cached, so that one never warms up. Absent is not an error: a build from an older rshono has none, and a\n * reader that finds none looks the way it always did.\n */\nexport const SSG_MANIFEST_FILE = 'manifest.json';\n\n/**\n * What a decoded path segment may not hold if it is to be one portable file name. `\\ / : * ? \" < > |` are\n * refused by Windows and `/` by every host, so a `staticPaths` value containing one fails the build rather\n * than writing a page on one machine and not on another — and a route pattern, whose params and wildcards are\n * spelled with `:` and `*`, falls out of the same rule.\n */\nconst UNPORTABLE_SEGMENT = /[\\\\/:*?\"<>|]/;\n\n/**\n * Decodes one path segment the way a URL does, treating a malformed escape as the literal text it is:\n * `decodeURIComponent('%')` throws, and a request path is whatever the client chose to send.\n */\nfunction decodeSegment(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n\n/**\n * Whether one decoded segment can be a directory in the prerender tree: not empty, not a relative-path\n * marker, and made only of characters a file name can hold — {@link UNPORTABLE_SEGMENT}, plus the control\n * characters, which a percent-escape is free to carry into a path and no filesystem wants in a name.\n */\nfunction isStorableSegment(segment: string): boolean {\n if (segment === '' || segment === '.' || segment === '..') return false;\n return !UNPORTABLE_SEGMENT.test(segment) && ![...segment].some((char) => char < ' ');\n}\n\n/**\n * Where a path's prerendered output lives, relative to the output root — or `null` for a path no single file\n * can answer.\n *\n * **The one canonical form**, shared by the build that writes the file and by every deploy target that reads\n * it back, because the two drifting apart is invisible: the build reports the page as prerendered and every\n * request afterwards falls through to SSR, forever. Each segment is stored *decoded*, so `/docs/café` is\n * `docs/café` whether the caller holds the path percent-encoded — the build, which interpolates `staticPaths`\n * values into a URL — or already decoded, as Hono's `c.req.path` is: it runs `decodeURI` over any path\n * containing a `%`. Decoded is also the form every other static file server addresses, so the tree stays\n * servable by something that is not this framework.\n *\n * Always `/`-separated: the same string addresses a file on a filesystem, which accepts forward slashes on\n * Windows too, and a key in an asset store, where a backslash would be the wrong character.\n *\n * `null` is everything that is not one file: a `.` or `..` segment — so traversal is a miss rather than a\n * lookup, which a store addressed by key, with no `resolve()` to fall back on, depends on — an empty segment,\n * and any segment holding a character a file name cannot.\n */\nexport function ssgFilePath(path: string, variant: PrerenderVariant = 'html'): string | null {\n const file = VARIANTS[variant].file;\n const trimmed = path.replace(/^\\/+|\\/+$/g, '');\n if (trimmed === '') return file;\n\n const segments: string[] = [];\n for (const encoded of trimmed.split('/')) {\n const segment = decodeSegment(encoded);\n if (!isStorableSegment(segment)) return null;\n segments.push(segment);\n }\n return `${segments.join('/')}/${file}`;\n}\n\n/**\n * {@link ssgFilePath} as a URL path, for a store addressed by one rather than by key. Every segment is escaped\n * on the way out and the store decodes it back, so a page reached through a URL lands on the same file a\n * filesystem target opens by name.\n */\nexport function ssgAssetPath(relPath: string): string {\n return relPath.split('/').map(encodeURIComponent).join('/');\n}\n\n/** The default cache budget: enough for a large documentation site's working set, small enough to be ignorable. */\nconst DEFAULT_CACHE_BYTES = 32 * 1024 * 1024;\n\n/**\n * A bounded, insertion-ordered cache of prerendered pages, so a site with thousands of them keeps a working set\n * rather than the whole build in memory. Only *hits* are stored — caching misses would let anyone mint entries\n * by requesting paths that don't exist — and the files never change while the server is up.\n *\n * Bounded by **bytes**, not by entry count: a count says nothing about how much memory it stands for, and a\n * page can be any size. At 128 entries — the previous limit — a documentation site with half-megabyte pages\n * retained ~64 MB across the two variants, with nothing about the number to suggest it.\n *\n * A page larger than the whole budget is served and not stored, rather than evicting everything to hold one.\n */\nexport function createPageCache(maxBytes = DEFAULT_CACHE_BYTES): {\n get(key: string): PrerenderedPage | undefined;\n set(key: string, page: PrerenderedPage): void;\n} {\n const pages = new Map<string, PrerenderedPage>();\n let bytes = 0;\n return {\n get: (key) => pages.get(key),\n set(key, page) {\n const size = page.body.byteLength;\n if (size > maxBytes) return;\n // Deleted before it is set, so re-setting an existing key moves it to the *back*: `Map.set` on a key it\n // already holds keeps that key's original position, which would leave the entry just stored sitting at\n // the front as the next thing evicted — a store whose eviction could drop the write that caused it.\n const replaced = pages.get(key);\n if (replaced) {\n bytes -= replaced.body.byteLength;\n pages.delete(key);\n }\n pages.set(key, page);\n bytes += size;\n // Insertion-ordered, so the first key is the oldest.\n while (bytes > maxBytes) {\n const oldest = pages.keys().next().value!;\n bytes -= pages.get(oldest)!.body.byteLength;\n pages.delete(oldest);\n }\n },\n };\n}\n\n/**\n * A weak `ETag` for a page body — see {@link PrerenderedPage.etag} for why weak. Web Crypto rather than\n * `node:crypto`, so one implementation serves both a Node server and `workerd`.\n */\nasync function weakEtag(body: Uint8Array<ArrayBuffer>): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', body);\n const base64url = btoa(String.fromCharCode(...new Uint8Array(digest)))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n return `W/\"${base64url.slice(0, 22)}\"`;\n}\n\n/**\n * Assembles a {@link PrerenderedPage} from a body just read out of the build. `storeEtag` is the validator the\n * store supplied, where it has one: it already describes these exact bytes, so it is weakened rather than\n * replaced by a fresh hash.\n */\nexport async function toPrerenderedPage(body: Uint8Array<ArrayBuffer>, storeEtag?: string | null): Promise<PrerenderedPage> {\n return {\n body,\n contentLength: String(body.byteLength),\n etag: storeEtag ? storeEtag.replace(/^(?!W\\/)/, 'W/') : await weakEtag(body),\n };\n}\n\n/** A prerendered page, ready to serve: its body and a validator derived from those exact bytes. */\nexport interface PrerenderedPage {\n /**\n * The document or the flight payload, depending on which {@link PrerenderVariant} was read. Bytes rather\n * than a string, because this cache entry is served verbatim to every request that hits it — as a string,\n * each would pay a fresh UTF-8 encode of the whole page.\n */\n body: Uint8Array<ArrayBuffer>;\n /**\n * `Content-Length` for {@link body}, in bytes. Served explicitly because Hono sets no length for an\n * in-memory body, and a proxy in front is entitled to make decisions with it.\n */\n contentLength: string;\n /**\n * `ETag` for the page, so a revalidating client can be answered with a 304 instead of the body.\n *\n * Deliberately **weak**: the bytes on the wire depend on whether something in front re-encoded them, so a\n * strong validator would differ per coding and a cache would treat the 200 and the 304 that revalidates it\n * as different pages. A weak tag says \"the same representation\", which stays true across codings.\n */\n etag: string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"prerendered.js","sourceRoot":"","sources":["../../src/server/prerendered.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Everything about a prerendered page that doesn't need a filesystem: where the build puts it, and what it\n * looks like once read back.\n *\n * Apart from `ssg.ts`, which does the reading and writing through `node:fs`, because a deploy target without a\n * filesystem reads the same layout out of an asset store — and importing it from there would drag `node:fs`\n * into a bundle that has neither.\n */\n\n/**\n * The two representations of a page, prerendered side by side: a hard load wants the HTML document, a soft\n * navigation asks the same URL for a flight payload. Without both, every in-app click would re-render a page\n * that was already built.\n */\nexport type PrerenderVariant = 'html' | 'flight';\n\nexport const VARIANTS = {\n // `headers` is what the build asks each representation for, so it stays the same request the browser makes:\n // a document is the default and a flight payload is the `RSC` header. See `runtime/request.ts`.\n html: { file: 'index.html', headers: {}, contentType: 'text/html' },\n flight: { file: 'index.rsc', headers: { RSC: '1' }, contentType: 'text/x-component' },\n} as const satisfies Record<PrerenderVariant, { file: string; headers: Record<string, string>; contentType: string }>;\n\n/**\n * How a build-time document render tells the prerender pass that the app minted a CSP nonce for its path.\n *\n * The other half of the same contract as {@link VARIANTS}, and it exists because the pass cannot see the\n * request it made: it renders through the app's own middleware inside the server bundle, in a module graph of\n * its own, and keeps the body alone. What it needs to know is whether a *request* for this path will mint a\n * nonce, because a document that will is one the framework re-renders per request — so the file the pass is\n * about to write is one no deployment will read, and saying \"prerendered\" about it is a lie by omission.\n *\n * Set only while `RSHONO_PRERENDER` is in the environment, which is `rshono build`'s own process, so it never\n * reaches a deployed response; and never stored, since only the body is.\n */\nexport const PRERENDER_NONCE_HEADER = 'x-rshono-prerender-nonce';\n\n/**\n * The index the build leaves beside the pages, naming every file it wrote — one `files` array of\n * {@link ssgFilePath} names.\n *\n * It exists so a reader can tell \"this store has no page for that path\" without asking the store, which is\n * the difference between a `Map` lookup and a failed `readFile` (or, without a filesystem, a failed store\n * fetch) on **every** request to a static route the build could not prerender — misses are deliberately not\n * cached, so that one never warms up. Absent is not an error: a build from an older rshono has none, and a\n * reader that finds none looks the way it always did.\n */\nexport const SSG_MANIFEST_FILE = 'manifest.json';\n\n/**\n * What a decoded path segment may not hold if it is to be one portable file name. `\\ / : * ? \" < > |` are\n * refused by Windows and `/` by every host, so a `staticPaths` value containing one fails the build rather\n * than writing a page on one machine and not on another — and a route pattern, whose params and wildcards are\n * spelled with `:` and `*`, falls out of the same rule.\n */\nconst UNPORTABLE_SEGMENT = /[\\\\/:*?\"<>|]/;\n\n/**\n * The DOS device names Windows still reserves, with or without an extension and whatever the case — so\n * `con`, `CON` and `Con.txt` are all the console. `mkdir` on one fails with `EINVAL`, naming a path the\n * author never wrote, which is why they are refused here with the rest.\n *\n * The list is Microsoft's, superscripts included: `CON PRN AUX NUL`, `COM0`–`COM9`, `COM¹ COM² COM³`, and\n * the same three suffixes for `LPT`. A documentation slug of `con` is a plausible thing to write and\n * builds fine on Linux today; refusing it everywhere is the same trade `:` and `*` already make — portable\n * or not at all, rather than a build that works on the author's machine and fails in CI on Windows.\n */\nconst RESERVED_DEVICE_NAME = /^(?:con|prn|aux|nul|com[0-9¹²³]|lpt[0-9¹²³])(?:\\..*)?$/i;\n\n/**\n * A trailing `.` or space, which Win32 **strips** rather than refusing: `docs/x.` is stored as `docs/x`, so\n * the page is written where no reader will look for it — \"a page the build reports as prerendered and\n * nothing ever serves\", which is the failure {@link ssgFilePath} exists to prevent. The silent half of this\n * rule, and the more important one: the reserved names above at least fail loudly.\n */\nconst TRAILING_STRIPPED = /[. ]$/;\n\n/**\n * Decodes one path segment the way a URL does, treating a malformed escape as the literal text it is:\n * `decodeURIComponent('%')` throws, and a request path is whatever the client chose to send.\n */\nfunction decodeSegment(segment: string): string {\n try {\n return decodeURIComponent(segment);\n } catch {\n return segment;\n }\n}\n\n/**\n * Whether one decoded segment can be a directory in the prerender tree, on every host rather than on this\n * one: not empty, not a relative-path marker, made only of characters a file name can hold\n * ({@link UNPORTABLE_SEGMENT}, plus the control characters, which a percent-escape is free to carry into a\n * path and no filesystem wants in a name), not a {@link RESERVED_DEVICE_NAME}, and not ending in a\n * character Win32 would {@link TRAILING_STRIPPED | strip}.\n *\n * The last two are Windows rules that no macOS or Linux build can discover for itself — which is exactly\n * why they belong in a check rather than in a caveat.\n */\nfunction isStorableSegment(segment: string): boolean {\n if (segment === '' || segment === '.' || segment === '..') return false;\n if (RESERVED_DEVICE_NAME.test(segment) || TRAILING_STRIPPED.test(segment)) return false;\n return !UNPORTABLE_SEGMENT.test(segment) && ![...segment].some((char) => char < ' ');\n}\n\n/**\n * Where a path's prerendered output lives, relative to the output root — or `null` for a path no single file\n * can answer.\n *\n * **The one canonical form**, shared by the build that writes the file and by every deploy target that reads\n * it back, because the two drifting apart is invisible: the build reports the page as prerendered and every\n * request afterwards falls through to SSR, forever. Each segment is stored *decoded*, so `/docs/café` is\n * `docs/café` whether the caller holds the path percent-encoded — the build, which interpolates `staticPaths`\n * values into a URL — or already decoded, as Hono's `c.req.path` is: it runs `decodeURI` over any path\n * containing a `%`. Decoded is also the form every other static file server addresses, so the tree stays\n * servable by something that is not this framework.\n *\n * Always `/`-separated: the same string addresses a file on a filesystem, which accepts forward slashes on\n * Windows too, and a key in an asset store, where a backslash would be the wrong character.\n *\n * `null` is everything that is not one file: a `.` or `..` segment — so traversal is a miss rather than a\n * lookup, which a store addressed by key, with no `resolve()` to fall back on, depends on — an empty segment,\n * and any segment {@link isStorableSegment} refuses as unportable.\n */\nexport function ssgFilePath(path: string, variant: PrerenderVariant = 'html'): string | null {\n const file = VARIANTS[variant].file;\n const trimmed = path.replace(/^\\/+|\\/+$/g, '');\n if (trimmed === '') return file;\n\n const segments: string[] = [];\n for (const encoded of trimmed.split('/')) {\n const segment = decodeSegment(encoded);\n if (!isStorableSegment(segment)) return null;\n segments.push(segment);\n }\n return `${segments.join('/')}/${file}`;\n}\n\n/**\n * {@link ssgFilePath} as a URL path, for a store addressed by one rather than by key. Every segment is escaped\n * on the way out and the store decodes it back, so a page reached through a URL lands on the same file a\n * filesystem target opens by name.\n */\nexport function ssgAssetPath(relPath: string): string {\n return relPath.split('/').map(encodeURIComponent).join('/');\n}\n\n/** The default cache budget: enough for a large documentation site's working set, small enough to be ignorable. */\nconst DEFAULT_CACHE_BYTES = 32 * 1024 * 1024;\n\n/**\n * A bounded, insertion-ordered cache of prerendered pages, so a site with thousands of them keeps a working set\n * rather than the whole build in memory. Only *hits* are stored — caching misses would let anyone mint entries\n * by requesting paths that don't exist — and the files never change while the server is up.\n *\n * Bounded by **bytes**, not by entry count: a count says nothing about how much memory it stands for, and a\n * page can be any size. At 128 entries — the previous limit — a documentation site with half-megabyte pages\n * retained ~64 MB across the two variants, with nothing about the number to suggest it.\n *\n * A page larger than the whole budget is served and not stored, rather than evicting everything to hold one.\n */\nexport function createPageCache(maxBytes = DEFAULT_CACHE_BYTES): {\n get(key: string): PrerenderedPage | undefined;\n set(key: string, page: PrerenderedPage): void;\n} {\n const pages = new Map<string, PrerenderedPage>();\n let bytes = 0;\n return {\n get: (key) => pages.get(key),\n set(key, page) {\n const size = page.body.byteLength;\n if (size > maxBytes) return;\n // Deleted before it is set, so re-setting an existing key moves it to the *back*: `Map.set` on a key it\n // already holds keeps that key's original position, which would leave the entry just stored sitting at\n // the front as the next thing evicted — a store whose eviction could drop the write that caused it.\n const replaced = pages.get(key);\n if (replaced) {\n bytes -= replaced.body.byteLength;\n pages.delete(key);\n }\n pages.set(key, page);\n bytes += size;\n // Insertion-ordered, so the first key is the oldest.\n while (bytes > maxBytes) {\n const oldest = pages.keys().next().value!;\n bytes -= pages.get(oldest)!.body.byteLength;\n pages.delete(oldest);\n }\n },\n };\n}\n\n/**\n * A weak `ETag` for a page body — see {@link PrerenderedPage.etag} for why weak. Web Crypto rather than\n * `node:crypto`, so one implementation serves both a Node server and `workerd`.\n */\nasync function weakEtag(body: Uint8Array<ArrayBuffer>): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', body);\n const base64url = btoa(String.fromCharCode(...new Uint8Array(digest)))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n return `W/\"${base64url.slice(0, 22)}\"`;\n}\n\n/**\n * Assembles a {@link PrerenderedPage} from a body just read out of the build. `storeEtag` is the validator the\n * store supplied, where it has one: it already describes these exact bytes, so it is weakened rather than\n * replaced by a fresh hash.\n */\nexport async function toPrerenderedPage(body: Uint8Array<ArrayBuffer>, storeEtag?: string | null): Promise<PrerenderedPage> {\n return {\n body,\n contentLength: String(body.byteLength),\n etag: storeEtag ? storeEtag.replace(/^(?!W\\/)/, 'W/') : await weakEtag(body),\n };\n}\n\n/** A prerendered page, ready to serve: its body and a validator derived from those exact bytes. */\nexport interface PrerenderedPage {\n /**\n * The document or the flight payload, depending on which {@link PrerenderVariant} was read. Bytes rather\n * than a string, because this cache entry is served verbatim to every request that hits it — as a string,\n * each would pay a fresh UTF-8 encode of the whole page.\n */\n body: Uint8Array<ArrayBuffer>;\n /**\n * `Content-Length` for {@link body}, in bytes. Served explicitly because Hono sets no length for an\n * in-memory body, and a proxy in front is entitled to make decisions with it.\n */\n contentLength: string;\n /**\n * `ETag` for the page, so a revalidating client can be answered with a 304 instead of the body.\n *\n * Deliberately **weak**: the bytes on the wire depend on whether something in front re-encoded them, so a\n * strong validator would differ per coding and a cache would treat the 200 and the 304 that revalidates it\n * as different pages. A weak tag says \"the same representation\", which stays true across codings.\n */\n etag: string;\n}\n"]}
|
package/dist/server/ssg.d.ts
CHANGED
|
@@ -16,7 +16,12 @@ interface PrerenderOptions {
|
|
|
16
16
|
export interface PrerenderResult {
|
|
17
17
|
written: string[];
|
|
18
18
|
skipped: string[];
|
|
19
|
+
/**
|
|
20
|
+
* The subset of {@link written} whose *document* the deployment will not serve, because the app mints a CSP
|
|
21
|
+
* nonce on that path — see {@link PRERENDER_NONCE_HEADER}. Their flight payloads are served from disk like
|
|
22
|
+
* any other, so these are half-prerendered pages rather than skipped ones, and the caller says so.
|
|
23
|
+
*/
|
|
24
|
+
flightOnly: string[];
|
|
19
25
|
}
|
|
20
26
|
export declare function prerenderStaticRoutes(options: PrerenderOptions): Promise<PrerenderResult>;
|
|
21
27
|
export {};
|
|
22
|
-
//# sourceMappingURL=ssg.d.ts.map
|