create-dalila 1.0.0 → 1.1.1

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/index.js CHANGED
@@ -104,6 +104,9 @@ function main() {
104
104
  console.log(` ${cyan('npm install')}`);
105
105
  console.log(` ${cyan('npm run dev')}`);
106
106
  console.log();
107
+ console.log('When you update files in src/app, regenerate routes with:');
108
+ console.log(` ${cyan('npm run routes')}`);
109
+ console.log();
107
110
  console.log(`Open ${cyan('http://localhost:4242')} to see your app.`);
108
111
  console.log();
109
112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-dalila",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Create Dalila apps with one command",
5
5
  "bin": {
6
6
  "create-dalila": "index.js"
@@ -8,7 +8,10 @@
8
8
  "type": "module",
9
9
  "files": [
10
10
  "index.js",
11
- "template"
11
+ "template",
12
+ "template/routes.generated.ts",
13
+ "template/routes.generated.manifest.ts",
14
+ "template/routes.generated.types.ts"
12
15
  ],
13
16
  "keywords": [
14
17
  "dalila",
@@ -3,32 +3,11 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Dalila App</title>
6
+ <title>Dalila Router App</title>
7
7
  <link rel="stylesheet" href="/src/style.css">
8
8
  </head>
9
9
  <body>
10
- <div id="app" class="container">
11
- <h1>Welcome to Dalila</h1>
12
-
13
- <section class="card">
14
- <h2>Counter: {count}</h2>
15
- <p>Doubled: {doubled}</p>
16
-
17
- <div class="buttons">
18
- <button d-on-click="decrement">-</button>
19
- <button d-on-click="increment">+</button>
20
- </div>
21
- </section>
22
-
23
- <section class="card">
24
- <p when={isEven}>The count is even</p>
25
- <p when={isOdd}>The count is odd</p>
26
- </section>
27
-
28
- <footer>
29
- <p>Edit <code>src/main.ts</code> and save to see changes.</p>
30
- </footer>
31
- </div>
10
+ <div id="app"></div>
32
11
 
33
12
  <script type="module" src="/src/main.ts"></script>
34
13
  </body>
@@ -5,11 +5,13 @@
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "dalila-dev",
8
+ "routes": "dalila routes generate",
9
+ "routes:watch": "dalila routes watch",
8
10
  "build": "tsc",
9
11
  "preview": "dalila-dev --dist"
10
12
  },
11
13
  "dependencies": {
12
- "dalila": "^1.4.0"
14
+ "dalila": "^1.5.0"
13
15
  },
14
16
  "devDependencies": {
15
17
  "typescript": "^5.7.3"
@@ -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
+
4
+ import type { RouteManifestEntry } from 'dalila/router';
5
+
6
+ export const routeManifest: RouteManifestEntry[] = [
7
+ {
8
+ id: 'root',
9
+ pattern: '/',
10
+ score: 1000,
11
+ paramKeys: [],
12
+ tags: [],
13
+ modules: ["./src/app/page.js"],
14
+ load: () => import('./src/app/page.js').then(() => undefined)
15
+ },
16
+ {
17
+ id: 'about',
18
+ pattern: '/about',
19
+ score: 301,
20
+ paramKeys: [],
21
+ tags: [],
22
+ modules: ["./src/app/page.js"],
23
+ load: () => import('./src/app/page.js').then(() => undefined)
24
+ },
25
+ ];
26
+
27
+ const manifestById = new Map(routeManifest.map(route => [route.id, route]));
28
+
29
+ export function getRouteManifestEntry(id: string): RouteManifestEntry | undefined {
30
+ return manifestById.get(id);
31
+ }
32
+
33
+ export async function prefetchRouteById(id: string): Promise<void> {
34
+ const entry = manifestById.get(id);
35
+ if (!entry) return;
36
+ await entry.load();
37
+ }
@@ -0,0 +1,64 @@
1
+ // This file is auto-generated by 'dalila routes generate'
2
+ // Do not edit manually - your changes will be overwritten
3
+
4
+ import type { RouteTable } from 'dalila/router';
5
+ import { fromHtml } from 'dalila';
6
+
7
+ const layout_html = `<div class="app-shell">
8
+ <header class="app-header">
9
+ <div>
10
+ <h1>Dalila Router App</h1>
11
+ <p>File-based routing starter template</p>
12
+ </div>
13
+ <nav class="app-nav">
14
+ <a d-link="/">Home</a>
15
+ <a d-link="/about">About</a>
16
+ </nav>
17
+ </header>
18
+
19
+ <main class="app-main" data-slot="children"></main>
20
+ </div>
21
+ `;
22
+ const page_html = `<section class="card">
23
+ <h2>Counter: {count}</h2>
24
+ <p>Doubled: {doubled}</p>
25
+
26
+ <div class="buttons">
27
+ <button type="button" d-on-click="decrement">-</button>
28
+ <button type="button" d-on-click="increment">+</button>
29
+ </div>
30
+ </section>
31
+
32
+ <section class="card">
33
+ <p when={isEven}>The count is even.</p>
34
+ <p when={isOdd}>The count is odd.</p>
35
+ </section>
36
+ `;
37
+ const about_page_html = `<section class="card">
38
+ <h2>About</h2>
39
+ <p>This starter uses file-based routes from <code>src/app</code>.</p>
40
+ <p>Use <code>npm run routes</code> after changing route files.</p>
41
+ </section>
42
+ `;
43
+
44
+ const page_lazy = () => import('./src/app/page.js');
45
+
46
+ export const routes: RouteTable[] = [
47
+ {
48
+ path: '/',
49
+ layout: (ctx, children) => fromHtml(layout_html, { data: { ...(ctx.params as Record<string, unknown>), 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 as Record<string, unknown>), ...((data ?? {}) as Record<string, unknown>), params: ctx.params, query: ctx.query.toString(), path: ctx.path, fullPath: ctx.fullPath }, scope: ctx.scope }), loader: (...args: any[]) => page_lazy().then(mod => {
52
+ const exported = (mod as any).loader;
53
+ if (typeof exported === 'function') {
54
+ return exported(...args);
55
+ }
56
+ return undefined;
57
+ }) },
58
+ {
59
+ path: 'about',
60
+ view: (ctx) => fromHtml(about_page_html, { data: { ...(ctx.params as Record<string, unknown>), params: ctx.params, query: ctx.query.toString(), path: ctx.path, fullPath: ctx.fullPath }, scope: ctx.scope }),
61
+ }
62
+ ]
63
+ }
64
+ ];
@@ -0,0 +1,54 @@
1
+ // This file is auto-generated by 'dalila routes generate'
2
+ // Do not edit manually - your changes will be overwritten
3
+
4
+ export type RoutePattern = '/' | '/about';
5
+
6
+ export type RouteParamsByPattern = {
7
+ '/': {};
8
+ '/about': {};
9
+ };
10
+
11
+ export type RouteSearchByPattern = {
12
+ [P in RoutePattern]: Record<string, string | string[]>;
13
+ };
14
+
15
+ export type RouteParams<P extends RoutePattern> = RouteParamsByPattern[P];
16
+ export type RouteSearch<P extends RoutePattern> = RouteSearchByPattern[P];
17
+
18
+ export function buildRoutePath<P extends RoutePattern>(pattern: P, params: RouteParams<P>): string {
19
+ const out: string[] = [];
20
+ for (const segment of pattern.split('/').filter(Boolean)) {
21
+ if (!segment.startsWith(':')) {
22
+ out.push(segment);
23
+ continue;
24
+ }
25
+
26
+ const isOptionalCatchAll = segment.endsWith('*?');
27
+ const isCatchAll = isOptionalCatchAll || segment.endsWith('*');
28
+ const key = segment.slice(1, isCatchAll ? (isOptionalCatchAll ? -2 : -1) : undefined);
29
+ const value = (params as Record<string, unknown>)[key];
30
+
31
+ if (isCatchAll) {
32
+ if (value === undefined || value === null) {
33
+ if (isOptionalCatchAll) continue;
34
+ throw new Error(`Missing route param: ${key}`);
35
+ }
36
+ if (!Array.isArray(value)) {
37
+ throw new Error(`Route param "${key}" must be an array`);
38
+ }
39
+ if (value.length === 0) {
40
+ if (isOptionalCatchAll) continue;
41
+ throw new Error(`Route param "${key}" cannot be empty`);
42
+ }
43
+ out.push(...value.map(v => encodeURIComponent(String(v))));
44
+ continue;
45
+ }
46
+
47
+ if (value === undefined || value === null) {
48
+ throw new Error(`Missing route param: ${key}`);
49
+ }
50
+ out.push(encodeURIComponent(String(value)));
51
+ }
52
+
53
+ return out.length === 0 ? '/' : `/${out.join('/')}`;
54
+ }
@@ -0,0 +1,5 @@
1
+ <section class="card">
2
+ <h2>About</h2>
3
+ <p>This starter uses file-based routes from <code>src/app</code>.</p>
4
+ <p>Use <code>npm run routes</code> after changing route files.</p>
5
+ </section>
@@ -0,0 +1,14 @@
1
+ <div class="app-shell">
2
+ <header class="app-header">
3
+ <div>
4
+ <h1>Dalila Router App</h1>
5
+ <p>File-based routing starter template</p>
6
+ </div>
7
+ <nav class="app-nav">
8
+ <a d-link="/">Home</a>
9
+ <a d-link="/about">About</a>
10
+ </nav>
11
+ </header>
12
+
13
+ <main class="app-main" data-slot="children"></main>
14
+ </div>
@@ -0,0 +1,14 @@
1
+ <section class="card">
2
+ <h2>Counter: {count}</h2>
3
+ <p>Doubled: {doubled}</p>
4
+
5
+ <div class="buttons">
6
+ <button type="button" d-on-click="decrement">-</button>
7
+ <button type="button" d-on-click="increment">+</button>
8
+ </div>
9
+ </section>
10
+
11
+ <section class="card">
12
+ <p when={isEven}>The count is even.</p>
13
+ <p when={isOdd}>The count is odd.</p>
14
+ </section>
@@ -0,0 +1,20 @@
1
+ import { computed, signal } from 'dalila';
2
+
3
+ export function loader() {
4
+ const count = signal(0);
5
+ const doubled = computed(() => count() * 2);
6
+ const isEven = computed(() => count() % 2 === 0);
7
+ const isOdd = computed(() => count() % 2 !== 0);
8
+
9
+ const increment = () => count.update(value => value + 1);
10
+ const decrement = () => count.update(value => value - 1);
11
+
12
+ return {
13
+ count,
14
+ doubled,
15
+ isEven,
16
+ isOdd,
17
+ increment,
18
+ decrement
19
+ };
20
+ }
@@ -1,30 +1,17 @@
1
- import { signal, computed, createScope, withScope } from 'dalila';
2
- import { bind } from 'dalila/runtime';
1
+ import { createRouter } from 'dalila/router';
2
+ import { routes } from '../routes.generated.ts';
3
+ import { routeManifest } from '../routes.generated.manifest.ts';
3
4
 
