@rshono/core 1.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +273 -0
  2. package/bin/rshono.mjs +15 -0
  3. package/dist/builder/env-shadow-loader.cjs +26 -0
  4. package/dist/builder/page-entry-loader.cjs +7 -0
  5. package/dist/builder/page-files.d.ts +2 -0
  6. package/dist/builder/page-files.d.ts.map +1 -0
  7. package/dist/builder/page-files.js +45 -0
  8. package/dist/builder/page-files.js.map +1 -0
  9. package/dist/builder/public-env.d.ts +2 -0
  10. package/dist/builder/public-env.d.ts.map +1 -0
  11. package/dist/builder/public-env.js +5 -0
  12. package/dist/builder/public-env.js.map +1 -0
  13. package/dist/builder/rspack-config.d.ts +14 -0
  14. package/dist/builder/rspack-config.d.ts.map +1 -0
  15. package/dist/builder/rspack-config.js +235 -0
  16. package/dist/builder/rspack-config.js.map +1 -0
  17. package/dist/cli/build.d.ts +11 -0
  18. package/dist/cli/build.d.ts.map +1 -0
  19. package/dist/cli/build.js +64 -0
  20. package/dist/cli/build.js.map +1 -0
  21. package/dist/cli/dev.d.ts +9 -0
  22. package/dist/cli/dev.d.ts.map +1 -0
  23. package/dist/cli/dev.js +210 -0
  24. package/dist/cli/dev.js.map +1 -0
  25. package/dist/cli/index.d.ts +2 -0
  26. package/dist/cli/index.d.ts.map +1 -0
  27. package/dist/cli/index.js +77 -0
  28. package/dist/cli/index.js.map +1 -0
  29. package/dist/cli/start.d.ts +8 -0
  30. package/dist/cli/start.d.ts.map +1 -0
  31. package/dist/cli/start.js +39 -0
  32. package/dist/cli/start.js.map +1 -0
  33. package/dist/config.d.ts +121 -0
  34. package/dist/config.d.ts.map +1 -0
  35. package/dist/config.js +5 -0
  36. package/dist/config.js.map +1 -0
  37. package/dist/deploy/aws-lambda/runtime.d.ts +14 -0
  38. package/dist/deploy/aws-lambda/runtime.d.ts.map +1 -0
  39. package/dist/deploy/aws-lambda/runtime.js +24 -0
  40. package/dist/deploy/aws-lambda/runtime.js.map +1 -0
  41. package/dist/deploy/build-marker.d.ts +15 -0
  42. package/dist/deploy/build-marker.d.ts.map +1 -0
  43. package/dist/deploy/build-marker.js +26 -0
  44. package/dist/deploy/build-marker.js.map +1 -0
  45. package/dist/deploy/bun/runtime.d.ts +11 -0
  46. package/dist/deploy/bun/runtime.d.ts.map +1 -0
  47. package/dist/deploy/bun/runtime.js +22 -0
  48. package/dist/deploy/bun/runtime.js.map +1 -0
  49. package/dist/deploy/cloudflare/build.d.ts +7 -0
  50. package/dist/deploy/cloudflare/build.d.ts.map +1 -0
  51. package/dist/deploy/cloudflare/build.js +74 -0
  52. package/dist/deploy/cloudflare/build.js.map +1 -0
  53. package/dist/deploy/cloudflare/runtime.d.ts +10 -0
  54. package/dist/deploy/cloudflare/runtime.d.ts.map +1 -0
  55. package/dist/deploy/cloudflare/runtime.js +129 -0
  56. package/dist/deploy/cloudflare/runtime.js.map +1 -0
  57. package/dist/deploy/contract.d.ts +65 -0
  58. package/dist/deploy/contract.d.ts.map +1 -0
  59. package/dist/deploy/contract.js +2 -0
  60. package/dist/deploy/contract.js.map +1 -0
  61. package/dist/deploy/deno/runtime.d.ts +11 -0
  62. package/dist/deploy/deno/runtime.d.ts.map +1 -0
  63. package/dist/deploy/deno/runtime.js +16 -0
  64. package/dist/deploy/deno/runtime.js.map +1 -0
  65. package/dist/deploy/filesystem.d.ts +10 -0
  66. package/dist/deploy/filesystem.d.ts.map +1 -0
  67. package/dist/deploy/filesystem.js +47 -0
  68. package/dist/deploy/filesystem.js.map +1 -0
  69. package/dist/deploy/listen.d.ts +20 -0
  70. package/dist/deploy/listen.d.ts.map +1 -0
  71. package/dist/deploy/listen.js +24 -0
  72. package/dist/deploy/listen.js.map +1 -0
  73. package/dist/deploy/netlify/build.d.ts +8 -0
  74. package/dist/deploy/netlify/build.d.ts.map +1 -0
  75. package/dist/deploy/netlify/build.js +52 -0
  76. package/dist/deploy/netlify/build.js.map +1 -0
  77. package/dist/deploy/netlify/runtime.d.ts +13 -0
  78. package/dist/deploy/netlify/runtime.d.ts.map +1 -0
  79. package/dist/deploy/netlify/runtime.js +24 -0
  80. package/dist/deploy/netlify/runtime.js.map +1 -0
  81. package/dist/deploy/node/runtime.d.ts +7 -0
  82. package/dist/deploy/node/runtime.d.ts.map +1 -0
  83. package/dist/deploy/node/runtime.js +36 -0
  84. package/dist/deploy/node/runtime.js.map +1 -0
  85. package/dist/deploy/presets.d.ts +88 -0
  86. package/dist/deploy/presets.d.ts.map +1 -0
  87. package/dist/deploy/presets.js +110 -0
  88. package/dist/deploy/presets.js.map +1 -0
  89. package/dist/deploy/vercel/build.d.ts +3 -0
  90. package/dist/deploy/vercel/build.d.ts.map +1 -0
  91. package/dist/deploy/vercel/build.js +56 -0
  92. package/dist/deploy/vercel/build.js.map +1 -0
  93. package/dist/deploy/vercel/runtime.d.ts +14 -0
  94. package/dist/deploy/vercel/runtime.d.ts.map +1 -0
  95. package/dist/deploy/vercel/runtime.js +26 -0
  96. package/dist/deploy/vercel/runtime.js.map +1 -0
  97. package/dist/index.d.ts +23 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +17 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/router.d.ts +337 -0
  102. package/dist/router.d.ts.map +1 -0
  103. package/dist/router.js +18 -0
  104. package/dist/router.js.map +1 -0
  105. package/dist/runtime/boundaries.d.ts +102 -0
  106. package/dist/runtime/boundaries.d.ts.map +1 -0
  107. package/dist/runtime/boundaries.js +101 -0
  108. package/dist/runtime/boundaries.js.map +1 -0
  109. package/dist/runtime/client.d.ts +16 -0
  110. package/dist/runtime/client.d.ts.map +1 -0
  111. package/dist/runtime/client.js +16 -0
  112. package/dist/runtime/client.js.map +1 -0
  113. package/dist/runtime/context.d.ts +271 -0
  114. package/dist/runtime/context.d.ts.map +1 -0
  115. package/dist/runtime/context.js +360 -0
  116. package/dist/runtime/context.js.map +1 -0
  117. package/dist/runtime/control.d.ts +18 -0
  118. package/dist/runtime/control.d.ts.map +1 -0
  119. package/dist/runtime/control.js +39 -0
  120. package/dist/runtime/control.js.map +1 -0
  121. package/dist/runtime/dev-protocol.d.ts +22 -0
  122. package/dist/runtime/dev-protocol.d.ts.map +1 -0
  123. package/dist/runtime/dev-protocol.js +2 -0
  124. package/dist/runtime/dev-protocol.js.map +1 -0
  125. package/dist/runtime/empty-server-app.d.ts +3 -0
  126. package/dist/runtime/empty-server-app.d.ts.map +1 -0
  127. package/dist/runtime/empty-server-app.js +2 -0
  128. package/dist/runtime/empty-server-app.js.map +1 -0
  129. package/dist/runtime/entry.client.d.ts +2 -0
  130. package/dist/runtime/entry.client.d.ts.map +1 -0
  131. package/dist/runtime/entry.client.js +453 -0
  132. package/dist/runtime/entry.client.js.map +1 -0
  133. package/dist/runtime/entry.rsc.d.ts +32 -0
  134. package/dist/runtime/entry.rsc.d.ts.map +1 -0
  135. package/dist/runtime/entry.rsc.js +466 -0
  136. package/dist/runtime/entry.rsc.js.map +1 -0
  137. package/dist/runtime/entry.ssr.d.ts +25 -0
  138. package/dist/runtime/entry.ssr.d.ts.map +1 -0
  139. package/dist/runtime/entry.ssr.js +86 -0
  140. package/dist/runtime/entry.ssr.js.map +1 -0
  141. package/dist/runtime/navigation.d.ts +114 -0
  142. package/dist/runtime/navigation.d.ts.map +1 -0
  143. package/dist/runtime/navigation.js +112 -0
  144. package/dist/runtime/navigation.js.map +1 -0
  145. package/dist/runtime/request.d.ts +35 -0
  146. package/dist/runtime/request.d.ts.map +1 -0
  147. package/dist/runtime/request.js +39 -0
  148. package/dist/runtime/request.js.map +1 -0
  149. package/dist/runtime/server.d.ts +24 -0
  150. package/dist/runtime/server.d.ts.map +1 -0
  151. package/dist/runtime/server.js +24 -0
  152. package/dist/runtime/server.js.map +1 -0
  153. package/dist/server/compress.d.ts +15 -0
  154. package/dist/server/compress.d.ts.map +1 -0
  155. package/dist/server/compress.js +76 -0
  156. package/dist/server/compress.js.map +1 -0
  157. package/dist/server/headers.d.ts +24 -0
  158. package/dist/server/headers.d.ts.map +1 -0
  159. package/dist/server/headers.js +44 -0
  160. package/dist/server/headers.js.map +1 -0
  161. package/dist/server/load-config.d.ts +7 -0
  162. package/dist/server/load-config.d.ts.map +1 -0
  163. package/dist/server/load-config.js +41 -0
  164. package/dist/server/load-config.js.map +1 -0
  165. package/dist/server/load-env.d.ts +2 -0
  166. package/dist/server/load-env.d.ts.map +1 -0
  167. package/dist/server/load-env.js +10 -0
  168. package/dist/server/load-env.js.map +1 -0
  169. package/dist/server/prerendered.d.ts +67 -0
  170. package/dist/server/prerendered.d.ts.map +1 -0
  171. package/dist/server/prerendered.js +39 -0
  172. package/dist/server/prerendered.js.map +1 -0
  173. package/dist/server/server-config.d.ts +72 -0
  174. package/dist/server/server-config.d.ts.map +1 -0
  175. package/dist/server/server-config.js +94 -0
  176. package/dist/server/server-config.js.map +1 -0
  177. package/dist/server/shutdown.d.ts +7 -0
  178. package/dist/server/shutdown.d.ts.map +1 -0
  179. package/dist/server/shutdown.js +11 -0
  180. package/dist/server/shutdown.js.map +1 -0
  181. package/dist/server/ssg.d.ts +25 -0
  182. package/dist/server/ssg.d.ts.map +1 -0
  183. package/dist/server/ssg.js +160 -0
  184. package/dist/server/ssg.js.map +1 -0
  185. package/dist/server/static.d.ts +14 -0
  186. package/dist/server/static.d.ts.map +1 -0
  187. package/dist/server/static.js +30 -0
  188. package/dist/server/static.js.map +1 -0
  189. package/package.json +86 -0
