@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.
Files changed (162) hide show
  1. package/AGENTS.md +291 -0
  2. package/CHANGELOG.md +23 -0
  3. package/LICENSE +21 -0
  4. package/README.md +371 -0
  5. package/ROADMAP.md +52 -0
  6. package/dist/src/component.d.ts +48 -0
  7. package/dist/src/component.js +140 -0
  8. package/dist/src/component.js.map +1 -0
  9. package/dist/src/context.d.ts +40 -0
  10. package/dist/src/context.js +67 -0
  11. package/dist/src/context.js.map +1 -0
  12. package/dist/src/css.d.ts +54 -0
  13. package/dist/src/css.js +137 -0
  14. package/dist/src/css.js.map +1 -0
  15. package/dist/src/devtools.d.ts +22 -0
  16. package/dist/src/devtools.js +63 -0
  17. package/dist/src/devtools.js.map +1 -0
  18. package/dist/src/diagnostics.d.ts +11 -0
  19. package/dist/src/diagnostics.js +28 -0
  20. package/dist/src/diagnostics.js.map +1 -0
  21. package/dist/src/each.d.ts +39 -0
  22. package/dist/src/each.js +35 -0
  23. package/dist/src/each.js.map +1 -0
  24. package/dist/src/forms.d.ts +71 -0
  25. package/dist/src/forms.js +161 -0
  26. package/dist/src/forms.js.map +1 -0
  27. package/dist/src/head.d.ts +19 -0
  28. package/dist/src/head.js +97 -0
  29. package/dist/src/head.js.map +1 -0
  30. package/dist/src/html/bindings.d.ts +78 -0
  31. package/dist/src/html/bindings.js +304 -0
  32. package/dist/src/html/bindings.js.map +1 -0
  33. package/dist/src/html/parser.d.ts +64 -0
  34. package/dist/src/html/parser.js +521 -0
  35. package/dist/src/html/parser.js.map +1 -0
  36. package/dist/src/html/template-types.d.ts +27 -0
  37. package/dist/src/html/template-types.js +8 -0
  38. package/dist/src/html/template-types.js.map +1 -0
  39. package/dist/src/html/template.d.ts +45 -0
  40. package/dist/src/html/template.js +119 -0
  41. package/dist/src/html/template.js.map +1 -0
  42. package/dist/src/html.d.ts +16 -0
  43. package/dist/src/html.js +16 -0
  44. package/dist/src/html.js.map +1 -0
  45. package/dist/src/index.d.ts +35 -0
  46. package/dist/src/index.js +39 -0
  47. package/dist/src/index.js.map +1 -0
  48. package/dist/src/lazy.d.ts +38 -0
  49. package/dist/src/lazy.js +73 -0
  50. package/dist/src/lazy.js.map +1 -0
  51. package/dist/src/lifecycle.d.ts +45 -0
  52. package/dist/src/lifecycle.js +66 -0
  53. package/dist/src/lifecycle.js.map +1 -0
  54. package/dist/src/page.d.ts +161 -0
  55. package/dist/src/page.js +38 -0
  56. package/dist/src/page.js.map +1 -0
  57. package/dist/src/persisted.d.ts +47 -0
  58. package/dist/src/persisted.js +119 -0
  59. package/dist/src/persisted.js.map +1 -0
  60. package/dist/src/resource.d.ts +120 -0
  61. package/dist/src/resource.js +275 -0
  62. package/dist/src/resource.js.map +1 -0
  63. package/dist/src/router/manifest.d.ts +56 -0
  64. package/dist/src/router/manifest.js +302 -0
  65. package/dist/src/router/manifest.js.map +1 -0
  66. package/dist/src/router/match.d.ts +62 -0
  67. package/dist/src/router/match.js +117 -0
  68. package/dist/src/router/match.js.map +1 -0
  69. package/dist/src/router/navigation.d.ts +89 -0
  70. package/dist/src/router/navigation.js +263 -0
  71. package/dist/src/router/navigation.js.map +1 -0
  72. package/dist/src/router.d.ts +13 -0
  73. package/dist/src/router.js +13 -0
  74. package/dist/src/router.js.map +1 -0
  75. package/dist/src/signal.d.ts +67 -0
  76. package/dist/src/signal.js +238 -0
  77. package/dist/src/signal.js.map +1 -0
  78. package/docs/README.md +12 -0
  79. package/docs/en/00-the-mado-way.md +106 -0
  80. package/docs/en/01-routing.md +204 -0
  81. package/docs/en/02-project-layout.md +58 -0
  82. package/docs/en/03-static-bake.md +251 -0
  83. package/docs/en/04-ide-setup.md +162 -0
  84. package/docs/en/05-why-mado.md +193 -0
  85. package/docs/en/06-for-backenders.md +422 -0
  86. package/docs/en/07-llm-pitfalls.md +486 -0
  87. package/docs/en/08-llm-zero-history-test.md +56 -0
  88. package/docs/en/09-shadow-vs-light-dom.md +122 -0
  89. package/docs/en/README.md +16 -0
  90. package/docs/fr/00-the-mado-way.md +108 -0
  91. package/docs/fr/01-routing.md +202 -0
  92. package/docs/fr/02-project-layout.md +58 -0
  93. package/docs/fr/03-static-bake.md +290 -0
  94. package/docs/fr/04-ide-setup.md +162 -0
  95. package/docs/fr/05-why-mado.md +193 -0
  96. package/docs/fr/06-for-backenders.md +432 -0
  97. package/docs/fr/07-llm-pitfalls.md +487 -0
  98. package/docs/fr/08-llm-zero-history-test.md +60 -0
  99. package/docs/fr/09-shadow-vs-light-dom.md +121 -0
  100. package/docs/fr/README.md +16 -0
  101. package/docs/ru/00-the-mado-way.md +93 -0
  102. package/docs/ru/01-routing.md +194 -0
  103. package/docs/ru/02-project-layout.md +57 -0
  104. package/docs/ru/03-static-bake.md +251 -0
  105. package/docs/ru/04-ide-setup.md +144 -0
  106. package/docs/ru/05-why-mado.md +193 -0
  107. package/docs/ru/06-for-backenders.md +422 -0
  108. package/docs/ru/07-llm-pitfalls.md +485 -0
  109. package/docs/ru/08-llm-zero-history-test.md +56 -0
  110. package/docs/ru/09-shadow-vs-light-dom.md +122 -0
  111. package/docs/ru/README.md +14 -0
  112. package/docs/uk/00-the-mado-way.md +54 -0
  113. package/docs/uk/01-routing.md +82 -0
  114. package/docs/uk/02-project-layout.md +46 -0
  115. package/docs/uk/03-static-bake.md +49 -0
  116. package/docs/uk/04-ide-setup.md +26 -0
  117. package/docs/uk/05-why-mado.md +34 -0
  118. package/docs/uk/06-for-backenders.md +50 -0
  119. package/docs/uk/07-llm-pitfalls.md +82 -0
  120. package/docs/uk/08-llm-zero-history-test.md +31 -0
  121. package/docs/uk/09-shadow-vs-light-dom.md +40 -0
  122. package/docs/uk/README.md +16 -0
  123. package/llms.txt +155 -0
  124. package/package.json +81 -0
  125. package/scripts/bake.mjs +406 -0
  126. package/scripts/bundle.mjs +146 -0
  127. package/scripts/cli.mjs +382 -0
  128. package/scripts/new.mjs +80 -0
  129. package/scripts/preview.mjs +176 -0
  130. package/scripts/release-notes.mjs +66 -0
  131. package/scripts/showcase-regression.mjs +392 -0
  132. package/server/serve.mjs +292 -0
  133. package/starters/crud/README.md +21 -0
  134. package/starters/crud/index.html +20 -0
  135. package/starters/crud/package.json +17 -0
  136. package/starters/crud/src/components/app-shell.ts +51 -0
  137. package/starters/crud/src/components/ticket-detail.ts +33 -0
  138. package/starters/crud/src/components/ticket-form.ts +69 -0
  139. package/starters/crud/src/components/ticket-list.ts +66 -0
  140. package/starters/crud/src/lib/api.ts +76 -0
  141. package/starters/crud/src/main.ts +12 -0
  142. package/starters/crud/src/pages/home.ts +18 -0
  143. package/starters/crud/src/pages/not-found.ts +12 -0
  144. package/starters/crud/src/pages/ticket-detail.ts +6 -0
  145. package/starters/crud/src/pages/ticket-new.ts +6 -0
  146. package/starters/crud/src/pages/tickets.ts +6 -0
  147. package/starters/crud/src/routes.ts +9 -0
  148. package/starters/crud/src/styles/global.ts +155 -0
  149. package/starters/crud/tsconfig.json +15 -0
  150. package/starters/minimal/README.md +19 -0
  151. package/starters/minimal/index.html +20 -0
  152. package/starters/minimal/package.json +17 -0
  153. package/starters/minimal/src/components/app-counter.ts +31 -0
  154. package/starters/minimal/src/main.ts +9 -0
  155. package/starters/minimal/src/pages/home.ts +18 -0
  156. package/starters/minimal/src/pages/not-found.ts +14 -0
  157. package/starters/minimal/src/routes.ts +6 -0
  158. package/starters/minimal/src/styles/global.ts +60 -0
  159. package/starters/minimal/tsconfig.json +15 -0
  160. package/templates/page-detail.ts +63 -0
  161. package/templates/page-form.ts +94 -0
  162. package/templates/page-list.ts +79 -0