4
- // Create app scope
5
- const app = createScope();
5
+ const outlet = document.getElementById('app');
6
6
 
7
- withScope(app, () => {
8
- // Reactive state
9
- const count = signal(0);
10
- const doubled = computed(() => count() * 2);
11
- const isEven = computed(() => count() % 2 === 0);
12
- const isOdd = computed(() => count() % 2 !== 0);
7
+ if (!outlet) {
8
+ throw new Error('Missing #app element');
9
+ }
13
10
 
14
- // Actions
15
- const increment = () => count.update(n => n + 1);
16
- const decrement = () => count.update(n => n - 1);
17
-
18
- // Bind to DOM
19
- const root = document.getElementById('app');
20
- if (root) {
21
- bind(root, {
22
- count,
23
- doubled,
24
- isEven,
25
- isOdd,
26
- increment,
27
- decrement,
28
- });
29
- }
11
+ const router = createRouter({
12
+ outlet,
13
+ routes,
14
+ routeManifest
30
15
  });
16
+
17
+ router.start();
@@ -5,97 +5,114 @@
5
5
  }
6
6
 
7
7
  :root {
8
- --bg: #1a1a2e;
9
- --card-bg: #16213e;
10
- --text: #eee;
11
- --text-muted: #888;
12
- --primary: #e94560;
13
- --primary-hover: #ff6b6b;
14
- --border: #333;
8
+ --bg: #f5f7fb;
9
+ --surface: #ffffff;
10
+ --text: #1f2937;
11
+ --muted: #5f6b7c;
12
+ --border: #dce2eb;
13
+ --primary: #1d4ed8;
14
+ --primary-hover: #1e40af;
15
15
  }
16
16
 
17
17
  body {
18
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
19
- background: var(--bg);
20
- color: var(--text);
18
+ margin: 0;
21
19
  min-height: 100vh;
20
+ background: radial-gradient(circle at top right, #dbeafe 0%, var(--bg) 48%);
21
+ color: var(--text);
22
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
23
+ }
24
+
25
+ #app {
26
+ max-width: 760px;
27
+ margin: 0 auto;
28
+ padding: 2rem 1rem 3rem;
29
+ }
30
+
31
+ .app-shell {
32
+ display: grid;
33
+ gap: 1rem;
34
+ }
35
+
36
+ .app-header {
37
+ background: var(--surface);
38
+ border: 1px solid var(--border);
39
+ border-radius: 14px;
40
+ padding: 1rem 1.25rem;
22
41
  display: flex;
23
- justify-content: center;
24
42
  align-items: center;
43
+ justify-content: space-between;
44
+ gap: 1rem;
45
+ }
46
+
47
+ .app-header h1 {
48
+ font-size: 1.2rem;
25
49
  }
26
50
 
27
- .container {
28
- max-width: 400px;
29
- padding: 2rem;
30
- text-align: center;
51
+ .app-header p {
52
+ color: var(--muted);
53
+ font-size: 0.9rem;
31
54
  }
32
55
 
33
- h1 {
34
- font-size: 2rem;
35
- margin-bottom: 2rem;
36
- background: linear-gradient(135deg, var(--primary), #a855f7);
37
- -webkit-background-clip: text;
38
- -webkit-text-fill-color: transparent;
39
- background-clip: text;
56
+ .app-nav {
57
+ display: flex;
58
+ gap: 0.5rem;
59
+ }
60
+
61
+ .app-nav a {
62
+ text-decoration: none;
63
+ color: var(--primary);
64
+ font-weight: 600;
65
+ padding: 0.45rem 0.7rem;
66
+ border-radius: 8px;
67
+ }
68
+
69
+ .app-nav a:hover {
70
+ background: #eff6ff;
71
+ color: var(--primary-hover);
72
+ }
73
+
74
+ .app-main {
75
+ display: grid;
76
+ gap: 1rem;
40
77
  }
41
78
 
42
79
  .card {
43
- background: var(--card-bg);
44
- border-radius: 12px;
45
- padding: 1.5rem;
46
- margin-bottom: 1rem;
80
+ background: var(--surface);
47
81
  border: 1px solid var(--border);
82
+ border-radius: 14px;
83
+ padding: 1.2rem;
48
84
  }
49
85
 
50
86
  .card h2 {
51
- font-size: 1.5rem;
52
87
  margin-bottom: 0.5rem;
53
88
  }
54
89
 
55
90
  .card p {
56
- color: var(--text-muted);
57
- margin-bottom: 1rem;
91
+ color: var(--muted);
92
+ margin-top: 0.4rem;
58
93
  }
59
94
 
60
95
  .buttons {
96
+ margin-top: 1rem;
61
97
  display: flex;
62
- gap: 1rem;
63
- justify-content: center;
98
+ gap: 0.75rem;
64
99
  }
65
100
 
66
101
  button {
67
- background: var(--primary);
68
- color: white;
69
102
  border: none;
70
- padding: 0.75rem 2rem;
71
- font-size: 1.25rem;
72
- border-radius: 8px;
103
+ border-radius: 10px;
104
+ padding: 0.6rem 1rem;
105
+ min-width: 3rem;
106
+ background: var(--primary);
107
+ color: #ffffff;
108
+ font-size: 1.1rem;
73
109
  cursor: pointer;
74
- transition: background 0.2s, transform 0.1s;
75
110
  }
76
111
 
77
112
  button:hover {
78
113
  background: var(--primary-hover);
79
114
  }
80
115
 
81
- button:active {
82
- transform: scale(0.95);
83
- }
84
-
85
- footer {
86
- margin-top: 2rem;
87
- color: var(--text-muted);
88
- font-size: 0.875rem;
89
- }
90
-
91
- footer code {
92
- background: var(--card-bg);
93
- padding: 0.25rem 0.5rem;
94
- border-radius: 4px;
95
- font-family: 'Fira Code', monospace;
96
- }
97
-
98
- /* Hide elements with tokens until bound */
99
116
  [d-loading] {
100
117
  visibility: hidden;
101
118
  }
@@ -8,5 +8,10 @@
8
8
  "skipLibCheck": true,
9
9
  "outDir": "dist"
10
10
  },
11
- "include": ["src"]
11
+ "include": [
12
+ "src",
13
+ "routes.generated.ts",
14
+ "routes.generated.manifest.ts",
15
+ "routes.generated.types.ts"
16
+ ]
12
17
  }