@rshono/core 1.0.0-rc.6 → 1.0.0-rc.8

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.
Files changed (88) hide show
  1. package/README.md +195 -168
  2. package/dist/builder/page-files.d.ts.map +1 -1
  3. package/dist/builder/page-files.js +7 -3
  4. package/dist/builder/page-files.js.map +1 -1
  5. package/dist/builder/public-env.d.ts +6 -0
  6. package/dist/builder/public-env.d.ts.map +1 -1
  7. package/dist/builder/public-env.js +6 -0
  8. package/dist/builder/public-env.js.map +1 -1
  9. package/dist/builder/rspack-config.d.ts.map +1 -1
  10. package/dist/builder/rspack-config.js +16 -19
  11. package/dist/builder/rspack-config.js.map +1 -1
  12. package/dist/cli/dev.d.ts.map +1 -1
  13. package/dist/cli/dev.js +63 -35
  14. package/dist/cli/dev.js.map +1 -1
  15. package/dist/cli/index.js +3 -4
  16. package/dist/cli/index.js.map +1 -1
  17. package/dist/config.d.ts +56 -55
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js +17 -1
  20. package/dist/config.js.map +1 -1
  21. package/dist/deploy/contract.d.ts +15 -10
  22. package/dist/deploy/contract.d.ts.map +1 -1
  23. package/dist/deploy/contract.js.map +1 -1
  24. package/dist/deploy/filesystem.d.ts.map +1 -1
  25. package/dist/deploy/filesystem.js +6 -7
  26. package/dist/deploy/filesystem.js.map +1 -1
  27. package/dist/deploy/node/runtime.d.ts +5 -5
  28. package/dist/deploy/node/runtime.d.ts.map +1 -1
  29. package/dist/deploy/node/runtime.js +7 -11
  30. package/dist/deploy/node/runtime.js.map +1 -1
  31. package/dist/deploy/presets.d.ts +2 -6
  32. package/dist/deploy/presets.d.ts.map +1 -1
  33. package/dist/deploy/presets.js +2 -6
  34. package/dist/deploy/presets.js.map +1 -1
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +5 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/router.d.ts +74 -36
  40. package/dist/router.d.ts.map +1 -1
  41. package/dist/router.js +2 -3
  42. package/dist/router.js.map +1 -1
  43. package/dist/runtime/boundaries.d.ts +10 -0
  44. package/dist/runtime/boundaries.d.ts.map +1 -1
  45. package/dist/runtime/boundaries.js +6 -0
  46. package/dist/runtime/boundaries.js.map +1 -1
  47. package/dist/runtime/client.d.ts +5 -3
  48. package/dist/runtime/client.d.ts.map +1 -1
  49. package/dist/runtime/client.js +5 -3
  50. package/dist/runtime/client.js.map +1 -1
  51. package/dist/runtime/context.d.ts +195 -45
  52. package/dist/runtime/context.d.ts.map +1 -1
  53. package/dist/runtime/context.js +277 -59
  54. package/dist/runtime/context.js.map +1 -1
  55. package/dist/runtime/control.d.ts.map +1 -1
  56. package/dist/runtime/control.js +7 -0
  57. package/dist/runtime/control.js.map +1 -1
  58. package/dist/runtime/entry.client.js +29 -47
  59. package/dist/runtime/entry.client.js.map +1 -1
  60. package/dist/runtime/entry.rsc.d.ts.map +1 -1
  61. package/dist/runtime/entry.rsc.js +109 -140
  62. package/dist/runtime/entry.rsc.js.map +1 -1
  63. package/dist/runtime/flight-inject.d.ts +4 -4
  64. package/dist/runtime/flight-inject.js +2 -2
  65. package/dist/runtime/flight-inject.js.map +1 -1
  66. package/dist/runtime/navigation.d.ts +11 -0
  67. package/dist/runtime/navigation.d.ts.map +1 -1
  68. package/dist/runtime/navigation.js +3 -0
  69. package/dist/runtime/navigation.js.map +1 -1
  70. package/dist/runtime/request.d.ts +3 -3
  71. package/dist/runtime/request.js +3 -3
  72. package/dist/runtime/request.js.map +1 -1
  73. package/dist/runtime/server.d.ts +7 -12
  74. package/dist/runtime/server.d.ts.map +1 -1
  75. package/dist/runtime/server.js +15 -12
  76. package/dist/runtime/server.js.map +1 -1
  77. package/dist/server/prerendered.d.ts +20 -35
  78. package/dist/server/prerendered.d.ts.map +1 -1
  79. package/dist/server/prerendered.js +8 -18
  80. package/dist/server/prerendered.js.map +1 -1
  81. package/dist/server/server-config.d.ts +10 -29
  82. package/dist/server/server-config.d.ts.map +1 -1
  83. package/dist/server/server-config.js +4 -70
  84. package/dist/server/server-config.js.map +1 -1
  85. package/dist/server/ssg.d.ts.map +1 -1
  86. package/dist/server/ssg.js +10 -11
  87. package/dist/server/ssg.js.map +1 -1
  88. package/package.json +1 -1
package/README.md CHANGED
@@ -1,13 +1,26 @@
1
- # rshono
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/rshono/rshono/main/logo.svg" alt="" width="72" height="72" />
3
+ </p>
2
4
 
