@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.
- package/README.md +195 -168
- package/dist/builder/page-files.d.ts.map +1 -1
- package/dist/builder/page-files.js +7 -3
- package/dist/builder/page-files.js.map +1 -1
- package/dist/builder/public-env.d.ts +6 -0
- package/dist/builder/public-env.d.ts.map +1 -1
- package/dist/builder/public-env.js +6 -0
- package/dist/builder/public-env.js.map +1 -1
- package/dist/builder/rspack-config.d.ts.map +1 -1
- package/dist/builder/rspack-config.js +16 -19
- package/dist/builder/rspack-config.js.map +1 -1
- package/dist/cli/dev.d.ts.map +1 -1
- package/dist/cli/dev.js +63 -35
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/index.js +3 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/config.d.ts +56 -55
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -1
- package/dist/config.js.map +1 -1
- package/dist/deploy/contract.d.ts +15 -10
- package/dist/deploy/contract.d.ts.map +1 -1
- package/dist/deploy/contract.js.map +1 -1
- package/dist/deploy/filesystem.d.ts.map +1 -1
- package/dist/deploy/filesystem.js +6 -7
- package/dist/deploy/filesystem.js.map +1 -1
- package/dist/deploy/node/runtime.d.ts +5 -5
- package/dist/deploy/node/runtime.d.ts.map +1 -1
- package/dist/deploy/node/runtime.js +7 -11
- package/dist/deploy/node/runtime.js.map +1 -1
- package/dist/deploy/presets.d.ts +2 -6
- package/dist/deploy/presets.d.ts.map +1 -1
- package/dist/deploy/presets.js +2 -6
- package/dist/deploy/presets.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +74 -36
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +2 -3
- package/dist/router.js.map +1 -1
- package/dist/runtime/boundaries.d.ts +10 -0
- package/dist/runtime/boundaries.d.ts.map +1 -1
- package/dist/runtime/boundaries.js +6 -0
- package/dist/runtime/boundaries.js.map +1 -1
- package/dist/runtime/client.d.ts +5 -3
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +5 -3
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/context.d.ts +195 -45
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/context.js +277 -59
- package/dist/runtime/context.js.map +1 -1
- package/dist/runtime/control.d.ts.map +1 -1
- package/dist/runtime/control.js +7 -0
- package/dist/runtime/control.js.map +1 -1
- package/dist/runtime/entry.client.js +29 -47
- package/dist/runtime/entry.client.js.map +1 -1
- package/dist/runtime/entry.rsc.d.ts.map +1 -1
- package/dist/runtime/entry.rsc.js +109 -140
- package/dist/runtime/entry.rsc.js.map +1 -1
- package/dist/runtime/flight-inject.d.ts +4 -4
- package/dist/runtime/flight-inject.js +2 -2
- package/dist/runtime/flight-inject.js.map +1 -1
- package/dist/runtime/navigation.d.ts +11 -0
- package/dist/runtime/navigation.d.ts.map +1 -1
- package/dist/runtime/navigation.js +3 -0
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/request.d.ts +3 -3
- package/dist/runtime/request.js +3 -3
- package/dist/runtime/request.js.map +1 -1
- package/dist/runtime/server.d.ts +7 -12
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +15 -12
- package/dist/runtime/server.js.map +1 -1
- package/dist/server/prerendered.d.ts +20 -35
- package/dist/server/prerendered.d.ts.map +1 -1
- package/dist/server/prerendered.js +8 -18
- package/dist/server/prerendered.js.map +1 -1
- package/dist/server/server-config.d.ts +10 -29
- package/dist/server/server-config.d.ts.map +1 -1
- package/dist/server/server-config.js +4 -70
- package/dist/server/server-config.js.map +1 -1
- package/dist/server/ssg.d.ts.map +1 -1
- package/dist/server/ssg.js +10 -11
- package/dist/server/ssg.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
5
|
+
<h1 align="center">@rshono/core</h1>
|
|
4
6
|
|
|
5
|
-
>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
>
|
|
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,
|
|
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 +
|
|
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
|
|
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
|
-
…
|
|
45
|
+
… everything else is yours to arrange
|
|
31
46
|
```
|
|
32
47
|
|
|
33
|
-
Only
|
|
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
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
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 }`
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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>` /
|
|
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
|
|
103
|
-
- `src/server.ts` may default-export a whole Hono sub-app: any method, streaming, cookies, middleware.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
139
|
+
[Hono docs](https://www.rshono.com/docs/hono).
|
|
136
140
|
|
|
137
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
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
|
-
|
|
228
|
+
## Prerendering (`render: 'static'`)
|
|
253
229
|
|
|
254
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
269
|
+
## Requirements & limitations
|
|
270
270
|
|
|
271
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
-
|
|
288
|
-
-
|
|
289
|
-
|
|
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":"
|
|
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
|
-
//
|
|
6
|
-
//
|
|
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,
|
|
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":["
|
|
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;
|
|
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"}
|