@ilha/router 0.6.7 → 0.6.8

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/vite.js CHANGED
@@ -1 +1 @@
1
- import{C as e,w as t}from"./src-Cprpo919.js";import{t as n}from"./plugin-OpojKenS.js";function r(e={}){return n.vite(e)}export{n as ilhaPages,r as pages,e as wrapError,t as wrapLayout};
1
+ import{E as e,T as t}from"./src-Cgv3m3sQ.js";import{t as n}from"./plugin-DBB11WaX.js";function r(e={}){return n.vite(e)}export{n as ilhaPages,r as pages,t as wrapError,e as wrapLayout};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ilha/router",
3
- "version": "0.6.7",
3
+ "version": "0.6.8",
4
4
  "description": "A tiny SPA router for Ilha",
5
5
  "keywords": [
6
6
  "frontend",
@@ -39,6 +39,10 @@
39
39
  "types": "./dist/index.d.ts",
40
40
  "import": "./dist/index.js"
41
41
  },
42
+ "./ssr": {
43
+ "types": "./dist/ssr.d.ts",
44
+ "import": "./dist/ssr.js"
45
+ },
42
46
  "./vite": {
43
47
  "types": "./dist/vite.d.ts",
44
48
  "import": "./dist/vite.js"
@@ -56,15 +60,18 @@
56
60
  "access": "public"
57
61
  },
58
62
  "scripts": {
59
- "build": "tsc && tsdown",
63
+ "build": "tsdown && tsc -p tsconfig.build.json",
60
64
  "test": "bun test"
61
65
  },
62
66
  "dependencies": {
63
- "ilha": "0.8.4",
64
67
  "rou3": "0.8.1",
65
68
  "unplugin": "3.0.0"
66
69
  },
67
70
  "devDependencies": {
68
- "vite": "^8.0.16"
71
+ "ilha": "0.8.5",
72
+ "vite": "^8.1.0"
73
+ },
74
+ "peerDependencies": {
75
+ "ilha": ">=0.8.5"
69
76
  }
70
77
  }
