@rshono/core 1.0.0-rc.1 → 1.0.0-rc.11
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 +202 -159
- package/dist/builder/page-files.d.ts.map +1 -1
- package/dist/builder/page-files.js +7 -3
- package/dist/builder/page-files.js.map +1 -1
- package/dist/builder/public-env.d.ts +6 -0
- package/dist/builder/public-env.d.ts.map +1 -1
- package/dist/builder/public-env.js +6 -0
- package/dist/builder/public-env.js.map +1 -1
- package/dist/builder/rspack-config.d.ts +3 -3
- package/dist/builder/rspack-config.d.ts.map +1 -1
- package/dist/builder/rspack-config.js +18 -21
- package/dist/builder/rspack-config.js.map +1 -1
- package/dist/cli/build.d.ts +2 -2
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev.d.ts +2 -2
- package/dist/cli/dev.d.ts.map +1 -1
- package/dist/cli/dev.js +63 -35
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/index.js +10 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/config.d.ts +58 -70
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -1
- package/dist/config.js.map +1 -1
- package/dist/deploy/cloudflare/build.d.ts.map +1 -1
- package/dist/deploy/cloudflare/build.js +7 -5
- package/dist/deploy/cloudflare/build.js.map +1 -1
- package/dist/deploy/cloudflare/runtime.d.ts.map +1 -1
- package/dist/deploy/cloudflare/runtime.js +7 -37
- package/dist/deploy/cloudflare/runtime.js.map +1 -1
- package/dist/deploy/contract.d.ts +22 -16
- package/dist/deploy/contract.d.ts.map +1 -1
- package/dist/deploy/contract.js.map +1 -1
- package/dist/deploy/filesystem.d.ts +1 -1
- package/dist/deploy/filesystem.d.ts.map +1 -1
- package/dist/deploy/filesystem.js +7 -10
- package/dist/deploy/filesystem.js.map +1 -1
- package/dist/deploy/node/runtime.d.ts +4 -0
- package/dist/deploy/node/runtime.d.ts.map +1 -1
- package/dist/deploy/node/runtime.js +24 -3
- package/dist/deploy/node/runtime.js.map +1 -1
- package/dist/deploy/presets.d.ts +3 -3
- package/dist/deploy/presets.d.ts.map +1 -1
- package/dist/deploy/presets.js +13 -30
- package/dist/deploy/presets.js.map +1 -1
- package/dist/deploy/vercel/runtime.d.ts.map +1 -1
- package/dist/deploy/vercel/runtime.js +0 -3
- package/dist/deploy/vercel/runtime.js.map +1 -1
- package/dist/index.d.ts +6 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +85 -48
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +4 -6
- package/dist/router.js.map +1 -1
- package/dist/runtime/boundaries.d.ts +39 -25
- package/dist/runtime/boundaries.d.ts.map +1 -1
- package/dist/runtime/boundaries.js +22 -17
- package/dist/runtime/boundaries.js.map +1 -1
- package/dist/runtime/client.d.ts +9 -8
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +9 -8
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/context.d.ts +216 -70
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/context.js +299 -93
- package/dist/runtime/context.js.map +1 -1
- package/dist/runtime/control.d.ts.map +1 -1
- package/dist/runtime/control.js +7 -0
- package/dist/runtime/control.js.map +1 -1
- package/dist/runtime/dev-protocol.d.ts.map +1 -1
- package/dist/runtime/dev-protocol.js.map +1 -1
- package/dist/runtime/entry.client.d.ts +4 -0
- package/dist/runtime/entry.client.d.ts.map +1 -1
- package/dist/runtime/entry.client.js +190 -154
- package/dist/runtime/entry.client.js.map +1 -1
- package/dist/runtime/entry.rsc.d.ts.map +1 -1
- package/dist/runtime/entry.rsc.js +145 -159
- package/dist/runtime/entry.rsc.js.map +1 -1
- package/dist/runtime/entry.ssr.d.ts +9 -1
- package/dist/runtime/entry.ssr.d.ts.map +1 -1
- package/dist/runtime/entry.ssr.js +36 -18
- package/dist/runtime/entry.ssr.js.map +1 -1
- package/dist/runtime/flight-inject.d.ts +31 -0
- package/dist/runtime/flight-inject.d.ts.map +1 -0
- package/dist/runtime/flight-inject.js +221 -0
- package/dist/runtime/flight-inject.js.map +1 -0
- package/dist/runtime/navigation.d.ts +20 -38
- package/dist/runtime/navigation.d.ts.map +1 -1
- package/dist/runtime/navigation.js +10 -53
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/request.d.ts +6 -0
- package/dist/runtime/request.d.ts.map +1 -1
- package/dist/runtime/request.js +8 -0
- package/dist/runtime/request.js.map +1 -1
- package/dist/runtime/server.d.ts +7 -12
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +15 -12
- package/dist/runtime/server.js.map +1 -1
- package/dist/server/headers.d.ts +9 -9
- package/dist/server/headers.js +9 -9
- package/dist/server/headers.js.map +1 -1
- package/dist/server/load-config.d.ts +2 -2
- package/dist/server/load-config.d.ts.map +1 -1
- package/dist/server/load-config.js +22 -11
- package/dist/server/load-config.js.map +1 -1
- package/dist/server/prerendered.d.ts +43 -15
- package/dist/server/prerendered.d.ts.map +1 -1
- package/dist/server/prerendered.js +47 -0
- package/dist/server/prerendered.js.map +1 -1
- package/dist/server/server-config.d.ts +13 -39
- package/dist/server/server-config.d.ts.map +1 -1
- package/dist/server/server-config.js +5 -74
- package/dist/server/server-config.js.map +1 -1
- package/dist/server/ssg.d.ts +2 -2
- package/dist/server/ssg.d.ts.map +1 -1
- package/dist/server/ssg.js +21 -34
- package/dist/server/ssg.js.map +1 -1
- package/package.json +13 -16
- package/dist/deploy/bun/runtime.d.ts +0 -11
- package/dist/deploy/bun/runtime.d.ts.map +0 -1
- package/dist/deploy/bun/runtime.js +0 -22
- package/dist/deploy/bun/runtime.js.map +0 -1
- package/dist/deploy/deno/runtime.d.ts +0 -11
- package/dist/deploy/deno/runtime.d.ts.map +0 -1
- package/dist/deploy/deno/runtime.js +0 -16
- package/dist/deploy/deno/runtime.js.map +0 -1
- package/dist/deploy/listen.d.ts +0 -20
- package/dist/deploy/listen.d.ts.map +0 -1
- package/dist/deploy/listen.js +0 -24
- package/dist/deploy/listen.js.map +0 -1
- package/dist/deploy/netlify/build.d.ts +0 -8
- package/dist/deploy/netlify/build.d.ts.map +0 -1
- package/dist/deploy/netlify/build.js +0 -52
- package/dist/deploy/netlify/build.js.map +0 -1
- package/dist/deploy/netlify/runtime.d.ts +0 -13
- package/dist/deploy/netlify/runtime.d.ts.map +0 -1
- package/dist/deploy/netlify/runtime.js +0 -24
- package/dist/deploy/netlify/runtime.js.map +0 -1
- package/dist/server/compress.d.ts +0 -15
- package/dist/server/compress.d.ts.map +0 -1
- package/dist/server/compress.js +0 -76
- package/dist/server/compress.js.map +0 -1
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RspackOptions } from '@rspack/core';
|
|
2
2
|
import type { DeployTarget } from './deploy/contract.js';
|
|
3
|
-
/** Which of the two Rspack compilers the {@link
|
|
3
|
+
/** Which of the two Rspack compilers the {@link RshonoConfig.rspack} hook is being called for. */
|
|
4
4
|
export interface RspackHookContext {
|
|
5
5
|
/** `true` for the server (`target: node`) bundle, `false` for the client (`target: web`) bundle. */
|
|
6
6
|
isServer: boolean;
|
|
@@ -11,18 +11,23 @@ export interface RspackHookContext {
|
|
|
11
11
|
* Project configuration for rshono, default-exported from `rshono.config.ts` at the project root
|
|
12
12
|
* (`.js` / `.mjs` also work). Every field is optional; omit the file entirely to accept all defaults.
|
|
13
13
|
*
|
|
14
|
+
* Deliberately small: it holds what only the *build* can decide. Everything that is a per-request
|
|
15
|
+
* concern — CSRF, CSP, the body cap — is Hono middleware in `src/server.ts` instead, because Hono
|
|
16
|
+
* already ships all of it and a config field would only be a worse way to spell the same call.
|
|
17
|
+
*
|
|
14
18
|
* @example
|
|
15
19
|
* ```ts
|
|
16
20
|
* import { defineConfig } from '@rshono/core';
|
|
17
21
|
*
|
|
18
22
|
* export default defineConfig({
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* allowedOrigins: ['https://admin.example.com'],
|
|
23
|
+
* deploy: 'cloudflare',
|
|
24
|
+
* siteUrl: 'https://example.com',
|
|
22
25
|
* });
|
|
23
26
|
* ```
|
|
27
|
+
*
|
|
28
|
+
* @see {@link https://www.rshono.com/docs/configuration | Docs — configuration}
|
|
24
29
|
*/
|
|
25
|
-
export interface
|
|
30
|
+
export interface RshonoConfig {
|
|
26
31
|
/**
|
|
27
32
|
* The hosting platform `rshono build` targets. Default `'node'` — a long-lived server process, for
|
|
28
33
|
* a VPS, a container or anywhere else you run `rshono start`.
|
|
@@ -30,92 +35,75 @@ export interface RSHonoConfig {
|
|
|
30
35
|
* Overridden by the `--deploy` flag or the `RSHONO_DEPLOY` env var, so one config can still be
|
|
31
36
|
* built for more than one place. `rshono dev` ignores it entirely and always runs the Node dev
|
|
32
37
|
* server.
|
|
38
|
+
*
|
|
39
|
+
* @see {@link https://www.rshono.com/docs/deployment | Docs — deployment}
|
|
33
40
|
*/
|
|
34
41
|
deploy?: DeployTarget;
|
|
35
42
|
/**
|
|
36
43
|
* The public origin the site is served from, e.g. `'https://example.com'`.
|
|
37
44
|
*
|
|
38
|
-
* Only used when prerendering `render: 'static'` routes
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* `og:url`. Dynamic routes are unaffected: they resolve the URL per request.
|
|
45
|
+
* Only used when prerendering `render: 'static'` routes: they are rendered once at build time, with
|
|
46
|
+
* no request to read a `Host` from, so a page's `url` prop falls back to `http://localhost` without
|
|
47
|
+
* this — and that is what its canonical tag, absolute links and `og:url` get baked with. Dynamic
|
|
48
|
+
* routes resolve the URL per request and are unaffected.
|
|
43
49
|
*
|
|
44
|
-
*
|
|
50
|
+
* Must be a bare origin; a path is rejected rather than silently dropped.
|
|
51
|
+
*
|
|
52
|
+
* @see {@link https://www.rshono.com/docs/configuration#siteurl | Docs — siteUrl}
|
|
45
53
|
*/
|
|
46
54
|
siteUrl?: string;
|
|
47
|
-
/** Default port for `dev` / `start`. Overridden by the `--port` flag or the `PORT` env var. Default `3000`. */
|
|
48
|
-
port?: number;
|
|
49
|
-
/** Bind address for `start`. Overridden by the `HOST` env var. Default `'0.0.0.0'`. */
|
|
50
|
-
host?: string;
|
|
51
55
|
/**
|
|
52
56
|
* Honour `X-Forwarded-Host` / `X-Forwarded-Proto` when resolving the browser-facing request
|
|
53
|
-
* URL
|
|
57
|
+
* URL — `getRequestContext().url` and a page's `url` prop.
|
|
58
|
+
*
|
|
59
|
+
* Middleware in `src/server.ts` is handed Hono's `c` and so reads `c.req.url`, the *internal*
|
|
60
|
+
* address, whatever this says. Hono's `csrf()` is the one to watch: behind a proxy that rewrites
|
|
61
|
+
* `Host`, give it the public origin explicitly (`csrf({ origin: 'https://example.com' })`) rather
|
|
62
|
+
* than leaving it to compare against an address the browser never used.
|
|
54
63
|
*
|
|
55
|
-
* **Off by default, and leave it off unless a proxy you control sets those headers
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
64
|
+
* **Off by default, and leave it off unless a proxy you control sets those headers**: any client
|
|
65
|
+
* can send them, so with nothing stripping them at the edge one request can point every absolute
|
|
66
|
+
* URL the app builds at an attacker's host (and poison a shared cache). Turn it on when you
|
|
67
|
+
* terminate TLS or rewrite `Host` at a reverse proxy / load balancer. Always `true` under
|
|
68
|
+
* `rshono dev`, where the framework's own proxy sets them and binds to localhost. Default `false`.
|
|
69
|
+
*
|
|
70
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host | MDN — X-Forwarded-Host}
|
|
71
|
+
* @see {@link https://www.rshono.com/docs/configuration#proxy-headers | Docs — proxy headers}
|
|
61
72
|
*/
|
|
62
73
|
trustProxy?: boolean;
|
|
63
74
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
|
|
67
|
-
checkOrigin?: boolean;
|
|
68
|
-
/**
|
|
69
|
-
* Extra origins allowed to post server actions, in addition to the app's own origin.
|
|
70
|
-
* Accepts full origins or bare hosts, e.g. `['https://admin.example.com', 'localhost:4000']`.
|
|
71
|
-
*/
|
|
72
|
-
allowedOrigins?: string[];
|
|
73
|
-
/**
|
|
74
|
-
* Send a strict per-request-nonce `Content-Security-Policy` with every HTML document.
|
|
75
|
-
* While enabled, `render: 'static'` routes render per request (a prerendered file can't carry a
|
|
76
|
-
* per-request nonce). Default `false`.
|
|
77
|
-
*/
|
|
78
|
-
csp?: boolean;
|
|
79
|
-
/**
|
|
80
|
-
* Directives merged over the built-in {@link csp} policy, which is deliberately strict
|
|
81
|
-
* (`default-src 'self'`, no framing, no plugins) and so blocks third-party images, fonts and
|
|
82
|
-
* API hosts until you widen it here. Set a directive to `''` to drop it entirely.
|
|
83
|
-
*
|
|
84
|
-
* The per-request nonce is always appended to `script-src`, whatever you put there.
|
|
85
|
-
* Ignored unless `csp` is `true`.
|
|
75
|
+
* Escape hatch: mutate the generated Rspack config just before it's compiled. Called once per
|
|
76
|
+
* compiler — inspect {@link RspackHookContext.isServer} to tell them apart. Mutate `config` in
|
|
77
|
+
* place and return nothing, or return a replacement.
|
|
86
78
|
*
|
|
87
79
|
* @example
|
|
88
80
|
* ```ts
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* 'font-src': "'self' https://fonts.gstatic.com",
|
|
92
|
-
* 'frame-ancestors': "'self'",
|
|
81
|
+
* rspack(config, { isServer }) {
|
|
82
|
+
* config.module?.rules?.push({ test: /\.svg$/, type: 'asset/source' });
|
|
93
83
|
* }
|
|
94
84
|
* ```
|
|
95
|
-
*/
|
|
96
|
-
cspDirectives?: Record<string, string>;
|
|
97
|
-
/**
|
|
98
|
-
* Max server-action request body before it's rejected with 413 — a memory-exhaustion guard.
|
|
99
|
-
* A number is bytes; a string carries a unit (`'512kb'`, `'4mb'`); `false` (or `0`) disables the cap.
|
|
100
|
-
* Default `'1mb'`.
|
|
101
|
-
*/
|
|
102
|
-
bodySizeLimit?: string | number | false;
|
|
103
|
-
/** Deadline in milliseconds for a single page render (flight + SSR). Default `10000`. */
|
|
104
|
-
renderTimeout?: number;
|
|
105
|
-
/**
|
|
106
|
-
* Gzip compressible responses (HTML, flight payloads, JSON, CSS, JS). Streaming-safe — each
|
|
107
|
-
* chunk the renderer flushes is flushed on the wire too. Default `true`.
|
|
108
85
|
*
|
|
109
|
-
*
|
|
110
|
-
|
|
111
|
-
compress?: boolean;
|
|
112
|
-
/**
|
|
113
|
-
* Escape hatch: mutate the generated Rspack config just before it's compiled. Called once per
|
|
114
|
-
* compiler — inspect {@link RspackHookContext.isServer} to tell them apart. Mutate `config` in
|
|
115
|
-
* place and return nothing, or return a replacement.
|
|
86
|
+
* @see {@link https://rspack.rs/config/ | Rspack — configuration reference}
|
|
87
|
+
* @see {@link https://www.rshono.com/docs/configuration#the-rspack-hook | Docs — the rspack hook}
|
|
116
88
|
*/
|
|
117
89
|
rspack?: (config: RspackOptions, ctx: RspackHookContext) => RspackOptions | void;
|
|
118
90
|
}
|
|
119
|
-
/**
|
|
120
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Identity helper that types a config object — gives editor autocomplete without an explicit
|
|
93
|
+
* annotation. Default-export the result from `rshono.config.ts`.
|
|
94
|
+
*
|
|
95
|
+
* @param config - The project's {@link RshonoConfig}; every field is optional.
|
|
96
|
+
* @returns The config, unchanged and fully typed.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* // rshono.config.ts
|
|
101
|
+
* import { defineConfig } from '@rshono/core';
|
|
102
|
+
*
|
|
103
|
+
* export default defineConfig({ deploy: 'cloudflare', siteUrl: 'https://example.com' });
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* @see {@link https://www.rshono.com/docs/configuration | Docs — configuration}
|
|
107
|
+
*/
|
|
108
|
+
export declare function defineConfig(config: RshonoConfig): RshonoConfig;
|
|
121
109
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,kGAAkG;AAClG,MAAM,WAAW,iBAAiB;IAChC,oGAAoG;IACpG,QAAQ,EAAE,OAAO,CAAC;IAClB,+DAA+D;IAC/D,KAAK,EAAE,OAAO,CAAC;CAChB;AAED
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,kGAAkG;AAClG,MAAM,WAAW,iBAAiB;IAChC,oGAAoG;IACpG,QAAQ,EAAE,OAAO,CAAC;IAClB,+DAA+D;IAC/D,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,iBAAiB,KAAK,aAAa,GAAG,IAAI,CAAC;CAClF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAE/D"}
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Identity helper that types a config object — gives editor autocomplete without an explicit
|
|
3
|
+
* annotation. Default-export the result from `rshono.config.ts`.
|
|
4
|
+
*
|
|
5
|
+
* @param config - The project's {@link RshonoConfig}; every field is optional.
|
|
6
|
+
* @returns The config, unchanged and fully typed.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* // rshono.config.ts
|
|
11
|
+
* import { defineConfig } from '@rshono/core';
|
|
12
|
+
*
|
|
13
|
+
* export default defineConfig({ deploy: 'cloudflare', siteUrl: 'https://example.com' });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @see {@link https://www.rshono.com/docs/configuration | Docs — configuration}
|
|
17
|
+
*/
|
|
2
18
|
export function defineConfig(config) {
|
|
3
19
|
return config;
|
|
4
20
|
}
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AA6FA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,YAAY,CAAC,MAAoB;IAC/C,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import type { RspackOptions } from '@rspack/core';\nimport type { DeployTarget } from './deploy/contract.js';\n\n/** Which of the two Rspack compilers the {@link RshonoConfig.rspack} hook is being called for. */\nexport interface RspackHookContext {\n /** `true` for the server (`target: node`) bundle, `false` for the client (`target: web`) bundle. */\n isServer: boolean;\n /** `true` under `rshono dev`, `false` under `rshono build`. */\n isDev: boolean;\n}\n\n/**\n * Project configuration for rshono, default-exported from `rshono.config.ts` at the project root\n * (`.js` / `.mjs` also work). Every field is optional; omit the file entirely to accept all defaults.\n *\n * Deliberately small: it holds what only the *build* can decide. Everything that is a per-request\n * concern — CSRF, CSP, the body cap — is Hono middleware in `src/server.ts` instead, because Hono\n * already ships all of it and a config field would only be a worse way to spell the same call.\n *\n * @example\n * ```ts\n * import { defineConfig } from '@rshono/core';\n *\n * export default defineConfig({\n * deploy: 'cloudflare',\n * siteUrl: 'https://example.com',\n * });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/configuration | Docs — configuration}\n */\nexport interface RshonoConfig {\n /**\n * The hosting platform `rshono build` targets. Default `'node'` — a long-lived server process, for\n * a VPS, a container or anywhere else you run `rshono start`.\n *\n * Overridden by the `--deploy` flag or the `RSHONO_DEPLOY` env var, so one config can still be\n * built for more than one place. `rshono dev` ignores it entirely and always runs the Node dev\n * server.\n *\n * @see {@link https://www.rshono.com/docs/deployment | Docs — deployment}\n */\n deploy?: DeployTarget;\n /**\n * The public origin the site is served from, e.g. `'https://example.com'`.\n *\n * Only used when prerendering `render: 'static'` routes: they are rendered once at build time, with\n * no request to read a `Host` from, so a page's `url` prop falls back to `http://localhost` without\n * this — and that is what its canonical tag, absolute links and `og:url` get baked with. Dynamic\n * routes resolve the URL per request and are unaffected.\n *\n * Must be a bare origin; a path is rejected rather than silently dropped.\n *\n * @see {@link https://www.rshono.com/docs/configuration#siteurl | Docs — siteUrl}\n */\n siteUrl?: string;\n /**\n * Honour `X-Forwarded-Host` / `X-Forwarded-Proto` when resolving the browser-facing request\n * URL — `getRequestContext().url` and a page's `url` prop.\n *\n * Middleware in `src/server.ts` is handed Hono's `c` and so reads `c.req.url`, the *internal*\n * address, whatever this says. Hono's `csrf()` is the one to watch: behind a proxy that rewrites\n * `Host`, give it the public origin explicitly (`csrf({ origin: 'https://example.com' })`) rather\n * than leaving it to compare against an address the browser never used.\n *\n * **Off by default, and leave it off unless a proxy you control sets those headers**: any client\n * can send them, so with nothing stripping them at the edge one request can point every absolute\n * URL the app builds at an attacker's host (and poison a shared cache). Turn it on when you\n * terminate TLS or rewrite `Host` at a reverse proxy / load balancer. Always `true` under\n * `rshono dev`, where the framework's own proxy sets them and binds to localhost. Default `false`.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host | MDN — X-Forwarded-Host}\n * @see {@link https://www.rshono.com/docs/configuration#proxy-headers | Docs — proxy headers}\n */\n trustProxy?: boolean;\n /**\n * Escape hatch: mutate the generated Rspack config just before it's compiled. Called once per\n * compiler — inspect {@link RspackHookContext.isServer} to tell them apart. Mutate `config` in\n * place and return nothing, or return a replacement.\n *\n * @example\n * ```ts\n * rspack(config, { isServer }) {\n * config.module?.rules?.push({ test: /\\.svg$/, type: 'asset/source' });\n * }\n * ```\n *\n * @see {@link https://rspack.rs/config/ | Rspack — configuration reference}\n * @see {@link https://www.rshono.com/docs/configuration#the-rspack-hook | Docs — the rspack hook}\n */\n rspack?: (config: RspackOptions, ctx: RspackHookContext) => RspackOptions | void;\n}\n\n/**\n * Identity helper that types a config object — gives editor autocomplete without an explicit\n * annotation. Default-export the result from `rshono.config.ts`.\n *\n * @param config - The project's {@link RshonoConfig}; every field is optional.\n * @returns The config, unchanged and fully typed.\n *\n * @example\n * ```ts\n * // rshono.config.ts\n * import { defineConfig } from '@rshono/core';\n *\n * export default defineConfig({ deploy: 'cloudflare', siteUrl: 'https://example.com' });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/configuration | Docs — configuration}\n */\nexport function defineConfig(config: RshonoConfig): RshonoConfig {\n return config;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/deploy/cloudflare/build.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/deploy/cloudflare/build.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAuDxD;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBpF"}
|
|
@@ -26,13 +26,16 @@ function workerName(rootDir) {
|
|
|
26
26
|
.replace(/^-+|-+$/g, '');
|
|
27
27
|
return name || 'rshono-app';
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
// Fixed, not the day the build ran: wrangler's bundled workerd refuses a date newer than its own, so a config
|
|
30
|
+
// dated today would deploy fine and never start under `wrangler dev`. See the test that holds it behind today.
|
|
31
|
+
const COMPATIBILITY_DATE = '2026-07-01';
|
|
32
|
+
function wranglerConfig(rootDir) {
|
|
30
33
|
return `${JSON.stringify({
|
|
31
34
|
$schema: 'node_modules/wrangler/config-schema.json',
|
|
32
35
|
name: workerName(rootDir),
|
|
33
36
|
main: 'dist/server/main.mjs',
|
|
34
|
-
compatibility_date:
|
|
35
|
-
// AsyncLocalStorage — how the framework binds the request context that `
|
|
37
|
+
compatibility_date: COMPATIBILITY_DATE,
|
|
38
|
+
// AsyncLocalStorage — how the framework binds the request context that `getRequestContext()` reads.
|
|
36
39
|
compatibility_flags: ['nodejs_compat'],
|
|
37
40
|
assets: {
|
|
38
41
|
directory: `dist/${ASSETS_DIR.split(/[\\/]/).join('/')}`,
|
|
@@ -65,8 +68,7 @@ export async function finalizeCloudflareBuild(ctx) {
|
|
|
65
68
|
}
|
|
66
69
|
const existing = WRANGLER_FILES.find((file) => existsSync(join(ctx.rootDir, file)));
|
|
67
70
|
if (!existing) {
|
|
68
|
-
|
|
69
|
-
writeFileSync(join(ctx.rootDir, 'wrangler.jsonc'), wranglerConfig(ctx.rootDir, today));
|
|
71
|
+
writeFileSync(join(ctx.rootDir, 'wrangler.jsonc'), wranglerConfig(ctx.rootDir));
|
|
70
72
|
console.log(' • wrote wrangler.jsonc (yours now — edit freely, the build will not touch it again)');
|
|
71
73
|
}
|
|
72
74
|
console.log(` • assembled ${join('dist', ASSETS_DIR)} for Workers Assets`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/deploy/cloudflare/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG3C,2GAA2G;AAC3G,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;AAEhD,MAAM,cAAc,GAAG,CAAC,gBAAgB,EAAE,eAAe,EAAE,eAAe,CAAC,CAAC;AAE5E;;;;;;;GAOG;AACH,MAAM,YAAY,GAAG;;;;;CAKpB,CAAC;AAEF,6EAA6E;AAC7E,SAAS,UAAU,CAAC,OAAe;IACjC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC;SAC3B,WAAW,EAAE;SACb,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;SAC5B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3B,OAAO,IAAI,IAAI,YAAY,CAAC;AAC9B,CAAC;AAED,SAAS,cAAc,CAAC,OAAe
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/deploy/cloudflare/build.ts"],"names":[],"mappings":"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","sourcesContent":["import { cpSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport type { DeployBuildContext } from '../presets.js';\n\n/** Wrangler serves one directory, so the three kinds of asset the build produces are assembled into it. */\nconst ASSETS_DIR = join('cloudflare', 'assets');\n\nconst WRANGLER_FILES = ['wrangler.jsonc', 'wrangler.json', 'wrangler.toml'];\n\n/**\n * Caching and crawler rules for the assembled tree, in the file Workers Assets reads them from.\n *\n * The hashed bundle is immutable by construction, which is the one thing the CDN cannot infer. The\n * prerender tree is reachable under its own prefix (see `SSG_PREFIX` in `runtime.ts`) and holds a\n * second copy of pages that already have real URLs, so it is marked `noindex` rather than left for a\n * crawler to find twice.\n */\nconst HEADERS_FILE = `/_static/*\n Cache-Control: public, max-age=31536000, immutable\n\n/__ssg/*\n X-Robots-Tag: noindex\n`;\n\n/** A Wrangler worker name: lowercase, and only the characters it accepts. */\nfunction workerName(rootDir: string): string {\n const name = basename(rootDir)\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return name || 'rshono-app';\n}\n\n// Fixed, not the day the build ran: wrangler's bundled workerd refuses a date newer than its own, so a config\n// dated today would deploy fine and never start under `wrangler dev`. See the test that holds it behind today.\nconst COMPATIBILITY_DATE = '2026-07-01';\n\nfunction wranglerConfig(rootDir: string): string {\n return `${JSON.stringify(\n {\n $schema: 'node_modules/wrangler/config-schema.json',\n name: workerName(rootDir),\n main: 'dist/server/main.mjs',\n compatibility_date: COMPATIBILITY_DATE,\n // AsyncLocalStorage — how the framework binds the request context that `getRequestContext()` reads.\n compatibility_flags: ['nodejs_compat'],\n assets: {\n directory: `dist/${ASSETS_DIR.split(/[\\\\/]/).join('/')}`,\n // Read by the worker for `public/` files and prerendered pages; the CDN still answers first.\n binding: 'ASSETS',\n },\n },\n null,\n 2,\n )}\\n`;\n}\n\n/**\n * Arranges a Workers deployment: one assets directory holding everything the CDN should serve, and a\n * `wrangler.jsonc` to deploy with if the project has no Wrangler config of its own.\n */\nexport async function finalizeCloudflareBuild(ctx: DeployBuildContext): Promise<void> {\n const assetsDir = join(ctx.distDir, ASSETS_DIR);\n // Rebuilt from scratch, so a file deleted from `public/` or a route that stopped being static does\n // not survive in the deployed tree.\n await rm(assetsDir, { recursive: true, force: true });\n mkdirSync(assetsDir, { recursive: true });\n\n cpSync(ctx.staticDir, join(assetsDir, '_static'), { recursive: true });\n if (ctx.publicDir) cpSync(ctx.publicDir, assetsDir, { recursive: true });\n if (existsSync(ctx.ssgDir)) cpSync(ctx.ssgDir, join(assetsDir, '__ssg'), { recursive: true });\n\n const headersFile = join(assetsDir, '_headers');\n if (existsSync(headersFile)) {\n console.warn(` ⚠ public/_headers is in the way of the generated one — /_static/* will not be marked immutable.`);\n } else {\n writeFileSync(headersFile, HEADERS_FILE);\n }\n\n const existing = WRANGLER_FILES.find((file) => existsSync(join(ctx.rootDir, file)));\n if (!existing) {\n writeFileSync(join(ctx.rootDir, 'wrangler.jsonc'), wranglerConfig(ctx.rootDir));\n console.log(' • wrote wrangler.jsonc (yours now — edit freely, the build will not touch it again)');\n }\n\n console.log(` • assembled ${join('dist', ASSETS_DIR)} for Workers Assets`);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/deploy/cloudflare/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/deploy/cloudflare/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAuDpD;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,aAgDrB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { prerenderedRelPath } from '../../server/prerendered.js';
|
|
1
|
+
import { createPageCache, prerenderedRelPath, toPrerenderedPage } from '../../server/prerendered.js';
|
|
2
2
|
/**
|
|
3
3
|
* Where `finalize` puts the prerendered pages inside the assets directory.
|
|
4
4
|
*
|
|
@@ -41,27 +41,8 @@ async function assetResponse(c, path, headers) {
|
|
|
41
41
|
function serveAsset(asset) {
|
|
42
42
|
return new Response(asset.body, asset);
|
|
43
43
|
}
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
*
|
|
47
|
-
* Web Crypto rather than `node:crypto`: this module is compiled for `workerd`, and the digest is the
|
|
48
|
-
* one thing here that has a platform-neutral form.
|
|
49
|
-
*/
|
|
50
|
-
async function weakEtag(body) {
|
|
51
|
-
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(body));
|
|
52
|
-
const bytes = String.fromCharCode(...new Uint8Array(digest));
|
|
53
|
-
const base64url = btoa(bytes).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
54
|
-
return `W/"${base64url.slice(0, 22)}"`;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Prerendered pages, keyed by the path that produced them.
|
|
58
|
-
*
|
|
59
|
-
* Per isolate rather than per process, and bounded for the same reason the Node cache is: a site with
|
|
60
|
-
* thousands of prerendered pages should keep a working set, not the whole build. Only *hits* are
|
|
61
|
-
* cached, so nobody can mint entries by requesting paths that do not exist.
|
|
62
|
-
*/
|
|
63
|
-
const pageCache = new Map();
|
|
64
|
-
const MAX_CACHED_PAGES = 128;
|
|
44
|
+
/** Prerendered pages, keyed by the path that produced them. Per isolate rather than per process. */
|
|
45
|
+
const pageCache = createPageCache();
|
|
65
46
|
/**
|
|
66
47
|
* Cloudflare Workers: the host owns the process, so there is nothing to listen on; the CDN owns the
|
|
67
48
|
* assets, so there is nothing to serve from disk; and there is no filesystem, so `.env` files and a
|
|
@@ -72,7 +53,7 @@ const MAX_CACHED_PAGES = 128;
|
|
|
72
53
|
export const runtime = {
|
|
73
54
|
serveApp(app) {
|
|
74
55
|
// What Workers looks for on the default export. `app.fetch` already takes `(request, env, ctx)`,
|
|
75
|
-
// which is why bindings arrive as `c.env` — and so why `
|
|
56
|
+
// which is why bindings arrive as `c.env` — and so why `getRequestContext().env` sees them.
|
|
76
57
|
return { fetch: app.fetch };
|
|
77
58
|
},
|
|
78
59
|
mountStaticAssets(app) {
|
|
@@ -105,25 +86,14 @@ export const runtime = {
|
|
|
105
86
|
const asset = await assetResponse(c, `${SSG_PREFIX}/${relPath}`);
|
|
106
87
|
if (!asset || asset.status !== 200)
|
|
107
88
|
return null;
|
|
108
|
-
|
|
109
|
-
const page =
|
|
110
|
-
body,
|
|
111
|
-
contentLength: String(new TextEncoder().encode(body).byteLength),
|
|
112
|
-
// The store's own validator where it has one — it already describes these exact bytes. Weakened
|
|
113
|
-
// because compression downstream would make a strong one wrong for the compressed encoding.
|
|
114
|
-
etag: asset.headers.get('etag')?.replace(/^(?!W\/)/, 'W/') ?? (await weakEtag(body)),
|
|
115
|
-
};
|
|
89
|
+
// The store's own validator where it has one — it already describes these exact bytes.
|
|
90
|
+
const page = await toPrerenderedPage(new Uint8Array(await asset.arrayBuffer()), asset.headers.get('etag'));
|
|
116
91
|
pageCache.set(key, page);
|
|
117
|
-
if (pageCache.size > MAX_CACHED_PAGES)
|
|
118
|
-
pageCache.delete(pageCache.keys().next().value);
|
|
119
92
|
return page;
|
|
120
93
|
},
|
|
121
|
-
// Cloudflare compresses on the way out at the edge, and `workerd` has no `node:zlib` stream to do
|
|
122
|
-
// it with anyway. Doing it here would only spend CPU on bytes the edge is about to re-encode.
|
|
123
|
-
compress: null,
|
|
124
94
|
loadEnv() {
|
|
125
95
|
// Nothing to load: a Worker has no filesystem and no `.env`. Secrets and bindings arrive per
|
|
126
|
-
// request as `c.env`, which `
|
|
96
|
+
// request as `c.env`, which `getRequestContext().env` already merges — see `runtime/context.ts`.
|
|
127
97
|
},
|
|
128
98
|
};
|
|
129
99
|
//# sourceMappingURL=runtime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/deploy/cloudflare/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context, Hono } from 'hono';\nimport { prerenderedRelPath, type PrerenderedPage } from '../../server/prerendered.js';\nimport type { DeployRuntime } from '../contract.js';\n\n/**\n * Where `finalize` puts the prerendered pages inside the assets directory.\n *\n * A prefix of its own, rather than the pages' real URLs, is what keeps every page URL reaching the\n * worker: one URL answers with a document or a flight payload depending on `Accept`, and a CDN keyed\n * on the path alone cannot make that choice. Assets miss, the worker runs, the worker negotiates.\n *\n * The bytes are public either way — they are a public page — but they are also reachable under this\n * path, so `finalize` writes a `_headers` rule marking the tree `noindex` rather than leaving a\n * crawler to find the same page twice.\n */\nconst SSG_PREFIX = '/__ssg';\n\n/** The Workers Assets binding, as much of it as this file uses. */\ninterface AssetsBinding {\n fetch(request: Request): Promise<Response>;\n}\n\nfunction assetsBinding(c: Context): AssetsBinding | null {\n const binding = (c.env as { ASSETS?: AssetsBinding } | undefined)?.ASSETS;\n return binding && typeof binding.fetch === 'function' ? binding : null;\n}\n\n/**\n * Asks the assets binding for one path, or `null` when there is no binding at all — so a caller can\n * tell \"this deployment serves its assets some other way\" from \"that file is not there\".\n *\n * `headers` is passed on only where the *client's* conditional and range requests should be answered\n * by the store: forwarding an `If-None-Match` while fetching a page to serve would come back 304 with\n * no body, which is a useless answer to \"give me this page\".\n */\nasync function assetResponse(c: Context, path: string, headers?: Headers): Promise<Response | null> {\n const binding = assetsBinding(c);\n if (!binding) return null;\n // Resolved against the request so the asset request carries this deployment's own origin; only the\n // path is ever used to look one up.\n const url = new URL(path, c.req.url);\n return binding.fetch(new Request(url, { method: 'GET', headers }));\n}\n\n/**\n * Hands an asset response back as the app's own.\n *\n * Rebuilt rather than returned as-is because a `Response` that came from `fetch` has immutable\n * headers, and the framework's outermost middleware sets its baseline security headers on the way out.\n */\nfunction serveAsset(asset: Response): Response {\n return new Response(asset.body, asset);\n}\n\n/**\n * A weak `ETag` for a body the asset store gave us no validator for.\n *\n * Web Crypto rather than `node:crypto`: this module is compiled for `workerd`, and the digest is the\n * one thing here that has a platform-neutral form.\n */\nasync function weakEtag(body: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(body));\n const bytes = String.fromCharCode(...new Uint8Array(digest));\n const base64url = btoa(bytes).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n return `W/\"${base64url.slice(0, 22)}\"`;\n}\n\n/**\n * Prerendered pages, keyed by the path that produced them.\n *\n * Per isolate rather than per process, and bounded for the same reason the Node cache is: a site with\n * thousands of prerendered pages should keep a working set, not the whole build. Only *hits* are\n * cached, so nobody can mint entries by requesting paths that do not exist.\n */\nconst pageCache = new Map<string, PrerenderedPage>();\nconst MAX_CACHED_PAGES = 128;\n\n/**\n * Cloudflare Workers: the host owns the process, so there is nothing to listen on; the CDN owns the\n * assets, so there is nothing to serve from disk; and there is no filesystem, so `.env` files and a\n * streaming gzip are both out.\n *\n * What is left is the assets binding, which every read here goes through.\n */\nexport const runtime: DeployRuntime = {\n serveApp(app: Hono): unknown {\n // What Workers looks for on the default export. `app.fetch` already takes `(request, env, ctx)`,\n // which is why bindings arrive as `c.env` — and so why `getContext().env` sees them.\n return { fetch: app.fetch };\n },\n\n mountStaticAssets(app: Hono): void {\n // Normally dead: with the scaffolded config the CDN answers `/_static/*` before the worker is\n // invoked at all. It exists so the deployment is still correct — if slower — under an assets\n // configuration that routes everything to the worker first.\n app.on(['GET', 'HEAD'], '/_static/*', async (c, next) => {\n const asset = await assetResponse(c, c.req.path, c.req.raw.headers);\n return asset && asset.status !== 404 ? serveAsset(asset) : next();\n });\n },\n\n mountPublicFallback(app: Hono): void {\n app.on(['GET', 'HEAD'], '/*', async (c, next) => {\n // The prerender tree lives in the same store; it is reachable directly from the CDN by design,\n // but the app itself only ever serves it through `readPrerendered`, negotiated on `Accept`.\n if (c.req.path.startsWith(`${SSG_PREFIX}/`)) return next();\n const asset = await assetResponse(c, c.req.path, c.req.raw.headers);\n return asset && asset.status !== 404 ? serveAsset(asset) : next();\n });\n },\n\n async readPrerendered(c: Context, variant): Promise<PrerenderedPage | null> {\n const relPath = prerenderedRelPath(c.req.path, variant);\n if (relPath === null) return null;\n\n const key = `${variant}\\0${relPath}`;\n const cached = pageCache.get(key);\n if (cached) return cached;\n\n const asset = await assetResponse(c, `${SSG_PREFIX}/${relPath}`);\n if (!asset || asset.status !== 200) return null;\n\n const body = await asset.text();\n const page: PrerenderedPage = {\n body,\n contentLength: String(new TextEncoder().encode(body).byteLength),\n // The store's own validator where it has one — it already describes these exact bytes. Weakened\n // because compression downstream would make a strong one wrong for the compressed encoding.\n etag: asset.headers.get('etag')?.replace(/^(?!W\\/)/, 'W/') ?? (await weakEtag(body)),\n };\n\n pageCache.set(key, page);\n if (pageCache.size > MAX_CACHED_PAGES) pageCache.delete(pageCache.keys().next().value!);\n return page;\n },\n\n // Cloudflare compresses on the way out at the edge, and `workerd` has no `node:zlib` stream to do\n // it with anyway. Doing it here would only spend CPU on bytes the edge is about to re-encode.\n compress: null,\n\n loadEnv(): void {\n // Nothing to load: a Worker has no filesystem and no `.env`. Secrets and bindings arrive per\n // request as `c.env`, which `getContext().env` already merges — see `runtime/context.ts`.\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/deploy/cloudflare/runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAwB,MAAM,6BAA6B,CAAC;AAG3H;;;;;;;;;;GAUG;AACH,MAAM,UAAU,GAAG,QAAQ,CAAC;AAO5B,SAAS,aAAa,CAAC,CAAU;IAC/B,MAAM,OAAO,GAAI,CAAC,CAAC,GAA8C,EAAE,MAAM,CAAC;IAC1E,OAAO,OAAO,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AACzE,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,aAAa,CAAC,CAAU,EAAE,IAAY,EAAE,OAAiB;IACtE,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IACjC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,mGAAmG;IACnG,oCAAoC;IACpC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AACrE,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,KAAe;IACjC,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACzC,CAAC;AAED,oGAAoG;AACpG,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;AAEpC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAkB;IACpC,QAAQ,CAAC,GAAS;QAChB,iGAAiG;QACjG,4FAA4F;QAC5F,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;IAC9B,CAAC;IAED,iBAAiB,CAAC,GAAS;QACzB,8FAA8F;QAC9F,6FAA6F;QAC7F,4DAA4D;QAC5D,GAAG,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;YACtD,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACpE,OAAO,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACpE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,mBAAmB,CAAC,GAAS;QAC3B,GAAG,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;YAC9C,+FAA+F;YAC/F,4FAA4F;YAC5F,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,UAAU,GAAG,CAAC;gBAAE,OAAO,IAAI,EAAE,CAAC;YAC3D,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACpE,OAAO,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACpE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,CAAU,EAAE,OAAO;QACvC,MAAM,OAAO,GAAG,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxD,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAElC,MAAM,GAAG,GAAG,GAAG,OAAO,KAAK,OAAO,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAE1B,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,CAAC,EAAE,GAAG,UAAU,IAAI,OAAO,EAAE,CAAC,CAAC;QACjE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAEhD,uFAAuF;QACvF,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAC3G,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,6FAA6F;QAC7F,iGAAiG;IACnG,CAAC;CACF,CAAC","sourcesContent":["import type { Context, Hono } from 'hono';\nimport { createPageCache, prerenderedRelPath, toPrerenderedPage, type PrerenderedPage } from '../../server/prerendered.js';\nimport type { DeployRuntime } from '../contract.js';\n\n/**\n * Where `finalize` puts the prerendered pages inside the assets directory.\n *\n * A prefix of its own, rather than the pages' real URLs, is what keeps every page URL reaching the\n * worker: one URL answers with a document or a flight payload depending on `Accept`, and a CDN keyed\n * on the path alone cannot make that choice. Assets miss, the worker runs, the worker negotiates.\n *\n * The bytes are public either way — they are a public page — but they are also reachable under this\n * path, so `finalize` writes a `_headers` rule marking the tree `noindex` rather than leaving a\n * crawler to find the same page twice.\n */\nconst SSG_PREFIX = '/__ssg';\n\n/** The Workers Assets binding, as much of it as this file uses. */\ninterface AssetsBinding {\n fetch(request: Request): Promise<Response>;\n}\n\nfunction assetsBinding(c: Context): AssetsBinding | null {\n const binding = (c.env as { ASSETS?: AssetsBinding } | undefined)?.ASSETS;\n return binding && typeof binding.fetch === 'function' ? binding : null;\n}\n\n/**\n * Asks the assets binding for one path, or `null` when there is no binding at all — so a caller can\n * tell \"this deployment serves its assets some other way\" from \"that file is not there\".\n *\n * `headers` is passed on only where the *client's* conditional and range requests should be answered\n * by the store: forwarding an `If-None-Match` while fetching a page to serve would come back 304 with\n * no body, which is a useless answer to \"give me this page\".\n */\nasync function assetResponse(c: Context, path: string, headers?: Headers): Promise<Response | null> {\n const binding = assetsBinding(c);\n if (!binding) return null;\n // Resolved against the request so the asset request carries this deployment's own origin; only the\n // path is ever used to look one up.\n const url = new URL(path, c.req.url);\n return binding.fetch(new Request(url, { method: 'GET', headers }));\n}\n\n/**\n * Hands an asset response back as the app's own.\n *\n * Rebuilt rather than returned as-is because a `Response` that came from `fetch` has immutable\n * headers, and the framework's outermost middleware sets its baseline security headers on the way out.\n */\nfunction serveAsset(asset: Response): Response {\n return new Response(asset.body, asset);\n}\n\n/** Prerendered pages, keyed by the path that produced them. Per isolate rather than per process. */\nconst pageCache = createPageCache();\n\n/**\n * Cloudflare Workers: the host owns the process, so there is nothing to listen on; the CDN owns the\n * assets, so there is nothing to serve from disk; and there is no filesystem, so `.env` files and a\n * streaming gzip are both out.\n *\n * What is left is the assets binding, which every read here goes through.\n */\nexport const runtime: DeployRuntime = {\n serveApp(app: Hono): unknown {\n // What Workers looks for on the default export. `app.fetch` already takes `(request, env, ctx)`,\n // which is why bindings arrive as `c.env` — and so why `getRequestContext().env` sees them.\n return { fetch: app.fetch };\n },\n\n mountStaticAssets(app: Hono): void {\n // Normally dead: with the scaffolded config the CDN answers `/_static/*` before the worker is\n // invoked at all. It exists so the deployment is still correct — if slower — under an assets\n // configuration that routes everything to the worker first.\n app.on(['GET', 'HEAD'], '/_static/*', async (c, next) => {\n const asset = await assetResponse(c, c.req.path, c.req.raw.headers);\n return asset && asset.status !== 404 ? serveAsset(asset) : next();\n });\n },\n\n mountPublicFallback(app: Hono): void {\n app.on(['GET', 'HEAD'], '/*', async (c, next) => {\n // The prerender tree lives in the same store; it is reachable directly from the CDN by design,\n // but the app itself only ever serves it through `readPrerendered`, negotiated on `Accept`.\n if (c.req.path.startsWith(`${SSG_PREFIX}/`)) return next();\n const asset = await assetResponse(c, c.req.path, c.req.raw.headers);\n return asset && asset.status !== 404 ? serveAsset(asset) : next();\n });\n },\n\n async readPrerendered(c: Context, variant): Promise<PrerenderedPage | null> {\n const relPath = prerenderedRelPath(c.req.path, variant);\n if (relPath === null) return null;\n\n const key = `${variant}\\0${relPath}`;\n const cached = pageCache.get(key);\n if (cached) return cached;\n\n const asset = await assetResponse(c, `${SSG_PREFIX}/${relPath}`);\n if (!asset || asset.status !== 200) return null;\n\n // The store's own validator where it has one — it already describes these exact bytes.\n const page = await toPrerenderedPage(new Uint8Array(await asset.arrayBuffer()), asset.headers.get('etag'));\n pageCache.set(key, page);\n return page;\n },\n\n loadEnv(): void {\n // Nothing to load: a Worker has no filesystem and no `.env`. Secrets and bindings arrive per\n // request as `c.env`, which `getRequestContext().env` already merges — see `runtime/context.ts`.\n },\n};\n"]}
|
|
@@ -1,13 +1,26 @@
|
|
|
1
|
-
import type { Context, Hono
|
|
1
|
+
import type { Context, Hono } from 'hono';
|
|
2
2
|
import type { PrerenderVariant, PrerenderedPage } from '../server/prerendered.js';
|
|
3
3
|
/**
|
|
4
|
-
* A hosting platform rshono
|
|
4
|
+
* A hosting platform `rshono build` targets. Selected with `deploy` in `rshono.config.ts`, the
|
|
5
5
|
* `--deploy` flag or the `RSHONO_DEPLOY` env var, and resolved to a preset by `deploy/presets.ts`.
|
|
6
6
|
*
|
|
7
|
+
* - `'node'` (the default) — rshono binds the port itself and you run the build with `rshono start`.
|
|
8
|
+
* Covers a VPS, a container, a PaaS, and — through `node:` compatibility — Bun and Deno.
|
|
9
|
+
* - `'cloudflare'` — a Worker; the entry exports `{ fetch }`.
|
|
10
|
+
* - `'vercel'` — a Vercel function, plus the on-disk layout and config file streaming needs there.
|
|
11
|
+
* - `'aws-lambda'` — a streaming Lambda handler.
|
|
12
|
+
*
|
|
13
|
+
* One entry per *handoff* — who opens the socket, and what shape a request arrives in — because that
|
|
14
|
+
* is the part an app cannot arrange for itself. There is deliberately no target whose only content
|
|
15
|
+
* would be "run the Node build", which is why Bun and Deno have none: importing the `node` bundle
|
|
16
|
+
* under those runtimes is the whole of what one would say.
|
|
17
|
+
*
|
|
7
18
|
* `rshono dev` always runs the `node` server whatever this says — the dev server owns the process,
|
|
8
19
|
* watches both compilers and fronts them on one port, none of which a hosting platform provides.
|
|
20
|
+
*
|
|
21
|
+
* @see {@link https://www.rshono.com/docs/deployment#the-targets | Docs — the targets}
|
|
9
22
|
*/
|
|
10
|
-
export type DeployTarget = 'node' | 'cloudflare' | '
|
|
23
|
+
export type DeployTarget = 'node' | 'cloudflare' | 'vercel' | 'aws-lambda';
|
|
11
24
|
/**
|
|
12
25
|
* Everything the app server needs from the platform it is running on.
|
|
13
26
|
*
|
|
@@ -16,18 +29,18 @@ export type DeployTarget = 'node' | 'cloudflare' | 'bun' | 'deno' | 'vercel' | '
|
|
|
16
29
|
* ever in the bundle. `runtime/entry.rsc.tsx` is written against this interface and nothing else —
|
|
17
30
|
* it is the whole of what "which platform is this" means at request time.
|
|
18
31
|
*
|
|
19
|
-
* The
|
|
20
|
-
*
|
|
21
|
-
*
|
|
32
|
+
* The members are the capabilities that genuinely differ between a host with a disk and one without:
|
|
33
|
+
* who opens the socket, who serves the assets, where a prerendered page is read from, and whether
|
|
34
|
+
* there is a `.env` to load at all.
|
|
22
35
|
*/
|
|
23
36
|
export interface DeployRuntime {
|
|
24
37
|
/**
|
|
25
38
|
* Hands the assembled app to the platform, and returns whatever the entry module should
|
|
26
39
|
* `export default` there.
|
|
27
40
|
*
|
|
28
|
-
* The two shapes hosting takes, in one call: where rshono owns the process
|
|
29
|
-
*
|
|
30
|
-
*
|
|
41
|
+
* The two shapes hosting takes, in one call: where rshono owns the process this binds a port and
|
|
42
|
+
* returns nothing; where the host owns it, it returns the export the platform looks for —
|
|
43
|
+
* `{ fetch }` on Workers, a handler function on Vercel and Lambda.
|
|
31
44
|
*/
|
|
32
45
|
serveApp(app: Hono): unknown;
|
|
33
46
|
/**
|
|
@@ -52,13 +65,6 @@ export interface DeployRuntime {
|
|
|
52
65
|
* (see `prerenderedRelPath`).
|
|
53
66
|
*/
|
|
54
67
|
readPrerendered(c: Context, variant: PrerenderVariant): Promise<PrerenderedPage | null>;
|
|
55
|
-
/**
|
|
56
|
-
* Response compression, or `null` where the platform already compresses on the way out (and
|
|
57
|
-
* doing it twice would only cost CPU) or cannot stream a compressor at all.
|
|
58
|
-
*
|
|
59
|
-
* Whether it is *used* is still the app's `compress` setting; this is only whether it is available.
|
|
60
|
-
*/
|
|
61
|
-
compress: MiddlewareHandler | null;
|
|
62
68
|
/** Loads `.env` files, where the platform has a filesystem to read them from. Env is bindings elsewhere. */
|
|
63
69
|
loadEnv(): void;
|
|
64
70
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/deploy/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/deploy/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAElF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE3E;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,iBAAiB,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,CAAC;IACnC;;;OAGG;IACH,mBAAmB,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,CAAC;IACrC;;;;;;;;OAQG;IACH,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACxF,4GAA4G;IAC5G,OAAO,IAAI,IAAI,CAAC;CACjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/deploy/contract.ts"],"names":[],"mappings":"","sourcesContent":["import type { Context, Hono
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/deploy/contract.ts"],"names":[],"mappings":"","sourcesContent":["import type { Context, Hono } from 'hono';\nimport type { PrerenderVariant, PrerenderedPage } from '../server/prerendered.js';\n\n/**\n * A hosting platform `rshono build` targets. Selected with `deploy` in `rshono.config.ts`, the\n * `--deploy` flag or the `RSHONO_DEPLOY` env var, and resolved to a preset by `deploy/presets.ts`.\n *\n * - `'node'` (the default) — rshono binds the port itself and you run the build with `rshono start`.\n * Covers a VPS, a container, a PaaS, and — through `node:` compatibility — Bun and Deno.\n * - `'cloudflare'` — a Worker; the entry exports `{ fetch }`.\n * - `'vercel'` — a Vercel function, plus the on-disk layout and config file streaming needs there.\n * - `'aws-lambda'` — a streaming Lambda handler.\n *\n * One entry per *handoff* — who opens the socket, and what shape a request arrives in — because that\n * is the part an app cannot arrange for itself. There is deliberately no target whose only content\n * would be \"run the Node build\", which is why Bun and Deno have none: importing the `node` bundle\n * under those runtimes is the whole of what one would say.\n *\n * `rshono dev` always runs the `node` server whatever this says — the dev server owns the process,\n * watches both compilers and fronts them on one port, none of which a hosting platform provides.\n *\n * @see {@link https://www.rshono.com/docs/deployment#the-targets | Docs — the targets}\n */\nexport type DeployTarget = 'node' | 'cloudflare' | 'vercel' | 'aws-lambda';\n\n/**\n * Everything the app server needs from the platform it is running on.\n *\n * One preset implements this per target, and the `@rshono/deploy` alias resolves to exactly that\n * module at build time (see `builder/rspack-config.ts`), so only the selected platform's code is\n * ever in the bundle. `runtime/entry.rsc.tsx` is written against this interface and nothing else —\n * it is the whole of what \"which platform is this\" means at request time.\n *\n * The members are the capabilities that genuinely differ between a host with a disk and one without:\n * who opens the socket, who serves the assets, where a prerendered page is read from, and whether\n * there is a `.env` to load at all.\n */\nexport interface DeployRuntime {\n /**\n * Hands the assembled app to the platform, and returns whatever the entry module should\n * `export default` there.\n *\n * The two shapes hosting takes, in one call: where rshono owns the process this binds a port and\n * returns nothing; where the host owns it, it returns the export the platform looks for —\n * `{ fetch }` on Workers, a handler function on Vercel and Lambda.\n */\n serveApp(app: Hono): unknown;\n /**\n * Mounts the hashed client bundle at `/_static`. A no-op where the platform's own CDN serves it\n * before a request ever reaches the app.\n *\n * Called *before* the app's routes, so the bundle is never shadowed by one.\n */\n mountStaticAssets(app: Hono): void;\n /**\n * Mounts the `public/` fallback at the web root — files served verbatim, and only for paths no\n * route claimed. Called *after* every route for exactly that reason.\n */\n mountPublicFallback(app: Hono): void;\n /**\n * Reads the page prerendered for `c.req.path` by `rshono build`, or `null` when there is none (in\n * which case the route renders per request, which is always a valid answer).\n *\n * Takes the whole {@link Context} rather than just the path because on a platform with no\n * filesystem the store *is* a request-scoped binding — `c.env.ASSETS` on Workers. The path is\n * untrusted either way, so an implementation has to treat traversal as a miss, not a lookup\n * (see `prerenderedRelPath`).\n */\n readPrerendered(c: Context, variant: PrerenderVariant): Promise<PrerenderedPage | null>;\n /** Loads `.env` files, where the platform has a filesystem to read them from. Env is bindings elsewhere. */\n loadEnv(): void;\n}\n"]}
|
|
@@ -2,7 +2,7 @@ import type { DeployRuntime } from './contract.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Everything a deploy target with a real filesystem does the same way.
|
|
4
4
|
*
|
|
5
|
-
* Node
|
|
5
|
+
* Node and the serverless runtimes that unpack a bundle onto a read-only disk (Vercel, AWS Lambda)
|
|
6
6
|
* differ only in how the finished app is handed over — so each of those presets is this object plus
|
|
7
7
|
* its own {@link DeployRuntime.serveApp}, and the implementations live once, in `server/`.
|
|
8
8
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/deploy/filesystem.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/deploy/filesystem.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAoBnD;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,CAiB7D,CAAC"}
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { compress } from '../server/compress.js';
|
|
4
3
|
import { loadEnvFiles } from '../server/load-env.js';
|
|
5
4
|
import { readPrerendered } from '../server/ssg.js';
|
|
6
5
|
import { createPublicFallback, createStaticAssetsApp } from '../server/static.js';
|
|
7
6
|
const isDev = __RSHONO_CONFIG__.isDev;
|
|
8
7
|
/**
|
|
9
|
-
* The project root, derived from where the bundle
|
|
10
|
-
*
|
|
11
|
-
* `import.meta.dirname` `<root>/dist/server` at runtime.
|
|
8
|
+
* The project root, derived from where the bundle ended up: this module is compiled into
|
|
9
|
+
* `<root>/dist/server/main.mjs`, so `import.meta.dirname` is `<root>/dist/server` at runtime.
|
|
12
10
|
*
|
|
13
|
-
* Derived rather than baked in at build time
|
|
14
|
-
* output to the machine that produced it
|
|
15
|
-
* A preset that relocates the bundle keeps `dist/server/main.mjs` intact inside its own layout
|
|
16
|
-
* exactly this reason.
|
|
11
|
+
* Derived rather than baked in at build time, because an absolute build-time path would tie the
|
|
12
|
+
* output to the machine that produced it — and building in CI to run somewhere else is the normal
|
|
13
|
+
* case. A preset that relocates the bundle keeps `dist/server/main.mjs` intact inside its own layout
|
|
14
|
+
* for exactly this reason.
|
|
17
15
|
*/
|
|
18
16
|
const rootDir = join(import.meta.dirname, '..', '..');
|
|
19
17
|
const staticDir = join(rootDir, 'dist', 'static');
|
|
@@ -23,7 +21,7 @@ const publicDir = isDev ? join(rootDir, 'public') : join(rootDir, 'dist', 'publi
|
|
|
23
21
|
/**
|
|
24
22
|
* Everything a deploy target with a real filesystem does the same way.
|
|
25
23
|
*
|
|
26
|
-
* Node
|
|
24
|
+
* Node and the serverless runtimes that unpack a bundle onto a read-only disk (Vercel, AWS Lambda)
|
|
27
25
|
* differ only in how the finished app is handed over — so each of those presets is this object plus
|
|
28
26
|
* its own {@link DeployRuntime.serveApp}, and the implementations live once, in `server/`.
|
|
29
27
|
*/
|
|
@@ -39,7 +37,6 @@ export const fileSystemRuntime = {
|
|
|
39
37
|
readPrerendered(c, variant) {
|
|
40
38
|
return readPrerendered(ssgDir, c.req.path, variant);
|
|
41
39
|
},
|
|
42
|
-
compress: compress(),
|
|
43
40
|
loadEnv() {
|
|
44
41
|
loadEnvFiles(rootDir);
|
|
45
42
|
},
|