@rshono/core 1.0.0-rc.0
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 +273 -0
- package/bin/rshono.mjs +15 -0
- package/dist/builder/env-shadow-loader.cjs +26 -0
- package/dist/builder/page-entry-loader.cjs +7 -0
- package/dist/builder/page-files.d.ts +2 -0
- package/dist/builder/page-files.d.ts.map +1 -0
- package/dist/builder/page-files.js +45 -0
- package/dist/builder/page-files.js.map +1 -0
- package/dist/builder/public-env.d.ts +2 -0
- package/dist/builder/public-env.d.ts.map +1 -0
- package/dist/builder/public-env.js +5 -0
- package/dist/builder/public-env.js.map +1 -0
- package/dist/builder/rspack-config.d.ts +14 -0
- package/dist/builder/rspack-config.d.ts.map +1 -0
- package/dist/builder/rspack-config.js +235 -0
- package/dist/builder/rspack-config.js.map +1 -0
- package/dist/cli/build.d.ts +11 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/build.js +64 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/dev.d.ts +9 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +210 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +77 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/start.d.ts +8 -0
- package/dist/cli/start.d.ts.map +1 -0
- package/dist/cli/start.js +39 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/config.d.ts +121 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -0
- package/dist/deploy/aws-lambda/runtime.d.ts +14 -0
- package/dist/deploy/aws-lambda/runtime.d.ts.map +1 -0
- package/dist/deploy/aws-lambda/runtime.js +24 -0
- package/dist/deploy/aws-lambda/runtime.js.map +1 -0
- package/dist/deploy/build-marker.d.ts +15 -0
- package/dist/deploy/build-marker.d.ts.map +1 -0
- package/dist/deploy/build-marker.js +26 -0
- package/dist/deploy/build-marker.js.map +1 -0
- package/dist/deploy/bun/runtime.d.ts +11 -0
- package/dist/deploy/bun/runtime.d.ts.map +1 -0
- package/dist/deploy/bun/runtime.js +22 -0
- package/dist/deploy/bun/runtime.js.map +1 -0
- package/dist/deploy/cloudflare/build.d.ts +7 -0
- package/dist/deploy/cloudflare/build.d.ts.map +1 -0
- package/dist/deploy/cloudflare/build.js +74 -0
- package/dist/deploy/cloudflare/build.js.map +1 -0
- package/dist/deploy/cloudflare/runtime.d.ts +10 -0
- package/dist/deploy/cloudflare/runtime.d.ts.map +1 -0
- package/dist/deploy/cloudflare/runtime.js +129 -0
- package/dist/deploy/cloudflare/runtime.js.map +1 -0
- package/dist/deploy/contract.d.ts +65 -0
- package/dist/deploy/contract.d.ts.map +1 -0
- package/dist/deploy/contract.js +2 -0
- package/dist/deploy/contract.js.map +1 -0
- package/dist/deploy/deno/runtime.d.ts +11 -0
- package/dist/deploy/deno/runtime.d.ts.map +1 -0
- package/dist/deploy/deno/runtime.js +16 -0
- package/dist/deploy/deno/runtime.js.map +1 -0
- package/dist/deploy/filesystem.d.ts +10 -0
- package/dist/deploy/filesystem.d.ts.map +1 -0
- package/dist/deploy/filesystem.js +47 -0
- package/dist/deploy/filesystem.js.map +1 -0
- package/dist/deploy/listen.d.ts +20 -0
- package/dist/deploy/listen.d.ts.map +1 -0
- package/dist/deploy/listen.js +24 -0
- package/dist/deploy/listen.js.map +1 -0
- package/dist/deploy/netlify/build.d.ts +8 -0
- package/dist/deploy/netlify/build.d.ts.map +1 -0
- package/dist/deploy/netlify/build.js +52 -0
- package/dist/deploy/netlify/build.js.map +1 -0
- package/dist/deploy/netlify/runtime.d.ts +13 -0
- package/dist/deploy/netlify/runtime.d.ts.map +1 -0
- package/dist/deploy/netlify/runtime.js +24 -0
- package/dist/deploy/netlify/runtime.js.map +1 -0
- package/dist/deploy/node/runtime.d.ts +7 -0
- package/dist/deploy/node/runtime.d.ts.map +1 -0
- package/dist/deploy/node/runtime.js +36 -0
- package/dist/deploy/node/runtime.js.map +1 -0
- package/dist/deploy/presets.d.ts +88 -0
- package/dist/deploy/presets.d.ts.map +1 -0
- package/dist/deploy/presets.js +110 -0
- package/dist/deploy/presets.js.map +1 -0
- package/dist/deploy/vercel/build.d.ts +3 -0
- package/dist/deploy/vercel/build.d.ts.map +1 -0
- package/dist/deploy/vercel/build.js +56 -0
- package/dist/deploy/vercel/build.js.map +1 -0
- package/dist/deploy/vercel/runtime.d.ts +14 -0
- package/dist/deploy/vercel/runtime.d.ts.map +1 -0
- package/dist/deploy/vercel/runtime.js +26 -0
- package/dist/deploy/vercel/runtime.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/router.d.ts +337 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +18 -0
- package/dist/router.js.map +1 -0
- package/dist/runtime/boundaries.d.ts +102 -0
- package/dist/runtime/boundaries.d.ts.map +1 -0
- package/dist/runtime/boundaries.js +101 -0
- package/dist/runtime/boundaries.js.map +1 -0
- package/dist/runtime/client.d.ts +16 -0
- package/dist/runtime/client.d.ts.map +1 -0
- package/dist/runtime/client.js +16 -0
- package/dist/runtime/client.js.map +1 -0
- package/dist/runtime/context.d.ts +271 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +360 -0
- package/dist/runtime/context.js.map +1 -0
- package/dist/runtime/control.d.ts +18 -0
- package/dist/runtime/control.d.ts.map +1 -0
- package/dist/runtime/control.js +39 -0
- package/dist/runtime/control.js.map +1 -0
- package/dist/runtime/dev-protocol.d.ts +22 -0
- package/dist/runtime/dev-protocol.d.ts.map +1 -0
- package/dist/runtime/dev-protocol.js +2 -0
- package/dist/runtime/dev-protocol.js.map +1 -0
- package/dist/runtime/empty-server-app.d.ts +3 -0
- package/dist/runtime/empty-server-app.d.ts.map +1 -0
- package/dist/runtime/empty-server-app.js +2 -0
- package/dist/runtime/empty-server-app.js.map +1 -0
- package/dist/runtime/entry.client.d.ts +2 -0
- package/dist/runtime/entry.client.d.ts.map +1 -0
- package/dist/runtime/entry.client.js +453 -0
- package/dist/runtime/entry.client.js.map +1 -0
- package/dist/runtime/entry.rsc.d.ts +32 -0
- package/dist/runtime/entry.rsc.d.ts.map +1 -0
- package/dist/runtime/entry.rsc.js +466 -0
- package/dist/runtime/entry.rsc.js.map +1 -0
- package/dist/runtime/entry.ssr.d.ts +25 -0
- package/dist/runtime/entry.ssr.d.ts.map +1 -0
- package/dist/runtime/entry.ssr.js +86 -0
- package/dist/runtime/entry.ssr.js.map +1 -0
- package/dist/runtime/navigation.d.ts +114 -0
- package/dist/runtime/navigation.d.ts.map +1 -0
- package/dist/runtime/navigation.js +112 -0
- package/dist/runtime/navigation.js.map +1 -0
- package/dist/runtime/request.d.ts +35 -0
- package/dist/runtime/request.d.ts.map +1 -0
- package/dist/runtime/request.js +39 -0
- package/dist/runtime/request.js.map +1 -0
- package/dist/runtime/server.d.ts +24 -0
- package/dist/runtime/server.d.ts.map +1 -0
- package/dist/runtime/server.js +24 -0
- package/dist/runtime/server.js.map +1 -0
- package/dist/server/compress.d.ts +15 -0
- package/dist/server/compress.d.ts.map +1 -0
- package/dist/server/compress.js +76 -0
- package/dist/server/compress.js.map +1 -0
- package/dist/server/headers.d.ts +24 -0
- package/dist/server/headers.d.ts.map +1 -0
- package/dist/server/headers.js +44 -0
- package/dist/server/headers.js.map +1 -0
- package/dist/server/load-config.d.ts +7 -0
- package/dist/server/load-config.d.ts.map +1 -0
- package/dist/server/load-config.js +41 -0
- package/dist/server/load-config.js.map +1 -0
- package/dist/server/load-env.d.ts +2 -0
- package/dist/server/load-env.d.ts.map +1 -0
- package/dist/server/load-env.js +10 -0
- package/dist/server/load-env.js.map +1 -0
- package/dist/server/prerendered.d.ts +67 -0
- package/dist/server/prerendered.d.ts.map +1 -0
- package/dist/server/prerendered.js +39 -0
- package/dist/server/prerendered.js.map +1 -0
- package/dist/server/server-config.d.ts +72 -0
- package/dist/server/server-config.d.ts.map +1 -0
- package/dist/server/server-config.js +94 -0
- package/dist/server/server-config.js.map +1 -0
- package/dist/server/shutdown.d.ts +7 -0
- package/dist/server/shutdown.d.ts.map +1 -0
- package/dist/server/shutdown.js +11 -0
- package/dist/server/shutdown.js.map +1 -0
- package/dist/server/ssg.d.ts +25 -0
- package/dist/server/ssg.d.ts.map +1 -0
- package/dist/server/ssg.js +160 -0
- package/dist/server/ssg.js.map +1 -0
- package/dist/server/static.d.ts +14 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +30 -0
- package/dist/server/static.js.map +1 -0
- package/package.json +86 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/server/server-config.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RSHonoConfig } from '../config.js';\n\n/**\n * The framework settings the server bundle needs at request time, fully resolved\n * (defaults applied, `bodySizeLimit` parsed to bytes, origins normalized to hosts).\n *\n * The value is produced once by {@link resolveServerConfig} from `rshono.config.ts`\n * and compiled into the server bundle as the `__RSHONO_CONFIG__` literal (see\n * `builder/rspack-config.ts`) — there is no runtime env-var interface for these.\n */\nexport interface ServerConfig {\n /**\n * `true` when the bundle came from `rshono dev`.\n *\n * Baked in rather than read from `process.env.NODE_ENV` at runtime: it is decided by which command\n * produced the bundle, and a deploy target need not have a `process` to read it from.\n */\n isDev: boolean;\n /** Deadline in ms for a single request (server action + flight + SSR). */\n renderTimeoutMs: number;\n /** Honour `X-Forwarded-Host` / `-Proto` when resolving the browser-facing URL. Forced on in dev. */\n trustProxy: boolean;\n /** Send a strict per-request-nonce Content-Security-Policy with every HTML document. */\n cspEnabled: boolean;\n /** The resolved CSP directives (built-in defaults with the user's merged over them), minus the nonce. */\n cspDirectives: Record<string, string>;\n /** CSRF origin check on server-action POSTs. */\n checkOrigin: boolean;\n /** Extra origins allowed to post server actions, normalized to lowercase `URL.host` values. */\n allowedOrigins: string[];\n /** Max request body in bytes before a 413; `0` disables the cap. */\n maxBodyBytes: number;\n /** Gzip compressible responses on the way out. */\n compress: boolean;\n /** Default listen port for `start` (overridden by `PORT`). */\n port?: number;\n /** Default bind address for `start` (overridden by `HOST`). */\n host?: string;\n}\n\n/**\n * The single source of truth for the framework's built-in defaults.\n *\n * `renderTimeoutMs` and `maxBodyBytes` are resolved here, into the bundle. `port` and `host` are not:\n * they stay env-overridable at runtime, so the default is applied where the address is resolved\n * (`deploy/listen.ts` for a server bundle, `cli/dev.ts` for the dev server) and this is what both read.\n */\nexport const SERVER_DEFAULTS = {\n renderTimeoutMs: 10_000,\n maxBodyBytes: 1024 * 1024, // 1 MiB, matching Next.js's server-action body-size limit.\n port: 3000,\n host: '0.0.0.0',\n} as const;\n\n/**\n * The built-in {@link RSHonoConfig.csp} policy, keyed by directive so\n * {@link RSHonoConfig.cspDirectives} can override entries individually.\n *\n * `script-src` carries the per-request nonce, appended at request time (see `entry.rsc.tsx`).\n * `style-src` needs `'unsafe-inline'` because React writes inline styles.\n */\nexport const CSP_DEFAULTS: Record<string, string> = {\n 'default-src': \"'self'\",\n 'script-src': \"'self'\",\n 'style-src': \"'self' 'unsafe-inline'\",\n 'img-src': \"'self' data:\",\n 'connect-src': \"'self'\",\n // Not covered by default-src, and each closes an injection route of its own: a stray <base>\n // retargeting every relative URL, plugin content, framing (clickjacking), off-site form posts.\n 'base-uri': \"'self'\",\n 'object-src': \"'none'\",\n 'frame-ancestors': \"'none'\",\n 'form-action': \"'self'\",\n};\n\nconst UNITS: Record<string, number> = { b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3 };\n\n/** Parse a {@link RSHonoConfig.bodySizeLimit} value into a byte count (`false`/`0` → `0`, disabling the cap). */\nexport function parseByteSize(value: string | number | false | undefined): number | undefined {\n if (value === undefined) return undefined;\n if (value === false) return 0;\n if (typeof value === 'number') return value;\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*(b|kb|mb|gb)?\\s*$/i.exec(value);\n if (!match) {\n throw new Error(`[rshono] invalid bodySizeLimit ${JSON.stringify(value)} — use e.g. '1mb', 1048576, or false.`);\n }\n return Math.floor(Number(match[1]) * UNITS[(match[2] ?? 'b').toLowerCase()]);\n}\n\n/**\n * Normalize a config `allowedOrigins` entry (full origin or bare host) to a lowercase `URL.host`,\n * ready for a direct comparison against a parsed `Origin` header's host.\n *\n * A bare `host:port` has to be retried against a base, because on its own `'localhost:4000'`\n * parses as the *scheme* `localhost:` with path `4000` — leaving an empty host that would\n * silently never match anything. Throws rather than passing a junk entry through, so a typo\n * fails the build instead of quietly disabling the allowlist entry it was meant to add.\n */\nfunction normalizeOrigin(entry: string): string {\n const host = URL.parse(entry)?.host || URL.parse(`http://${entry}`)?.host;\n if (!host) {\n throw new Error(\n `[rshono] invalid allowedOrigins entry ${JSON.stringify(entry)} — use a full origin ('https://admin.example.com') or a bare host ('localhost:4000').`,\n );\n }\n return host.toLowerCase();\n}\n\n/** Drop directives the user blanked out, so `cspDirectives: { 'frame-ancestors': '' }` removes one. */\nfunction resolveCspDirectives(overrides: Record<string, string> | undefined): Record<string, string> {\n return Object.fromEntries(Object.entries({ ...CSP_DEFAULTS, ...overrides }).filter(([, value]) => value.trim() !== ''));\n}\n\n/**\n * Resolve the user's {@link RSHonoConfig} into the {@link ServerConfig} baked into the bundle.\n *\n * `isDev` is a build-time input rather than a config field because it decides one thing the user\n * shouldn't have to: `trustProxy` is forced on under `rshono dev`, where the framework's own proxy\n * is the only way in (it sets the forwarded headers itself and binds to localhost).\n */\nexport function resolveServerConfig(config: RSHonoConfig, { isDev }: { isDev: boolean }): ServerConfig {\n return {\n isDev,\n renderTimeoutMs: config.renderTimeout ?? SERVER_DEFAULTS.renderTimeoutMs,\n trustProxy: isDev || (config.trustProxy ?? false),\n cspEnabled: config.csp ?? false,\n cspDirectives: resolveCspDirectives(config.cspDirectives),\n checkOrigin: config.checkOrigin ?? true,\n allowedOrigins: (config.allowedOrigins ?? [])\n .map((entry) => entry.trim())\n .filter(Boolean)\n .map(normalizeOrigin),\n maxBodyBytes: parseByteSize(config.bodySizeLimit) ?? SERVER_DEFAULTS.maxBodyBytes,\n compress: config.compress ?? true,\n port: config.port,\n host: config.host,\n };\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers `handler` for both `SIGINT` and `SIGTERM` — the two signals a process
|
|
3
|
+
* manager or `Ctrl-C` uses to ask for a graceful stop. Consolidates the shutdown
|
|
4
|
+
* wiring shared by the server bundle and the `start` launcher.
|
|
5
|
+
*/
|
|
6
|
+
export declare function onShutdown(handler: (signal: NodeJS.Signals) => void): void;
|
|
7
|
+
//# sourceMappingURL=shutdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../src/server/shutdown.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,GAAG,IAAI,CAI1E"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers `handler` for both `SIGINT` and `SIGTERM` — the two signals a process
|
|
3
|
+
* manager or `Ctrl-C` uses to ask for a graceful stop. Consolidates the shutdown
|
|
4
|
+
* wiring shared by the server bundle and the `start` launcher.
|
|
5
|
+
*/
|
|
6
|
+
export function onShutdown(handler) {
|
|
7
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
8
|
+
process.on(signal, () => handler(signal));
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=shutdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.js","sourceRoot":"","sources":["../../src/server/shutdown.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,OAAyC;IAClE,KAAK,MAAM,MAAM,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAU,EAAE,CAAC;QACpD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC","sourcesContent":["/**\n * Registers `handler` for both `SIGINT` and `SIGTERM` — the two signals a process\n * manager or `Ctrl-C` uses to ask for a graceful stop. Consolidates the shutdown\n * wiring shared by the server bundle and the `start` launcher.\n */\nexport function onShutdown(handler: (signal: NodeJS.Signals) => void): void {\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.on(signal, () => handler(signal));\n }\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Route } from '../router.js';
|
|
2
|
+
import { type PrerenderedPage, type PrerenderVariant } from './prerendered.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve {@link RSHonoConfig.siteUrl} to the origin prerendering should render against.
|
|
5
|
+
*
|
|
6
|
+
* A path is rejected rather than dropped: `'https://example.com/docs'` almost certainly means the
|
|
7
|
+
* author expects a base path, and silently serving from the root would be a confusing way to find
|
|
8
|
+
* out that isn't supported.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveSiteOrigin(siteUrl: string | undefined): string;
|
|
11
|
+
export declare function readPrerendered(ssgDir: string, requestPath: string, variant?: PrerenderVariant): Promise<PrerenderedPage | null>;
|
|
12
|
+
interface PrerenderOptions {
|
|
13
|
+
routes: readonly Route[];
|
|
14
|
+
fetch: (request: Request) => Response | Promise<Response>;
|
|
15
|
+
ssgDir: string;
|
|
16
|
+
/** {@link RSHonoConfig.siteUrl} — the origin absolute URLs in the output are built against. */
|
|
17
|
+
siteUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface PrerenderResult {
|
|
20
|
+
written: string[];
|
|
21
|
+
skipped: string[];
|
|
22
|
+
}
|
|
23
|
+
export declare function prerenderStaticRoutes(options: PrerenderOptions): Promise<PrerenderResult>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=ssg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssg.d.ts","sourceRoot":"","sources":["../../src/server/ssg.ts"],"names":[],"mappings":"AAIA,OAAO,EAA+B,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAA6C,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAS1H;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAUrE;AAoCD,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAyB,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAgC9I;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IACzB,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAcD,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CA2D/F"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
5
|
+
import { isPageRoute } from '../router.js';
|
|
6
|
+
import { prerenderedRelPath, ssgFilePath, VARIANTS } from './prerendered.js';
|
|
7
|
+
/**
|
|
8
|
+
* Stand-in origin for a build that didn't declare {@link RSHonoConfig.siteUrl}. Deliberately
|
|
9
|
+
* obviously-wrong rather than a guess: a page that bakes this into a canonical tag should be easy
|
|
10
|
+
* to spot, and the build warns when static routes are prerendered without a real origin.
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_SSG_ORIGIN = 'http://localhost';
|
|
13
|
+
/**
|
|
14
|
+
* Resolve {@link RSHonoConfig.siteUrl} to the origin prerendering should render against.
|
|
15
|
+
*
|
|
16
|
+
* A path is rejected rather than dropped: `'https://example.com/docs'` almost certainly means the
|
|
17
|
+
* author expects a base path, and silently serving from the root would be a confusing way to find
|
|
18
|
+
* out that isn't supported.
|
|
19
|
+
*/
|
|
20
|
+
export function resolveSiteOrigin(siteUrl) {
|
|
21
|
+
if (!siteUrl)
|
|
22
|
+
return DEFAULT_SSG_ORIGIN;
|
|
23
|
+
const parsed = URL.parse(siteUrl);
|
|
24
|
+
if (!parsed || (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')) {
|
|
25
|
+
throw new Error(`[rshono] invalid siteUrl ${JSON.stringify(siteUrl)} — use a full origin, e.g. 'https://example.com'.`);
|
|
26
|
+
}
|
|
27
|
+
if (parsed.pathname !== '/' || parsed.search || parsed.hash) {
|
|
28
|
+
throw new Error(`[rshono] siteUrl ${JSON.stringify(siteUrl)} must be a bare origin — a base path is not supported.`);
|
|
29
|
+
}
|
|
30
|
+
return parsed.origin;
|
|
31
|
+
}
|
|
32
|
+
function interpolatePath(pattern, params) {
|
|
33
|
+
return pattern
|
|
34
|
+
.split('/')
|
|
35
|
+
.map((segment) => {
|
|
36
|
+
if (!segment.startsWith(':')) {
|
|
37
|
+
if (segment.includes('*')) {
|
|
38
|
+
throw new Error(`Cannot prerender "${pattern}": wildcard segments are not supported by staticPaths.`);
|
|
39
|
+
}
|
|
40
|
+
return segment;
|
|
41
|
+
}
|
|
42
|
+
const name = segment.slice(1);
|
|
43
|
+
if (!/^\w+$/.test(name)) {
|
|
44
|
+
throw new Error(`Cannot prerender "${pattern}": optional/regex params are not supported by staticPaths.`);
|
|
45
|
+
}
|
|
46
|
+
const value = params[name];
|
|
47
|
+
if (value === undefined) {
|
|
48
|
+
throw new Error(`staticPaths for "${pattern}" returned a param set without "${name}".`);
|
|
49
|
+
}
|
|
50
|
+
return encodeURIComponent(value);
|
|
51
|
+
})
|
|
52
|
+
.join('/');
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Prerendered pages, keyed by the request that produced them (see {@link readPrerendered}).
|
|
56
|
+
*
|
|
57
|
+
* Bounded so a site with thousands of prerendered pages keeps a working set rather than the whole
|
|
58
|
+
* build in memory. Only *hits* are cached: caching misses would let anyone mint entries by
|
|
59
|
+
* requesting paths that don't exist. The files are written at build time and never change while
|
|
60
|
+
* the server is up, so an entry never needs invalidating.
|
|
61
|
+
*/
|
|
62
|
+
const pageCache = new Map();
|
|
63
|
+
const MAX_CACHED_PAGES = 128;
|
|
64
|
+
export async function readPrerendered(ssgDir, requestPath, variant = 'html') {
|
|
65
|
+
// Keyed by what the request carried, not by the resolved filename, so a hit costs one Map lookup
|
|
66
|
+
// instead of re-deriving the path every time. Safe because only *hits* are cached: an entry
|
|
67
|
+
// exists only if this exact key already passed the checks below.
|
|
68
|
+
const key = `${ssgDir}\0${variant}\0${requestPath}`;
|
|
69
|
+
const cached = pageCache.get(key);
|
|
70
|
+
if (cached)
|
|
71
|
+
return cached;
|
|
72
|
+
const relPath = prerenderedRelPath(requestPath, variant);
|
|
73
|
+
if (relPath === null)
|
|
74
|
+
return null;
|
|
75
|
+
// Belt and braces: the shared guard already refused a traversal, and this proves the resolved file
|
|
76
|
+
// is under the root whatever else the path contained.
|
|
77
|
+
const root = resolve(ssgDir);
|
|
78
|
+
const file = resolve(root, relPath);
|
|
79
|
+
if (!file.startsWith(root + sep))
|
|
80
|
+
return null;
|
|
81
|
+
let body;
|
|
82
|
+
try {
|
|
83
|
+
body = await readFile(file, 'utf8');
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const page = {
|
|
89
|
+
body,
|
|
90
|
+
contentLength: String(Buffer.byteLength(body)),
|
|
91
|
+
etag: `W/"${createHash('sha256').update(body).digest('base64url').slice(0, 22)}"`,
|
|
92
|
+
};
|
|
93
|
+
pageCache.set(key, page);
|
|
94
|
+
// Insertion-ordered, so the first key is the oldest. One entry in means at most one out.
|
|
95
|
+
if (pageCache.size > MAX_CACHED_PAGES)
|
|
96
|
+
pageCache.delete(pageCache.keys().next().value);
|
|
97
|
+
return page;
|
|
98
|
+
}
|
|
99
|
+
/** Renders one representation of a path, or `null` if the app didn't answer with it. */
|
|
100
|
+
async function renderVariant(fetch, url, variant) {
|
|
101
|
+
const response = await fetch(new Request(url, { headers: { Accept: VARIANTS[variant].accept } }));
|
|
102
|
+
if (response.status !== 200)
|
|
103
|
+
return { status: response.status };
|
|
104
|
+
if (!(response.headers.get('Content-Type') ?? '').includes(VARIANTS[variant].contentType))
|
|
105
|
+
return null;
|
|
106
|
+
return { body: await response.text() };
|
|
107
|
+
}
|
|
108
|
+
export async function prerenderStaticRoutes(options) {
|
|
109
|
+
const { routes, fetch, ssgDir } = options;
|
|
110
|
+
const staticRoutes = routes.filter((r) => isPageRoute(r) && r.render === 'static');
|
|
111
|
+
const origin = resolveSiteOrigin(options.siteUrl);
|
|
112
|
+
if (staticRoutes.length > 0 && !options.siteUrl) {
|
|
113
|
+
console.warn(` ⚠ No siteUrl in rshono.config — prerendered pages are built against ${DEFAULT_SSG_ORIGIN}, so any absolute URL\n` +
|
|
114
|
+
` they derive from a page's \`url\` prop (canonical tags, og:url, absolute links) will point there.`);
|
|
115
|
+
}
|
|
116
|
+
const written = [];
|
|
117
|
+
const skipped = [];
|
|
118
|
+
for (const route of staticRoutes) {
|
|
119
|
+
let paths;
|
|
120
|
+
if (!/[:*]/.test(route.path)) {
|
|
121
|
+
paths = [route.path];
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
if (!route.staticPaths) {
|
|
125
|
+
console.warn(` ⚠ Static route "${route.path}" has params but no staticPaths — will SSR per request.`);
|
|
126
|
+
skipped.push(route.path);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
paths = (await route.staticPaths()).map((params) => interpolatePath(route.path, params));
|
|
130
|
+
}
|
|
131
|
+
for (const path of paths) {
|
|
132
|
+
const document = await renderVariant(fetch, origin + path, 'html');
|
|
133
|
+
if (document === null || !('body' in document)) {
|
|
134
|
+
const rendered = document === null ? 'a non-HTML response' : `${document.status}`;
|
|
135
|
+
console.warn(` ⚠ "${path}" rendered ${rendered} at build time — skipping, will SSR per request.`);
|
|
136
|
+
skipped.push(path);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const write = (variant, body) => {
|
|
140
|
+
const file = join(ssgDir, ssgFilePath(path, variant));
|
|
141
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
142
|
+
writeFileSync(file, body);
|
|
143
|
+
};
|
|
144
|
+
write('html', document.body);
|
|
145
|
+
// The soft-navigation representation of the same page. Best-effort: if it doesn't come back
|
|
146
|
+
// cleanly the document is still valid on its own, and serving falls back to rendering flight
|
|
147
|
+
// per request — the behaviour before this was written at all.
|
|
148
|
+
const flight = await renderVariant(fetch, origin + path, 'flight');
|
|
149
|
+
if (flight !== null && 'body' in flight) {
|
|
150
|
+
write('flight', flight.body);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.warn(` ⚠ "${path}" produced no flight payload — soft navigations to it will render per request.`);
|
|
154
|
+
}
|
|
155
|
+
written.push(path);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return { written, skipped };
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=ssg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssg.js","sourceRoot":"","sources":["../../src/server/ssg.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname, join, resolve, sep } from 'node:path';\nimport { isPageRoute, type PageRoute, type Route } from '../router.js';\nimport { prerenderedRelPath, ssgFilePath, VARIANTS, type PrerenderedPage, type PrerenderVariant } from './prerendered.js';\n\n/**\n * Stand-in origin for a build that didn't declare {@link RSHonoConfig.siteUrl}. Deliberately\n * obviously-wrong rather than a guess: a page that bakes this into a canonical tag should be easy\n * to spot, and the build warns when static routes are prerendered without a real origin.\n */\nconst DEFAULT_SSG_ORIGIN = 'http://localhost';\n\n/**\n * Resolve {@link RSHonoConfig.siteUrl} to the origin prerendering should render against.\n *\n * A path is rejected rather than dropped: `'https://example.com/docs'` almost certainly means the\n * author expects a base path, and silently serving from the root would be a confusing way to find\n * out that isn't supported.\n */\nexport function resolveSiteOrigin(siteUrl: string | undefined): string {\n if (!siteUrl) return DEFAULT_SSG_ORIGIN;\n const parsed = URL.parse(siteUrl);\n if (!parsed || (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')) {\n throw new Error(`[rshono] invalid siteUrl ${JSON.stringify(siteUrl)} — use a full origin, e.g. 'https://example.com'.`);\n }\n if (parsed.pathname !== '/' || parsed.search || parsed.hash) {\n throw new Error(`[rshono] siteUrl ${JSON.stringify(siteUrl)} must be a bare origin — a base path is not supported.`);\n }\n return parsed.origin;\n}\n\nfunction interpolatePath(pattern: string, params: Record<string, string>): string {\n return pattern\n .split('/')\n .map((segment) => {\n if (!segment.startsWith(':')) {\n if (segment.includes('*')) {\n throw new Error(`Cannot prerender \"${pattern}\": wildcard segments are not supported by staticPaths.`);\n }\n return segment;\n }\n const name = segment.slice(1);\n if (!/^\\w+$/.test(name)) {\n throw new Error(`Cannot prerender \"${pattern}\": optional/regex params are not supported by staticPaths.`);\n }\n const value = params[name];\n if (value === undefined) {\n throw new Error(`staticPaths for \"${pattern}\" returned a param set without \"${name}\".`);\n }\n return encodeURIComponent(value);\n })\n .join('/');\n}\n\n/**\n * Prerendered pages, keyed by the request that produced them (see {@link readPrerendered}).\n *\n * Bounded so a site with thousands of prerendered pages keeps a working set rather than the whole\n * build in memory. Only *hits* are cached: caching misses would let anyone mint entries by\n * requesting paths that don't exist. The files are written at build time and never change while\n * the server is up, so an entry never needs invalidating.\n */\nconst pageCache = new Map<string, PrerenderedPage>();\nconst MAX_CACHED_PAGES = 128;\n\nexport async function readPrerendered(ssgDir: string, requestPath: string, variant: PrerenderVariant = 'html'): Promise<PrerenderedPage | null> {\n // Keyed by what the request carried, not by the resolved filename, so a hit costs one Map lookup\n // instead of re-deriving the path every time. Safe because only *hits* are cached: an entry\n // exists only if this exact key already passed the checks below.\n const key = `${ssgDir}\\0${variant}\\0${requestPath}`;\n const cached = pageCache.get(key);\n if (cached) return cached;\n\n const relPath = prerenderedRelPath(requestPath, variant);\n if (relPath === null) return null;\n // Belt and braces: the shared guard already refused a traversal, and this proves the resolved file\n // is under the root whatever else the path contained.\n const root = resolve(ssgDir);\n const file = resolve(root, relPath);\n if (!file.startsWith(root + sep)) return null;\n\n let body: string;\n try {\n body = await readFile(file, 'utf8');\n } catch {\n return null;\n }\n\n const page: PrerenderedPage = {\n body,\n contentLength: String(Buffer.byteLength(body)),\n etag: `W/\"${createHash('sha256').update(body).digest('base64url').slice(0, 22)}\"`,\n };\n pageCache.set(key, page);\n // Insertion-ordered, so the first key is the oldest. One entry in means at most one out.\n if (pageCache.size > MAX_CACHED_PAGES) pageCache.delete(pageCache.keys().next().value!);\n return page;\n}\n\ninterface PrerenderOptions {\n routes: readonly Route[];\n fetch: (request: Request) => Response | Promise<Response>;\n ssgDir: string;\n /** {@link RSHonoConfig.siteUrl} — the origin absolute URLs in the output are built against. */\n siteUrl?: string;\n}\n\nexport interface PrerenderResult {\n written: string[];\n skipped: string[];\n}\n\n/** Renders one representation of a path, or `null` if the app didn't answer with it. */\nasync function renderVariant(\n fetch: PrerenderOptions['fetch'],\n url: string,\n variant: PrerenderVariant,\n): Promise<{ body: string } | { status: number } | null> {\n const response = await fetch(new Request(url, { headers: { Accept: VARIANTS[variant].accept } }));\n if (response.status !== 200) return { status: response.status };\n if (!(response.headers.get('Content-Type') ?? '').includes(VARIANTS[variant].contentType)) return null;\n return { body: await response.text() };\n}\n\nexport async function prerenderStaticRoutes(options: PrerenderOptions): Promise<PrerenderResult> {\n const { routes, fetch, ssgDir } = options;\n const staticRoutes = routes.filter((r): r is PageRoute => isPageRoute(r) && r.render === 'static');\n const origin = resolveSiteOrigin(options.siteUrl);\n\n if (staticRoutes.length > 0 && !options.siteUrl) {\n console.warn(\n ` ⚠ No siteUrl in rshono.config — prerendered pages are built against ${DEFAULT_SSG_ORIGIN}, so any absolute URL\\n` +\n ` they derive from a page's \\`url\\` prop (canonical tags, og:url, absolute links) will point there.`,\n );\n }\n\n const written: string[] = [];\n const skipped: string[] = [];\n\n for (const route of staticRoutes) {\n let paths: string[];\n if (!/[:*]/.test(route.path)) {\n paths = [route.path];\n } else {\n if (!route.staticPaths) {\n console.warn(` ⚠ Static route \"${route.path}\" has params but no staticPaths — will SSR per request.`);\n skipped.push(route.path);\n continue;\n }\n paths = (await route.staticPaths()).map((params) => interpolatePath(route.path, params));\n }\n\n for (const path of paths) {\n const document = await renderVariant(fetch, origin + path, 'html');\n if (document === null || !('body' in document)) {\n const rendered = document === null ? 'a non-HTML response' : `${document.status}`;\n console.warn(` ⚠ \"${path}\" rendered ${rendered} at build time — skipping, will SSR per request.`);\n skipped.push(path);\n continue;\n }\n\n const write = (variant: PrerenderVariant, body: string) => {\n const file = join(ssgDir, ssgFilePath(path, variant)!);\n mkdirSync(dirname(file), { recursive: true });\n writeFileSync(file, body);\n };\n write('html', document.body);\n\n // The soft-navigation representation of the same page. Best-effort: if it doesn't come back\n // cleanly the document is still valid on its own, and serving falls back to rendering flight\n // per request — the behaviour before this was written at all.\n const flight = await renderVariant(fetch, origin + path, 'flight');\n if (flight !== null && 'body' in flight) {\n write('flight', flight.body);\n } else {\n console.warn(` ⚠ \"${path}\" produced no flight payload — soft navigations to it will render per request.`);\n }\n\n written.push(path);\n }\n }\n\n return { written, skipped };\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
interface StaticOptions {
|
|
4
|
+
/** Directory the files are read from. */
|
|
5
|
+
root: string;
|
|
6
|
+
/** Dev serves revalidated (`no-cache`); a build serves hashed files as immutable. */
|
|
7
|
+
isDev: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** A sub-app serving the hashed client bundle, to be mounted at `/_static`. */
|
|
10
|
+
export declare function createStaticAssetsApp(options: StaticOptions): Hono;
|
|
11
|
+
/** Middleware serving the app's `public/` files verbatim at the web root, for paths no route claimed. */
|
|
12
|
+
export declare function createPublicFallback({ root, isDev }: StaticOptions): MiddlewareHandler;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=static.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../../src/server/static.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAe5B,UAAU,aAAa;IACrB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CASlE;AAED,yGAAyG;AACzG,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,aAAa,GAAG,iBAAiB,CAStF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { serveStatic } from '@hono/node-server/serve-static';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
const CONTENT_HASHED = /\.[0-9a-f]{8,}\./;
|
|
4
|
+
function cacheControl(isDev) {
|
|
5
|
+
return async (c, next) => {
|
|
6
|
+
await next();
|
|
7
|
+
if (c.res.status !== 200 && c.res.status !== 206)
|
|
8
|
+
return;
|
|
9
|
+
c.res.headers.set('Cache-Control', isDev ? 'no-cache' : CONTENT_HASHED.test(c.req.path) ? 'public, max-age=31536000, immutable' : 'public, max-age=300');
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** A sub-app serving the hashed client bundle, to be mounted at `/_static`. */
|
|
13
|
+
export function createStaticAssetsApp(options) {
|
|
14
|
+
const { root, isDev } = options;
|
|
15
|
+
const app = new Hono();
|
|
16
|
+
app.on(['GET', 'HEAD'], '/*', cacheControl(isDev), serveStatic({ root, rewriteRequestPath: (path) => path.replace(/^\/_static/, '') }), (c) => c.text('Not Found', 404));
|
|
17
|
+
return app;
|
|
18
|
+
}
|
|
19
|
+
/** Middleware serving the app's `public/` files verbatim at the web root, for paths no route claimed. */
|
|
20
|
+
export function createPublicFallback({ root, isDev }) {
|
|
21
|
+
const serve = serveStatic({ root });
|
|
22
|
+
return async (c, next) => {
|
|
23
|
+
const result = await serve(c, next);
|
|
24
|
+
if (result instanceof Response && (result.status === 200 || result.status === 206)) {
|
|
25
|
+
result.headers.set('Cache-Control', isDev ? 'no-cache' : 'public, max-age=300');
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=static.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.js","sourceRoot":"","sources":["../../src/server/static.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAE7D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,MAAM,cAAc,GAAG,kBAAkB,CAAC;AAE1C,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,IAAI,EAAE,CAAC;QACb,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO;QACzD,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CACf,eAAe,EACf,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,qCAAqC,CAAC,CAAC,CAAC,qBAAqB,CACrH,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AASD,+EAA+E;AAC/E,MAAM,UAAU,qBAAqB,CAAC,OAAsB;IAC1D,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAChC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,GAAG,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAC5I,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,CACzB,CAAC;IAEF,OAAO,GAAG,CAAC;AACb,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,oBAAoB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAiB;IACjE,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACpC,IAAI,MAAM,YAAY,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EAAE,CAAC;YACnF,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC;QAClF,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { serveStatic } from '@hono/node-server/serve-static';\nimport type { MiddlewareHandler } from 'hono';\nimport { Hono } from 'hono';\n\nconst CONTENT_HASHED = /\\.[0-9a-f]{8,}\\./;\n\nfunction cacheControl(isDev: boolean): MiddlewareHandler {\n return async (c, next) => {\n await next();\n if (c.res.status !== 200 && c.res.status !== 206) return;\n c.res.headers.set(\n 'Cache-Control',\n isDev ? 'no-cache' : CONTENT_HASHED.test(c.req.path) ? 'public, max-age=31536000, immutable' : 'public, max-age=300',\n );\n };\n}\n\ninterface StaticOptions {\n /** Directory the files are read from. */\n root: string;\n /** Dev serves revalidated (`no-cache`); a build serves hashed files as immutable. */\n isDev: boolean;\n}\n\n/** A sub-app serving the hashed client bundle, to be mounted at `/_static`. */\nexport function createStaticAssetsApp(options: StaticOptions): Hono {\n const { root, isDev } = options;\n const app = new Hono();\n\n app.on(['GET', 'HEAD'], '/*', cacheControl(isDev), serveStatic({ root, rewriteRequestPath: (path) => path.replace(/^\\/_static/, '') }), (c) =>\n c.text('Not Found', 404),\n );\n\n return app;\n}\n\n/** Middleware serving the app's `public/` files verbatim at the web root, for paths no route claimed. */\nexport function createPublicFallback({ root, isDev }: StaticOptions): MiddlewareHandler {\n const serve = serveStatic({ root });\n return async (c, next) => {\n const result = await serve(c, next);\n if (result instanceof Response && (result.status === 200 || result.status === 206)) {\n result.headers.set('Cache-Control', isDev ? 'no-cache' : 'public, max-age=300');\n }\n return result;\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rshono/core",
|
|
3
|
+
"version": "1.0.0-rc.0",
|
|
4
|
+
"description": "Minimalist web framework — Hono + Rspack + React Server Components",
|
|
5
|
+
"author": "Lasse <lasse@lassetange.com> (https://www.lassetange.com)",
|
|
6
|
+
"license": "ISC",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"react",
|
|
9
|
+
"react-server-components",
|
|
10
|
+
"rsc",
|
|
11
|
+
"hono",
|
|
12
|
+
"rspack",
|
|
13
|
+
"framework"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/rshono/rshono.git",
|
|
18
|
+
"directory": "packages/core"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/rshono/rshono/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/rshono/rshono",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./server": {
|
|
31
|
+
"types": "./dist/runtime/server.d.ts",
|
|
32
|
+
"import": "./dist/runtime/server.js"
|
|
33
|
+
},
|
|
34
|
+
"./client": {
|
|
35
|
+
"types": "./dist/runtime/client.d.ts",
|
|
36
|
+
"import": "./dist/runtime/client.js"
|
|
37
|
+
},
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"bin": {
|
|
41
|
+
"rshono": "./bin/rshono.mjs"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"bin"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=22.1.0"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsc -p tsconfig.build.json && node scripts/copy-dist-assets.mjs",
|
|
55
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
56
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"test": "npm run build && node --test --test-concurrency=1 \"test/*.test.mjs\"",
|
|
59
|
+
"test:browser": "npm run build && playwright test",
|
|
60
|
+
"prepack": "npm run build"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@hono/node-server": "^2.0.11",
|
|
64
|
+
"@rspack/core": "2.1.5",
|
|
65
|
+
"@rspack/plugin-react-refresh": "2.0.2",
|
|
66
|
+
"react-refresh": "0.18.0",
|
|
67
|
+
"react-server-dom-rspack": "0.0.2",
|
|
68
|
+
"rsc-html-stream": "^0.0.7",
|
|
69
|
+
"tsx": "^4.23.1"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@playwright/test": "^1.58.0",
|
|
73
|
+
"@types/node": "^26.1.1",
|
|
74
|
+
"@types/react": "^19.2.17",
|
|
75
|
+
"@types/react-dom": "^19.2.3",
|
|
76
|
+
"hono": "^4.12.31",
|
|
77
|
+
"react": "19.2.8",
|
|
78
|
+
"react-dom": "19.2.8",
|
|
79
|
+
"typescript": "^7.0.2"
|
|
80
|
+
},
|
|
81
|
+
"peerDependencies": {
|
|
82
|
+
"hono": "^4.8.0",
|
|
83
|
+
"react": "^19.1.0",
|
|
84
|
+
"react-dom": "^19.1.0"
|
|
85
|
+
}
|
|
86
|
+
}
|