@ilha/router 0.6.9 → 0.8.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/README.md +98 -8
- package/dist/codegen.d.ts +5 -0
- package/dist/hash.d.ts +4 -4
- package/dist/index.d.ts +94 -33
- package/dist/index.js +1 -1
- package/dist/plugin-BW2tnuyF.js +8 -0
- package/dist/plugin.d.ts +8 -1
- package/dist/rolldown.d.ts +1 -1
- package/dist/rolldown.js +1 -1
- package/dist/rspack.js +1 -1
- package/dist/src-8LBo_Ieg.js +4 -0
- package/dist/ssr.d.ts +1 -0
- package/dist/ssr.js +9 -9
- package/dist/vite.js +1 -1
- package/package.json +1 -1
- package/dist/plugin-C07ziXtR.js +0 -6
- package/dist/src-B5twcrhj.js +0 -4
package/README.md
CHANGED
|
@@ -135,10 +135,19 @@ You can still register loaders, but they run on the client (via the loader endpo
|
|
|
135
135
|
|
|
136
136
|
## Core API
|
|
137
137
|
|
|
138
|
-
### `router()`
|
|
138
|
+
### `router(options?)`
|
|
139
139
|
|
|
140
140
|
Creates a new router instance and **resets the route registry**. Always call `router()` fresh — never share instances across server requests.
|
|
141
141
|
|
|
142
|
+
| Option | Type | Default | Description |
|
|
143
|
+
| ------------------------ | ------------------- | ------- | ----------------------------------------------------------------------------------------- |
|
|
144
|
+
| `mode` | `"spa" \| "static"` | `"spa"` | `"static"` disables client navigation — hydrate with `hydrateStatic()` |
|
|
145
|
+
| `interceptLinks` | `boolean` | `true` | Intercept internal `<a>` clicks for SPA navigation |
|
|
146
|
+
| `notFound` | `Island` | — | Custom 404 island (SSR status 404; mounted with a full lifecycle in the browser) |
|
|
147
|
+
| `allowExternalRedirects` | `boolean` | `false` | Allow loader `redirect()` to cross-origin URLs; blocked targets become a 500 error |
|
|
148
|
+
| `loaderTimeout` | `number` | — | Abort + fail a loader after this many ms (enforced even if the loader ignores its signal) |
|
|
149
|
+
| `viewTransitions` | `boolean` | `false` | Wrap client view swaps in `document.startViewTransition()` when supported |
|
|
150
|
+
|
|
142
151
|
Returns a `RouterBuilder`.
|
|
143
152
|
|
|
144
153
|
---
|
|
@@ -147,7 +156,9 @@ Returns a `RouterBuilder`.
|
|
|
147
156
|
|
|
148
157
|
Registers a route. Patterns are matched in **declaration order** — first match wins. Uses [rou3](https://github.com/h3js/rou3) for matching, the same engine as Nitro.
|
|
149
158
|
|
|
150
|
-
The optional `loader` is a data-fetching function that runs before the page renders. Its return value is passed as input props to the island.
|
|
159
|
+
The optional `loader` is a data-fetching function that runs before the page renders. Its return value is passed as input props to the island. The loader runs **wherever the router runs**: during SSR it executes on the server; when the route was registered in the browser (a plain SPA, hash mode, `file://`), client navigations execute it locally — no server or `/__ilha/loader` endpoint needed. Routes marked via `.markLoader()` (the SSR-split pages build) still fetch from the endpoint.
|
|
160
|
+
|
|
161
|
+
A locally-executed loader receives a synthetic `Request` (no cookies or server context) — rely on `url`, `params`, and `signal`. Loader `redirect()`s are checked against the same cross-origin policy as on the server (`allowExternalRedirects`).
|
|
151
162
|
|
|
152
163
|
```ts
|
|
153
164
|
import { loader } from "@ilha/router";
|
|
@@ -344,6 +355,35 @@ router().route("/user/:id", userPage).attachLoader("/user/:id", serverLoader);
|
|
|
344
355
|
|
|
345
356
|
---
|
|
346
357
|
|
|
358
|
+
#### `.clientLoader(pattern, loader)` — runtime
|
|
359
|
+
|
|
360
|
+
Attaches a loader that runs **in the browser** on client navigations, instead of fetching from the `/__ilha/loader` endpoint. Used by the FS-routing codegen for `clientLoad` exports; also available for manual routers. When a route has both a server loader and a client loader, the client loader wins on client navigations and the server loader runs during SSR. No-op (with a warning) if the pattern was never registered via `.route()`.
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
router()
|
|
364
|
+
.route("/dashboard", dashboardPage)
|
|
365
|
+
.clientLoader(
|
|
366
|
+
"/dashboard",
|
|
367
|
+
loader(async ({ signal }) => ({ stats: await fetchStats({ signal }) })),
|
|
368
|
+
);
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
#### `.errorBoundary(pattern, handler)` — runtime
|
|
374
|
+
|
|
375
|
+
Attaches the route's `+error` boundary so **loader** errors render through it — on the server (`renderResponse` returns the boundary's HTML with the error status) and on client navigations. Render errors are already handled by `wrapError` inside the island; this closes the gap for errors thrown before rendering starts. The FS-routing codegen wires the nearest `+error.ts` automatically; manual routers can call it directly. A throwing boundary falls back to the minimal inline error.
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
router()
|
|
379
|
+
.route("/user/:id", userPage, userLoader)
|
|
380
|
+
.errorBoundary("/user/:id", (err, route) =>
|
|
381
|
+
ilha.render(() => `<h1>${err.status ?? 500}</h1><p>${err.message}</p>`),
|
|
382
|
+
);
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
347
387
|
### `setHistoryMode(mode)` · `getHistoryMode()`
|
|
348
388
|
|
|
349
389
|
Selects the history strategy used by the router. Defaults to `"history"` (HTML5 History API, reads/writes `location.pathname`). Set to `"hash"` to store the route in `location.hash` instead — see the [Hash mode](#hash-mode) section above for when to use it.
|
|
@@ -376,6 +416,31 @@ No-op on the server.
|
|
|
376
416
|
|
|
377
417
|
---
|
|
378
418
|
|
|
419
|
+
### `navigating()`
|
|
420
|
+
|
|
421
|
+
Reactive — `true` while a client navigation (loader fetch + view swap) is in flight. Read it inside any island render to drive a progress bar or spinner; it re-renders when the state flips. Also available as `useRoute().navigating`.
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
import { navigating } from "@ilha/router";
|
|
425
|
+
|
|
426
|
+
const Spinner = ilha.render(() => (navigating() ? `<div class="bar" />` : ""));
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
### `invalidate()`
|
|
432
|
+
|
|
433
|
+
Re-runs the current route's loader and re-renders the view with fresh data — call it after a mutation. Resolves when the view has updated. No-op on the server or when no router is mounted.
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
import { invalidate } from "@ilha/router";
|
|
437
|
+
|
|
438
|
+
await api.deleteItem(id);
|
|
439
|
+
await invalidate(); // current page refetches and re-renders
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
379
444
|
### `prime()`
|
|
380
445
|
|
|
381
446
|
Standalone export of the same signal-priming function available as `.prime()` on the builder. Useful when managing the priming step separately from the router instance.
|
|
@@ -894,6 +959,31 @@ export default defineLayout((children) => /* … */);
|
|
|
894
959
|
|
|
895
960
|
Layout loaders are composed automatically — you do not need to call `composeLoaders()` manually.
|
|
896
961
|
|
|
962
|
+
### Client loaders (`clientLoad`)
|
|
963
|
+
|
|
964
|
+
A page or layout can export a `clientLoad` function that runs **in the browser** on client navigations, instead of fetching from the loader endpoint. Use it for data that is fetchable from the client anyway (public APIs, the app's own REST endpoints) — it saves a server round-trip per navigation, and it works on static hosts with no loader endpoint at all.
|
|
965
|
+
|
|
966
|
+
```ts
|
|
967
|
+
// src/pages/dashboard.ts
|
|
968
|
+
import { loader } from "@ilha/router";
|
|
969
|
+
import ilha from "ilha";
|
|
970
|
+
|
|
971
|
+
export const clientLoad = loader(async ({ signal }) => {
|
|
972
|
+
const stats = await fetch("/api/stats", { signal }).then((r) => r.json());
|
|
973
|
+
return { stats };
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
export default ilha.input<{ stats: Stats }>().render(/* … */);
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
Rules and caveats:
|
|
980
|
+
|
|
981
|
+
- `clientLoad` is bundled into the client — never put secrets, database clients, or server-only imports in it. Keep those in `load`, which stays server-only.
|
|
982
|
+
- A page can export **both**: `load` runs during SSR (first paint), `clientLoad` runs on client navigations instead of the endpoint fetch. Make them return the same shape.
|
|
983
|
+
- Layout `clientLoad`s compose with the page's, layouts first — the page wins on key collision, mirroring server loaders.
|
|
984
|
+
- With SSR + hydration, a `clientLoad`-only page is server-rendered **without** its data; the router runs `clientLoad` on the client right after hydration and re-renders the route with the loaded props.
|
|
985
|
+
- `clientLoad` receives a synthetic `Request` — rely on `url`, `params`, and `signal`, not cookies or headers.
|
|
986
|
+
|
|
897
987
|
### Error boundaries
|
|
898
988
|
|
|
899
989
|
A `+error.ts` catches any error thrown during rendering of pages in its directory and all subdirectories. The nearest boundary wins. If an inner boundary re-throws, the next outer boundary takes over.
|
|
@@ -1007,15 +1097,15 @@ Or use the one-liner: `pageRouter.hydrate(registry)`.
|
|
|
1007
1097
|
|
|
1008
1098
|
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.
|
|
1009
1099
|
|
|
1010
|
-
On the **client**, navigations
|
|
1100
|
+
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 Nitro adapter (production).
|
|
1011
1101
|
|
|
1012
1102
|
```
|
|
1013
1103
|
server client (navigation)
|
|
1014
|
-
────────────────────────────
|
|
1015
|
-
renderHydratable
|
|
1016
|
-
→ executeLoader(…) →
|
|
1017
|
-
→ island.hydratable(props) → fetchLoaderData(
|
|
1018
|
-
→ data-ilha-props="{…}"
|
|
1104
|
+
──────────────────────────── ─────────────────────────────────────────
|
|
1105
|
+
renderHydratable local loader (clientLoad / .route loader)?
|
|
1106
|
+
→ executeLoader(…) yes → runLocalLoader(…) in-browser
|
|
1107
|
+
→ island.hydratable(props) no → fetchLoaderData(…) GET /__ilha/loader?path=/user/42
|
|
1108
|
+
→ data-ilha-props="{…}" → mountRouteWithHydration(island, host, …)
|
|
1019
1109
|
```
|
|
1020
1110
|
|
|
1021
1111
|
---
|
package/dist/codegen.d.ts
CHANGED
|
@@ -7,6 +7,11 @@ export interface GenerateOptions {
|
|
|
7
7
|
* mode. Default: `true`.
|
|
8
8
|
*/
|
|
9
9
|
interceptLinks?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Fail codegen (instead of warning) on duplicate route patterns or registry
|
|
12
|
+
* name collisions. Recommended for production builds. Default: `false`.
|
|
13
|
+
*/
|
|
14
|
+
strict?: boolean;
|
|
10
15
|
}
|
|
11
16
|
/** Paths for all generated files derived from the base output directory. */
|
|
12
17
|
export interface GeneratedPaths {
|
package/dist/hash.d.ts
CHANGED
|
@@ -7,10 +7,10 @@ export interface LogicalLocation {
|
|
|
7
7
|
export interface HistoryAdapter {
|
|
8
8
|
/** Read the current logical URL (the one routes are matched against). */
|
|
9
9
|
readLocation(): LogicalLocation;
|
|
10
|
-
/** Push a new logical URL onto the history stack. */
|
|
11
|
-
push(to: string): void;
|
|
12
|
-
/** Replace the current history entry with a new logical URL. */
|
|
13
|
-
replace(to: string): void;
|
|
10
|
+
/** Push a new logical URL onto the history stack. `state` is stored on the history entry. */
|
|
11
|
+
push(to: string, state?: unknown): void;
|
|
12
|
+
/** Replace the current history entry with a new logical URL. `state` is stored on the history entry. */
|
|
13
|
+
replace(to: string, state?: unknown): void;
|
|
14
14
|
/** Subscribe to logical-URL changes. Returns a cleanup function. */
|
|
15
15
|
onChange(handler: () => void): () => void;
|
|
16
16
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -109,6 +109,11 @@ export declare function wrapError(handler: ErrorHandler, page: Island<any, any>)
|
|
|
109
109
|
export declare function defineLayout(layout: LayoutHandler): LayoutHandler;
|
|
110
110
|
export interface NavigateOptions {
|
|
111
111
|
replace?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* When `false`, keep the current scroll position instead of scrolling to the
|
|
114
|
+
* top (or to the URL hash target) after navigation. Default: `true`.
|
|
115
|
+
*/
|
|
116
|
+
scroll?: boolean;
|
|
112
117
|
}
|
|
113
118
|
export type RouterMode = "spa" | "static";
|
|
114
119
|
export interface RouterOptions {
|
|
@@ -128,6 +133,30 @@ export interface RouterOptions {
|
|
|
128
133
|
* Default: `true`.
|
|
129
134
|
*/
|
|
130
135
|
interceptLinks?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Island rendered when no route matches the current URL — both on the
|
|
138
|
+
* server (with a 404 status) and in the client `RouterView`.
|
|
139
|
+
*/
|
|
140
|
+
notFound?: Island<any, any>;
|
|
141
|
+
/**
|
|
142
|
+
* Allow loader `redirect()` targets pointing at other origins. When `false`
|
|
143
|
+
* (default), absolute cross-origin redirect targets are rejected with a 500
|
|
144
|
+
* — redirect targets frequently carry user input (`?next=` params), and
|
|
145
|
+
* rejecting external targets by default prevents open redirects.
|
|
146
|
+
*/
|
|
147
|
+
allowExternalRedirects?: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Abort a route loader after this many milliseconds during SSR / loader
|
|
150
|
+
* endpoint execution. `0`/`undefined` disables the timeout. The loader's
|
|
151
|
+
* `ctx.signal` also aborts when the incoming `Request`'s signal aborts.
|
|
152
|
+
*/
|
|
153
|
+
loaderTimeout?: number;
|
|
154
|
+
/**
|
|
155
|
+
* Wrap client-side view swaps in `document.startViewTransition()` when the
|
|
156
|
+
* browser supports it (falls back to an instant swap otherwise).
|
|
157
|
+
* Default: `false`.
|
|
158
|
+
*/
|
|
159
|
+
viewTransitions?: boolean;
|
|
131
160
|
}
|
|
132
161
|
export interface HydratableRenderOptions extends Partial<Omit<HydratableOptions, "name">> {
|
|
133
162
|
/**
|
|
@@ -186,6 +215,22 @@ export interface RouterBuilder {
|
|
|
186
215
|
* was never registered via `.route()`.
|
|
187
216
|
*/
|
|
188
217
|
attachLoader(pattern: string, loader: Loader<any>): RouterBuilder;
|
|
218
|
+
/**
|
|
219
|
+
* Attach a loader that runs **in the browser** on client navigations,
|
|
220
|
+
* instead of fetching from the loader endpoint. Used by the FS-routing
|
|
221
|
+
* codegen for `clientLoad` exports; also available for manual routers.
|
|
222
|
+
* When a route has both, the client loader wins on client navigations and
|
|
223
|
+
* the server loader runs during SSR. No-op if the pattern was never
|
|
224
|
+
* registered via `.route()`.
|
|
225
|
+
*/
|
|
226
|
+
clientLoader(pattern: string, loader: Loader<any>): RouterBuilder;
|
|
227
|
+
/**
|
|
228
|
+
* Attach the route's nearest `+error` boundary so **loader** errors render
|
|
229
|
+
* through it (render errors are already handled by `wrapError` inside the
|
|
230
|
+
* island). Used by the FS-routing codegen; also available for manual
|
|
231
|
+
* routers. No-op if the pattern was never registered via `.route()`.
|
|
232
|
+
*/
|
|
233
|
+
errorBoundary(pattern: string, handler: ErrorHandler): RouterBuilder;
|
|
189
234
|
/**
|
|
190
235
|
* Mark an already-registered route as having a server-side loader without
|
|
191
236
|
* importing that loader into the client bundle. Used by FS-routing codegen
|
|
@@ -253,39 +298,24 @@ export declare const LOADER_ENDPOINT = "/__ilha/loader";
|
|
|
253
298
|
* navigation) or is superseded by another prefetch.
|
|
254
299
|
*/
|
|
255
300
|
export declare function prefetch(pathWithSearch: string): void;
|
|
256
|
-
export declare
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
export declare const routeHash: {
|
|
269
|
-
(): string;
|
|
270
|
-
(value: string): void;
|
|
271
|
-
};
|
|
301
|
+
export declare function routePath(value?: string): string;
|
|
302
|
+
export declare function routeParams(value?: Record<string, string>): Record<string, string>;
|
|
303
|
+
export declare function routeSearch(value?: string): string;
|
|
304
|
+
export declare function routeHash(value?: string): string;
|
|
305
|
+
/** Reactive: `true` while a client navigation (loader fetch + view swap) is in flight. */
|
|
306
|
+
export declare function navigating(): boolean;
|
|
307
|
+
/**
|
|
308
|
+
* Re-run the current route's loader and re-render the view with fresh data —
|
|
309
|
+
* e.g. after a mutation. Resolves when the view has updated. No-op on the
|
|
310
|
+
* server or when no router is mounted.
|
|
311
|
+
*/
|
|
312
|
+
export declare function invalidate(): Promise<void>;
|
|
272
313
|
export declare function useRoute(): {
|
|
273
|
-
path:
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
(): Record<string, string>;
|
|
279
|
-
(value: Record<string, string>): void;
|
|
280
|
-
};
|
|
281
|
-
search: {
|
|
282
|
-
(): string;
|
|
283
|
-
(value: string): void;
|
|
284
|
-
};
|
|
285
|
-
hash: {
|
|
286
|
-
(): string;
|
|
287
|
-
(value: string): void;
|
|
288
|
-
};
|
|
314
|
+
path: typeof routePath;
|
|
315
|
+
params: typeof routeParams;
|
|
316
|
+
search: typeof routeSearch;
|
|
317
|
+
hash: typeof routeHash;
|
|
318
|
+
navigating: typeof navigating;
|
|
289
319
|
};
|
|
290
320
|
/**
|
|
291
321
|
* Prime route context signals from the current `location` so that islands
|
|
@@ -293,6 +323,27 @@ export declare function useRoute(): {
|
|
|
293
323
|
* render — preventing a mismatch morph that would destroy hydrated bindings.
|
|
294
324
|
*/
|
|
295
325
|
export declare function prime(): void;
|
|
326
|
+
export interface Navigation {
|
|
327
|
+
/** Logical URL (path + search + hash) being navigated away from. */
|
|
328
|
+
from: string;
|
|
329
|
+
/** Logical URL being navigated to. */
|
|
330
|
+
to: string;
|
|
331
|
+
/** `"push"`/`"replace"` for programmatic navigations, `"pop"` for history traversal. */
|
|
332
|
+
type: "push" | "replace" | "pop";
|
|
333
|
+
}
|
|
334
|
+
export type BeforeNavigateHook = (nav: Navigation & {
|
|
335
|
+
cancel(): void;
|
|
336
|
+
}) => void;
|
|
337
|
+
export type AfterNavigateHook = (nav: Navigation) => void;
|
|
338
|
+
/**
|
|
339
|
+
* Run before a programmatic navigation commits. Call `nav.cancel()` to keep
|
|
340
|
+
* the current URL (e.g. unsaved-changes guards). Not invoked for browser
|
|
341
|
+
* back/forward — the URL has already changed by the time `popstate` fires.
|
|
342
|
+
* Returns an unsubscribe function.
|
|
343
|
+
*/
|
|
344
|
+
export declare function beforeNavigate(fn: BeforeNavigateHook): () => void;
|
|
345
|
+
/** Run after a navigation (push, replace, or pop) has committed. Returns an unsubscribe function. */
|
|
346
|
+
export declare function afterNavigate(fn: AfterNavigateHook): () => void;
|
|
296
347
|
export declare function navigate(to: string, opts?: NavigateOptions): void;
|
|
297
348
|
export interface LinkInterceptionOptions {
|
|
298
349
|
/**
|
|
@@ -306,7 +357,15 @@ export interface LinkInterceptionOptions {
|
|
|
306
357
|
export declare function enableLinkInterception(root?: Element | Document, options?: LinkInterceptionOptions): () => void;
|
|
307
358
|
export declare const RouterView: Island<Record<string, unknown>, Record<never, never>>;
|
|
308
359
|
export declare const RouterLink: Island<Record<string, unknown>, Omit<Omit<Record<never, never>, K> & Record<"href", string>, "label"> & Record<"label", string>>;
|
|
309
|
-
export
|
|
360
|
+
export interface IsActiveOptions {
|
|
361
|
+
/**
|
|
362
|
+
* When `false`, `isActive("/docs")` also matches nested paths like
|
|
363
|
+
* `/docs/getting-started` (prefix match on the current path).
|
|
364
|
+
* Default: `true` (the matched route's pattern must equal `pattern`).
|
|
365
|
+
*/
|
|
366
|
+
exact?: boolean;
|
|
367
|
+
}
|
|
368
|
+
export declare function isActive(pattern: string, options?: IsActiveOptions): boolean;
|
|
310
369
|
/**
|
|
311
370
|
* Contribute `<head>` data from inside an island's `.render()` body or a
|
|
312
371
|
* layout. During SSR this collects into the active render window; on the
|
|
@@ -330,6 +389,8 @@ declare const _default: {
|
|
|
330
389
|
enableLinkInterception: typeof enableLinkInterception;
|
|
331
390
|
prime: typeof prime;
|
|
332
391
|
prefetch: typeof prefetch;
|
|
392
|
+
beforeNavigate: typeof beforeNavigate;
|
|
393
|
+
afterNavigate: typeof afterNavigate;
|
|
333
394
|
RouterView: Island<Record<string, unknown>, Record<never, never>>;
|
|
334
395
|
RouterLink: Island<Record<string, unknown>, Omit<Omit<Record<never, never>, K> & Record<"href", string>, "label"> & Record<"label", string>>;
|
|
335
396
|
loader: typeof loader;
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{A as e,C as t,D as n,E as r,M as i,O as a,S as o,T as s,_ as c,a as l,b as u,c as d,d as f,f as p,g as m,h,i as g,j as _,k as v,l as y,m as b,n as x,o as S,p as C,r as w,s as T,t as E,u as D,v as O,w as k,x as A,y as j}from"./src-8LBo_Ieg.js";export{E as LOADER_ENDPOINT,x as LoaderError,w as Redirect,g as RouterLink,l as RouterView,S as afterNavigate,T as beforeNavigate,d as composeLoaders,n as default,y as defineLayout,D as enableLinkInterception,f as error,_ as getHistoryMode,p as head,C as invalidate,b as isActive,h as loader,m as navigate,c as navigating,O as prefetch,j as prime,u as redirect,A as routeHash,o as routeParams,t as routePath,k as routeSearch,s as router,r as serializeHead,i as setHistoryMode,a as useRoute,v as wrapError,e as wrapLayout};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import{existsSync as e,readFileSync as t,watch as n}from"node:fs";import{basename as r,dirname as i,extname as a,join as o,relative as s,resolve as c,sep as l}from"node:path";import{createUnplugin as u}from"unplugin";import{mkdir as d,readFile as f,readdir as p,writeFile as m}from"node:fs/promises";function h(e){return e.replace(/\\/g,`/`)}const g=/\.(test|spec|d)\.(ts|tsx)$/,_=/^\s*export\s+(?:const|let|var|async\s+function|function)\s+load\b/m,v=/^\s*export\s+(?:const|let|var|async\s+function|function)\s+clientLoad\b/m;async function y(e){try{let t=(await f(e,`utf8`)).replace(/^\s*\/\/.*$/gm,``);return{load:_.test(t),clientLoad:v.test(t)}}catch(t){return t?.code!==`ENOENT`&&console.warn(`[ilha-router] failed to read ${e} while detecting loader exports:`,t),{load:!1,clientLoad:!1}}}function b(e){return e.startsWith(`[...`)&&e.endsWith(`]`)?`**:${e.slice(4,-1)}`:e.startsWith(`[`)&&e.endsWith(`]`)?`:${e.slice(1,-1)}`:e}function x(e){return e.startsWith(`(`)&&e.endsWith(`)`)?``:b(e)}function S(e,t){let n=h(s(e,t)),r=n.slice(0,-a(n).length).split(`/`),i=[...r.slice(0,-1).map(x),b(r.at(-1))];return i.at(-1)===`index`&&i.pop(),`/`+i.filter(Boolean).join(`/`)||`/`}function C(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 w(e){return e===`/`?3:e.includes(`**`)?0:e.includes(`:`)?1:2}function T(e){return[...e].sort((e,t)=>{let n=w(t.pattern)-w(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 E(e,t,n,r){let a=h(s(e,i(t))),c=a===``?[]:a.split(`/`);return[e,...c.map((t,n)=>o(e,...c.slice(0,n+1)))].flatMap(e=>{let t=`${o(e,r)}.tsx`;if(n.has(t))return[t];let i=`${o(e,r)}.ts`;return n.has(i)?[i]:[]})}async function D(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 p(e,{withFileTypes:!0})){let i=o(e,r.name);r.isDirectory()?n.push(...await D(i,t+1)):r.isFile()&&/\.(ts|tsx)$/.test(r.name)&&!g.test(r.name)&&n.push(i)}}catch(e){if(e.code===`ENOENT`)return[];throw e}return n}async function O(e){let t=await D(e),n=new Set(t),i=t.filter(e=>!r(e).startsWith(`+`)),a=new Map,o=e=>{let t=a.get(e);return t||(t=y(e),a.set(e,t)),t};return Promise.all(i.map(async t=>{let r=S(e,t),i=E(e,t,n,`+layout`),a=E(e,t,n,`+error`),[s,...c]=await Promise.all([y(t),...i.map(o)]),l=i.filter((e,t)=>c[t].load),u=i.filter((e,t)=>c[t].clientLoad);return{file:t,pattern:r,name:C(r),layouts:i,errors:a,hasLoader:s.load,loaderLayouts:l,hasClientLoader:s.clientLoad,clientLoaderLayouts:u}}))}function k(e,t,n){if(e.length===0){console.warn(`[ilha:pages] No pages found in ${t}`);return}let r=new Map,i=new Map,a=[];for(let t of e){let e=r.get(t.pattern);e?a.push(`Duplicate route pattern "${t.pattern}"\n first: ${e}\n second: ${t.file}\n The first match wins — the second page will never be reached.`):r.set(t.pattern,t.file);let n=i.get(t.name);n?a.push(`Registry name collision: "${t.name}" is used by both\n ${n}\n ${t.file}\n Hydration may not work correctly for one of these routes.`):i.set(t.name,t.file)}if(a.length!==0){if(n)throw Error(`[ilha:pages] Route validation failed:\n\n${a.join(`
|
|
2
|
+
|
|
3
|
+
`)}`);for(let e of a)console.warn(`[ilha:pages] ${e}`)}}function A(e){return{serverFile:o(e,`pages.server.ts`),clientFile:o(e,`pages.client.ts`),loadersFile:o(e,`loaders.ts`)}}async function j(e,t,n={}){let r=n.mode??`spa`,i=n.interceptLinks,a=r===`static`,o=T(await O(e));k(o,e,n.strict===!0),await d(t,{recursive:!0});let{serverFile:s,clientFile:c,loadersFile:l}=A(t),u=await F(s,M(o,s)),f=await F(c,N(o,c,{isStatic:a,interceptLinks:i}));a||await F(l,P(o,l,s)),(u||f)&&await I(t)}function M(e,t){let n=e=>{let n=h(s(i(t),e));return n.startsWith(`.`)?n:`./${n}`},r=[`import { router, wrapLayout, wrapError } from "@ilha/router";`,`import type { Island } from "ilha";`],a=[],o=[],c=[];for(let[t,i]of e.entries()){r.push(`import { default as _page${t} } from ${JSON.stringify(n(i.file))};`);for(let[e,a]of i.layouts.entries())r.push(`import { default as _layout${t}_${e} } from ${JSON.stringify(n(a))};`);for(let[e,a]of i.errors.entries())r.push(`import { default as _error${t}_${e} } from ${JSON.stringify(n(a))};`);let s=`_page${t}`;for(let e=i.errors.length-1;e>=0;e--)s=`wrapError(_error${t}_${e}, ${s})`;for(let e=i.layouts.length-1;e>=0;e--)s=`wrapLayout(_layout${t}_${e}, ${s})`;let l=`_wrapped${t}`;a.push(`const ${l} = ${s};`),o.push(` ${JSON.stringify(i.name)}: ${l}`+(t<e.length-1?`,`:``)),c.push(` .route(${JSON.stringify(i.pattern)}, ${l})`+(i.hasLoader||i.loaderLayouts.length>0?`.markLoader(${JSON.stringify(i.pattern)})`:``)),i.errors.length>0&&c.push(` .errorBoundary(${JSON.stringify(i.pattern)}, _error${t}_${i.errors.length-1})`)}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";`,``,...r,``,...a,``,`export const registry: Record<string, Island<any, any>> = {`,...o,`};`,``,`export const pageRouter = router()`,...c,` ;`].join(`
|
|
4
|
+
`)}function N(e,t,n){let{isStatic:r,interceptLinks:a}=n,o=e=>{let n=h(s(i(t),e));return n.startsWith(`.`)?n:`./${n}`},c=e=>`${o(e)}?client`,l=e=>`${o(e)}?client-loader`,u=!1,d=r?[`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";`],f=[],p=[],m=[];for(let[t,n]of e.entries()){d.push(`import { default as _page${t} } from ${JSON.stringify(c(n.file))};`);for(let[e,r]of n.layouts.entries())d.push(`import { default as _layout${t}_${e} } from ${JSON.stringify(c(r))};`);for(let[e,r]of n.errors.entries())d.push(`import { default as _error${t}_${e} } from ${JSON.stringify(c(r))};`);let i=`_page${t}`;for(let e=n.errors.length-1;e>=0;e--)i=`wrapError(_error${t}_${e}, ${i})`;for(let e=n.layouts.length-1;e>=0;e--)i=`wrapLayout(_layout${t}_${e}, ${i})`;let a=`_wrapped${t}`;if(f.push(`const ${a} = ${i};`),p.push(` ${JSON.stringify(n.name)}: ${a}`+(t<e.length-1?`,`:``)),!r){m.push(` .route(${JSON.stringify(n.pattern)}, ${a})`+(n.hasLoader||n.loaderLayouts.length>0?`.markLoader(${JSON.stringify(n.pattern)})`:``));let e=[];for(let[r,i]of n.clientLoaderLayouts.entries()){let n=`_cl${t}_l${r}`;d.push(`import { clientLoad as ${n} } from ${JSON.stringify(l(i))};`),e.push(n)}if(n.hasClientLoader){let r=`_cl${t}`;d.push(`import { clientLoad as ${r} } from ${JSON.stringify(l(n.file))};`),e.push(r)}if(e.length>0){let t=e.length===1?e[0]:`composeLoaders([${e.join(`, `)}])`;e.length>1&&(u=!0),m.push(` .clientLoader(${JSON.stringify(n.pattern)}, ${t})`)}n.errors.length>0&&m.push(` .errorBoundary(${JSON.stringify(n.pattern)}, _error${t}_${n.errors.length-1})`)}}u&&(d[0]=d[0].replace(`{ router`,`{ composeLoaders, router`));let g=r?`_router({ mode: "static" })`:`router(${a===!1?`{ interceptLinks: false }`:``})`,_=[`// @generated by @ilha/router — do not edit`,`// Client module. Use for browser hydration.`,`// Import via: import { pageRouter, registry } from "ilha:pages/client";`,``,...d,``,...f,``,`export const registry: Record<string, Island<any, any>> = {`,...p,`};`,``];return r?_.push(`export const pageRouter = ${g};`):_.push(`export const pageRouter = ${g}`,...m,` ;`),_.join(`
|
|
5
|
+
`)}function P(e,t,n){let r=e=>{let n=h(s(i(t),e));return n.startsWith(`.`)?n:`./${n}`},a=e.filter(e=>e.hasLoader||e.loaderLayouts.length>0);if(a.length===0)return[`// @generated by @ilha/router — do not edit`,`// This project has no loader exports; this file is intentionally empty.`,``,`export {};`,``].join(`
|
|
6
|
+
`);let o=r(n).replace(/\.tsx?$/,``),c=[`import { pageRouter } from ${JSON.stringify(o)};`],l=!1,u=[];for(let[e,t]of a.entries()){let n=[];for(let[i,a]of t.loaderLayouts.entries()){let t=`_p${e}_l${i}`;c.push(`import { load as ${t} } from ${JSON.stringify(r(a))};`),n.push(t)}if(t.hasLoader){let i=`_p${e}`;c.push(`import { load as ${i} } from ${JSON.stringify(r(t.file))};`),n.push(i)}let i=n.length===1?n[0]:`composeLoaders([${n.join(`, `)}])`;n.length>1&&(l=!0),u.push(`pageRouter.attachLoader(${JSON.stringify(t.pattern)}, ${i});`)}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(`
|
|
7
|
+
`)}async function F(e,t){try{if(await f(e,`utf8`)===t)return!1}catch{}return await m(e,t,`utf8`),!0}async function I(e){await F(o(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(`
|
|
8
|
+
`))}const L=`\0ilha:pages/server`,R=`\0ilha:pages/client`,z=`\0ilha:loaders`,B=[L,R,z];function V(e){try{return JSON.parse(t(e,`utf8`))}catch{return null}}function H(t,n){let r=t;for(;;){let t=o(r,`node_modules`,n,`package.json`);if(e(t))return V(t);let a=i(r);if(a===r)return null;r=a}}function U(e){let t=V(o(e,`package.json`));if(!t)return[];let n={...t.dependencies??{},...t.devDependencies??{}},r=[];for(let t of Object.keys(n)){if(t===`ilha`)continue;let n=H(e,t);if(!n)continue;let i=n.peerDependencies??{},a=n.dependencies??{};(`ilha`in i||`ilha`in a)&&r.push(t)}return r}function W(e,t){let n=c(e,t.dir??`src/pages`),r=c(e,t.outDir??`.ilha`),{serverFile:i,clientFile:a,loadersFile:o}=A(r);return{pagesDir:n,outDir:r,serverFile:i,clientFile:a,loadersFile:o}}function G(e){let t,n,i,a,o,s=r=>{({pagesDir:t,outDir:n,serverFile:i,clientFile:a,loadersFile:o}=W(r,e))},c=async()=>{try{await j(t,n,{mode:e.mode,interceptLinks:e.interceptLinks,strict:e.strict})}catch(t){if(console.error(`[ilha:pages] codegen failed:`,t),e.strict)throw t}},u=e=>e===t||e.startsWith(t+l);return{get pagesDir(){return t},get outDir(){return n},get serverFile(){return i},get clientFile(){return a},get loadersFile(){return o},setPaths:s,regen:c,shouldRegenOnChange:e=>{if(!u(e))return!1;let t=r(e);return t.startsWith(`+`)||/\.(ts|tsx)$/.test(t)},isUnderPagesDir:u}}async function K(e,t,n){n(t)&&await e.regen()}function q(e,t,n){if(t===`ilha:pages/server`)return L;if(t===`ilha:pages/client`)return R;if(t===`ilha:loaders`)return z;for(let r of[`?client-loader`,`?client`]){if(!t.endsWith(r))continue;let i=t.slice(0,-r.length),a=n?c(n.replace(/\?.*$/,``),`..`,i):c(i);return!e.pagesDir||!e.isUnderPagesDir(a)?void 0:a+r}}function J(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-loader`)){let e=t.slice(0,-14);return`export { clientLoad } from ${JSON.stringify(e)};`}if(t.endsWith(`?client`)){let e=t.slice(0,-7);return`export { default } from ${JSON.stringify(e)};`}}function Y(e,t){return async n=>{e.isUnderPagesDir(n)&&(await e.regen(),await t())}}function X(t,r){let i=null,a=null,s=!1,c=()=>{i=n(t.pagesDir,{recursive:!0},(e,n)=>{n&&r(o(t.pagesDir,n))})};return e(t.pagesDir)?c():(a=setInterval(()=>{s||!e(t.pagesDir)||(clearInterval(a),a=null,c(),r(o(t.pagesDir,`.`)))},1e3),a.unref?.()),()=>{s=!0,a&&clearInterval(a),i?.close()}}const Z=u((e={})=>{let t=G(e);return{name:`ilha:pages`,async buildStart(){t.pagesDir||t.setPaths(process.cwd()),this.addWatchFile?.(t.pagesDir),await t.regen()},async watchChange(e){await K(t,e,e=>t.shouldRegenOnChange(e))},resolveId(e,n){return q(t,e,n)},load(e){return J(t,e)},vite:{config(e){let t=[`ilha`,`@ilha/store`,`@ilha/router`,`alien-signals`,...U(e.root?c(e.root):process.cwd())],n=e.ssr?.noExternal,r=n===!0?!0:[...new Set([...Array.isArray(n)?n:n==null?[]:[n],...t])];return{resolve:{dedupe:[...new Set([...e.resolve?.dedupe??[],...t])]},ssr:{noExternal:r},optimizeDeps:{...e.optimizeDeps,include:[...new Set([...e.optimizeDeps?.include??[],`ilha`,`ilha/jsx-runtime`,`ilha/jsx-dev-runtime`,`@ilha/store`,`alien-signals`])]}}},configResolved(e){t.setPaths(e.root)},configureServer(e){e.watcher.add(t.pagesDir);let n=Y(t,async()=>{for(let t of B){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=Y(t,()=>{e.watching&&e.invalidate()}),r;e.hooks.watchRun.tap(`ilha:pages`,()=>{r?.(),r=X(t,n)}),e.hooks.shutdown.tap(`ilha:pages`,()=>r?.())}}});export{Z as t};
|
package/dist/plugin.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export declare const RESOLVED_LOADERS = "\0ilha:loaders";
|
|
|
8
8
|
export declare const RESOLVED_VIRTUAL_IDS: readonly ["\0ilha:pages/server", "\0ilha:pages/client", "\0ilha:loaders"];
|
|
9
9
|
/** Query suffix used on page/layout imports in the client file. */
|
|
10
10
|
export declare const CLIENT_QUERY = "?client";
|
|
11
|
+
/** Query suffix that re-exports a page/layout's `clientLoad` for the browser bundle. */
|
|
12
|
+
export declare const CLIENT_LOADER_QUERY = "?client-loader";
|
|
11
13
|
export interface IlhaPagesOptions {
|
|
12
14
|
/** Directory containing page files. Default: `src/pages` */
|
|
13
15
|
dir?: string;
|
|
@@ -26,6 +28,11 @@ export interface IlhaPagesOptions {
|
|
|
26
28
|
* Default: `true`.
|
|
27
29
|
*/
|
|
28
30
|
interceptLinks?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Fail codegen on duplicate route patterns / registry name collisions
|
|
33
|
+
* instead of warning. Recommended for CI/production builds. Default: `false`.
|
|
34
|
+
*/
|
|
35
|
+
strict?: boolean;
|
|
29
36
|
}
|
|
30
37
|
export declare function resolvePluginPaths(root: string, options: IlhaPagesOptions): {
|
|
31
38
|
pagesDir: string;
|
|
@@ -47,7 +54,7 @@ export interface PagesPluginState {
|
|
|
47
54
|
}
|
|
48
55
|
export declare function createPagesPluginState(options: IlhaPagesOptions): PagesPluginState;
|
|
49
56
|
export declare function regenFromPagesChange(state: PagesPluginState, file: string, shouldRegen: (file: string) => boolean): Promise<void>;
|
|
50
|
-
export declare function resolvePagesId(
|
|
57
|
+
export declare function resolvePagesId(state: PagesPluginState, id: string, importer?: string): string | undefined;
|
|
51
58
|
export declare function loadPagesModule(state: PagesPluginState, id: string): string | undefined;
|
|
52
59
|
type InvalidateModules = () => void | Promise<void>;
|
|
53
60
|
export declare function createStructuralInvalidate(state: PagesPluginState, invalidate: InvalidateModules): (file: string) => Promise<void>;
|
package/dist/rolldown.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export { wrapLayout, wrapError, type LayoutHandler, type ErrorHandler, type Rout
|
|
|
2
2
|
export { ilhaPages, type IlhaPagesOptions } from "./plugin";
|
|
3
3
|
import { type IlhaPagesOptions } from "./plugin";
|
|
4
4
|
/** Rolldown plugin — use via `@ilha/router/rolldown`. */
|
|
5
|
-
export declare function pages(options?: IlhaPagesOptions): import("
|
|
5
|
+
export declare function pages(options?: IlhaPagesOptions): import("rolldown").Plugin<any> | import("rolldown").Plugin<any>[];
|
package/dist/rolldown.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{A as e,k as t}from"./src-8LBo_Ieg.js";import{t as n}from"./plugin-BW2tnuyF.js";function r(e={}){return n.rolldown(e)}export{n as ilhaPages,r as pages,t as wrapError,e as wrapLayout};
|
package/dist/rspack.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{A as e,k as t}from"./src-8LBo_Ieg.js";import{t as n}from"./plugin-BW2tnuyF.js";function r(e={}){return n.rspack(e)}export{n as ilhaPages,r as pages,t as wrapError,e as wrapLayout};
|
|
@@ -0,0 +1,4 @@
|
|
|
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,t){c&&history.pushState(t??null,``,e)},replace(e,t){c&&history.replaceState(t??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,t){c&&history.pushState(t??null,``,e.startsWith(`#`)?e:`#`+e)},replace(e,t){c&&history.replaceState(t??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 ee(e,t){throw new b(e,t)}function S(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 C=Symbol.for(`ilha.router.wrapLayout.leaf`),te=Symbol.for(`ilha.router.wrapLayout.handler`);function ne(e){let t=e.match(/^<([a-zA-Z][\w-]*)\s[^>]*>([\s\S]*)<\/\1>\s*$/);return t?t[2]:e}function re(e){let t=e.match(/^<([a-zA-Z][\w-]*)\s([^>]*)>/);return t?{tag:t[1],attrs:t[2]}:null}const ie=/<(pre|script|style|textarea)\b/i;function ae(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 oe(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(ie);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=ae(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 se(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=oe(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 w(e,t,n){let r=se(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 ce(e,t){let n=e[te];if(!n)return e.toString(t);let r=e[C]??e;return n(Object.assign(r.key(`page`),{toString:()=>``})).toString(t)}async function le(e,t,n,r){let i=ne(await t.hydratable(n,r));return w(w(ce(e,n),``,`innermost`),i,`innermost`)}function ue(e,n){let r=n[C]??n,i=r===n?null:n,a=e(Object.assign(n.key(`page`),{toString:n.toString.bind(n)}));a[C]=r,a[te]=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){let n=e=>{delete e._skipOnMount,t.setAttribute(`data-ilha-state`,JSON.stringify(e))};if(t.hasAttribute(`data-ilha-state`)){let r=e.getAttribute(`data-ilha-state`);if(r)try{n(JSON.parse(r));return}catch{}let i=t.getAttribute(`data-ilha-state`);if(i)try{let e=JSON.parse(i);delete e._skipOnMount,t.setAttribute(`data-ilha-state`,JSON.stringify(e))}catch{}return}let r=e.getAttribute(`data-ilha-state`);if(r)try{n(JSON.parse(r));return}catch{}t.childNodes.length>0&&t.setAttribute(`data-ilha-state`,`{}`)}function c(e){let n=e[t];if(typeof n!=`function`)return;e[t]=(e,t)=>{let r=e.closest(`[data-ilha]`);return r&&r!==e&&s(r,e),n(e,t)};let r=e.mount.bind(e);e.mount=(e,t)=>{let n=e.closest(`[data-ilha]`);return n&&n!==e&&s(n,e),r(e,t)}}c(r);let l=a.mount.bind(a),u=a[t];function d(e){s(e,o(e))}return a.mount=(e,t)=>(d(e),l(e,t)),a[t]=(e,t)=>(d(e),typeof u==`function`?u(e,t):{unmount:l(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=re(o);if(!s)return o;let c=ne(o);i&&(c=await le(i,r,n,t));let l=w(w(ce(a,n),``,`first`),c,`first`);return`<${s.tag} ${s.attrs}>${l}</${s.tag}>`},a}function de(n,r){let i=e.render(()=>{try{return r.toString()}catch(e){let t={path:A(),params:j(),search:M(),hash:N()};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:A(),params:j(),search:M(),hash:N()},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:A(),params:j(),search:M(),hash:N()},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 fe(e){return e}function pe(e){let t=new Map;for(let[n,r]of Object.entries(e))t.has(r)||t.set(r,n);return t}const me=`/__ilha/loader`,T=new Map;let he=!1;async function E(e){let t=document.startViewTransition?.bind(document);if(!he||!t)return e();let n,r,i=!1;if(await t(()=>{try{n=e()}catch(e){throw i=!0,r=e,e}}).updateCallbackDone?.catch(()=>{}),i)throw r;return n}async function ge(e,t,n,r){let i=new URL(n,location.origin),a=z(t),o=[],s=await pt(e,i,a,ft(i),r??new AbortController().signal,e=>o.push(e));if(r?.throwIfAborted(),s.kind===`redirect`){let e=ut(s.to,i,Ge);return e.ok?{kind:`redirect`,to:e.to,status:s.status}:(console.warn(`[ilha-router] Blocked unsafe redirect target "${s.to}". Set allowExternalRedirects: true to allow cross-origin redirects.`),{kind:`error`,status:500,message:`Unsafe redirect target`})}return s.kind===`data`?o.length>0?{kind:`data`,data:s.data,headEntries:o}:{kind:`data`,data:s.data}:s}async function _e(e,t){let n=T.get(e);if(n&&(T.delete(e),Date.now()<=n.expires))try{t?.throwIfAborted();let e=await n.promise;return t?.throwIfAborted(),e}catch(e){if(e?.name===`AbortError`)throw e}let r=e.split(`?`)[0]??``,i=s(R,`GET`,r),a=i?.data?.clientLoader??i?.data?.loader;if(a)return ge(a,i?.params,e,t);let o=`${me}?path=${encodeURIComponent(e)}`;try{let e=await fetch(o,{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 D(e){if(!_)return;let t=T.get(e);if(t&&Date.now()<=t.expires)return;let n=e.split(`?`)[0]??``;if(!s(R,`GET`,n)?.data?.hasLoader)return;let r=_e(e).catch(e=>({kind:`error`,status:0,message:e?.message??`prefetch failed`}));T.set(e,{promise:r,expires:Date.now()+3e4})}async function ve(e,t,n,r,i,a){if(!e){if(W){let e=W;return E(()=>{t.innerHTML=`<div data-router-view data-router-not-found>${e.toString()}</div>`;let n=t.firstElementChild;return n?e.mount(n):()=>{}})}return await E(()=>{t.innerHTML=`<div data-router-empty></div>`}),()=>{}}let o=s(R,`GET`,n.split(`?`)[0]??``),c=!!o?.data?.hasLoader,l={},u=c?await _e(n,r):{kind:`data`,data:{}};if(u.kind===`redirect`)return Ue(u.to),()=>{};if(u.kind===`error`){let e=o?.data?.errorHandler;if(e)return E(()=>ye(e,t,u.status,u.message));let n=it(u.message);return await E(()=>{t.innerHTML=`<div data-router-view data-router-error="${u.status}">${n}</div>`}),()=>{}}if(u.kind===`not-found`)return await E(()=>{t.innerHTML=`<div data-router-empty></div>`}),()=>{};l=u.data;let d={entries:[...u.headEntries??[]]};if(!i){console.warn(`[ilha-router] No registry provided for client-side navigation. Island will not be interactive.`);let n=await Z(d,()=>e.toString(l));return await E(()=>{X(d.entries),t.innerHTML=`<div data-router-view>${n}</div>`}),()=>{}}let f=a?.get(e)??Object.entries(i).find(([,t])=>t===e)?.[0];if(!f){console.warn(`[ilha-router] Island not found in registry for client-side navigation.`);let n=await Z(d,()=>e.toString(l));return await E(()=>{X(d.entries),t.innerHTML=`<div data-router-view>${n}</div>`}),()=>{}}let p=await Z(d,()=>e.hydratable(l,{name:f,as:`div`,snapshot:!0}));return E(()=>{X(d.entries),t.innerHTML=`<div data-router-view>${p}</div>`;let n=t.querySelector(`[data-ilha="${f}"]`);return n?e.mount(n):()=>{}})}function ye(e,t,n,r){try{let i=e({message:r,status:n},{path:A(),params:j(),search:M(),hash:N()});t.innerHTML=`<div data-router-view data-router-error="${n}">${i.toString()}</div>`;let a=t.firstElementChild;return a?i.mount(a):()=>{}}catch(e){return console.error(`[ilha-router] error boundary threw while rendering a loader error:`,e),t.innerHTML=`<div data-router-view data-router-error="${n}"></div>`,()=>{}}}let O=null,be=null;async function xe(){return O||(be||=import(`node:async_hooks`).then(({AsyncLocalStorage:e})=>(O=new e,O)),be)}_||xe().catch(()=>{});function k(){return _?null:O?.getStore()??null}function Se(){return{path:``,params:{},search:``,hash:``,island:null}}const Ce=n(`router.path`,``),we=n(`router.params`,{}),Te=n(`router.search`,``),Ee=n(`router.hash`,``);function A(e){let t=k();return arguments.length>0?t?t.path=e:(Ce(e),e):t?t.path:Ce()}function j(e){let t=k();return arguments.length>0?t?t.params=e:(we(e),e):t?t.params:we()}function M(e){let t=k();return arguments.length>0?t?t.search=e:(Te(e),e):t?t.search:Te()}function N(e){let t=k();return arguments.length>0?t?t.hash=e:(Ee(e),e):t?t.hash:Ee()}const P=n(`router.navigating`,0);function De(){return P()>0}let F=null;function Oe(){return!_||!F?Promise.resolve():F()}function I(){if(!_)return()=>{};P(P()+1);let e=!1;return()=>{e||(e=!0,P(Math.max(0,P()-1)))}}function ke(){return{path:A,params:j,search:M,hash:N,navigating:De}}const Ae=n(`router.active`,null);function L(e){let t=k();return arguments.length>0?t?t.island=e??null:(Ae(e??null),e??null):t?t.island:Ae()}let R=o();function z(e){let t={};if(e)for(let[n,r]of Object.entries(e))t[n]=decodeURIComponent(r);return t}function je(e,t=R){let n=typeof e==`string`?new URL(e,`http://localhost`):e,r=s(t,`GET`,n.pathname);A(n.pathname),j(z(r?.params)),M(n.search),N(n.hash),L(r?.data?.island??null)}function Me(){let e=g().readLocation(),t=s(R,`GET`,e.pathname);A(e.pathname),j(z(t?.params)),M(e.search),N(e.hash),L(t?.data?.island??null)}function B(){_&&Me()}const Ne=new Set,Pe=new Set;function Fe(e){return Ne.add(e),()=>Ne.delete(e)}function Ie(e){return Pe.add(e),()=>Pe.delete(e)}function Le(e){for(let t of Pe)try{t(e)}catch(e){console.error(`[ilha-router] afterNavigate hook threw:`,e)}}const Re=new Map;let ze=0,V=0;function H(){if(!_)return 0;let e=history.state;return typeof e?.__ilhaNavKey==`number`?e.__ilhaNavKey:0}function Be(){Re.set(H(),{x:window.scrollX,y:window.scrollY})}function Ve(e){requestAnimationFrame(()=>{if(e&&e!==`#`){let t=document.getElementById(e.slice(1))??document.querySelector(`a[name="${Y(e.slice(1))}"]`);if(t){t.scrollIntoView();return}}window.scrollTo(0,0)})}function He(){let e=Re.get(H());e&&requestAnimationFrame(()=>window.scrollTo(e.x,e.y))}function U(e,t={}){if(!_)return;let n=g(),r=n.readLocation(),i=r.pathname+r.search+r.hash;if(e===i)return;let a=t.replace?`replace`:`push`,o=!1;for(let t of Ne)try{t({from:i,to:e,type:a,cancel:()=>o=!0})}catch(e){console.error(`[ilha-router] beforeNavigate hook threw:`,e)}o||(t.replace?n.replace(e,{__ilhaNavKey:H()}):(Be(),ze=Math.max(ze+1,H()+1),n.push(e,{__ilhaNavKey:ze})),V=H(),Me(),t.scroll!==!1&&Ve(n.readLocation().hash),Le({from:i,to:e,type:a}))}function Ue(e){if(/^https?:\/\//i.test(e)){try{let t=new URL(e);if(t.origin===location.origin){U(t.pathname+t.search+t.hash,{replace:!0});return}}catch{return}location.assign(e);return}U(e,{replace:!0})}function We(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||t.altKey),i=e.hasAttribute(`data-no-intercept`),a=e.hasAttribute(`download`),o=/\bexternal\b/i.test(e.getAttribute(`rel`)??``);return n||r||i||a||o?null:g().extractLogicalPath(e)}let i=e=>{if(e.defaultPrevented||typeof e.button==`number`&&e.button!==0)return;let t=e.target.closest(`a`);if(!t)return;let n=r(t,e);n!==null&&(e.preventDefault(),U(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&&D(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)}}let W=null,Ge=!1;const G=e.render(()=>{let e=L();return e?`<div data-router-view>${e.toString()}</div>`:W?`<div data-router-view data-router-not-found>${W.toString()}</div>`:`<div data-router-empty></div>`}),Ke=e.state(`href`,``).state(`label`,``).on(`[data-link]@click`,({state:e,event:t})=>{t.preventDefault(),U(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;D(e.pathname+e.search);return}catch{return}D(t)}}).render(({state:e})=>r`<a data-link data-prefetch href="${()=>g().toLinkHref(e.href())}"
|
|
2
|
+
>${e.label}</a
|
|
3
|
+
>`);function qe(e,t={}){if(t.exact===!1){let t=A(),n=e.endsWith(`/`)?e.slice(0,-1):e;return t===n||t===n+`/`||t.startsWith(n+`/`)}let n=s(R,`GET`,A());return n?n.data.pattern===e:!1}const K=`data-ilha-head`,Je=`data-ilha-router-html`,Ye=`data-ilha-router-body`;let q=null,J=null,Xe=null;async function Ze(){return J||(Xe||=import(`node:async_hooks`).then(({AsyncLocalStorage:e})=>(J=new e,J)),Xe)}function Qe(){return _?q:J?.getStore()??null}function $e(e){let t=Qe();if(!t){_||console.warn(`[ilha-router] head() called outside an SSR render window — ignored.`);return}t.entries.push(e)}function Y(e){return typeof CSS<`u`&&typeof CSS.escape==`function`?CSS.escape(e):e.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`)}function et(e){return`charset`in e?`meta[charset][${K}]`:`name`in e?`meta[name="${Y(e.name)}"][${K}]`:`property`in e?`meta[property="${Y(e.property)}"][${K}]`:`http-equiv`in e?`meta[http-equiv="${Y(e[`http-equiv`])}"][${K}]`:null}function tt(e){return e.rel&&e.href?`link[rel="${Y(e.rel)}"][href="${Y(e.href)}"][${K}]`:null}function X(e){if(!_)return;let t,n,r=[],i=[],a={},o={};for(let s of e)s.title!==void 0&&(t=s.title),s.titleTemplate!==void 0&&(n=s.titleTemplate),s.meta&&r.push(...s.meta),s.link&&i.push(...s.link),s.htmlAttrs&&(a={...a,...s.htmlAttrs}),s.bodyAttrs&&(o={...o,...s.bodyAttrs});let s=st(t,n);s!==void 0&&(document.title=s);let c=ot(r,at),l=ot(i,e=>`${e.rel??``}:${e.href??``}`),u=new Set;for(let e of c){let t=et(e);if(!t)continue;let n=document.querySelector(t);n||(n=document.createElement(`meta`),n.setAttribute(K,``),document.head.appendChild(n));for(let[t,r]of Object.entries(e))n.setAttribute(t,r);u.add(n)}for(let e of l){let t=tt(e),n=t?document.querySelector(t):null;n||(n=document.createElement(`link`),n.setAttribute(K,``),document.head.appendChild(n));for(let[t,r]of Object.entries(e))n.setAttribute(t,r);u.add(n)}for(let e of[...document.head.querySelectorAll(`[${K}]`)])u.has(e)||e.remove();let d=document.documentElement,f=(d.getAttribute(Je)??``).split(/\s+/).filter(Boolean);for(let e of f)d.removeAttribute(e);let p=Object.keys(a);for(let[e,t]of Object.entries(a))d.setAttribute(e,t);p.length?d.setAttribute(Je,p.join(` `)):d.removeAttribute(Je);let m=document.body,h=(m.getAttribute(Ye)??``).split(/\s+/).filter(Boolean);for(let e of h)m.removeAttribute(e);let g=Object.keys(o);for(let[e,t]of Object.entries(o))m.setAttribute(e,t);g.length?m.setAttribute(Ye,g.join(` `)):m.removeAttribute(Ye)}async function Z(e,t){if(_){let n=q;q=e;try{return await t()}finally{q=n}}return await(await Ze()).run(e,()=>Promise.resolve(t()))}const nt={"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`};function rt(e){return String(e).replace(/[&<>"']/g,e=>nt[e])}function it(e){return String(e).replace(/[&<>]/g,e=>nt[e])}function Q(e){return Object.entries(e).map(([e,t])=>` ${e}="${rt(t)}"`).join(``)}function at(e){return`charset`in e?`charset`:`name`in e?`name:${e.name}`:`property`in e?`property:${e.property}`:`http-equiv`in e?`http-equiv:${e[`http-equiv`]}`:JSON.stringify(e)}function ot(e,t){let n=new Map;for(let r of e)n.set(t(r),r);return[...n.values()]}function st(e,t){return t===void 0?e:typeof t==`function`?t(e):t.replace(/%s/g,e??``)}function $(e){let t,n,r=[],i=[],a=[],o={},s={};for(let c of e)c.title!==void 0&&(t=c.title),c.titleTemplate!==void 0&&(n=c.titleTemplate),c.meta&&r.push(...c.meta),c.link&&i.push(...c.link),c.script&&a.push(...c.script),c.htmlAttrs&&(o={...o,...c.htmlAttrs}),c.bodyAttrs&&(s={...s,...c.bodyAttrs});let c=st(t,n),l=[];c!==void 0&&l.push(`<title>${rt(c)}</title>`);for(let e of ot(r,at))l.push(`<meta${Q({...e,[K]:``})} />`);for(let e of ot(i,e=>`${e.rel??``}:${e.href??``}`))l.push(`<link${Q({...e,[K]:``})} />`);for(let e of a){let{children:t,...n}=e,r=(t??``).replace(/<\/script/gi,`<\\/script`);l.push(`<script${Q(n)}>${r}<\/script>`)}return{headTags:l.join(`
|
|
4
|
+
`),htmlAttrs:Q(o),bodyAttrs:Q(s)}}function ct(e){return typeof e==`string`?new URL(e,`http://localhost`):e}function lt(){try{return typeof process<`u`&&!!process.env&&process.env.NODE_ENV!==`production`}catch{return!1}}function ut(e,t,n){if(e.startsWith(`/`)&&!e.startsWith(`//`))return{ok:!0,to:e};try{let r=new URL(e,t);return/^https?:$/.test(r.protocol)?r.origin===t.origin?{ok:!0,to:r.pathname+r.search+r.hash}:n?{ok:!0,to:r.href}:{ok:!1}:{ok:!1}}catch{return{ok:!1}}}function dt(e,t){let n=new AbortController,r=()=>n.abort(),i=e?.signal;i&&(i.aborted?r():i.addEventListener(`abort`,r,{once:!0}));let a;return t&&t>0&&(a=setTimeout(r,t)),{signal:n.signal,done:()=>{a!==void 0&&clearTimeout(a),i?.removeEventListener(`abort`,r)}}}function ft(e){try{return new Request(e.toString())}catch{return{url:e.toString(),headers:new Headers}}}async function pt(e,t,n,r,i,a){let o=[],s=a??(e=>o.push(e));try{let a=Promise.resolve(e({params:n,request:r,url:t,signal:i,head:s}));a.catch(()=>{});let c={kind:`data`,data:await Promise.race([a,new Promise((e,t)=>{let n=()=>t(new b(504,`Loader aborted or timed out`));i.aborted?n():i.addEventListener(`abort`,n,{once:!0})})])??{}};return o.length>0&&(c.head=$(o)),c}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}:(console.error(`[ilha-router] loader failed:`,e),{kind:`error`,status:typeof e?.status==`number`?e.status:500,message:lt()?e?.message??`Loader failed`:`Internal error`})}}function mt(t={}){let n=t.mode??`spa`,r=t.interceptLinks!==!1,c=t.allowExternalRedirects===!0,l=t.loaderTimeout,u=[],d=o(),f=new Map,p=t.notFound??null;R=d,W=p,Ge=c,he=t.viewTransitions===!0;let m=null,v=null,y={route(e,t,n){let r=!!n,i={island:t,pattern:e,loader:n,hasLoader:r};return u.push({pattern:e,island:t,loader:n,hasLoader:r}),a(d,`GET`,e,i),f.set(e,i),y},attachLoader(e,t){let n=f.get(e);if(!n)return console.warn(`[ilha-router] attachLoader("${e}", …): pattern was never registered via .route(). The loader will be ignored.`),y;n.loader=t,n.hasLoader=!0;let r=u.find(t=>t.pattern===e);return r&&(r.loader=t,r.hasLoader=!0),y},clientLoader(e,t){let n=f.get(e);if(!n)return console.warn(`[ilha-router] clientLoader("${e}", …): pattern was never registered via .route(). The loader will be ignored.`),y;n.clientLoader=t,n.hasLoader=!0;let r=u.find(t=>t.pattern===e);return r&&(r.hasLoader=!0),y},errorBoundary(e,t){let n=f.get(e);return n?(n.errorHandler=t,y):(console.warn(`[ilha-router] errorBoundary("${e}", …): pattern was never registered via .route(). The boundary will be ignored.`),y)},markLoader(e){let t=f.get(e);if(!t)return console.warn(`[ilha-router] markLoader("${e}"): pattern was never registered via .route(). The loader marker will be ignored.`),y;t.hasLoader=!0;let n=u.find(t=>t.pattern===e);return n&&(n.hasLoader=!0),y},routes(){return u.map(e=>({...e}))},prime:B,hydrateStatic(e,t={}){if(!_)return()=>{};let n=t.root??document.body;B();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 c=typeof t==`string`?document.querySelector(t):t;if(!c)return console.warn(`[ilha-router] No element found for selector "${t}"`),()=>{};if(Me(),V=H(),n===`static`)return console.warn(`[ilha-router] router.mount() called in static mode. Use router.hydrateStatic(registry) instead.`),()=>{};let l=!0,u=`scrollRestoration`in history?history.scrollRestoration:null;u!==null&&(history.scrollRestoration=`manual`),m=g().onChange(()=>{if(!l)return;let e=A()+M()+N();Re.set(V,{x:window.scrollX,y:window.scrollY}),V=H(),Me(),He(),Le({from:e,to:A()+M()+N(),type:`pop`})}),v=o??r?We(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=c.querySelector(`[data-router-view]`)??c,n=L(),r=a?pe(a):void 0,i=0,o=e.render(()=>{let e=L();if(e!==n){let o=++i;f?.abort(),f=new AbortController;let s=f.signal;queueMicrotask(async()=>{if(o!==i)return;let c=I();d?.(),d=null;try{let n=g().readLocation();d=await ve(e,t,n.pathname+n.search,s,a,r)}catch(e){if(e?.name===`AbortError`)return;console.error(`[ilha-router] navigation failed:`,e),t.innerHTML=`<div data-router-view data-router-error="500"></div>`;return}finally{c()}n=e})}return``}),p=document.createElement(`div`);p.style.display=`none`,c.appendChild(p);let _=o.mount(p);return(async()=>{let e=L();if(!e)return;let n=g().readLocation(),o=n.pathname+n.search,c=s(R,`GET`,n.pathname);if(c?.data?.clientLoader){let n=++i,s=new AbortController;f=s;try{let c=await ve(e,t,o,s.signal,a,r);n===i?d=c:c()}catch(e){e?.name!==`AbortError`&&console.error(`[ilha-router] initial client loader render failed:`,e)}return}let u=c?.data?.hasLoader?await _e(o):{kind:`data`,data:{}};if(u.kind===`redirect`||u.kind===`error`)return;let p=u.kind===`data`?u.data:{},m={entries:[...u.kind===`data`?u.headEntries??[]:[]]};await Z(m,()=>e.toString(p)),l&&X(m.entries)})(),F=async()=>{let e=L(),o=++i;f?.abort();let s=new AbortController;f=s;let c=I();try{let c=g().readLocation(),l=await ve(e,t,c.pathname+c.search,s.signal,a,r);o===i?(d?.(),d=l,n=e):l()}catch(e){e?.name!==`AbortError`&&console.error(`[ilha-router] invalidate failed:`,e)}finally{c()}},()=>{l=!1,++i,F=null,f?.abort(),_(),p.remove(),d?.(),v?.(),m?.(),v=null,m=null,u!==null&&(history.scrollRestoration=u)}}let p=null,y=null,b=0;d=G.mount(c);async function x(e,t){if(p?.(),p=null,y=e,!e){let e=c?.querySelector(`[data-router-not-found]`);W&&e&&(p=W.mount(e));return}let n=c?.querySelector(`[data-router-view]`);if(!n)return;let r=g().readLocation(),i=s(R,`GET`,r.pathname),a=i?.data?.hasLoader?await _e(r.pathname+r.search,t):{kind:`data`,data:{}};if(t.aborted)return;if(a.kind===`redirect`){Ue(a.to);return}if(a.kind===`error`){let e=i?.data?.errorHandler;if(e){p=await E(()=>ye(e,n,a.status,a.message));return}let t=it(a.message);await E(()=>{n.innerHTML=`<div data-router-error="${a.status}">${t}</div>`});return}let o=a.kind===`data`?a.data:{},l={entries:[...a.kind===`data`?a.headEntries??[]:[]]},u=await Z(l,()=>e.toString(o));p=await E(()=>(X(l.entries),n.innerHTML=u,e.mount(n,o)))}f=new AbortController,x(L(),f.signal).catch(e=>{e?.name!==`AbortError`&&console.error(`[ilha-router] initial mount failed:`,e)});let ee=e.render(()=>{let e=L();if(e!==y){let t=++b;f?.abort(),f=new AbortController;let n=f.signal;queueMicrotask(()=>{if(t!==b)return;let r=I();x(e,n).catch(e=>{e?.name!==`AbortError`&&console.error(`[ilha-router] navigation failed:`,e)}).finally(r)})}return``}),S=document.createElement(`div`);S.style.display=`none`,c.appendChild(S);let C=ee.mount(S);return F=async()=>{++b,f?.abort(),f=new AbortController;let e=I();try{await x(L(),f.signal)}catch(e){e?.name!==`AbortError`&&console.error(`[ilha-router] invalidate failed:`,e)}finally{e()}},()=>{l=!1,++b,F=null,f?.abort(),p?.(),C(),S.remove(),d?.(),v?.(),m?.(),v=null,m=null,u!==null&&(history.scrollRestoration=u)}},render(e){let t=()=>(je(e,d),G.toString());return!_&&O?O.run(Se(),t):t()},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=${rt(i.to)}">`},async renderResponse(e,t,n={},r){if(!_){let i=await xe();if(!i.getStore())return i.run(Se(),()=>b(e,t,n,r))}return b(e,t,n,r)},async runLoader(e,t){let n=ct(e),r=s(d,`GET`,n.pathname);if(!r?.data?.island)return{kind:`not-found`};if(!r.data.loader)return{kind:`data`,data:{}};let i=z(r.params),a=t??ft(n),o=dt(t,l),u={entries:[]};try{let e=await pt(r.data.loader,n,i,a,o.signal,e=>u.entries.push(e));if(e.kind===`redirect`){let t=ut(e.to,n,c);return t.ok?{...e,to:t.to}:(console.warn(`[ilha-router] Blocked unsafe redirect target "${e.to}". Set allowExternalRedirects: true to allow cross-origin redirects.`),{kind:`error`,status:500,message:`Unsafe redirect target`})}return e.kind!==`data`||u.entries.length===0?e:{...e,head:$(u.entries),headEntries:u.entries}}finally{o.done()}},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;B();let{unmount:a}=i(e,{root:n}),o=this.mount(r,{hydrate:!0,registry:e,interceptLinks:t.interceptLinks});return()=>{a(),o()}}};async function b(e,t,n={},r){let{baseHead:i,...a}=n,o=ct(e);je(o,d);let u=s(d,`GET`,o.pathname),f=u?.data?.island??null;if(!f){let e={entries:i?[i]:[]};return p?{kind:`html`,html:`<div data-router-view data-router-not-found>${await Z(e,()=>p.toString())}</div>`,status:404,head:$(e.entries)}:{kind:`html`,html:`<div data-router-empty></div>`,status:404,head:i?$([i]):void 0}}let m={entries:i?[i]:[]},h={};if(u?.data?.loader){let e=r??ft(o),t=dt(r,l),n;try{n=await pt(u.data.loader,o,j(),e,t.signal,e=>m.entries.push(e))}finally{t.done()}if(n.kind===`redirect`){let e=ut(n.to,o,c);if(!e.ok)console.warn(`[ilha-router] Blocked unsafe redirect target "${n.to}". Set allowExternalRedirects: true to allow cross-origin redirects.`),n={kind:`error`,status:500,message:`Unsafe redirect target`};else return{kind:`redirect`,to:e.to,status:n.status}}if(n.kind===`error`){let e=u.data.errorHandler;if(e)try{let t=e({message:n.message,status:n.status},{path:A(),params:j(),search:M(),hash:N()}),r=await Z(m,()=>t.toString());return{kind:`error`,status:n.status,message:n.message,html:`<div data-router-view data-router-error="${n.status}">${r}</div>`,head:$(m.entries)}}catch(e){console.error(`[ilha-router] error boundary threw while rendering a loader error:`,e)}let t=it(n.message),r=`<div data-router-view data-router-error="${n.status}">${t}</div>`;return{kind:`error`,status:n.status,message:n.message,html:r,head:$(m.entries)}}h=n.data}let g=pe(t).get(f);return g?{kind:`html`,html:`<div data-router-view>${await Z(m,()=>f.hydratable(h,{name:g,as:`div`,snapshot:!0,...a}))}</div>`,head:$(m.entries)}:(console.warn(`[ilha-router] renderHydratable: active island for "${A()}" 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>${await Z(m,()=>f.toString(h))}</div>`,head:$(m.entries)})}return y}var ht={router:mt,navigate:U,useRoute:ke,isActive:qe,enableLinkInterception:We,prime:B,prefetch:D,beforeNavigate:Fe,afterNavigate:Ie,RouterView:G,RouterLink:Ke,loader:v,redirect:x,error:ee,composeLoaders:S,head:$e};export{ue as A,A as C,ht as D,$ as E,m as M,ke as O,j as S,mt as T,De as _,G as a,x as b,S as c,ee as d,$e as f,U as g,v as h,Ke as i,h as j,de as k,fe as l,qe as m,b as n,Ie as o,Oe as p,y as r,Fe as s,me as t,We as u,D as v,M as w,N as x,B as y};
|
package/dist/ssr.d.ts
CHANGED
package/dist/ssr.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import"./src-
|
|
2
|
-
`),
|
|
3
|
-
`,
|
|
4
|
-
<html lang="${
|
|
1
|
+
import"./src-8LBo_Ieg.js";import{pageRouter as e,registry as t}from"ilha:pages/server";import"ilha:loaders";function n({client:e,server:t}){return e.merge(t)}function r(e){return e}const i={"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`};function a(e){return e.replace(/[&<>"']/g,e=>i[e])}function o(e){let t=e[`data-vite-dev-id`]?` data-vite-dev-id="${a(e[`data-vite-dev-id`])}"`:``;return`<link rel="stylesheet" href="${a(e.href)}"${t} />`}var s=class{assets;lang;appId;clientEntry;head;renderOptions;constructor(e){this.assets=e.assets,this.lang=e.lang??`en`,this.appId=e.appId??`app`,this.clientEntry=e.clientEntry??`/entry-client.js`,this.head=e.head??{},this.renderOptions=e.renderOptions??{}}document(e,t){let n=this.assets.entry??this.clientEntry,r=this.assets.css.map(o).join(`
|
|
2
|
+
`),i=t?.headTags.includes(`<title`)?``:`<title>Ilha</title>
|
|
3
|
+
`,s=t?.headTags?`\n ${t.headTags}`:``,c=t?.htmlAttrs??``;return`<!doctype html>
|
|
4
|
+
<html lang="${c.match(/\blang="([^"]*)"/)?.[1]??this.lang}"${c.replace(/\s*lang="[^"]*"/,``)}>
|
|
5
5
|
<head>
|
|
6
6
|
<meta charset="UTF-8" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
|
-
${
|
|
9
|
-
${r}${
|
|
8
|
+
${i}<link rel="icon" href="/favicon.svg" />
|
|
9
|
+
${r}${s}
|
|
10
10
|
</head>
|
|
11
11
|
<body${t?.bodyAttrs??``}>
|
|
12
|
-
<div id="${this.appId}">${e}</div>
|
|
13
|
-
<script type="module" src="${n}"><\/script>
|
|
12
|
+
<div id="${a(this.appId)}">${e}</div>
|
|
13
|
+
<script type="module" src="${a(n)}"><\/script>
|
|
14
14
|
</body>
|
|
15
|
-
</html>`}async handle(n){let r=new URL(n.url);if(r.pathname===`/__ilha/loader`){let t=r.searchParams.get(`path`)??`/`,i=await e.runLoader(t,n),a=i.kind===`error`?i.status:i.kind===`not-found`?404:200;return new Response(JSON.stringify(i),{status:a,headers:{"content-type":`application/json;charset=utf-8`}})}let i=r.href.slice(r.origin.length),a={...this.renderOptions,baseHead:this.head},o=await e.renderResponse(i,t,a,n);if(o.kind===`redirect`)return new Response(null,{status:o.status,headers:{location:o.to}});let s=o.kind===`error`?o.status:o.status??200;return new Response(this.document(o.html,o.head),{status:s,headers:{"content-type":`text/html;charset=utf-8`}})}};export{
|
|
15
|
+
</html>`}async handle(e){try{return await this.handleInner(e)}catch(e){return console.error(`[ilha-router] request handling failed:`,e),new Response(`Internal Server Error`,{status:500,headers:{"content-type":`text/plain;charset=utf-8`}})}}async handleInner(n){let r=new URL(n.url);if(r.pathname===`/__ilha/loader`){if(n.method!==`GET`&&n.method!==`HEAD`)return new Response(JSON.stringify({kind:`error`,status:405,message:`Method Not Allowed`}),{status:405,headers:{"content-type":`application/json;charset=utf-8`,allow:`GET, HEAD`}});let t=r.searchParams.get(`path`)??`/`,i=await e.runLoader(t,n),a=i.kind===`error`?i.status:i.kind===`not-found`?404:200;return new Response(JSON.stringify(i),{status:a,headers:{"content-type":`application/json;charset=utf-8`,"cache-control":`no-store`}})}let i=r.href.slice(r.origin.length),a={...this.renderOptions,baseHead:this.head},o=await e.renderResponse(i,t,a,n);if(o.kind===`redirect`)return new Response(null,{status:o.status,headers:{location:o.to}});let s=o.kind===`error`?o.status:o.status??200;return new Response(this.document(o.html,o.head),{status:s,headers:{"content-type":`text/html;charset=utf-8`}})}};export{s as IlhaHandler,r as appHead,n as mergeAssets};
|
package/dist/vite.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{A as e,k as t}from"./src-8LBo_Ieg.js";import{t as n}from"./plugin-BW2tnuyF.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
package/dist/plugin-C07ziXtR.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import{existsSync as e,readFileSync as t,watch as n}from"node:fs";import{basename as r,dirname as i,extname as a,join as o,relative as s,resolve as c,sep as l}from"node:path";import{createUnplugin as u}from"unplugin";import{mkdir as d,readFile as f,readdir as p,writeFile as m}from"node:fs/promises";function h(e){return e.replace(/\\/g,`/`)}const g=/\.(test|spec|d)\.(ts|tsx)$/,_=/^\s*export\s+(?:const|let|var|async\s+function|function)\s+load\b/m;async function v(e){try{let t=(await f(e,`utf8`)).replace(/^\s*\/\/.*$/gm,``);return _.test(t)}catch{return!1}}function y(e){return e.startsWith(`[...`)&&e.endsWith(`]`)?`**:${e.slice(4,-1)}`:e.startsWith(`[`)&&e.endsWith(`]`)?`:${e.slice(1,-1)}`:e}function b(e){return e.startsWith(`(`)&&e.endsWith(`)`)?``:y(e)}function x(e,t){let n=h(s(e,t)),r=n.slice(0,-a(n).length).split(`/`),i=[...r.slice(0,-1).map(b),y(r.at(-1))];return i.at(-1)===`index`&&i.pop(),`/`+i.filter(Boolean).join(`/`)||`/`}function S(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 C(e){return e===`/`?3:e.includes(`**`)?0:e.includes(`:`)?1:2}function w(e){return[...e].sort((e,t)=>{let n=C(t.pattern)-C(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 T(e,t,n,r){let a=h(s(e,i(t))),c=a===``?[]:a.split(`/`);return[e,...c.map((t,n)=>o(e,...c.slice(0,n+1)))].flatMap(e=>{let t=`${o(e,r)}.tsx`;if(n.has(t))return[t];let i=`${o(e,r)}.ts`;return n.has(i)?[i]:[]})}async function E(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 p(e,{withFileTypes:!0})){let i=o(e,r.name);r.isDirectory()?n.push(...await E(i,t+1)):r.isFile()&&/\.(ts|tsx)$/.test(r.name)&&!g.test(r.name)&&n.push(i)}}catch(e){if(e.code===`ENOENT`)return[];throw e}return n}async function D(e){let t=await E(e),n=new Set(t),i=t.filter(e=>!r(e).startsWith(`+`)),a=new Map,o=e=>{let t=a.get(e);return t||(t=v(e),a.set(e,t)),t};return Promise.all(i.map(async t=>{let r=x(e,t),i=T(e,t,n,`+layout`),a=T(e,t,n,`+error`),[s,...c]=await Promise.all([v(t),...i.map(o)]),l=i.filter((e,t)=>c[t]);return{file:t,pattern:r,name:S(r),layouts:i,errors:a,hasLoader:s,loaderLayouts:l}}))}function O(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 k(e){return{serverFile:o(e,`pages.server.ts`),clientFile:o(e,`pages.client.ts`),loadersFile:o(e,`loaders.ts`)}}async function A(e,t,n={}){let r=n.mode??`spa`,i=n.interceptLinks,a=r===`static`,o=w(await D(e));O(o,e),await d(t,{recursive:!0});let{serverFile:s,clientFile:c,loadersFile:l}=k(t),u=await P(s,j(o,s)),f=await P(c,M(o,c,{isStatic:a,interceptLinks:i}));a||await P(l,N(o,l,s)),(u||f)&&await F(t)}function j(e,t){let n=e=>{let n=h(s(i(t),e));return n.startsWith(`.`)?n:`./${n}`},r=[`import { router, wrapLayout, wrapError } from "@ilha/router";`,`import type { Island } from "ilha";`],a=[],o=[],c=[];for(let[t,i]of e.entries()){r.push(`import { default as _page${t} } from ${JSON.stringify(n(i.file))};`);for(let[e,a]of i.layouts.entries())r.push(`import { default as _layout${t}_${e} } from ${JSON.stringify(n(a))};`);for(let[e,a]of i.errors.entries())r.push(`import { default as _error${t}_${e} } from ${JSON.stringify(n(a))};`);let s=`_page${t}`;for(let e=i.errors.length-1;e>=0;e--)s=`wrapError(_error${t}_${e}, ${s})`;for(let e=i.layouts.length-1;e>=0;e--)s=`wrapLayout(_layout${t}_${e}, ${s})`;let l=`_wrapped${t}`;a.push(`const ${l} = ${s};`),o.push(` ${JSON.stringify(i.name)}: ${l}`+(t<e.length-1?`,`:``)),c.push(` .route(${JSON.stringify(i.pattern)}, ${l})`+(i.hasLoader||i.loaderLayouts.length>0?`.markLoader(${JSON.stringify(i.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";`,``,...r,``,...a,``,`export const registry: Record<string, Island<any, any>> = {`,...o,`};`,``,`export const pageRouter = router()`,...c,` ;`].join(`
|
|
2
|
-
`)}function M(e,t,n){let{isStatic:r,interceptLinks:a}=n,o=e=>{let n=h(s(i(t),e));return n.startsWith(`.`)?n:`./${n}`},c=e=>`${o(e)}?client`,l=r?[`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 i=`_page${t}`;for(let e=n.errors.length-1;e>=0;e--)i=`wrapError(_error${t}_${e}, ${i})`;for(let e=n.layouts.length-1;e>=0;e--)i=`wrapLayout(_layout${t}_${e}, ${i})`;let a=`_wrapped${t}`;u.push(`const ${a} = ${i};`),d.push(` ${JSON.stringify(n.name)}: ${a}`+(t<e.length-1?`,`:``)),r||f.push(` .route(${JSON.stringify(n.pattern)}, ${a})`+(n.hasLoader||n.loaderLayouts.length>0?`.markLoader(${JSON.stringify(n.pattern)})`:``))}let p=r?`_router({ mode: "static" })`:`router(${a===!1?`{ interceptLinks: false }`:``})`,m=[`// @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 r?m.push(`export const pageRouter = ${p};`):m.push(`export const pageRouter = ${p}`,...f,` ;`),m.join(`
|
|
3
|
-
`)}function N(e,t,n){let r=e=>{let n=h(s(i(t),e));return n.startsWith(`.`)?n:`./${n}`},a=e.filter(e=>e.hasLoader||e.loaderLayouts.length>0);if(a.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 o=r(n).replace(/\.tsx?$/,``),c=[`import { pageRouter } from ${JSON.stringify(o)};`],l=!1,u=[];for(let[e,t]of a.entries()){let n=[];for(let[i,a]of t.loaderLayouts.entries()){let t=`_p${e}_l${i}`;c.push(`import { load as ${t} } from ${JSON.stringify(r(a))};`),n.push(t)}if(t.hasLoader){let i=`_p${e}`;c.push(`import { load as ${i} } from ${JSON.stringify(r(t.file))};`),n.push(i)}let i=n.length===1?n[0]:`composeLoaders([${n.join(`, `)}])`;n.length>1&&(l=!0),u.push(`pageRouter.attachLoader(${JSON.stringify(t.pattern)}, ${i});`)}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 P(e,t){try{if(await f(e,`utf8`)===t)return!1}catch{}return await m(e,t,`utf8`),!0}async function F(e){await P(o(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 I=`\0ilha:pages/server`,L=`\0ilha:pages/client`,R=`\0ilha:loaders`,z=[I,L,R];function B(e){try{return JSON.parse(t(e,`utf8`))}catch{return null}}function V(t,n){let r=t;for(;;){let t=o(r,`node_modules`,n,`package.json`);if(e(t))return B(t);let a=i(r);if(a===r)return null;r=a}}function H(e){let t=B(o(e,`package.json`));if(!t)return[];let n={...t.dependencies??{},...t.devDependencies??{}},r=[];for(let t of Object.keys(n)){if(t===`ilha`)continue;let n=V(e,t);if(!n)continue;let i=n.peerDependencies??{},a=n.dependencies??{};(`ilha`in i||`ilha`in a)&&r.push(t)}return r}function U(e,t){let n=c(e,t.dir??`src/pages`),r=c(e,t.outDir??`.ilha`),{serverFile:i,clientFile:a,loadersFile:o}=k(r);return{pagesDir:n,outDir:r,serverFile:i,clientFile:a,loadersFile:o}}function W(e){let t,n,i,a,o,s=r=>{({pagesDir:t,outDir:n,serverFile:i,clientFile:a,loadersFile:o}=U(r,e))},c=async()=>{try{await A(t,n,{mode:e.mode,interceptLinks:e.interceptLinks})}catch(e){console.error(`[ilha:pages] codegen failed:`,e)}},u=e=>e===t||e.startsWith(t+l);return{get pagesDir(){return t},get outDir(){return n},get serverFile(){return i},get clientFile(){return a},get loadersFile(){return o},setPaths:s,regen:c,shouldRegenOnChange:e=>{if(!u(e))return!1;let t=r(e);return t.startsWith(`+`)||/\.(ts|tsx)$/.test(t)},isUnderPagesDir:u}}async function G(e,t,n){n(t)&&await e.regen()}function K(e,t,n){if(t===`ilha:pages/server`)return I;if(t===`ilha:pages/client`)return L;if(t===`ilha:loaders`)return R;if(t.endsWith(`?client`)){let e=t.slice(0,-7);return(n?c(n.replace(/\?.*$/,``),`..`,e):c(e))+`?client`}}function q(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 J(e,t){return async n=>{e.isUnderPagesDir(n)&&(await e.regen(),await t())}}function Y(e,t){let r=n(e.pagesDir,{recursive:!0},(n,r)=>{r&&t(o(e.pagesDir,r))});return()=>r.close()}const X=u((e={})=>{let t=W(e);return{name:`ilha:pages`,async buildStart(){t.pagesDir||t.setPaths(process.cwd()),this.addWatchFile?.(t.pagesDir),await t.regen()},async watchChange(e){await G(t,e,e=>t.shouldRegenOnChange(e))},resolveId(e,n){return K(t,e,n)},load(e){return q(t,e)},vite:{config(e){let t=[`ilha`,`@ilha/store`,`@ilha/router`,`alien-signals`,...H(e.root?c(e.root):process.cwd())],n=e.ssr?.noExternal,r=n===!0?!0:[...new Set([...Array.isArray(n)?n:n==null?[]:[n],...t])];return{resolve:{dedupe:[...new Set([...e.resolve?.dedupe??[],...t])]},ssr:{noExternal:r},optimizeDeps:{...e.optimizeDeps,include:[...new Set([...e.optimizeDeps?.include??[],`ilha`,`ilha/jsx-runtime`,`ilha/jsx-dev-runtime`,`@ilha/store`,`alien-signals`])]}}},configResolved(e){t.setPaths(e.root)},configureServer(e){e.watcher.add(t.pagesDir);let n=J(t,async()=>{for(let t of z){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=J(t,()=>{e.watching&&e.invalidate()}),r;e.hooks.watchRun.tap(`ilha:pages`,()=>{r?.(),r=Y(t,n)}),e.hooks.shutdown.tap(`ilha:pages`,()=>r?.())}}});export{X as t};
|
package/dist/src-B5twcrhj.js
DELETED
|
@@ -1,4 +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`),ee=Symbol.for(`ilha.router.wrapLayout.handler`);function te(e){let t=e.match(/^<([a-zA-Z][\w-]*)\s[^>]*>([\s\S]*)<\/\1>\s*$/);return t?t[2]:e}function ne(e){let t=e.match(/^<([a-zA-Z][\w-]*)\s([^>]*)>/);return t?{tag:t[1],attrs:t[2]}:null}const re=/<(pre|script|style|textarea)\b/i;function ie(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 ae(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(re);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=ie(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 oe(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=ae(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 T(e,t,n){let r=oe(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 se(e,t){let n=e[ee];if(!n)return e.toString(t);let r=e[w]??e;return n(Object.assign(r.key(`page`),{toString:()=>``})).toString(t)}async function ce(e,t,n,r){let i=te(await t.hydratable(n,r));return T(T(se(e,n),``,`innermost`),i,`innermost`)}function le(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[ee]=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){let n=e=>{delete e._skipOnMount,t.setAttribute(`data-ilha-state`,JSON.stringify(e))};if(t.hasAttribute(`data-ilha-state`)){let r=e.getAttribute(`data-ilha-state`);if(r)try{n(JSON.parse(r));return}catch{}let i=t.getAttribute(`data-ilha-state`);if(i)try{let e=JSON.parse(i);delete e._skipOnMount,t.setAttribute(`data-ilha-state`,JSON.stringify(e))}catch{}return}let r=e.getAttribute(`data-ilha-state`);if(r)try{n(JSON.parse(r));return}catch{}t.childNodes.length>0&&t.setAttribute(`data-ilha-state`,`{}`)}function c(e){let n=e[t];if(typeof n!=`function`)return;e[t]=(e,t)=>{let r=e.closest(`[data-ilha]`);return r&&r!==e&&s(r,e),n(e,t)};let r=e.mount.bind(e);e.mount=(e,t)=>{let n=e.closest(`[data-ilha]`);return n&&n!==e&&s(n,e),r(e,t)}}c(r);let l=a.mount.bind(a),u=a[t];function d(e){s(e,o(e))}return a.mount=(e,t)=>(d(e),l(e,t)),a[t]=(e,t)=>(d(e),typeof u==`function`?u(e,t):{unmount:l(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=ne(o);if(!s)return o;let c=te(o);i&&(c=await ce(i,r,n,t));let l=T(T(se(a,n),``,`first`),c,`first`);return`<${s.tag} ${s.attrs}>${l}</${s.tag}>`},a}function ue(n,r){let i=e.render(()=>{try{return r.toString()}catch(e){let t={path:k(),params:A(),search:j(),hash:M()};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:k(),params:A(),search:j(),hash:M()},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:k(),params:A(),search:j(),hash:M()},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 de(e){return e}function fe(e){let t=new Map;for(let[n,r]of Object.entries(e))t.has(r)||t.set(r,n);return t}const pe=`/__ilha/loader`,E=new Map;async function D(e,t){let n=E.get(e);if(n){E.delete(e);try{return await n}catch{}}let r=`${pe}?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 O(e){if(!_||E.has(e))return;let t=e.split(`?`)[0]??``;if(!s(F,`GET`,t)?.data?.hasLoader)return;let n=D(e).catch(e=>({kind:`error`,status:0,message:e?.message??`prefetch failed`}));E.set(e,n)}async function me(e,t,n,r,i,a){if(!e)return t.innerHTML=`<div data-router-empty></div>`,()=>{};let o=!!s(F,`GET`,n.split(`?`)[0]??``)?.data?.hasLoader,c={},l=o?await D(n,r):{kind:`data`,data:{}};if(l.kind===`redirect`)return V(l.to,{replace:!0}),()=>{};if(l.kind===`error`){let e=String(l.message).replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`);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>`,()=>{};c=l.data;let u={entries:[]};if(!i){console.warn(`[ilha-router] No registry provided for client-side navigation. Island will not be interactive.`);let n=await X(u,()=>e.toString(c));return Y(u.entries),t.innerHTML=`<div data-router-view>${n}</div>`,()=>{}}let d=a?.get(e)??Object.entries(i).find(([,t])=>t===e)?.[0];if(!d){console.warn(`[ilha-router] Island not found in registry for client-side navigation.`);let n=await X(u,()=>e.toString(c));return Y(u.entries),t.innerHTML=`<div data-router-view>${n}</div>`,()=>{}}let f=await X(u,()=>e.hydratable(c,{name:d,as:`div`,snapshot:!0}));Y(u.entries),t.innerHTML=`<div data-router-view>${f}</div>`;let p=t.querySelector(`[data-ilha="${d}"]`);return p?e.mount(p):()=>{}}const k=n(`router.path`,``),A=n(`router.params`,{}),j=n(`router.search`,``),M=n(`router.hash`,``);function he(){return{path:k,params:A,search:j,hash:M}}const N=n(`router.active`,null);let P=[],F=o(),I=new Map,L=new Map;function R(e){let t={};if(e)for(let[n,r]of Object.entries(e))t[n]=decodeURIComponent(r);return t}function ge(e){let t=typeof e==`string`?new URL(e,`http://localhost`):e,n=s(F,`GET`,t.pathname);k(t.pathname),A(R(n?.params)),j(t.search),M(t.hash),N(n?.data?.island??null)}function z(){let e=g().readLocation(),t=s(F,`GET`,e.pathname);k(e.pathname),A(R(t?.params)),j(e.search),M(e.hash),N(t?.data?.island??null)}function B(){_&&z()}function V(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),z())}function H(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(),V(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&&O(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 U=e.render(()=>{let e=N();return e?`<div data-router-view>${e.toString()}</div>`:`<div data-router-empty></div>`}),_e=e.state(`href`,``).state(`label`,``).on(`[data-link]@click`,({state:e,event:t})=>{t.preventDefault(),V(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;O(e.pathname+e.search);return}catch{return}O(t)}}).render(({state:e})=>r`<a data-link data-prefetch href="${()=>g().toLinkHref(e.href())}"
|
|
2
|
-
>${e.label}</a
|
|
3
|
-
>`);function ve(e){let t=s(F,`GET`,k());return t?I.get(t.data.island)===e:!1}const W=`data-ilha-head`,G=`data-ilha-router-html`,ye=`data-ilha-router-body`;let K=null,q=null,be=null;async function xe(){return q||(be||=import(`node:async_hooks`).then(({AsyncLocalStorage:e})=>(q=new e,q)),be)}function Se(){return _?K:q?.getStore()??null}function Ce(e){let t=Se();if(!t){_||console.warn(`[ilha-router] head() called outside an SSR render window — ignored.`);return}t.entries.push(e)}function J(e){return typeof CSS<`u`&&typeof CSS.escape==`function`?CSS.escape(e):e.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`)}function we(e){return`charset`in e?`meta[charset][${W}]`:`name`in e?`meta[name="${J(e.name)}"][${W}]`:`property`in e?`meta[property="${J(e.property)}"][${W}]`:`http-equiv`in e?`meta[http-equiv="${J(e[`http-equiv`])}"][${W}]`:null}function Te(e){return e.rel&&e.href?`link[rel="${J(e.rel)}"][href="${J(e.href)}"][${W}]`:null}function Y(e){if(!_)return;let t,n,r=[],i=[],a={},o={};for(let s of e)s.title!==void 0&&(t=s.title),s.titleTemplate!==void 0&&(n=s.titleTemplate),s.meta&&r.push(...s.meta),s.link&&i.push(...s.link),s.htmlAttrs&&(a={...a,...s.htmlAttrs}),s.bodyAttrs&&(o={...o,...s.bodyAttrs});let s=ke(t,n);s!==void 0&&(document.title=s);let c=Q(r,Oe),l=Q(i,e=>`${e.rel??``}:${e.href??``}`),u=new Set;for(let e of c){let t=we(e);if(!t)continue;let n=document.querySelector(t);n||(n=document.createElement(`meta`),n.setAttribute(W,``),document.head.appendChild(n));for(let[t,r]of Object.entries(e))n.setAttribute(t,r);u.add(n)}for(let e of l){let t=Te(e),n=t?document.querySelector(t):null;n||(n=document.createElement(`link`),n.setAttribute(W,``),document.head.appendChild(n));for(let[t,r]of Object.entries(e))n.setAttribute(t,r);u.add(n)}for(let e of[...document.head.querySelectorAll(`[${W}]`)])u.has(e)||e.remove();let d=document.documentElement,f=(d.getAttribute(G)??``).split(/\s+/).filter(Boolean);for(let e of f)d.removeAttribute(e);let p=Object.keys(a);for(let[e,t]of Object.entries(a))d.setAttribute(e,t);p.length?d.setAttribute(G,p.join(` `)):d.removeAttribute(G);let m=document.body,h=(m.getAttribute(ye)??``).split(/\s+/).filter(Boolean);for(let e of h)m.removeAttribute(e);let g=Object.keys(o);for(let[e,t]of Object.entries(o))m.setAttribute(e,t);g.length?m.setAttribute(ye,g.join(` `)):m.removeAttribute(ye)}async function X(e,t){if(_){let n=K;K=e;try{return await t()}finally{K=n}}return await(await xe()).run(e,()=>Promise.resolve(t()))}const Ee={"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`};function De(e){return String(e).replace(/[&<>"']/g,e=>Ee[e])}function Z(e){return Object.entries(e).map(([e,t])=>` ${e}="${De(t)}"`).join(``)}function Oe(e){return`charset`in e?`charset`:`name`in e?`name:${e.name}`:`property`in e?`property:${e.property}`:`http-equiv`in e?`http-equiv:${e[`http-equiv`]}`:JSON.stringify(e)}function Q(e,t){let n=new Map;for(let r of e)n.set(t(r),r);return[...n.values()]}function ke(e,t){return t===void 0?e:typeof t==`function`?t(e):t.replace(/%s/g,e??``)}function $(e){let t,n,r=[],i=[],a=[],o={},s={};for(let c of e)c.title!==void 0&&(t=c.title),c.titleTemplate!==void 0&&(n=c.titleTemplate),c.meta&&r.push(...c.meta),c.link&&i.push(...c.link),c.script&&a.push(...c.script),c.htmlAttrs&&(o={...o,...c.htmlAttrs}),c.bodyAttrs&&(s={...s,...c.bodyAttrs});let c=ke(t,n),l=[];c!==void 0&&l.push(`<title>${De(c)}</title>`);for(let e of Q(r,Oe))l.push(`<meta${Z({...e,[W]:``})} />`);for(let e of Q(i,e=>`${e.rel??``}:${e.href??``}`))l.push(`<link${Z({...e,[W]:``})} />`);for(let e of a){let{children:t,...n}=e;l.push(`<script${Z(n)}>${t??``}<\/script>`)}return{headTags:l.join(`
|
|
4
|
-
`),htmlAttrs:Z(o),bodyAttrs:Z(s)}}function Ae(e){return typeof e==`string`?new URL(e,`http://localhost`):e}function je(e){try{return new Request(e.toString())}catch{return{url:e.toString(),headers:new Headers}}}async function Me(e,t,n,r,i,a){let o=[],s=a??(e=>o.push(e));try{let a={kind:`data`,data:await e({params:n,request:r,url:t,signal:i,head:s})??{}};return o.length>0&&(a.head=$(o)),a}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 Ne(t={}){let n=t.mode??`spa`,r=t.interceptLinks!==!1;P=[],F=o(),I=new Map,L=new Map;let c=null,l=null,u={route(e,t,n){let r=!!n,i={island:t,loader:n,hasLoader:r};return P.push({pattern:e,island:t,loader:n,hasLoader:r}),a(F,`GET`,e,i),L.set(e,i),I.has(t)||I.set(t,e),u},attachLoader(e,t){let n=L.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=P.find(t=>t.pattern===e);return r&&(r.loader=t,r.hasLoader=!0),u},markLoader(e){let t=L.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=P.find(t=>t.pattern===e);return n&&(n.hasLoader=!0),u},routes(){return P.map(e=>({...e}))},prime:B,hydrateStatic(e,t={}){if(!_)return()=>{};let n=t.root??document.body;B();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(z(),n===`static`)return console.warn(`[ilha-router] router.mount() called in static mode. Use router.hydrateStatic(registry) instead.`),()=>{};let d=!0;c=g().onChange(()=>{d&&z()}),l=o??r?H(document):null;let f=null,p=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=N(),r=a?fe(a):void 0,i=0,o=e.render(()=>{let e=N();if(e!==n){let o=++i;p?.abort(),p=new AbortController;let s=p.signal;queueMicrotask(async()=>{if(o===i){f?.();try{let n=g().readLocation();f=await me(e,t,n.pathname+n.search,s,a,r)}catch(e){if(e?.name===`AbortError`)return;throw e}n=e}})}return``}),m=document.createElement(`div`);m.style.display=`none`,u.appendChild(m);let _=o.mount(m);return(async()=>{let e=N();if(!e)return;let t=g().readLocation(),n=t.pathname+t.search,r=s(F,`GET`,t.pathname)?.data?.hasLoader?await D(n):{kind:`data`,data:{}};if(r.kind===`redirect`||r.kind===`error`)return;let i=r.kind===`data`?r.data:{},a={entries:[]};await X(a,()=>e.toString(i)),d&&Y(a.entries)})(),()=>{d=!1,++i,p?.abort(),_(),m.remove(),f?.(),l?.(),c?.(),l=null,c=null}}let m=null,v=null,y=0;f=U.mount(u);async function b(e,t){if(m?.(),m=null,v=e,!e)return;let n=u?.querySelector(`[data-router-view]`);if(!n)return;let r=g().readLocation(),i=s(F,`GET`,r.pathname)?.data?.hasLoader?await D(r.pathname+r.search,t):{kind:`data`,data:{}};if(t.aborted)return;if(i.kind===`redirect`){V(i.to,{replace:!0});return}let a=i.kind===`data`?i.data:{},o={entries:[]},c=await X(o,()=>e.toString(a));Y(o.entries),n.innerHTML=c,m=e.mount(n,a)}p=new AbortController,b(N(),p.signal);let x=e.render(()=>{let e=N();if(e!==v){let t=++y;p?.abort(),p=new AbortController;let n=p.signal;queueMicrotask(()=>{t===y&&b(e,n)})}return``}),S=document.createElement(`div`);S.style.display=`none`,u.appendChild(S);let C=x.mount(S);return()=>{d=!1,++y,p?.abort(),m?.(),C(),S.remove(),f?.(),l?.(),c?.(),l=null,c=null}},render(e){return ge(e),U.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{baseHead:i,...a}=n,o=Ae(e);ge(o);let c=s(F,`GET`,o.pathname),l=c?.data?.island??null;if(!l)return{kind:`html`,html:`<div data-router-empty></div>`,status:404,head:i?$([i]):void 0};let u={entries:i?[i]:[]},d={};if(c?.data?.loader){let e=r??je(o),t=new AbortController,n=await Me(c.data.loader,o,A(),e,t.signal,e=>u.entries.push(e));if(n.kind===`redirect`)return{kind:`redirect`,to:n.to,status:n.status};if(n.kind===`error`){let e=String(n.message).replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`),t=`<div data-router-view data-router-error="${n.status}">${e}</div>`;return{kind:`error`,status:n.status,message:n.message,html:t,head:$(u.entries)}}d=n.data}let f=fe(t).get(l);return f?{kind:`html`,html:`<div data-router-view>${await X(u,()=>l.hydratable(d,{name:f,as:`div`,snapshot:!0,...a}))}</div>`,head:$(u.entries)}:(console.warn(`[ilha-router] renderHydratable: active island for "${k()}" 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>${await X(u,()=>l.toString(d))}</div>`,head:$(u.entries)})},async runLoader(e,t){let n=Ae(e),r=s(F,`GET`,n.pathname);if(!r?.data?.island)return{kind:`not-found`};if(!r.data.loader)return{kind:`data`,data:{}};let i=R(r.params),a=t??je(n),o=new AbortController,c={entries:[]};return Me(r.data.loader,n,i,a,o.signal,e=>c.entries.push(e)).then(e=>e.kind!==`data`||c.entries.length===0?e:{...e,head:$(c.entries)})},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;B();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:Ne,navigate:V,useRoute:he,isActive:ve,enableLinkInterception:H,prime:B,prefetch:O,RouterView:U,RouterLink:_e,loader:v,redirect:x,error:S,composeLoaders:C,head:Ce};export{Pe as C,h as D,le as E,m as O,$ as S,ue as T,M as _,U as a,j as b,H as c,ve as d,v as f,x as g,B as h,_e as i,S as l,O as m,b as n,C as o,V as p,y as r,de as s,pe as t,Ce as u,A as v,he as w,Ne as x,k as y};
|