@ilha/router 0.10.2 → 0.10.3

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.
@@ -1,4 +1,4 @@
1
- import { C as setLoaderGuard, T as runWithIslandRequest, b as setFrameAuth, c as frameEnvelope, m as isSafeFramePath, n as FrameError, o as authorizeFrameRequest, u as getFrameGuard, x as setFrameGuard, y as renderServerIsland } from "./ssr-CGcMUP1G.js";
1
+ import { E as runWithIslandRequest, S as setFrameGuard, _ as parseFrameProps, b as renderServerIsland, c as frameEnvelope, m as isSafeFramePath, n as FrameError, o as authorizeFrameRequest, u as getFrameGuard, w as setLoaderGuard, x as setFrameAuth } from "./ssr-CTd2quoA.js";
2
2
  import { existsSync, readFileSync, statSync, watch } from "node:fs";
3
3
  import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
4
4
  import { createUnplugin } from "unplugin";
@@ -198,7 +198,7 @@ function generateServerIslandModule(spec, scan) {
198
198
  if (actions.length) wiring.push(`actions: { ${actions.join(", ")} }`);
199
199
  if (scan.clientRefs.length) wiring.push(`children: { ${scan.clientRefs.map((ref, index) => `${JSON.stringify(ref.id)}: $$child${index}`).join(", ")} }`);
200
200
  const id = serverIslandPublicId(spec, island.name);
201
- wiring.push(`frame: () => fetch("/__ilha/frame", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ id: ${JSON.stringify(id)}, path: location.pathname + location.search }) }).then((r) => { if (!r.ok) throw new Error("frame failed"); return r.json(); }).then((j) => { if (j.redirect) { location.assign(j.redirect); throw new Error("frame redirected"); } __ilhaApplyHead(j.head); return j.html; })`);
201
+ wiring.push(`frame: (props) => fetch("/__ilha/frame", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ id: ${JSON.stringify(id)}, path: location.pathname + location.search, props }) }).then((r) => { if (!r.ok) throw new Error("frame failed"); return r.json(); }).then((j) => { if (j.redirect) { location.assign(j.redirect); throw new Error("frame redirected"); } __ilhaApplyHead(j.head); return j.html; })`);
202
202
  if (scan.clientLoader) wiring.push(`clientLoader: () => $$call("load", [])`);
203
203
  const call = `__ilhaServerIsland(${JSON.stringify(id)}, ${JSON.stringify(island.as)}, { ${wiring.join(", ")} })`;
204
204
  if (island.name === "default") lines.push(`export default ${call};`);
@@ -1046,6 +1046,7 @@ const pagesFactory = (options = {}) => {
1046
1046
  }
1047
1047
  try {
1048
1048
  const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
1049
+ const incomingProps = parseFrameProps(body.props);
1049
1050
  const target = serverIslands.get(body.id ?? "");
1050
1051
  if (!target) throw new Error("unknown island");
1051
1052
  let framePath = "/";
@@ -1068,7 +1069,7 @@ const pagesFactory = (options = {}) => {
1068
1069
  headers
1069
1070
  });
1070
1071
  let head;
1071
- const html = await renderServerIsland(body.id ?? "", request, (r, fn) => runWithIslandRequest(r, fn), (entries) => head = entries);
1072
+ const html = await renderServerIsland(body.id ?? "", request, (r, fn) => runWithIslandRequest(r, fn), (entries) => head = entries, incomingProps);
1072
1073
  const env = frameEnvelope(200, {
1073
1074
  html: String(html),
1074
1075
  head
package/dist/rsbuild.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as ilhaPages } from "./plugin-Bh0y5kkI.js";
1
+ import { t as ilhaPages } from "./plugin-DBbbaeFm.js";
2
2
 
3
3
  //#region src/rsbuild.ts
4
4
  /** Rsbuild plugin — use via `@ilha/router/rsbuild`. */
@@ -22,8 +22,8 @@ export interface ServerIslandWiring {
22
22
  /** Action key → client transport. Event payloads are not serializable;
23
23
  * handlers receive `undefined` and should read island state instead. */
24
24
  actions?: Record<string, (payload?: unknown) => unknown>;
25
- /** Frame transport: re-renders the island from server-owned state. */
26
- frame?: () => unknown;
25
+ /** Frame transport: re-renders the island with the latest parent props. */
26
+ frame?: (props?: Record<string, unknown>) => unknown;
27
27
  /** RPC transport for the module's `loader.client` export — invoked once
28
28
  * when the view hydrates. Side-effect loader on server pages. */
29
29
  clientLoader?: () => unknown;
@@ -51,7 +51,7 @@ interface IslandCallShape {
51
51
  key: string;
52
52
  }
53
53
  export type ServerIslandCallable = Record<symbol, unknown> & ((props?: Record<string, unknown>) => string) & {
54
- mount: (host: Element) => () => void;
54
+ mount: (host: Element, props?: Record<string, unknown>) => () => void;
55
55
  toString: () => string;
56
56
  key: (slotKey: string) => (props?: Record<string, unknown>) => IslandCallShape;
57
57
  };
@@ -48,7 +48,7 @@ function belongsToHost(host, candidate) {
48
48
  }
49
49
  return el === host;
50
50
  }
51
- function hydrateServerIsland(host, id, wiring) {
51
+ function hydrateServerIsland(host, id, wiring, props) {
52
52
  const controller = new AbortController();
53
53
  const cleanups = [];
54
54
  cleanups.push(() => controller.abort());
@@ -61,12 +61,14 @@ function hydrateServerIsland(host, id, wiring) {
61
61
  }
62
62
  }
63
63
  const frame = wiring.frame;
64
+ let currentProps = props;
64
65
  let repaintChain = Promise.resolve();
65
66
  const scheduleRepaint = () => {
66
67
  if (!frame || controller.signal.aborted) return;
68
+ const sent = currentProps;
67
69
  repaintChain = repaintChain.then(async () => {
68
70
  if (controller.signal.aborted || !host.isConnected) return;
69
- const html = await frame();
71
+ const html = await frame(sent);
70
72
  if (controller.signal.aborted || typeof html !== "string") return;
71
73
  morph(host, html);
72
74
  syncChildren();
@@ -187,7 +189,10 @@ function hydrateServerIsland(host, id, wiring) {
187
189
  for (const cleanup of cleanups) cleanup();
188
190
  cleanups.length = 0;
189
191
  },
190
- updateProps: () => {}
192
+ updateProps: (next) => {
193
+ currentProps = next;
194
+ scheduleRepaint();
195
+ }
191
196
  };
192
197
  }
193
198
  function __ilhaServerIsland(id, as, wiring = {}) {
@@ -208,8 +213,8 @@ function __ilhaServerIsland(id, as, wiring = {}) {
208
213
  key: slotKey
209
214
  });
210
215
  };
211
- island[ISLAND_MOUNT_INTERNAL] = (host) => hydrateServerIsland(host, id, wiring);
212
- island.mount = (host) => hydrateServerIsland(host, id, wiring).unmount;
216
+ island[ISLAND_MOUNT_INTERNAL] = (host, mountProps) => hydrateServerIsland(host, id, wiring, mountProps);
217
+ island.mount = (host, mountProps) => hydrateServerIsland(host, id, wiring, mountProps).unmount;
213
218
  return island;
214
219
  }
215
220
 
@@ -214,10 +214,10 @@ function matchPatternParams(pattern, pathname) {
214
214
  * caller's scope. Throws `FrameError` with an HTTP status for client-facing
215
215
  * failures; loader redirects surface via `FrameError.redirect`.
216
216
  */
217
- async function renderServerIsland(id, request, runWithScope, onHead) {
217
+ async function renderServerIsland(id, request, runWithScope, onHead, incomingProps) {
218
218
  const entry = registry().get(id);
219
219
  if (!entry) throw new FrameError(400, "unknown island");
220
- let props;
220
+ let props = incomingProps;
221
221
  if (entry.load) {
222
222
  let url;
223
223
  try {
@@ -264,6 +264,12 @@ const FRAME_ENDPOINT = "/__ilha/frame";
264
264
  const LOADER_ENDPOINT = "/__ilha/loader";
265
265
  /** Max request body size — matches the dev middleware cap. */
266
266
  const MAX_BODY = 16384;
267
+ /** Parent-island props on a frame POST. Missing is fine; anything else is 400. */
268
+ function parseFrameProps(value) {
269
+ if (value == null) return void 0;
270
+ if (typeof value !== "object" || Array.isArray(value)) throw new FrameError(400, "frame failed");
271
+ return value;
272
+ }
267
273
  function json(status, body) {
268
274
  const env = frameEnvelope(status, body);
269
275
  return new Response(env.body, {
@@ -423,11 +429,13 @@ async function ssr(request) {
423
429
  if (!authorized.ok) return json(authorized.status, { error: "frame failed" });
424
430
  let id;
425
431
  let path = "/";
432
+ let incomingProps;
426
433
  try {
427
434
  const text = await readBodyBounded(request, MAX_BODY);
428
435
  if (text === null) return json(413, { error: "frame failed" });
429
436
  const body = JSON.parse(text);
430
437
  id = String(body.id ?? "");
438
+ incomingProps = parseFrameProps(body.props);
431
439
  if (typeof body.path === "string") {
432
440
  if (!isSafeFramePath(body.path)) return json(400, { error: "frame failed" });
433
441
  path = body.path;
@@ -455,7 +463,7 @@ async function ssr(request) {
455
463
  }
456
464
  let head;
457
465
  return json(200, {
458
- html: await renderServerIsland(id, scoped, (scopedRequest, fn) => Promise.resolve(runWithIslandRequest(scopedRequest, fn)), (entries) => head = entries),
466
+ html: await renderServerIsland(id, scoped, (scopedRequest, fn) => Promise.resolve(runWithIslandRequest(scopedRequest, fn)), (entries) => head = entries, incomingProps),
459
467
  head
460
468
  });
461
469
  } catch (error) {
@@ -472,4 +480,4 @@ async function ssr(request) {
472
480
  ssr.imports = ["ilha:pages/server", "ilha:loaders"];
473
481
 
474
482
  //#endregion
475
- export { setLoaderGuard as C, setFrameLoaderRunner as S, runWithIslandRequest as T, readBodyBounded as _, __ilhaServerAction as a, setFrameAuth as b, frameEnvelope as c, getFrameLoaderRunner as d, getLoaderGuard as f, json as g, isTrustedOrigin as h, MAX_BODY as i, getFrameAuth as l, isSafeFramePath as m, FrameError as n, authorizeFrameRequest as o, getServerIslandEntry as p, LOADER_ENDPOINT as r, forwardIdentityHeaders as s, FRAME_ENDPOINT as t, getFrameGuard as u, registerServerIsland as v, ssr as w, setFrameGuard as x, renderServerIsland as y };
483
+ export { setFrameLoaderRunner as C, runWithIslandRequest as E, setFrameGuard as S, ssr as T, parseFrameProps as _, __ilhaServerAction as a, renderServerIsland as b, frameEnvelope as c, getFrameLoaderRunner as d, getLoaderGuard as f, json as g, isTrustedOrigin as h, MAX_BODY as i, getFrameAuth as l, isSafeFramePath as m, FrameError as n, authorizeFrameRequest as o, getServerIslandEntry as p, LOADER_ENDPOINT as r, forwardIdentityHeaders as s, FRAME_ENDPOINT as t, getFrameGuard as u, readBodyBounded as v, setLoaderGuard as w, setFrameAuth as x, registerServerIsland as y };
package/dist/ssr.d.ts CHANGED
@@ -164,12 +164,14 @@ export declare class FrameError extends Error {
164
164
  * caller's scope. Throws `FrameError` with an HTTP status for client-facing
165
165
  * failures; loader redirects surface via `FrameError.redirect`.
166
166
  */
167
- export declare function renderServerIsland(id: string, request: Request, runWithScope: <T>(request: Request, fn: () => T) => T | Promise<T>, onHead?: (entries: HeadInput[]) => void): Promise<string>;
167
+ export declare function renderServerIsland(id: string, request: Request, runWithScope: <T>(request: Request, fn: () => T) => T | Promise<T>, onHead?: (entries: HeadInput[]) => void, incomingProps?: Record<string, unknown>): Promise<string>;
168
168
  export declare const FRAME_ENDPOINT = "/__ilha/frame";
169
169
  /** Regular-page server loads: served through the loader-runner slot. */
170
170
  export declare const LOADER_ENDPOINT = "/__ilha/loader";
171
171
  /** Max request body size — matches the dev middleware cap. */
172
172
  export declare const MAX_BODY: number;
173
+ /** Parent-island props on a frame POST. Missing is fine; anything else is 400. */
174
+ export declare function parseFrameProps(value: unknown): Record<string, unknown> | undefined;
173
175
  export declare function json(status: number, body: Record<string, unknown>): Response;
174
176
  /**
175
177
  * Read a request body as UTF-8, streaming it with a hard byte cap. Returns
package/dist/ssr.js CHANGED
@@ -1,3 +1,3 @@
1
- import { C as setLoaderGuard, S as setFrameLoaderRunner, _ as readBodyBounded, a as __ilhaServerAction, b as setFrameAuth, c as frameEnvelope, d as getFrameLoaderRunner, f as getLoaderGuard, g as json, h as isTrustedOrigin, i as MAX_BODY, l as getFrameAuth, m as isSafeFramePath, n as FrameError, o as authorizeFrameRequest, p as getServerIslandEntry, r as LOADER_ENDPOINT, s as forwardIdentityHeaders, t as FRAME_ENDPOINT, u as getFrameGuard, v as registerServerIsland, w as ssr, x as setFrameGuard, y as renderServerIsland } from "./ssr-CGcMUP1G.js";
1
+ import { C as setFrameLoaderRunner, S as setFrameGuard, T as ssr, _ as parseFrameProps, a as __ilhaServerAction, b as renderServerIsland, c as frameEnvelope, d as getFrameLoaderRunner, f as getLoaderGuard, g as json, h as isTrustedOrigin, i as MAX_BODY, l as getFrameAuth, m as isSafeFramePath, n as FrameError, o as authorizeFrameRequest, p as getServerIslandEntry, r as LOADER_ENDPOINT, s as forwardIdentityHeaders, t as FRAME_ENDPOINT, u as getFrameGuard, v as readBodyBounded, w as setLoaderGuard, x as setFrameAuth, y as registerServerIsland } from "./ssr-CTd2quoA.js";
2
2
 
3
- export { FRAME_ENDPOINT, FrameError, LOADER_ENDPOINT, MAX_BODY, __ilhaServerAction, authorizeFrameRequest, ssr as default, forwardIdentityHeaders, frameEnvelope, getFrameAuth, getFrameGuard, getFrameLoaderRunner, getLoaderGuard, getServerIslandEntry, isSafeFramePath, isTrustedOrigin, json, readBodyBounded, registerServerIsland, renderServerIsland, setFrameAuth, setFrameGuard, setFrameLoaderRunner, setLoaderGuard };
3
+ export { FRAME_ENDPOINT, FrameError, LOADER_ENDPOINT, MAX_BODY, __ilhaServerAction, authorizeFrameRequest, ssr as default, forwardIdentityHeaders, frameEnvelope, getFrameAuth, getFrameGuard, getFrameLoaderRunner, getLoaderGuard, getServerIslandEntry, isSafeFramePath, isTrustedOrigin, json, parseFrameProps, readBodyBounded, registerServerIsland, renderServerIsland, setFrameAuth, setFrameGuard, setFrameLoaderRunner, setLoaderGuard };
package/dist/vite.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as ilhaPages } from "./plugin-Bh0y5kkI.js";
1
+ import { t as ilhaPages } from "./plugin-DBbbaeFm.js";
2
2
 
3
3
  //#region src/vite.ts
4
4
  /** Vite plugin — use via `@ilha/router/vite`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ilha/router",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
4
4
  "description": "A tiny SPA router for Ilha",
5
5
  "keywords": [
6
6
  "frontend",