package/README.md ADDED
@@ -0,0 +1,273 @@
1
+ # rshono
2
+
3
+ Minimalist web framework — [Hono](https://hono.dev) + [Rspack](https://rspack.rs) + [React Server Components](https://react.dev/reference/rsc/server-components).
4
+
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.
9
+
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.
11
+
12
+ ```bash
13
+ npm create @rshono@latest my-app # scaffold one, with a deploy target and tooling of your choosing
14
+ ```
15
+
16
+ ```bash
17
+ rshono dev # dev server with HMR (default port 3000)
18
+ rshono build # production build: client + server bundles + SSG
19
+ rshono start # run the production build
20
+ ```
21
+
22
+ ## Project layout
23
+
24
+ ```
25
+ rshono.config.ts optional — every field has a default (see Configuration)
26
+ public/ optional — served verbatim at the web root
27
+ src/
28
+ routes.ts required — the route table
29
+ server.ts optional — a Hono sub-app mounted ahead of the page routes
30
+ … everything else is yours to arrange
31
+ ```
32
+
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:
34
+
35
+ ```json
36
+ { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }
37
+ ```
38
+
39
+ ## The one required file: src/routes.ts
40
+
41
+ ```ts
42
+ import { defineRoutes } from '@rshono/core';
43
+
44
+ export const routes = defineRoutes({
45
+ routes: [
46
+ { path: '/', component: () => import('./components/home') },
47
+ { path: '/profile/:id', component: () => import('./components/profile') },
48
+ {
49
+ path: '/docs/:slug',
50
+ render: 'static',
51
+ component: () => import('./components/documentation'),
52
+ staticPaths: async () => [{ slug: 'getting-started' }, { slug: 'deployment' }],
53
+ },
54
+ { type: 'endpoint', path: '/api/health', server: () => import('./health') },
55
+ ],
56
+ notFound: { component: () => import('./components/404') },
57
+ error: { component: () => import('./components/500') },
58
+ });
59
+ ```
60
+
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.
62
+
63
+ ## Pages are server components
64
+
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.
68
+
69
+ ```tsx
70
+ import type { PageProps } from '@rshono/core';
71
+ import { db } from '../db';
72
+
73
+ export default async function Profile({ params, ctx }: PageProps<'/profile/:id'>) {
74
+ const user = await db.getUser(params.id);
75
+ const theme = ctx.cookies.get('theme') ?? 'light';
76
+ return <Layout>…</Layout>;
77
+ }
78
+ ```
79
+
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 `getContext()` returns from `@rshono/core/server`, handed over so a page needs no import; reach for `getContext()` 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`).
88
+
89
+ ## Server actions
90
+
91
+ `'use server'` modules export functions callable from client components:
92
+
93
+ ```ts
94
+ 'use server';
95
+ export async function createUser(data: { name: string; email: string }) { … }
96
+ ```
97
+
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.
99
+
100
+ ## Full Hono underneath
101
+
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
+ ```
134
+
135
+ Keep `type: 'css/auto'` rather than `'css'`, or `*.module.css` stops being a CSS module. `npm create @rshono@latest --tailwind` writes all four of these for you.
136
+
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.
146
+
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.
148
+
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.
150
+
151
+ ## Env & secret safety
152
+
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
+
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.
161
+
162
+ ## Configuration: rshono.config.ts
163
+
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.
165
+
166
+ ```ts
167
+ import { defineConfig } from '@rshono/core';
168
+
169
+ export default defineConfig({
170
+ deploy: 'node', // hosting platform to build for — see Deployment (--deploy or RSHONO_DEPLOY override)
171
+ siteUrl: 'https://example.com', // public origin, baked into prerendered pages' absolute URLs
172
+ port: 3000, // default port for dev/start (--port or PORT env override)
173
+ host: '0.0.0.0', // bind address for start (HOST env overrides)
174
+ trustProxy: false, // honour X-Forwarded-Host/-Proto — only behind a proxy you control
175
+ checkOrigin: true, // CSRF origin check on server-action POSTs
176
+ allowedOrigins: [], // extra origins allowed to post actions, e.g. ['https://admin.example.com']
177
+ csp: false, // strict per-request-nonce Content-Security-Policy
178
+ cspDirectives: {}, // widen the built-in CSP, e.g. { 'img-src': "'self' https://cdn.example.com" }
179
+ bodySizeLimit: '1mb', // request body cap: '512kb' | 4_000_000 | false to disable
180
+ renderTimeout: 10_000, // ms deadline for a request (action + flight + SSR)
181
+ compress: true, // gzip compressible responses (streaming-safe)
182
+ rspack(config, { isServer, isDev }) {
183
+ return config; // escape hatch: mutate the generated Rspack config
184
+ },
185
+ });
186
+ ```
187
+
188
+ `defineConfig` is an identity helper for editor autocomplete; `export default { … } satisfies RSHonoConfig` works too. `deploy`/`port`/`host`/`rspack` are consumed by the CLI; the framework settings (`trustProxy`, `checkOrigin`, `allowedOrigins`, `csp`, `cspDirectives`, `bodySizeLimit`, `renderTimeout`, `compress`) 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. The two deployment-conventional exceptions stay env-overridable: `--port`/`PORT` and `HOST` win over the file, which wins over the built-in default. Point `rshono build` at a different config with `--config <path>`.
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 (`getContext().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
+ - **Request deadline**: every request races a timeout (`renderTimeout`, default 10000) and the client-disconnect signal — covering the server action as well as flight + SSR — so neither a hung data fetch nor a hung mutation can pin sockets open.
196
+ - **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.
197
+ - **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.
198
+ - **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 `getContext().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.
199
+ - **`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.
200
+ - **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.
201
+ - **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.
202
+
203
+ ```ts
204
+ // src/server.ts
205
+ import { onServerError } from '@rshono/core/server';
206
+
207
+ onServerError((error, { source, request }) => {
208
+ Sentry.captureException(error, { tags: { source }, extra: { url: request.url } });
209
+ });
210
+ ```
211
+
212
+ - **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.
213
+ - **No blank screens.** Three fallbacks behind the `error` page, so a failure is always something you can read:
214
+ - 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).
215
+ - 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.
216
+ - A **client bootstrap failure** (a truncated or malformed initial payload) is reported and surfaced rather than becoming a silent unhandled rejection.
217
+
218
+ ## Testing
219
+
220
+ `pnpm --filter @rshono/core test` builds the package and runs everything that doesn't need a browser:
221
+
222
+ - **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.
223
+ - **compression** — that gzip does not swallow a streamed response: a chunk the renderer flushes has to reach the client while the response is still open, which is the one property the platform `CompressionStream` would quietly break.
224
+ - **production e2e** — builds `examples/rs-basic`, 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`).
225
+ - **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.
226
+ - **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.
227
+ - **dev** — a smoke test through the dev server's worker + proxy.
228
+
229
+ `pnpm --filter @rshono/core test:browser` runs the Playwright suite against a production build: hydration, soft navigation, prefetch-on-hover, `useNavigation`, client-initiated actions, boundary fallbacks, scroll restoration and the fatal overlay — the client runtime, which no amount of asserting on HTML can reach.
230
+
231
+ ## How it works
232
+
233
+ Two coordinated Rspack compilers (native RSC support, `rspack.experiments.rsc`):
234
+
235
+ - **client** (`target: web`) → `dist/static`: hydration runtime, `'use client'` chunks, CSS.
236
+ - **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).
237
+
238
+ 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.
239
+
240
+ 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`.
241
+
242
+ Everything in that bundle that depends on _where_ it runs — binding a port, serving `/_static` and `public/`, reading a prerendered page, gzipping, 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.
243
+
244
+ ## Deployment
245
+
246
+ `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.
247
+
248
+ | `deploy` | Handoff | Assets & prerendered pages | After `build` |
249
+ | ------------ | -------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------- |
250
+ | `node` | binds a port | from `dist/` on disk | `rshono start` |
251
+ | `bun` | `{ fetch, port }` default export | from `dist/` on disk | `bun dist/server/main.mjs` |
252
+ | `deno` | `{ fetch }` default export | from `dist/` on disk | `deno serve -A dist/server/main.mjs` |
253
+ | `cloudflare` | `{ fetch }` default export | Workers Assets; prerendered pages read via the `ASSETS` binding | `wrangler deploy` |
254
+ | `vercel` | web handler in a Node function | CDN for assets; prerendered pages inside the function | `vercel deploy --prebuilt` |
255
+ | `netlify` | web handler, Functions v2 | CDN for assets; prerendered pages inside the function | `netlify deploy --build=false --dir=.netlify/publish` |
256
+ | `aws-lambda` | streaming handler (Function URL) | from the deployment package | zip `dist/`, handler `dist/server/main.mjs` |
257
+
258
+ 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.
259
+
260
+ Notes worth knowing before choosing one:
261
+
262
+ - **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 `getContext().env`; they are not available under `rshono dev`, which is plain Node.
263
+ - **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.
264
+ - **Compression** is left to the platform on `cloudflare`, `vercel` and `netlify`; the framework's streaming gzip is used on `node`, `bun`, `deno` and `aws-lambda`. Your `compress` setting only decides whether an available compressor is used.
265
+ - **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 `getContext().env` would be empty there, which is a documented feature quietly doing nothing.
266
+ - `rshono start` refuses a build made for another platform rather than starting a bundle with no listener in it.
267
+
268
+ ## Requirements & limitations
269
+
270
+ - Node ≥ 22.1 (worker threads, `process.loadEnvFile`, `Promise.withResolvers`, `URL.parse`), React ≥ 19.1 (the floor `react-server-dom-rspack` itself requires).
271
+ - Responses are gzipped, not brotli — one encoding every client accepts, chosen per chunk so streaming survives. Set `compress: false` behind a proxy that does better.
272
+ - Dev-mode proxy doesn't forward WebSocket upgrades to a custom sub-app (prod is unaffected — the bundle owns the socket there).
273
+ - 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).
package/bin/rshono.mjs ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ // Thin launcher for the compiled CLI. Everything it does has to happen *before* the CLI module
3
+ // graph loads, which is why the import below is dynamic rather than a static one at the top.
4
+ const args = process.argv.slice(2);
5
+
6
+ // `build` and `start` are the production commands, so they default NODE_ENV accordingly — the
7
+ // value is read during config resolution and baked into the bundle, so it has to be set this early.
8
+ if ((args[0] === 'build' || args[0] === 'start') && !process.env.NODE_ENV) {
9
+ process.env.NODE_ENV = 'production';
10
+ }
11
+
12
+ // dist ships source maps, so framework stack traces point at the original TypeScript.
13
+ process.setSourceMapsEnabled(true);
14
+
15
+ await import('../dist/cli/index.js');
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+ const DIRECTIVE_PROLOGUE = /^(?:\s|\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)*(?:(['"])use [a-z -]+\1\s*;?)?/;
3
+
4
+ /**
5
+ * Shadows `process.env` with the PUBLIC_-only view inside SSR-layer modules, so a secret read from a
6
+ * `'use client'` component renders empty on the server instead of leaking into the HTML stream (and
7
+ * SSR output keeps agreeing with hydration, which sees the same view via DefinePlugin).
8
+ *
9
+ * This is a security control, so it fails the build rather than passing source through when it can't
10
+ * confirm which layer a module is in — `_module` is a private Rspack field, and silently no-op'ing
11
+ * if a future version renames it would drop the guarantee with nothing to notice it.
12
+ */
13
+ module.exports = function envShadowLoader(source) {
14
+ if (!source.includes('process.env')) return source;
15
+ const { prelude, layer } = this.getOptions();
16
+ if (!this._module) {
17
+ throw new Error(
18
+ "[rshono] env-shadow-loader could not read the module's layer (Rspack's `this._module` is unavailable). " +
19
+ 'That check is what keeps server secrets out of SSR-rendered client components, so the build is failed ' +
20
+ 'instead of shipping without it. This is a framework/Rspack incompatibility — please report it.',
21
+ );
22
+ }
23
+ if (this._module.layer !== layer) return source;
24
+ const prologue = source.match(DIRECTIVE_PROLOGUE)[0];
25
+ return prologue + prelude + source.slice(prologue.length);
26
+ };
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+ const OPENS_WITH_DIRECTIVE = /^(?:\s|\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)*(['"])use (?:client|server|server-entry)\1\s*(?:;|\n|$)/;
3
+
4
+ module.exports = function pageEntryLoader(source) {
5
+ if (OPENS_WITH_DIRECTIVE.test(source)) return source;
6
+ return "'use server-entry';" + source;
7
+ };
@@ -0,0 +1,2 @@
1
+ export declare function scanPageFiles(routesFile: string, srcDir: string, into: Set<string>): void;
2
+ //# sourceMappingURL=page-files.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,45 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ const COMPONENT_THUNK = /component:\s*(?:async\s*)?\(\s*\)\s*=>\s*import\(\s*(['"])([^'"]+)\1\s*\)/g;
4
+ 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.
8
+ function resolveCandidates(base) {
9
+ return [...EXTENSIONS.map((ext) => base + ext), ...EXTENSIONS.map((ext) => join(base, `index${ext}`)), base];
10
+ }
11
+ function isFile(path) {
12
+ try {
13
+ return statSync(path).isFile();
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
19
+ export function scanPageFiles(routesFile, srcDir, into) {
20
+ into.clear();
21
+ let source;
22
+ try {
23
+ source = readFileSync(routesFile, 'utf8');
24
+ }
25
+ catch {
26
+ return;
27
+ }
28
+ for (const match of source.matchAll(COMPONENT_THUNK)) {
29
+ const spec = match[2];
30
+ let base;
31
+ if (spec.startsWith('.'))
32
+ base = resolve(dirname(routesFile), spec);
33
+ else if (spec.startsWith('@/'))
34
+ base = resolve(srcDir, spec.slice(2));
35
+ if (!base)
36
+ continue;
37
+ for (const candidate of resolveCandidates(base)) {
38
+ if (isFile(candidate)) {
39
+ into.add(candidate);
40
+ break;
41
+ }
42
+ }
43
+ }
44
+ }
45
+ //# sourceMappingURL=page-files.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,2 @@
1
+ export declare function publicEnv(isDev: boolean): Record<string, string>;
2
+ //# sourceMappingURL=public-env.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,5 @@
1
+ export function publicEnv(isDev) {
2
+ const entries = Object.entries(process.env).filter((entry) => entry[0].startsWith('PUBLIC_') && entry[1] !== undefined);
3
+ return { NODE_ENV: isDev ? 'development' : 'production', ...Object.fromEntries(entries) };
4
+ }
5
+ //# sourceMappingURL=public-env.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,14 @@
1
+ import { type RspackOptions } from '@rspack/core';
2
+ import type { RSHonoConfig } from '../config.js';
3
+ import type { DeployPreset } from '../deploy/presets.js';
4
+ export interface RspackConfigOptions {
5
+ rootDir: string;
6
+ isDev: boolean;
7
+ /** The project's resolved config — supplies the {@link RSHonoConfig.rspack} hook and the runtime settings baked into the server bundle. */
8
+ config: RSHonoConfig;
9
+ /** The platform being built for: decides the `@rshono/deploy` runtime and any server-compiler overrides. */
10
+ preset: DeployPreset;
11
+ onServerComponentChanges?: () => void;
12
+ }
13
+ export declare function createConfigs(options: RspackConfigOptions): [RspackOptions, RspackOptions];
14
+ //# sourceMappingURL=rspack-config.d.ts.map
@@ -0,0 +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;AAI3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AA4CzD,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"}