3
- Minimalist web framework — [Hono](https://hono.dev) + [Rspack](https://rspack.rs) + [React Server Components](https://react.dev/reference/rsc/server-components).
5
+ <h1 align="center">@rshono/core</h1>
4
6
 
5
- > **Alpha.** The framework itself is covered by an end-to-end suite (see [Testing](#testing)), but it
6
- > is built on Rspack's experimental RSC support (`rspack.experiments.rsc`) and `react-server-dom-rspack`,
7
- > which is still `0.0.x`. Those two move underneath us, so `@rspack/core` and `react-server-dom-rspack`
8
- > are pinned to exact versions and a release of rshono is what moves them.
7
+ <p align="center">
8
+ Minimalist web framework
9
+ <a href="https://hono.dev">Hono</a> +
10
+ <a href="https://rspack.rs">Rspack</a> +
11
+ <a href="https://react.dev/reference/rsc/server-components">React Server Components</a>.
12
+ </p>
9
13
 
10
- One required file (`src/routes.ts`), one optional file (`src/server.ts`), and you get a dev server with HMR, streaming SSR with RSC hydration, server actions with progressive enhancement, soft navigation, build-time prerendering, and hard env/secret safety.
14
+ One required file (`src/routes.ts`), one optional file (`src/server.ts`), and you get a dev server with HMR,
15
+ streaming SSR with RSC hydration, server actions with progressive enhancement, soft navigation, build-time
16
+ prerendering, and hard env/secret safety.
17
+
18
+ > **Alpha.** The framework has an end-to-end suite of its own (see [Testing](#testing)), but it is built on
19
+ > Rspack's experimental RSC support (`rspack.experiments.rsc`) and `react-server-dom-rspack`, which is still
20
+ > `0.0.x`. Those two move underneath us, so `@rspack/core` and `react-server-dom-rspack` are pinned to exact
21
+ > versions and a release of rshono is what moves them.
22
+
23
+ **Full documentation: [rshono.com/docs](https://www.rshono.com/docs).**
11
24
 
12
25
  ```bash
13
26
  npx @rshono/create@latest my-app # scaffold one, with a deploy target and tooling of your choosing
@@ -15,22 +28,26 @@ npx @rshono/create@latest my-app # scaffold one, with a deploy target and tool
15
28
 
16
29
  ```bash
17
30
  rshono dev # dev server with HMR (default port 3000)
18
- rshono build # production build: client + server bundles + SSG
31
+ rshono build # production build: client + server bundles + prerendered pages
19
32
  rshono start # run the production build
20
33
  ```
21
34
 
35
+ `--port` / `PORT` and `HOST` set where it listens; `--config <path>` points `build` at another config file.
36
+
22
37
  ## Project layout
23
38
 
24
39
  ```
25
- rshono.config.ts optional — every field has a default (see Configuration)
40
+ rshono.config.ts optional — every field has a default
26
41
  public/ optional — served verbatim at the web root
27
42
  src/
28
43
  routes.ts required — the route table
29
44
  server.ts optional — a Hono sub-app mounted ahead of the page routes
30
- everything else is yours to arrange
45
+ everything else is yours to arrange
31
46
  ```
32
47
 
33
- Only the two files under `src/` mean anything to the framework; there is no convention attached to any other name or directory. `@/…` resolves to `src/…` in both compilers, so add the matching `paths` to your `tsconfig.json` if you use it — relative, and with no `baseUrl`, which TypeScript 7 removed:
48
+ Only those two files under `src/` mean anything to the framework; no other name or directory carries a
49
+ convention. `@/…` resolves to `src/…` in both compilers, so add the matching `paths` to `tsconfig.json` if
50
+ you use it — relative, and with no `baseUrl`, which TypeScript 7 removed:
34
51
 
35
52
  ```json
36
53
  { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }
@@ -58,13 +75,15 @@ export const routes = defineRoutes({
58
75
  });
59
76
  ```
60
77
 
61
- `routes.ts` only ever runs on the server importing server-only modules from it (e.g. inside `staticPaths`) is safe. A plain array (no special pages) is accepted as shorthand.
78
+ `routes.ts` only ever runs on the server, so importing server-only modules from it (inside `staticPaths`,
79
+ say) is safe. A plain array, with no special pages, is accepted as shorthand.
80
+ [Routing docs](https://www.rshono.com/docs/routing).
62
81
 
63
82
  ## Pages are server components
64
83
 
65
- Every page module **default-exports a server component** — nothing else. Under the hood each page carries Rspack's `'use server-entry'` directive (it attaches the page's client JS/CSS assets to the component — per-page code splitting with no asset manifest), but the framework **injects it automatically** for every component referenced with the inline `component: () => import('…')` thunk form in routes.ts. This also works for routes added while the dev server is running.
66
-
67
- If a component is wired up some other way (variable indirection, barrel re-exports, computed specifiers), write `'use server-entry'` as the first line of the page module yourself a manually written directive is always respected. The framework throws a descriptive error when neither happened.
84
+ Every page module default-exports a server component — nothing else. It renders the whole document
85
+ (`<html>…</html>`), may be `async`, and awaits its data directly. Interactive parts are `'use client'`
86
+ components the page imports; only those ship JavaScript.
68
87
 
69
88
  ```tsx
70
89
  import type { PageProps } from '@rshono/core';
@@ -77,14 +96,22 @@ export default async function Profile({ params, ctx }: PageProps<'/profile/:id'>
77
96
  }
78
97
  ```
79
98
 
80
- - Pages receive `{ url, params, ctx }` (`PageProps<'/profile/:id'>` types `params.id`). `url` is a real `URL` — read `url.pathname` and `url.searchParams` off it — and the pair matches what a `'use client'` component gets from `useNavigation()`, so a read moves across the line unchanged.
81
- - **`ctx` is the request context** — cookies, headers, env, middleware variables, the proxy-aware URL. It is the same object `getRequestContext()` returns from `@rshono/core/server`, handed over so a page needs no import; reach for `getRequestContext()` in the places that get no props (a nested server component, a `'use server'` action). Type `ctx.var` / `ctx.env` for your app by passing its Hono `Env`: `PageProps<'/profile/:id', MyEnv>`.
82
- - Reading `ctx` on a **`render: 'static'`** page throws — a page rendered once at build time has no request to read. Use `params` and `url`, which are available either way, or make the route `render: 'dynamic'`. One quiet caveat: a prerendered `url` is the build-time one (`siteUrl` + the path, no query), and that one file answers every request whatever its own query — so `url.searchParams` is always empty there. Read the query with `useNavigation().url` on the client instead.
83
- - Pages render the **entire document** (`<html>…</html>`), usually via a shared layout component.
84
- - Interactive parts are `'use client'` components imported by the page; only those ship JavaScript.
85
- - A fully interactive page is a thin server component wrapping a `'use client'` component.
86
- - Page props are **server-only and never serialized** React puts a server component's output on the wire, not its props. `ctx` is additionally non-enumerable, which keeps it out of React's dev-only debug payload (an enumerable one would ship the whole Hono context, bindings included, to the browser in dev).
87
- - **`ctx` cannot cross into a `'use client'` component**; it wraps the live request and response, which don't exist in the browser. Passing it explicitly (`<Counter ctx={ctx} />`) fails the render with React's _"Only plain objects … can be passed to Client Components"_, naming the prop. Spreading page props instead (`<Counter {...props} />`) drops it silently, since a spread copies enumerables only — though that spread fails anyway on `url`, which is enumerable and just as unserializable. Either way: read what you need on the server and pass plain values down (`url.href`, not `url`).
99
+ - Pages receive `{ url, params, ctx }` `PageProps<'/profile/:id'>` types `params.id`, and `url` is a real
100
+ `URL`. The same pair reaches a `'use client'` component from `useNavigation()`, so a read moves across the
101
+ boundary unchanged.
102
+ - **`ctx` is the request context** — `ctx.req`, cookies, env, middleware variables, the proxy-aware URL. It is
103
+ the same object `getRequestContext()` returns from `@rshono/core/server`, handed over so a page needs no
104
+ import. Reading it on a `render: 'static'` page throws: a page rendered once at build time has no request.
105
+ A page can only _read_ it`cookies.set` and `setHeader` throw there, because a page streams and its
106
+ response head is already committed; set them from a `'use server'` action or from middleware instead.
107
+ - Page props are server-only and never serialized, and `ctx` cannot cross into a client component. Read what
108
+ you need on the server and pass plain values down (`url.href`, not `url`).
109
+ - The framework injects Rspack's `'use server-entry'` directive for every component referenced with the
110
+ inline `component: () => import('…')` thunk — that is what attaches the page's JS and CSS to it, so code
111
+ splitting needs no asset manifest. Wire a component up some other way and you write the directive
112
+ yourself; the framework throws a descriptive error when neither happened.
113
+
114
+ [Pages docs](https://www.rshono.com/docs/pages).
88
115
 
89
116
  ## Server actions
90
117
 
@@ -95,167 +122,123 @@ export default async function Profile({ params, ctx }: PageProps<'/profile/:id'>
95
122
  export async function createUser(data: { name: string; email: string }) { … }
96
123
  ```
97
124
 
98
- Call them directly from client code (typed args and result), or wire them to `<form action>` / `useActionState` — forms keep working before hydration and with JavaScript disabled (progressive enhancement). Every action response carries a fresh page payload, so server-rendered UI updates automatically after mutations.
125
+ Call them directly from client code (typed args and result), or wire them to `<form action>` /
126
+ `useActionState` — forms keep working before hydration and with JavaScript disabled. Every action response
127
+ carries a fresh page payload, so server-rendered UI updates after a mutation.
128
+ **Every `'use server'` export is a public HTTP endpoint**, so authenticate, authorize and validate inside the
129
+ action. [Server actions docs](https://www.rshono.com/docs/server-actions).
99
130
 
100
131
  ## Full Hono underneath
101
132
 
102
- - `{ type: 'endpoint' }` routes export a Hono `handler` from a server module (it only ever runs on the server).
103
- - `src/server.ts` may default-export a whole Hono sub-app: any method, streaming, cookies, middleware. `export type AppType = typeof server` gives end-to-end type safety with `hono/client`.
104
- - The sub-app is mounted at `/` **ahead of the page routes**, so its middleware (auth, logging, trailing-slash) wraps page requests too. The flip side: a _terminal_ handler in `src/server.ts` at the same path as a page route shadows the page.
105
-
106
- ## Styling
107
-
108
- - `import './styles.css'` from any component. Rspack's native CSS pipeline compiles it, and the import attaches the stylesheet to the importing page's assets — so CSS is code-split per route and arrives with the page that needs it, `<link>`ed in the streamed HTML rather than fetched after hydration. `*.module.css` gets a class map.
109
- - **There is no PostCSS in the framework**, and no dependency on any of it — native CSS is fast, and it is everything a plain stylesheet needs. What it cannot do is read CSS that isn't finished yet: `@import 'tailwindcss'`, `@theme` and `@apply` are all parse-time nonsense to it. A stylesheet that needs a plugin puts the plugin in front of that parser itself, through the [`rspack` hook](#configuration-rshonoconfigts), and installs the two packages a PostCSS pass takes.
110
- - **Tailwind** is exactly that, and nothing else:
111
-
112
- ```bash
113
- npm i -D tailwindcss @tailwindcss/postcss postcss postcss-loader
114
- ```
115
-
116
- ```ts
117
- // rshono.config.ts — the hook is called once per compiler, so this reaches both graphs
118
- export default defineConfig({
119
- rspack(config) {
120
- config.module!.rules!.push({ test: /\.css$/i, use: ['postcss-loader'], type: 'css/auto' });
121
- },
122
- });
123
- ```
124
-
125
- ```js
126
- // postcss.config.mjs — the plugin list, which postcss-loader finds on its own
127
- export default { plugins: { '@tailwindcss/postcss': {} } };
128
- ```
129
-
130
- ```css
131
- /* src/styles.css */
132
- @import 'tailwindcss';
133
- ```
133
+ - `{ type: 'endpoint' }` routes export a Hono `handler` from a server module.
134
+ - `src/server.ts` may default-export a whole Hono sub-app: any method, streaming, cookies, middleware.
135
+ `export type AppType = typeof server` gives end-to-end type safety with `hono/client`.
136
+ - It is mounted at `/` **ahead of the page routes**, so its middleware (auth, logging, trailing-slash) wraps
137
+ page requests too. The flip side: a _terminal_ handler at the same path as a page route shadows the page.
134
138
 
135
- Keep `type: 'css/auto'` rather than `'css'`, or `*.module.css` stops being a CSS module. `npx @rshono/create@latest --tailwind` writes all four of these for you.
139
+ [Hono docs](https://www.rshono.com/docs/hono).
136
140
 
137
- ## Static files
138
-
139
- - Drop anything you want served verbatim into `public/` — it's mounted at the **web root**, so `public/favicon.ico` → `/favicon.ico`, `public/robots.txt` → `/robots.txt`, `public/.well-known/…` resolves too. This is the home for the conventional files browsers and crawlers request by path.
140
- - It's a **fallback**: your routes always win, and unmatched paths still fall through to the `notFound` page — so a `public/` file never shadows a real route. `build` copies `public/` into `dist/` so a deployed build is self-contained.
141
- - Hashed bundle output is served separately under `/_static/` with long-lived immutable caching; `public/` files get a short `max-age` (and `no-cache` in dev).
142
-
143
- ## Prerendering (`render: 'static'`)
144
-
145
- A static route is built once and served from disk in **both** representations — `index.html` for a hard load, `index.rsc` for the flight payload a soft navigation asks for. Serving only the document would mean every in-app click re-rendered a page the build had already produced, so the prerender would pay off for crawlers and nobody else. Both carry a weak `ETag`, so a revalidation costs a 304.
141
+ ## Styling
146
142
 
147
- Set **`siteUrl`** if your static pages build absolute URLs — a canonical tag, an `og:url`, an absolute link. A prerendered file is one set of bytes handed to everyone, so there is no request to read a `Host` from and the origin has to be decided at build time; without `siteUrl` it is `http://localhost`, and the build warns. Dynamic routes are unaffected — they resolve the URL per request, `siteUrl` or not.
143
+ `import './styles.css'` from any component. Rspack's native CSS pipeline compiles it, and the import
144
+ attaches the stylesheet to the importing page — so CSS is code-split per route and `<link>`ed in the streamed
145
+ HTML rather than fetched after hydration. `*.module.css` gets a class map.
148
146
 
149
- If a page can't be prerendered (its `staticPaths` is missing, or it didn't render cleanly at build time) the build says so and that route falls back to rendering per request.
147
+ **There is no PostCSS in the framework**, so a stylesheet that needs a plugin brings one, through the
148
+ [`rspack` hook](#configuration-rshonoconfigts). Tailwind is exactly that and nothing else — four packages, a
149
+ `postcss.config.mjs` and one rule; `npx @rshono/create@latest --tailwind` writes all of it.
150
+ [Styling docs](https://www.rshono.com/docs/styling).
150
151
 
151
152
  ## Env & secret safety
152
153
 
153
- The client/server boundary is the RSC directives — `'use client'` and `'use server'` — not filenames, and `process.env` access follows it. There is no `*.server` naming convention.
154
+ The boundary is the RSC directives — `'use client'` and `'use server'` — not filenames, and `process.env`
155
+ follows it.
156
+
157
+ - In the **client bundle** `process.env` is _replaced at build time_ with a literal holding only `NODE_ENV`
158
+ and `PUBLIC_`-prefixed variables. A stray `process.env.DATABASE_URL` in client code compiles to
159
+ `undefined`; the value cannot ship. That is a build-time substitution, not tree-shaking, and it covers
160
+ `node_modules` too.
161
+ - Your `'use client'` modules see the same `PUBLIC_`-only view **while being SSR'd**, so a secret read there
162
+ renders empty instead of leaking into the HTML stream, and SSR output still agrees with hydration.
163
+ - Server components and `'use server'` actions read the real `process.env`. Anything a server component
164
+ _renders_ is public by definition.
165
+ - `.env.local` and `.env` are loaded automatically; the real environment wins.
154
166
 
155
- - **Client bundle**: `process.env` is _replaced at build time_ with a literal containing only `NODE_ENV` and `PUBLIC_`-prefixed variables. A stray `process.env.DATABASE_URL` in client code compiles to `undefined` — the value cannot ship. This is a hard guarantee, not tree-shaking, and it covers your `node_modules` too.
156
- - **`'use client'` modules are also SSR'd on the server**, and there they see the same `PUBLIC_`-only view. A `process.env.SECRET` in a client component renders empty instead of leaking into the HTML stream, and SSR output always agrees with hydration. This SSR-side shadowing is scoped to your own `src/` — a _third-party_ client component that reads `process.env` during SSR sees the real environment, so treat a dependency that does that as you would any other dependency handling secrets.
157
- - **Server components and `'use server'` actions read the real `process.env`.** They run only on the server — server components stay in the server graph, actions compile to server references — so a secret read there never reaches the browser. Read secrets in server code and pass derived data down.
158
- - `.env.local` and `.env` are loaded automatically (real environment wins).
159
- - Anything a server component _renders_ is public by definition — whatever you put in the tree ships in the flight payload.
160
- - Keeping a server-only module out of the client bundle is the module graph's job: import it only from server code. For a hard failure if that ever slips, add React's `server-only` package — the RSC layer resolves its `react-server` condition, so importing it from client code throws.
167
+ [Environment and secrets](https://www.rshono.com/docs/configuration#environment-and-secrets).
161
168
 
162
169
  ## Configuration: rshono.config.ts
163
170
 
164
- An optional `rshono.config.ts` (`.js` / `.mjs` also work) at the project root tunes the framework. Every field is optional; delete the file to accept all defaults.
171
+ Optional (`.js` / `.mjs` also work). Every field is optional; delete the file to accept all defaults.
165
172
 
166
173
  ```ts
167
174
  import { defineConfig } from '@rshono/core';
168
175
 
169
176
  export default defineConfig({
170
- deploy: 'node', // hosting platform to build for — see Deployment (--deploy or RSHONO_DEPLOY override)
177
+ deploy: 'node', // hosting platform to build for (--deploy or RSHONO_DEPLOY override)
171
178
  siteUrl: 'https://example.com', // public origin, baked into prerendered pages' absolute URLs
172
179
  trustProxy: false, // honour X-Forwarded-Host/-Proto — only behind a proxy you control
173
- checkOrigin: true, // CSRF origin check on server-action POSTs
174
- allowedOrigins: [], // extra origins allowed to post actions, e.g. ['https://admin.example.com']
175
- csp: false, // strict per-request-nonce Content-Security-Policy
176
- cspDirectives: {}, // widen the built-in CSP, e.g. { 'img-src': "'self' https://cdn.example.com" }
177
- bodySizeLimit: '1mb', // request body cap: '512kb' | 4_000_000 | false to disable
178
180
  rspack(config, { isServer, isDev }) {
179
181
  return config; // escape hatch: mutate the generated Rspack config
180
182
  },
181
183
  });
182
184
  ```
183
185
 
184
- `defineConfig` is an identity helper for editor autocomplete; `export default { … } satisfies RshonoConfig` works too. `deploy` and `rspack` are consumed by the CLI; the framework settings (`trustProxy`, `checkOrigin`, `allowedOrigins`, `csp`, `cspDirectives`, `bodySizeLimit`) are resolved from this file at build time and **compiled into the server bundle** — there is no parallel env-var interface for them (environment variables are for secrets). Changing one of these settings means a rebuild. Point `rshono build` at a different config with `--config <path>`.
185
-
186
- **The port and bind address are not config fields.** They are `--port` / `PORT` and `HOST`, in that precedence order, falling back to `3000` and `0.0.0.0` — because on every host that runs this (a container, a process manager, a PaaS) the environment is what sets them, so a config field would only have been a level that always loses.
187
-
188
- A `.ts` config is loaded by Node's own type stripping, with no TypeScript loader in the dependency tree. The one thing that costs: Node will not resolve a `.js` specifier to the `.ts` file beside it, so a config that imports a sibling module has to name it with its real extension (or be an `.mjs` file).
189
-
190
- ## Security & hardening
191
-
192
- - **Every `'use server'` export is a public HTTP endpoint.** That's the RSC model, not an rshono choice: the client is handed an id for each action and can call it with whatever arguments it likes. The CSRF check below proves a request came from your own site — it says nothing about _who_ sent it. Authenticate and authorize inside the action (and validate its arguments) exactly as you would in a route handler.
193
- - **CSRF**: server-action POSTs are origin-checked automatically — a cross-origin `Origin` (compared against your own host) is rejected with 403, as is anything the browser labels `Sec-Fetch-Site: cross-site`/`same-site`. A browser-asserted `Sec-Fetch-Site: same-origin` is accepted directly, which is what keeps the check from misfiring behind a proxy that rewrites `Host`. Applies to both client-initiated calls and no-JS form posts. Turn it off with `checkOrigin: false` behind a gateway that already enforces it, or list trusted cross-origins in `allowedOrigins` (full origins or bare hosts; a malformed entry fails the build).
194
- - **Proxy headers are not trusted by default.** `X-Forwarded-Host` / `-Proto` are client-supplied, so honouring them blindly lets anyone who can reach the server dictate the origin of every absolute URL the app builds (`getRequestContext().url`, a page's `url` prop) — poisoning canonical tags, emails and redirects, and any shared cache in front. Set `trustProxy: true` only when a proxy you control sets those headers; `rshono dev` forces it on for its own localhost-bound proxy.
195
- - **Client disconnect**: a render is aborted when the client goes away, so a browser that navigated on stops the work it asked for. There is **no request deadline in the framework** — a `renderTimeout` setting used to wrap every request in a timer, but every host this deploys to enforces one already (Workers, Vercel and Lambda all do), leaving a bare Node process as the only shape that needed it. Put it in your proxy, or in your own middleware if you want it in-process:
196
-
197
- ```ts
198
- // src/server.ts — a deadline for a Node deploy that fronts nothing
199
- const TIMEOUT_MS = 10_000;
200
- server.use(async (c, next) => {
201
- const timedOut = Symbol('timeout');
202
- const timer = new Promise((resolve) => setTimeout(() => resolve(timedOut), TIMEOUT_MS).unref?.());
203
- if ((await Promise.race([next(), timer])) === timedOut) return c.text('Gateway Timeout', 504);
204
- });
205
- ```
206
-
207
- Note what that cannot do, and what the framework's own version could not either: the abandoned render keeps running to completion. All either one buys you is not holding the socket open for it.
186
+ That is the whole file. It holds what the **build** decides; per-request security is Hono middleware in
187
+ `src/server.ts`, which is mounted ahead of the page routes and so wraps renders and server actions too:
208
188
 
209
- - **Request-body limit**: request bodies are capped (`bodySizeLimit`, default 1048576 = 1 MiB) before they're buffered into memory — oversized bodies are rejected with `413 Payload Too Large`. This covers **every** route, not just server actions: `{ type: 'endpoint' }` routes and the `src/server.ts` sub-app are equally exposed the moment they call `.json()` or `.formData()`. An over-cap `Content-Length` is refused up front; bodies that omit it (chunked) are cut off mid-stream. Set to `false`/`0` to disable (e.g. behind a proxy that already enforces a limit, or to stream a large upload yourself). Raise it for large multipart uploads.
210
- - **Baseline response headers**: `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin` and `X-Frame-Options: SAMEORIGIN` on every response, unconditionally. The framing header is the floor for everyone who hasn't opted into `csp` — that policy's `frame-ancestors 'none'` is stricter and takes precedence where both apply. Set any of them in your own middleware to override.
211
- - **Caching defaults**: a dynamic page is answered with `Cache-Control: private, no-cache` — a page is request-specific by default (cookies, session, headers), and with no directives at all a shared cache is free to store one user's page and serve it to the next. `private` forbids exactly that, and `no-cache` makes the browser revalidate rather than re-show a stale personalised page; neither disables bfcache the way `no-store` would. Set your own value (from middleware, or `getRequestContext().raw.header(…)`) and it is left alone. Prerendered pages keep `public, max-age=300` and carry a weak `ETag`, so a revalidation costs a 304 instead of the page.
212
- - **`Vary: Accept` on page responses.** One URL answers with an HTML document or a flight payload depending on `Accept`. Without `Vary` a cache keyed on the URL alone will eventually hand a document to a soft navigation that asked for flight — a hard reload at best. Compression appends `Accept-Encoding` to the same header rather than replacing it.
213
- - **CSP (opt-in)**: set `csp: true` to send a strict per-request-nonce `Content-Security-Policy` with every HTML document (nonce stamped on bootstrap scripts, inlined flight payload, and dynamically loaded chunks). Beyond `default-src 'self'` it also closes the gaps `default-src` doesn't cover — `base-uri`, `object-src`, `frame-ancestors`, `form-action` — so it blocks framing and third-party assets until you widen it with `cspDirectives` (the nonce is always re-appended to `script-src`, and `''` drops a directive). While enabled, the **document** for a `render: 'static'` route is rendered per request — a prerendered file can't carry a per-request nonce. Its flight payload never carries one, so soft navigations are still served from the prerender.
214
- - **Error reporting**: every error the framework catches — a thrown action, a failed render, SSR falling over, anything reaching the top-level handler — goes through one funnel. Register a handler at the top level of `src/server.ts` to send them somewhere real; they keep going to `stderr` either way, and a handler that throws is caught rather than failing the request.
215
-
216
- ```ts
217
- // src/server.ts
218
- import { onServerError } from '@rshono/core/server';
219
-
220
- onServerError((error, { source, request }) => {
221
- Sentry.captureException(error, { tags: { source }, extra: { url: request.url } });
222
- });
223
- ```
224
-
225
- - **Error responses**: thrown server-action errors are logged server-side and redacted in the production payload (React sends no message or digest for them) — so return values, not throws, for anything the user should see. Custom 404/500 pages are real server components declared in routes.ts (`notFound` / `error`); the error page's `error` prop is message-only in production, message + stack in dev.
226
- - **No blank screens.** Three fallbacks behind the `error` page, so a failure is always something you can read:
227
- - An **uncaught client-side render error** makes React tear down its root — which here is the whole `document`, so the page would go genuinely white with the reason only in the console. The runtime paints a fatal overlay over it instead: full stack and component stack in dev, a generic notice plus a reload button in production (the dev detail is compiled out of the production bundle).
228
- - If **SSR fails before the shell is sent**, the `error` page can't be reached either, so the framework answers with its own visible 500 document — the real message and stack in dev, generic in production. It deliberately attaches no client runtime: the flight payload came from the same failed render, so hydrating it would tear the document down and blank the message.
229
- - A **client bootstrap failure** (a truncated or malformed initial payload) is reported and surfaced rather than becoming a silent unhandled rejection.
230
-
231
- ## Testing
232
-
233
- `pnpm --filter @rshono/core test` builds the package and runs everything that doesn't need a browser:
234
-
235
- - **unit** — the parsers and path maths (`bodySizeLimit`, `allowedOrigins`, SSG paths and traversal, control-signal digests, page-file scanning, `Vary`/`ETag` helpers). Imports the built `dist/`, so it also proves the published output loads in plain Node.
236
- - **production e2e** — builds `apps/testbed`, boots the real production server, and asserts pages, flight protocol, actions (client + progressive enhancement), CSRF rejection, secret stripping in bundles _and_ rendered HTML, SSG output with `ETag`/304, cache and security headers, and error reporting. Settings baked into the bundle (CSP, CSRF allowlist/origin-check, body-size cap) each get their own build from a fixture config (`test/fixtures/`, via `rshono build --config`).
237
- - **minimal app** — a fixture with `src/routes.ts` and nothing else: no `server.ts`, no `public/`, no config, no `notFound`/`error` pages. Everything the docs call optional, actually left out.
238
- - **postcss** — a Tailwind fixture wiring the loader up through the `rspack` hook, from an `@import "tailwindcss"` nothing could resolve through to compiled utilities in the stylesheet the served page links. The documented four lines, actually run.
239
- - **dev** — a smoke test through the dev server's worker + proxy.
240
-
241
- `pnpm --filter @rshono/core test:browser` runs the Playwright suite against a production build: hydration, soft navigation, `useNavigation`, client-initiated actions, boundary fallbacks and the fatal overlay — the client runtime, which no amount of asserting on HTML can reach.
242
-
243
- ## How it works
244
-
245
- Two coordinated Rspack compilers (native RSC support, `rspack.experiments.rsc`):
246
-
247
- - **client** (`target: web`) → `dist/static`: hydration runtime, `'use client'` chunks, CSS.
248
- - **server** (`target: node`) → `dist/server/main.mjs`: the app server itself — a Hono app assembled from your routes, rendered through two layers (RSC layer with the `react-server` condition → flight payload; SSR layer → HTML stream with the payload inlined for hydration).
189
+ ```ts
190
+ // src/server.ts
191
+ import { publicUrl } from '@rshono/core/server';
192
+ import { bodyLimit } from 'hono/body-limit'; // https://hono.dev/docs/middleware/builtin/body-limit
193
+ import { csrf } from 'hono/csrf'; // https://hono.dev/docs/middleware/builtin/csrf
194
+ import { NONCE, secureHeaders } from 'hono/secure-headers'; // https://hono.dev/docs/middleware/builtin/secure-headers
195
+
196
+ // Caps a body before anything buffers it — pages, actions and your own handlers alike. 413 over cap.
197
+ server.use(bodyLimit({ maxSize: 1024 * 1024 }));
198
+
199
+ // Rejects a cross-origin POST with 403 before it can reach a server action. `publicUrl(c)` rather
200
+ // than Hono's default, which compares against the address the server was *reached* on — the internal
201
+ // one behind any proxy. It honours `trustProxy`.
202
+ server.use(csrf({ origin: (origin, c) => origin === publicUrl(c).origin }));
203
+
204
+ // HSTS, COOP, CORP and the rest. `NONCE` in `scriptSrc` makes the CSP per-request: Hono mints the
205
+ // nonce, rshono stamps it onto the bootstrap scripts and the inlined flight payload.
206
+ server.use(secureHeaders({ contentSecurityPolicy: { scriptSrc: ["'self'", NONCE] } }));
207
+ ```
249
208
 
250
- In dev, the CLI watches both bundles, runs the server bundle in a worker thread (restarted per rebuild; requests gate on readiness — no dropped connections), and fronts everything on one port with static serving and an SSE channel: client edits hot-apply via react-refresh, server component edits re-fetch the payload in place — browser state survives both.
209
+ `create-rshono` scaffolds the first two. Under `rshono dev` the framework widens `script-src` with
210
+ `'unsafe-eval'` for React Refresh — never in a build — so one policy serves both, and a route with a
211
+ nonce in play falls back to rendering per request. Everything else Hono ships works the same way:
212
+ `cors`, `basicAuth`, `jwt`, `timeout`, `requestId`, `ipRestriction`. A middleware that rejects by
213
+ throwing an `HTTPException` keeps its own status rather than becoming the 500 page.
214
+ [Middleware docs](https://www.rshono.com/docs/hono#security-middleware) · [Hono](https://hono.dev/docs).
215
+
216
+ `deploy` and `rspack` are consumed by the CLI; `trustProxy` is **compiled into the server bundle** at build
217
+ time, so changing it means a rebuild and there is no parallel env-var interface for it (environment
218
+ variables are for secrets). The port and bind address are deliberately not config fields — on every host
219
+ that runs this, the environment is what sets them. [Configuration docs](https://www.rshono.com/docs/configuration).
220
+
221
+ The defaults, in short: untrusted proxy headers, `nosniff` / `Referrer-Policy` / `X-Frame-Options` on
222
+ every response (a floor your own `secureHeaders()` overrides),
223
+ `private, no-cache` plus `Vary: Accept` on dynamic pages, `public, max-age=300` and a weak `ETag` on
224
+ prerendered ones, and errors redacted in production — with one `onServerError()` funnel for reporting them
225
+ and three fallbacks (a fatal client overlay, a visible 500 document, a reported bootstrap failure) so a
226
+ failure is never a blank screen.
251
227
 
252
- In production, `dist/server/main.mjs` is self-contained (React, Hono and the framework are bundled in; your other npm dependencies resolve from `node_modules`): `rshono start` or any process manager running `node dist/server/main.mjs`.
228
+ ## Prerendering (`render: 'static'`)
253
229
 
254
- Everything in that bundle that depends on _where_ it runs — binding a port, serving `/_static` and `public/`, reading a prerendered page, loading `.env` sits behind a single interface (`DeployRuntime`) that the build resolves per `deploy` target, so the request-handling code has no platform in it. The entry's default export is whatever the platform expects: nothing where rshono owns the process, a `fetch` handler where the host does.
230
+ A static route is built once and served from disk in **both** representations — `index.html` for a hard load,
231
+ `index.rsc` for the flight payload a soft navigation asks for — each with a weak `ETag`. Set **`siteUrl`** if
232
+ those pages build absolute URLs: one set of bytes is handed to everyone, so the origin has to be decided at
233
+ build time (without it the build warns and uses `http://localhost`). A page that can't be prerendered — no
234
+ `staticPaths`, or it didn't render cleanly — is reported by the build and falls back to rendering per
235
+ request. [Static rendering docs](https://www.rshono.com/docs/routing#static-rendering).
255
236
 
256
237
  ## Deployment
257
238
 
258
- `rshono build` targets one platform. Pick it with `deploy` in the config, `--deploy <name>`, or `RSHONO_DEPLOY` (in that precedence order); the default is `node`. `rshono dev` always runs the Node dev server whatever you choose — the target is a property of the build, not of developing.
239
+ `rshono build` targets one platform. Pick it with `deploy` in the config, `--deploy <name>` or
240
+ `RSHONO_DEPLOY`, in that precedence order; the default is `node`. `rshono dev` always runs the Node dev
241
+ server whatever you choose — the target is a property of the build, not of developing.
259
242
 
260
243
  | `deploy` | Handoff | Assets & prerendered pages | After `build` |
261
244
  | ------------ | -------------------------------- | --------------------------------------------------------------- | ------------------------------------------- |
@@ -264,26 +247,70 @@ Everything in that bundle that depends on _where_ it runs — binding a port, se
264
247
  | `vercel` | web handler in a Node function | CDN for assets; prerendered pages inside the function | `vercel deploy --prebuilt` |
265
248
  | `aws-lambda` | streaming handler (Function URL) | from the deployment package | zip `dist/`, handler `dist/server/main.mjs` |
266
249
 
267
- One target per _handoff_ — the thing an app cannot arrange for itself. Everything else about a platform sits behind `DeployRuntime`, and `node`, `vercel` and `aws-lambda` share one filesystem implementation.
250
+ One target per _handoff_ — the thing an app cannot arrange for itself. Everything else about a platform sits
251
+ behind one `DeployRuntime` interface, and `node`, `vercel` and `aws-lambda` share a filesystem implementation.
252
+ Every target streams, which is the bar a new one has to clear.
253
+
254
+ - **`node` is not only Node.** Anything that runs a Node process runs this build — a VPS, a container, a PaaS.
255
+ Bun (`bun dist/server/main.mjs`) and Deno (`deno run -A dist/server/main.mjs`) are expected to as well,
256
+ since the listener is `@hono/node-server`; they had a target each, which held nothing beyond a default
257
+ export. The suite runs on Node, so treat those two as an expectation rather than a guarantee.
258
+ - **Don't build a handler out of the `app` export.** The entry calls `runtime.serveApp(app)` at module scope,
259
+ so importing a `node` build binds a port as a side effect. Build for the target you deploy to, and
260
+ `rshono start` will refuse a build made for another platform.
261
+ - **Streaming is the fragile part of a serverless target, and it fails silently** — `supportsResponseStreaming`
262
+ on Vercel, `streamifyResponse` plus a `RESPONSE_STREAM` Function URL on Lambda. Getting those right is what
263
+ the presets are for.
264
+ - **Prerendered pages are never CDN-served**: one URL answers with a document or a flight payload depending on
265
+ `Accept`, and a path-keyed CDN cannot choose. `/_static` and `public/` do go straight to the CDN.
266
+
267
+ [Deployment docs](https://www.rshono.com/docs/deployment), including Cloudflare bindings and the AWS setup.
268
268
 
269
- Every target streams: a page's HTML reaches the browser as it renders, which is the whole reason the SSR shell is worth having. That is the bar a new target has to clear.
269
+ ## Requirements & limitations
270
270
 
271
- Notes worth knowing before choosing one:
271
+ - **Node 22.18** (worker threads, `process.loadEnvFile`, `Promise.withResolvers`, `URL.parse`, and native
272
+ TypeScript stripping, so a `.ts` config needs no loader) and **React ≥ 19.1** (the floor
273
+ `react-server-dom-rspack` requires).
274
+ - No response compression, no prefetching, no base path (`siteUrl` is a bare origin), and wildcard, optional
275
+ and regex params cannot be prerendered.
276
+ - Scroll restoration is the browser's (`history.scrollRestoration = 'auto'`).
277
+ - The dev proxy doesn't forward WebSocket upgrades to a custom sub-app; production is unaffected.
278
+ - Dev source maps embed the original source of `'use server'` modules (dev binds 127.0.0.1 only, and
279
+ production ships no client source maps).
272
280
 
273
- - **`node` is not only Node.** Anything that runs a Node process runs this build — a VPS, a container, a PaaS. **Bun** (`bun dist/server/main.mjs`) and **Deno** (`deno run -A dist/server/main.mjs`) are expected to as well, since the listener is `@hono/node-server` and both implement the `node:` APIs it needs. They had a target each; neither held anything beyond a default export, so running the `node` build replaces it. The suite runs on Node, so treat those two as an expectation rather than a guarantee.
274
- - **Netlify is not a target.** It was, and it was removed. Its handoff is `hono/netlify`'s `handle(app)` plus a `functions-internal` entry declaring `path: '/*'` and `preferStatic: true` — reconstructible, but you maintain it. If you want the preset back, ask.
275
- - **Do not reach for the `app` export to make your own handler.** The entry evaluates `runtime.serveApp(app)` at module scope, so importing a `node` build binds a port as a side effect. Build for the target you're deploying to.
276
- - **Streaming is the fragile part of a serverless target**, and it fails silently. Vercel needs `supportsResponseStreaming: true` in `.vc-config.json` or it buffers the whole response; Lambda needs `awslambda.streamifyResponse` and a Function URL in `RESPONSE_STREAM` mode. Both are what their preset exists to get right — the deployment works either way, it just stops streaming.
277
- - **AWS** means a Lambda Function URL with the invoke mode set to `RESPONSE_STREAM`, usually with CloudFront in front for `/_static` and `public/`. **Lambda@Edge is deliberately not a target**: CloudFront returns the response as a value rather than a stream, caps a generated origin-request response near 1 MB, and supports no environment variables at all — so `getRequestContext().env` would be empty there, which is a documented feature quietly doing nothing.
278
- - **Cloudflare** bundles all your dependencies (a Worker resolves no `node_modules` at runtime), so a dependency that needs a real `node:` API beyond `nodejs_compat` will not work. The build scaffolds a `wrangler.jsonc` if the project has none — including `nodejs_compat`, which the request context needs for `AsyncLocalStorage` — and never touches it again. Bindings (D1, KV, R2) arrive as `getRequestContext().env`; they are not available under `rshono dev`, which is plain Node.
279
- - **Prerendered pages are never CDN-served.** One URL answers with an HTML document or a flight payload depending on `Accept`, and a path-keyed CDN cannot choose, so the app always handles page URLs. Assets under `/_static` and `public/` do go straight to the CDN where there is one.
280
- - **Compression is not the framework's job.** `cloudflare` and `vercel` compress at the edge regardless, and a `node` or `aws-lambda` deploy is almost always behind something that does — a reverse proxy, a load balancer, CloudFront. rshono shipped a streaming-safe gzip for the two targets that might not be; it is gone, because it was one target's feature by the end and a proxy does it better. If you serve a bare Node process straight to the internet and want it, `hono/compress` is one `app.use` in `src/server.ts` — read its docs on streaming first, since a buffering compressor undoes streamed SSR.
281
- - `rshono start` refuses a build made for another platform rather than starting a bundle with no listener in it.
281
+ ## Testing
282
282
 
283
- ## Requirements & limitations
283
+ `pnpm --filter @rshono/core test` builds the package and runs everything that needs no browser:
284
+
285
+ - **unit** — the parsers and path maths, against the built `dist/`, which also proves the published output
286
+ loads in plain Node.
287
+ - **production e2e** — builds `apps/testbed`, boots the real production server, and asserts pages, the flight
288
+ protocol, actions (client and no-JS), CSRF rejection, secret stripping in bundles _and_ rendered HTML, SSG
289
+ output with `ETag`/304, cache and security headers, and error reporting. The hardened permutations — a
290
+ nonce CSP, a CSRF allowlist, a small body cap — are middleware, so they run against that same build
291
+ under a different environment; `trustProxy` is baked in and gets a fixture config.
292
+ - **the other deploy targets** — the `cloudflare` bundle driven as `fetch(request, env, ctx)` against a
293
+ stand-in `ASSETS` binding, a real build per serverless target checked against the handoff its platform
294
+ expects, and `rshono start`'s refusal to run a build made for another one.
295
+ - **minimal app**, a fixture with `src/routes.ts` and nothing else (everything the docs call optional, left
296
+ out); **postcss**, the documented Tailwind wiring actually run; **dev**, a smoke test through the dev
297
+ server's worker and proxy.
298
+
299
+ `pnpm --filter @rshono/core test:browser` runs the Playwright suite against a production build: hydration,
300
+ soft navigation, `useNavigation`, client-initiated actions, boundary fallbacks and the fatal overlay — the
301
+ client runtime, which no amount of asserting on HTML can reach.
302
+
303
+ ## How it works
284
304
 
285
- - Node 22.18 (worker threads, `process.loadEnvFile`, `Promise.withResolvers`, `URL.parse`, and native TypeScript stripping so a `.ts` config needs no loader), React ≥ 19.1 (the floor `react-server-dom-rspack` itself requires).
286
- - Responses are not compressed. A proxy, a load balancer or a CDN is where that belongs, and every hosted target already does it.
287
- - Scroll restoration is the browser's (`history.scrollRestoration = 'auto'`). A soft navigation to a new page starts at the top — or at the `#hash` the link named, once that page's payload is on screen — and a traversal is restored by the browser. Same-page anchors are untouched and jump natively.
288
- - Dev-mode proxy doesn't forward WebSocket upgrades to a custom sub-app (prod is unaffected the bundle owns the socket there).
289
- - Dev source maps embed the original source of `'use server'` action modules (dev binds to 127.0.0.1 only; production ships no client source maps).
305
+ Two coordinated Rspack compilers, using native RSC support (`rspack.experiments.rsc`):
306
+
307
+ - **client** (`target: web`) `dist/static`: hydration runtime, `'use client'` chunks, CSS.
308
+ - **server** (`target: node`) `dist/server/main.mjs`: a Hono app assembled from your routes, rendered
309
+ through two layers the RSC layer, with the `react-server` condition, produces the flight payload; the SSR
310
+ layer turns it into an HTML stream with the payload inlined for hydration.
311
+
312
+ In dev the CLI watches both bundles, runs the server bundle in a worker thread (restarted per rebuild,
313
+ requests gated on readiness so nothing drops), and fronts everything on one port with static serving and an
314
+ SSE channel: client edits hot-apply via react-refresh, server component edits re-fetch the payload in place,
315
+ and browser state survives both. In production `dist/server/main.mjs` is self-contained — React, Hono and the
316
+ framework are bundled in; your other dependencies resolve from `node_modules`.
@@ -1 +1 @@
1
- {"version":3,"file":"page-files.d.ts","sourceRoot":"","sources":["../../src/builder/page-files.ts"],"names":[],"mappings":"AAsBA,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAqBzF"}
1
+ {"version":3,"file":"page-files.d.ts","sourceRoot":"","sources":["../../src/builder/page-files.ts"],"names":[],"mappings":"AA0BA,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAqBzF"}
@@ -1,10 +1,14 @@
1
+ /**
2
+ * Finds the page modules named by inline `component: () => import('…')` thunks in `routes.ts`, so the
3
+ * server compiler can put `page-entry-loader.cjs` in front of exactly those files and inject their
4
+ * `'use server-entry'` directive. A page wired up any other way declares the directive itself.
5
+ */
1
6
  import { readFileSync, statSync } from 'node:fs';
2
7
  import { dirname, join, resolve } from 'node:path';
3
8
  const COMPONENT_THUNK = /component:\s*(?:async\s*)?\(\s*\)\s*=>\s*import\(\s*(['"])([^'"]+)\1\s*\)/g;
4
9
  const EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];
5
- // The index candidates go through `join` rather than `base + '/index…'`: these paths end up in a Set
6
- // that rspack compares against its own resource paths, and a hardcoded `/` resolves fine on Windows
7
- // while storing a path no rspack resource will ever equal.
10
+ // Index candidates go through `join` rather than `base + '/index…'`: these paths are compared against
11
+ // Rspack's own resource paths, and a hardcoded `/` stores one no Windows resource will ever equal.
8
12
  function resolveCandidates(base) {
9
13
  return [...EXTENSIONS.map((ext) => base + ext), ...EXTENSIONS.map((ext) => join(base, `index${ext}`)), base];
10
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"page-files.js","sourceRoot":"","sources":["../../src/builder/page-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD,MAAM,eAAe,GAAG,4EAA4E,CAAC;AAErG,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAElD,qGAAqG;AACrG,oGAAoG;AACpG,2DAA2D;AAC3D,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAC/G,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,MAAc,EAAE,IAAiB;IACjF,IAAI,CAAC,KAAK,EAAE,CAAC;IACb,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,IAAwB,CAAC;QAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;aAC/D,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,KAAK,MAAM,SAAS,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACpB,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import { readFileSync, statSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\nconst COMPONENT_THUNK = /component:\\s*(?:async\\s*)?\\(\\s*\\)\\s*=>\\s*import\\(\\s*(['\"])([^'\"]+)\\1\\s*\\)/g;\n\nconst EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];\n\n// The index candidates go through `join` rather than `base + '/index…'`: these paths end up in a Set\n// that rspack compares against its own resource paths, and a hardcoded `/` resolves fine on Windows\n// while storing a path no rspack resource will ever equal.\nfunction resolveCandidates(base: string): string[] {\n return [...EXTENSIONS.map((ext) => base + ext), ...EXTENSIONS.map((ext) => join(base, `index${ext}`)), base];\n}\n\nfunction isFile(path: string): boolean {\n try {\n return statSync(path).isFile();\n } catch {\n return false;\n }\n}\n\nexport function scanPageFiles(routesFile: string, srcDir: string, into: Set<string>): void {\n into.clear();\n let source: string;\n try {\n source = readFileSync(routesFile, 'utf8');\n } catch {\n return;\n }\n for (const match of source.matchAll(COMPONENT_THUNK)) {\n const spec = match[2];\n let base: string | undefined;\n if (spec.startsWith('.')) base = resolve(dirname(routesFile), spec);\n else if (spec.startsWith('@/')) base = resolve(srcDir, spec.slice(2));\n if (!base) continue;\n for (const candidate of resolveCandidates(base)) {\n if (isFile(candidate)) {\n into.add(candidate);\n break;\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"page-files.js","sourceRoot":"","sources":["../../src/builder/page-files.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD,MAAM,eAAe,GAAG,4EAA4E,CAAC;AAErG,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAElD,sGAAsG;AACtG,mGAAmG;AACnG,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAC/G,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,MAAc,EAAE,IAAiB;IACjF,IAAI,CAAC,KAAK,EAAE,CAAC;IACb,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,IAAwB,CAAC;QAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;aAC/D,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,KAAK,MAAM,SAAS,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACpB,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["/**\n * Finds the page modules named by inline `component: () => import('…')` thunks in `routes.ts`, so the\n * server compiler can put `page-entry-loader.cjs` in front of exactly those files and inject their\n * `'use server-entry'` directive. A page wired up any other way declares the directive itself.\n */\nimport { readFileSync, statSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\nconst COMPONENT_THUNK = /component:\\s*(?:async\\s*)?\\(\\s*\\)\\s*=>\\s*import\\(\\s*(['\"])([^'\"]+)\\1\\s*\\)/g;\n\nconst EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];\n\n// Index candidates go through `join` rather than `base + '/index…'`: these paths are compared against\n// Rspack's own resource paths, and a hardcoded `/` stores one no Windows resource will ever equal.\nfunction resolveCandidates(base: string): string[] {\n return [...EXTENSIONS.map((ext) => base + ext), ...EXTENSIONS.map((ext) => join(base, `index${ext}`)), base];\n}\n\nfunction isFile(path: string): boolean {\n try {\n return statSync(path).isFile();\n } catch {\n return false;\n }\n}\n\nexport function scanPageFiles(routesFile: string, srcDir: string, into: Set<string>): void {\n into.clear();\n let source: string;\n try {\n source = readFileSync(routesFile, 'utf8');\n } catch {\n return;\n }\n for (const match of source.matchAll(COMPONENT_THUNK)) {\n const spec = match[2];\n let base: string | undefined;\n if (spec.startsWith('.')) base = resolve(dirname(routesFile), spec);\n else if (spec.startsWith('@/')) base = resolve(srcDir, spec.slice(2));\n if (!base) continue;\n for (const candidate of resolveCandidates(base)) {\n if (isFile(candidate)) {\n into.add(candidate);\n break;\n }\n }\n }\n}\n"]}
@@ -1,2 +1,8 @@
1
+ /**
2
+ * The `process.env` view compiled into the browser bundle: `NODE_ENV` plus the `PUBLIC_`-prefixed
3
+ * variables. Everything else stays server-only — a stray read of it in client code becomes
4
+ * `undefined` rather than shipping the value. Also what `env-shadow-loader.cjs` shadows SSR-layer
5
+ * modules with, so the server renders the same thing the browser hydrates.
6
+ */
1
7
  export declare function publicEnv(isDev: boolean): Record<string, string>;
2
8
  //# sourceMappingURL=public-env.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public-env.d.ts","sourceRoot":"","sources":["../../src/builder/public-env.ts"],"names":[],"mappings":"AAAA,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGhE"}
1
+ {"version":3,"file":"public-env.d.ts","sourceRoot":"","sources":["../../src/builder/public-env.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGhE"}
@@ -1,3 +1,9 @@
1
+ /**
2
+ * The `process.env` view compiled into the browser bundle: `NODE_ENV` plus the `PUBLIC_`-prefixed
3
+ * variables. Everything else stays server-only — a stray read of it in client code becomes
4
+ * `undefined` rather than shipping the value. Also what `env-shadow-loader.cjs` shadows SSR-layer
5
+ * modules with, so the server renders the same thing the browser hydrates.
6
+ */
1
7
  export function publicEnv(isDev) {
2
8
  const entries = Object.entries(process.env).filter((entry) => entry[0].startsWith('PUBLIC_') && entry[1] !== undefined);
3
9
  return { NODE_ENV: isDev ? 'development' : 'production', ...Object.fromEntries(entries) };
@@ -1 +1 @@
1
- {"version":3,"file":"public-env.js","sourceRoot":"","sources":["../../src/builder/public-env.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAA6B,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IACnJ,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;AAC5F,CAAC","sourcesContent":["export function publicEnv(isDev: boolean): Record<string, string> {\n const entries = Object.entries(process.env).filter((entry): entry is [string, string] => entry[0].startsWith('PUBLIC_') && entry[1] !== undefined);\n return { NODE_ENV: isDev ? 'development' : 'production', ...Object.fromEntries(entries) };\n}\n"]}
1
+ {"version":3,"file":"public-env.js","sourceRoot":"","sources":["../../src/builder/public-env.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAA6B,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IACnJ,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;AAC5F,CAAC","sourcesContent":["/**\n * The `process.env` view compiled into the browser bundle: `NODE_ENV` plus the `PUBLIC_`-prefixed\n * variables. Everything else stays server-only — a stray read of it in client code becomes\n * `undefined` rather than shipping the value. Also what `env-shadow-loader.cjs` shadows SSR-layer\n * modules with, so the server renders the same thing the browser hydrates.\n */\nexport function publicEnv(isDev: boolean): Record<string, string> {\n const entries = Object.entries(process.env).filter((entry): entry is [string, string] => entry[0].startsWith('PUBLIC_') && entry[1] !== undefined);\n return { NODE_ENV: isDev ? 'development' : 'production', ...Object.fromEntries(entries) };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"rspack-config.d.ts","sourceRoot":"","sources":["../../src/builder/rspack-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,aAAa,EAAoB,MAAM,cAAc,CAAC;AAK3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AA4FzD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,2IAA2I;IAC3I,MAAM,EAAE,YAAY,CAAC;IACrB,4GAA4G;IAC5G,MAAM,EAAE,YAAY,CAAC;IACrB,wBAAwB,CAAC,EAAE,MAAM,IAAI,CAAC;CACvC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,mBAAmB,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CA6M1F"}
1
+ {"version":3,"file":"rspack-config.d.ts","sourceRoot":"","sources":["../../src/builder/rspack-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,aAAa,EAAoB,MAAM,cAAc,CAAC;AAK3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AA0FzD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,2IAA2I;IAC3I,MAAM,EAAE,YAAY,CAAC;IACrB,4GAA4G;IAC5G,MAAM,EAAE,YAAY,CAAC;IACrB,wBAAwB,CAAC,EAAE,MAAM,IAAI,CAAC;CACvC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,mBAAmB,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CA4M1F"}