@madojs/mado 0.5.0 → 0.6.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/AGENTS.md +49 -1
- package/CHANGELOG.md +188 -0
- package/MADO_V1_PLAN.md +179 -0
- package/README.md +53 -14
- package/ROADMAP.md +36 -5
- package/TODO.md +72 -0
- package/dist/src/forms.d.ts +41 -7
- package/dist/src/forms.js +334 -59
- package/dist/src/forms.js.map +1 -1
- package/dist/src/html/bindings.d.ts +41 -0
- package/dist/src/html/bindings.js +163 -6
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html.d.ts +2 -0
- package/dist/src/html.js +1 -0
- package/dist/src/html.js.map +1 -1
- package/dist/src/index.d.ts +6 -6
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/page.d.ts +56 -0
- package/dist/src/page.js +17 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/router/manifest.d.ts +16 -1
- package/dist/src/router/manifest.js +181 -38
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +7 -2
- package/dist/src/router/match.js +14 -4
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.d.ts +10 -0
- package/dist/src/router/navigation.js +73 -12
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/signal.d.ts +15 -1
- package/dist/src/signal.js +112 -16
- package/dist/src/signal.js.map +1 -1
- package/docs/en/02-project-layout.md +99 -40
- package/docs/en/05-why-mado.md +1 -1
- package/docs/en/06-for-backenders.md +1 -1
- package/docs/en/07-llm-pitfalls.md +1 -1
- package/docs/en/09-shadow-vs-light-dom.md +60 -0
- package/docs/en/10-app-architecture.md +141 -0
- package/docs/en/11-layouts.md +115 -0
- package/docs/en/12-auth-and-api.md +217 -0
- package/docs/en/13-deployment.md +192 -0
- package/docs/en/14-testing.md +82 -0
- package/docs/en/15-error-handling.md +100 -0
- package/docs/en/16-bake-cookbook.md +93 -0
- package/docs/en/README.md +7 -0
- package/docs/fr/05-why-mado.md +1 -1
- package/docs/fr/06-for-backenders.md +1 -1
- package/docs/fr/07-llm-pitfalls.md +1 -1
- package/docs/fr/09-shadow-vs-light-dom.md +63 -0
- package/docs/fr/10-app-architecture.md +61 -0
- package/docs/fr/11-layouts.md +35 -0
- package/docs/fr/12-auth-and-api.md +35 -0
- package/docs/fr/13-deployment.md +39 -0
- package/docs/fr/14-testing.md +41 -0
- package/docs/fr/15-error-handling.md +50 -0
- package/docs/fr/16-bake-cookbook.md +35 -0
- package/docs/fr/README.md +7 -0
- package/docs/ru/05-why-mado.md +2 -2
- package/docs/ru/06-for-backenders.md +1 -1
- package/docs/ru/09-shadow-vs-light-dom.md +60 -0
- package/docs/ru/10-app-architecture.md +100 -0
- package/docs/ru/11-layouts.md +47 -0
- package/docs/ru/12-auth-and-api.md +53 -0
- package/docs/ru/13-deployment.md +60 -0
- package/docs/ru/14-testing.md +50 -0
- package/docs/ru/15-error-handling.md +56 -0
- package/docs/ru/16-bake-cookbook.md +55 -0
- package/docs/ru/README.md +7 -0
- package/docs/uk/06-for-backenders.md +2 -2
- package/docs/uk/09-shadow-vs-light-dom.md +91 -24
- package/docs/uk/10-app-architecture.md +56 -0
- package/docs/uk/11-layouts.md +34 -0
- package/docs/uk/12-auth-and-api.md +34 -0
- package/docs/uk/13-deployment.md +39 -0
- package/docs/uk/14-testing.md +34 -0
- package/docs/uk/15-error-handling.md +32 -0
- package/docs/uk/16-bake-cookbook.md +36 -0
- package/docs/uk/README.md +7 -0
- package/llms.txt +24 -1
- package/package.json +3 -1
- package/scripts/_config.mjs +224 -0
- package/scripts/bake.mjs +217 -120
- package/scripts/bundle.mjs +110 -67
- package/scripts/cli.mjs +127 -16
- package/scripts/preview.mjs +22 -12
- package/server/serve.mjs +101 -11
- package/starters/admin/README.md +63 -0
- package/starters/admin/index.html +21 -0
- package/starters/admin/mado.config.json +22 -0
- package/starters/admin/package.json +22 -0
- package/starters/admin/public/favicon.svg +4 -0
- package/starters/admin/src/components/x-button.ts +55 -0
- package/starters/admin/src/components/x-input.ts +74 -0
- package/starters/admin/src/layouts/app.ts +101 -0
- package/starters/admin/src/layouts/auth.ts +41 -0
- package/starters/admin/src/lib/api.ts +133 -0
- package/starters/admin/src/lib/auth.ts +83 -0
- package/starters/admin/src/main.ts +15 -0
- package/starters/admin/src/pages/admin/dashboard.ts +48 -0
- package/starters/admin/src/pages/admin/order-detail.ts +78 -0
- package/starters/admin/src/pages/admin/orders.ts +117 -0
- package/starters/admin/src/pages/home.ts +25 -0
- package/starters/admin/src/pages/login.ts +70 -0
- package/starters/admin/src/pages/not-found.ts +12 -0
- package/starters/admin/src/routes.ts +40 -0
- package/starters/admin/src/styles/global.ts +86 -0
- package/starters/admin/tsconfig.json +15 -0
- package/starters/crud/README.md +14 -2
- package/starters/crud/mado.config.json +20 -0
- package/starters/crud/package.json +9 -4
- package/starters/crud/src/components/app-shell.ts +13 -8
- package/starters/crud/src/main.ts +1 -4
- package/starters/crud/src/pages/ticket-detail.ts +1 -0
- package/starters/crud/src/pages/ticket-new.ts +1 -0
- package/starters/crud/src/pages/tickets.ts +1 -0
- package/starters/crud/src/routes.ts +4 -2
- package/starters/minimal/README.md +4 -2
- package/starters/minimal/mado.config.json +20 -0
- package/starters/minimal/package.json +8 -3
- package/starters/minimal/src/components/app-counter.ts +1 -1
- package/starters/minimal/src/routes.ts +4 -2
package/dist/src/signal.d.ts
CHANGED
|
@@ -44,6 +44,16 @@ export interface Computed<T> {
|
|
|
44
44
|
(): T;
|
|
45
45
|
peek(): T;
|
|
46
46
|
}
|
|
47
|
+
export interface ComputedOptions<T> {
|
|
48
|
+
/**
|
|
49
|
+
* Equality check used when an observed computed is invalidated.
|
|
50
|
+
*
|
|
51
|
+
* If the new value is equal to the previous value, subscribers are not
|
|
52
|
+
* notified. Defaults to always notifying on dependency invalidation, which
|
|
53
|
+
* preserves the classic lazy dirty-flag behavior.
|
|
54
|
+
*/
|
|
55
|
+
equals?: (prev: T, next: T) => boolean;
|
|
56
|
+
}
|
|
47
57
|
/**
|
|
48
58
|
* Lazy computed based on a dirty-flag:
|
|
49
59
|
* - fn is NOT called until the computed is read;
|
|
@@ -58,10 +68,14 @@ export interface Computed<T> {
|
|
|
58
68
|
* instead of actually recomputing we mark ourselves dirty and
|
|
59
69
|
* propagate to subscribers.
|
|
60
70
|
*/
|
|
61
|
-
export declare function computed<T>(fn: () => T): Computed<T>;
|
|
71
|
+
export declare function computed<T>(fn: () => T, options?: ComputedOptions<T>): Computed<T>;
|
|
62
72
|
export type Disposer = () => void;
|
|
63
73
|
export declare function effect(fn: () => void | Disposer): Disposer;
|
|
64
74
|
/**
|
|
65
75
|
* Execute a function outside of tracking — reading signals will not create a subscription.
|
|
66
76
|
*/
|
|
67
77
|
export declare function untracked<T>(fn: () => T): T;
|
|
78
|
+
export declare const _testHooks: {
|
|
79
|
+
subscriberCount(source: object): number;
|
|
80
|
+
dependencyCount(source: object): number;
|
|
81
|
+
};
|
package/dist/src/signal.js
CHANGED
|
@@ -23,7 +23,48 @@
|
|
|
23
23
|
*
|
|
24
24
|
* batch(() => { a.set(1); b.set(2); });
|
|
25
25
|
*/
|
|
26
|
+
const MAX_FLUSH_RUNS_PER_SUBSCRIBER = 100;
|
|
26
27
|
let activeTracker = null;
|
|
28
|
+
class SubscriberSet extends Set {
|
|
29
|
+
onEmpty;
|
|
30
|
+
emptyScheduled = false;
|
|
31
|
+
constructor(onEmpty) {
|
|
32
|
+
super();
|
|
33
|
+
this.onEmpty = onEmpty;
|
|
34
|
+
}
|
|
35
|
+
add(entry) {
|
|
36
|
+
super.add(entry);
|
|
37
|
+
this.emptyScheduled = false;
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
delete(entry) {
|
|
41
|
+
const deleted = super.delete(entry);
|
|
42
|
+
if (deleted)
|
|
43
|
+
this.queueEmpty();
|
|
44
|
+
return deleted;
|
|
45
|
+
}
|
|
46
|
+
clear() {
|
|
47
|
+
const hadEntries = this.size > 0;
|
|
48
|
+
super.clear();
|
|
49
|
+
if (hadEntries)
|
|
50
|
+
this.queueEmpty();
|
|
51
|
+
}
|
|
52
|
+
queueEmpty() {
|
|
53
|
+
if (!this.onEmpty || this.size > 0 || this.emptyScheduled)
|
|
54
|
+
return;
|
|
55
|
+
this.emptyScheduled = true;
|
|
56
|
+
queueMicrotask(() => {
|
|
57
|
+
this.emptyScheduled = false;
|
|
58
|
+
if (this.size === 0)
|
|
59
|
+
this.onEmpty?.();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function cleanupTracker(tracker) {
|
|
64
|
+
for (const dep of tracker.deps)
|
|
65
|
+
dep.delete(tracker.entry);
|
|
66
|
+
tracker.deps.clear();
|
|
67
|
+
}
|
|
27
68
|
// ---------- Scheduler ----------
|
|
28
69
|
const pending = new Set();
|
|
29
70
|
let batchDepth = 0;
|
|
@@ -39,11 +80,20 @@ function schedule(sub) {
|
|
|
39
80
|
}
|
|
40
81
|
function flush() {
|
|
41
82
|
flushScheduled = false;
|
|
83
|
+
const runCounts = new Map();
|
|
42
84
|
// guard against Set modification during iteration
|
|
43
85
|
while (pending.size > 0) {
|
|
44
86
|
const subs = [...pending];
|
|
45
87
|
pending.clear();
|
|
46
88
|
for (const sub of subs) {
|
|
89
|
+
const runs = (runCounts.get(sub) ?? 0) + 1;
|
|
90
|
+
runCounts.set(sub, runs);
|
|
91
|
+
if (runs > MAX_FLUSH_RUNS_PER_SUBSCRIBER) {
|
|
92
|
+
// eslint-disable-next-line no-console
|
|
93
|
+
console.error("[mado] effect cycle detected: subscriber re-ran more than " +
|
|
94
|
+
`${MAX_FLUSH_RUNS_PER_SUBSCRIBER} times in one flush.`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
47
97
|
try {
|
|
48
98
|
sub();
|
|
49
99
|
}
|
|
@@ -81,7 +131,7 @@ export function flushSync() {
|
|
|
81
131
|
}
|
|
82
132
|
export function signal(initial) {
|
|
83
133
|
let value = initial;
|
|
84
|
-
const subscribers = new
|
|
134
|
+
const subscribers = new SubscriberSet();
|
|
85
135
|
const read = (() => {
|
|
86
136
|
if (activeTracker) {
|
|
87
137
|
subscribers.add(activeTracker.entry);
|
|
@@ -115,6 +165,7 @@ export function signal(initial) {
|
|
|
115
165
|
};
|
|
116
166
|
read.update = (fn) => read.set(fn(value));
|
|
117
167
|
read.peek = () => value;
|
|
168
|
+
debugInfo.set(read, { subscribers });
|
|
118
169
|
return read;
|
|
119
170
|
}
|
|
120
171
|
/**
|
|
@@ -131,17 +182,55 @@ export function signal(initial) {
|
|
|
131
182
|
* instead of actually recomputing we mark ourselves dirty and
|
|
132
183
|
* propagate to subscribers.
|
|
133
184
|
*/
|
|
134
|
-
export function computed(fn) {
|
|
135
|
-
const subscribers = new Set();
|
|
185
|
+
export function computed(fn, options = {}) {
|
|
136
186
|
let value = undefined;
|
|
137
187
|
let dirty = true;
|
|
188
|
+
let hasValue = false;
|
|
189
|
+
let computing = false;
|
|
138
190
|
const onInvalidate = () => {
|
|
139
191
|
// dep changed → mark dirty synchronously and cascade.
|
|
140
192
|
// Sync subscribers (other computed) are triggered immediately — they also
|
|
141
193
|
// set dirty without delay. Async (effects) go through the scheduler.
|
|
142
194
|
if (dirty)
|
|
143
195
|
return;
|
|
196
|
+
if (subscribers.size === 0) {
|
|
197
|
+
dirty = true;
|
|
198
|
+
suspend();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (options.equals && hasValue) {
|
|
202
|
+
const prevValue = value;
|
|
203
|
+
recompute();
|
|
204
|
+
if (options.equals(prevValue, value))
|
|
205
|
+
return;
|
|
206
|
+
notifySubscribers();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
144
209
|
dirty = true;
|
|
210
|
+
notifySubscribers();
|
|
211
|
+
};
|
|
212
|
+
const tracker = {
|
|
213
|
+
deps: new Set(),
|
|
214
|
+
entry: { run: onInvalidate, sync: true },
|
|
215
|
+
};
|
|
216
|
+
const subscribers = new SubscriberSet(() => {
|
|
217
|
+
suspend();
|
|
218
|
+
});
|
|
219
|
+
const suspend = () => {
|
|
220
|
+
if (subscribers.size > 0)
|
|
221
|
+
return;
|
|
222
|
+
cleanupTracker(tracker);
|
|
223
|
+
dirty = true;
|
|
224
|
+
};
|
|
225
|
+
const queueSuspendIfUnobserved = () => {
|
|
226
|
+
if (subscribers.size > 0)
|
|
227
|
+
return;
|
|
228
|
+
queueMicrotask(() => {
|
|
229
|
+
if (subscribers.size === 0)
|
|
230
|
+
suspend();
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
const notifySubscribers = () => {
|
|
145
234
|
const snapshot = [...subscribers];
|
|
146
235
|
for (const e of snapshot) {
|
|
147
236
|
if (e.sync) {
|
|
@@ -158,23 +247,24 @@ export function computed(fn) {
|
|
|
158
247
|
}
|
|
159
248
|
}
|
|
160
249
|
};
|
|
161
|
-
const tracker = {
|
|
162
|
-
deps: new Set(),
|
|
163
|
-
entry: { run: onInvalidate, sync: true },
|
|
164
|
-
};
|
|
165
250
|
const recompute = () => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
251
|
+
if (computing) {
|
|
252
|
+
throw new Error("[mado] computed cycle detected");
|
|
253
|
+
}
|
|
254
|
+
cleanupTracker(tracker);
|
|
169
255
|
const prev = activeTracker;
|
|
170
256
|
activeTracker = tracker;
|
|
257
|
+
computing = true;
|
|
171
258
|
try {
|
|
172
259
|
value = fn();
|
|
173
260
|
}
|
|
174
261
|
finally {
|
|
262
|
+
computing = false;
|
|
175
263
|
activeTracker = prev;
|
|
176
264
|
}
|
|
177
265
|
dirty = false;
|
|
266
|
+
hasValue = true;
|
|
267
|
+
queueSuspendIfUnobserved();
|
|
178
268
|
};
|
|
179
269
|
const read = (() => {
|
|
180
270
|
if (activeTracker) {
|
|
@@ -190,14 +280,13 @@ export function computed(fn) {
|
|
|
190
280
|
recompute();
|
|
191
281
|
return value;
|
|
192
282
|
};
|
|
283
|
+
debugInfo.set(read, { subscribers, tracker });
|
|
193
284
|
return read;
|
|
194
285
|
}
|
|
195
286
|
export function effect(fn) {
|
|
196
287
|
let cleanup;
|
|
197
288
|
const run = () => {
|
|
198
|
-
|
|
199
|
-
dep.delete(tracker.entry);
|
|
200
|
-
tracker.deps.clear();
|
|
289
|
+
cleanupTracker(tracker);
|
|
201
290
|
if (typeof cleanup === "function")
|
|
202
291
|
cleanup();
|
|
203
292
|
const prev = activeTracker;
|
|
@@ -215,9 +304,7 @@ export function effect(fn) {
|
|
|
215
304
|
};
|
|
216
305
|
run();
|
|
217
306
|
return () => {
|
|
218
|
-
|
|
219
|
-
dep.delete(tracker.entry);
|
|
220
|
-
tracker.deps.clear();
|
|
307
|
+
cleanupTracker(tracker);
|
|
221
308
|
if (typeof cleanup === "function")
|
|
222
309
|
cleanup();
|
|
223
310
|
};
|
|
@@ -235,4 +322,13 @@ export function untracked(fn) {
|
|
|
235
322
|
activeTracker = prev;
|
|
236
323
|
}
|
|
237
324
|
}
|
|
325
|
+
const debugInfo = new WeakMap();
|
|
326
|
+
export const _testHooks = {
|
|
327
|
+
subscriberCount(source) {
|
|
328
|
+
return debugInfo.get(source)?.subscribers.size ?? 0;
|
|
329
|
+
},
|
|
330
|
+
dependencyCount(source) {
|
|
331
|
+
return debugInfo.get(source)?.tracker?.deps.size ?? 0;
|
|
332
|
+
},
|
|
333
|
+
};
|
|
238
334
|
//# sourceMappingURL=signal.js.map
|
package/dist/src/signal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signal.js","sourceRoot":"","sources":["../../src/signal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;
|
|
1
|
+
{"version":3,"file":"signal.js","sourceRoot":"","sources":["../../src/signal.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,58 +1,117 @@
|
|
|
1
1
|
# Project layout
|
|
2
2
|
|
|
3
|
-
Every
|
|
3
|
+
Every Mado app uses the same shape. This is a **mandatory** convention — it
|
|
4
|
+
exists so that you, your teammates, and any LLM assistant always know where
|
|
5
|
+
things live.
|
|
4
6
|
|
|
5
7
|
```
|
|
6
8
|
my-app/
|
|
7
|
-
├── package.json # exactly
|
|
8
|
-
├── tsconfig.json #
|
|
9
|
-
├──
|
|
10
|
-
├── .
|
|
11
|
-
├──
|
|
12
|
-
├── scripts/
|
|
13
|
-
│ ├── bundle.mjs # esbuild prod bundle
|
|
14
|
-
│ └── new.mjs # page scaffolder
|
|
15
|
-
├── templates/ # templates for new.mjs
|
|
16
|
-
├── docs/ # project docs (can copy our guides)
|
|
17
|
-
├── public/ # static assets (favicon, manifests)
|
|
9
|
+
├── package.json # exactly one runtime dep: @madojs/mado
|
|
10
|
+
├── tsconfig.json # strict TS, ES2022, Bundler resolution
|
|
11
|
+
├── mado.config.json # single config file (dev/build/bake/bundle)
|
|
12
|
+
├── index.html # SPA shell (also the template for `mado bake`)
|
|
13
|
+
├── public/ # static assets (favicons, images, robots.txt)
|
|
18
14
|
└── src/
|
|
19
|
-
├── main.ts # entry:
|
|
20
|
-
├── routes.ts # route manifest
|
|
21
|
-
├──
|
|
22
|
-
├──
|
|
23
|
-
├──
|
|
15
|
+
├── main.ts # entry: mount router into #app
|
|
16
|
+
├── routes.ts # route manifest (default + named `manifest`)
|
|
17
|
+
├── layouts/ # `page({ child })` layouts for nested routes
|
|
18
|
+
├── pages/ # one page = one file
|
|
19
|
+
├── components/ # reusable x-* Web Components
|
|
24
20
|
└── lib/
|
|
25
|
-
├── api.ts #
|
|
26
|
-
├──
|
|
27
|
-
|
|
28
|
-
└── ... # utilities, types, business rules
|
|
21
|
+
├── api.ts # API client + error type
|
|
22
|
+
├── auth.ts # auth recipe (token + guard)
|
|
23
|
+
└── ... # contexts, helpers, business rules
|
|
29
24
|
```
|
|
30
25
|
|
|
26
|
+
## The three artifact states (read this once, never wonder again)
|
|
27
|
+
|
|
28
|
+
| Folder | What it is | Who writes | Who reads | Deploy? |
|
|
29
|
+
|-------------|----------------------------------------------------------------|-------------------|----------------------------|-------------------|
|
|
30
|
+
| `src/` | your source (TypeScript) | you | `tsc`, `esbuild` | ❌ no |
|
|
31
|
+
| `dist/` | `tsc` output — native ESM `.js` for the browser | `mado build` | `mado dev` (during dev) | ❌ no (internal) |
|
|
32
|
+
| `public/` | static assets you authored (favicon, images, robots.txt) | you | `mado release` copies it | ✅ via `out/` |
|
|
33
|
+
| `out/` | **the deploy artifact**: SPA shell + bundles + baked HTML | `mado release` | nginx / CDN / Cloudflare | ✅ **yes** |
|
|
34
|
+
|
|
35
|
+
One-liner to remember:
|
|
36
|
+
> Develop with `mado dev`. To ship: run `mado release`, then upload `out/`.
|
|
37
|
+
|
|
38
|
+
`mado release` = `typecheck` + `build` (tsc → `dist/`) + `bundle` (esbuild
|
|
39
|
+
→ `out/assets/`) + `bake` (HTML → `out/baked/`) + copy `public/*` → `out/`.
|
|
40
|
+
|
|
41
|
+
You almost never need to look inside `dist/`. It exists so the dev browser can
|
|
42
|
+
load native ESM modules without a bundler during development. In production
|
|
43
|
+
the equivalent code is bundled and hashed into `out/assets/`.
|
|
44
|
+
|
|
45
|
+
### Quick deployment matrix
|
|
46
|
+
|
|
47
|
+
| Target | Command | Where it goes |
|
|
48
|
+
|-----------------------|--------------------------------------|----------------------------|
|
|
49
|
+
| VPS + nginx | `mado release && rsync -avz out/ …` | `/var/www/<app>/` |
|
|
50
|
+
| Cloudflare Pages | `mado release && wrangler pages deploy out` | CF Pages |
|
|
51
|
+
| Netlify / S3 / GH Pages | `mado release && upload out/*` | any static host |
|
|
52
|
+
|
|
53
|
+
See `docs/en/13-deployment.md` for full recipes.
|
|
54
|
+
|
|
31
55
|
## Where to put a new file?
|
|
32
56
|
|
|
33
|
-
| What
|
|
34
|
-
|
|
35
|
-
| Page for a new URL
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
57
|
+
| What | Where |
|
|
58
|
+
|-------------------------------------|------------------------------------------------------|
|
|
59
|
+
| Page for a new URL | `src/pages/<name>.ts` + add to `src/routes.ts` |
|
|
60
|
+
| Layout for a group of routes | `src/layouts/<name>.ts` (referenced from `routes.ts`)|
|
|
61
|
+
| Reusable UI widget | `src/components/<x-name>.ts` |
|
|
62
|
+
| API call | `src/lib/api.ts` (add a method) |
|
|
63
|
+
| Auth/session | `src/lib/auth.ts` |
|
|
64
|
+
| Global context (theme, user, i18n) | `src/lib/<name>.ts` |
|
|
65
|
+
| Pure function with no UI | `src/lib/util/<name>.ts` |
|
|
66
|
+
| Static image / favicon | `public/<file>` |
|
|
40
67
|
|
|
41
|
-
If you don't know where — that is a signal that **the architecture is
|
|
42
|
-
Ask the team
|
|
68
|
+
If you don't know where — that is a signal that **the architecture is
|
|
69
|
+
suffering**. Ask the team and **record** the answer in `docs/`. Don't invent a
|
|
70
|
+
new top-level folder.
|
|
43
71
|
|
|
44
72
|
## Naming rules
|
|
45
73
|
|
|
46
|
-
| What
|
|
47
|
-
|
|
48
|
-
| File
|
|
49
|
-
| Component tag
|
|
50
|
-
| Context
|
|
51
|
-
| Signal
|
|
52
|
-
| Page
|
|
74
|
+
| What | Style | Example |
|
|
75
|
+
|-------------------------------------|----------------------|------------------------|
|
|
76
|
+
| File | kebab-case | `user-profile.ts` |
|
|
77
|
+
| Component tag | `x-` + kebab | `<x-user-profile>` |
|
|
78
|
+
| Context | PascalCase + `Ctx` | `ThemeCtx`, `AuthCtx` |
|
|
79
|
+
| Signal | camelCase | `userId`, `isLoggedIn` |
|
|
80
|
+
| Page-internal element | `x-<route>-page` | `<x-posts-page>` |
|
|
81
|
+
|
|
82
|
+
## `mado.config.json` in one screen
|
|
83
|
+
|
|
84
|
+
```jsonc
|
|
85
|
+
{
|
|
86
|
+
"dev": {
|
|
87
|
+
"port": 5173,
|
|
88
|
+
"proxy": { "/api": "http://localhost:3000" } // dev → backend
|
|
89
|
+
},
|
|
90
|
+
"build": {
|
|
91
|
+
"out": "out",
|
|
92
|
+
"dist": "dist",
|
|
93
|
+
"publicDir": "public"
|
|
94
|
+
},
|
|
95
|
+
"bake": {
|
|
96
|
+
"entry": "src/routes.ts",
|
|
97
|
+
"template": "index.html",
|
|
98
|
+
"baseUrl": "https://example.com"
|
|
99
|
+
},
|
|
100
|
+
"bundle": {
|
|
101
|
+
"splitting": true,
|
|
102
|
+
"compress": ["gz", "br"]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Precedence: built-in defaults < `mado.config.json` < CLI flags
|
|
108
|
+
(< legacy env vars). All keys are optional.
|
|
53
109
|
|
|
54
|
-
## What does NOT go in src
|
|
110
|
+
## What does NOT go in `src/`
|
|
55
111
|
|
|
56
112
|
- ❌ Build tool configs (webpack, rollup, vite) — we don't have any.
|
|
57
|
-
- ❌ `.env` files — env
|
|
58
|
-
|
|
113
|
+
- ❌ `.env` files — read env in `src/lib/config.ts` from `import.meta.env` /
|
|
114
|
+
`process.env` and import that one module everywhere.
|
|
115
|
+
- ❌ Tests mixed with code — put them in `test/`.
|
|
116
|
+
- ❌ `examples/` folder — the framework repository has examples, your app
|
|
117
|
+
does not need one.
|
package/docs/en/05-why-mado.md
CHANGED
|
@@ -33,7 +33,7 @@ If your case does not fall into the last point — Mado is most likely not the b
|
|
|
33
33
|
| Reactivity | `@property` decorators + manual `requestUpdate` | signals (`signal`/`computed`/`effect`) out of the box |
|
|
34
34
|
| Router | none, you need to find one (`@lit-labs/router`, etc) | included: `routes()` + nested + prefetch + sync-cache |
|
|
35
35
|
| Data fetching | none, you need to assemble it | `resource()` + `mutation()` + glob invalidation |
|
|
36
|
-
| Forms | none | `useForm()`
|
|
36
|
+
| Forms | none | `useForm()` with HTML-like constraints |
|
|
37
37
|
| SEO / static | complex (`@lit-labs/ssr`) | `bake` (linkedom) + edge-prerender |
|
|
38
38
|
| Build | needs esbuild/rollup/webpack | `tsc` is enough |
|
|
39
39
|
| Code style | classes + decorators | functions + tagged templates |
|
|
@@ -188,7 +188,7 @@ We register the `<x-counter>` tag in the browser — it becomes a "function" tha
|
|
|
188
188
|
|
|
189
189
|
## Forms — like `form.Validate()` on the backend
|
|
190
190
|
|
|
191
|
-
Mado uses **native
|
|
191
|
+
Mado uses **schema-based validation close to native HTML constraints**, plus adds state tracking.
|
|
192
192
|
|
|
193
193
|
```ts
|
|
194
194
|
import { useForm } from "@madojs/mado";
|
|
@@ -256,7 +256,7 @@ This means:
|
|
|
256
256
|
// ❌ No such API
|
|
257
257
|
const f = useForm({ resolver: zodResolver(schema) });
|
|
258
258
|
|
|
259
|
-
// ✅ Correct:
|
|
259
|
+
// ✅ Correct: HTML-like validation through useForm schema
|
|
260
260
|
const f = useForm({
|
|
261
261
|
email: { required: true, type: "email" },
|
|
262
262
|
age: { required: true, type: "number", min: 18 },
|
|
@@ -6,6 +6,19 @@ an application.
|
|
|
6
6
|
|
|
7
7
|
## Rule of Thumb
|
|
8
8
|
|
|
9
|
+
In Mado, layouts are components too. If a file represents a visible reusable
|
|
10
|
+
part of the app tree — app shell, sidebar, modal, table, page section — prefer a
|
|
11
|
+
Web Component registered with `component()`.
|
|
12
|
+
|
|
13
|
+
Use plain functions only for small inline template helpers:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
const money = (value: number) => html`<span>${formatMoney(value)}</span>`;
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Do not use functions for app shells in public examples. They work, but they
|
|
20
|
+
hide the browser model instead of teaching it.
|
|
21
|
+
|
|
9
22
|
Use **Shadow DOM** for leaf widgets:
|
|
10
23
|
|
|
11
24
|
- buttons, badges, cards, metrics;
|
|
@@ -22,6 +35,15 @@ global CSS utilities:
|
|
|
22
35
|
- components that intentionally share global layout, form and table utilities;
|
|
23
36
|
- places where children should simply remain normal document DOM.
|
|
24
37
|
|
|
38
|
+
Use **Shadow DOM** for slot-based layouts:
|
|
39
|
+
|
|
40
|
+
- app shells that render `<slot>`;
|
|
41
|
+
- sidebar/content wrappers;
|
|
42
|
+
- reusable layout frames that own their own grid/header/sidebar CSS.
|
|
43
|
+
|
|
44
|
+
`<slot>` is a Shadow DOM feature. In a `shadow: false` component, `<slot>` is
|
|
45
|
+
just a normal element and does not move children into that position.
|
|
46
|
+
|
|
25
47
|
## The Footgun
|
|
26
48
|
|
|
27
49
|
Global CSS does not cross a Shadow DOM boundary.
|
|
@@ -90,6 +112,18 @@ component("x-toast-stack", setup);
|
|
|
90
112
|
This gives backend-admin screens predictable CSS while preserving encapsulation
|
|
91
113
|
for reusable widgets and slot-based shells.
|
|
92
114
|
|
|
115
|
+
The import model is deliberately browser-native:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import "./components/app-layout.js";
|
|
119
|
+
|
|
120
|
+
render(html`<x-app-layout>${router.view}</x-app-layout>`, app);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The import registers the custom element with `customElements.define()`. The
|
|
124
|
+
template creates an `<x-app-layout>` element. The browser connects the two.
|
|
125
|
+
There is no React-style component value being passed around.
|
|
126
|
+
|
|
93
127
|
If a layout does not need slot projection and should be styled entirely by
|
|
94
128
|
global CSS, `shadow: false` can still be a good choice. If it contains
|
|
95
129
|
`<slot>`, keep Shadow DOM and put the shell styles in that component.
|
|
@@ -107,6 +141,32 @@ component("x-card-link", () => () => html`
|
|
|
107
141
|
|
|
108
142
|
The link can be in Shadow DOM; navigation still stays SPA.
|
|
109
143
|
|
|
144
|
+
## Where To Import Components
|
|
145
|
+
|
|
146
|
+
Custom elements are global after registration, but registration is still an
|
|
147
|
+
explicit JavaScript import.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
// main.ts: global app frame
|
|
151
|
+
import "./components/app-shell.js";
|
|
152
|
+
|
|
153
|
+
// pages/tickets.ts: page-owned feature component
|
|
154
|
+
import "../components/ticket-list.js";
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The browser does **not** download `ticket-list.js` just because it sees
|
|
158
|
+
`<ticket-list>`. The file must be imported somewhere first. Once imported, it
|
|
159
|
+
calls `customElements.define(...)`, and the tag becomes known in the current
|
|
160
|
+
document.
|
|
161
|
+
|
|
162
|
+
Do not bulk-import every component in `main.ts` "just in case". It works for
|
|
163
|
+
tiny demos, but it hides ownership and defeats lazy route loading. Prefer:
|
|
164
|
+
|
|
165
|
+
- global app shell/providers in `main.ts`;
|
|
166
|
+
- page-owned components in that page file;
|
|
167
|
+
- feature-owned shared components in the feature entry page;
|
|
168
|
+
- truly global leaf components in `main.ts` only when they are used everywhere.
|
|
169
|
+
|
|
110
170
|
## Showcase Lesson
|
|
111
171
|
|
|
112
172
|
`examples/showcase` uses this split deliberately:
|