@kasperrt/smolter 0.2.0 → 0.3.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/{src/internal → .generated}/client/index.ts +1 -0
- package/.generated/core/environment.ts +43 -0
- package/{src/internal → .generated}/core/errors.ts +1 -1
- package/{src/internal → .generated}/core/index.ts +1 -0
- package/.generated/core/navigation-router.ts +107 -0
- package/.generated/core/navigation.ts +147 -0
- package/.generated/core/presentation.ts +108 -0
- package/.generated/core/resource-types.ts +44 -0
- package/.generated/core/resources.ts +444 -0
- package/.generated/core/responses.ts +86 -0
- package/{src/internal → .generated}/core/router.ts +136 -63
- package/{src/internal → .generated}/core/serialize.ts +7 -2
- package/.generated/core/store.ts +25 -0
- package/.generated/core/submission.ts +116 -0
- package/{src/internal → .generated}/core/types.ts +18 -1
- package/.generated/core/url.ts +29 -0
- package/.generated/core/utils.ts +5 -0
- package/.generated/preact/await.tsx +41 -0
- package/.generated/preact/client.tsx +93 -0
- package/.generated/preact/context.ts +59 -0
- package/.generated/preact/document.tsx +198 -0
- package/{src/internal → .generated}/preact/fetcher.ts +43 -23
- package/.generated/preact/form.ts +58 -0
- package/{src/internal → .generated}/preact/hooks.ts +25 -28
- package/.generated/preact/index.ts +34 -0
- package/.generated/preact/lazy-route.ts +100 -0
- package/.generated/preact/navigation-state.ts +9 -0
- package/.generated/preact/navigation.tsx +147 -0
- package/.generated/preact/root-context.ts +21 -0
- package/.generated/preact/root-data.ts +45 -0
- package/.generated/preact/route-hooks.tsx +170 -0
- package/.generated/preact/route-types.ts +76 -0
- package/.generated/preact/router.tsx +107 -0
- package/{src/internal → .generated}/preact/routes.ts +17 -9
- package/.generated/react/context.ts +59 -0
- package/.generated/react/data.ts +25 -0
- package/{src/internal → .generated}/react/fetcher.ts +13 -11
- package/.generated/react/form.ts +58 -0
- package/{src/internal → .generated}/react/hooks.ts +9 -14
- package/{src/internal/preact → .generated/react}/index.ts +5 -1
- package/{src/internal → .generated}/react/link.ts +13 -21
- package/.generated/react/resources/components.tsx +404 -0
- package/{src/internal → .generated}/react/routes.ts +17 -9
- package/.generated/server/application.tsx +185 -0
- package/.generated/server/assets.ts +19 -0
- package/.generated/server/handler.ts +69 -0
- package/.generated/server/http.ts +55 -0
- package/{src/internal → .generated}/server/index.ts +3 -3
- package/.generated/server/node.ts +4 -0
- package/.generated/server/preact.ts +7 -0
- package/.generated/vite/application/browser-boundary.ts +42 -0
- package/.generated/vite/application/lazy-files.ts +136 -0
- package/.generated/vite/application/vite.ts +144 -0
- package/{src/preact-vite.ts → .generated/vite/preact.ts} +9 -3
- package/{src/react-vite.ts → .generated/vite/react.ts} +3 -3
- package/CHANGELOG.md +54 -0
- package/README.md +227 -35
- package/dist/client/deserialize.js +10 -0
- package/dist/{internal/client → client}/index.d.ts +1 -0
- package/dist/client/index.js +46 -0
- package/dist/core/environment.d.ts +12 -0
- package/dist/core/environment.js +23 -0
- package/dist/core/errors.js +7 -0
- package/dist/{internal/core → core}/index.d.ts +1 -0
- package/dist/core/match.js +33 -0
- package/dist/core/navigation-router.d.ts +15 -0
- package/dist/core/navigation-router.js +64 -0
- package/dist/core/navigation.d.ts +15 -0
- package/dist/core/navigation.js +92 -0
- package/dist/core/presentation.d.ts +5 -0
- package/dist/core/presentation.js +69 -0
- package/dist/core/resource-types.d.ts +37 -0
- package/dist/core/resources.d.ts +19 -0
- package/dist/core/resources.js +345 -0
- package/dist/core/responses.d.ts +15 -0
- package/dist/core/responses.js +48 -0
- package/dist/{internal/core → core}/router.d.ts +1 -1
- package/dist/core/router.js +204 -0
- package/dist/{internal/core → core}/serialize.d.ts +2 -0
- package/dist/core/serialize.js +17 -0
- package/dist/core/store.d.ts +6 -0
- package/dist/core/store.js +21 -0
- package/dist/core/submission.d.ts +30 -0
- package/dist/core/submission.js +41 -0
- package/dist/{internal/core → core}/types.d.ts +16 -1
- package/dist/core/url.d.ts +7 -0
- package/dist/core/url.js +12 -0
- package/dist/core/utils.d.ts +5 -0
- package/dist/core/utils.js +5 -0
- package/dist/files/index.js +64 -0
- package/dist/preact/await.d.ts +7 -0
- package/dist/preact/await.js +19 -0
- package/dist/preact/client.d.ts +7 -0
- package/dist/preact/client.js +67 -0
- package/dist/preact/context.d.ts +18 -0
- package/dist/preact/context.js +23 -0
- package/dist/preact/document.d.ts +15 -0
- package/dist/preact/document.js +155 -0
- package/dist/{internal/preact → preact}/fetcher.d.ts +9 -5
- package/dist/preact/fetcher.js +45 -0
- package/dist/{internal/preact → preact}/form.d.ts +5 -6
- package/dist/preact/form.js +21 -0
- package/dist/{internal/preact → preact}/hooks.d.ts +7 -6
- package/dist/preact/hooks.js +25 -0
- package/dist/preact/index.d.ts +18 -0
- package/dist/preact/index.js +15 -0
- package/dist/preact/lazy-route.d.ts +19 -0
- package/dist/preact/lazy-route.js +51 -0
- package/dist/preact/navigation-state.d.ts +2 -0
- package/dist/preact/navigation-state.js +9 -0
- package/dist/preact/navigation.d.ts +28 -0
- package/dist/preact/navigation.js +60 -0
- package/dist/preact/root-context.d.ts +13 -0
- package/dist/preact/root-context.js +9 -0
- package/dist/preact/root-data.d.ts +8 -0
- package/dist/preact/root-data.js +28 -0
- package/dist/preact/route-hooks.d.ts +32 -0
- package/dist/preact/route-hooks.js +94 -0
- package/dist/preact/route-types.d.ts +54 -0
- package/dist/preact/router.d.ts +16 -0
- package/dist/preact/router.js +63 -0
- package/dist/{internal/preact → preact}/routes.d.ts +1 -1
- package/dist/preact/routes.js +12 -0
- package/dist/{internal/preact → preact}/runtime.js +1 -1
- package/dist/react/context.d.ts +18 -0
- package/dist/react/context.js +23 -0
- package/dist/react/data.d.ts +5 -0
- package/dist/react/data.js +3 -0
- package/dist/{internal/react → react}/fetcher.d.ts +2 -2
- package/dist/react/fetcher.js +34 -0
- package/dist/{internal/react → react}/form.d.ts +5 -6
- package/dist/react/form.js +21 -0
- package/dist/react/hooks.js +25 -0
- package/dist/{internal/react → react}/index.d.ts +1 -1
- package/dist/react/index.js +10 -0
- package/dist/{internal/react → react}/link.d.ts +2 -2
- package/dist/react/link.js +27 -0
- package/dist/react/resources/components.d.ts +64 -0
- package/dist/react/resources/components.js +239 -0
- package/dist/{internal/react → react}/routes.d.ts +1 -1
- package/dist/react/routes.js +12 -0
- package/dist/react/runtime.js +1 -0
- package/dist/server/application.d.ts +4 -0
- package/dist/server/application.js +119 -0
- package/dist/server/assets.d.ts +5 -0
- package/dist/server/assets.js +7 -0
- package/dist/server/handler.d.ts +19 -0
- package/dist/server/handler.js +22 -0
- package/dist/server/http.d.ts +4 -0
- package/dist/server/http.js +27 -0
- package/dist/server/index.js +76 -0
- package/dist/server/node.d.ts +3 -0
- package/dist/server/node.js +3 -0
- package/dist/server/preact.d.ts +3 -0
- package/dist/server/preact.js +4 -0
- package/dist/vite/application/browser-boundary.d.ts +3 -0
- package/dist/vite/application/browser-boundary.js +35 -0
- package/dist/vite/application/lazy-files.d.ts +2 -0
- package/dist/vite/application/lazy-files.js +87 -0
- package/dist/vite/application/vite.d.ts +9 -0
- package/dist/vite/application/vite.js +101 -0
- package/dist/vite/index.js +104 -0
- package/dist/{preact-vite.d.ts → vite/preact.d.ts} +5 -3
- package/dist/vite/preact.js +15 -0
- package/dist/{react-vite.d.ts → vite/react.d.ts} +3 -3
- package/dist/vite/react.js +14 -0
- package/dist/vite/transport.js +35 -0
- package/jsr.json +28 -16
- package/package.json +116 -36
- package/dist/client.d.ts +0 -2
- package/dist/client.js +0 -2
- package/dist/core.d.ts +0 -2
- package/dist/core.js +0 -2
- package/dist/files.d.ts +0 -2
- package/dist/files.js +0 -2
- package/dist/internal/client/deserialize.js +0 -11
- package/dist/internal/client/index.js +0 -44
- package/dist/internal/core/errors.js +0 -6
- package/dist/internal/core/index.js +0 -7
- package/dist/internal/core/match.js +0 -27
- package/dist/internal/core/router.js +0 -189
- package/dist/internal/core/serialize.js +0 -13
- package/dist/internal/core/types.js +0 -5
- package/dist/internal/core/url.d.ts +0 -2
- package/dist/internal/core/url.js +0 -6
- package/dist/internal/files/index.js +0 -77
- package/dist/internal/preact/context.d.ts +0 -18
- package/dist/internal/preact/context.js +0 -20
- package/dist/internal/preact/fetcher.js +0 -28
- package/dist/internal/preact/form.js +0 -41
- package/dist/internal/preact/hooks.js +0 -35
- package/dist/internal/preact/index.d.ts +0 -9
- package/dist/internal/preact/index.js +0 -7
- package/dist/internal/preact/link.d.ts +0 -12
- package/dist/internal/preact/link.js +0 -28
- package/dist/internal/preact/routes.js +0 -22
- package/dist/internal/react/context.d.ts +0 -18
- package/dist/internal/react/context.js +0 -20
- package/dist/internal/react/fetcher.js +0 -28
- package/dist/internal/react/form.js +0 -41
- package/dist/internal/react/hooks.js +0 -35
- package/dist/internal/react/index.js +0 -7
- package/dist/internal/react/link.js +0 -28
- package/dist/internal/react/routes.js +0 -22
- package/dist/internal/react/runtime.js +0 -2
- package/dist/internal/server/index.js +0 -69
- package/dist/internal/vite/index.js +0 -132
- package/dist/internal/vite/transport.js +0 -42
- package/dist/preact-client.d.ts +0 -2
- package/dist/preact-client.js +0 -2
- package/dist/preact-server.d.ts +0 -2
- package/dist/preact-server.js +0 -2
- package/dist/preact-vite.js +0 -9
- package/dist/preact.d.ts +0 -2
- package/dist/preact.js +0 -2
- package/dist/react-client.d.ts +0 -2
- package/dist/react-client.js +0 -2
- package/dist/react-server.d.ts +0 -2
- package/dist/react-server.js +0 -2
- package/dist/react-vite.js +0 -9
- package/dist/react.d.ts +0 -2
- package/dist/react.js +0 -2
- package/dist/server.d.ts +0 -2
- package/dist/server.js +0 -2
- package/dist/utils.d.ts +0 -5
- package/dist/utils.js +0 -5
- package/dist/vite.d.ts +0 -2
- package/dist/vite.js +0 -2
- package/src/client.ts +0 -2
- package/src/core.ts +0 -2
- package/src/files.ts +0 -2
- package/src/internal/core/url.ts +0 -6
- package/src/internal/preact/context.ts +0 -46
- package/src/internal/preact/form.ts +0 -79
- package/src/internal/preact/link.ts +0 -46
- package/src/internal/react/context.ts +0 -46
- package/src/internal/react/form.ts +0 -79
- package/src/internal/react/index.ts +0 -13
- package/src/preact-client.ts +0 -2
- package/src/preact-server.ts +0 -2
- package/src/preact.ts +0 -2
- package/src/react-client.ts +0 -2
- package/src/react-server.ts +0 -2
- package/src/react.ts +0 -2
- package/src/server.ts +0 -2
- package/src/utils.ts +0 -5
- package/src/vite.ts +0 -2
- /package/{src/internal → .generated}/client/deserialize.ts +0 -0
- /package/{src/internal → .generated}/core/match.ts +0 -0
- /package/{src/internal → .generated}/files/index.ts +0 -0
- /package/{src/internal → .generated}/preact/runtime.ts +0 -0
- /package/{src/internal → .generated}/react/runtime.ts +0 -0
- /package/{src/internal → .generated}/vite/index.ts +0 -0
- /package/{src/internal → .generated}/vite/transport.ts +0 -0
- /package/dist/{internal/client → client}/deserialize.d.ts +0 -0
- /package/dist/{internal/core → core}/errors.d.ts +0 -0
- /package/dist/{internal/core → core}/match.d.ts +0 -0
- /package/dist/{internal/files → files}/index.d.ts +0 -0
- /package/dist/{internal/preact → preact}/runtime.d.ts +0 -0
- /package/dist/{internal/react → react}/hooks.d.ts +0 -0
- /package/dist/{internal/react → react}/runtime.d.ts +0 -0
- /package/dist/{internal/server → server}/index.d.ts +0 -0
- /package/dist/{internal/vite → vite}/index.d.ts +0 -0
- /package/dist/{internal/vite → vite}/transport.d.ts +0 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# smolter _
|
|
2
2
|
|
|
3
3
|
<!-- bundlejs:start -->
|
|
4
|
-
[](https://bundlejs.com/?q=%40kasperrt%2Fsmolter%400.3.0%2Freact&treeshake=%5B*%5D&config=%7B%22compression%22%3A%22gzip%22%2C%22esbuild%22%3A%7B%22external%22%3A%5B%22react%22%2C%22react%2F*%22%5D%7D%7D&bundle)
|
|
5
|
+
[](https://bundlejs.com/?q=%40kasperrt%2Fsmolter%400.3.0%2Fpreact&treeshake=%5B*%5D&config=%7B%22compression%22%3A%22gzip%22%2C%22esbuild%22%3A%7B%22external%22%3A%5B%22preact%22%2C%22preact%2F*%22%5D%7D%7D&bundle)
|
|
6
6
|
<!-- bundlejs:end -->
|
|
7
7
|
|
|
8
8
|
A typed data router for React and Preact. **Zero runtime dependencies.**
|
|
@@ -29,7 +29,10 @@ Start with [loaders](https://kasperrt.github.io/smolter/react/docs/loaders/), [a
|
|
|
29
29
|
- [Types follow the handler](#types-follow-the-handler)
|
|
30
30
|
- [Server rendering](#server-rendering)
|
|
31
31
|
- [Package entries](#package-entries)
|
|
32
|
+
- [Application integration](#application-integration)
|
|
33
|
+
- [Resource subscriptions](#resource-subscriptions)
|
|
32
34
|
- [Size and checks](#size-and-checks)
|
|
35
|
+
- [Routing without data hooks](#routing-without-data-hooks)
|
|
33
36
|
|
|
34
37
|
## Install
|
|
35
38
|
|
|
@@ -40,7 +43,7 @@ pnpm add @kasperrt/smolter
|
|
|
40
43
|
```
|
|
41
44
|
|
|
42
45
|
```ts
|
|
43
|
-
import {
|
|
46
|
+
import { createDataRouter, useFetcher } from "@kasperrt/smolter/react";
|
|
44
47
|
// Preact: import from "@kasperrt/smolter/preact".
|
|
45
48
|
```
|
|
46
49
|
|
|
@@ -61,7 +64,8 @@ pnpm dev
|
|
|
61
64
|
Each app has its own entrypoints, routes, and data code. Only UI components are
|
|
62
65
|
shared. Preact examples do not import React. Each includes:
|
|
63
66
|
|
|
64
|
-
- `/`:
|
|
67
|
+
- `/`: a short introduction and a link to the docs.
|
|
68
|
+
- `/examples/counter`: fetcher POST, automatic and manual revalidation.
|
|
65
69
|
- `/tasks`: list, create form, and a fetcher-only task preview.
|
|
66
70
|
- `/tasks/:id`: PATCH, DELETE, validation, and skipping no-op revalidation.
|
|
67
71
|
- `/about`: the separation between data and rendering.
|
|
@@ -94,10 +98,10 @@ export default defineConfig({
|
|
|
94
98
|
|
|
95
99
|
```ts
|
|
96
100
|
// app.ts
|
|
97
|
-
import {
|
|
101
|
+
import { createDataRouter } from "@kasperrt/smolter/react";
|
|
98
102
|
import routes from "virtual:smolter/routes";
|
|
99
103
|
|
|
100
|
-
export const router =
|
|
104
|
+
export const router = createDataRouter(routes, { browser: true });
|
|
101
105
|
```
|
|
102
106
|
|
|
103
107
|
| File or folder | URL |
|
|
@@ -136,14 +140,14 @@ Outside Vite, `flatRoutes(directory)` from `@kasperrt/smolter/react/vite` return
|
|
|
136
140
|
### Manual routes
|
|
137
141
|
|
|
138
142
|
```tsx
|
|
139
|
-
import {
|
|
143
|
+
import { createDataRouter, defineRoute } from "@kasperrt/smolter/react";
|
|
140
144
|
import * as tasks from "./tasks/route";
|
|
141
145
|
import * as task from "./task/route";
|
|
142
146
|
|
|
143
147
|
export const tasksRoute = defineRoute("/tasks", tasks);
|
|
144
148
|
export const taskRoute = defineRoute("/tasks/:id", task);
|
|
145
149
|
export const routes = [tasksRoute, taskRoute];
|
|
146
|
-
export const router =
|
|
150
|
+
export const router = createDataRouter(routes, { browser: true });
|
|
147
151
|
```
|
|
148
152
|
|
|
149
153
|
Or pass handlers directly to `defineRoute(path, { loader, action, default: View })`.
|
|
@@ -171,11 +175,12 @@ export { default } from "./view";
|
|
|
171
175
|
export { loader } from "./loader";
|
|
172
176
|
export { action } from "./action";
|
|
173
177
|
|
|
174
|
-
export { clientLoader } from "./
|
|
175
|
-
export { clientAction } from "./
|
|
178
|
+
export { clientLoader } from "./clientLoader";
|
|
179
|
+
export { clientAction } from "./clientAction";
|
|
176
180
|
|
|
177
|
-
export { shouldRevalidate } from "./
|
|
178
|
-
export { HydrateFallback
|
|
181
|
+
export { shouldRevalidate } from "./shouldRevalidate";
|
|
182
|
+
export { HydrateFallback } from "./HydrateFallback";
|
|
183
|
+
export { ErrorBoundary } from "./ErrorBoundary";
|
|
179
184
|
```
|
|
180
185
|
|
|
181
186
|
With the optional server integration:
|
|
@@ -187,7 +192,7 @@ With the optional server integration:
|
|
|
187
192
|
- SSR executes the server loader and hydration reuses that data.
|
|
188
193
|
|
|
189
194
|
```ts
|
|
190
|
-
//
|
|
195
|
+
// clientLoader.ts
|
|
191
196
|
import type { ClientLoaderArgs } from "@kasperrt/smolter/react/client";
|
|
192
197
|
import type { loader } from "./loader";
|
|
193
198
|
|
|
@@ -197,7 +202,7 @@ export async function clientLoader({ serverLoader }: ClientLoaderArgs<typeof loa
|
|
|
197
202
|
```
|
|
198
203
|
|
|
199
204
|
```ts
|
|
200
|
-
//
|
|
205
|
+
// clientAction.ts
|
|
201
206
|
import type { ClientActionArgs } from "@kasperrt/smolter/react/client";
|
|
202
207
|
import type { action } from "./action";
|
|
203
208
|
|
|
@@ -258,7 +263,7 @@ routerPlugin({
|
|
|
258
263
|
|
|
259
264
|
Each table exports `routes`. The standalone examples contain both tables.
|
|
260
265
|
|
|
261
|
-
Without the optional integration, `
|
|
266
|
+
Without the optional integration, `createDataRouter` invokes handlers locally:
|
|
262
267
|
client handlers take precedence, with ordinary handlers as fallbacks.
|
|
263
268
|
`loadRouter` selects server handlers only. Core-only usage does not create HTTP
|
|
264
269
|
endpoints or compiler boundaries by itself.
|
|
@@ -319,7 +324,7 @@ Fetcher results belong to `fetcher.data`, not `useActionData()`.
|
|
|
319
324
|
## Decide when to revalidate
|
|
320
325
|
|
|
321
326
|
```ts
|
|
322
|
-
//
|
|
327
|
+
// shouldRevalidate.ts
|
|
323
328
|
import type { RevalidationArgs } from "@kasperrt/smolter/react";
|
|
324
329
|
|
|
325
330
|
export function shouldRevalidate({ actionResult, defaultShouldRevalidate }: RevalidationArgs) {
|
|
@@ -344,7 +349,25 @@ and explicit `fetcher.load()` calls always load their destination.
|
|
|
344
349
|
This controls loader refreshes; it does not rerun actions or reload the document.
|
|
345
350
|
Existing page data stays visible during manual and post-action refreshes.
|
|
346
351
|
|
|
347
|
-
`
|
|
352
|
+
`useRevalidator()` returns an async function. Target a registered route ID or path
|
|
353
|
+
pattern to refresh only its active data, including loaded fetchers:
|
|
354
|
+
|
|
355
|
+
```tsx
|
|
356
|
+
const revalidate = useRevalidator();
|
|
357
|
+
|
|
358
|
+
await revalidate({ route: "/tasks/:id" });
|
|
359
|
+
await revalidate({ route: "root" });
|
|
360
|
+
await revalidate({ route: ["root", "/tasks/:id"] });
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Without a target, `revalidate()` refreshes the active page, loaded fetchers, and
|
|
364
|
+
registered root/layout data. `shouldRevalidate` still applies. Inactive routes
|
|
365
|
+
are not loaded, unknown targets do nothing, and revalidation never navigates.
|
|
366
|
+
|
|
367
|
+
The Preact root integration uses `root` as its default ID. Configure its
|
|
368
|
+
`loaderPath` with a registered data route to enable root refreshes. Custom React
|
|
369
|
+
or Preact layouts can use `router.registerRevalidator(id, callback)`; call its
|
|
370
|
+
returned cleanup function when the layout is removed.
|
|
348
371
|
The task-detail examples return `{ changed: false }` for an unchanged title;
|
|
349
372
|
the corresponding loader is skipped.
|
|
350
373
|
|
|
@@ -381,17 +404,17 @@ const hydrationJSON = serialize(router.getSnapshot());
|
|
|
381
404
|
|
|
382
405
|
Create a fresh router per request. Embed the serialized snapshot in a JSON
|
|
383
406
|
script, restore it with `deserialize` from `@kasperrt/smolter/react/client`, and pass it as
|
|
384
|
-
`hydrationData` to `
|
|
407
|
+
`hydrationData` to `createDataRouter`. This preserves loader errors through hydration
|
|
385
408
|
without exposing server stacks or causes. Error boundaries should display the
|
|
386
409
|
error message, not its stack.
|
|
387
410
|
|
|
388
411
|
```ts
|
|
389
412
|
import { deserialize } from "@kasperrt/smolter/react/client";
|
|
390
|
-
import {
|
|
413
|
+
import { createDataRouter } from "@kasperrt/smolter/react";
|
|
391
414
|
import routes from "virtual:smolter/routes";
|
|
392
415
|
|
|
393
416
|
const data = document.getElementById("smolter-data");
|
|
394
|
-
const router =
|
|
417
|
+
const router = createDataRouter(routes, {
|
|
395
418
|
browser: true,
|
|
396
419
|
hydrationData: data?.textContent ? deserialize(data.textContent) : undefined,
|
|
397
420
|
});
|
|
@@ -406,7 +429,7 @@ The SSR examples use the same server route definitions for HTML and data request
|
|
|
406
429
|
|
|
407
430
|
## Package entries
|
|
408
431
|
|
|
409
|
-
Use `@kasperrt/smolter/react` for React 18+ and `@kasperrt/smolter/preact` for Preact
|
|
432
|
+
Use `@kasperrt/smolter/react` for React 18+ and `@kasperrt/smolter/preact` for Preact 11.0.1+.
|
|
410
433
|
Choose one renderer for your application. Both expose the same routing API:
|
|
411
434
|
|
|
412
435
|
| Purpose | React | Preact |
|
|
@@ -421,39 +444,171 @@ The main renderer entries never import server or build modules. Named imports
|
|
|
421
444
|
are tree-shaken; optional transport is included only when you import `/client`.
|
|
422
445
|
Server entries do not import either UI renderer. Vite entries are build-time only.
|
|
423
446
|
|
|
424
|
-
**Migrating
|
|
447
|
+
**Migrating renderer subpaths:** replace `/core` imports for types, router factories, and
|
|
425
448
|
`defineRoute` with your renderer entry. Use `/utils` for `matchRoutes`,
|
|
426
449
|
`internalURL` and `safeWrapAsync`. Move `/client`, `/server`,
|
|
427
450
|
`/files`, and `/vite` imports to the corresponding renderer-prefixed path above.
|
|
428
451
|
The bare package root and old top-level subpaths are no longer exported.
|
|
429
452
|
|
|
453
|
+
See the [0.3 migration guide](https://kasperrt.github.io/smolter/react/docs/migration-0.3/) and [changelog](./CHANGELOG.md).
|
|
454
|
+
|
|
455
|
+
**Router factory migration:** code that previously used `createRouter` with
|
|
456
|
+
loaders, actions, fetchers or revalidation must now use `createDataRouter`.
|
|
457
|
+
`createRouter` now creates a navigation-only router. SSR and file-router
|
|
458
|
+
application helpers select `createDataRouter` automatically.
|
|
459
|
+
|
|
430
460
|
On JSR, install `@kasperrt/smolter` using the
|
|
431
461
|
[JSR package instructions](https://jsr.io/docs/npm-compatibility) and import
|
|
432
|
-
`@kasperrt/smolter/react` or `@kasperrt/smolter/preact`. Its
|
|
433
|
-
`server`, and `files` subpaths match npm. The Vite plugin is npm-only:
|
|
462
|
+
`@kasperrt/smolter/react` or `@kasperrt/smolter/preact`. Its renderer, client, server and utility subpaths match npm. The Vite plugin is npm-only:
|
|
434
463
|
use `@kasperrt/smolter/react/vite` from the npm package when adding file routing.
|
|
435
464
|
|
|
465
|
+
## Application integration
|
|
466
|
+
|
|
467
|
+
Import routing, forms and data hooks from `@kasperrt/smolter/preact`.
|
|
468
|
+
Document rendering is optional: `@kasperrt/smolter/preact/document` supplies
|
|
469
|
+
`Document`, `Meta`, `Links`, `Scripts` and `ScrollRestoration`.
|
|
470
|
+
`@kasperrt/smolter/preact/client` supplies `mountApplication`,
|
|
471
|
+
`configureApplication` and the server transport helpers. Mounting connects
|
|
472
|
+
hydration, root data and navigation presentation to the same core router.
|
|
473
|
+
These modules remain tree-shakable; basic routing does not import the document
|
|
474
|
+
or mounting code. The size table also measures all three together, because
|
|
475
|
+
moving APIs to subpaths does not make their implementation disappear.
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
// vite.config.ts
|
|
479
|
+
import { defineConfig } from "vite";
|
|
480
|
+
import { applicationPlugins } from "@kasperrt/smolter/preact/vite";
|
|
481
|
+
|
|
482
|
+
export default defineConfig({
|
|
483
|
+
plugins: applicationPlugins({ directory: "src/routes" }),
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
```tsx
|
|
488
|
+
// src/entry.client.tsx
|
|
489
|
+
import routes from "virtual:smolter/routes";
|
|
490
|
+
import { mountApplication } from "@kasperrt/smolter/preact/client";
|
|
491
|
+
import * as root from "./root";
|
|
492
|
+
|
|
493
|
+
mountApplication(routes, root);
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
`root` exports a default component containing `Outlet` and a `Layout` wrapping
|
|
497
|
+
it in `Document`, a head, and a body containing `Scripts`. Its optional loader
|
|
498
|
+
supplies root data. `renderApplication(request, routes, root, entry)` from
|
|
499
|
+
`preact/server` renders the same tree and serializes hydration data;
|
|
500
|
+
`dispatchRoute(request, routes)` handles route data requests. Server rendering
|
|
501
|
+
requires the optional `preact-render-to-string` peer.
|
|
502
|
+
|
|
503
|
+
Deployment-specific URL mapping can be configured once at startup with
|
|
504
|
+
`configureApplication({ resolveURL, isDocumentHandoff, leaveDocument,
|
|
505
|
+
entryScript })`. Do not put user-specific or request-specific state there.
|
|
506
|
+
The library does not define application domains, ports, or branding.
|
|
507
|
+
|
|
508
|
+
`utils/http` contains the Node request/response adapter; `utils/assets` contains
|
|
509
|
+
manifest/preload helpers; `utils/responses` contains response normalization.
|
|
510
|
+
Keep the first two in server/build entrypoints. Browser application imports do
|
|
511
|
+
not include them.
|
|
512
|
+
|
|
513
|
+
### Virtual modules and entrypoints
|
|
514
|
+
|
|
515
|
+
`virtual:smolter/routes` is the default route-array export generated by the Vite
|
|
516
|
+
plugin from `src/routes`. It is not a physical file or a runtime package.
|
|
517
|
+
The server build retains loaders/actions; the browser build strips their
|
|
518
|
+
re-exports and installs server proxies. `entry.client.tsx` and `entry.server.tsx`
|
|
519
|
+
are application-owned files wiring those manifests to hydration and SSR.
|
|
520
|
+
Manual tables can be imported directly instead.
|
|
521
|
+
|
|
522
|
+
See the [virtual routes guide](https://kasperrt.github.io/smolter/react/docs/virtual-routes)
|
|
523
|
+
for the exact contract, TypeScript declaration, and both entrypoint examples.
|
|
524
|
+
|
|
525
|
+
### Express hosting
|
|
526
|
+
|
|
527
|
+
Keep Express in control of metrics, tracing, authentication and static files.
|
|
528
|
+
Mount `createRequestHandler({ build, entry })` from
|
|
529
|
+
`@kasperrt/smolter/utils/http` after those middleware. `build` is the compiled
|
|
530
|
+
server entry and `entry` is the browser entry URL from Vite's manifest. Do not
|
|
531
|
+
consume request bodies with Express body parsers before this handler.
|
|
532
|
+
The adapter has no Express dependency and forwards failures to `next(error)`.
|
|
533
|
+
|
|
534
|
+
See the [Express guide](https://kasperrt.github.io/smolter/react/docs/express)
|
|
535
|
+
for a complete server example and current buffering limitations.
|
|
536
|
+
|
|
537
|
+
## Resource subscriptions
|
|
538
|
+
|
|
539
|
+
`@kasperrt/smolter/react/data` adds subscriptions to named resources when their
|
|
540
|
+
lifetime is independent of URL navigation. The state and cancellation logic
|
|
541
|
+
lives in core; the entry adds React bindings without DOM or HTTP globals.
|
|
542
|
+
|
|
543
|
+
```ts
|
|
544
|
+
import { createResourceRouter } from "@kasperrt/smolter/react/data";
|
|
545
|
+
|
|
546
|
+
const resources = createResourceRouter(new Map([
|
|
547
|
+
["settings", {
|
|
548
|
+
loader: async ({ signal }) => ({
|
|
549
|
+
data: await readSettings(signal),
|
|
550
|
+
error: "",
|
|
551
|
+
}),
|
|
552
|
+
action: async ({ input, signal }) => ({
|
|
553
|
+
data: await saveSettings(input, signal),
|
|
554
|
+
error: "",
|
|
555
|
+
}),
|
|
556
|
+
}],
|
|
557
|
+
]));
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
`ResourceProvider` owns the store lifetime. `Resource` selects a resource by
|
|
561
|
+
`routeId` and params. `useResourceData` reads it; `useRouteLoaderData` reads a
|
|
562
|
+
named resource. `useResourceFetcher` returns `[state, operations]`, with
|
|
563
|
+
independent `load` and `submit` operations. Successful mutations consult the
|
|
564
|
+
resource's `shouldRevalidate` before reloading its data. Last-subscriber cleanup
|
|
565
|
+
aborts pending work; `persistent` keeps a resource until explicitly disposed.
|
|
566
|
+
This optional entry does not contribute to the base React bundle measurement.
|
|
567
|
+
|
|
436
568
|
## Size and checks
|
|
437
569
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
570
|
+
Use `createRouter` for navigation only, or `createDataRouter` for loaders,
|
|
571
|
+
actions, fetchers and revalidation. Both are named exports from
|
|
572
|
+
`@kasperrt/smolter/preact` and `/react`. No extra package or plugin is required.
|
|
573
|
+
The production bundler can remove the data engine when only `createRouter` is used.
|
|
574
|
+
|
|
575
|
+
`pnpm size` regenerates this table and the site's size data using identical
|
|
576
|
+
minifiers. The routing-only gate compares our selected exports with wouter's
|
|
577
|
+
routing subset: `Router`, `Route`, `Switch`, `Link`, `useParams`, and `useLocation`.
|
|
578
|
+
Both complete wouter entries are reported too. The APIs differ; this is not
|
|
579
|
+
a claim of identical features.
|
|
443
580
|
|
|
444
581
|
<!-- size-comparison:start -->
|
|
445
582
|
| Browser exports | Gzip bytes |
|
|
446
583
|
| --- | ---: |
|
|
447
|
-
|
|
|
448
|
-
|
|
|
449
|
-
| `@kasperrt/smolter/react
|
|
450
|
-
| `@kasperrt/smolter/preact
|
|
584
|
+
| `React: routing-only named exports` | 1245 |
|
|
585
|
+
| `Preact: routing-only named exports` | 1405 |
|
|
586
|
+
| `@kasperrt/smolter/react` | 2873 |
|
|
587
|
+
| `@kasperrt/smolter/preact` | 4748 |
|
|
588
|
+
| `@kasperrt/smolter/react + @kasperrt/smolter/react/client` | 3219 |
|
|
589
|
+
| `@kasperrt/smolter/preact + server transport` | 5059 |
|
|
590
|
+
| `@kasperrt/smolter/preact + client + document (application without lazy routes)` | 7415 |
|
|
591
|
+
| `@kasperrt/smolter/preact + client + document + lazy-route (file-routed application)` | 7696 |
|
|
592
|
+
| `@kasperrt/smolter/react/data (optional resource API)` | 2962 |
|
|
593
|
+
| `React: common routing/data exports` | 2683 |
|
|
594
|
+
| `Preact: common routing/data exports` | 2966 |
|
|
595
|
+
| `wouter: routing subset (Router, Route, Switch, Link, useParams, useLocation)` | 2854 |
|
|
596
|
+
| `wouter-preact: same routing subset` | 2211 |
|
|
451
597
|
| `wouter@3.13.0` | 3091 |
|
|
452
598
|
| `wouter-preact@3.13.0` | 2450 |
|
|
453
599
|
| `react-router@8.4.0` | 60994 |
|
|
454
600
|
<!-- size-comparison:end -->
|
|
455
601
|
|
|
456
|
-
The
|
|
602
|
+
The common-export rows measure the same named API set: `createDataRouter`,
|
|
603
|
+
`defineRoute`, `RouterProvider`, `Routes`, `Link`, `Form`, `useRouter`,
|
|
604
|
+
`useLoaderData`, `useActionData`, `useParams`, `useLocation`,
|
|
605
|
+
`useRevalidator` and `useFetcher`. Preact’s link and submission APIs currently
|
|
606
|
+
have additional behavior; equal names do not imply identical implementations.
|
|
607
|
+
The routing-only rows retain `createRouter`, `defineRoute`, `RouterProvider`,
|
|
608
|
+
`Routes`, `Link`, `useParams` and `useLocation`. Full Preact also exposes richer
|
|
609
|
+
application APIs. These tiers overlap: do not add the rows together.
|
|
610
|
+
|
|
611
|
+
The table is generated by `pnpm size` from the named subsets above or all exports of each other listed entry,
|
|
457
612
|
using production ESM bundles, esbuild, Terser, and gzip level 9. React, React DOM,
|
|
458
613
|
and Preact are external in every bundle. Transitive library dependencies are
|
|
459
614
|
included. React Router provides a broader API; this compares complete entry
|
|
@@ -461,6 +616,43 @@ sizes, not equivalent feature sets or the minimum code for one route.
|
|
|
461
616
|
`@kasperrt/smolter/react/client` adds browser transport for server loaders/actions; server-only
|
|
462
617
|
rendering code and the Vite plugin are not shipped to the browser.
|
|
463
618
|
|
|
619
|
+
### Routing without data hooks
|
|
620
|
+
|
|
621
|
+
```tsx
|
|
622
|
+
import { render } from "preact";
|
|
623
|
+
import {
|
|
624
|
+
createRouter,
|
|
625
|
+
defineRoute,
|
|
626
|
+
Link,
|
|
627
|
+
RouterProvider,
|
|
628
|
+
Routes,
|
|
629
|
+
} from "@kasperrt/smolter/preact";
|
|
630
|
+
import Home from "./routes/home";
|
|
631
|
+
import About from "./routes/about";
|
|
632
|
+
|
|
633
|
+
const router = createRouter([
|
|
634
|
+
defineRoute("/", { default: Home }),
|
|
635
|
+
defineRoute("/about", { default: About }),
|
|
636
|
+
], { browser: true });
|
|
637
|
+
|
|
638
|
+
render(
|
|
639
|
+
<RouterProvider router={router}>
|
|
640
|
+
<nav>
|
|
641
|
+
<Link href="/">Home</Link>
|
|
642
|
+
<Link href="/about">About</Link>
|
|
643
|
+
</nav>
|
|
644
|
+
<Routes />
|
|
645
|
+
</RouterProvider>,
|
|
646
|
+
document.getElementById("app")!,
|
|
647
|
+
);
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
Add `useParams` for dynamic paths such as `/tasks/:id` and `useLocation` for
|
|
651
|
+
the current URL. Both are included in the routing-only measurement, even though
|
|
652
|
+
this example does not need them. Switch the factory to `createDataRouter` when
|
|
653
|
+
adding loaders, actions or fetchers, importing it from the same entry. SSR hydration and file-routed applications use the
|
|
654
|
+
larger client/document/lazy-route tier; they are not represented by the routing-only number.
|
|
655
|
+
|
|
464
656
|
Exact current measurements are generated in
|
|
465
657
|
[`packages/ui/src/size.json`](./packages/ui/src/size.json).
|
|
466
658
|
|
|
@@ -486,7 +678,7 @@ PRs. [Release workflow](https://github.com/kasperrt/smolter/blob/main/RELEASING.
|
|
|
486
678
|
To test a local package build in another project:
|
|
487
679
|
|
|
488
680
|
```sh
|
|
489
|
-
pnpm
|
|
681
|
+
pnpm pack
|
|
490
682
|
pnpm add /absolute/path/to/kasperrt-smolter-0.0.1.tgz
|
|
491
683
|
```
|
|
492
684
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region .generated/client/deserialize.ts
|
|
2
|
+
/** Restore a trusted snapshot produced by @kasperrt/smolter/server serialize, including its route error. */
|
|
3
|
+
function deserialize(json) {
|
|
4
|
+
const snapshot = JSON.parse(json);
|
|
5
|
+
const error = snapshot.error;
|
|
6
|
+
if (error) snapshot.error = Object.assign(new Error(error.message), { name: error.name });
|
|
7
|
+
return snapshot;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { deserialize };
|
|
@@ -7,6 +7,7 @@ export type { ClientActionArgs, ClientLoaderArgs } from "../core/index.ts";
|
|
|
7
7
|
export { deserialize } from "./deserialize.ts";
|
|
8
8
|
/** Browser-safe route exports, excluding server implementation code. */
|
|
9
9
|
export interface ClientRouteModule {
|
|
10
|
+
id?: string;
|
|
10
11
|
default?: unknown;
|
|
11
12
|
HydrateFallback?: unknown;
|
|
12
13
|
ErrorBoundary?: unknown;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { deserialize } from "./deserialize.js";
|
|
2
|
+
//#region .generated/client/index.ts
|
|
3
|
+
/** Browser transport for server routes. File-router builds generate this call. */
|
|
4
|
+
function serverRoute(path, module, handlers = {
|
|
5
|
+
loader: true,
|
|
6
|
+
action: true
|
|
7
|
+
}) {
|
|
8
|
+
const remote = async ({ request, signal }) => {
|
|
9
|
+
const url = new URL(request.url);
|
|
10
|
+
const response = await fetch(`/_smolter?url=${encodeURIComponent(url.pathname + url.search)}`, {
|
|
11
|
+
method: request.method,
|
|
12
|
+
signal,
|
|
13
|
+
...request.method !== "GET" && { body: await request.formData() }
|
|
14
|
+
});
|
|
15
|
+
const location = response.headers.get("X-Smolter-Redirect");
|
|
16
|
+
if (location) return new Response(null, {
|
|
17
|
+
status: 303,
|
|
18
|
+
headers: { Location: location }
|
|
19
|
+
});
|
|
20
|
+
const data = await response.json();
|
|
21
|
+
if (!response.ok) return new Error(data.error ?? "Route request failed");
|
|
22
|
+
return data;
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
...module,
|
|
26
|
+
path,
|
|
27
|
+
...handlers.loader && { loader: remote },
|
|
28
|
+
...handlers.action && { action: remote },
|
|
29
|
+
...module.clientLoader && { clientLoader: (args) => module.clientLoader({
|
|
30
|
+
...args,
|
|
31
|
+
serverLoader: () => remote(args)
|
|
32
|
+
}) },
|
|
33
|
+
...module.clientAction && { clientAction: (args) => {
|
|
34
|
+
const request = args.request.clone();
|
|
35
|
+
return module.clientAction({
|
|
36
|
+
...args,
|
|
37
|
+
serverAction: () => remote({
|
|
38
|
+
...args,
|
|
39
|
+
request: request.clone()
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
} }
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { deserialize, serverRoute };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ApplicationEnvironment {
|
|
2
|
+
resolveURL?: (url: URL) => URL;
|
|
3
|
+
isDocumentHandoff?: (from: URL, to: URL) => boolean;
|
|
4
|
+
leaveDocument?: (destination: URL) => void;
|
|
5
|
+
entryScript?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare let applicationEnvironment: ApplicationEnvironment | undefined;
|
|
8
|
+
/** Configure deployment-specific navigation before rendering or mounting. */
|
|
9
|
+
export declare function configureApplication(environment: ApplicationEnvironment): void;
|
|
10
|
+
export declare function resolveApplicationURL(url: URL): URL;
|
|
11
|
+
export declare function handoffDocument(event: MouseEvent, destination: URL): void;
|
|
12
|
+
export declare function leaveDocument(destination: URL): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region .generated/core/environment.ts
|
|
2
|
+
var applicationEnvironment;
|
|
3
|
+
/** Configure deployment-specific navigation before rendering or mounting. */
|
|
4
|
+
function configureApplication(environment) {
|
|
5
|
+
applicationEnvironment = environment;
|
|
6
|
+
}
|
|
7
|
+
function resolveApplicationURL(url) {
|
|
8
|
+
return applicationEnvironment?.resolveURL?.(url) ?? url;
|
|
9
|
+
}
|
|
10
|
+
function handoffDocument(event, destination) {
|
|
11
|
+
if (!applicationEnvironment?.isDocumentHandoff?.(new URL(location.href), destination)) return;
|
|
12
|
+
event.preventDefault();
|
|
13
|
+
leaveDocument(destination);
|
|
14
|
+
}
|
|
15
|
+
function leaveDocument(destination) {
|
|
16
|
+
if (applicationEnvironment?.leaveDocument) {
|
|
17
|
+
applicationEnvironment.leaveDocument(destination);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
location.assign(destination.href);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { applicationEnvironment, configureApplication, handoffDocument, leaveDocument, resolveApplicationURL };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region .generated/core/errors.ts
|
|
2
|
+
/** Resolve returned or rejected errors into Error values without losing their cause. */
|
|
3
|
+
function safeWrapAsync(work) {
|
|
4
|
+
return Promise.resolve().then(work).then((data) => data instanceof Error ? { error: data } : { data }, (cause) => ({ error: Error(String(cause), { cause }) }));
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { safeWrapAsync };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region .generated/core/match.ts
|
|
2
|
+
/** Associate a URL pattern with a module while retaining its inferred handler types. */
|
|
3
|
+
function defineRoute(path, module) {
|
|
4
|
+
return {
|
|
5
|
+
...module,
|
|
6
|
+
path
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
/** Static segments, :params and a terminal * wildcard; first match wins. */
|
|
10
|
+
function matchRoutes(routes, pathname) {
|
|
11
|
+
const segments = pathname.replace(/\/$/, "").split("/");
|
|
12
|
+
for (const route of routes) {
|
|
13
|
+
const parts = route.path.replace(/\/$/, "").split("/");
|
|
14
|
+
const params = {};
|
|
15
|
+
if ((parts.length === segments.length || parts.at(-1) === "*") && parts.every((part, index) => {
|
|
16
|
+
const value = segments[index];
|
|
17
|
+
if (part === "*") {
|
|
18
|
+
params["*"] = segments.slice(index).join("/");
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (part[0] === ":" && value) {
|
|
22
|
+
params[part.slice(1)] = new URLSearchParams(`=${value.replace(/[+&]/g, encodeURIComponent)}`).get("");
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return part === value;
|
|
26
|
+
})) return {
|
|
27
|
+
route,
|
|
28
|
+
params
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { defineRoute, matchRoutes };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Route, Router, RouterOptions } from "./types.ts";
|
|
2
|
+
/** Navigation and subscriptions without the loader/action engine. */
|
|
3
|
+
export type NavigationRouter = Pick<Router, "routes" | "getSnapshot" | "subscribe" | "start" | "navigate" | "dispose">;
|
|
4
|
+
/** Route modules for navigation only; data handlers require createDataRouter. */
|
|
5
|
+
export type NavigationRoute = Route & {
|
|
6
|
+
loader?: never;
|
|
7
|
+
action?: never;
|
|
8
|
+
clientLoader?: never;
|
|
9
|
+
clientAction?: never;
|
|
10
|
+
shouldRevalidate?: never;
|
|
11
|
+
};
|
|
12
|
+
/** Initial URL and optional browser history for navigation-only routing. */
|
|
13
|
+
export type NavigationRouterOptions = Pick<RouterOptions, "url" | "browser">;
|
|
14
|
+
/** Create a route matcher and history store without data requests or fetchers. */
|
|
15
|
+
export declare function createRouter(routes: readonly NavigationRoute[], options?: NavigationRouterOptions): NavigationRouter;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { matchRoutes } from "./match.js";
|
|
2
|
+
import { internalURL } from "./url.js";
|
|
3
|
+
//#region .generated/core/navigation-router.ts
|
|
4
|
+
/** Create a route matcher and history store without data requests or fetchers. */
|
|
5
|
+
function createRouter(routes, options = {}) {
|
|
6
|
+
const browser = options.browser && typeof window !== "undefined";
|
|
7
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
8
|
+
let users = 0;
|
|
9
|
+
const resolve = (url) => {
|
|
10
|
+
const match = matchRoutes(routes, url.pathname);
|
|
11
|
+
return {
|
|
12
|
+
url: url.href,
|
|
13
|
+
path: match?.route.path,
|
|
14
|
+
params: match?.params ?? {},
|
|
15
|
+
state: "idle",
|
|
16
|
+
loaded: true
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
let snapshot = resolve(new URL(options.url ?? (browser ? location.href : "/"), "http://localhost"));
|
|
20
|
+
const update = (url) => {
|
|
21
|
+
if (url.href === snapshot.url) return;
|
|
22
|
+
snapshot = resolve(url);
|
|
23
|
+
for (const listener of listeners) listener();
|
|
24
|
+
};
|
|
25
|
+
const pop = () => update(new URL(location.href));
|
|
26
|
+
return {
|
|
27
|
+
routes,
|
|
28
|
+
getSnapshot: () => snapshot,
|
|
29
|
+
subscribe(listener) {
|
|
30
|
+
listeners.add(listener);
|
|
31
|
+
return () => {
|
|
32
|
+
listeners.delete(listener);
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
start() {
|
|
36
|
+
if (!users++ && browser) {
|
|
37
|
+
addEventListener("popstate", pop);
|
|
38
|
+
addEventListener("hashchange", pop);
|
|
39
|
+
pop();
|
|
40
|
+
}
|
|
41
|
+
return () => {
|
|
42
|
+
if (!--users && browser) {
|
|
43
|
+
removeEventListener("popstate", pop);
|
|
44
|
+
removeEventListener("hashchange", pop);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
async navigate(href, operation) {
|
|
49
|
+
const url = internalURL(snapshot.url, href);
|
|
50
|
+
if (!url) return;
|
|
51
|
+
if (browser) history[operation?.replace ? "replaceState" : "pushState"](operation?.state ?? null, "", url);
|
|
52
|
+
update(url);
|
|
53
|
+
},
|
|
54
|
+
dispose() {
|
|
55
|
+
listeners.clear();
|
|
56
|
+
if (browser) {
|
|
57
|
+
removeEventListener("popstate", pop);
|
|
58
|
+
removeEventListener("hashchange", pop);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { createRouter };
|