@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,54 @@
|
|
|
1
|
+
# Шлях Mado
|
|
2
|
+
|
|
3
|
+
> Один зрозумілий шлях. Жорсткі контракти. Мінімум магії.
|
|
4
|
+
|
|
5
|
+
Mado — це не лише набір API, а набір домовленостей. Якщо їх дотримуватись,
|
|
6
|
+
проєкт залишається читабельним навіть тоді, коли в ньому десятки сторінок і
|
|
7
|
+
кілька розробників.
|
|
8
|
+
|
|
9
|
+
## Принципи
|
|
10
|
+
|
|
11
|
+
1. **Один спосіб.** Для типової задачі має бути один канонічний шлях, а не п’ять
|
|
12
|
+
рівноправних стилів.
|
|
13
|
+
2. **Явність замість магії.** Немає сканерів файлів, прихованих глобалів і
|
|
14
|
+
неочевидних side effects.
|
|
15
|
+
3. **Платформа спочатку.** Якщо браузер уже має потрібну можливість, Mado дає
|
|
16
|
+
тонку обгортку, а не переписує платформу.
|
|
17
|
+
4. **Суворий TypeScript.** `tsc --strict` — базовий контракт.
|
|
18
|
+
5. **Нуль runtime-залежностей.** Кожна залежність — це довгострокове
|
|
19
|
+
зобов’язання.
|
|
20
|
+
|
|
21
|
+
## Базова структура
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
src/
|
|
25
|
+
├── routes.ts
|
|
26
|
+
├── main.ts
|
|
27
|
+
├── pages/
|
|
28
|
+
├── components/
|
|
29
|
+
├── layouts/
|
|
30
|
+
├── lib/
|
|
31
|
+
└── styles/
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Кожна сторінка живе в окремому файлі та експортує `page({...})`.
|
|
35
|
+
Компоненти реєструються через `component()`. Дані читаються через `resource()`,
|
|
36
|
+
зміни виконуються через `mutation()`, списки рендеряться через `each()`.
|
|
37
|
+
|
|
38
|
+
## Імена компонентів
|
|
39
|
+
|
|
40
|
+
Єдине правило браузера: ім’я custom element має містити дефіс. `x-*` у прикладах
|
|
41
|
+
Mado — це демо-конвенція, а не вимога фреймворку. У production краще брати
|
|
42
|
+
префікс домену: `app-*`, `crm-*`, `ticket-*`, `admin-*`.
|
|
43
|
+
|
|
44
|
+
## Чого не робимо
|
|
45
|
+
|
|
46
|
+
- Не пишемо JSX або Vue-шаблони.
|
|
47
|
+
- Не читаємо сигнал через `.value`; сигнал читається як функція: `count()`.
|
|
48
|
+
- Не використовуємо `disabled=${...}` для булевих атрибутів; треба
|
|
49
|
+
`?disabled=${...}`.
|
|
50
|
+
- Не рендеримо динамічні списки через `.map()` там, де потрібне збереження DOM
|
|
51
|
+
стану; використовуємо `each()`.
|
|
52
|
+
- Не додаємо runtime-залежності без дуже вагомої причини.
|
|
53
|
+
|
|
54
|
+
Коли є сумнів, краще додати рецепт у документацію, ніж новий primitive в core.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Маршрутизація
|
|
2
|
+
|
|
3
|
+
Mado використовує явний route manifest: один файл показує весь URL-граф
|
|
4
|
+
застосунку.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { routes } from "@madojs/mado";
|
|
8
|
+
|
|
9
|
+
export const manifest = {
|
|
10
|
+
"/": () => import("./pages/home.js"),
|
|
11
|
+
"/users/:id": () => import("./pages/user-detail.js"),
|
|
12
|
+
"*": () => import("./pages/not-found.js"),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default routes(manifest);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Сторінка
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { page, html } from "@madojs/mado";
|
|
22
|
+
|
|
23
|
+
export default page<{ id: string }>({
|
|
24
|
+
title: ({ id }) => `User ${id}`,
|
|
25
|
+
view: ({ params }) => html`<x-user data-id=${params.id}></x-user>`,
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Сторінка може мати `title`, `head`, `load`, `view`, `errorView` і `bake`.
|
|
30
|
+
Маршрут може бути lazy import або готовим `page({...})`.
|
|
31
|
+
|
|
32
|
+
## Nested routes
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { nested, routes } from "@madojs/mado";
|
|
36
|
+
|
|
37
|
+
export const manifest = {
|
|
38
|
+
"/": () => import("./pages/home.js"),
|
|
39
|
+
"/app": nested({
|
|
40
|
+
layout: () => import("./layouts/app-layout.js"),
|
|
41
|
+
routes: {
|
|
42
|
+
"/dashboard": () => import("./pages/dashboard.js"),
|
|
43
|
+
"/settings": () => import("./pages/settings.js"),
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default routes(manifest);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Layout отримує дочірній view і може рендерити shell: nav, sidebar, toolbar,
|
|
52
|
+
notifications.
|
|
53
|
+
|
|
54
|
+
## Навігація
|
|
55
|
+
|
|
56
|
+
Посилання з `data-link` перехоплюються роутером:
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<a href="/users/42" data-link>Open</a>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Програмна навігація:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { navigate } from "@madojs/mado";
|
|
66
|
+
|
|
67
|
+
navigate("/users/42");
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Router підтримує hover-prefetch, stale async guard, scroll-to-top для нової
|
|
71
|
+
навігації та `dispose()` для тестів/dev overlay.
|
|
72
|
+
|
|
73
|
+
## Query params
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { queryParam } from "@madojs/mado";
|
|
77
|
+
|
|
78
|
+
const search = queryParam("q", "");
|
|
79
|
+
search.set("mado");
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`queryParam()` повертає signal-like API і синхронізує стан із URL.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Структура проєкту
|
|
2
|
+
|
|
3
|
+
Рекомендована структура Mado-застосунку:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
src/
|
|
7
|
+
├── main.ts
|
|
8
|
+
├── routes.ts
|
|
9
|
+
├── pages/
|
|
10
|
+
├── components/
|
|
11
|
+
├── layouts/
|
|
12
|
+
├── lib/
|
|
13
|
+
└── styles/
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## `main.ts`
|
|
17
|
+
|
|
18
|
+
Точка входу: встановлює глобальні стилі, провайдери контексту та монтує кореневий
|
|
19
|
+
компонент у `#app`.
|
|
20
|
+
|
|
21
|
+
## `routes.ts`
|
|
22
|
+
|
|
23
|
+
Єдиний manifest маршрутів. Немає file-system routing, груп у назвах папок або
|
|
24
|
+
прихованих conventions.
|
|
25
|
+
|
|
26
|
+
## `pages/`
|
|
27
|
+
|
|
28
|
+
Одна сторінка — один файл — `export default page({...})`.
|
|
29
|
+
|
|
30
|
+
## `components/`
|
|
31
|
+
|
|
32
|
+
Повторно використовувані Web Components. Імпорт файлу реєструє компонент як side
|
|
33
|
+
effect.
|
|
34
|
+
|
|
35
|
+
## `layouts/`
|
|
36
|
+
|
|
37
|
+
Shell для nested routes: admin layout, marketing layout, authenticated area.
|
|
38
|
+
|
|
39
|
+
## `lib/`
|
|
40
|
+
|
|
41
|
+
API-клієнти, contexts, чиста бізнес-логіка без UI.
|
|
42
|
+
|
|
43
|
+
## `styles/`
|
|
44
|
+
|
|
45
|
+
Глобальні tokens або shared CSS через `css```. Для app-shell часто зручно
|
|
46
|
+
використовувати Light DOM, для leaf-компонентів — Shadow DOM.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Static Bake & SEO
|
|
2
|
+
|
|
3
|
+
Mado навмисно не робить SSR із hydration. Для SEO-сторінок є `bake`: build-time
|
|
4
|
+
prerender у статичний HTML із meta-тегами, JSON-LD і baked data.
|
|
5
|
+
|
|
6
|
+
## Коли підходить
|
|
7
|
+
|
|
8
|
+
- Блог, документація, landing pages.
|
|
9
|
+
- Невеликі каталоги з кінцевим набором сторінок.
|
|
10
|
+
- Сторінки, де crawlers мають одразу побачити контент.
|
|
11
|
+
- Контент не персоналізований для конкретного користувача.
|
|
12
|
+
|
|
13
|
+
## Коли не підходить
|
|
14
|
+
|
|
15
|
+
- Авторизовані dashboards.
|
|
16
|
+
- Персоналізований HTML.
|
|
17
|
+
- Real-time дані.
|
|
18
|
+
- Каталоги, які потребують справжнього server rendering на кожен запит.
|
|
19
|
+
|
|
20
|
+
## Сторінка з bake
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { page, html } from "@madojs/mado";
|
|
24
|
+
|
|
25
|
+
export default page<{ slug: string }, Product>({
|
|
26
|
+
title: ({ slug }) => `Product ${slug}`,
|
|
27
|
+
head: ({ slug }, data) => ({
|
|
28
|
+
description: data?.description ?? `Product ${slug}`,
|
|
29
|
+
}),
|
|
30
|
+
bake: {
|
|
31
|
+
paths: () => products.map((p) => ({ slug: p.slug })),
|
|
32
|
+
data: ({ slug }) => findProduct(slug),
|
|
33
|
+
revalidate: 3600,
|
|
34
|
+
},
|
|
35
|
+
view: ({ params }) => html`<x-product data-slug=${params.slug}></x-product>`,
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`bake.paths()` повертає всі params, `bake.data()` повертає дані для конкретної
|
|
40
|
+
сторінки, `head()` формує SEO-метадані.
|
|
41
|
+
|
|
42
|
+
## Edge prerender
|
|
43
|
+
|
|
44
|
+
Для великих наборів сторінок можна робити той самий підхід на edge: Cloudflare
|
|
45
|
+
Worker генерує HTML на cache miss, кладе в KV і віддає з TTL. Приклад лежить в
|
|
46
|
+
`examples/cloudflare`.
|
|
47
|
+
|
|
48
|
+
Це не hydration. Клієнтський Mado-застосунок все одно стартує нормально і
|
|
49
|
+
перерендерює сторінку після завантаження.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Налаштування IDE
|
|
2
|
+
|
|
3
|
+
Mado використовує tagged templates `html`` і `css``. Для TypeScript це звичайні
|
|
4
|
+
рядки, тому підсвітка HTML/CSS залежить від IDE.
|
|
5
|
+
|
|
6
|
+
## VS Code
|
|
7
|
+
|
|
8
|
+
Рекомендовано встановити `lit-plugin` або інше розширення, яке розуміє
|
|
9
|
+
`html``/`css`` tagged templates.
|
|
10
|
+
|
|
11
|
+
## WebStorm
|
|
12
|
+
|
|
13
|
+
WebStorm зазвичай розуміє tagged templates без додаткового налаштування.
|
|
14
|
+
|
|
15
|
+
## Neovim / Helix
|
|
16
|
+
|
|
17
|
+
Можна використовувати LSP/плагіни для lit-html або inline HTML у template
|
|
18
|
+
strings.
|
|
19
|
+
|
|
20
|
+
## Що IDE не перевірить
|
|
21
|
+
|
|
22
|
+
- Чи правильно ти обгорнув `count()` у `() => count()`.
|
|
23
|
+
- Чи варто використовувати `?disabled` замість `disabled`.
|
|
24
|
+
- Чи є `.js` у browser ESM imports.
|
|
25
|
+
|
|
26
|
+
Для цього потрібні правила з `AGENTS.md`, тести та уважний review.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Чому Mado
|
|
2
|
+
|
|
3
|
+
Mado не намагається “вбити React” або довести, що всі інші помиляються. Це
|
|
4
|
+
інструмент для людей, які хочуть маленький, читабельний frontend runtime поверх
|
|
5
|
+
нативного браузера.
|
|
6
|
+
|
|
7
|
+
## Lit
|
|
8
|
+
|
|
9
|
+
Lit чудовий для дизайн-систем і компонентів, які мають жити всередині будь-якого
|
|
10
|
+
framework. Mado краще підходить, коли ти пишеш увесь застосунок і хочеш router,
|
|
11
|
+
data, forms, context і static bake в одному маленькому пакеті.
|
|
12
|
+
|
|
13
|
+
## Solid
|
|
14
|
+
|
|
15
|
+
Solid швидший і зріліший. Якщо тобі комфортно з JSX transform, Vite і mature
|
|
16
|
+
ecosystem — Solid може бути кращим вибором. Mado обирає інше: browser ESM,
|
|
17
|
+
`tsc`, tagged templates і код, який можна прочитати.
|
|
18
|
+
|
|
19
|
+
## htmx
|
|
20
|
+
|
|
21
|
+
htmx сильний, коли backend володіє HTML fragments. Mado потрібен тоді, коли все
|
|
22
|
+
ж хочеться SPA: локальний UI state, optimistic updates, query params, cached
|
|
23
|
+
resources, persisted state і lazy modules.
|
|
24
|
+
|
|
25
|
+
## React / Vue / Svelte
|
|
26
|
+
|
|
27
|
+
Вони мають більші ecosystem, більше відповідей у Google/LLM і більше людей на
|
|
28
|
+
ринку. Mado виграє не масштабом ecosystem, а ownership: маленький runtime,
|
|
29
|
+
нуль runtime-залежностей і зрозумілі правила.
|
|
30
|
+
|
|
31
|
+
## Головна теза
|
|
32
|
+
|
|
33
|
+
Mado — не найшвидший у synthetic benchmarks і не найбагатший ecosystem. Його
|
|
34
|
+
сенс у тому, що frontend знову можна тримати в голові.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Mado для бекендерів
|
|
2
|
+
|
|
3
|
+
Якщо ти пишеш Go, Rust, .NET, Java, Python або Node backend, Mado можна уявляти
|
|
4
|
+
як маленький HTTP-сервер у браузері.
|
|
5
|
+
|
|
6
|
+
| Backend mental model | Mado |
|
|
7
|
+
|---|---|
|
|
8
|
+
| router | `routes()` |
|
|
9
|
+
| handler | `page().view` |
|
|
10
|
+
| middleware/layout | `nested()` + layout |
|
|
11
|
+
| cache get-or-set | `resource()` |
|
|
12
|
+
| POST/PUT/DELETE | `mutation()` |
|
|
13
|
+
| cache invalidation | `invalidates` / `invalidate()` |
|
|
14
|
+
| dependency injection | `createContext/provide/inject` |
|
|
15
|
+
|
|
16
|
+
## CRUD
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
const users = resource(
|
|
20
|
+
() => "/api/users",
|
|
21
|
+
jsonFetcher<User[]>(),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const save = mutation(api.saveUser, {
|
|
25
|
+
invalidates: ["/api/users*"],
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Форми
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const form = useForm({
|
|
33
|
+
email: { required: true, type: "email" },
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Mado спирається на Constraint Validation API браузера, а не на окрему validation
|
|
38
|
+
екосистему.
|
|
39
|
+
|
|
40
|
+
## Auth
|
|
41
|
+
|
|
42
|
+
Auth зазвичай живе в `lib/auth.ts` як signal/context. Protected area зручно
|
|
43
|
+
робити через nested layout, який перевіряє session і показує dashboard або
|
|
44
|
+
redirect/login.
|
|
45
|
+
|
|
46
|
+
## Правило
|
|
47
|
+
|
|
48
|
+
Не тягни backend-архітектуру в frontend дослівно. Тримай UI state локальним,
|
|
49
|
+
data fetching через `resource()`, mutation через `mutation()`, а shared services
|
|
50
|
+
через context.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Типові помилки LLM у Mado-коді
|
|
2
|
+
|
|
3
|
+
Цей файл потрібен, щоб AI-агенти не писали React/Vue-код у Mado-проєкті.
|
|
4
|
+
|
|
5
|
+
## JSX
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
// Ні
|
|
9
|
+
const view = <button>{count}</button>;
|
|
10
|
+
|
|
11
|
+
// Так
|
|
12
|
+
const view = html`<button>${count}</button>`;
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## `useState` / `useEffect`
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// Ні
|
|
19
|
+
const [count, setCount] = useState(0);
|
|
20
|
+
|
|
21
|
+
// Так
|
|
22
|
+
const count = signal(0);
|
|
23
|
+
count.set(1);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Signal `.value`
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// Ні
|
|
30
|
+
count.value
|
|
31
|
+
|
|
32
|
+
// Так
|
|
33
|
+
count()
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Нереактивний child binding
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// Нереактивно: count() прочитано один раз
|
|
40
|
+
html`<div>${count() * 2}</div>`;
|
|
41
|
+
|
|
42
|
+
// Реактивно
|
|
43
|
+
html`<div>${() => count() * 2}</div>`;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Boolean attributes
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// Ні
|
|
50
|
+
html`<button disabled=${loading}>Save</button>`;
|
|
51
|
+
|
|
52
|
+
// Так
|
|
53
|
+
html`<button ?disabled=${loading}>Save</button>`;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Списки
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// Не для динамічного reorder
|
|
60
|
+
items().map((item) => html`<li>${item.name}</li>`);
|
|
61
|
+
|
|
62
|
+
// Так
|
|
63
|
+
each(items(), (item) => item.id, (item) => html`<li>${item.name}</li>`);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Imports
|
|
67
|
+
|
|
68
|
+
У browser ESM локальні imports мають мати `.js`:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import "./components/x-card.js";
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## `resource()` lifecycle
|
|
75
|
+
|
|
76
|
+
`resource()` створюй у component setup або всередині явного lifecycle, щоб cleanup
|
|
77
|
+
відбувався автоматично.
|
|
78
|
+
|
|
79
|
+
## Component names
|
|
80
|
+
|
|
81
|
+
Custom element name має містити дефіс. `x-*` — демо-конвенція; production може
|
|
82
|
+
мати `app-*`, `crm-*`, `ticket-*`, `admin-*`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# LLM Zero-History Test
|
|
2
|
+
|
|
3
|
+
Мета тесту: перевірити, чи може LLM без попередньої історії написати ідіоматичний
|
|
4
|
+
Mado CRUD, не перетворюючи його на React у tagged templates.
|
|
5
|
+
|
|
6
|
+
## Дозволений контекст
|
|
7
|
+
|
|
8
|
+
- `AGENTS.md`
|
|
9
|
+
- `README.md`
|
|
10
|
+
- `docs/uk/07-llm-pitfalls.md` або відповідна англійська версія
|
|
11
|
+
- `examples/basic/README.md`
|
|
12
|
+
- конкретні файли прикладів тільки за потреби
|
|
13
|
+
|
|
14
|
+
## Що перевіряти
|
|
15
|
+
|
|
16
|
+
- Немає JSX, `useState`, `useEffect`.
|
|
17
|
+
- Немає signal `.value`.
|
|
18
|
+
- Reactive child bindings з `count()` обгорнуті у `() =>`.
|
|
19
|
+
- Boolean attributes пишуться як `?disabled`, `?checked`.
|
|
20
|
+
- Динамічні списки використовують `each()`.
|
|
21
|
+
- Imports мають `.js`.
|
|
22
|
+
- `resource()` створюється в lifecycle-aware контексті.
|
|
23
|
+
|
|
24
|
+
## Артефакт
|
|
25
|
+
|
|
26
|
+
`examples/tickets` — маленький ticket-admin SPA з routes, mock API, forms,
|
|
27
|
+
resources, mutations, invalidation, `queryParam`, `computed`, `signal` і
|
|
28
|
+
keyed lists.
|
|
29
|
+
|
|
30
|
+
Критерій успіху: код виглядає як Mado, а не як React/Vue, переодягнений у
|
|
31
|
+
template strings.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Shadow DOM vs Light DOM
|
|
2
|
+
|
|
3
|
+
Mado використовує Shadow DOM за замовчуванням, але це не означає, що його треба
|
|
4
|
+
вмикати всюди.
|
|
5
|
+
|
|
6
|
+
## Shadow DOM добре підходить для
|
|
7
|
+
|
|
8
|
+
- leaf-компонентів;
|
|
9
|
+
- isolated widgets;
|
|
10
|
+
- badges, cards, modals, buttons;
|
|
11
|
+
- компонентів, які мають не ламатися від зовнішнього CSS.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
component("x-badge", () => () => html`<span><slot></slot></span>`, {
|
|
15
|
+
styles: css`:host { display: inline-block; }`,
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Light DOM краще для
|
|
20
|
+
|
|
21
|
+
- app shell;
|
|
22
|
+
- admin layouts;
|
|
23
|
+
- route-level pages;
|
|
24
|
+
- компонентів, які мають користуватися глобальними utility classes;
|
|
25
|
+
- великих CRUD surfaces, де CSS має бути простим і передбачуваним.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
component("x-app-layout", () => () => html`<main><slot></slot></main>`, {
|
|
29
|
+
shadow: false,
|
|
30
|
+
styles: css`x-app-layout main { display: grid; }`,
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Практичне правило
|
|
35
|
+
|
|
36
|
+
Якщо компонент — самостійний reusable visual, бери Shadow DOM. Якщо це частина
|
|
37
|
+
app layout або page composition, часто краще Light DOM.
|
|
38
|
+
|
|
39
|
+
Посилання з `data-link` працюють і в Shadow DOM, бо router використовує
|
|
40
|
+
`event.composedPath()`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Документація Mado — Українська
|
|
2
|
+
|
|
3
|
+
Український комплект документації.
|
|
4
|
+
|
|
5
|
+
| Розділ | Файл |
|
|
6
|
+
|---|---|
|
|
7
|
+
| Шлях Mado | [00-the-mado-way.md](./00-the-mado-way.md) |
|
|
8
|
+
| Маршрутизація | [01-routing.md](./01-routing.md) |
|
|
9
|
+
| Структура проєкту | [02-project-layout.md](./02-project-layout.md) |
|
|
10
|
+
| Static bake & SEO | [03-static-bake.md](./03-static-bake.md) |
|
|
11
|
+
| Налаштування IDE | [04-ide-setup.md](./04-ide-setup.md) |
|
|
12
|
+
| Чому Mado | [05-why-mado.md](./05-why-mado.md) |
|
|
13
|
+
| Для бекендерів | [06-for-backenders.md](./06-for-backenders.md) |
|
|
14
|
+
| Типові помилки LLM | [07-llm-pitfalls.md](./07-llm-pitfalls.md) |
|
|
15
|
+
| LLM zero-history тест | [08-llm-zero-history-test.md](./08-llm-zero-history-test.md) |
|
|
16
|
+
| Shadow DOM vs Light DOM | [09-shadow-vs-light-dom.md](./09-shadow-vs-light-dom.md) |
|
package/llms.txt
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Mado
|
|
2
|
+
|
|
3
|
+
> Small native-web SPA framework on the platform (Web Components + signals + tagged-template `html`).
|
|
4
|
+
> No build step (only `tsc`), no runtime dependencies, readable in an evening.
|
|
5
|
+
|
|
6
|
+
Mado is a narrowly-focused frontend framework that deliberately avoids React patterns (no JSX, no hooks, no VDOM, no Vite). Target audience: backend developers and senior frontend engineers who are tired of the infrastructure complexity of React/Next.
|
|
7
|
+
|
|
8
|
+
## Key things an AI assistant needs to know
|
|
9
|
+
|
|
10
|
+
- **This is NOT React, NOT Lit, NOT Solid.** When generating code for Mado, **do not use JSX, `useState`, `useEffect`, classes, decorators, `requestUpdate`**. That will be an error.
|
|
11
|
+
- **All templates are tagged template `html\`...\``** (not JSX). Allowed bindings inside: `${value}`, `attr=${v}`, `@evt=${fn}`, `.prop=${v}`, `?attr=${flag}`.
|
|
12
|
+
- **Reactivity via signals.** `signal()`, `computed()`, `effect()`. A signal is a getter function (`count()`), not an object field (`count.value`).
|
|
13
|
+
- **Components are Web Components.** Registered via `component('x-name', setupFn, options)`. Names must include a hyphen (`x-foo`, `my-button`).
|
|
14
|
+
- **Cleanup via `ctx.onDispose(fn)`** in setup, not via return from effect.
|
|
15
|
+
|
|
16
|
+
## Critical template rules
|
|
17
|
+
|
|
18
|
+
1. **A reactive value in a template = getter function.**
|
|
19
|
+
```ts
|
|
20
|
+
// ❌ NOT REACTIVE: read once at render time
|
|
21
|
+
html`<div>${count() * 2}</div>`
|
|
22
|
+
// ✅ REACTIVE: subscription
|
|
23
|
+
html`<div>${() => count() * 2}</div>`
|
|
24
|
+
```
|
|
25
|
+
The signal itself can also be inserted directly — Mado will recognise it as a function:
|
|
26
|
+
```ts
|
|
27
|
+
html`<div>${count}</div>` // ✅ ok, count is a function
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. **DOM properties vs HTML attributes:**
|
|
31
|
+
- `attr=${v}` → setAttribute (strings/numbers only);
|
|
32
|
+
- `.prop=${v}` → DOM property (objects, arrays, numbers without serialisation);
|
|
33
|
+
- `?attr=${flag}` → boolean attribute (toggle attribute);
|
|
34
|
+
- `@evt=${fn}` → addEventListener.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
html`<input ?disabled=${isLoading} .value=${user.name} @input=${onInput}>`
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
3. **Lists — must use `each(items, keyFn, renderFn)`**, not `.map()` without a key:
|
|
41
|
+
```ts
|
|
42
|
+
import { each } from "@madojs/mado";
|
|
43
|
+
html`<ul>${() => each(users(), u => u.id, u => html`<li>${u.name}</li>`)}</ul>`
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Canonical imports
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// Everything you normally need — from one place:
|
|
50
|
+
import {
|
|
51
|
+
signal, computed, effect, batch,
|
|
52
|
+
html, render,
|
|
53
|
+
each,
|
|
54
|
+
component,
|
|
55
|
+
css, cssVars,
|
|
56
|
+
routes, router, navigate, queryParam, prefetchPath,
|
|
57
|
+
page, nested,
|
|
58
|
+
resource, mutation, invalidate, jsonFetcher,
|
|
59
|
+
useForm,
|
|
60
|
+
createContext, provide, inject,
|
|
61
|
+
persisted,
|
|
62
|
+
lazy,
|
|
63
|
+
} from "@madojs/mado";
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Canonical "Hello world"
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// src/main.ts
|
|
70
|
+
import { html, render } from "@madojs/mado";
|
|
71
|
+
import routesApi from "./routes.js";
|
|
72
|
+
render(html`${routesApi.view}`, document.getElementById("app")!);
|
|
73
|
+
|
|
74
|
+
// src/routes.ts
|
|
75
|
+
import { routes } from "@madojs/mado";
|
|
76
|
+
export default routes({
|
|
77
|
+
"/": () => import("./pages/home.js"),
|
|
78
|
+
"*": () => import("./pages/not-found.js"),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// src/pages/home.ts
|
|
82
|
+
import { page, component, html, css, signal } from "@madojs/mado";
|
|
83
|
+
|
|
84
|
+
component("x-counter", () => {
|
|
85
|
+
const count = signal(0);
|
|
86
|
+
return () => html`
|
|
87
|
+
<button @click=${() => count.update(n => n + 1)}>${count}</button>
|
|
88
|
+
`;
|
|
89
|
+
}, { styles: css`button { padding: .5rem 1rem; }` });
|
|
90
|
+
|
|
91
|
+
export default page({
|
|
92
|
+
title: "Home",
|
|
93
|
+
view: () => html`<x-counter></x-counter>`,
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Canonical CRUD pattern (for backend developers)
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { page, html, resource, mutation, each, useForm, navigate } from "@madojs/mado";
|
|
101
|
+
import { api } from "../lib/api.js";
|
|
102
|
+
|
|
103
|
+
const users = resource(() => "/api/users", () => api.list(), { staleTime: 30_000 });
|
|
104
|
+
const create = mutation((u) => api.create(u), { invalidates: ["/api/users*"] });
|
|
105
|
+
|
|
106
|
+
export default page({
|
|
107
|
+
view: () => {
|
|
108
|
+
const f = useForm({ name: { required: true } });
|
|
109
|
+
return html`
|
|
110
|
+
<form @submit=${f.onSubmit(async v => { await create.run(v); navigate("/"); })}>
|
|
111
|
+
<input name="name" @input=${f.onInput}>
|
|
112
|
+
<button ?disabled=${() => !f.isValid()}>Create</button>
|
|
113
|
+
</form>
|
|
114
|
+
${() => each(users.data() ?? [], u => u.id, u => html`<li>${u.name}</li>`)}
|
|
115
|
+
`;
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Documentation
|
|
121
|
+
|
|
122
|
+
- README: https://github.com/madojs/mado#readme — full description + cookbook
|
|
123
|
+
- docs/en/00-the-mado-way.md — philosophy and applicability boundaries
|
|
124
|
+
- docs/en/01-routing.md — detailed router documentation
|
|
125
|
+
- docs/en/02-project-layout.md — recommended project structure
|
|
126
|
+
- docs/en/03-static-bake.md — SEO without SSR (static build / edge prerender)
|
|
127
|
+
- docs/en/04-ide-setup.md — VS Code / WebStorm configuration
|
|
128
|
+
- docs/en/05-why-mado.md — honest comparison with Lit / Solid / Svelte / htmx / Alpine / React
|
|
129
|
+
- docs/en/06-for-backenders.md — mental model in 10 minutes for Go/Rust/.NET/Java developers
|
|
130
|
+
- docs/en/07-llm-pitfalls.md — common mistakes when generating Mado code
|
|
131
|
+
- examples/basic/ — minimal API tour
|
|
132
|
+
- examples/tickets/ — LLM zero-history CRUD validation
|
|
133
|
+
- examples/showcase/ — flagship CRM pressure app (auth, nested routes, forms, mutations)
|
|
134
|
+
- examples/cloudflare/ — Edge prerender PoC on Cloudflare Workers
|
|
135
|
+
|
|
136
|
+
## What Mado does NOT do (intentionally)
|
|
137
|
+
|
|
138
|
+
- ❌ JSX → tagged templates instead
|
|
139
|
+
- ❌ Virtual DOM → fine-grained signal updates
|
|
140
|
+
- ❌ SSR with hydration → `bake` (static build) or edge-prerender for SEO
|
|
141
|
+
- ❌ Hooks and rules of hooks → signals
|
|
142
|
+
- ❌ Mandatory Webpack/Vite → only `tsc`
|
|
143
|
+
- ❌ React-Router / TanStack → built-in 500-line `routes()`
|
|
144
|
+
- ❌ React-Query / SWR → built-in `resource()`
|
|
145
|
+
- ❌ Formik / RHF → built-in `useForm()` (HTML5 validation)
|
|
146
|
+
- ❌ Redux / Zustand → `signal()` + `createContext`
|
|
147
|
+
- ❌ Decorators → plain functions
|
|
148
|
+
|
|
149
|
+
## Version
|
|
150
|
+
|
|
151
|
+
`0.5.0` — early, API may change before 1.0. Semver is not guaranteed on minor versions before 1.0.
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT.
|