dalila 1.9.2 → 1.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -9,27 +9,30 @@ const routeArgs = args.slice(2);
9
9
  const WATCH_DEBOUNCE_MS = 120;
10
10
  function showHelp() {
11
11
  console.log(`
12
- Dalila CLI
12
+ 🐰 ✂️ Dalila CLI
13
13
 
14
14
  Usage:
15
15
  dalila routes generate [options] Generate routes + manifest from app file structure
16
16
  dalila routes init Initialize app and generate routes outputs
17
17
  dalila routes watch [options] Watch routes and regenerate outputs on changes
18
18
  dalila routes --help Show routes command help
19
+ dalila check [path] [--strict] Static analysis of HTML templates against loaders
19
20
  dalila help Show this help message
20
21
 
21
22
  Options:
22
23
  --output <path> Output file (default: ./routes.generated.ts)
23
24
 
24
25
  Examples:
25
- dalila routes generate
26
- dalila routes generate --output src/routes.generated.ts
27
- dalila routes init
26
+ npx dalila routes generate
27
+ npx dalila routes generate --output src/routes.generated.ts
28
+ npx dalila routes init
29
+ npx dalila check
30
+ npx dalila check src/app --strict
28
31
  `);
29
32
  }
30
33
  function showRoutesHelp() {
31
34
  console.log(`
32
- Dalila CLI - Routes
35
+ 🐰 ✂️ Dalila CLI - Routes
33
36
 
34
37
  Usage:
35
38
  dalila routes generate [options] Generate routes + manifest from app file structure
@@ -41,10 +44,33 @@ Options:
41
44
  --output <path> Output file (default: ./routes.generated.ts)
42
45
 
43
46
  Examples:
44
- dalila routes generate
45
- dalila routes generate --output src/routes.generated.ts
46
- dalila routes watch
47
- dalila routes init
47
+ npx dalila routes generate
48
+ npx dalila routes generate --output src/routes.generated.ts
49
+ npx dalila routes watch
50
+ npx dalila routes init
51
+ `);
52
+ }
53
+ function showCheckHelp() {
54
+ console.log(`
55
+ 🐰 ✂️ Dalila CLI - Check
56
+
57
+ Usage:
58
+ dalila check [path] [options] Static analysis of HTML templates
59
+
60
+ Validates that identifiers used in HTML templates ({expr}, d-* directives)
61
+ match the return type of the corresponding loader() in TypeScript.
62
+
63
+ Arguments:
64
+ [path] App directory to check (default: src/app)
65
+
66
+ Options:
67
+ --strict Fail when exported loader return keys cannot be inferred
68
+ --help, -h Show this help message
69
+
70
+ Examples:
71
+ npx dalila check
72
+ npx dalila check src/app
73
+ npx dalila check --strict
48
74
  `);
49
75
  }