package/AGENTS.md ADDED
@@ -0,0 +1,291 @@
1
+ # Agent rules for Mado
2
+
3
+ > This file is read by AI agents in IDEs (Cursor, Cline, Copilot, Continue, etc.).
4
+ > Goal: prevent them from generating React-like code where Mado should be used.
5
+
6
+ ## Project at a glance
7
+
8
+ - **Mado** — SPA framework built on Web Components + signals + tagged-template `html`.
9
+ - No build system (only `tsc`), no runtime dependencies.
10
+ - Small TypeScript core in `src/`; bundled/minified full API is roughly 11 KB gzip.
11
+
12
+ ## HARD RULES — violation = bug
13
+
14
+ ### 1. Templates — tagged template `html\`\`` only
15
+
16
+ ```ts
17
+ // ❌ NO
18
+ const view = <button onClick={fn}>{count}</button>;
19
+
20
+ // ❌ NO (this is Vue)
21
+ const view = `<button @click="fn">{{ count }}</button>`;
22
+
23
+ // ✅ YES
24
+ const view = html`<button @click=${fn}>${count}</button>`;
25
+ ```
26
+
27
+ ### 2. Reactivity — only via `signal()` / `computed()` / `effect()`
28
+
29
+ ```ts
30
+ // ❌ NO (this is React)
31
+ const [count, setCount] = useState(0);
32
+ useEffect(() => { ... }, [count]);
33
+
34
+ // ❌ NO (this is Vue/Svelte)
35
+ let count = $state(0);
36
+ const ref = ref(0);
37
+
38
+ // ✅ YES
39
+ const count = signal(0);
40
+ const doubled = computed(() => count() * 2);
41
+ effect(() => console.log(count()));
42
+ count.set(5);
43
+ count.update(n => n + 1);
44
+ ```
45
+
46
+ **A signal is a getter function**: read as `count()`, not `count.value`.
47
+
48
+ ### 3. Components — Web Components via `component()`
49
+
50
+ ```ts
51
+ // ❌ NO (classes / decorators / Lit-style)
52
+ class MyButton extends HTMLElement { ... }
53
+ @customElement('my-button')
54
+ class MyButton extends LitElement { ... }
55
+
56
+ // ❌ NO (React-style functional components)
57
+ function Counter() { return <button>...</button>; }
58
+
59
+ // ✅ YES
60
+ component("x-counter", (ctx) => {
61
+ const count = signal(0);
62
+ // setup: return a renderer function
63
+ return () => html`<button @click=${() => count.update(n => n + 1)}>${count}</button>`;
64
+ });
65
+ ```
66
+
67
+ - The element name **must contain a hyphen** (`x-foo`, `my-btn`, `app-shell`).
68
+ - `setup()` is called once on connect. Inside, we create signals and resources.
69
+ - We return a renderer function — it is called reactively.
70
+
71
+ ### 4. Cleanup — `ctx.onDispose(fn)`
72
+
73
+ ```ts
74
+ // ❌ NO (React)
75
+ useEffect(() => {
76
+ const id = setInterval(...);
77
+ return () => clearInterval(id);
78
+ }, []);
79
+
80
+ // ✅ YES
81
+ component("x-timer", (ctx) => {
82
+ const id = setInterval(..., 1000);
83
+ ctx.onDispose(() => clearInterval(id));
84
+ return () => html`...`;
85
+ });
86
+ ```
87
+
88
+ **`resource()`, `effect()`, and subscriptions inside `setup()` hook into the lifecycle automatically** — no need to write onDispose for them.
89
+
90
+ ### 5. Reactive value in template child position = function
91
+
92
+ The most common AI mistake:
93
+
94
+ ```ts
95
+ const count = signal(0);
96
+
97
+ // ❌ NOT REACTIVE — count() is read once
98
+ html`<div>${count() * 2}</div>`
99
+
100
+ // ✅ REACTIVE — the function will be called when count changes
101
+ html`<div>${() => count() * 2}</div>`
102
+
103
+ // ✅ ALSO OK — the signal itself is a function, Mado recognizes it
104
+ html`<div>${count}</div>`
105
+ ```
106
+
107
+ **Rule of thumb:** if there is a signal call (with parentheses) inside `${...}`, wrap it in `() => ...`.
108
+
109
+ ### 6. Attribute bindings
110
+
111
+ ```ts
112
+ // string/number → attribute
113
+ html`<a href=${url}>...</a>`
114
+
115
+ // DOM property (objects, numbers without serialization, .value for input)
116
+ html`<input .value=${user.name}>`
117
+ html`<my-list .items=${arr}>`
118
+
119
+ // boolean attribute (toggle)
120
+ html`<button ?disabled=${isLoading}>...</button>`
121
+
122
+ // event
123
+ html`<button @click=${fn}>...</button>`
124
+ ```
125
+
126
+ Common mistake: `disabled=${loading()}` — this attempts to set a **string** attribute `disabled="true"` or `disabled="false"`, which does not work correctly. **Use `?disabled=`.**
127
+
128
+ ### 7. Lists — via `each()`
129
+
130
+ ```ts
131
+ import { each } from "@madojs/mado";
132
+
133
+ // ❌ Works, but no keyed reconciliation → loses focus on reorder
134
+ html`<ul>${() => items().map(t => html`<li>${t.name}</li>`)}</ul>`
135
+
136
+ // ✅ Correct: keyed, reuses DOM nodes
137
+ html`<ul>${() => each(items(), t => t.id, t => html`<li>${t.name}</li>`)}</ul>`
138
+ ```
139
+
140
+ ### 8. Routing — `routes()` + `page()`
141
+
142
+ ```ts
143
+ // routes.ts — manifest
144
+ import { routes } from "@madojs/mado";
145
+ export default routes({
146
+ "/": () => import("./pages/home.js"),
147
+ "/users/:id": () => import("./pages/user.js"),
148
+ "*": () => import("./pages/not-found.js"),
149
+ });
150
+
151
+ // page file
152
+ import { page, html } from "@madojs/mado";
153
+ export default page<{ id: string }>({
154
+ title: ({ id }) => `User ${id}`,
155
+ view: ({ params }) => html`<x-user data-id=${params.id}></x-user>`,
156
+ });
157
+ ```
158
+
159
+ - Each page is a **separate file** in `pages/` with `export default page({...})`.
160
+ - Import via `() => import("./pages/foo.js")` — this enables code-splitting via ESM.
161
+ - Programmatic navigation: `import { navigate } from "@madojs/mado"; navigate("/users/42")`.
162
+
163
+ ### 9. Data fetching — `resource()` / `mutation()`
164
+
165
+ ```ts
166
+ // ❌ NO (React Query / SWR)
167
+ const { data } = useQuery(['user', id], () => fetch(...));
168
+
169
+ // ✅ YES
170
+ import { resource, jsonFetcher, mutation, invalidate } from "@madojs/mado";
171
+
172
+ const user = resource(
173
+ () => `/api/users/${userId()}`, // key (reactive — will recreate on change)
174
+ jsonFetcher<User>(), // how to load
175
+ { staleTime: 60_000 },
176
+ );
177
+ // user.data() / user.error() / user.loading() / user.refresh() / user.mutate()
178
+
179
+ const save = mutation<User, User>(
180
+ (u) => fetch("/api/users", { method: "POST", body: JSON.stringify(u) }).then(r => r.json()),
181
+ { invalidates: ["/api/users*"] }, // glob invalidation
182
+ );
183
+ await save.run(newUser);
184
+ ```
185
+
186
+ ### 10. Forms — `useForm()`
187
+
188
+ ```ts
189
+ // ❌ NO (Formik / RHF / Yup)
190
+ const { register, handleSubmit } = useForm({ resolver: yupResolver(schema) });
191
+
192
+ // ✅ YES
193
+ import { useForm } from "@madojs/mado";
194
+
195
+ const f = useForm({
196
+ email: { required: true, type: "email" },
197
+ age: { required: true, type: "number", min: 18 },
198
+ });
199
+
200
+ html`
201
+ <form @submit=${f.onSubmit(async v => { await api.save(v); })}>
202
+ <input name="email" @input=${f.onInput} @blur=${f.onBlur}>
203
+ ${() => f.touched().email && f.errors().email
204
+ ? html`<small>${f.errors().email}</small>` : null}
205
+ <button ?disabled=${() => !f.isValid() || f.submitting()}>Save</button>
206
+ </form>
207
+ `;
208
+ ```
209
+
210
+ Custom validation — `validate: (values) => ({ field: 'error message' } | null)`.
211
+
212
+ ### 11. Styles — `css\`\`` + Shadow DOM by default
213
+
214
+ ```ts
215
+ import { component, css, html } from "@madojs/mado";
216
+
217
+ component("x-card", () => () => html`<div><slot></slot></div>`, {
218
+ styles: css`
219
+ :host { display: block; padding: 1rem; }
220
+ div { background: var(--bg); }
221
+ ::slotted(h2) { margin: 0; }
222
+ `,
223
+ });
224
+
225
+ // Light DOM (without Shadow), global styles:
226
+ component("x-shell", () => () => html`...`, {
227
+ shadow: false, // disables Shadow DOM
228
+ styles: css`x-shell header { ... }`, // selectors are written as usual
229
+ });
230
+ ```
231
+
232
+ ### 12. Context (DI) — `createContext` / `provide` / `inject`
233
+
234
+ ```ts
235
+ import { createContext, provide, inject } from "@madojs/mado";
236
+
237
+ const ApiCtx = createContext<ApiClient>(defaultApi);
238
+
239
+ component("x-app", ({ host }) => {
240
+ provide(host, ApiCtx, new ApiClient(...));
241
+ return () => html`<x-child></x-child>`;
242
+ });
243
+
244
+ component("x-child", ({ host }) => {
245
+ const api = inject(host, ApiCtx); // signal<ApiClient>
246
+ return () => html`<div>${() => api().version}</div>`;
247
+ });
248
+ ```
249
+
250
+ ## SOFT GUIDELINES — recommended, but not critical
251
+
252
+ - **TypeScript strict.** Use `noUncheckedIndexedAccess`-aware code (with `!` or a type guard).
253
+ - **Import using `.js`** (not `.ts`) — this is required by ES modules in the browser: `import { foo } from "./bar.js"`.
254
+ - **One file = one responsibility.** Don't put 5 components in one file "because they're all small".
255
+ - **Do not add runtime dependencies** (`npm install` in `dependencies`). This violates the framework's principle.
256
+ - **JSDoc on public functions** is required. Comments explain "why", not "what".
257
+
258
+ ## Project structure
259
+
260
+ ```
261
+ src/
262
+ ├── routes.ts ← route manifest, ONE file
263
+ ├── main.ts ← entry: mount to #app
264
+ ├── pages/ ← one page = one file
265
+ ├── components/ ← reusable x-* components
266
+ ├── layouts/ ← layout for nested routes
267
+ └── lib/ ← API client, contexts, pure logic
268
+ ```
269
+
270
+ ## Where to find specific answers
271
+
272
+ | Question | File |
273
+ |---|---|
274
+ | How does reactivity work? | `src/signal.ts` (283 lines) |
275
+ | How are templates parsed? | `src/html.ts` (1013 lines) |
276
+ | How does the router work? | `src/router.ts` (~530 lines) |
277
+ | How does resource + cache work? | `src/resource.ts` (297 lines) |
278
+ | How do forms work? | `src/forms.ts` (212 lines) |
279
+ | When something goes wrong | `docs/en/07-llm-pitfalls.md` |
280
+
281
+ ## Before committing
282
+
283
+ ```bash
284
+ npm run typecheck # must pass
285
+ npm run build # must build without warnings
286
+ npm test # all tests green
287
+ ```
288
+
289
+ ## TL;DR for the agent
290
+
291
+ > If you are about to generate `useState`, JSX, `class extends HTMLElement`, `useEffect` with a return cleanup, `useForm` with yupResolver, `useQuery` with queryClient — **stop, you are not writing Mado**. Read this file again.
package/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ ## 0.5.0
4
+
5
+ First public release preparation for Mado.
6
+
7
+ ### Added
8
+
9
+ - `mado init <name>` project creation command.
10
+ - `minimal` and `crud` starters included in the npm package.
11
+ - GitHub CI workflow with typecheck, build, tests, package dry-run and tarball starter smoke.
12
+ - Tag-based release workflow for npm publishing after Trusted Publishing is configured.
13
+ - Manual browser regression workflow.
14
+ - Release notes generator based on Conventional Commit subjects.
15
+
16
+ ### Changed
17
+
18
+ - Package version is set to `0.5.0`.
19
+ - npm package files include starters, AI-agent docs and changelog.
20
+
21
+ ### Release Notes
22
+
23
+ - First npm publish is intended to be manual.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 madojs contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,371 @@
1
+ <div align="center">
2
+ <img
3
+ src="./docs/assets/brand/mado-logo-light.png"
4
+ alt="MadoJS"
5
+ width="560"
6
+ />
7
+
8
+ <p>
9
+ <strong>A small native-web SPA framework you can read in an evening.</strong>
10
+ </p>
11
+
12
+ <p>
13
+ <code>tsc → browser</code> · Zero runtime dependencies · No required bundler
14
+ </p>
15
+ </div>
16
+
17
+
18
+ # Mado
19
+
20
+ > A small native-web SPA framework you can read in an evening.
21
+
22
+ Mado is a thin TypeScript framework on top of the browser platform: Web
23
+ Components, signals, tagged-template `html`, a router, resources, forms,
24
+ context, persisted state and static prerender. No runtime dependencies, no
25
+ required bundler, no hidden build pipeline: `tsc → browser`.
26
+
27
+ Mado (`窓`) means “window” in Japanese: a calm native-web window into an app,
28
+ without dragging a whole frontend factory into the room.
29
+
30
+ ```txt
31
+ Runtime budget after build:
32
+ native ESM graph: ~60 KB raw, ~24 KB gzip across separate modules
33
+ bundled/minified full API: ~32 KB raw, ~11 KB gzip, ~10 KB brotli
34
+ Runtime dependencies: 0
35
+ Required dev dependency: typescript
36
+ ```
37
+
38
+ ## Who It Is For
39
+
40
+ - Backend developers who need admin panels, dashboards or internal tools
41
+ without learning another build stack first.
42
+ - Frontend developers who like the browser platform and want a compact,
43
+ readable tool instead of a large ecosystem contract.
44
+ - Small teams that value ownership: if something breaks, `src/` is small
45
+ enough to inspect and patch.
46
+ - Landing pages, widgets, embedded tools and CRUD-heavy SPA surfaces where
47
+ predictable code matters more than framework fashion.
48
+
49
+ ## Who It Is Not For
50
+
51
+ - Beginners learning frontend for the first time. React, Vue and Svelte have
52
+ far larger learning ecosystems.
53
+ - Teams that need a ready-made UI-kit ecosystem comparable to React.
54
+ - Products that require SSR with hydration as a hard requirement.
55
+ - Projects that need a mature plugin marketplace today.
56
+ - Teams that are uncomfortable using a pre-v1 framework.
57
+
58
+ ## Why Not Lit, Solid Or htmx?
59
+
60
+ Short honest version:
61
+
62
+ - **Lit** is better for design systems and reusable components that must live
63
+ inside many host frameworks. Mado is for whole apps: router, data, forms and
64
+ SEO tools in one small package.
65
+ - **Solid** is faster and more mature. It also expects a JSX transform and a
66
+ build pipeline. Mado intentionally works with browser ESM and `tsc`.
67
+ - **htmx** is excellent when your backend wants to own HTML fragments. Mado is
68
+ for cases where you still want an SPA: local state, optimistic updates,
69
+ cached resources, query params, lazy modules and persisted UI state.
70
+
71
+ Mado does not try to win synthetic benchmark marketing. It avoids a Virtual DOM,
72
+ uses fine-grained signals and keyed DOM reconciliation, and aims to stay fast
73
+ enough for serious admin apps while remaining small and readable.
74
+
75
+ ## Quick Start
76
+
77
+ ### Start a new app
78
+
79
+ ```bash
80
+ npm exec --package @madojs/mado@latest -- mado init my-app
81
+ cd my-app
82
+ npm install
83
+ npm run build
84
+ npm run serve
85
+ ```
86
+
87
+ Use the CRUD starter when you want a compact admin-style example with
88
+ `resource()`, `mutation()`, `useForm()`, `each()` and `queryParam()`:
89
+
90
+ ```bash
91
+ npm exec --package @madojs/mado@latest -- mado init my-app --starter crud
92
+ ```
93
+
94
+ ### Try the repository examples
95
+
96
+ ```bash
97
+ git clone https://github.com/madojs/mado.git
98
+ cd mado
99
+ npm install
100
+ npm run build
101
+ npm run serve -- basic
102
+ ```
103
+
104
+ ### Small component example
105
+
106
+ ```ts
107
+ // src/pages/counter.ts
108
+ import { component, css, html, page, signal } from "@madojs/mado";
109
+
110
+ component(
111
+ "x-counter",
112
+ () => {
113
+ const count = signal(0);
114
+ return () => html`
115
+ <button @click=${() => count.update((n) => n + 1)}>${count}</button>
116
+ `;
117
+ },
118
+ { styles: css`button { padding: .5rem 1rem; }` },
119
+ );
120
+
121
+ export default page({
122
+ title: "Counter",
123
+ view: () => html`<x-counter></x-counter>`,
124
+ });
125
+ ```
126
+
127
+ ```ts
128
+ // src/routes.ts
129
+ import { routes } from "@madojs/mado";
130
+
131
+ export const manifest = {
132
+ "/": () => import("./pages/counter.js"),
133
+ "*": () => import("./pages/not-found.js"),
134
+ };
135
+
136
+ export default routes(manifest);
137
+ ```
138
+
139
+ The developer convenience CLI is available as `mado`:
140
+
141
+ ```bash
142
+ mado init my-app
143
+ mado init my-app --starter crud
144
+ mado build
145
+ mado typecheck
146
+ mado test
147
+ mado serve basic
148
+ mado dev showcase
149
+ mado examples
150
+ ```
151
+
152
+ The CLI is useful before v1, but its command polish may still change.
153
+
154
+ ## Documentation
155
+
156
+ - [Language index](./docs/README.md)
157
+ - [English docs](./docs/en/README.md)
158
+ - [Russian docs](./docs/ru/README.md)
159
+ - [Documentation française](./docs/fr/README.md)
160
+ - [Ukrainian docs](./docs/uk/README.md)
161
+
162
+ Core topics:
163
+
164
+ - [The Mado way](./docs/en/00-the-mado-way.md)
165
+ - [Routing](./docs/en/01-routing.md)
166
+ - [Project layout](./docs/en/02-project-layout.md)
167
+ - [Static bake & SEO](./docs/en/03-static-bake.md)
168
+ - [IDE setup](./docs/en/04-ide-setup.md)
169
+ - [Why Mado](./docs/en/05-why-mado.md)
170
+ - [For backenders](./docs/en/06-for-backenders.md)
171
+ - [LLM pitfalls](./docs/en/07-llm-pitfalls.md)
172
+ - [Shadow DOM vs Light DOM](./docs/en/09-shadow-vs-light-dom.md)
173
+
174
+ AI-agent entrypoints:
175
+
176
+ - [AGENTS.md](./AGENTS.md)
177
+ - [llms.txt](./llms.txt)
178
+
179
+ ## Examples
180
+
181
+ - [`examples/basic`](./examples/basic/) — minimal API tour.
182
+ - [`examples/tickets`](./examples/tickets/) — LLM zero-history CRUD validation.
183
+ - [`examples/showcase`](./examples/showcase/) — flagship SaaS CRM pressure app.
184
+ - [`examples/cloudflare`](./examples/cloudflare/) — edge prerender / deployment PoC.
185
+
186
+ ## Core API
187
+
188
+ ### Signals
189
+
190
+ ```ts
191
+ import { batch, computed, effect, flushSync, signal } from "@madojs/mado";
192
+
193
+ const count = signal(0);
194
+ const doubled = computed(() => count() * 2);
195
+
196
+ effect(() => console.log(count()));
197
+
198
+ batch(() => {
199
+ count.set(1);
200
+ count.set(2);
201
+ });
202
+
203
+ flushSync();
204
+ ```
205
+
206
+ Signals are getter functions: read with `count()`, write with `count.set(next)`
207
+ or `count.update(fn)`.
208
+
209
+ ### Templates
210
+
211
+ ```ts
212
+ html`<button @click=${fn} ?disabled=${loading} class=${className}>${label}</button>`;
213
+ ```
214
+
215
+ - Child bindings accept text, nodes, arrays, nested `html```, and `each(...)`.
216
+ - `attr=${v}` writes an attribute.
217
+ - `@event=${fn}` attaches an event listener.
218
+ - `.prop=${v}` writes a DOM property.
219
+ - `?attr=${flag}` toggles a boolean attribute.
220
+ - Function values, including signals and computed values, are tracked
221
+ reactively.
222
+
223
+ ### Components
224
+
225
+ ```ts
226
+ import { component, css, html } from "@madojs/mado";
227
+
228
+ component(
229
+ "x-card",
230
+ () => () => html`<section><slot></slot></section>`,
231
+ {
232
+ styles: css`
233
+ :host { display: block; }
234
+ section { padding: 1rem; border: 1px solid var(--border); }
235
+ `,
236
+ },
237
+ );
238
+ ```
239
+
240
+ Components are Custom Elements. Shadow DOM is enabled by default. Use
241
+ `{ shadow: false }` for app shells and admin layouts that should inherit global
242
+ utility classes.
243
+
244
+ ### Lists
245
+
246
+ ```ts
247
+ import { each } from "@madojs/mado";
248
+
249
+ html`
250
+ <ul>
251
+ ${() => each(items(), (item) => item.id, (item) => html`<li>${item.name}</li>`)}
252
+ </ul>
253
+ `;
254
+ ```
255
+
256
+ `each()` performs keyed reconciliation and reuses DOM nodes across reorder,
257
+ insert and delete operations.
258
+
259
+ ### Routing
260
+
261
+ ```ts
262
+ import { routes } from "@madojs/mado";
263
+
264
+ export const manifest = {
265
+ "/": () => import("./pages/home.js"),
266
+ "/users/:id": () => import("./pages/user-detail.js"),
267
+ "*": () => import("./pages/not-found.js"),
268
+ };
269
+
270
+ export default routes(manifest);
271
+ ```
272
+
273
+ The router provides lazy page loading, nested routes, query params, hover
274
+ prefetch, async stale guards, loading delay, View Transitions support and
275
+ `dispose()` for tests/dev overlays.
276
+
277
+ ### Data
278
+
279
+ ```ts
280
+ import { invalidate, jsonFetcher, mutation, resource } from "@madojs/mado";
281
+
282
+ const user = resource(
283
+ () => `/api/users/${userId()}`,
284
+ jsonFetcher<User>(),
285
+ { staleTime: 60_000 },
286
+ );
287
+
288
+ const save = mutation(api.saveUser, {
289
+ invalidates: ["/api/users*"],
290
+ });
291
+
292
+ await save.run(values);
293
+ invalidate("/api/users*");
294
+ ```
295
+
296
+ `resource()` handles cache, loading/error state, aborts, refresh and optimistic
297
+ local `mutate()`. Inside `component()` setup it is lifecycle-aware.
298
+
299
+ ### Forms
300
+
301
+ ```ts
302
+ import { html, useForm } from "@madojs/mado";
303
+
304
+ const form = useForm({
305
+ email: { required: true, type: "email" },
306
+ age: { type: "number", min: 18 },
307
+ });
308
+
309
+ html`
310
+ <form @submit=${form.onSubmit(async (values) => api.save(values))}>
311
+ <input name="email" @input=${form.onInput} @blur=${form.onBlur}>
312
+ <button ?disabled=${() => !form.isValid() || form.submitting()}>Save</button>
313
+ </form>
314
+ `;
315
+ ```
316
+
317
+ Validation is based on native HTML constraint validation plus optional custom
318
+ `validate(values)`.
319
+
320
+ ## Static HTML Without Hydration
321
+
322
+ Mado intentionally does not ship SSR with hydration. For SEO-oriented pages it
323
+ offers `bake`: build-time prerender of finite routes into HTML with meta tags,
324
+ JSON-LD and baked data. For very large or changing catalogs, see the
325
+ Cloudflare Worker edge-prerender PoC in [`examples/cloudflare`](./examples/cloudflare/).
326
+
327
+ ## Production Bundle
328
+
329
+ Native ESM is the default development and demo path. When a bundled production
330
+ artifact is useful, run:
331
+
332
+ ```bash
333
+ npm run bundle
334
+ npm run preview
335
+ ```
336
+
337
+ `bundle` uses optional `esbuild` for code splitting, SRI, `.gz` and `.br`
338
+ outputs. `preview` emulates the provided `nginx.conf` with `node:http`.
339
+
340
+ ## Tests
341
+
342
+ ```bash
343
+ npm run typecheck
344
+ npm run build
345
+ npm test
346
+ npm run test:browser
347
+ ```
348
+
349
+ The test suite covers signals, computed values, effects, dynamic dependencies,
350
+ the html parser, keyed reconciliation, resources, mutations, forms, router
351
+ isolation, component lifecycle and example smoke tests.
352
+
353
+ ## Known Limits
354
+
355
+ | Limit | Meaning |
356
+ |---|---|
357
+ | No SSR hydration | Use `bake` or edge prerender for SEO. Per-user server rendering is intentionally out of scope for now. |
358
+ | Small ecosystem | No plugin marketplace or UI-kit ecosystem comparable to React. |
359
+ | Template IDE support needs plugins | `html`` syntax highlighting/type tooling usually needs lit-plugin or similar tooling. |
360
+ | Evergreen browsers | Targets modern Chrome/Edge/Firefox/Safari. Legacy browsers are out of scope. |
361
+ | Pre-v1 API | Public API is intentionally small, but v0.x may still change before v1. |
362
+
363
+ ## Contributing
364
+
365
+ Read [CONTRIBUTING.md](./CONTRIBUTING.md). The short version: bug fixes with
366
+ tests, docs improvements, examples and carefully discussed small core changes
367
+ are welcome. Runtime dependencies are not.
368
+
369
+ ## License
370
+
371
+ MIT.