@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
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.
|