50
76
  function hasHelpFlag(list) {
@@ -156,7 +182,7 @@ function resolveGenerateConfig(cliArgs, cwd = process.cwd()) {
156
182
  async function generateRoutes(cliArgs) {
157
183
  const { appDir, outputPath } = resolveGenerateConfig(cliArgs);
158
184
  console.log('');
159
- console.log('🚀 Dalila Routes Generator');
185
+ console.log('🐰 ✂️ Dalila Routes Generator');
160
186
  console.log('');
161
187
  try {
162
188
  await generateRoutesFile(appDir, outputPath);
@@ -217,7 +243,7 @@ function watchRoutes(cliArgs) {
217
243
  process.exit(1);
218
244
  }
219
245
  console.log('');
220
- console.log('👀 Dalila Routes Watch');
246
+ console.log('🐰 ✂️ Dalila Routes Watch');
221
247
  console.log(` app: ${appDir}`);
222
248
  console.log(` output: ${outputPath}`);
223
249
  console.log('');
@@ -331,6 +357,22 @@ async function main() {
331
357
  process.exit(1);
332
358
  }
333
359
  }
360
+ else if (command === 'check') {
361
+ const checkArgs = args.slice(1);
362
+ if (hasHelpFlag(checkArgs)) {
363
+ showCheckHelp();
364
+ }
365
+ else {
366
+ const strict = checkArgs.includes('--strict');
367
+ const positional = checkArgs.filter(a => !a.startsWith('--'));
368
+ const appDir = positional[0]
369
+ ? path.resolve(positional[0])
370
+ : resolveDefaultAppDir(process.cwd());
371
+ const { runCheck } = await import('./check.js');
372
+ const exitCode = await runCheck(appDir, { strict });
373
+ process.exit(exitCode);
374
+ }
375
+ }
334
376
  else if (command === '--help' || command === '-h') {
335
377
  showHelp();
336
378
  }
@@ -1,3 +1,28 @@
1
+ export type RouteFileType = 'middleware' | 'layout' | 'page' | 'error' | 'pending' | 'notFound';
2
+ export interface RouteFile {
3
+ path: string;
4
+ type: RouteFileType;
5
+ importName: string;
6
+ isHtml: boolean;
7
+ htmlContent?: string;
8
+ htmlPath?: string;
9
+ sourceContent?: string;
10
+ namedExports?: string[];
11
+ tags?: string[];
12
+ lazy?: boolean;
13
+ }
14
+ export interface RouteNode {
15
+ fsPath: string;
16
+ segment: string;
17
+ routePath: string;
18
+ files: RouteFile[];
19
+ children: RouteNode[];
20
+ }
21
+ export declare function extractParamKeys(routePattern: string): string[];
22
+ export declare function injectHtmlPathTemplates(node: RouteNode, routesDir: string, projectRoot: string): Promise<void>;
23
+ export declare function findProjectRoot(startDir: string): Promise<string | null>;
24
+ export declare function findFile(node: RouteNode, type: RouteFileType, isHtml?: boolean): RouteFile | undefined;
25
+ export declare function buildRouteTree(routesDir: string, currentPath?: string, currentSegment?: string): Promise<RouteNode>;
1
26
  export declare function collectHtmlPathDependencyDirs(routesDir: string): string[];
2
27
  /**
3
28
  * Generate route files from the app directory.
@@ -128,7 +128,7 @@ function parseRouteParamSegment(segment) {
128
128
  }
129
129
  return { key: raw, isCatchAll: false, isOptionalCatchAll: false };
130
130
  }
131
- function extractParamKeys(routePattern) {
131
+ export function extractParamKeys(routePattern) {
132
132
  const keys = [];
133
133
  const segments = normalizeRoutePath(routePattern).split('/').filter(Boolean);
134
134
  for (const segment of segments) {
@@ -257,7 +257,7 @@ function resolveHtmlPath(htmlPath, routesDir, filePath, projectRoot) {
257
257
  }
258
258
  return path.resolve(routeFileDir, htmlPath);
259
259
  }
260
- async function injectHtmlPathTemplates(node, routesDir, projectRoot) {
260
+ export async function injectHtmlPathTemplates(node, routesDir, projectRoot) {
261
261
  const syntheticHtmlFiles = [];
262
262
  for (const file of node.files) {
263
263
  if (file.isHtml || !file.htmlPath)
@@ -299,7 +299,7 @@ const DEFAULT_ROUTE_TAG_POLICY = {
299
299
  ],
300
300
  priority: ['auth', 'public']
301
301
  };
302
- async function findProjectRoot(startDir) {
302
+ export async function findProjectRoot(startDir) {
303
303
  let current = path.resolve(startDir);
304
304
  while (true) {
305
305
  if (await pathExists(path.join(current, 'package.json'))) {
@@ -409,7 +409,7 @@ function validateManifestTags(entries, policy) {
409
409
  }
410
410
  }
411
411
  }
412
- function findFile(node, type, isHtml) {
412
+ export function findFile(node, type, isHtml) {
413
413
  return node.files.find((file) => {
414
414
  if (file.type !== type)
415
415
  return false;
@@ -467,16 +467,31 @@ function pageProps(pageHtml, pageTs) {
467
467
  if (hasNamedExport(pageTs, 'onMount')) {
468
468
  if (pageTs.lazy) {
469
469
  const lazyLoader = `${pageTs.importName}_lazy`;
470
- props.push(`onMount: (root: HTMLElement) => ${lazyLoader}().then(mod => {
470
+ props.push(`onMount: (root: HTMLElement, data: unknown, ctx: unknown) => ${lazyLoader}().then(mod => {
471
471
  if (typeof (mod as any).onMount === 'function') {
472
- (mod as any).onMount(root);
472
+ return (mod as any).onMount(root, data, ctx);
473
473
  }
474
+ return undefined;
474
475
  })`);
475
476
  }
476
477
  else {
477
478
  props.push(`onMount: ${moduleExport(pageTs, 'onMount')}`);
478
479
  }
479
480
  }
481
+ if (hasNamedExport(pageTs, 'onUnmount')) {
482
+ if (pageTs.lazy) {
483
+ const lazyLoader = `${pageTs.importName}_lazy`;
484
+ props.push(`onUnmount: (root: HTMLElement, data: unknown, ctx: unknown) => ${lazyLoader}().then(mod => {
485
+ if (typeof (mod as any).onUnmount === 'function') {
486
+ return (mod as any).onUnmount(root, data, ctx);
487
+ }
488
+ return undefined;
489
+ })`);
490
+ }
491
+ else {
492
+ props.push(`onUnmount: ${moduleExport(pageTs, 'onUnmount')}`);
493
+ }
494
+ }
480
495
  }
481
496
  else {
482
497
  props.push(`view: (ctx) => fromHtml(${viewConst}, { data: ${routeDataExpr()}, scope: ctx.scope })`);
@@ -501,6 +516,12 @@ function pageProps(pageHtml, pageTs) {
501
516
  if (hasNamedExport(pageTs, 'validation')) {
502
517
  props.push(`validation: ${moduleExport(pageTs, 'validation', { allowValue: true })}`);
503
518
  }
519
+ if (hasNamedExport(pageTs, 'onMount')) {
520
+ props.push(`onMount: ${moduleExport(pageTs, 'onMount')}`);
521
+ }
522
+ if (hasNamedExport(pageTs, 'onUnmount')) {
523
+ props.push(`onUnmount: ${moduleExport(pageTs, 'onUnmount')}`);
524
+ }
504
525
  return props;
505
526
  }
506
527
  function layoutProps(indent, layoutHtml, layoutTs) {
@@ -591,7 +612,7 @@ function buildRouteTreeSync(routesDir, currentPath = '', currentSegment = '') {
591
612
  }
592
613
  return node;
593
614
  }
594
- async function buildRouteTree(routesDir, currentPath = '', currentSegment = '') {
615
+ export async function buildRouteTree(routesDir, currentPath = '', currentSegment = '') {
595
616
  const node = {
596
617
  fsPath: currentPath.replace(/\\/g, '/'),
597
618
  segment: currentSegment,
@@ -33,6 +33,9 @@ export type RouteRedirectResult = string | null | undefined | Promise<string | n
33
33
  export type RouteMiddlewareResult = RouteGuardResult;
34
34
  export type RouteMiddleware = (ctx: RouteCtx) => RouteMiddlewareResult;
35
35
  export type RouteMiddlewareResolver = RouteMiddleware[] | ((ctx: RouteCtx) => RouteMiddleware[] | null | undefined | Promise<RouteMiddleware[] | null | undefined>);
36
+ export type RouteMountCleanup = () => void;
37
+ export type RouteMountResult = void | RouteMountCleanup | Promise<void | RouteMountCleanup>;
38
+ export type RouteUnmountResult = void | Promise<void>;
36
39
  /**
37
40
  * Route definition.
38
41
  *
@@ -40,19 +43,20 @@ export type RouteMiddlewareResolver = RouteMiddleware[] | ((ctx: RouteCtx) => Ro
40
43
  * data loading, state views, children, guards, middleware, redirects,
41
44
  * and params/query validation.
42
45
  */
43
- export interface RouteTable {
46
+ export interface RouteTable<T = any> {
44
47
  path: string;
45
48
  id?: string;
46
49
  score?: number;
47
50
  paramKeys?: string[];
48
51
  tags?: string[];
49
- view?: (ctx: RouteCtx, data: any) => Node | DocumentFragment | Node[];
50
- layout?: (ctx: RouteCtx, child: Node | DocumentFragment | Node[], data: any) => Node | DocumentFragment | Node[];
51
- loader?: (ctx: RouteCtx) => Promise<any>;
52
- preload?: (ctx: RouteCtx) => Promise<any>;
53
- onMount?: (root: HTMLElement) => void;
52
+ view?: (ctx: RouteCtx, data: T) => Node | DocumentFragment | Node[];
53
+ layout?: (ctx: RouteCtx, child: Node | DocumentFragment | Node[], data: T) => Node | DocumentFragment | Node[];
54
+ loader?: (ctx: RouteCtx) => Promise<T>;
55
+ preload?: (ctx: RouteCtx) => Promise<T>;
56
+ onMount?: (root: HTMLElement, data: T, ctx: RouteCtx) => RouteMountResult;
57
+ onUnmount?: (root: HTMLElement, data: T, ctx: RouteCtx) => RouteUnmountResult;
54
58
  pending?: (ctx: RouteCtx) => Node | DocumentFragment | Node[];
55
- error?: (ctx: RouteCtx, error: unknown, data?: any) => Node | DocumentFragment | Node[];
59
+ error?: (ctx: RouteCtx, error: unknown, data?: T) => Node | DocumentFragment | Node[];
56
60
  notFound?: (ctx: RouteCtx) => Node | DocumentFragment | Node[];
57
61
  children?: RouteTable[];
58
62
  middleware?: RouteMiddlewareResolver;
@@ -60,6 +64,23 @@ export interface RouteTable {
60
64
  redirect?: string | ((ctx: RouteCtx) => RouteRedirectResult);
61
65
  validation?: RouteValidationResolver;
62
66
  }
67
+ /**
68
+ * Helper to define a single route with full type inference between
69
+ * `loader` return type and the `view` / `layout` / `error` `data` parameter.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * const route = defineRoute({
74
+ * path: '/users',
75
+ * loader: async () => ({ users: await fetchUsers() }),
76
+ * view: (ctx, data) => {
77
+ * // data is inferred as { users: User[] }
78
+ * return fromHtml(tpl, { data });
79
+ * },
80
+ * });
81
+ * ```
82
+ */
83
+ export declare function defineRoute<T = any>(route: RouteTable<T>): RouteTable<T>;
63
84
  /** Immutable snapshot of the current navigation state. */
64
85
  export interface RouteState {
65
86
  path: string;
@@ -1,3 +1,22 @@
1
+ /**
2
+ * Helper to define a single route with full type inference between
3
+ * `loader` return type and the `view` / `layout` / `error` `data` parameter.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * const route = defineRoute({
8
+ * path: '/users',
9
+ * loader: async () => ({ users: await fetchUsers() }),
10
+ * view: (ctx, data) => {
11
+ * // data is inferred as { users: User[] }
12
+ * return fromHtml(tpl, { data });
13
+ * },
14
+ * });
15
+ * ```
16
+ */
17
+ export function defineRoute(route) {
18
+ return route;
19
+ }
1
20
  /** Normalize a path: ensure leading slash, collapse duplicates, strip trailing slash. */
2
21
  export function normalizePath(path) {
3
22
  if (!path)
@@ -109,6 +109,11 @@ export function createRouter(config) {
109
109
  let currentLoaderController = null;
110
110
  let started = false;
111
111
  let navigationToken = 0;
112
+ let activeLeafRoute = null;
113
+ let activeRouteMountCleanup = null;
114
+ let activeRouteUnmountHook = null;
115
+ let activeLeafData = undefined;
116
+ let activeLeafCtx = null;
112
117
  const transitionCoalescing = new Map();
113
118
  const scrollPositions = new LRUCache(config.scrollPositionsCacheSize ?? 100);
114
119
  const preloadTagsByKey = new Map();
@@ -582,10 +587,43 @@ export function createRouter(config) {
582
587
  replace: true
583
588
  };
584
589
  }
590
+ function runRouteUnmountLifecycle() {
591
+ const cleanup = activeRouteMountCleanup;
592
+ const onUnmount = activeRouteUnmountHook;
593
+ const data = activeLeafData;
594
+ const ctx = activeLeafCtx;
595
+ activeRouteMountCleanup = null;
596
+ activeRouteUnmountHook = null;
597
+ activeLeafRoute = null;
598
+ activeLeafData = undefined;
599
+ activeLeafCtx = null;
600
+ if (cleanup) {
601
+ try {
602
+ cleanup();
603
+ }
604
+ catch (error) {
605
+ console.error('[Dalila] Error in onMount cleanup lifecycle hook:', error);
606
+ }
607
+ }
608
+ if (onUnmount) {
609
+ try {
610
+ const result = onUnmount(outletElement, data, ctx);
611
+ if (result && typeof result.then === 'function') {
612
+ void result.catch((error) => {
613
+ console.error('[Dalila] Error in onUnmount lifecycle hook:', error);
614
+ });
615
+ }
616
+ }
617
+ catch (error) {
618
+ console.error('[Dalila] Error in onUnmount lifecycle hook:', error);
619
+ }
620
+ }
621
+ }
585
622
  /**
586
623
  * Mount nodes into outlet and remove loading state
587
624
  */
588
625
  function mountToOutlet(...nodes) {
626
+ runRouteUnmountLifecycle();
589
627
  outletElement.replaceChildren(...nodes);
590
628
  queueMicrotask(() => {
591
629
  outletElement.removeAttribute('d-loading');
@@ -950,7 +988,7 @@ export function createRouter(config) {
950
988
  }
951
989
  commitRouteState();
952
990
  try {
953
- mountViewStack(matchStack, ctx, dataStack);
991
+ mountViewStack(matchStack, ctx, dataStack, token);
954
992
  await finishSuccessfulTransition();
955
993
  }
956
994
  catch (error) {
@@ -986,16 +1024,18 @@ export function createRouter(config) {
986
1024
  return promise;
987
1025
  }
988
1026
  /** Compose and mount the view stack (leaf view wrapped by parent layouts). */
989
- function mountViewStack(matchStack, ctx, dataStack) {
1027
+ function mountViewStack(matchStack, ctx, dataStack, navToken) {
990
1028
  try {
991
1029
  let content = null;
992
1030
  let leafRoute = null;
1031
+ let leafData = undefined;
993
1032
  for (let i = matchStack.length - 1; i >= 0; i--) {
994
1033
  const match = matchStack[i];
995
1034
  const data = dataStack[i];
996
1035
  const route = match.route;
997
1036
  if (i === matchStack.length - 1) {
998
1037
  leafRoute = route;
1038
+ leafData = data;
999
1039
  if (!route.view) {
1000
1040
  console.warn(`[Dalila] Leaf route ${match.path} has no view function`);
1001
1041
  return;
@@ -1027,11 +1067,54 @@ export function createRouter(config) {
1027
1067
  if (content) {
1028
1068
  const nodes = Array.isArray(content) ? content : [content];
1029
1069
  mountToOutlet(...nodes);
1070
+ activeLeafRoute = leafRoute ?? null;
1071
+ activeRouteUnmountHook = leafRoute?.onUnmount ?? null;
1072
+ activeLeafData = leafData;
1073
+ activeLeafCtx = ctx;
1030
1074
  // Call onMount lifecycle hook if present
1031
1075
  if (leafRoute?.onMount) {
1032
1076
  queueMicrotask(() => {
1077
+ if (navigationToken !== navToken)
1078
+ return;
1079
+ if (activeLeafRoute !== leafRoute)
1080
+ return;
1033
1081
  try {
1034
- leafRoute.onMount(outletElement);
1082
+ const result = leafRoute.onMount(outletElement, leafData, ctx);
1083
+ if (typeof result === 'function') {
1084
+ if (navigationToken === navToken && activeLeafRoute === leafRoute) {
1085
+ activeRouteMountCleanup = result;
1086
+ }
1087
+ else {
1088
+ try {
1089
+ result();
1090
+ }
1091
+ catch (cleanupError) {
1092
+ console.error('[Dalila] Error in stale onMount cleanup lifecycle hook:', cleanupError);
1093
+ }
1094
+ }
1095
+ return;
1096
+ }
1097
+ if (result && typeof result.then === 'function') {
1098
+ void result
1099
+ .then((resolved) => {
1100
+ if (typeof resolved !== 'function')
1101
+ return;
1102
+ if (navigationToken === navToken && activeLeafRoute === leafRoute) {
1103
+ activeRouteMountCleanup = resolved;
1104
+ }
1105
+ else {
1106
+ try {
1107
+ resolved();
1108
+ }
1109
+ catch (cleanupError) {
1110
+ console.error('[Dalila] Error in stale async onMount cleanup lifecycle hook:', cleanupError);
1111
+ }
1112
+ }
1113
+ })
1114
+ .catch((error) => {
1115
+ console.error('[Dalila] Error in onMount lifecycle hook:', error);
1116
+ });
1117
+ }
1035
1118
  }
1036
1119
  catch (error) {
1037
1120
  console.error('[Dalila] Error in onMount lifecycle hook:', error);
@@ -1413,6 +1496,7 @@ export function createRouter(config) {
1413
1496
  document.removeEventListener('click', handleLink);
1414
1497
  document.removeEventListener('pointerover', handleLinkIntent);
1415
1498
  document.removeEventListener('focusin', handleLinkIntent);
1499
+ runRouteUnmountLifecycle();
1416
1500
  if (currentLoaderController) {
1417
1501
  currentLoaderController.abort();
1418
1502
  currentLoaderController = null;
@@ -0,0 +1,2 @@
1
+ import type { RouteTable } from 'dalila/router';
2
+ export declare const routes: RouteTable[];
@@ -0,0 +1,76 @@
1
+ // This file is auto-generated by 'dalila routes generate'
2
+ // Do not edit manually - your changes will be overwritten
3
+ import { fromHtml } from 'dalila';
4
+ const layout_html = `<div class="app-shell">
5
+ <h1>Router + PageVM + Componente</h1>
6
+ <nav>
7
+ <a href="/examples/router-simple/" d-link>Home</a>
8
+ <a href="/examples/router-simple/about" d-link>Sobre</a>
9
+ </nav>
10
+ <main id="outlet-slot" data-slot="children"></main>
11
+ </div>
12
+ `;
13
+ const page_html = `<section>
14
+ <h2>Home</h2>
15
+ <p>PageVM da rota: <strong>HomePageVM</strong></p>
16
+ <status-badge d-props-text="status"></status-badge>
17
+ <div style="display:flex; gap:.5rem; margin-top:.75rem;">
18
+ <button d-on-click="increment">Incrementar</button>
19
+ <button d-on-click="openModal">Abrir modal</button>
20
+ </div>
21
+
22
+ <d-dialog d-ui="infoDialog" class="d-dialog">
23
+ <d-dialog-header>
24
+ <d-dialog-title>Modal Dalila</d-dialog-title>
25
+ <d-dialog-close d-on-click="closeModal">&times;</d-dialog-close>
26
+ </d-dialog-header>
27
+ <d-dialog-body>
28
+ <p>This modal is mounted with <code>mountUI()</code> in the route <code>onMount</code>.</p>
29
+ <p>Status atual: <strong d-text="status"></strong></p>
30
+ </d-dialog-body>
31
+ <d-dialog-footer>
32
+ <button d-on-click="closeModal">Fechar</button>
33
+ </d-dialog-footer>
34
+ </d-dialog>
35
+ </section>
36
+ `;
37
+ const about_page_html = `<section>
38
+ <h2>Sobre</h2>
39
+ <p>PageVM da rota: <strong>AboutPageVM</strong></p>
40
+ <p>Nome atual: <strong d-text="nomeUpper"></strong></p>
41
+ <input d-bind-value="nome" placeholder="Digite um nome" />
42
+ </section>
43
+ `;
44
+ const page_lazy = () => import('./app/page.js');
45
+ const about_page_lazy = () => import('./app/about/page.js');
46
+ export const routes = [
47
+ {
48
+ path: '/',
49
+ layout: (ctx, children) => fromHtml(layout_html, { data: { ...ctx.params, params: ctx.params, query: ctx.query.toString(), path: ctx.path, fullPath: ctx.fullPath }, children, scope: ctx.scope }),
50
+ children: [
51
+ { path: '', view: (ctx, data) => fromHtml(page_html, { data: { ...ctx.params, ...(data ?? {}), params: ctx.params, query: ctx.query.toString(), path: ctx.path, fullPath: ctx.fullPath }, scope: ctx.scope }), loader: (...args) => page_lazy().then(mod => {
52
+ const exported = mod.loader;
53
+ if (typeof exported === 'function') {
54
+ return exported(...args);
55
+ }
56
+ return undefined;
57
+ }), onMount: (root, data, ctx) => page_lazy().then(mod => {
58
+ if (typeof mod.onMount === 'function') {
59
+ return mod.onMount(root, data, ctx);
60
+ }
61
+ return undefined;
62
+ }) },
63
+ {
64
+ path: 'about',
65
+ view: (ctx, data) => fromHtml(about_page_html, { data: { ...ctx.params, ...(data ?? {}), params: ctx.params, query: ctx.query.toString(), path: ctx.path, fullPath: ctx.fullPath }, scope: ctx.scope }),
66
+ loader: (...args) => about_page_lazy().then(mod => {
67
+ const exported = mod.loader;
68
+ if (typeof exported === 'function') {
69
+ return exported(...args);
70
+ }
71
+ return undefined;
72
+ }),
73
+ }
74
+ ]
75
+ }
76
+ ];
@@ -0,0 +1,4 @@
1
+ import type { RouteManifestEntry } from 'dalila/router';
2
+ export declare const routeManifest: RouteManifestEntry[];
3
+ export declare function getRouteManifestEntry(id: string): RouteManifestEntry | undefined;
4
+ export declare function prefetchRouteById(id: string): Promise<void>;
@@ -0,0 +1,32 @@
1
+ // This file is auto-generated by 'dalila routes generate'
2
+ // Do not edit manually - your changes will be overwritten
3
+ export const routeManifest = [
4
+ {
5
+ id: 'root',
6
+ pattern: '/',
7
+ score: 1000,
8
+ paramKeys: [],
9
+ tags: [],
10
+ modules: ["./app/page.js"],
11
+ load: () => import('./app/page.js').then(() => undefined)
12
+ },
13
+ {
14
+ id: 'about',
15
+ pattern: '/about',
16
+ score: 301,
17
+ paramKeys: [],
18
+ tags: [],
19
+ modules: ["./app/page.js", "./app/about/page.js"],
20
+ load: () => Promise.all([import('./app/page.js'), import('./app/about/page.js')]).then(() => undefined)
21
+ },
22
+ ];
23
+ const manifestById = new Map(routeManifest.map(route => [route.id, route]));
24
+ export function getRouteManifestEntry(id) {
25
+ return manifestById.get(id);
26
+ }
27
+ export async function prefetchRouteById(id) {
28
+ const entry = manifestById.get(id);
29
+ if (!entry)
30
+ return;
31
+ await entry.load();
32
+ }
@@ -0,0 +1,11 @@
1
+ export type RoutePattern = '/' | '/about';
2
+ export type RouteParamsByPattern = {
3
+ '/': {};
4
+ '/about': {};
5
+ };
6
+ export type RouteSearchByPattern = {
7
+ [P in RoutePattern]: Record<string, string | string[]>;
8
+ };
9
+ export type RouteParams<P extends RoutePattern> = RouteParamsByPattern[P];
10
+ export type RouteSearch<P extends RoutePattern> = RouteSearchByPattern[P];
11
+ export declare function buildRoutePath<P extends RoutePattern>(pattern: P, params: RouteParams<P>): string;
@@ -0,0 +1,37 @@
1
+ // This file is auto-generated by 'dalila routes generate'
2
+ // Do not edit manually - your changes will be overwritten
3
+ export function buildRoutePath(pattern, params) {
4
+ const out = [];
5
+ for (const segment of pattern.split('/').filter(Boolean)) {
6
+ if (!segment.startsWith(':')) {
7
+ out.push(segment);
8
+ continue;
9
+ }
10
+ const isOptionalCatchAll = segment.endsWith('*?');
11
+ const isCatchAll = isOptionalCatchAll || segment.endsWith('*');
12
+ const key = segment.slice(1, isCatchAll ? (isOptionalCatchAll ? -2 : -1) : undefined);
13
+ const value = params[key];
14
+ if (isCatchAll) {
15
+ if (value === undefined || value === null) {
16
+ if (isOptionalCatchAll)
17
+ continue;
18
+ throw new Error(`Missing route param: ${key}`);
19
+ }
20
+ if (!Array.isArray(value)) {
21
+ throw new Error(`Route param "${key}" must be an array`);
22
+ }
23
+ if (value.length === 0) {
24
+ if (isOptionalCatchAll)
25
+ continue;
26
+ throw new Error(`Route param "${key}" cannot be empty`);
27
+ }
28
+ out.push(...value.map(v => encodeURIComponent(String(v))));
29
+ continue;
30
+ }
31
+ if (value === undefined || value === null) {
32
+ throw new Error(`Missing route param: ${key}`);
33
+ }
34
+ out.push(encodeURIComponent(String(value)));
35
+ }
36
+ return out.length === 0 ? '/' : `/${out.join('/')}`;
37
+ }