@madojs/mado 0.5.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 +291 -0
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +371 -0
- package/ROADMAP.md +52 -0
- package/dist/src/component.d.ts +48 -0
- package/dist/src/component.js +140 -0
- package/dist/src/component.js.map +1 -0
- package/dist/src/context.d.ts +40 -0
- package/dist/src/context.js +67 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/css.d.ts +54 -0
- package/dist/src/css.js +137 -0
- package/dist/src/css.js.map +1 -0
- package/dist/src/devtools.d.ts +22 -0
- package/dist/src/devtools.js +63 -0
- package/dist/src/devtools.js.map +1 -0
- package/dist/src/diagnostics.d.ts +11 -0
- package/dist/src/diagnostics.js +28 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/each.d.ts +39 -0
- package/dist/src/each.js +35 -0
- package/dist/src/each.js.map +1 -0
- package/dist/src/forms.d.ts +71 -0
- package/dist/src/forms.js +161 -0
- package/dist/src/forms.js.map +1 -0
- package/dist/src/head.d.ts +19 -0
- package/dist/src/head.js +97 -0
- package/dist/src/head.js.map +1 -0
- package/dist/src/html/bindings.d.ts +78 -0
- package/dist/src/html/bindings.js +304 -0
- package/dist/src/html/bindings.js.map +1 -0
- package/dist/src/html/parser.d.ts +64 -0
- package/dist/src/html/parser.js +521 -0
- package/dist/src/html/parser.js.map +1 -0
- package/dist/src/html/template-types.d.ts +27 -0
- package/dist/src/html/template-types.js +8 -0
- package/dist/src/html/template-types.js.map +1 -0
- package/dist/src/html/template.d.ts +45 -0
- package/dist/src/html/template.js +119 -0
- package/dist/src/html/template.js.map +1 -0
- package/dist/src/html.d.ts +16 -0
- package/dist/src/html.js +16 -0
- package/dist/src/html.js.map +1 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.js +39 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lazy.d.ts +38 -0
- package/dist/src/lazy.js +73 -0
- package/dist/src/lazy.js.map +1 -0
- package/dist/src/lifecycle.d.ts +45 -0
- package/dist/src/lifecycle.js +66 -0
- package/dist/src/lifecycle.js.map +1 -0
- package/dist/src/page.d.ts +161 -0
- package/dist/src/page.js +38 -0
- package/dist/src/page.js.map +1 -0
- package/dist/src/persisted.d.ts +47 -0
- package/dist/src/persisted.js +119 -0
- package/dist/src/persisted.js.map +1 -0
- package/dist/src/resource.d.ts +120 -0
- package/dist/src/resource.js +275 -0
- package/dist/src/resource.js.map +1 -0
- package/dist/src/router/manifest.d.ts +56 -0
- package/dist/src/router/manifest.js +302 -0
- package/dist/src/router/manifest.js.map +1 -0
- package/dist/src/router/match.d.ts +62 -0
- package/dist/src/router/match.js +117 -0
- package/dist/src/router/match.js.map +1 -0
- package/dist/src/router/navigation.d.ts +89 -0
- package/dist/src/router/navigation.js +263 -0
- package/dist/src/router/navigation.js.map +1 -0
- package/dist/src/router.d.ts +13 -0
- package/dist/src/router.js +13 -0
- package/dist/src/router.js.map +1 -0
- package/dist/src/signal.d.ts +67 -0
- package/dist/src/signal.js +238 -0
- package/dist/src/signal.js.map +1 -0
- package/docs/README.md +12 -0
- package/docs/en/00-the-mado-way.md +106 -0
- package/docs/en/01-routing.md +204 -0
- package/docs/en/02-project-layout.md +58 -0
- package/docs/en/03-static-bake.md +251 -0
- package/docs/en/04-ide-setup.md +162 -0
- package/docs/en/05-why-mado.md +193 -0
- package/docs/en/06-for-backenders.md +422 -0
- package/docs/en/07-llm-pitfalls.md +486 -0
- package/docs/en/08-llm-zero-history-test.md +56 -0
- package/docs/en/09-shadow-vs-light-dom.md +122 -0
- package/docs/en/README.md +16 -0
- package/docs/fr/00-the-mado-way.md +108 -0
- package/docs/fr/01-routing.md +202 -0
- package/docs/fr/02-project-layout.md +58 -0
- package/docs/fr/03-static-bake.md +290 -0
- package/docs/fr/04-ide-setup.md +162 -0
- package/docs/fr/05-why-mado.md +193 -0
- package/docs/fr/06-for-backenders.md +432 -0
- package/docs/fr/07-llm-pitfalls.md +487 -0
- package/docs/fr/08-llm-zero-history-test.md +60 -0
- package/docs/fr/09-shadow-vs-light-dom.md +121 -0
- package/docs/fr/README.md +16 -0
- package/docs/ru/00-the-mado-way.md +93 -0
- package/docs/ru/01-routing.md +194 -0
- package/docs/ru/02-project-layout.md +57 -0
- package/docs/ru/03-static-bake.md +251 -0
- package/docs/ru/04-ide-setup.md +144 -0
- package/docs/ru/05-why-mado.md +193 -0
- package/docs/ru/06-for-backenders.md +422 -0
- package/docs/ru/07-llm-pitfalls.md +485 -0
- package/docs/ru/08-llm-zero-history-test.md +56 -0
- package/docs/ru/09-shadow-vs-light-dom.md +122 -0
- package/docs/ru/README.md +14 -0
- package/docs/uk/00-the-mado-way.md +54 -0
- package/docs/uk/01-routing.md +82 -0
- package/docs/uk/02-project-layout.md +46 -0
- package/docs/uk/03-static-bake.md +49 -0
- package/docs/uk/04-ide-setup.md +26 -0
- package/docs/uk/05-why-mado.md +34 -0
- package/docs/uk/06-for-backenders.md +50 -0
- package/docs/uk/07-llm-pitfalls.md +82 -0
- package/docs/uk/08-llm-zero-history-test.md +31 -0
- package/docs/uk/09-shadow-vs-light-dom.md +40 -0
- package/docs/uk/README.md +16 -0
- package/llms.txt +155 -0
- package/package.json +81 -0
- package/scripts/bake.mjs +406 -0
- package/scripts/bundle.mjs +146 -0
- package/scripts/cli.mjs +382 -0
- package/scripts/new.mjs +80 -0
- package/scripts/preview.mjs +176 -0
- package/scripts/release-notes.mjs +66 -0
- package/scripts/showcase-regression.mjs +392 -0
- package/server/serve.mjs +292 -0
- package/starters/crud/README.md +21 -0
- package/starters/crud/index.html +20 -0
- package/starters/crud/package.json +17 -0
- package/starters/crud/src/components/app-shell.ts +51 -0
- package/starters/crud/src/components/ticket-detail.ts +33 -0
- package/starters/crud/src/components/ticket-form.ts +69 -0
- package/starters/crud/src/components/ticket-list.ts +66 -0
- package/starters/crud/src/lib/api.ts +76 -0
- package/starters/crud/src/main.ts +12 -0
- package/starters/crud/src/pages/home.ts +18 -0
- package/starters/crud/src/pages/not-found.ts +12 -0
- package/starters/crud/src/pages/ticket-detail.ts +6 -0
- package/starters/crud/src/pages/ticket-new.ts +6 -0
- package/starters/crud/src/pages/tickets.ts +6 -0
- package/starters/crud/src/routes.ts +9 -0
- package/starters/crud/src/styles/global.ts +155 -0
- package/starters/crud/tsconfig.json +15 -0
- package/starters/minimal/README.md +19 -0
- package/starters/minimal/index.html +20 -0
- package/starters/minimal/package.json +17 -0
- package/starters/minimal/src/components/app-counter.ts +31 -0
- package/starters/minimal/src/main.ts +9 -0
- package/starters/minimal/src/pages/home.ts +18 -0
- package/starters/minimal/src/pages/not-found.ts +14 -0
- package/starters/minimal/src/routes.ts +6 -0
- package/starters/minimal/src/styles/global.ts +60 -0
- package/starters/minimal/tsconfig.json +15 -0
- package/templates/page-detail.ts +63 -0
- package/templates/page-form.ts +94 -0
- package/templates/page-list.ts +79 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# Mado pour les développeurs backend
|
|
2
|
+
|
|
3
|
+
> Vous écrivez en Go / Rust / .NET / Java / Python et vous devez construire une UI web.
|
|
4
|
+
> Cette page est le modèle mental de Mado en 10 minutes, dans votre langage.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## L'analogie principale
|
|
9
|
+
|
|
10
|
+
Mado est structuré **comme un serveur HTTP**. Sérieusement :
|
|
11
|
+
|
|
12
|
+
| Monde serveur | Mado |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Routeur HTTP (chi, axum, mux) | `routes()` — manifeste de chemins |
|
|
15
|
+
| Handler `func(req, resp)` | `page({ view: (ctx) => html\`...\` })` |
|
|
16
|
+
| Middleware | `layout` dans `nested()` (enveloppe le handler) |
|
|
17
|
+
| Moteur de template (Jinja, Handlebars) | tagged template `html\`\`` |
|
|
18
|
+
| Client HTTP avec cache | `resource()` — fetch + cache + invalidation |
|
|
19
|
+
| Variable réactive / atom | `signal()` — getter réactif |
|
|
20
|
+
| Goroutine de fond / tâche | `effect()` — se ré-exécute automatiquement quand un signal change |
|
|
21
|
+
| `defer cleanup()` | `ctx.onDispose(fn)` dans le setup du composant |
|
|
22
|
+
| Variables ENV | `createContext()` + `provide()`/`inject()` |
|
|
23
|
+
|
|
24
|
+
Si vous comprenez un serveur HTTP, vous comprenez Mado.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Structure de fichiers — comme une application ordinaire
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
src/
|
|
32
|
+
├── routes.ts ← manifeste de chemins (comme router.go dans chi)
|
|
33
|
+
├── main.ts ← point d'entrée (comme main.go : setup + run)
|
|
34
|
+
├── pages/ ← un fichier par page (comme handler.go)
|
|
35
|
+
├── components/ ← UI réutilisable (comme helpers/)
|
|
36
|
+
├── layouts/ ← wrappers pour des groupes de pages (comme middleware/)
|
|
37
|
+
└── lib/ ← logique métier, client API (comme service/, repo/)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Un fichier = une page. Pas de routage magique basé sur les fichiers — tout est déclaré à la main dans `routes.ts`.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Hello World — analogie serveur
|
|
45
|
+
|
|
46
|
+
### Go (chi) — pour comparaison
|
|
47
|
+
|
|
48
|
+
```go
|
|
49
|
+
r := chi.NewRouter()
|
|
50
|
+
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
|
51
|
+
w.Write([]byte("<h1>Bonjour</h1>"))
|
|
52
|
+
})
|
|
53
|
+
r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
|
|
54
|
+
id := chi.URLParam(r, "id")
|
|
55
|
+
fmt.Fprintf(w, "<h1>Utilisateur %s</h1>", id)
|
|
56
|
+
})
|
|
57
|
+
http.ListenAndServe(":8080", r)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Mado — la même chose
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// src/routes.ts
|
|
64
|
+
import { routes } from "@madojs/mado";
|
|
65
|
+
|
|
66
|
+
export default routes({
|
|
67
|
+
"/": () => import("./pages/home.js"),
|
|
68
|
+
"/users/:id": () => import("./pages/user.js"),
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// src/pages/home.ts
|
|
74
|
+
import { page, html } from "@madojs/mado";
|
|
75
|
+
export default page({
|
|
76
|
+
view: () => html`<h1>Bonjour</h1>`,
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// src/pages/user.ts
|
|
82
|
+
import { page, html } from "@madojs/mado";
|
|
83
|
+
export default page<{ id: string }>({
|
|
84
|
+
view: ({ params }) => html`<h1>Utilisateur ${params.id}</h1>`,
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Les paramètres de chemin sont disponibles dans `params` — exactement comme `chi.URLParam`.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Signals — une variable réactive
|
|
93
|
+
|
|
94
|
+
Si vous avez écrit Erlang/Elixir avec `Agent`, ou Rust avec `Arc<Mutex<T>>`, ou simplement
|
|
95
|
+
stocké de l'état dans une struct et l'avez mis à jour — `signal` est la même chose, plus
|
|
96
|
+
le **re-rendu automatique** des composants qui lisent cet état.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { signal, effect } from "@madojs/mado";
|
|
100
|
+
|
|
101
|
+
// "variable" avec abonnement
|
|
102
|
+
const count = signal(0);
|
|
103
|
+
|
|
104
|
+
// lecture
|
|
105
|
+
console.log(count()); // 0
|
|
106
|
+
|
|
107
|
+
// écriture
|
|
108
|
+
count.set(5);
|
|
109
|
+
|
|
110
|
+
// "goroutine" qui s'exécute à chaque changement
|
|
111
|
+
effect(() => {
|
|
112
|
+
console.log("count vaut maintenant", count());
|
|
113
|
+
});
|
|
114
|
+
// → affichera "count vaut maintenant 5"
|
|
115
|
+
|
|
116
|
+
count.set(10);
|
|
117
|
+
// → affichera "count vaut maintenant 10"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Pas de règles comme "ne peut pas être utilisé dans une condition". Un signal est juste une
|
|
121
|
+
fonction getter. Là où il est lu — c'est là que l'abonnement est créé.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## `resource()` — client HTTP avec cache (comme `cache.GetOrSet`)
|
|
126
|
+
|
|
127
|
+
C'est l'**abstraction la plus utile pour un développeur backend**. C'est comme Redis avec
|
|
128
|
+
invalidation automatique, mais dans le navigateur.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { resource, mutation, jsonFetcher, invalidate } from "@madojs/mado";
|
|
132
|
+
|
|
133
|
+
// "référentiel d'utilisateurs"
|
|
134
|
+
const userId = signal(1);
|
|
135
|
+
|
|
136
|
+
const user = resource(
|
|
137
|
+
() => `/api/users/${userId()}`, // clé de cache (réactive !)
|
|
138
|
+
jsonFetcher<User>(), // comment charger
|
|
139
|
+
{ staleTime: 60_000 }, // cache de 60 secondes
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// dans le composant :
|
|
143
|
+
user.data(); // User | undefined
|
|
144
|
+
user.error(); // Error | null
|
|
145
|
+
user.loading(); // boolean
|
|
146
|
+
|
|
147
|
+
// mutation (comme POST/PUT)
|
|
148
|
+
const save = mutation<User, User>(
|
|
149
|
+
(u) => fetch("/api/users", { method: "POST", body: JSON.stringify(u) }).then(r => r.json()),
|
|
150
|
+
{ invalidates: ["/api/users*"] }, // invalidation glob — comme `cache.Drop("users:*")`
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
await save.run(newUser);
|
|
154
|
+
// automatiquement : user.data() se mettra à jour si le glob correspond
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Si une telle abstraction existait dans le monde Go pour les caches côté serveur — on
|
|
158
|
+
pleurerait tous de joie.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Composants = handler avec sa propre mémoire
|
|
163
|
+
|
|
164
|
+
Un composant est un **handler** qui rend son bout d'UI. Il possède :
|
|
165
|
+
|
|
166
|
+
- des paramètres (attributs/propriétés) ;
|
|
167
|
+
- un état interne (`signal`s) ;
|
|
168
|
+
- un lifecycle : `connectedCallback` (comme Init), `disconnectedCallback` (comme Close).
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { component, html, signal } from "@madojs/mado";
|
|
172
|
+
|
|
173
|
+
component("x-counter", () => {
|
|
174
|
+
const count = signal(0);
|
|
175
|
+
|
|
176
|
+
return () => html`
|
|
177
|
+
<button @click=${() => count.update(n => n + 1)}>
|
|
178
|
+
Clics : ${count}
|
|
179
|
+
</button>
|
|
180
|
+
`;
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Utilisation :
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
html`<x-counter></x-counter>`
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Nous enregistrons le tag `<x-counter>` dans le navigateur — il devient une "fonction" qui
|
|
191
|
+
peut être insérée dans le HTML. C'est un mécanisme **natif** du navigateur (Web Components),
|
|
192
|
+
Mado ne fait que le coller avec les signals.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Forms — comme `form.Validate()` côté backend
|
|
197
|
+
|
|
198
|
+
Mado utilise la **validation HTML5 native**, plus ajoute le suivi d'état.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { useForm } from "@madojs/mado";
|
|
202
|
+
|
|
203
|
+
const f = useForm({
|
|
204
|
+
email: { required: true, type: "email" },
|
|
205
|
+
age: { required: true, type: "number", min: 18 },
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// dans le template :
|
|
209
|
+
html`
|
|
210
|
+
<form @submit=${f.onSubmit(async (v) => {
|
|
211
|
+
await api.save(v);
|
|
212
|
+
f.reset();
|
|
213
|
+
})}>
|
|
214
|
+
<input name="email" .value=${() => f.values().email ?? ""}
|
|
215
|
+
@input=${f.onInput} @blur=${f.onBlur} />
|
|
216
|
+
|
|
217
|
+
${() => f.errors().email && f.touched().email
|
|
218
|
+
? html`<small>${f.errors().email}</small>`
|
|
219
|
+
: null}
|
|
220
|
+
|
|
221
|
+
<button ?disabled=${() => !f.isValid() || f.submitting()}>Enregistrer</button>
|
|
222
|
+
</form>
|
|
223
|
+
`;
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Validation personnalisée — `validate: (values) => errors | null`. Pas de schémas Yup ni de dépendances.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Context = DI / injection de dépendances
|
|
231
|
+
|
|
232
|
+
Tout comme vous passez `context.Context` à travers la pile d'appels en Go — dans Mado le
|
|
233
|
+
context est propagé à travers l'arbre DOM.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import { createContext, provide, inject } from "@madojs/mado";
|
|
237
|
+
|
|
238
|
+
// déclarer le "type" de la dépendance
|
|
239
|
+
const ApiCtx = createContext<ApiClient>(defaultApiClient);
|
|
240
|
+
|
|
241
|
+
// dans le composant racine — fournir
|
|
242
|
+
component("x-app", ({ host }) => {
|
|
243
|
+
provide(host, ApiCtx, new ApiClient("https://api.example.com"));
|
|
244
|
+
return () => html`<x-page/>`;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// dans n'importe quel enfant — consommer
|
|
248
|
+
component("x-page", ({ host }) => {
|
|
249
|
+
const api = inject(host, ApiCtx); // signal<ApiClient>
|
|
250
|
+
return () => html`<div>Version API : ${() => api().version}</div>`;
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
C'est comme `context.WithValue` / `ctx.Value` en Go, mais réactif.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## SEO — pas du SSR, mais `bake` (comme `templ generate` en Go)
|
|
259
|
+
|
|
260
|
+
Si vous êtes habitué au rendu côté serveur pour le SEO, dans Mado c'est résolu différemment :
|
|
261
|
+
**prérendu au moment du build**.
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
// src/pages/product.ts
|
|
265
|
+
export default page({
|
|
266
|
+
bake: {
|
|
267
|
+
paths: () => api.allProductSlugs(), // fetch au build-time
|
|
268
|
+
data: ({ slug }) => api.getProduct(slug),
|
|
269
|
+
revalidate: 3600,
|
|
270
|
+
},
|
|
271
|
+
head: ({ slug }, data) => ({
|
|
272
|
+
description: data.description,
|
|
273
|
+
canonical: `/product/${slug}`,
|
|
274
|
+
og: { title: data.name, image: data.image },
|
|
275
|
+
}),
|
|
276
|
+
view: ({ params }) => html`<x-product data-slug=${params.slug}/>`,
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
npm run bake # → out/product/iphone-15/index.html (+ sitemap)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Le crawler voit du HTML prêt avec des meta tags. L'utilisateur voit la même chose + interactivité
|
|
285
|
+
après le chargement du JS.
|
|
286
|
+
|
|
287
|
+
Plus de détails : [`03-static-bake.md`](./03-static-bake.md).
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Tâches typiques d'un développeur backend — recettes
|
|
292
|
+
|
|
293
|
+
### Page CRUD avec une liste
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import { page, html, resource, each, signal } from "@madojs/mado";
|
|
297
|
+
|
|
298
|
+
export default page({
|
|
299
|
+
view: () => {
|
|
300
|
+
const users = resource(() => "/api/users", jsonFetcher<User[]>());
|
|
301
|
+
|
|
302
|
+
return html`
|
|
303
|
+
${() => users.loading() ? html`<p>Chargement…</p>` : null}
|
|
304
|
+
${() => users.error() ? html`<p>Erreur : ${users.error()!.message}</p>` : null}
|
|
305
|
+
<ul>
|
|
306
|
+
${() => each(users.data() ?? [], u => u.id, u => html`
|
|
307
|
+
<li><a href="/users/${u.id}" data-link>${u.name}</a></li>
|
|
308
|
+
`)}
|
|
309
|
+
</ul>
|
|
310
|
+
`;
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Formulaire avec POST
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
import { useForm, mutation } from "@madojs/mado";
|
|
319
|
+
|
|
320
|
+
const createUser = mutation<NewUser, User>(
|
|
321
|
+
(u) => fetch("/api/users", { method: "POST", body: JSON.stringify(u) }).then(r => r.json()),
|
|
322
|
+
{ invalidates: ["/api/users*"] },
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
// dans page.view :
|
|
326
|
+
const f = useForm({ name: { required: true } });
|
|
327
|
+
|
|
328
|
+
html`
|
|
329
|
+
<form @submit=${f.onSubmit(async (v) => {
|
|
330
|
+
await createUser.run(v);
|
|
331
|
+
navigate("/users");
|
|
332
|
+
})}>
|
|
333
|
+
<input name="name" @input=${f.onInput}>
|
|
334
|
+
<button>Créer</button>
|
|
335
|
+
</form>
|
|
336
|
+
`;
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Zone protégée (middleware auth)
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
// src/layouts/auth-layout.ts
|
|
343
|
+
import { page, html, effect } from "@madojs/mado";
|
|
344
|
+
import { isAuthed, navigate } from "../lib/auth.js";
|
|
345
|
+
|
|
346
|
+
export default page({
|
|
347
|
+
view: ({ child }) => {
|
|
348
|
+
effect(() => {
|
|
349
|
+
if (!isAuthed()) navigate("/login");
|
|
350
|
+
});
|
|
351
|
+
return html`<div class="app-shell">${child}</div>`;
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
// src/routes.ts
|
|
358
|
+
import { routes, nested } from "@madojs/mado";
|
|
359
|
+
|
|
360
|
+
export default routes({
|
|
361
|
+
"/login": () => import("./pages/login.js"),
|
|
362
|
+
|
|
363
|
+
"/app/*": nested({
|
|
364
|
+
layout: () => import("./layouts/auth-layout.js"),
|
|
365
|
+
routes: {
|
|
366
|
+
"dashboard": () => import("./pages/dashboard.js"),
|
|
367
|
+
"users": () => import("./pages/users.js"),
|
|
368
|
+
},
|
|
369
|
+
}),
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Client API global (comme un singleton en Go)
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
// src/lib/api.ts
|
|
377
|
+
export class ApiClient {
|
|
378
|
+
constructor(private base: string) {}
|
|
379
|
+
get<T>(path: string): Promise<T> {
|
|
380
|
+
return fetch(this.base + path).then(r => r.json());
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export const api = new ApiClient("/api");
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Utilisé directement via `import { api } from '...'` ou via `createContext` pour la testabilité.
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## Ce que vous n'avez **pas** besoin d'apprendre (bonne nouvelle)
|
|
392
|
+
|
|
393
|
+
- **Hooks et règles des hooks.** Pas dans Mado. Les signals sont des fonctions ordinaires.
|
|
394
|
+
- **VDOM et réconciliation.** Aucun. Les signals mettent à jour le DOM directement, chirurgicalement.
|
|
395
|
+
- **Configurations Webpack/Vite.** Pas de build. `tsc → navigateur`.
|
|
396
|
+
- **Tableaux de dépendances `useEffect`.** `effect()` voit ce que vous lisez de lui-même.
|
|
397
|
+
- **Bibliothèques de gestion d'état** (Redux/Zustand). Signals + context.
|
|
398
|
+
- **Transformations CSS-in-JS.** Shadow DOM + `css\`\`` + cssVars.
|
|
399
|
+
- **Guide de migration routing v6 → v7.** `routes()` fait 500 lignes, lisible en 20 minutes.
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Ce que vous **devrez** apprendre (honnêtement)
|
|
404
|
+
|
|
405
|
+
Ce sont de nouveaux concepts. Pas effrayants, mais ce sont des additions à votre base React/Vue :
|
|
406
|
+
|
|
407
|
+
1. **Custom Elements / Shadow DOM.** `<x-foo>` n'est pas une div, c'est un élément à part entière avec son propre DOM. Slots, CSS scopé. Une soirée de lecture MDN.
|
|
408
|
+
2. **`attribute` vs `property`.** L'attribut est une string en HTML (`data-id="5"`), la property est une propriété JS (`el.id = 5`). `?attr=${flag}` et `.prop=${value}` dans les templates se réfèrent à des choses différentes. Règle principale : **nombres/objets/tableaux — via `.prop`, drapeaux — via `?attr`, strings — via `attr`**.
|
|
409
|
+
3. **Signals.** Si c'est votre première fois — vous bloquerez 10 minutes, puis c'est plus facile que les hooks.
|
|
410
|
+
4. **templates `html\`\``.** C'est juste une fonction JS avec coloration via [lit-plugin](./04-ide-setup.md). Pas de magie.
|
|
411
|
+
|
|
412
|
+
Tout le reste — navigateur standard + TypeScript.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Ce qui manque (honnêtement)
|
|
417
|
+
|
|
418
|
+
- Pas de hot reload, seulement un rechargement complet via SSE. Suffisant pour la plupart des cas, mais pas comme Vite.
|
|
419
|
+
- Pas d'extension navigateur dev-tools. Utilisez `localStorage.madoDebug = '1'` + console.
|
|
420
|
+
- Pas de starters StackBlitz (encore).
|
|
421
|
+
- Pas d'assistant IA qui connaît Mado aussi bien que React. En cas de doute — lisez `src/`, c'est pas effrayant.
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Lecture complémentaire
|
|
426
|
+
|
|
427
|
+
- **[`01-routing.md`](./01-routing.md)** — le router en détail.
|
|
428
|
+
- **[`02-project-layout.md`](./02-project-layout.md)** — structure du projet.
|
|
429
|
+
- **[`03-static-bake.md`](./03-static-bake.md)** — SEO sans SSR.
|
|
430
|
+
- **[`examples/showcase/`](../../examples/showcase/)** — exemple complet (landing + admin).
|
|
431
|
+
|
|
432
|
+
Si quelque chose n'est pas clair — ouvrez une issue, ou ouvrez simplement le source. Il est vraiment lisible en une soirée.
|