@ilha/router 0.9.1 → 0.9.2
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 +26 -6
- package/dist/index.d.ts +52 -3
- package/dist/index.js +2 -2247
- package/dist/{plugin-DogkskcY.js → plugin-BHuojFhQ.js} +36 -18
- package/dist/plugin.d.ts +17 -0
- package/dist/public-types.d.ts +7 -0
- package/dist/rolldown.js +1 -1
- package/dist/route-match.d.ts +22 -0
- package/dist/rspack.js +1 -1
- package/dist/server-island-registry.d.ts +46 -1
- package/dist/server-island-registry.js +76 -21
- package/dist/src-BBsbD5vU.js +2365 -0
- package/dist/ssr.d.ts +9 -0
- package/dist/ssr.js +57 -16
- package/dist/vite.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -227,7 +227,7 @@ Renders `<div data-router-empty></div>` when no route matches.
|
|
|
227
227
|
|
|
228
228
|
---
|
|
229
229
|
|
|
230
|
-
#### `.renderHydratable(
|
|
230
|
+
#### `.renderHydratable(urlOrRequest, registry, options?, request?)` — server / SSR
|
|
231
231
|
|
|
232
232
|
Async variant of `.render()` that outputs HTML with `data-ilha` hydration markers so the client can rehydrate without a full re-render. If a loader is registered for the matched route, it runs first and its return value is serialized into `data-ilha-props`.
|
|
233
233
|
|
|
@@ -236,6 +236,10 @@ const html = await router().route("/", HomePage).renderHydratable("/", registry)
|
|
|
236
236
|
// → '<div data-router-view><div data-ilha="Home">…</div></div>'
|
|
237
237
|
```
|
|
238
238
|
|
|
239
|
+
All server render APIs accept a `Request` as the first argument — route, origin, headers, and loader context derive from it, so server handlers can pass the real request directly.
|
|
240
|
+
|
|
241
|
+
> **Redirects.** For callers using the string API, a loader redirect is encoded as a `<meta http-equiv="refresh">` tag. This is deprecated: it can't set a real HTTP status. Prefer `.renderResponse()` or `.respond()` to emit a proper 302.
|
|
242
|
+
|
|
239
243
|
If the active island is not found in the registry, falls back to plain SSR and emits a `console.warn`.
|
|
240
244
|
|
|
241
245
|
**Options** extend `HydratableOptions` from `ilha`:
|
|
@@ -246,9 +250,9 @@ If the active island is not found in the registry, falls back to plain SSR and e
|
|
|
246
250
|
|
|
247
251
|
---
|
|
248
252
|
|
|
249
|
-
#### `.renderResponse(
|
|
253
|
+
#### `.renderResponse(urlOrRequest, registry, options?, request?)` — server / SSR
|
|
250
254
|
|
|
251
|
-
Structured-envelope variant of `.renderHydratable()`. Returns a `RenderResponse` discriminated union instead of a raw HTML string, so the host server can emit proper HTTP status codes for redirects and loader errors.
|
|
255
|
+
Structured-envelope variant of `.renderHydratable()`. Returns a `RenderResponse` discriminated union instead of a raw HTML string, so the host server can emit proper HTTP status codes for redirects and loader errors. Accepts a `Request` as the first argument.
|
|
252
256
|
|
|
253
257
|
```ts
|
|
254
258
|
const res = await router()
|
|
@@ -270,11 +274,25 @@ return new Response(res.html, { headers: { "content-type": "text/html" } });
|
|
|
270
274
|
| `"redirect"` | `to: string`, `status: number` | Loader called `redirect()` |
|
|
271
275
|
| `"error"` | `status: number`, `message: string`, `html: string` | Loader called `error()` or threw |
|
|
272
276
|
|
|
277
|
+
#### `.respond(urlOrRequest, registry, options?)` — server / SSR
|
|
278
|
+
|
|
279
|
+
Renders a route to a ready-to-send HTTP `Response`, handling redirects, loader errors, and security headers (`Content-Type`, `X-Content-Type-Options: nosniff`, `Referrer-Policy`, `Cache-Control: no-store`, and an optional CSP nonce). Pass a `shell` to inject the serialized `<head>` into a document shell.
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
const response = await router()
|
|
283
|
+
.route("/", HomePage)
|
|
284
|
+
.respond(new Request(request.url), registry, {
|
|
285
|
+
cspNonce,
|
|
286
|
+
shell: (head, html) =>
|
|
287
|
+
`<!doctype html><html lang="en"><head>${head.headTags}</head><body>${html}</body></html>`,
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
273
291
|
---
|
|
274
292
|
|
|
275
|
-
#### `.runLoader(
|
|
293
|
+
#### `.runLoader(urlOrRequest, request?)` — server / SSR
|
|
276
294
|
|
|
277
|
-
Runs the loader chain for the matched route without rendering any HTML. Returns a discriminated union result. Used by the `/__ilha/loader` endpoint the Vite plugin exposes for client-side navigation.
|
|
295
|
+
Runs the loader chain for the matched route without rendering any HTML. Returns a discriminated union result. Used by the `/__ilha/loader` endpoint the Vite plugin exposes for client-side navigation — the originating `Request` (cookies, identity, abort signal) is forwarded to the loader through both the endpoint and this method.
|
|
278
296
|
|
|
279
297
|
```ts
|
|
280
298
|
const result = await router().route("/user/:id", userPage, userLoader).runLoader("/user/42");
|
|
@@ -1102,7 +1120,9 @@ Or use the one-liner: `pageRouter.hydrate(registry)`.
|
|
|
1102
1120
|
|
|
1103
1121
|
On the **server**, loaders run inside `.renderHydratable()` / `.renderResponse()`. Their return value is serialized into `data-ilha-props` on the island element so the client can rehydrate without re-fetching.
|
|
1104
1122
|
|
|
1105
|
-
On the **client**, navigations resolve loader data before mounting the next island. Routes with a loader registered in the browser — a manual `.route(path, island, loader)` or an FS-routing `clientLoad` export — run that loader locally, with no network round-trip. Routes with only a server loader (`markLoader()` / a `load` export) fetch from the `/__ilha/loader` endpoint, served automatically by the Vite plugin (dev) and the server adapter (production).
|
|
1123
|
+
On the **client**, navigations resolve loader data before mounting the next island. Routes with a loader registered in the browser — a manual `.route(path, island, loader)` or an FS-routing `clientLoad` export — run that loader locally, with no network round-trip. Routes with only a server loader (`markLoader()` / a `load` export) fetch from the `/__ilha/loader` endpoint, served automatically by the Vite plugin (dev) and the server adapter (production). The originating `Request` (cookies, identity, abort signal) is forwarded to the loader and the island-request scope, so `ctx.request` and `useContext().request` behave in client navigations exactly as they do during SSR.
|
|
1124
|
+
|
|
1125
|
+
Like `/__ilha/frame`, the loader endpoint is **denied by default** in production when no guard is registered — gate it with `setLoaderGuard()` (or the shared frame guard / `defaultAction: "open"` policy) or client navigations to server-loader routes return 403.
|
|
1106
1126
|
|
|
1107
1127
|
```
|
|
1108
1128
|
server client (navigation)
|
package/dist/index.d.ts
CHANGED
|
@@ -253,20 +253,20 @@ export interface RouterBuilder {
|
|
|
253
253
|
prime(): void;
|
|
254
254
|
mount(target: string | Element, options?: MountOptions): () => void;
|
|
255
255
|
render(url: string | URL): string;
|
|
256
|
-
renderHydratable(
|
|
256
|
+
renderHydratable(urlOrRequest: string | URL | Request, registry: Record<string, Island<any, any>>, options?: HydratableRenderOptions, request?: Request): Promise<string>;
|
|
257
257
|
/**
|
|
258
258
|
* Like `renderHydratable` but surfaces loader redirects and errors as
|
|
259
259
|
* structured responses instead of baking them into HTML. Prefer this from
|
|
260
260
|
* host server code so you can emit proper 302 / 4xx responses.
|
|
261
261
|
*/
|
|
262
|
-
renderResponse(
|
|
262
|
+
renderResponse(urlOrRequest: string | URL | Request, registry: Record<string, Island<any, any>>, options?: HydratableRenderOptions, request?: Request): Promise<RenderResponse>;
|
|
263
263
|
/**
|
|
264
264
|
* Run the loader chain for a given URL without rendering. Backs the
|
|
265
265
|
* `/__ilha/loader` endpoint that the host server handler
|
|
266
266
|
* serves as JSON for client-side navigation. Returns the raw loader result, a
|
|
267
267
|
* redirect sentinel, or an error sentinel.
|
|
268
268
|
*/
|
|
269
|
-
runLoader(
|
|
269
|
+
runLoader(urlOrRequest: string | URL | Request, request?: Request): Promise<{
|
|
270
270
|
kind: "data";
|
|
271
271
|
data: Record<string, unknown>;
|
|
272
272
|
head?: SerializedHead;
|
|
@@ -281,6 +281,12 @@ export interface RouterBuilder {
|
|
|
281
281
|
} | {
|
|
282
282
|
kind: "not-found";
|
|
283
283
|
}>;
|
|
284
|
+
/**
|
|
285
|
+
* Render a route to a ready-to-send HTTP `Response`, handling redirects,
|
|
286
|
+
* loader errors, and security headers. `request` (or a URL string) selects
|
|
287
|
+
* the route; the optional `shell` injects `head` tags into a document.
|
|
288
|
+
*/
|
|
289
|
+
respond(urlOrRequest: string | URL | Request, registry: Record<string, Island<any, any>>, options?: RespondOptions): Promise<Response>;
|
|
284
290
|
/**
|
|
285
291
|
* Hydrate the application - combines prime(), mount(), and router.mount() into one call.
|
|
286
292
|
* @param registry - The island registry from ilha:registry
|
|
@@ -413,6 +419,49 @@ export declare function head(input: HeadInput): void;
|
|
|
413
419
|
* win on collision; the last `titleTemplate` wraps the resolved title.
|
|
414
420
|
*/
|
|
415
421
|
export declare function serializeHead(entries: HeadInput[]): SerializedHead;
|
|
422
|
+
export interface HttpResponseOptions {
|
|
423
|
+
status?: number;
|
|
424
|
+
headers?: HeadersInit;
|
|
425
|
+
/**
|
|
426
|
+
* CSP nonce for inline scripts. When set (and `contentSecurityPolicy` is
|
|
427
|
+
* not), a conservative default CSP is emitted with `'nonce-${nonce}'` for
|
|
428
|
+
* `script-src` — pass the same nonce to head `<script nonce=…>` tags.
|
|
429
|
+
*/
|
|
430
|
+
cspNonce?: string;
|
|
431
|
+
/** Full `Content-Security-Policy` string; overrides the nonce-derived default. */
|
|
432
|
+
contentSecurityPolicy?: string;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Build an HTTP `Response` for SSR output with sensible security headers:
|
|
436
|
+
* `Content-Type: text/html`, `X-Content-Type-Options: nosniff`,
|
|
437
|
+
* `Referrer-Policy`, `Cache-Control: no-store`, and an optional CSP. This is
|
|
438
|
+
* a low-level helper — prefer {@link RouterBuilder.respond} for the full
|
|
439
|
+
* render+head+headers pipeline.
|
|
440
|
+
*/
|
|
441
|
+
export declare function httpResponse(body: string | null, options?: HttpResponseOptions): Response;
|
|
442
|
+
/**
|
|
443
|
+
* Options for {@link RouterBuilder.respond}.
|
|
444
|
+
*/
|
|
445
|
+
export interface RespondOptions extends HydratableRenderOptions, HttpResponseOptions {
|
|
446
|
+
/**
|
|
447
|
+
* Wrap the rendered body with a document shell. Receives the serialized
|
|
448
|
+
* head (title/meta/link/script tags + html/body attributes) and the inner
|
|
449
|
+
* HTML; return the full document.
|
|
450
|
+
*/
|
|
451
|
+
shell?: (head: SerializedHead, html: string) => string;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Validate a loader redirect target. Relative paths always pass; same-origin
|
|
455
|
+
* absolute URLs collapse to a path; cross-origin targets are rejected unless
|
|
456
|
+
* `allowExternal`. Protocol-relative (`//host`) and unparsable targets are
|
|
457
|
+
* always rejected.
|
|
458
|
+
*/
|
|
459
|
+
export declare function resolveRedirectTarget(to: string, base: URL, allowExternal: boolean): {
|
|
460
|
+
ok: true;
|
|
461
|
+
to: string;
|
|
462
|
+
} | {
|
|
463
|
+
ok: false;
|
|
464
|
+
};
|
|
416
465
|
export declare function router(options?: RouterOptions): RouterBuilder;
|
|
417
466
|
declare const _default: {
|
|
418
467
|
router: typeof router;
|