@k2b/ssr 0.12.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.
- package/LICENSE +21 -0
- package/README.md +402 -0
- package/package.json +69 -0
- package/src/adapter/bun.ts +51 -0
- package/src/adapter/client.js +421 -0
- package/src/adapter/elysia.ts +39 -0
- package/src/adapter/hono.ts +190 -0
- package/src/adapter/utils.ts +190 -0
- package/src/build.ts +230 -0
- package/src/index.ts +246 -0
- package/src/island-id.ts +78 -0
- package/src/island-resolve.ts +39 -0
- package/src/nav.ts +308 -0
- package/src/transform.ts +157 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Valentin Kolb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# @k2b/ssr
|
|
2
|
+
|
|
3
|
+
Minimal SSR + islands framework for SolidJS on Bun.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This library renders Solid components on the server and hydrates only interactive islands on the client.
|
|
8
|
+
|
|
9
|
+
It uses file conventions:
|
|
10
|
+
|
|
11
|
+
- `*.island.tsx`: SSR + hydrated on the client
|
|
12
|
+
- `*.client.tsx`: client-only (no SSR HTML content)
|
|
13
|
+
- `*.tsx`: server-only output
|
|
14
|
+
|
|
15
|
+
## Size & Philosophy
|
|
16
|
+
|
|
17
|
+
This framework is intentionally minimal and focused on SSR + islands only.
|
|
18
|
+
|
|
19
|
+
Current source size in this repo (`packages/ssr-core/src`):
|
|
20
|
+
|
|
21
|
+
| Component | Lines | Raw | Gzipped |
|
|
22
|
+
| --- | ---: | ---: | ---: |
|
|
23
|
+
| Core (`index`, `transform`, `build`, island ID + resolver) | ~750 | 26.2 KB | 8.3 KB |
|
|
24
|
+
| Dev client (overlay + reload, dev only) | ~421 | 11.9 KB | 3.5 KB |
|
|
25
|
+
| Adapters (`bun`, `hono`, `elysia`, shared utils) | ~465 | 14.4 KB | 4.8 KB |
|
|
26
|
+
|
|
27
|
+
Important: these sizes describe framework source code that runs at build time and on the server.
|
|
28
|
+
The browser receives only:
|
|
29
|
+
|
|
30
|
+
- your island bundles
|
|
31
|
+
- Solid runtime from your app dependencies
|
|
32
|
+
- `seroval` deserialize runtime
|
|
33
|
+
- a tiny hydration import snippet
|
|
34
|
+
|
|
35
|
+
Framework overhead in the browser is intentionally small.
|
|
36
|
+
|
|
37
|
+
What is intentionally not included:
|
|
38
|
+
|
|
39
|
+
- no client-side router
|
|
40
|
+
- no state management layer
|
|
41
|
+
- no CSS-in-JS abstraction
|
|
42
|
+
- no build tool wrapper around Bun
|
|
43
|
+
|
|
44
|
+
Use the libraries you already prefer. This package only handles SSR and islands hydration.
|
|
45
|
+
The optional `@k2b/ssr/nav` subpath provides progressive anchor
|
|
46
|
+
enhancement for islands, but it still does not add route matching, loaders, or
|
|
47
|
+
SPA routing.
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- Small SSR core with Bun-native build/plugin flow
|
|
52
|
+
- Adapters for Bun, Hono, and Elysia
|
|
53
|
+
- Type-safe Hono page helper via `createSSRHandler`
|
|
54
|
+
- Optional progressive navigation helpers via `@k2b/ssr/nav`
|
|
55
|
+
- Monorepo support via `rootDir`
|
|
56
|
+
- Public path mounting via `basePath` for microfrontends
|
|
57
|
+
- Stable file-path-based island IDs (collision-safe across workspace packages)
|
|
58
|
+
- Production chunk cache busting (`/_ssr/*.js?v=<buildTimestamp>`)
|
|
59
|
+
- Linked development source maps and validator-aware asset delivery
|
|
60
|
+
- Stale generated island assets removed after successful builds
|
|
61
|
+
- Visibility-aware development reload with cross-tab SSE coordination
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bun add @k2b/ssr solid-js
|
|
67
|
+
|
|
68
|
+
# choose adapter deps you need
|
|
69
|
+
bun add hono
|
|
70
|
+
# or
|
|
71
|
+
bun add elysia
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Package scope migration
|
|
75
|
+
|
|
76
|
+
`@k2b/ssr` is the maintained successor to `@valentinkolb/ssr`. There are no
|
|
77
|
+
framework API changes in the scope migration. Replace the dependency and all
|
|
78
|
+
root or subpath imports:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// Before
|
|
82
|
+
import { createConfig } from "@valentinkolb/ssr";
|
|
83
|
+
import { routes } from "@valentinkolb/ssr/hono";
|
|
84
|
+
|
|
85
|
+
// After
|
|
86
|
+
import { createConfig } from "@k2b/ssr";
|
|
87
|
+
import { routes } from "@k2b/ssr/hono";
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Required TypeScript settings
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"compilerOptions": {
|
|
95
|
+
"lib": ["ESNext", "DOM"],
|
|
96
|
+
"jsx": "preserve",
|
|
97
|
+
"jsxImportSource": "solid-js",
|
|
98
|
+
"moduleResolution": "bundler"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Quick Start (Hono)
|
|
104
|
+
|
|
105
|
+
### 1) Create config
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// config.ts
|
|
109
|
+
import { createConfig } from "@k2b/ssr";
|
|
110
|
+
import { createSSRHandler, routes } from "@k2b/ssr/hono";
|
|
111
|
+
|
|
112
|
+
type PageOptions = {
|
|
113
|
+
title?: string;
|
|
114
|
+
description?: string;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const { config, plugin, html } = createConfig<PageOptions>({
|
|
118
|
+
dev: process.env.NODE_ENV === "development",
|
|
119
|
+
// For monorepos with separated packages:
|
|
120
|
+
// rootDir: "/path/to/workspace-root",
|
|
121
|
+
// For microfrontends mounted under /docs:
|
|
122
|
+
// basePath: "/docs",
|
|
123
|
+
template: ({ body, scripts, title, description }) => `
|
|
124
|
+
<!doctype html>
|
|
125
|
+
<html>
|
|
126
|
+
<head>
|
|
127
|
+
<meta charset="utf-8" />
|
|
128
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
129
|
+
<title>${title ?? "App"}</title>
|
|
130
|
+
${description ? `<meta name="description" content="${description}">` : ""}
|
|
131
|
+
</head>
|
|
132
|
+
<body>${body}${scripts}</body>
|
|
133
|
+
</html>
|
|
134
|
+
`,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export const ssr = createSSRHandler(html);
|
|
138
|
+
export { routes };
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 2) Register plugin in dev
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
// scripts/preload.ts
|
|
145
|
+
import { plugin } from "../config";
|
|
146
|
+
|
|
147
|
+
Bun.plugin(plugin());
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 3) Create an island
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
// components/Counter.island.tsx
|
|
154
|
+
import { createSignal } from "solid-js";
|
|
155
|
+
|
|
156
|
+
export default function Counter({ initial = 0 }: { initial?: number }) {
|
|
157
|
+
const [count, setCount] = createSignal(initial);
|
|
158
|
+
return <button onClick={() => setCount((c) => c + 1)}>Count: {count()}</button>;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 4) Create a page
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
// pages/Home.tsx
|
|
166
|
+
import { ssr } from "../config";
|
|
167
|
+
import Counter from "../components/Counter.island";
|
|
168
|
+
|
|
169
|
+
export default ssr(async (c) => {
|
|
170
|
+
c.get("page").title = "Home";
|
|
171
|
+
return () => <Counter initial={5} />;
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 5) Wire server
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
// server.ts
|
|
179
|
+
import { Hono } from "hono";
|
|
180
|
+
import { config, routes } from "./config";
|
|
181
|
+
import Home from "./pages/Home";
|
|
182
|
+
|
|
183
|
+
export default new Hono()
|
|
184
|
+
.route("/_ssr", routes(config))
|
|
185
|
+
.get("/", ...Home);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 6) Run
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
NODE_ENV=development bun --watch --preload=./scripts/preload.ts src/server.ts
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Adapter imports
|
|
195
|
+
|
|
196
|
+
- Bun: `@k2b/ssr/bun`
|
|
197
|
+
- Hono: `@k2b/ssr/hono`
|
|
198
|
+
- Elysia: `@k2b/ssr/elysia`
|
|
199
|
+
|
|
200
|
+
## Optional Navigation Helpers
|
|
201
|
+
|
|
202
|
+
`@k2b/ssr/nav` is an opt-in browser helper for islands that want to
|
|
203
|
+
update URL history after they have already updated client state.
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
import { createSignal, onCleanup, onMount } from "solid-js";
|
|
207
|
+
import { Link, listenPopState, type LinkNavigateEvent } from "@k2b/ssr/nav";
|
|
208
|
+
|
|
209
|
+
export default function Tabs() {
|
|
210
|
+
const [tab, setTab] = createSignal("alpha");
|
|
211
|
+
|
|
212
|
+
onMount(() => {
|
|
213
|
+
onCleanup(
|
|
214
|
+
listenPopState(({ url }) => {
|
|
215
|
+
setTab(url.searchParams.get("tab") ?? "alpha");
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const openTab = (nav: LinkNavigateEvent) => {
|
|
221
|
+
const next = nav.url.searchParams.get("tab") ?? "alpha";
|
|
222
|
+
setTab(next);
|
|
223
|
+
nav.push(`/demo?tab=${next}`, { scroll: "preserve", state: { tab: next } });
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<Link href="/demo?tab=beta" scroll="preserve" onNavigate={openTab}>
|
|
228
|
+
Open beta
|
|
229
|
+
</Link>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`Link` renders a real `<a href>` during SSR. Enhanced clicks only run in the
|
|
235
|
+
browser for same-origin, left-click navigation without modifier keys. Without
|
|
236
|
+
`onNavigate`, `Link` calls `navigate()` directly and only updates browser
|
|
237
|
+
history. With `onNavigate`, the island owns data loading and state updates, then
|
|
238
|
+
calls `nav.push()`, `nav.replaceWith()`, or `nav.fallback()`.
|
|
239
|
+
|
|
240
|
+
Use `listenPopState()` whenever `nav.push()` represents client state. Browser
|
|
241
|
+
Back/Forward changes history but cannot infer how an island maps the URL back to
|
|
242
|
+
signals or stores. The helper reports the current `URL`, native `PopStateEvent`,
|
|
243
|
+
and history state without adding route matching or data loading.
|
|
244
|
+
|
|
245
|
+
Navigation behavior:
|
|
246
|
+
|
|
247
|
+
- reactive anchor props remain reactive after `Link` renders
|
|
248
|
+
- same-document hash links retain native target scrolling unless `onNavigate`
|
|
249
|
+
or `scroll` explicitly takes ownership
|
|
250
|
+
- relative URLs follow `document.baseURI`
|
|
251
|
+
- cross-origin `navigate()` calls use full document navigation
|
|
252
|
+
- replace navigation preserves existing `history.state` unless `state` is set
|
|
253
|
+
- rejected async `onNavigate` callbacks log the error and fall back to a full
|
|
254
|
+
document navigation
|
|
255
|
+
|
|
256
|
+
Available exports:
|
|
257
|
+
|
|
258
|
+
- `Link`
|
|
259
|
+
- `navigate()`, `navigateTo()`, `documentNavigate()`, `currentPathWithQuery()`, `refreshCurrentPath()`
|
|
260
|
+
- `captureScroll()`, `restoreScroll()`, `listenPopState()`, `startViewTransition()`
|
|
261
|
+
- `LinkNavigateEvent`, `LinkProps`, `EnhancedNavigateOptions`, `NavigationScrollMode`, `PopStateNavigationEvent`, `ScrollSnapshot`
|
|
262
|
+
|
|
263
|
+
Use `data-scroll-preserve="stable-key"` on scroll containers that should keep
|
|
264
|
+
their scroll position across enhanced navigation.
|
|
265
|
+
|
|
266
|
+
## Rendering API
|
|
267
|
+
|
|
268
|
+
`html()` and Hono `ssr()` handlers expect a synchronous render function:
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
export default ssr(async (c) => {
|
|
272
|
+
const data = await loadData();
|
|
273
|
+
c.get("page").title = data.title;
|
|
274
|
+
|
|
275
|
+
return () => <Page data={data} />;
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
app.get("/", () => html(() => <Page />));
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
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.
|
|
282
|
+
|
|
283
|
+
### v0.9.0 migration
|
|
284
|
+
|
|
285
|
+
This is a breaking change in v0.9.0. In v0.8.x and earlier, examples often returned already-created JSX:
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
// v0.8.x and earlier
|
|
289
|
+
export default ssr(async () => <Page />);
|
|
290
|
+
|
|
291
|
+
app.get("/", () => html(<Page />));
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
In v0.9.0, wrap JSX creation in a render function:
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
// v0.9.0+
|
|
298
|
+
export default ssr(async () => () => <Page />);
|
|
299
|
+
|
|
300
|
+
app.get("/", () => html(() => <Page />));
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
This ensures Solid primitives such as `createUniqueId()` run inside `renderToString()`, where the SSR context exists.
|
|
304
|
+
|
|
305
|
+
## `createConfig` options
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
createConfig({
|
|
309
|
+
dev?: boolean; // default: false
|
|
310
|
+
verbose?: boolean; // default: !dev
|
|
311
|
+
rootDir?: string; // default: process.cwd()
|
|
312
|
+
basePath?: string; // default: "", example: "/docs"
|
|
313
|
+
external?: string[]; // passed to Bun.build for island bundle
|
|
314
|
+
devSourcemap?: "none" | "linked" | "inline"; // default: "linked"
|
|
315
|
+
template?: ({ body, scripts, ...custom }) => string | Promise<string>;
|
|
316
|
+
})
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Notes
|
|
320
|
+
|
|
321
|
+
- `rootDir` is important in monorepos where server entrypoint and island files live in different packages.
|
|
322
|
+
- `basePath` moves SSR assets and dev endpoints under that prefix, e.g. `/docs/_ssr`.
|
|
323
|
+
- Development builds emit linked source maps by default. Use `"inline"` only when a tool requires embedded maps, or `"none"` to disable them.
|
|
324
|
+
- In production, hydration imports include a build timestamp query (`?v=...`) for cache busting.
|
|
325
|
+
- All adapters stream island assets from `Bun.file`. Production assets and content-hashed development chunks are immutable; stable development entries and source maps use validators for inexpensive freshness checks.
|
|
326
|
+
|
|
327
|
+
## Microfrontend mount example
|
|
328
|
+
|
|
329
|
+
Use `basePath` when the SSR app is mounted under a sub-path:
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
// config.ts
|
|
333
|
+
export const { config, html } = createConfig({
|
|
334
|
+
basePath: "/docs",
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// docs-app.ts
|
|
338
|
+
const docsApp = new Hono()
|
|
339
|
+
.route("/_ssr", routes(config))
|
|
340
|
+
.get("/", () => html(() => <DocsHome />));
|
|
341
|
+
|
|
342
|
+
// host-app.ts
|
|
343
|
+
export default new Hono().route("/docs", docsApp);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
With this setup, hydration chunks and dev endpoints are served from `/docs/_ssr/...`.
|
|
347
|
+
|
|
348
|
+
## Build for production
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
// scripts/build.ts
|
|
352
|
+
import { plugin } from "./config";
|
|
353
|
+
|
|
354
|
+
await Bun.build({
|
|
355
|
+
entrypoints: ["src/server.tsx"],
|
|
356
|
+
outdir: "dist",
|
|
357
|
+
target: "bun",
|
|
358
|
+
plugins: [plugin()],
|
|
359
|
+
});
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Hono `createSSRHandler` behavior
|
|
363
|
+
|
|
364
|
+
`createSSRHandler(html)` returns an `ssr()` helper that:
|
|
365
|
+
|
|
366
|
+
- initializes `c.get("page")` as typed page options
|
|
367
|
+
- accepts middlewares/validators before final handler
|
|
368
|
+
- lets handlers return either a synchronous render function or `Response`
|
|
369
|
+
|
|
370
|
+
## Dev mode tools
|
|
371
|
+
|
|
372
|
+
With `dev: true`, a small `[ssr]` overlay is injected.
|
|
373
|
+
|
|
374
|
+
It can:
|
|
375
|
+
|
|
376
|
+
- auto-reload on server restart
|
|
377
|
+
- highlight island/client boundaries
|
|
378
|
+
- show source filenames for wrapped components
|
|
379
|
+
|
|
380
|
+
In browsers with Web Locks support, auto-reload elects one visible tab per
|
|
381
|
+
origin and SSR path to hold the SSE connection. Hidden tabs suspend reload work,
|
|
382
|
+
leadership transfers automatically, and cached pages resume safely after a
|
|
383
|
+
back-forward cache restore. Browsers without Web Locks retain visibility-scoped
|
|
384
|
+
per-tab connections as a compatibility fallback.
|
|
385
|
+
|
|
386
|
+
## Limitations
|
|
387
|
+
|
|
388
|
+
- islands must use default export
|
|
389
|
+
- props must be serializable via `seroval`; do not pass functions, callbacks, event handlers, Solid signals/stores, DOM nodes, or class instances as island/client props
|
|
390
|
+
- nested island/client imports are not supported
|
|
391
|
+
|
|
392
|
+
## Local monorepo example
|
|
393
|
+
|
|
394
|
+
This repo includes a current example app:
|
|
395
|
+
|
|
396
|
+
- `packages/ssr-example`
|
|
397
|
+
|
|
398
|
+
Run from workspace root:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
bun run dev:example
|
|
402
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@k2b/ssr",
|
|
3
|
+
"version": "0.12.0-rc.0",
|
|
4
|
+
"description": "Minimal SSR framework for SolidJS and Bun",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./bun": "./src/adapter/bun.ts",
|
|
11
|
+
"./elysia": "./src/adapter/elysia.ts",
|
|
12
|
+
"./hono": "./src/adapter/hono.ts",
|
|
13
|
+
"./nav": "./src/nav.ts"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "bunx tsc -p test/tsconfig.json && bun test test/unit && bun test --conditions=browser --preload ./test/browser/setup.ts test/browser"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"elysia": "^1.0.0",
|
|
20
|
+
"hono": "^4.0.0",
|
|
21
|
+
"solid-js": "^1.9.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependenciesMeta": {
|
|
24
|
+
"elysia": {
|
|
25
|
+
"optional": true
|
|
26
|
+
},
|
|
27
|
+
"hono": {
|
|
28
|
+
"optional": true
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@babel/core": "^7.29.7",
|
|
33
|
+
"@babel/preset-typescript": "^7.29.7",
|
|
34
|
+
"@types/babel__core": "^7.20.5",
|
|
35
|
+
"babel-preset-solid": "^1.9.12",
|
|
36
|
+
"seroval": "^1.5.5"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@happy-dom/global-registrator": "^20.10.6",
|
|
40
|
+
"@types/bun": "^1.3.14",
|
|
41
|
+
"elysia": "^1.4.29",
|
|
42
|
+
"file-type": "^21.3.1",
|
|
43
|
+
"hono": "^4.12.30",
|
|
44
|
+
"solid-js": "^1.9.14",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
46
|
+
},
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"author": "Valentin Kolb",
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/k2b-dev/ssr.git"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"solid",
|
|
55
|
+
"solidjs",
|
|
56
|
+
"ssr",
|
|
57
|
+
"bun",
|
|
58
|
+
"server-side-rendering",
|
|
59
|
+
"islands",
|
|
60
|
+
"elysia",
|
|
61
|
+
"hono"
|
|
62
|
+
],
|
|
63
|
+
"files": [
|
|
64
|
+
"src/**/*.ts",
|
|
65
|
+
"src/**/*.js",
|
|
66
|
+
"README.md",
|
|
67
|
+
"LICENSE"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun.serve() adapter - provides routes object for native Bun server.
|
|
3
|
+
* Serves island chunks from _ssr and dev tools endpoints.
|
|
4
|
+
*/
|
|
5
|
+
import type { SsrConfig } from "../index";
|
|
6
|
+
import {
|
|
7
|
+
createAssetResponse,
|
|
8
|
+
createPingResponse,
|
|
9
|
+
getSsrDir,
|
|
10
|
+
createReloadResponse,
|
|
11
|
+
} from "./utils";
|
|
12
|
+
|
|
13
|
+
type RouteHandler = (req: Request) => Response | Promise<Response>;
|
|
14
|
+
type Routes = Record<string, RouteHandler>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates routes for Bun.serve from SSR config.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { routes } from "@k2b/ssr/bun";
|
|
22
|
+
* serve({
|
|
23
|
+
* routes: {
|
|
24
|
+
* ...routes(config),
|
|
25
|
+
* "/": () => html(() => <Home />),
|
|
26
|
+
* },
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export const routes = (config: SsrConfig): Routes => {
|
|
31
|
+
const { dev, ssrPath } = config;
|
|
32
|
+
const ssrDir = getSsrDir(config);
|
|
33
|
+
|
|
34
|
+
const devRoutes: Routes = dev
|
|
35
|
+
? {
|
|
36
|
+
[`${ssrPath}/_reload`]: (req) => createReloadResponse(req.signal),
|
|
37
|
+
[`${ssrPath}/_ping`]: () => createPingResponse(),
|
|
38
|
+
}
|
|
39
|
+
: {};
|
|
40
|
+
|
|
41
|
+
const serveAsset: RouteHandler = (req) => {
|
|
42
|
+
const filename = new URL(req.url).pathname.split("/").pop()!;
|
|
43
|
+
return createAssetResponse(req, ssrDir, filename, dev);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
...devRoutes,
|
|
48
|
+
[`${ssrPath}/*.js`]: serveAsset,
|
|
49
|
+
[`${ssrPath}/*.js.map`]: serveAsset,
|
|
50
|
+
};
|
|
51
|
+
};
|