@valentinkolb/ssr 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -3
- package/package.json +1 -1
- package/src/adapter/bun.ts +1 -1
- package/src/adapter/elysia.ts +1 -1
- package/src/adapter/hono.ts +16 -10
- package/src/index.ts +9 -3
package/README.md
CHANGED
|
@@ -145,7 +145,7 @@ import Counter from "../components/Counter.island";
|
|
|
145
145
|
|
|
146
146
|
export default ssr(async (c) => {
|
|
147
147
|
c.get("page").title = "Home";
|
|
148
|
-
return <Counter initial={5} />;
|
|
148
|
+
return () => <Counter initial={5} />;
|
|
149
149
|
});
|
|
150
150
|
```
|
|
151
151
|
|
|
@@ -174,6 +174,45 @@ NODE_ENV=development bun --watch --preload=./scripts/preload.ts src/server.ts
|
|
|
174
174
|
- Hono: `@valentinkolb/ssr/hono`
|
|
175
175
|
- Elysia: `@valentinkolb/ssr/elysia`
|
|
176
176
|
|
|
177
|
+
## Rendering API
|
|
178
|
+
|
|
179
|
+
`html()` and Hono `ssr()` handlers expect a synchronous render function:
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
export default ssr(async (c) => {
|
|
183
|
+
const data = await loadData();
|
|
184
|
+
c.get("page").title = data.title;
|
|
185
|
+
|
|
186
|
+
return () => <Page data={data} />;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
app.get("/", () => html(() => <Page />));
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Do async work in the handler before returning the render function. Do not make the render function itself `async`; Solid SSR expects synchronous JSX evaluation.
|
|
193
|
+
|
|
194
|
+
### v0.9.0 migration
|
|
195
|
+
|
|
196
|
+
This is a breaking change in v0.9.0. In v0.8.x and earlier, examples often returned already-created JSX:
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
// v0.8.x and earlier
|
|
200
|
+
export default ssr(async () => <Page />);
|
|
201
|
+
|
|
202
|
+
app.get("/", () => html(<Page />));
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
In v0.9.0, wrap JSX creation in a render function:
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
// v0.9.0+
|
|
209
|
+
export default ssr(async () => () => <Page />);
|
|
210
|
+
|
|
211
|
+
app.get("/", () => html(() => <Page />));
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
This ensures Solid primitives such as `createUniqueId()` run inside `renderToString()`, where the SSR context exists.
|
|
215
|
+
|
|
177
216
|
## `createConfig` options
|
|
178
217
|
|
|
179
218
|
```ts
|
|
@@ -206,7 +245,7 @@ export const { config, html } = createConfig({
|
|
|
206
245
|
// docs-app.ts
|
|
207
246
|
const docsApp = new Hono()
|
|
208
247
|
.route("/_ssr", routes(config))
|
|
209
|
-
.get("/", () => html(<DocsHome />));
|
|
248
|
+
.get("/", () => html(() => <DocsHome />));
|
|
210
249
|
|
|
211
250
|
// host-app.ts
|
|
212
251
|
export default new Hono().route("/docs", docsApp);
|
|
@@ -234,7 +273,7 @@ await Bun.build({
|
|
|
234
273
|
|
|
235
274
|
- initializes `c.get("page")` as typed page options
|
|
236
275
|
- accepts middlewares/validators before final handler
|
|
237
|
-
- lets handlers return either
|
|
276
|
+
- lets handlers return either a synchronous render function or `Response`
|
|
238
277
|
|
|
239
278
|
## Dev mode tools
|
|
240
279
|
|
package/package.json
CHANGED
package/src/adapter/bun.ts
CHANGED
package/src/adapter/elysia.ts
CHANGED
package/src/adapter/hono.ts
CHANGED
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import { createFactory } from "hono/factory";
|
|
7
7
|
import type { Context, Env, Handler, MiddlewareHandler, TypedResponse } from "hono";
|
|
8
|
-
import type {
|
|
9
|
-
import type { SsrConfig, HtmlFn } from "../index";
|
|
8
|
+
import type { SsrConfig, HtmlFn, RenderFn } from "../index";
|
|
10
9
|
import { getSsrDir, getCacheHeaders, createReloadResponse, safePath } from "./utils";
|
|
11
10
|
|
|
12
11
|
// ============================================================================
|
|
@@ -20,8 +19,8 @@ type PageEnv<T extends object> = {
|
|
|
20
19
|
};
|
|
21
20
|
};
|
|
22
21
|
|
|
23
|
-
/** SSR handler return type -
|
|
24
|
-
type SsrHandlerResult =
|
|
22
|
+
/** SSR handler return type - render function or Response (for redirects etc.) */
|
|
23
|
+
type SsrHandlerResult = RenderFn | Response | TypedResponse;
|
|
25
24
|
|
|
26
25
|
/** SSR handler function signature */
|
|
27
26
|
type SsrHandler<E extends Env, T extends object> = (
|
|
@@ -41,7 +40,7 @@ type SsrHandlers = [MiddlewareHandler, ...MiddlewareHandler[], Handler];
|
|
|
41
40
|
* Features:
|
|
42
41
|
* - Full compatibility with Hono middlewares/validators
|
|
43
42
|
* - Type-safe page options via `c.get("page")`
|
|
44
|
-
* - Return
|
|
43
|
+
* - Return a render function or Response for redirects
|
|
45
44
|
* - No manual `html()` call needed
|
|
46
45
|
*
|
|
47
46
|
* @example
|
|
@@ -88,7 +87,7 @@ type SsrHandlers = [MiddlewareHandler, ...MiddlewareHandler[], Handler];
|
|
|
88
87
|
* c.get("page").title = room.name;
|
|
89
88
|
* c.get("page").description = `Welcome to ${room.name}`;
|
|
90
89
|
*
|
|
91
|
-
* return <RoomView room={room} />;
|
|
90
|
+
* return () => <RoomView room={room} />;
|
|
92
91
|
* }
|
|
93
92
|
* );
|
|
94
93
|
* ```
|
|
@@ -125,7 +124,7 @@ export const createSSRHandler = <T extends object>(html: HtmlFn<T>) => {
|
|
|
125
124
|
await next();
|
|
126
125
|
});
|
|
127
126
|
|
|
128
|
-
// Wrapped handler:
|
|
127
|
+
// Wrapped handler: render function → html(), Response → passthrough
|
|
129
128
|
const wrappedHandler: Handler = async (c) => {
|
|
130
129
|
const result = await finalHandler(c as Context<E & PageEnv<T>>);
|
|
131
130
|
|
|
@@ -134,8 +133,15 @@ export const createSSRHandler = <T extends object>(html: HtmlFn<T>) => {
|
|
|
134
133
|
return result;
|
|
135
134
|
}
|
|
136
135
|
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
if (typeof result !== "function") {
|
|
137
|
+
throw new Error("[ssr] ssr() handlers must return a render function: return () => <Page />");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (result.constructor.name === "AsyncFunction") {
|
|
141
|
+
throw new Error("[ssr] ssr() render functions must be synchronous: return () => <Page />");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return html(result as RenderFn, c.get("page") as T);
|
|
139
145
|
};
|
|
140
146
|
|
|
141
147
|
// Return tuple for spread: .get('/path', ...ssr(handler))
|
|
@@ -155,7 +161,7 @@ export const createSSRHandler = <T extends object>(html: HtmlFn<T>) => {
|
|
|
155
161
|
* import { routes } from "@valentinkolb/ssr/adapter/hono";
|
|
156
162
|
* const app = new Hono()
|
|
157
163
|
* .route("/_ssr", routes(config))
|
|
158
|
-
* .get("/", () => html(<Home />));
|
|
164
|
+
* .get("/", () => html(() => <Home />));
|
|
159
165
|
* ```
|
|
160
166
|
*/
|
|
161
167
|
export const routes = (config: SsrConfig) => {
|
package/src/index.ts
CHANGED
|
@@ -68,7 +68,9 @@ export type SsrConfig = {
|
|
|
68
68
|
ssrPath: string;
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
export type
|
|
71
|
+
export type RenderFn = () => JSX.Element;
|
|
72
|
+
|
|
73
|
+
export type HtmlFn<T extends object> = (render: RenderFn, options?: T) => Promise<Response>;
|
|
72
74
|
|
|
73
75
|
type PluginFn = () => BunPlugin;
|
|
74
76
|
|
|
@@ -151,8 +153,12 @@ export const createConfig = <T extends object = object>(options: SsrOptions<T> =
|
|
|
151
153
|
const devConfigScript = `<script>globalThis.__SSR_CONFIG=${JSON.stringify({ ssrPath })}</script>`;
|
|
152
154
|
|
|
153
155
|
// HTML renderer
|
|
154
|
-
const html: HtmlFn<T> = async (
|
|
155
|
-
|
|
156
|
+
const html: HtmlFn<T> = async (render, opts = {} as T) => {
|
|
157
|
+
if (render.constructor.name === "AsyncFunction") {
|
|
158
|
+
throw new Error("[ssr] html() expects a synchronous render function: html(() => <Page />)");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const body = renderToString(render);
|
|
156
162
|
|
|
157
163
|
// Framework-injected assets
|
|
158
164
|
let scripts = `${islandDisplayStyle}\n${hydrationScript}`;
|