@@ -1,299 +0,0 @@
1
- import { HydratableOptions, Island } from "ilha";
2
-
3
- //#region src/hash.d.ts
4
- type HistoryMode = "history" | "hash";
5
- /**
6
- * Set the router's history mode. Call this once at app entry, before
7
- * mounting any router. Defaults to "history" (HTML5 History API).
8
- *
9
- * Use "hash" when the document is loaded over file:// (Electron, Tauri, etc.)
10
- * or any time there's no server able to serve a SPA fallback at arbitrary
11
- * pathnames.
12
- *
13
- * Switching modes mid-session is supported but not common — listeners
14
- * registered before the switch will keep using their original adapter
15
- * until they're re-attached (typically by unmounting and remounting
16
- * the router).
17
- */
18
- declare function setHistoryMode(mode: HistoryMode): void;
19
- declare function getHistoryMode(): HistoryMode;
20
- //#endregion
21
- //#region src/index.d.ts
22
- interface RouteRecord {
23
- pattern: string;
24
- island: Island<any, any>;
25
- /** Merged loader chain (layouts outer→inner, then page) — `undefined` if no loaders. */
26
- loader?: Loader<any>;
27
- /** True when the route has a server-side loader, even if the client only has a marker. */
28
- hasLoader?: boolean;
29
- }
30
- interface RouteSnapshot {
31
- path: string;
32
- params: Record<string, string>;
33
- search: string;
34
- hash: string;
35
- }
36
- interface AppError {
37
- message: string;
38
- status?: number;
39
- stack?: string;
40
- }
41
- type LayoutHandler = (children: Island<any, any>) => Island<any, any>;
42
- type ErrorHandler = (error: AppError, route: RouteSnapshot) => Island<any, any>;
43
- interface LoaderContext {
44
- params: Record<string, string>;
45
- request: Request;
46
- url: URL;
47
- signal: AbortSignal;
48
- }
49
- type Loader<T> = (ctx: LoaderContext) => Promise<T> | T;
50
- /**
51
- * Identity function for declaring a loader. Exists purely as a type anchor and
52
- * a marker for the Vite plugin to detect by export name.
53
- */
54
- declare function loader<T>(fn: Loader<T>): Loader<T>;
55
- /** Extract the return type of a loader. */
56
- type InferLoader<L> = L extends Loader<infer T> ? Awaited<T> : never;
57
- /**
58
- * Merge multiple loader return types into a single object type.
59
- * Later loaders override earlier ones on key collision — matching runtime merge.
60
- *
61
- * @example
62
- * type PageInput = MergeLoaders<[typeof rootLayoutLoad, typeof sectionLayoutLoad, typeof pageLoad]>;
63
- */
64
- type MergeLoaders<Ls extends readonly Loader<any>[]> = Ls extends readonly [infer First extends Loader<any>, ...infer Rest extends readonly Loader<any>[]] ? Rest extends readonly [] ? InferLoader<First> : Omit<InferLoader<First>, keyof MergeLoaders<Rest>> & MergeLoaders<Rest> : {};
65
- declare class Redirect {
66
- readonly __ilhaRedirect: true;
67
- readonly to: string;
68
- readonly status: number;
69
- constructor(to: string, status?: number);
70
- }
71
- declare class LoaderError {
72
- readonly __ilhaLoaderError: true;
73
- readonly status: number;
74
- readonly message: string;
75
- constructor(status: number, message: string);
76
- }
77
- declare function redirect(to: string, status?: number): never;
78
- declare function error(status: number, message: string): never;
79
- /**
80
- * Compose a list of loaders into a single loader. Later loaders win on key
81
- * collision (page loader overrides layout loader for the same key). All loaders
82
- * run concurrently within a chain since they share the same abort signal and
83
- * request — re-fetching is cheap with a request-scoped cache (future work).
84
- *
85
- * For v1 we run them in parallel via `Promise.all`. If a loader throws a
86
- * `Redirect` or `LoaderError`, the composed loader re-throws it unchanged.
87
- */
88
- declare function composeLoaders<Ls extends readonly Loader<any>[]>(loaders: Ls): Loader<MergeLoaders<Ls>>;
89
- declare function wrapLayout(layout: LayoutHandler, page: Island<any, any>): Island<any, any>;
90
- declare function wrapError(handler: ErrorHandler, page: Island<any, any>): Island<any, any>;
91
- declare function defineLayout(layout: LayoutHandler): LayoutHandler;
92
- interface NavigateOptions {
93
- replace?: boolean;
94
- }
95
- type RouterMode = "spa" | "static";
96
- interface RouterOptions {
97
- /**
98
- * Client navigation mode.
99
- * - `spa` — full route graph, SSR/hydration, client-side navigation.
100
- * - `static` — no route graph bundled; hydrate islands on the current
101
- * pre-rendered page only.
102
- * Default: `spa`.
103
- */
104
- mode?: RouterMode;
105
- /**
106
- * When `true` (default), internal `<a>` clicks are intercepted and handled
107
- * by the client router. Set to `false` for MPA-style behavior where links
108
- * perform full document navigations.
109
- * Only meaningful in `spa` mode; ignored in `static` mode.
110
- * Default: `true`.
111
- */
112
- interceptLinks?: boolean;
113
- }
114
- interface HydratableRenderOptions extends Partial<Omit<HydratableOptions, "name">> {}
115
- interface HydrateOptions {
116
- root?: Element;
117
- target?: string | Element;
118
- /**
119
- * When `true` (default), internal `<a>` clicks are intercepted for
120
- * client-side navigation. Set to `false` for MPA-style full-page navigations.
121
- */
122
- interceptLinks?: boolean;
123
- }
124
- interface MountOptions {
125
- hydrate?: boolean;
126
- registry?: Record<string, Island<any, any>>;
127
- /**
128
- * When `true` (default), internal `<a>` clicks are intercepted for
129
- * client-side navigation. Set to `false` for MPA-style full-page navigations.
130
- */
131
- interceptLinks?: boolean;
132
- }
133
- /** Response envelope returned by `renderResponse` — lets the host app handle redirects. */
134
- type RenderResponse = {
135
- kind: "html";
136
- html: string;
137
- status?: number;
138
- } | {
139
- kind: "redirect";
140
- to: string;
141
- status: number;
142
- } | {
143
- kind: "error";
144
- status: number;
145
- message: string;
146
- html: string;
147
- };
148
- interface RouterBuilder {
149
- /**
150
- * Register a route. The optional `loader` is the merged loader chain
151
- * (layout loaders outer→inner followed by the page loader) produced by
152
- * the FS-routing codegen.
153
- */
154
- route(pattern: string, island: Island<any, any>, loader?: Loader<any>): RouterBuilder;
155
- /**
156
- * Attach (or replace) a loader on an already-registered route pattern.
157
- * Used by the `ilha:loaders` virtual module to wire server-only loaders
158
- * onto the client-safe `pageRouter` at SSR time. No-op if the pattern
159
- * was never registered via `.route()`.
160
- */
161
- attachLoader(pattern: string, loader: Loader<any>): RouterBuilder;
162
- /**
163
- * Mark an already-registered route as having a server-side loader without
164
- * importing that loader into the client bundle. Used by FS-routing codegen
165
- * so SPA navigation knows to call the loader endpoint.
166
- */
167
- markLoader(pattern: string): RouterBuilder;
168
- /**
169
- * Return a snapshot of every registered route in match order. Useful for
170
- * prerenderers that need to discover the filesystem routes exposed by
171
- * `pageRouter` without reaching into router internals.
172
- */
173
- routes(): RouteRecord[];
174
- prime(): void;
175
- mount(target: string | Element, options?: MountOptions): () => void;
176
- render(url: string | URL): string;
177
- renderHydratable(url: string | URL, registry: Record<string, Island<any, any>>, options?: HydratableRenderOptions, request?: Request): Promise<string>;
178
- /**
179
- * Like `renderHydratable` but surfaces loader redirects and errors as
180
- * structured responses instead of baking them into HTML. Prefer this from
181
- * host server code so you can emit proper 302 / 4xx responses.
182
- */
183
- renderResponse(url: string | URL, registry: Record<string, Island<any, any>>, options?: HydratableRenderOptions, request?: Request): Promise<RenderResponse>;
184
- /**
185
- * Run the loader chain for a given URL without rendering. Used by the
186
- * `/__ilha/loader` endpoint the Vite plugin exposes for client-side
187
- * navigation. Returns the raw loader result, a redirect sentinel, or an
188
- * error sentinel.
189
- */
190
- runLoader(url: string | URL, request?: Request): Promise<{
191
- kind: "data";
192
- data: Record<string, unknown>;
193
- } | {
194
- kind: "redirect";
195
- to: string;
196
- status: number;
197
- } | {
198
- kind: "error";
199
- status: number;
200
- message: string;
201
- } | {
202
- kind: "not-found";
203
- }>;
204
- /**
205
- * Hydrate the application - combines prime(), mount(), and router.mount() into one call.
206
- * @param registry - The island registry from ilha:registry
207
- * @param options - Optional root element (defaults to document.body) and router target (defaults to root)
208
- * @returns Cleanup function
209
- */
210
- hydrate(registry: Record<string, Island<any, any>>, options?: HydrateOptions): () => void;
211
- /**
212
- * Hydrate islands on the current pre-rendered page without mounting a route
213
- * view or enabling client navigation. Intended for `static` mode: each page
214
- * is a self-contained HTML file; only interactive islands need activation.
215
- */
216
- hydrateStatic(registry: Record<string, Island<any, any>>, options?: {
217
- root?: Element;
218
- }): () => void;
219
- }
220
- /** Path of the loader endpoint served by the Vite plugin / production adapter. */
221
- declare const LOADER_ENDPOINT = "/__ilha/loader";
222
- /**
223
- * Prefetch loader data for a given path. Safe to call repeatedly — a single
224
- * inflight request is reused until it either resolves (and is consumed by
225
- * navigation) or is superseded by another prefetch.
226
- */
227
- declare function prefetch(pathWithSearch: string): void;
228
- declare const routePath: {
229
- (): string;
230
- (value: string): void;
231
- };
232
- declare const routeParams: {
233
- (): Record<string, string>;
234
- (value: Record<string, string>): void;
235
- };
236
- declare const routeSearch: {
237
- (): string;
238
- (value: string): void;
239
- };
240
- declare const routeHash: {
241
- (): string;
242
- (value: string): void;
243
- };
244
- declare function useRoute(): {
245
- path: {
246
- (): string;
247
- (value: string): void;
248
- };
249
- params: {
250
- (): Record<string, string>;
251
- (value: Record<string, string>): void;
252
- };
253
- search: {
254
- (): string;
255
- (value: string): void;
256
- };
257
- hash: {
258
- (): string;
259
- (value: string): void;
260
- };
261
- };
262
- /**
263
- * Prime route context signals from the current `location` so that islands
264
- * hydrated by `ilha.mount()` see the correct route values on their first
265
- * render — preventing a mismatch morph that would destroy hydrated bindings.
266
- */
267
- declare function prime(): void;
268
- declare function navigate(to: string, opts?: NavigateOptions): void;
269
- interface LinkInterceptionOptions {
270
- /**
271
- * Prefetch loader data on `mouseenter` for eligible links. Links opt in via
272
- * the `data-prefetch` attribute (set `data-prefetch="false"` to opt out a
273
- * specific link even when the framework is configured to prefetch by default).
274
- * Default: `true` — prefetches on hover for any link with `data-prefetch`.
275
- */
276
- prefetch?: boolean;
277
- }
278
- declare function enableLinkInterception(root?: Element | Document, options?: LinkInterceptionOptions): () => void;
279
- declare const RouterView: Island<Record<string, unknown>, Record<never, never>>;
280
- declare const RouterLink: Island<Record<string, unknown>, Omit<Omit<Record<never, never>, K> & Record<"href", string>, "label"> & Record<"label", string>>;
281
- declare function isActive(pattern: string): boolean;
282
- declare function router(options?: RouterOptions): RouterBuilder;
283
- declare const _default: {
284
- router: typeof router;
285
- navigate: typeof navigate;
286
- useRoute: typeof useRoute;
287
- isActive: typeof isActive;
288
- enableLinkInterception: typeof enableLinkInterception;
289
- prime: typeof prime;
290
- prefetch: typeof prefetch;
291
- RouterView: Island<Record<string, unknown>, Record<never, never>>;
292
- RouterLink: Island<Record<string, unknown>, Omit<Omit<Record<never, never>, K> & Record<"href", string>, "label"> & Record<"label", string>>;
293
- loader: typeof loader;
294
- redirect: typeof redirect;
295
- error: typeof error;
296
- composeLoaders: typeof composeLoaders;
297
- };
298
- //#endregion
299
- export { loader as A, useRoute as B, RouterView as C, enableLinkInterception as D, defineLayout as E, routeHash as F, setHistoryMode as G, wrapLayout as H, routeParams as I, routePath as L, prefetch as M, prime as N, error as O, redirect as P, routeSearch as R, RouterOptions as S, composeLoaders as T, HistoryMode as U, wrapError as V, getHistoryMode as W, RouteRecord as _, InferLoader as a, RouterLink as b, LinkInterceptionOptions as c, LoaderError as d, MergeLoaders as f, RenderResponse as g, Redirect as h, HydrateOptions as i, navigate as j, isActive as k, Loader as l, NavigateOptions as m, ErrorHandler as n, LOADER_ENDPOINT as o, MountOptions as p, HydratableRenderOptions as r, LayoutHandler as s, AppError as t, LoaderContext as u, RouteSnapshot as v, _default as w, RouterMode as x, RouterBuilder as y, router as z };
@@ -1,6 +0,0 @@
1
- import{watch as e}from"node:fs";import{basename as t,dirname as n,extname as r,join as i,relative as a,resolve as o,sep as s}from"node:path";import{createUnplugin as c}from"unplugin";import{mkdir as l,readFile as u,readdir as d,writeFile as f}from"node:fs/promises";function p(e){return e.replace(/\\/g,`/`)}const m=/\.(test|spec|d)\.(ts|tsx)$/,h=/^\s*export\s+(?:const|let|var|async\s+function|function)\s+load\b/m;async function g(e){try{let t=(await u(e,`utf8`)).replace(/^\s*\/\/.*$/gm,``);return h.test(t)}catch{return!1}}function _(e){return e.startsWith(`[...`)&&e.endsWith(`]`)?`**:${e.slice(4,-1)}`:e.startsWith(`[`)&&e.endsWith(`]`)?`:${e.slice(1,-1)}`:e}function v(e){return e.startsWith(`(`)&&e.endsWith(`)`)?``:_(e)}function y(e,t){let n=p(a(e,t)),i=n.slice(0,-r(n).length).split(`/`),o=[...i.slice(0,-1).map(v),_(i.at(-1))];return o.at(-1)===`index`&&o.pop(),`/`+o.filter(Boolean).join(`/`)||`/`}function b(e){return e===`/`?`index`:e.replace(/^\//,``).replace(/\*\*:[^/]*/g,e=>e.length>3?e.slice(3):`wildcard`).replace(/:/g,``).replace(/\*\*/g,`wildcard`).replace(/\//g,`-`).replace(/[^a-zA-Z0-9-]/g,``)||`page`}function x(e){return e===`/`?3:e.includes(`**`)?0:e.includes(`:`)?1:2}function S(e){return[...e].sort((e,t)=>{let n=x(t.pattern)-x(e.pattern);if(n!==0)return n;let r=t.pattern.split(`/`).length-e.pattern.split(`/`).length;return r===0?e.pattern.localeCompare(t.pattern):r})}function C(e,t,r,o){let s=p(a(e,n(t))),c=s===``?[]:s.split(`/`);return[e,...c.map((t,n)=>i(e,...c.slice(0,n+1)))].flatMap(e=>{let t=`${i(e,o)}.tsx`;if(r.has(t))return[t];let n=`${i(e,o)}.ts`;return r.has(n)?[n]:[]})}async function w(e,t=0){if(t>20)return console.warn(`[ilha:pages] Max scan depth (20) reached at ${e} — skipping`),[];let n=[];try{for(let r of await d(e,{withFileTypes:!0})){let a=i(e,r.name);r.isDirectory()?n.push(...await w(a,t+1)):r.isFile()&&/\.(ts|tsx)$/.test(r.name)&&!m.test(r.name)&&n.push(a)}}catch(e){if(e.code===`ENOENT`)return[];throw e}return n}async function T(e){let n=await w(e),r=new Set(n),i=n.filter(e=>!t(e).startsWith(`+`)),a=new Map,o=e=>{let t=a.get(e);return t||(t=g(e),a.set(e,t)),t};return Promise.all(i.map(async t=>{let n=y(e,t),i=C(e,t,r,`+layout`),a=C(e,t,r,`+error`),[s,...c]=await Promise.all([g(t),...i.map(o)]),l=i.filter((e,t)=>c[t]);return{file:t,pattern:n,name:b(n),layouts:i,errors:a,hasLoader:s,loaderLayouts:l}}))}function E(e,t){if(e.length===0){console.warn(`[ilha:pages] No pages found in ${t}`);return}let n=new Map,r=new Map;for(let t of e){let e=n.get(t.pattern);e?console.warn(`[ilha:pages] Duplicate route pattern "${t.pattern}"\n first: ${e}\n second: ${t.file}\n The first match wins — the second page will never be reached.`):n.set(t.pattern,t.file);let i=r.get(t.name);i?console.warn(`[ilha:pages] Registry name collision: "${t.name}" is used by both\n ${i}\n ${t.file}\n Hydration may not work correctly for one of these routes.`):r.set(t.name,t.file)}}function D(e){return{serverFile:i(e,`pages.server.ts`),clientFile:i(e,`pages.client.ts`),loadersFile:i(e,`loaders.ts`)}}async function O(e,t,n={}){let r=n.mode??`spa`,i=n.interceptLinks,a=r===`static`,o=S(await T(e));E(o,e),await l(t,{recursive:!0});let{serverFile:s,clientFile:c,loadersFile:u}=D(t),d=await M(s,k(o,s)),f=await M(c,A(o,c,{isStatic:a,interceptLinks:i}));a||await M(u,j(o,u,s)),(d||f)&&await N(t)}function k(e,t){let r=e=>{let r=p(a(n(t),e));return r.startsWith(`.`)?r:`./${r}`},i=[`import { router, wrapLayout, wrapError } from "@ilha/router";`,`import type { Island } from "ilha";`],o=[],s=[],c=[];for(let[t,n]of e.entries()){i.push(`import { default as _page${t} } from ${JSON.stringify(r(n.file))};`);for(let[e,a]of n.layouts.entries())i.push(`import { default as _layout${t}_${e} } from ${JSON.stringify(r(a))};`);for(let[e,a]of n.errors.entries())i.push(`import { default as _error${t}_${e} } from ${JSON.stringify(r(a))};`);let a=`_page${t}`;for(let e=n.errors.length-1;e>=0;e--)a=`wrapError(_error${t}_${e}, ${a})`;for(let e=n.layouts.length-1;e>=0;e--)a=`wrapLayout(_layout${t}_${e}, ${a})`;let l=`_wrapped${t}`;o.push(`const ${l} = ${a};`),s.push(` ${JSON.stringify(n.name)}: ${l}`+(t<e.length-1?`,`:``)),c.push(` .route(${JSON.stringify(n.pattern)}, ${l})`+(n.hasLoader||n.loaderLayouts.length>0?`.markLoader(${JSON.stringify(n.pattern)})`:``))}return[`// @generated by @ilha/router — do not edit`,`// Server module. Use for SSR and SSG/prerender.`,`// Import via: import { pageRouter, registry } from "ilha:pages/server";`,``,...i,``,...o,``,`export const registry: Record<string, Island<any, any>> = {`,...s,`};`,``,`export const pageRouter = router()`,...c,` ;`].join(`
2
- `)}function A(e,t,r){let{isStatic:i,interceptLinks:o}=r,s=e=>{let r=p(a(n(t),e));return r.startsWith(`.`)?r:`./${r}`},c=e=>`${s(e)}?client`,l=i?[`import { router as _router, wrapLayout, wrapError } from "@ilha/router";`,`import type { Island } from "ilha";`]:[`import { router, wrapLayout, wrapError } from "@ilha/router";`,`import type { Island } from "ilha";`],u=[],d=[],f=[];for(let[t,n]of e.entries()){l.push(`import { default as _page${t} } from ${JSON.stringify(c(n.file))};`);for(let[e,r]of n.layouts.entries())l.push(`import { default as _layout${t}_${e} } from ${JSON.stringify(c(r))};`);for(let[e,r]of n.errors.entries())l.push(`import { default as _error${t}_${e} } from ${JSON.stringify(c(r))};`);let r=`_page${t}`;for(let e=n.errors.length-1;e>=0;e--)r=`wrapError(_error${t}_${e}, ${r})`;for(let e=n.layouts.length-1;e>=0;e--)r=`wrapLayout(_layout${t}_${e}, ${r})`;let a=`_wrapped${t}`;u.push(`const ${a} = ${r};`),d.push(` ${JSON.stringify(n.name)}: ${a}`+(t<e.length-1?`,`:``)),i||f.push(` .route(${JSON.stringify(n.pattern)}, ${a})`+(n.hasLoader||n.loaderLayouts.length>0?`.markLoader(${JSON.stringify(n.pattern)})`:``))}let m=i?`_router({ mode: "static" })`:`router(${o===!1?`{ interceptLinks: false }`:``})`,h=[`// @generated by @ilha/router — do not edit`,`// Client module. Use for browser hydration.`,`// Import via: import { pageRouter, registry } from "ilha:pages/client";`,``,...l,``,...u,``,`export const registry: Record<string, Island<any, any>> = {`,...d,`};`,``];return i?h.push(`export const pageRouter = ${m};`):h.push(`export const pageRouter = ${m}`,...f,` ;`),h.join(`
3
- `)}function j(e,t,r){let i=e=>{let r=p(a(n(t),e));return r.startsWith(`.`)?r:`./${r}`},o=e.filter(e=>e.hasLoader||e.loaderLayouts.length>0);if(o.length===0)return[`// @generated by @ilha/router — do not edit`,`// This project has no loader exports; this file is intentionally empty.`,``,`export {};`,``].join(`
4
- `);let s=i(r).replace(/\.tsx?$/,``),c=[`import { pageRouter } from ${JSON.stringify(s)};`],l=!1,u=[];for(let[e,t]of o.entries()){let n=[];for(let[r,a]of t.loaderLayouts.entries()){let t=`_p${e}_l${r}`;c.push(`import { load as ${t} } from ${JSON.stringify(i(a))};`),n.push(t)}if(t.hasLoader){let r=`_p${e}`;c.push(`import { load as ${r} } from ${JSON.stringify(i(t.file))};`),n.push(r)}let r=n.length===1?n[0]:`composeLoaders([${n.join(`, `)}])`;n.length>1&&(l=!0),u.push(`pageRouter.attachLoader(${JSON.stringify(t.pattern)}, ${r});`)}return l&&c.unshift(`import { composeLoaders } from "@ilha/router";`),[`// @generated by @ilha/router — do not edit`,`// Server-only. Import this module from your SSR entry to wire loaders`,`// onto pageRouter. Importing it from the client is a no-op but wastes`,`// bundle size — rely on the default build pipeline to keep it out.`,``,...c,``,...u,``].join(`
5
- `)}async function M(e,t){try{if(await u(e,`utf8`)===t)return!1}catch{}return await f(e,t,`utf8`),!0}async function N(e){await M(i(e,`pages.d.ts`),[`// @generated by @ilha/router — do not edit`,``,`declare module "ilha:pages/server" {`,` import type { RouterBuilder } from "@ilha/router";`,` import type { Island } from "ilha";`,` export const pageRouter: RouterBuilder;`,` export const registry: Record<string, Island<any, any>>;`,`}`,``,`declare module "ilha:pages/client" {`,` import type { RouterBuilder } from "@ilha/router";`,` import type { Island } from "ilha";`,` export const pageRouter: RouterBuilder;`,` export const registry: Record<string, Island<any, any>>;`,`}`,``,`declare module "ilha:loaders" {`,` // Side-effect-only module. Importing it attaches loaders to pageRouter.`,`}`,``].join(`
6
- `))}const P=`\0ilha:pages/server`,F=`\0ilha:pages/client`,I=`\0ilha:loaders`,L=[P,F,I];function R(e,t){let n=o(e,t.dir??`src/pages`),r=o(e,t.outDir??`.ilha`),{serverFile:i,clientFile:a,loadersFile:s}=D(r);return{pagesDir:n,outDir:r,serverFile:i,clientFile:a,loadersFile:s}}function z(e){let n,r,i,a,o,c=t=>{({pagesDir:n,outDir:r,serverFile:i,clientFile:a,loadersFile:o}=R(t,e))},l=async()=>{try{await O(n,r,{mode:e.mode,interceptLinks:e.interceptLinks})}catch(e){console.error(`[ilha:pages] codegen failed:`,e)}},u=e=>e===n||e.startsWith(n+s);return{get pagesDir(){return n},get outDir(){return r},get serverFile(){return i},get clientFile(){return a},get loadersFile(){return o},setPaths:c,regen:l,shouldRegenOnChange:e=>{if(!u(e))return!1;let n=t(e);return n.startsWith(`+`)||/\.(ts|tsx)$/.test(n)},isUnderPagesDir:u}}async function B(e,t,n){n(t)&&await e.regen()}function V(e,t,n){if(t===`ilha:pages/server`)return P;if(t===`ilha:pages/client`)return F;if(t===`ilha:loaders`)return I;if(t.endsWith(`?client`)){let e=t.slice(0,-7);return(n?o(n.replace(/\?.*$/,``),`..`,e):o(e))+`?client`}}function H(e,t){if(t===`\0ilha:pages/server`){let t=e.serverFile.replace(/\.tsx?$/,``);return`export { pageRouter, registry } from ${JSON.stringify(t)};`}if(t===`\0ilha:pages/client`){let t=e.clientFile.replace(/\.tsx?$/,``);return`export { pageRouter, registry } from ${JSON.stringify(t)};`}if(t===`\0ilha:loaders`){let t=e.loadersFile.replace(/\.tsx?$/,``);return`import ${JSON.stringify(t)};`}if(t.endsWith(`?client`)){let e=t.slice(0,-7);return`export { default } from ${JSON.stringify(e)};`}}function U(e,t){return async n=>{e.isUnderPagesDir(n)&&(await e.regen(),await t())}}function W(t,n){let r=e(t.pagesDir,{recursive:!0},(e,r)=>{r&&n(i(t.pagesDir,r))});return()=>r.close()}const G=c((e={})=>{let t=z(e);return{name:`ilha:pages`,async buildStart(){t.pagesDir||t.setPaths(process.cwd()),this.addWatchFile?.(t.pagesDir),await t.regen()},async watchChange(e){await B(t,e,e=>t.shouldRegenOnChange(e))},resolveId(e,n){return V(t,e,n)},load(e){return H(t,e)},vite:{configResolved(e){t.setPaths(e.root)},configureServer(e){e.watcher.add(t.pagesDir);let n=U(t,async()=>{for(let t of L){let n=e.moduleGraph.getModuleById(t);n&&e.moduleGraph.invalidateModule(n)}e.hot.send({type:`full-reload`})});e.watcher.on(`add`,n),e.watcher.on(`addDir`,n),e.watcher.on(`unlink`,n),e.watcher.on(`change`,async e=>{t.shouldRegenOnChange(e)&&await n(e)})}},rspack(e){t.setPaths(e.options.context??process.cwd());let n=U(t,()=>{e.watching&&e.invalidate()}),r;e.hooks.watchRun.tap(`ilha:pages`,()=>{r?.(),r=W(t,n)}),e.hooks.shutdown.tap(`ilha:pages`,()=>r?.())}}});export{G as t};
@@ -1,28 +0,0 @@
1
- import * as _$unplugin from "unplugin";
2
-
3
- //#region src/codegen.d.ts
4
- type PagesMode = "spa" | "static";
5
- //#endregion
6
- //#region src/plugin.d.ts
7
- interface IlhaPagesOptions {
8
- /** Directory containing page files. Default: `src/pages` */
9
- dir?: string;
10
- /** Output directory for generated files. Default: `.ilha` */
11
- outDir?: string;
12
- /**
13
- * File-system router navigation mode.
14
- * - `spa` — full client route graph with SSR/hydration and client navigation.
15
- * - `static` — island registry only; no route graph bundled into the client.
16
- * Default: `spa`.
17
- */
18
- mode?: PagesMode;
19
- /**
20
- * When `false`, internal `<a>` clicks are not intercepted — browser performs
21
- * full document navigations. Only meaningful in `spa` mode.
22
- * Default: `true`.
23
- */
24
- interceptLinks?: boolean;
25
- }
26
- declare const ilhaPages: _$unplugin.UnpluginInstance<IlhaPagesOptions | undefined, boolean>;
27
- //#endregion
28
- export { ilhaPages as n, IlhaPagesOptions as t };
@@ -1,3 +0,0 @@
1
- import e,{ISLAND_MOUNT_INTERNAL as t,context as n,html as r,mount as i}from"ilha";import{addRoute as a,createRouter as o,findRoute as s}from"rou3";const c=typeof window<`u`&&typeof document<`u`,l={readLocation(){return c?{pathname:location.pathname,search:location.search,hash:location.hash}:{pathname:`/`,search:``,hash:``}},push(e){c&&history.pushState(null,``,e)},replace(e){c&&history.replaceState(null,``,e)},onChange(e){return c?(window.addEventListener(`popstate`,e),()=>window.removeEventListener(`popstate`,e)):()=>{}},toLinkHref(e){return e},extractLogicalPath(e){let t=e.getAttribute(`href`);return!t||e.protocol&&!/^(http:|https:)$/.test(e.protocol)||t.startsWith(`#`)||e.hostname&&(e.hostname!==location.hostname||e.protocol!==location.protocol)?null:e.pathname+e.search+e.hash}};function u(e){let t=e.startsWith(`#`)?e.slice(1):e,n=t===``?`/`:t.startsWith(`/`)?t:`/`+t,r=new URL(n,`http://_`);return{pathname:r.pathname,search:r.search,hash:r.hash}}const d={readLocation(){return c?u(location.hash):{pathname:`/`,search:``,hash:``}},push(e){c&&history.pushState(null,``,e.startsWith(`#`)?e:`#`+e)},replace(e){c&&history.replaceState(null,``,e.startsWith(`#`)?e:`#`+e)},onChange(e){return c?(window.addEventListener(`popstate`,e),window.addEventListener(`hashchange`,e),()=>{window.removeEventListener(`popstate`,e),window.removeEventListener(`hashchange`,e)}):()=>{}},toLinkHref(e){return e.startsWith(`#`)?e:`#`+e},extractLogicalPath(e){let t=e.getAttribute(`href`);if(!t||e.protocol&&!/^(http:|https:)$/.test(e.protocol))return null;if(t.startsWith(`#`)){let e=t.slice(1);return e===``||!e.startsWith(`/`)?null:e}if(/^https?:\/\//i.test(t))try{let e=new URL(t);if(e.origin!==location.origin||!e.hash||e.hash===`#`)return null;let n=e.hash.slice(1);return n.startsWith(`/`)?n:null}catch{return null}return t}};let f=`history`,p=l;function m(e){f=e,p=e===`hash`?d:l}function h(){return f}function g(){return p}const _=typeof window<`u`&&typeof document<`u`;function v(e){return e}var y=class{__ilhaRedirect=!0;to;status;constructor(e,t=302){this.to=e,this.status=t}},b=class{__ilhaLoaderError=!0;status;message;constructor(e,t){this.status=e,this.message=t}};function x(e,t=302){throw new y(e,t)}function S(e,t){throw new b(e,t)}function C(e){return e.length===0?async()=>({}):e.length===1?e[0]:async t=>{let n=await Promise.all(e.map(e=>e(t)));return Object.assign({},...n)}}const w=Symbol.for(`ilha.router.wrapLayout.leaf`),T=Symbol.for(`ilha.router.wrapLayout.handler`);function E(e){let t=e.match(/^<([a-zA-Z][\w-]*)\s[^>]*>([\s\S]*)<\/\1>\s*$/);return t?t[2]:e}function ee(e){let t=e.match(/^<([a-zA-Z][\w-]*)\s([^>]*)>/);return t?{tag:t[1],attrs:t[2]}:null}const te=/<(pre|script|style|textarea)\b/i;function ne(e,t,n){let r=RegExp(`<${n}\\b`,`gi`),i=RegExp(`</${n}>`,`gi`),a=1,o=t;for(;a>0&&o<e.length;){r.lastIndex=o,i.lastIndex=o;let t=r.exec(e),n=i.exec(e);if(!n)return null;if(t&&t.index<n.index)a+=1,o=t.index+t[0].length;else{if(--a,a===0)return n.index+n[0].length;o=n.index+n[0].length}}return o}function re(e,t){let n=t;for(;n<e.length;){if(e.startsWith(`<!--`,n)){let t=e.indexOf(`-->`,n);if(t===-1)return null;n=t+3;continue}let t=e.slice(n).match(te);if(t&&t.index!=null&&t.index>=0){let r=n+t.index,i=!1,a=n;for(;a<r;){let t=e.indexOf(`<!--`,a);if(t===-1||t>=r)break;let o=e.indexOf(`-->`,t);if(o===-1)return null;if(r<o+3){n=o+3,i=!0;break}a=o+3}if(i)continue;let o=e.indexOf(`<div`,n),s=e.indexOf(`</div>`,n);if(!(o!==-1&&o<r||s!==-1&&s<r)){let i=t[1].toLowerCase(),a=ne(e,r+t[0].length,i);if(a===null)return null;n=a;continue}}let r=e.indexOf(`<div`,n),i=e.indexOf(`</div>`,n);return i===-1&&r===-1?null:r===-1||i!==-1&&i<r?{kind:`close`,index:i}:{kind:`open`,index:r}}return null}function ie(e){let t=[];for(let n of e.matchAll(/<div\s[^>]*data-ilha-slot="k:page"[^>]*>/g)){let r=n.index+n[0].length,i=1,a=r;for(;i>0;){let n=re(e,a);if(!n)break;if(n.kind===`open`)i+=1,a=n.index+4;else{if(--i,i===0){t.push({openEnd:r,closeStart:n.index});break}a=n.index+6}}}return t}function D(e,t,n){let r=ie(e);if(r.length===0)return e;let i=n===`innermost`?r[r.length-1]:r[0];return e.slice(0,i.openEnd)+t+e.slice(i.closeStart)}function O(e,t){let n=e[T];if(!n)return e.toString(t);let r=e[w]??e;return n(Object.assign(r.key(`page`),{toString:()=>``})).toString(t)}async function ae(e,t,n,r){let i=E(await t.hydratable(n,r));return D(D(O(e,n),``,`innermost`),i,`innermost`)}function oe(e,n){let r=n[w]??n,i=r===n?null:n,a=e(Object.assign(n.key(`page`),{toString:n.toString.bind(n)}));a[w]=r,a[T]=e;function o(e){let t=[...e.querySelectorAll(`[data-ilha-slot="k:page"]`)].filter(t=>{let n=t.closest(`[data-ilha]`);return n===null||n===e});return t.length===0?e:t[t.length-1]}function s(e,t){if(t.hasAttribute(`data-ilha-state`))return;let n=e.getAttribute(`data-ilha-state`);if(n)try{let e=JSON.parse(n);delete e._skipOnMount,t.setAttribute(`data-ilha-state`,JSON.stringify(e))}catch{}}let c=a.mount.bind(a),l=a[t];function u(e){s(e,o(e)),e.removeAttribute(`data-ilha-state`)}return a.mount=(e,t)=>(u(e),c(e,t)),a[t]=(e,t)=>(u(e),typeof l==`function`?l(e,t):{unmount:c(e,t),updateProps:()=>{}}),a.hydratable=async(e,t)=>{if(!t?.name)throw Error(`wrapLayout: hydratable requires options.name`);let n=e??{},o=await r.hydratable(n,t),s=ee(o);if(!s)return o;let c=E(o);i&&(c=await ae(i,r,n,t));let l=D(D(O(a,n),``,`first`),c,`first`);return`<${s.tag} ${s.attrs}>${l}</${s.tag}>`},a}function se(n,r){let i=e.render(()=>{try{return r.toString()}catch(e){let t={path:P(),params:F(),search:I(),hash:L()};return n({message:e.message,status:e.status,stack:e.stack},t).toString()}});return i.mount=(e,t)=>{try{return r.mount(e,t)}catch(r){let i={path:P(),params:F(),search:I(),hash:L()},a=n({message:r.message,status:r.status,stack:r.stack},i);return e.innerHTML=a.toString(),a.mount(e,t)}},i[t]=(e,i)=>{try{let n=r[t];return typeof n==`function`?n(e,i):{unmount:r.mount(e,i),updateProps:()=>{}}}catch(t){let r={path:P(),params:F(),search:I(),hash:L()},a=n({message:t.message,status:t.status,stack:t.stack},r);return e.innerHTML=a.toString(),{unmount:a.mount(e,i),updateProps:()=>{}}}},i.hydratable=async(e,t)=>{if(!t?.name)throw Error(`wrapError: hydratable requires options.name`);return r.hydratable(e??{},t)},i}function ce(e){return e}function k(e){let t=new Map;for(let[n,r]of Object.entries(e))t.has(r)||t.set(r,n);return t}const A=`/__ilha/loader`,j=new Map;async function M(e,t){let n=j.get(e);if(n){j.delete(e);try{return await n}catch{}}let r=`${A}?path=${encodeURIComponent(e)}`;try{let e=await fetch(r,{signal:t,headers:{accept:`application/json`}});if(!e.ok){try{let t=await e.json();if(t&&typeof t==`object`&&`kind`in t)return t}catch{}return{kind:`error`,status:e.status,message:e.statusText}}return await e.json()}catch(e){if(e?.name===`AbortError`)throw e;return{kind:`error`,status:0,message:e?.message??`network error`}}}function N(e){if(!_||j.has(e))return;let t=e.split(`?`)[0]??``;if(!s(V,`GET`,t)?.data?.hasLoader)return;let n=M(e).catch(e=>({kind:`error`,status:0,message:e?.message??`prefetch failed`}));j.set(e,n)}async function le(e,t,n,r,i,a){if(!e)return t.innerHTML=`<div data-router-empty></div>`,()=>{};let o=!!s(V,`GET`,n.split(`?`)[0]??``)?.data?.hasLoader,c={},l=o?await M(n,r):{kind:`data`,data:{}};if(l.kind===`redirect`)return J(l.to,{replace:!0}),()=>{};if(l.kind===`error`){let e=String(l.message).replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`);return t.innerHTML=`<div data-router-view data-router-error="${l.status}">${e}</div>`,()=>{}}if(l.kind===`not-found`)return t.innerHTML=`<div data-router-empty></div>`,()=>{};if(c=l.data,!i)return console.warn(`[ilha-router] No registry provided for client-side navigation. Island will not be interactive.`),t.innerHTML=`<div data-router-view>${e.toString(c)}</div>`,()=>{};let u=a?.get(e)??Object.entries(i).find(([,t])=>t===e)?.[0];if(!u)return console.warn(`[ilha-router] Island not found in registry for client-side navigation.`),t.innerHTML=`<div data-router-view>${e.toString(c)}</div>`,()=>{};t.innerHTML=`<div data-router-view>${await e.hydratable(c,{name:u,as:`div`,snapshot:!0})}</div>`;let d=t.querySelector(`[data-ilha="${u}"]`);return d?e.mount(d):()=>{}}const P=n(`router.path`,``),F=n(`router.params`,{}),I=n(`router.search`,``),L=n(`router.hash`,``);function R(){return{path:P,params:F,search:I,hash:L}}const z=n(`router.active`,null);let B=[],V=o(),H=new Map,U=new Map;function W(e){let t={};if(e)for(let[n,r]of Object.entries(e))t[n]=decodeURIComponent(r);return t}function G(e){let t=typeof e==`string`?new URL(e,`http://localhost`):e,n=s(V,`GET`,t.pathname);P(t.pathname),F(W(n?.params)),I(t.search),L(t.hash),z(n?.data?.island??null)}function K(){let e=g().readLocation(),t=s(V,`GET`,e.pathname);P(e.pathname),F(W(t?.params)),I(e.search),L(e.hash),z(t?.data?.island??null)}function q(){_&&K()}function J(e,t={}){if(!_)return;let n=g(),r=n.readLocation();e!==r.pathname+r.search+r.hash&&(t.replace?n.replace(e):n.push(e),K())}function Y(e=document,t={}){if(!_)return()=>{};let n=t.prefetch!==!1;function r(e,t){let n=e.getAttribute(`target`)===`_blank`,r=!!t&&(t.ctrlKey||t.metaKey||t.shiftKey),i=e.hasAttribute(`data-no-intercept`);return n||r||i?null:g().extractLogicalPath(e)}let i=e=>{if(e.defaultPrevented)return;let t=e.target.closest(`a`);if(!t)return;let n=r(t,e);n!==null&&(e.preventDefault(),J(n))},a=e=>{let t=e.target.closest(`a`);if(!t)return;let n=t.getAttribute(`data-prefetch`);if(n===null||n===`false`)return;let i=r(t);i!==null&&N(i.split(`#`)[0]??i)};return e.addEventListener(`click`,i),n&&e.addEventListener(`mouseover`,a,{passive:!0}),()=>{e.removeEventListener(`click`,i),n&&e.removeEventListener(`mouseover`,a)}}const X=e.render(()=>{let e=z();return e?`<div data-router-view>${e.toString()}</div>`:`<div data-router-empty></div>`}),Z=e.state(`href`,``).state(`label`,``).on(`[data-link]@click`,({state:e,event:t})=>{t.preventDefault(),J(e.href())}).on(`[data-link]@mouseenter`,({state:e})=>{let t=e.href();if(t){if(/^https?:\/\//i.test(t))try{let e=new URL(t);if(e.origin!==location.origin)return;N(e.pathname+e.search);return}catch{return}N(t)}}).render(({state:e})=>r`<a data-link data-prefetch href="${()=>g().toLinkHref(e.href())}"
2
- >${e.label}</a
3
- >`);function Q(e){let t=s(V,`GET`,P());return t?H.get(t.data.island)===e:!1}function ue(e){return typeof e==`string`?new URL(e,`http://localhost`):e}function de(e){try{return new Request(e.toString())}catch{return{url:e.toString(),headers:new Headers}}}async function fe(e,t,n,r,i){try{return{kind:`data`,data:await e({params:n,request:r,url:t,signal:i})??{}}}catch(e){return e instanceof y?{kind:`redirect`,to:e.to,status:e.status}:e instanceof b?{kind:`error`,status:e.status,message:e.message}:{kind:`error`,status:e?.status??500,message:e?.message??`Loader failed`}}}function $(t={}){let n=t.mode??`spa`,r=t.interceptLinks!==!1;B=[],V=o(),H=new Map,U=new Map;let c=null,l=null,u={route(e,t,n){let r=!!n,i={island:t,loader:n,hasLoader:r};return B.push({pattern:e,island:t,loader:n,hasLoader:r}),a(V,`GET`,e,i),U.set(e,i),H.has(t)||H.set(t,e),u},attachLoader(e,t){let n=U.get(e);if(!n)return console.warn(`[ilha-router] attachLoader("${e}", …): pattern was never registered via .route(). The loader will be ignored.`),u;n.loader=t,n.hasLoader=!0;let r=B.find(t=>t.pattern===e);return r&&(r.loader=t,r.hasLoader=!0),u},markLoader(e){let t=U.get(e);if(!t)return console.warn(`[ilha-router] markLoader("${e}"): pattern was never registered via .route(). The loader marker will be ignored.`),u;t.hasLoader=!0;let n=B.find(t=>t.pattern===e);return n&&(n.hasLoader=!0),u},routes(){return B.map(e=>({...e}))},prime:q,hydrateStatic(e,t={}){if(!_)return()=>{};let n=t.root??document.body;q();let{unmount:r}=i(e,{root:n});return r},mount(t,{hydrate:i=!1,registry:a,interceptLinks:o}={}){if(!_)return console.warn(`[ilha-router] mount() called in a non-browser environment`),()=>{};let u=typeof t==`string`?document.querySelector(t):t;if(!u)return console.warn(`[ilha-router] No element found for selector "${t}"`),()=>{};if(K(),n===`static`)return console.warn(`[ilha-router] router.mount() called in static mode. Use router.hydrateStatic(registry) instead.`),()=>{};c=g().onChange(()=>K()),l=o??r?Y(document):null;let d=null,f=null;if(i){h()===`hash`&&console.warn("[ilha-router] mount({ hydrate: true }) was called in hash mode. SSR + hydration assumes the server can render the active route, but in hash mode the server only ever sees the document URL. Use plain SPA mode (`mount(target)` without `hydrate: true`) for hash-mode apps.");let t=u.querySelector(`[data-router-view]`)??u,n=z(),r=a?k(a):void 0,i=0,o=e.render(()=>{let e=z();if(e!==n){let o=++i;f?.abort(),f=new AbortController;let s=f.signal;queueMicrotask(async()=>{if(o===i){d?.();try{let n=g().readLocation();d=await le(e,t,n.pathname+n.search,s,a,r)}catch(e){if(e?.name===`AbortError`)return;throw e}n=e}})}return``}),s=document.createElement(`div`);s.style.display=`none`,u.appendChild(s);let p=o.mount(s);return()=>{++i,f?.abort(),p(),s.remove(),d?.(),c?.(),l?.(),c=null,l=null}}let p=null,m=null,v=0;d=X.mount(u);async function y(e,t){if(p?.(),p=null,m=e,!e)return;let n=u?.querySelector(`[data-router-view]`);if(!n)return;let r=g().readLocation(),i=s(V,`GET`,r.pathname)?.data?.hasLoader?await M(r.pathname+r.search,t):{kind:`data`,data:{}};if(t.aborted)return;if(i.kind===`redirect`){J(i.to,{replace:!0});return}let a=i.kind===`data`?i.data:{};n.innerHTML=e.toString(a),p=e.mount(n,a)}f=new AbortController,y(z(),f.signal);let b=e.render(()=>{let e=z();if(e!==m){let t=++v;f?.abort(),f=new AbortController;let n=f.signal;queueMicrotask(()=>{t===v&&y(e,n)})}return``}),x=document.createElement(`div`);x.style.display=`none`,u.appendChild(x);let S=b.mount(x);return()=>{++v,f?.abort(),p?.(),S(),x.remove(),d?.(),c?.(),l?.(),c=null,l=null}},render(e){return G(e),X.toString()},async renderHydratable(e,t,n={},r){let i=await this.renderResponse(e,t,n,r);return i.kind===`html`||i.kind===`error`?i.html:`<meta http-equiv="refresh" content="0; url=${i.to}">`},async renderResponse(e,t,n={},r){let i=ue(e);G(i);let a=s(V,`GET`,i.pathname),o=a?.data?.island??null;if(!o)return{kind:`html`,html:`<div data-router-empty></div>`,status:404};let c={};if(a?.data?.loader){let e=r??de(i),t=new AbortController,n=await fe(a.data.loader,i,F(),e,t.signal);if(n.kind===`redirect`)return{kind:`redirect`,to:n.to,status:n.status};if(n.kind===`error`){let e=String(n.message).replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`),t=`<div data-router-view data-router-error="${n.status}">${e}</div>`;return{kind:`error`,status:n.status,message:n.message,html:t}}c=n.data}let l=k(t).get(o);return l?{kind:`html`,html:`<div data-router-view>${await o.hydratable(c,{name:l,as:`div`,snapshot:!0,...n})}</div>`}:(console.warn(`[ilha-router] renderHydratable: active island for "${P()}" is not in the registry. Falling back to plain SSR — the island will not be interactive on the client.`),{kind:`html`,html:`<div data-router-view>${o.toString(c)}</div>`})},async runLoader(e,t){let n=ue(e),r=s(V,`GET`,n.pathname);if(!r?.data?.island)return{kind:`not-found`};if(!r.data.loader)return{kind:`data`,data:{}};let i=W(r.params),a=t??de(n),o=new AbortController;return fe(r.data.loader,n,i,a,o.signal)},hydrate(e,t={}){if(!_)return console.warn(`[ilha-router] hydrate() called in a non-browser environment`),()=>{};let n=t.root??document.body,r=t.target??n;q();let{unmount:a}=i(e,{root:n}),o=this.mount(r,{hydrate:!0,registry:e,interceptLinks:t.interceptLinks});return()=>{a(),o()}}};return u}var pe={router:$,navigate:J,useRoute:R,isActive:Q,enableLinkInterception:Y,prime:q,prefetch:N,RouterView:X,RouterLink:Z,loader:v,redirect:x,error:S,composeLoaders:C};export{se as C,m as E,R as S,h as T,F as _,X as a,$ as b,Y as c,v as d,J as f,L as g,x as h,Z as i,S as l,q as m,b as n,C as o,N as p,y as r,ce as s,A as t,Q as u,P as v,oe as w,pe as x,I as y};