@ilha/router 0.11.1 → 0.11.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/dist/{plugin-C3AAI3e_.js → plugin-Bfnn44nu.js} +21 -4
- package/dist/rsbuild.js +1 -1
- package/dist/server-island.d.ts +3 -1
- package/dist/server-island.js +30 -8
- package/dist/server-islands.d.ts +4 -0
- package/dist/{ssr-WA3bSsCo.js → ssr-CV_vNpS3.js} +3 -11
- package/dist/ssr.d.ts +1 -3
- package/dist/ssr.js +1 -1
- package/dist/vite.js +1 -1
- package/package.json +11 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as runWithIslandRequest, a as authorizeFrameRequest, b as setFrameAuth, c as frameScopedUrl, f as isSafeFramePath, h as parseFrameProps, n as FrameError, s as frameEnvelope, u as getFrameGuard, x as setFrameGuard, y as renderServerIslandResult } from "./ssr-
|
|
1
|
+
import { C as runWithIslandRequest, a as authorizeFrameRequest, b as setFrameAuth, c as frameScopedUrl, f as isSafeFramePath, h as parseFrameProps, n as FrameError, s as frameEnvelope, u as getFrameGuard, x as setFrameGuard, y as renderServerIslandResult } from "./ssr-CV_vNpS3.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 * as Result from "effect/Result";
|
|
@@ -8,6 +8,22 @@ import { createHash } from "node:crypto";
|
|
|
8
8
|
|
|
9
9
|
//#region src/server-islands.ts
|
|
10
10
|
/**
|
|
11
|
+
* Build-time support for server-defined islands. The plugin scans
|
|
12
|
+
* `*.server.ts(x)` modules for island exports (`export const X = ilha…`),
|
|
13
|
+
* generates a client virtual module per file that re-creates those exports as
|
|
14
|
+
* proxies wired to tacho stubs, and rewrites client-graph import sites so
|
|
15
|
+
* island bindings resolve to the proxy while everything else keeps flowing
|
|
16
|
+
* through oxidejs's tacho stub replacement.
|
|
17
|
+
*/
|
|
18
|
+
/** Oxide RPC client key — basename without `.server.*` (oxide indexes by module name). */
|
|
19
|
+
function serverModuleRpcKey(spec) {
|
|
20
|
+
return basename(spec).replace(/\.server\.(?:[jt]sx?)$/i, "");
|
|
21
|
+
}
|
|
22
|
+
/** Client repaint group key — full normalized path so same-basename files stay distinct. */
|
|
23
|
+
function serverModuleRepaintKey(spec) {
|
|
24
|
+
return spec.replace(/\\/g, "/").replace(/\.server\.(?:[jt]sx?)$/i, "");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
11
27
|
* Replace identity `action(` wrappers of exported server actions with the
|
|
12
28
|
* capture-aware shim on ALREADY-COMPILED module code. Must run inside the
|
|
13
29
|
* SSR transform so upstream JSX/TS output is preserved.
|
|
@@ -165,11 +181,12 @@ function serverIslandPublicId(spec, name) {
|
|
|
165
181
|
return createHash("sha256").update(`${spec}#${name}`).digest("base64url");
|
|
166
182
|
}
|
|
167
183
|
function generateServerIslandModule(spec, scan) {
|
|
168
|
-
const
|
|
184
|
+
const rpcKey = serverModuleRpcKey(spec);
|
|
185
|
+
const repaintKey = serverModuleRepaintKey(spec);
|
|
169
186
|
const lines = [
|
|
170
187
|
`import { client as $$rpc } from "virtual:oxide/client";`,
|
|
171
188
|
`import { __ilhaApplyHead, __ilhaServerIsland } from "@ilha/router/server-island";`,
|
|
172
|
-
`const $$call = (method, args) => { const opts = args.at(-1); return opts && typeof opts === "object" && opts.signal instanceof AbortSignal && Object.keys(opts).length === 1 ? $$rpc[${JSON.stringify(
|
|
189
|
+
`const $$call = (method, args) => { const opts = args.at(-1); return opts && typeof opts === "object" && opts.signal instanceof AbortSignal && Object.keys(opts).length === 1 ? $$rpc[${JSON.stringify(rpcKey)}][method](...args.slice(0, -1), opts) : $$rpc[${JSON.stringify(rpcKey)}][method](...args); };`,
|
|
173
190
|
...scan.clientRefs.map((ref, index) => ref.imported === "default" ? `import $$child${index} from ${JSON.stringify(ref.spec)};` : `import { ${ref.imported} as $$child${index} } from ${JSON.stringify(ref.spec)};`)
|
|
174
191
|
];
|
|
175
192
|
for (const name of scan.exports) if (!scan.islands.some((island) => island.name === name)) lines.push(`export const ${name} = (...args) => $$call(${JSON.stringify(name)}, args);`);
|
|
@@ -186,7 +203,7 @@ function generateServerIslandModule(spec, scan) {
|
|
|
186
203
|
if (scan.clientRefs.length) wiring.push(`children: { ${scan.clientRefs.map((ref, index) => `${JSON.stringify(ref.id)}: $$child${index}`).join(", ")} }`);
|
|
187
204
|
const id = serverIslandPublicId(spec, island.name);
|
|
188
205
|
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) { try { const u = new URL(j.redirect, location.href); if (u.origin === location.origin) location.assign(u.pathname + u.search + u.hash); } catch {} throw new Error("frame redirected"); } __ilhaApplyHead(j.head); return j.html; })`);
|
|
189
|
-
const call = `__ilhaServerIsland(${JSON.stringify(id)}, ${JSON.stringify(island.as)}, { ${wiring.join(", ")} })`;
|
|
206
|
+
const call = `__ilhaServerIsland(${JSON.stringify(id)}, ${JSON.stringify(island.as)}, { ${wiring.join(", ")} }, ${JSON.stringify(repaintKey)})`;
|
|
190
207
|
if (island.name === "default") lines.push(`export default ${call};`);
|
|
191
208
|
else lines.push(`export const ${island.name} = ${call};`);
|
|
192
209
|
}
|
package/dist/rsbuild.js
CHANGED
package/dist/server-island.d.ts
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* `data-ilha-actions` manifest emitted by `hydratable()`.
|
|
16
16
|
*/
|
|
17
17
|
declare const ISLAND_CALL: unique symbol;
|
|
18
|
+
/** @internal Repaint every mounted island from the same `*.server` module. */
|
|
19
|
+
export declare function __ilhaRepaintServerModule(moduleKey: string): void;
|
|
18
20
|
export type ServerStreamFn = (signal: AbortSignal) => AsyncGenerator<unknown> | Generator<unknown>;
|
|
19
21
|
export interface ServerIslandWiring {
|
|
20
22
|
/** Stream key → client transport. The plugin wires these to tacho stubs. */
|
|
@@ -52,5 +54,5 @@ export type ServerIslandCallable = Record<symbol, unknown> & ((props?: Record<st
|
|
|
52
54
|
toString: () => string;
|
|
53
55
|
key: (slotKey: string) => (props?: Record<string, unknown>) => IslandCallShape;
|
|
54
56
|
};
|
|
55
|
-
export declare function __ilhaServerIsland(id: string, as: string, wiring?: ServerIslandWiring): ServerIslandCallable;
|
|
57
|
+
export declare function __ilhaServerIsland(id: string, as: string, wiring?: ServerIslandWiring, moduleKey?: string): ServerIslandCallable;
|
|
56
58
|
export {};
|
package/dist/server-island.js
CHANGED
|
@@ -27,6 +27,23 @@ const EVENT_SENTINEL_ATTR = "data-ilha-on";
|
|
|
27
27
|
const ACTIONS_ATTR = "data-ilha-actions";
|
|
28
28
|
const PROPS_ATTR = "data-ilha-props";
|
|
29
29
|
const CLIENT_REF_ATTR = "data-ilha-client-ref";
|
|
30
|
+
const moduleRepaints = /* @__PURE__ */ new Map();
|
|
31
|
+
/** @internal Repaint every mounted island from the same `*.server` module. */
|
|
32
|
+
function __ilhaRepaintServerModule(moduleKey) {
|
|
33
|
+
for (const repaint of moduleRepaints.get(moduleKey) ?? []) repaint();
|
|
34
|
+
}
|
|
35
|
+
function linkServerModuleRepaints(moduleKey, repaint) {
|
|
36
|
+
let set = moduleRepaints.get(moduleKey);
|
|
37
|
+
if (!set) {
|
|
38
|
+
set = /* @__PURE__ */ new Set();
|
|
39
|
+
moduleRepaints.set(moduleKey, set);
|
|
40
|
+
}
|
|
41
|
+
set.add(repaint);
|
|
42
|
+
return () => {
|
|
43
|
+
set.delete(repaint);
|
|
44
|
+
if (set.size === 0) moduleRepaints.delete(moduleKey);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
30
47
|
/** @internal Apply head entries returned with a server-page frame. */
|
|
31
48
|
function __ilhaApplyHead(entries) {
|
|
32
49
|
if (Array.isArray(entries)) applyHeadEntriesToDocument(entries);
|
|
@@ -85,7 +102,7 @@ async function startServerStream(fn, signal) {
|
|
|
85
102
|
}
|
|
86
103
|
throw last;
|
|
87
104
|
}
|
|
88
|
-
function hydrateServerIsland(host, id, wiring, props) {
|
|
105
|
+
function hydrateServerIsland(host, id, wiring, props, moduleKey) {
|
|
89
106
|
const controller = new AbortController();
|
|
90
107
|
const cleanups = [];
|
|
91
108
|
cleanups.push(() => controller.abort());
|
|
@@ -115,6 +132,11 @@ function hydrateServerIsland(host, id, wiring, props) {
|
|
|
115
132
|
if (!controller.signal.aborted) console.error(`[ilha-router] frame render failed for "${id}":`, err);
|
|
116
133
|
});
|
|
117
134
|
};
|
|
135
|
+
const repaintModule = () => {
|
|
136
|
+
if (moduleKey) __ilhaRepaintServerModule(moduleKey);
|
|
137
|
+
else scheduleRepaint();
|
|
138
|
+
};
|
|
139
|
+
if (moduleKey) cleanups.push(linkServerModuleRepaints(moduleKey, scheduleRepaint));
|
|
118
140
|
const attached = [];
|
|
119
141
|
const readManifest = () => {
|
|
120
142
|
const raw = host.querySelector(`:scope > template[${ACTIONS_ATTR}], template[${ACTIONS_ATTR}]`)?.getAttribute(ACTIONS_ATTR) ?? host.getAttribute(ACTIONS_ATTR) ?? null;
|
|
@@ -145,7 +167,7 @@ function hydrateServerIsland(host, id, wiring, props) {
|
|
|
145
167
|
const action = actionKey == null ? void 0 : wiring.actions?.[actionKey];
|
|
146
168
|
if (!action) continue;
|
|
147
169
|
const listener = () => {
|
|
148
|
-
Promise.resolve(action(...callArgs)).then(() =>
|
|
170
|
+
Promise.resolve(action(...callArgs)).then(() => repaintModule()).catch((err) => {
|
|
149
171
|
console.error(`[ilha-router] action "${String(actionKey)}" failed:`, err);
|
|
150
172
|
});
|
|
151
173
|
};
|
|
@@ -170,7 +192,7 @@ function hydrateServerIsland(host, id, wiring, props) {
|
|
|
170
192
|
if (!action) continue;
|
|
171
193
|
const args = Array.isArray(marker.a) ? marker.a : [];
|
|
172
194
|
props[key] = (..._runtimeArgs) => Promise.resolve(action(...args)).then((result) => {
|
|
173
|
-
|
|
195
|
+
repaintModule();
|
|
174
196
|
return result;
|
|
175
197
|
});
|
|
176
198
|
}
|
|
@@ -204,7 +226,7 @@ function hydrateServerIsland(host, id, wiring, props) {
|
|
|
204
226
|
const { done, value } = step;
|
|
205
227
|
if (controller.signal.aborted || done) break;
|
|
206
228
|
state[key] = value;
|
|
207
|
-
|
|
229
|
+
repaintModule();
|
|
208
230
|
step = await gen.next();
|
|
209
231
|
}
|
|
210
232
|
} catch (err) {
|
|
@@ -232,7 +254,7 @@ function hydrateServerIsland(host, id, wiring, props) {
|
|
|
232
254
|
}
|
|
233
255
|
};
|
|
234
256
|
}
|
|
235
|
-
function __ilhaServerIsland(id, as, wiring = {}) {
|
|
257
|
+
function __ilhaServerIsland(id, as, wiring = {}, moduleKey) {
|
|
236
258
|
const slotTag = assertValidTag(as);
|
|
237
259
|
const island = ((props) => {
|
|
238
260
|
return "";
|
|
@@ -250,10 +272,10 @@ function __ilhaServerIsland(id, as, wiring = {}) {
|
|
|
250
272
|
key: slotKey
|
|
251
273
|
});
|
|
252
274
|
};
|
|
253
|
-
island[ISLAND_MOUNT_INTERNAL] = (host, mountProps) => hydrateServerIsland(host, id, wiring, mountProps);
|
|
254
|
-
island.mount = (host, mountProps) => hydrateServerIsland(host, id, wiring, mountProps).unmount;
|
|
275
|
+
island[ISLAND_MOUNT_INTERNAL] = (host, mountProps) => hydrateServerIsland(host, id, wiring, mountProps, moduleKey);
|
|
276
|
+
island.mount = (host, mountProps) => hydrateServerIsland(host, id, wiring, mountProps, moduleKey).unmount;
|
|
255
277
|
return island;
|
|
256
278
|
}
|
|
257
279
|
|
|
258
280
|
//#endregion
|
|
259
|
-
export { __ilhaApplyHead, __ilhaServerIsland };
|
|
281
|
+
export { __ilhaApplyHead, __ilhaRepaintServerModule, __ilhaServerIsland };
|
package/dist/server-islands.d.ts
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
* island bindings resolve to the proxy while everything else keeps flowing
|
|
7
7
|
* through oxidejs's tacho stub replacement.
|
|
8
8
|
*/
|
|
9
|
+
/** Oxide RPC client key — basename without `.server.*` (oxide indexes by module name). */
|
|
10
|
+
export declare function serverModuleRpcKey(spec: string): string;
|
|
11
|
+
/** Client repaint group key — full normalized path so same-basename files stay distinct. */
|
|
12
|
+
export declare function serverModuleRepaintKey(spec: string): string;
|
|
9
13
|
export interface ScannedServerIsland {
|
|
10
14
|
/** Export binding name, or `"default"` for `export default ilha…`. */
|
|
11
15
|
name: string;
|
|
@@ -5,6 +5,7 @@ import * as Result from "effect/Result";
|
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import * as Data from "effect/Data";
|
|
7
7
|
import * as Effect from "effect/Effect";
|
|
8
|
+
import { action, brandServerAction } from "oxidejs";
|
|
8
9
|
|
|
9
10
|
//#region src/request-scope.ts
|
|
10
11
|
/**
|
|
@@ -184,19 +185,10 @@ function frameEnvelope(status, body) {
|
|
|
184
185
|
body: JSON.stringify(body)
|
|
185
186
|
};
|
|
186
187
|
}
|
|
187
|
-
const ACTION_CALL = Symbol.for("ilha.actionCall");
|
|
188
188
|
/** @internal Brand an exported server action with its generated RPC transport key. */
|
|
189
189
|
function __ilhaServerAction(key, fn) {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
const handler = ((..._ev) => wrapped(...args));
|
|
193
|
-
handler[ACTION_CALL] = {
|
|
194
|
-
k: key,
|
|
195
|
-
a: args
|
|
196
|
-
};
|
|
197
|
-
return handler;
|
|
198
|
-
};
|
|
199
|
-
return wrapped;
|
|
190
|
+
const handle = typeof fn === "function" && fn.$$atom === 1 ? fn : action(fn);
|
|
191
|
+
return brandServerAction(key, handle);
|
|
200
192
|
}
|
|
201
193
|
function registerServerIsland(id, render) {
|
|
202
194
|
registry().set(id, { render });
|
package/dist/ssr.d.ts
CHANGED
|
@@ -110,9 +110,7 @@ export interface FrameEnvelope {
|
|
|
110
110
|
}
|
|
111
111
|
export declare function frameEnvelope(status: number, body: Record<string, unknown>): FrameEnvelope;
|
|
112
112
|
/** @internal Brand an exported server action with its generated RPC transport key. */
|
|
113
|
-
export declare function __ilhaServerAction<A extends unknown[], R>(key: string, fn: (...args: A) => R): (
|
|
114
|
-
with: (...args: A) => (...ev: unknown[]) => R;
|
|
115
|
-
};
|
|
113
|
+
export declare function __ilhaServerAction<A extends unknown[], R>(key: string, fn: (...args: A) => R | Promise<R>): import("oxidejs").ServerActionHandle<A, Awaited<Awaited<R>>>;
|
|
116
114
|
export declare function registerServerIsland(id: string, render: () => unknown): void;
|
|
117
115
|
export declare function getServerIslandEntry(id: string): ServerIslandEntry | undefined;
|
|
118
116
|
declare const FrameError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
package/dist/ssr.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { S as ssr, _ as registerServerIsland, a as authorizeFrameRequest, b as setFrameAuth, c as frameScopedUrl, d as getServerIslandEntry, f as isSafeFramePath, g as readBodyBounded, h as parseFrameProps, i as __ilhaServerAction, l as getFrameAuth, m as json, n as FrameError, o as forwardIdentityHeaders, p as isTrustedOrigin, r as MAX_BODY, s as frameEnvelope, t as FRAME_ENDPOINT, u as getFrameGuard, v as renderServerIsland, x as setFrameGuard, y as renderServerIslandResult } from "./ssr-
|
|
1
|
+
import { S as ssr, _ as registerServerIsland, a as authorizeFrameRequest, b as setFrameAuth, c as frameScopedUrl, d as getServerIslandEntry, f as isSafeFramePath, g as readBodyBounded, h as parseFrameProps, i as __ilhaServerAction, l as getFrameAuth, m as json, n as FrameError, o as forwardIdentityHeaders, p as isTrustedOrigin, r as MAX_BODY, s as frameEnvelope, t as FRAME_ENDPOINT, u as getFrameGuard, v as renderServerIsland, x as setFrameGuard, y as renderServerIslandResult } from "./ssr-CV_vNpS3.js";
|
|
2
2
|
|
|
3
3
|
export { FRAME_ENDPOINT, FrameError, MAX_BODY, __ilhaServerAction, authorizeFrameRequest, ssr as default, forwardIdentityHeaders, frameEnvelope, frameScopedUrl, getFrameAuth, getFrameGuard, getServerIslandEntry, isSafeFramePath, isTrustedOrigin, json, parseFrameProps, readBodyBounded, registerServerIsland, renderServerIsland, renderServerIslandResult, setFrameAuth, setFrameGuard };
|
package/dist/vite.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ilha/router",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "A tiny SPA router for Ilha",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"frontend",
|
|
@@ -69,14 +69,21 @@
|
|
|
69
69
|
"test": "bun test"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"effect": "
|
|
72
|
+
"effect": "4.0.0-rc.112",
|
|
73
73
|
"unplugin": "3.3.0"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"ilha": "0.14.
|
|
76
|
+
"ilha": "^0.14.2",
|
|
77
|
+
"oxidejs": "^0.3.0",
|
|
77
78
|
"vite": "^8.2.2"
|
|
78
79
|
},
|
|
79
80
|
"peerDependencies": {
|
|
80
|
-
"ilha": ">=0.14.
|
|
81
|
+
"ilha": ">=0.14.2",
|
|
82
|
+
"oxidejs": ">=0.3.0"
|
|
83
|
+
},
|
|
84
|
+
"peerDependenciesMeta": {
|
|
85
|
+
"oxidejs": {
|
|
86
|
+
"optional": true
|
|
87
|
+
}
|
|
81
88
|
}
|
|
82
89
|
}
|