@madojs/mado 0.11.1 → 0.13.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 +116 -32
- package/CHANGELOG.md +216 -1
- package/README.md +158 -173
- package/dist/src/component.d.ts +6 -3
- package/dist/src/component.d.ts.map +1 -0
- package/dist/src/component.js +163 -36
- package/dist/src/component.js.map +1 -1
- package/dist/src/context.d.ts +13 -32
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +73 -48
- package/dist/src/context.js.map +1 -1
- package/dist/src/css.d.ts +1 -0
- package/dist/src/css.d.ts.map +1 -0
- package/dist/src/css.js +34 -5
- package/dist/src/css.js.map +1 -1
- package/dist/src/devtools-hook.d.ts +14 -0
- package/dist/src/devtools-hook.d.ts.map +1 -0
- package/dist/src/devtools-hook.js +27 -0
- package/dist/src/devtools-hook.js.map +1 -0
- package/dist/src/devtools.d.ts +24 -22
- package/dist/src/devtools.d.ts.map +1 -0
- package/dist/src/devtools.js +230 -58
- package/dist/src/devtools.js.map +1 -1
- package/dist/src/diagnostics.d.ts +12 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +68 -8
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/each.d.ts +1 -5
- package/dist/src/each.d.ts.map +1 -0
- package/dist/src/each.js +0 -8
- package/dist/src/each.js.map +1 -1
- package/dist/src/forms.d.ts +27 -96
- package/dist/src/forms.d.ts.map +1 -0
- package/dist/src/forms.js +184 -394
- package/dist/src/forms.js.map +1 -1
- package/dist/src/head.d.ts +7 -6
- package/dist/src/head.d.ts.map +1 -0
- package/dist/src/head.js +8 -7
- package/dist/src/head.js.map +1 -1
- package/dist/src/html/bindings.d.ts +1 -0
- package/dist/src/html/bindings.d.ts.map +1 -0
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html/parser.d.ts +4 -5
- package/dist/src/html/parser.d.ts.map +1 -0
- package/dist/src/html/parser.js +64 -15
- package/dist/src/html/parser.js.map +1 -1
- package/dist/src/html/template-types.d.ts +1 -0
- package/dist/src/html/template-types.d.ts.map +1 -0
- package/dist/src/html/template-types.js.map +1 -1
- package/dist/src/html/template.d.ts +5 -1
- package/dist/src/html/template.d.ts.map +1 -0
- package/dist/src/html/template.js +102 -13
- package/dist/src/html/template.js.map +1 -1
- package/dist/src/index.d.ts +15 -12
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +18 -10
- package/dist/src/index.js.map +1 -1
- package/dist/src/json.d.ts +3 -0
- package/dist/src/json.d.ts.map +1 -0
- package/dist/src/json.js +14 -0
- package/dist/src/json.js.map +1 -0
- package/dist/src/lifecycle.d.ts +1 -0
- package/dist/src/lifecycle.d.ts.map +1 -0
- package/dist/src/lifecycle.js +3 -2
- package/dist/src/lifecycle.js.map +1 -1
- package/dist/src/page.d.ts +63 -28
- package/dist/src/page.d.ts.map +1 -0
- package/dist/src/page.js +5 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/persisted.d.ts +6 -1
- package/dist/src/persisted.d.ts.map +1 -0
- package/dist/src/persisted.js +17 -11
- package/dist/src/persisted.js.map +1 -1
- package/dist/src/resource.d.ts +2 -1
- package/dist/src/resource.d.ts.map +1 -0
- package/dist/src/resource.js +128 -50
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/base.d.ts +66 -0
- package/dist/src/router/base.d.ts.map +1 -0
- package/dist/src/router/base.js +168 -0
- package/dist/src/router/base.js.map +1 -0
- package/dist/src/router/manifest.d.ts +1 -0
- package/dist/src/router/manifest.d.ts.map +1 -0
- package/dist/src/router/manifest.js +156 -171
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +17 -2
- package/dist/src/router/match.d.ts.map +1 -0
- package/dist/src/router/match.js +60 -6
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.d.ts +1 -0
- package/dist/src/router/navigation.d.ts.map +1 -0
- package/dist/src/router/navigation.js +58 -9
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/signal.d.ts +1 -0
- package/dist/src/signal.d.ts.map +1 -0
- package/dist/src/signal.js +46 -17
- package/dist/src/signal.js.map +1 -1
- package/dist/src/static-runtime.d.ts +82 -0
- package/dist/src/static-runtime.d.ts.map +1 -0
- package/dist/src/static-runtime.js +209 -0
- package/dist/src/static-runtime.js.map +1 -0
- package/dist/src/vite/index.d.ts +32 -3
- package/dist/src/vite/index.d.ts.map +1 -0
- package/dist/src/vite/index.js +49 -17
- package/dist/src/vite/index.js.map +1 -1
- package/docs/README.md +5 -8
- package/docs/architecture/adr/0001-browser-static-snapshots.md +132 -0
- package/docs/architecture/v1-roadmap.md +19 -0
- package/docs/en/00-the-mado-way.md +1 -1
- package/docs/en/01-quickstart.md +183 -0
- package/docs/en/10-pages-and-components.md +279 -0
- package/docs/en/11-templates-and-signals.md +211 -0
- package/docs/en/12-routing.md +229 -0
- package/docs/en/13-data.md +227 -0
- package/docs/en/14-forms.md +91 -0
- package/docs/en/15-static-snapshots.md +181 -0
- package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +56 -4
- package/docs/en/{13-deployment.md → 20-deployment.md} +79 -17
- package/docs/en/{15-error-handling.md → 21-error-handling.md} +9 -6
- package/docs/en/{14-testing.md → 22-testing.md} +6 -5
- package/docs/en/23-cookbook.md +292 -0
- package/docs/en/24-devtools-and-diagnostics.md +45 -0
- package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +17 -9
- package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +5 -5
- package/docs/en/33-migration-0.12-0.13.md +56 -0
- package/docs/en/40-llm-guide.md +774 -0
- package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +28 -25
- package/docs/en/{05-why-mado.md → 42-why-mado.md} +3 -3
- package/docs/en/README.md +99 -27
- package/docs/recipes/nginx/Containerfile +26 -0
- package/docs/recipes/nginx/nginx.conf +42 -0
- package/llms.txt +263 -210
- package/package.json +51 -23
- package/scripts/clean.mjs +8 -0
- package/scripts/cli/generate.mjs +57 -10
- package/scripts/cli/help.mjs +13 -5
- package/scripts/cli/index.mjs +31 -7
- package/scripts/cli/init.mjs +19 -19
- package/scripts/cli/release.mjs +38 -83
- package/scripts/cli/run.mjs +5 -3
- package/scripts/logger.mjs +117 -0
- package/scripts/output-guard.mjs +128 -0
- package/scripts/preview.mjs +105 -24
- package/scripts/static/browser.mjs +695 -0
- package/scripts/static/discover.mjs +284 -0
- package/scripts/static/output.mjs +141 -0
- package/scripts/static/serialize.mjs +212 -0
- package/scripts/static/server.mjs +167 -0
- package/scripts/static.mjs +205 -0
- package/starters/default/README.md +57 -55
- package/starters/default/package.json +6 -12
- package/starters/default/src/app.routes.ts +20 -33
- package/starters/default/src/components/feature-card.component.ts +40 -0
- package/starters/default/src/components/live-counter.component.ts +57 -0
- package/starters/default/src/content/guides.ts +55 -0
- package/starters/default/src/main.ts +9 -13
- package/starters/default/src/pages/app.page.ts +41 -0
- package/starters/default/src/pages/guide.page.ts +40 -0
- package/starters/default/src/pages/home.page.ts +49 -0
- package/starters/default/src/pages/not-found.page.ts +18 -0
- package/starters/default/src/styles/document.css +39 -0
- package/starters/default/src/styles/reset.css +37 -0
- package/starters/default/src/styles/tokens.css +30 -0
- package/starters/default/src/styles.d.ts +3 -1
- package/starters/default/src/vite-env.d.ts +1 -1
- package/starters/default/tsconfig.node.json +13 -0
- package/starters/default/vite.config.ts +14 -4
- package/starters/modular/README.md +142 -0
- package/starters/modular/index.html +13 -0
- package/starters/modular/package.json +30 -0
- package/starters/modular/src/app.routes.ts +39 -0
- package/starters/{default → modular}/src/layouts/app-shell.layout.ts +9 -7
- package/starters/{default → modular}/src/layouts/auth-shell.layout.ts +3 -3
- package/starters/modular/src/main.ts +16 -0
- package/starters/{default → modular}/src/modules/auth/login.page.ts +5 -12
- package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +3 -4
- package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +4 -4
- package/starters/{default → modular}/src/modules/home/home.page.ts +5 -7
- package/starters/modular/src/modules/home/not-found.page.ts +14 -0
- package/starters/{default → modular}/src/shared/styles/reset.css +21 -1
- package/starters/modular/src/styles.d.ts +1 -0
- package/starters/modular/src/vite-env.d.ts +1 -0
- package/starters/modular/tsconfig.json +33 -0
- package/starters/modular/tsconfig.node.json +13 -0
- package/starters/modular/vite.config.ts +132 -0
- package/TODO.md +0 -79
- package/dist/src/lazy.d.ts +0 -38
- package/dist/src/lazy.js +0 -73
- package/dist/src/lazy.js.map +0 -1
- package/docs/en/01-routing.md +0 -152
- package/docs/en/02-project-layout.md +0 -124
- package/docs/en/03-static-bake.md +0 -249
- package/docs/en/04-ide-setup.md +0 -162
- package/docs/en/07-llm-pitfalls.md +0 -724
- package/docs/en/08-llm-zero-history-test.md +0 -56
- package/docs/en/09-shadow-vs-light-dom.md +0 -174
- package/docs/en/11-layouts.md +0 -113
- package/docs/en/12-auth-and-api.md +0 -124
- package/docs/en/16-bake-cookbook.md +0 -100
- package/docs/en/17-shadow-dom-forms.md +0 -192
- package/docs/fr/00-the-mado-way.md +0 -85
- package/docs/fr/01-routing.md +0 -120
- package/docs/fr/02-project-layout.md +0 -84
- package/docs/fr/03-static-bake.md +0 -289
- package/docs/fr/04-ide-setup.md +0 -162
- package/docs/fr/05-why-mado.md +0 -193
- package/docs/fr/06-for-backenders.md +0 -438
- package/docs/fr/07-llm-pitfalls.md +0 -625
- package/docs/fr/08-llm-zero-history-test.md +0 -38
- package/docs/fr/09-shadow-vs-light-dom.md +0 -65
- package/docs/fr/10-app-architecture.md +0 -138
- package/docs/fr/11-layouts.md +0 -47
- package/docs/fr/12-auth-and-api.md +0 -76
- package/docs/fr/13-deployment.md +0 -57
- package/docs/fr/14-testing.md +0 -41
- package/docs/fr/15-error-handling.md +0 -50
- package/docs/fr/16-bake-cookbook.md +0 -88
- package/docs/fr/17-shadow-dom-forms.md +0 -196
- package/docs/fr/18-api-freeze-map.md +0 -63
- package/docs/fr/19-reactivity-ordering.md +0 -97
- package/docs/fr/20-v1-stability.md +0 -88
- package/docs/fr/README.md +0 -27
- package/docs/ru/00-the-mado-way.md +0 -84
- package/docs/ru/01-routing.md +0 -119
- package/docs/ru/02-project-layout.md +0 -84
- package/docs/ru/03-static-bake.md +0 -250
- package/docs/ru/04-ide-setup.md +0 -144
- package/docs/ru/05-why-mado.md +0 -193
- package/docs/ru/06-for-backenders.md +0 -428
- package/docs/ru/07-llm-pitfalls.md +0 -624
- package/docs/ru/08-llm-zero-history-test.md +0 -57
- package/docs/ru/09-shadow-vs-light-dom.md +0 -63
- package/docs/ru/10-app-architecture.md +0 -152
- package/docs/ru/11-layouts.md +0 -47
- package/docs/ru/12-auth-and-api.md +0 -75
- package/docs/ru/13-deployment.md +0 -66
- package/docs/ru/14-testing.md +0 -50
- package/docs/ru/15-error-handling.md +0 -56
- package/docs/ru/16-bake-cookbook.md +0 -95
- package/docs/ru/17-shadow-dom-forms.md +0 -193
- package/docs/ru/18-api-freeze-map.md +0 -64
- package/docs/ru/19-reactivity-ordering.md +0 -95
- package/docs/ru/20-v1-stability.md +0 -82
- package/docs/ru/README.md +0 -25
- package/docs/uk/00-the-mado-way.md +0 -82
- package/docs/uk/01-routing.md +0 -76
- package/docs/uk/02-project-layout.md +0 -73
- package/docs/uk/03-static-bake.md +0 -48
- package/docs/uk/04-ide-setup.md +0 -26
- package/docs/uk/05-why-mado.md +0 -34
- package/docs/uk/06-for-backenders.md +0 -55
- package/docs/uk/07-llm-pitfalls.md +0 -145
- package/docs/uk/08-llm-zero-history-test.md +0 -34
- package/docs/uk/09-shadow-vs-light-dom.md +0 -58
- package/docs/uk/10-app-architecture.md +0 -97
- package/docs/uk/11-layouts.md +0 -47
- package/docs/uk/12-auth-and-api.md +0 -70
- package/docs/uk/13-deployment.md +0 -40
- package/docs/uk/14-testing.md +0 -34
- package/docs/uk/15-error-handling.md +0 -32
- package/docs/uk/16-bake-cookbook.md +0 -36
- package/docs/uk/17-shadow-dom-forms.md +0 -193
- package/docs/uk/18-api-freeze-map.md +0 -61
- package/docs/uk/19-reactivity-ordering.md +0 -95
- package/docs/uk/20-v1-stability.md +0 -83
- package/docs/uk/README.md +0 -27
- package/scripts/bake.mjs +0 -570
- package/scripts/package-smoke.mjs +0 -77
- package/scripts/release-notes.mjs +0 -66
- package/scripts/size-budget.mjs +0 -91
- package/starters/default/src/modules/home/not-found.page.ts +0 -11
- package/starters/default/src/shared/ui/x-button.component.ts +0 -49
- /package/docs/en/{19-reactivity-ordering.md → 31-reactivity-ordering.md} +0 -0
- /package/starters/{default → modular}/.editorconfig +0 -0
- /package/starters/{default → modular}/eslint.config.mjs +0 -0
- /package/starters/{default → modular}/public/favicon.svg +0 -0
- /package/starters/{default → modular}/src/modules/auth/_contracts/auth-api.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.connector.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.guard.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.public.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.routes.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.service.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/_contracts/stripe.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/api/stripe.connector.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.public.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.routes.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/components/invoice-status-badge.component.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/data/invoices.resource.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/http-client.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/http-error.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/interceptors.ts +0 -0
- /package/starters/{default → modular}/src/shared/lib/format-date.ts +0 -0
- /package/starters/{default → modular}/src/shared/styles/content.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/shell.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
- /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
package/docs/ru/05-why-mado.md
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
# Why Mado (and why not Lit / Solid / Alpine / htmx)
|
|
2
|
-
|
|
3
|
-
> Если ты выбираешь фронт-стек для нового проекта, эта страница для тебя.
|
|
4
|
-
> Если у тебя уже что-то работает — **не мигрируй ради миграции**, это всегда дороже, чем кажется.
|
|
5
|
-
|
|
6
|
-
Mado — не «убийца» React/Vue/Svelte. Это узкоспециализированный инструмент. Здесь я честно объясняю, **в каких случаях Mado действительно лучше альтернатив**, а в каких — нет.
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## TL;DR — одной таблицей
|
|
11
|
-
|
|
12
|
-
| Если вам важно… | Берите |
|
|
13
|
-
|---|---|
|
|
14
|
-
| Лучшая обучающая инфраструктура / огромная экосистема | **React** или **Vue** |
|
|
15
|
-
| Дизайн-система компонентов для встраивания в любые фреймворки | **Lit** |
|
|
16
|
-
| Топовый перфоманс на больших списках, "близкий к ванилле" с JSX | **Solid** или **Svelte 5** |
|
|
17
|
-
| Прогрессивное улучшение классических серверных приложений | **htmx** + ваш бек |
|
|
18
|
-
| "Украшение" статического сайта реактивностью | **Alpine.js** |
|
|
19
|
-
| Минимум инструмента, максимум платформы, всё в одной коробке (роутер + data + формы + SEO), читается за вечер | **Mado** ✓ |
|
|
20
|
-
|
|
21
|
-
Если ваш кейс не попадает в последний пункт — Mado скорее всего не лучший выбор. Это нормально.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Mado vs Lit
|
|
26
|
-
|
|
27
|
-
**Lit** — самая близкая по духу альтернатива. Тот же подход: Web Components + tagged templates + минимум магии.
|
|
28
|
-
|
|
29
|
-
| | Lit | Mado |
|
|
30
|
-
|---|---|---|
|
|
31
|
-
| Размер | ~6 КБ | ~16 КБ |
|
|
32
|
-
| Возраст / поддержка | ~10 лет, Google | 6 месяцев, один автор |
|
|
33
|
-
| Реактивность | декораторы `@property` + ручной `requestUpdate` | сигналы (`signal`/`computed`/`effect`) из коробки |
|
|
34
|
-
| Роутер | нет, нужно искать (`@lit-labs/router`, etc) | в комплекте: `routes()` + layout groups + prefetch |
|
|
35
|
-
| Data fetching | нет, нужно собирать | `resource()` + `mutation()` + glob-инвалидация |
|
|
36
|
-
| Формы | нет | `useForm()` с HTML-like constraints |
|
|
37
|
-
| SEO / static | сложно (`@lit-labs/ssr`) | `bake` (linkedom) + edge-prerender |
|
|
38
|
-
| Билд | нужны framework-specific build plugins | Vite transport + native runtime |
|
|
39
|
-
| Стиль кода | классы + декораторы | функции + tagged templates |
|
|
40
|
-
| Экосистема | реальная (Shoelace, Material Web, и т.д.) | нет |
|
|
41
|
-
| Когда выбрать | пишете дизайн-систему / Web-Components-библиотеку для встраивания | пишете приложение целиком, хотите всё в одной коробке |
|
|
42
|
-
|
|
43
|
-
**Честный pitch:** *«Lit — лучше, если ты пишешь дизайн-систему компонентов. Mado — лучше, если ты пишешь приложение и хочешь батарейки в коробке без сборки 8 пакетов.»*
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Mado vs Solid
|
|
48
|
-
|
|
49
|
-
**Solid** — топовая реактивная либа на сигналах. Технически очень крутая.
|
|
50
|
-
|
|
51
|
-
| | Solid | Mado |
|
|
52
|
-
|---|---|---|
|
|
53
|
-
| Размер | ~7 КБ | ~16 КБ |
|
|
54
|
-
| Перфоманс | топ-3 на js-framework-benchmark | хорошо, но не топ |
|
|
55
|
-
| Реактивность | сигналы (тот же класс идей) | сигналы |
|
|
56
|
-
| Шаблоны | JSX (компилируется в реактивные expressions) | tagged template `html\`\`` |
|
|
57
|
-
| Компонентная модель | функции, виртуальные узлы Solid | Web Components |
|
|
58
|
-
| Билд | Vite + babel-plugin-solid обязательны | Vite для dev/build, без runtime-зависимостей |
|
|
59
|
-
| Роутер | `@solidjs/router` | в комплекте |
|
|
60
|
-
| Data | `createResource` | `resource()` |
|
|
61
|
-
| SSR | поддерживается серьёзно (SolidStart) | намеренно нет |
|
|
62
|
-
| Экосистема | растёт, ~50 пакетов | нет |
|
|
63
|
-
| Когда выбрать | нужен топовый перф + JSX + готов настраивать билд | нужен browser-native runtime с простым tooling |
|
|
64
|
-
|
|
65
|
-
**Честный pitch:** *«Solid технически быстрее и зрелее. Mado проще концептуально: browser-native runtime, Web Components, tagged templates, а Vite забирает на себя скучный dev/build. Если нужны JSX и большая экосистема — берите Solid.»*
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## Mado vs Svelte 5
|
|
70
|
-
|
|
71
|
-
**Svelte 5** с runes — тоже сигнальная модель, тоже минималистичная.
|
|
72
|
-
|
|
73
|
-
| | Svelte 5 | Mado |
|
|
74
|
-
|---|---|---|
|
|
75
|
-
| Размер runtime | ~3 КБ | ~16 КБ |
|
|
76
|
-
| Компилятор | обязателен (.svelte → JS) | нет |
|
|
77
|
-
| Синтаксис | свой .svelte формат | TS + tagged templates |
|
|
78
|
-
| Реактивность | `$state`/`$derived` (runes) | `signal`/`computed` |
|
|
79
|
-
| SSR / SvelteKit | полноценная, mature | намеренно нет |
|
|
80
|
-
| Экосистема | большая, dev-tools отличные | нет |
|
|
81
|
-
| Когда выбрать | новый продакшен проект с командой | приватный/внутренний инструмент, нужна простота |
|
|
82
|
-
|
|
83
|
-
**Честный pitch:** *«Svelte — продуктовый выбор. Mado — инженерный. Если у вас команда и продакшен — Svelte. Если вы один и хотите контроль — Mado.»*
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## Mado vs htmx
|
|
88
|
-
|
|
89
|
-
**htmx** — другая школа: HTML-fragments over the wire.
|
|
90
|
-
|
|
91
|
-
| | htmx | Mado |
|
|
92
|
-
|---|---|---|
|
|
93
|
-
| Архитектура | HTML с сервера, обновление через fragments | SPA: JS грузит данные, рендерит сам |
|
|
94
|
-
| Зависит от бека | сильно (бек должен уметь отдавать HTML) | слабо (бек — JSON API) |
|
|
95
|
-
| State на клиенте | минимальный (cookies, localStorage) | полноценный (signal, persisted) |
|
|
96
|
-
| Optimistic updates | сложно | легко (mutation + invalidates) |
|
|
97
|
-
| Offline / PWA | плохо | нормально |
|
|
98
|
-
| Размер | ~14 КБ | ~16 КБ |
|
|
99
|
-
| Когда выбрать | классическое серверное приложение (Rails, Django, Phoenix), нужно «оживить» | SPA-опыт обязателен, бек — REST/GraphQL |
|
|
100
|
-
|
|
101
|
-
**Честный pitch:** *«htmx — если бек крепкий и умеет HTML. Mado — если бек отдаёт JSON, а вам нужен полноценный SPA-опыт.»*
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Mado vs Alpine.js
|
|
106
|
-
|
|
107
|
-
**Alpine** — реактивные атрибуты прямо в HTML.
|
|
108
|
-
|
|
109
|
-
| | Alpine | Mado |
|
|
110
|
-
|---|---|---|
|
|
111
|
-
| Назначение | украшение статического HTML | полноценное SPA |
|
|
112
|
-
| Размер | ~7 КБ | ~16 КБ |
|
|
113
|
-
| State management | `x-data` локально | сигналы + context + persisted |
|
|
114
|
-
| Роутинг | нет | в комплекте |
|
|
115
|
-
| TypeScript | плохо | first-class |
|
|
116
|
-
| Когда выбрать | статика, лендинги, нужны 5 интерактивных кнопок | full app: страницы, навигация, формы, data |
|
|
117
|
-
|
|
118
|
-
**Честный pitch:** *«Alpine — для интерактива на статике. Mado — для полноценного приложения.»*
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## Mado vs React + ecosystem
|
|
123
|
-
|
|
124
|
-
Я не буду долго на этом останавливаться, потому что React — это **другая весовая категория** по экосистеме и зрелости. Но если вы серьёзно сравниваете:
|
|
125
|
-
|
|
126
|
-
**React выигрывает:**
|
|
127
|
-
- огромная экосистема: тысячи UI-китов, тысячи статей, бесконечные туториалы;
|
|
128
|
-
- AI-помощники (ChatGPT, Copilot) знают React лучше всех;
|
|
129
|
-
- лучший job market;
|
|
130
|
-
- лучшая поддержка SSR (Next.js).
|
|
131
|
-
|
|
132
|
-
**Mado выигрывает:**
|
|
133
|
-
- размер бандла в десятки раз меньше;
|
|
134
|
-
- нулевая инфраструктура (нет Vite, нет Babel, нет 200 пакетов);
|
|
135
|
-
- читается за вечер — если что-то сломалось, открой `src/`;
|
|
136
|
-
- сигналы вместо хуков (нет правил «нельзя в if», нет stale-closure ловушек);
|
|
137
|
-
- не нужно мигрировать с major на major.
|
|
138
|
-
|
|
139
|
-
**Когда выбрать Mado вместо React:**
|
|
140
|
-
- проект на 1-3 человек, на годы вперёд;
|
|
141
|
-
- размер бандла критичен;
|
|
142
|
-
- вы устали от React-усталости и готовы пожертвовать экосистемой ради простоты.
|
|
143
|
-
|
|
144
|
-
**Когда выбрать React:**
|
|
145
|
-
- команда от 5 человек;
|
|
146
|
-
- нужны UI-киты, нужна экосистема;
|
|
147
|
-
- проект, который будет нанимать новых людей с рынка;
|
|
148
|
-
- нужен SSR с гидрацией (Next.js).
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
## Сильнейший аргумент Mado
|
|
153
|
-
|
|
154
|
-
Не размер, не перф, не сигналы — у всего этого есть конкуренты лучше.
|
|
155
|
-
|
|
156
|
-
> **«Открой исходник и прочитай его за вечер. ~3500 строк, маленькие модули. Если что-то сломалось — ты не идёшь в issue на 3000 комментариев. Ты идёшь в `src/router/` и читаешь код.»**
|
|
157
|
-
|
|
158
|
-
Это называется **ownership** — ты владеешь кодом, а не зависишь от чужого.
|
|
159
|
-
|
|
160
|
-
Для бекендеров, которые привыкли к маленьким понятным библиотекам (chi в Go, axum в Rust, FastAPI в Python), это **родное ощущение**. Для тех, кому это не важно — берите что больше и зрелее.
|
|
161
|
-
|
|
162
|
-
---
|
|
163
|
-
|
|
164
|
-
## Что насчёт перфоманса?
|
|
165
|
-
|
|
166
|
-
Честно: **Mado не самый быстрый**. Топ-3 на js-framework-benchmark — это Solid, Inferno, Svelte. Mado ближе к Lit / Preact по характеристикам.
|
|
167
|
-
|
|
168
|
-
Что Mado делает для перфа из коробки:
|
|
169
|
-
- **lazy `computed`** (dirty-flag, не eager);
|
|
170
|
-
- **batch microtask scheduler** для `signal.set`;
|
|
171
|
-
- **keyed reconciliation** в `each()` с реальным DOM-reuse;
|
|
172
|
-
- **sync-rendering** для кешированных страниц в роутере;
|
|
173
|
-
- **hover-prefetch** lazy-chunk'ов;
|
|
174
|
-
- **View Transitions API** для плавных переходов;
|
|
175
|
-
- **shared `adoptedStyleSheets`** для CSS;
|
|
176
|
-
- **`modulepreload` hints** на dev-сервере.
|
|
177
|
-
|
|
178
|
-
Этого достаточно для большинства приложений. Если вы строите Excel в браузере или 60fps WebGL-визуализацию — это не сюда (это в Solid или нативный JS).
|
|
179
|
-
|
|
180
|
-
---
|
|
181
|
-
|
|
182
|
-
## Резюме
|
|
183
|
-
|
|
184
|
-
Mado — это **узкий** инструмент с честным позиционированием. Он сильнее всего там, где:
|
|
185
|
-
|
|
186
|
-
1. Вы хотите **владеть** кодом и читать его целиком.
|
|
187
|
-
2. Вам критична **простота инфраструктуры** (никакого Vite/Webpack/Babel).
|
|
188
|
-
3. Вам нужны **батарейки в одной коробке** (роутер + data + формы + SEO).
|
|
189
|
-
4. Вы не junior и не боитесь Web Components.
|
|
190
|
-
|
|
191
|
-
Если хоть один пункт не про вас — берите альтернативу из таблицы выше. Не миритесь с инструментом, который не подходит.
|
|
192
|
-
|
|
193
|
-
— Автор Mado, бывший React-разработчик, перешедший на бекенд и теперь склеивающий фронт в свободное время.
|
|
@@ -1,428 +0,0 @@
|
|
|
1
|
-
# Mado для бекендеров
|
|
2
|
-
|
|
3
|
-
> Ты пишешь на Go / Rust / .NET / Java / Python и тебе нужно запилить веб-морду.
|
|
4
|
-
> Эта страница — mental model Mado за 10 минут, на твоём языке.
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Главная аналогия
|
|
9
|
-
|
|
10
|
-
Mado устроен **как HTTP-сервер**. Серьёзно:
|
|
11
|
-
|
|
12
|
-
| Серверный мир | Mado |
|
|
13
|
-
|---|---|
|
|
14
|
-
| HTTP-роутер (chi, axum, mux) | `routes()` — манифест путей |
|
|
15
|
-
| Handler `func(req, resp)` | `page({ view: (ctx) => html\`...\` })` |
|
|
16
|
-
| Middleware | route group через `layout()` (оборачивает handler) |
|
|
17
|
-
| Шаблонизатор (Jinja, Handlebars) | `html\`\`` tagged template |
|
|
18
|
-
| HTTP-клиент с кешем | `resource()` — fetch + cache + invalidation |
|
|
19
|
-
| Reactive variable / atom | `signal()` — реактивный геттер |
|
|
20
|
-
| Background goroutine / task | `effect()` — авто-перезапускается при изменении сигнала |
|
|
21
|
-
| `defer cleanup()` | `ctx.onDispose(fn)` в setup компонента |
|
|
22
|
-
| ENV-переменные | `createContext()` + `provide()`/`inject()` |
|
|
23
|
-
|
|
24
|
-
Если ты понимаешь HTTP-сервер, ты понимаешь Mado.
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Файловая структура — как у обычного приложения
|
|
29
|
-
|
|
30
|
-
```
|
|
31
|
-
src/
|
|
32
|
-
├── routes.ts ← манифест путей (как router.go в chi)
|
|
33
|
-
├── main.ts ← entry point (как main.go: настройка + run)
|
|
34
|
-
├── pages/ ← по одному файлу на страницу (как handler.go)
|
|
35
|
-
├── components/ ← переиспользуемые UI (как helpers/)
|
|
36
|
-
├── layouts/ ← обёртки для групп страниц (как middleware/)
|
|
37
|
-
└── lib/ ← бизнес-логика, API-клиент (как service/, repo/)
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Один файл = одна страница. Никакой file-based magic routing — всё руками в `routes.ts`.
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Hello World — серверная аналогия
|
|
45
|
-
|
|
46
|
-
### Go (chi) — для сравнения
|
|
47
|
-
|
|
48
|
-
```go
|
|
49
|
-
r := chi.NewRouter()
|
|
50
|
-
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
|
51
|
-
w.Write([]byte("<h1>Hello</h1>"))
|
|
52
|
-
})
|
|
53
|
-
r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
|
|
54
|
-
id := chi.URLParam(r, "id")
|
|
55
|
-
fmt.Fprintf(w, "<h1>User %s</h1>", id)
|
|
56
|
-
})
|
|
57
|
-
http.ListenAndServe(":8080", r)
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Mado — то же самое
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
// src/routes.ts
|
|
64
|
-
import { routes } from "@madojs/mado";
|
|
65
|
-
|
|
66
|
-
export default routes({
|
|
67
|
-
"/": () => import("./pages/home.js"),
|
|
68
|
-
"/users/:id": () => import("./pages/user.js"),
|
|
69
|
-
});
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
// src/pages/home.ts
|
|
74
|
-
import { page, html } from "@madojs/mado";
|
|
75
|
-
export default page({
|
|
76
|
-
view: () => html`<h1>Hello</h1>`,
|
|
77
|
-
});
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
```ts
|
|
81
|
-
// src/pages/user.ts
|
|
82
|
-
import { page, html } from "@madojs/mado";
|
|
83
|
-
export default page<{ id: string }>({
|
|
84
|
-
view: ({ params }) => html`<h1>User ${params.id}</h1>`,
|
|
85
|
-
});
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Параметры пути попадают в `params` — так же, как в `chi.URLParam`.
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## Сигналы — это reactive variable
|
|
93
|
-
|
|
94
|
-
Если ты писал на Erlang/Elixir с `Agent`, или на Rust с `Arc<Mutex<T>>`, или просто хранил state в struct и обновлял его — `signal` это то же самое, плюс **авто-перерисовка** компонентов, которые этот state читают.
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
import { signal, effect } from "@madojs/mado";
|
|
98
|
-
|
|
99
|
-
// "переменная" с подпиской
|
|
100
|
-
const count = signal(0);
|
|
101
|
-
|
|
102
|
-
// читать
|
|
103
|
-
console.log(count()); // 0
|
|
104
|
-
|
|
105
|
-
// писать
|
|
106
|
-
count.set(5);
|
|
107
|
-
|
|
108
|
-
// "горутина", которая запускается на каждое изменение
|
|
109
|
-
effect(() => {
|
|
110
|
-
console.log("count is now", count());
|
|
111
|
-
});
|
|
112
|
-
// → выведет "count is now 5"
|
|
113
|
-
|
|
114
|
-
count.set(10);
|
|
115
|
-
// → выведет "count is now 10"
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Никаких правил вида «нельзя в условии». Сигнал — это просто функция-геттер. Где её прочитали — туда и подписались.
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## `resource()` — HTTP-клиент с кешем (как `cache.GetOrSet`)
|
|
123
|
-
|
|
124
|
-
Это **самая полезная абстракция для бекендера**. Это как Redis с автоматической инвалидацией, только в браузере.
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
import { resource, mutation, jsonFetcher, invalidate } from "@madojs/mado";
|
|
128
|
-
|
|
129
|
-
// "репозиторий пользователя"
|
|
130
|
-
const userId = signal(1);
|
|
131
|
-
|
|
132
|
-
const user = resource(
|
|
133
|
-
() => `/api/users/${userId()}`, // ключ кеша (реактивный!)
|
|
134
|
-
jsonFetcher<User>(), // как загружать
|
|
135
|
-
{ staleTime: 60_000 }, // 60 секунд кеш
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
// в компоненте:
|
|
139
|
-
user.data(); // User | undefined
|
|
140
|
-
user.error(); // Error | null
|
|
141
|
-
user.loading(); // boolean
|
|
142
|
-
|
|
143
|
-
// мутация (как POST/PUT)
|
|
144
|
-
const save = mutation<User, User>(
|
|
145
|
-
(u) => fetch("/api/users", { method: "POST", body: JSON.stringify(u) }).then(r => r.json()),
|
|
146
|
-
{ invalidates: ["/api/users*"] }, // glob-инвалидация — как `cache.Drop("users:*")`
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
await save.run(newUser);
|
|
150
|
-
// автоматически: user.data() обновится, если совпал glob
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
Ключи `resource()` — это identity кеша. Включайте endpoint, query params и
|
|
154
|
-
форму данных в ключ: два живых `resource()` с одинаковым ключом разделяют кеш
|
|
155
|
-
и in-flight request. Если один и тот же ключ используется с другим fetcher,
|
|
156
|
-
Mado предупреждает, потому что обычно это значит, что ключ кеша слишком
|
|
157
|
-
широкий.
|
|
158
|
-
|
|
159
|
-
Если бы такая абстракция была в Go-мире для серверных кешей — мы бы все плакали от счастья.
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## Компоненты = handler с собственной памятью
|
|
164
|
-
|
|
165
|
-
Компонент — это **handler**, который рендерит свой кусок UI. У него есть:
|
|
166
|
-
|
|
167
|
-
- параметры (attributes/properties);
|
|
168
|
-
- внутренний state (`signal`'ы);
|
|
169
|
-
- lifecycle: `connectedCallback` (как Init), `disconnectedCallback` (как Close).
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
import { component, html, signal } from "@madojs/mado";
|
|
173
|
-
|
|
174
|
-
component("x-counter", () => {
|
|
175
|
-
const count = signal(0);
|
|
176
|
-
|
|
177
|
-
return () => html`
|
|
178
|
-
<button @click=${() => count.update(n => n + 1)}>
|
|
179
|
-
Clicks: ${count}
|
|
180
|
-
</button>
|
|
181
|
-
`;
|
|
182
|
-
});
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
Использование:
|
|
186
|
-
|
|
187
|
-
```ts
|
|
188
|
-
html`<x-counter></x-counter>`
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
Регистрируем тег `<x-counter>` в браузере — он становится «функцией», которую можно вставлять в HTML. Это **нативный** механизм браузера (Web Components), Mado только склеивает его с сигналами.
|
|
192
|
-
|
|
193
|
-
---
|
|
194
|
-
|
|
195
|
-
## Формы — как `form.Validate()` на бекенде
|
|
196
|
-
|
|
197
|
-
Mado использует **schema-based validation в духе HTML constraints**, плюс добавляет state-tracking.
|
|
198
|
-
|
|
199
|
-
```ts
|
|
200
|
-
import { useForm } from "@madojs/mado";
|
|
201
|
-
|
|
202
|
-
const f = useForm({
|
|
203
|
-
email: { required: true, type: "email" },
|
|
204
|
-
age: { required: true, type: "number", min: 18 },
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// в шаблоне:
|
|
208
|
-
html`
|
|
209
|
-
<form @submit=${f.onSubmit(async (v) => {
|
|
210
|
-
await api.save(v);
|
|
211
|
-
f.reset();
|
|
212
|
-
})}>
|
|
213
|
-
<input name="email" .value=${() => f.values().email ?? ""}
|
|
214
|
-
@input=${f.onInput} @blur=${f.onBlur} />
|
|
215
|
-
|
|
216
|
-
${() => f.errors().email && f.touched().email
|
|
217
|
-
? html`<small>${f.errors().email}</small>`
|
|
218
|
-
: null}
|
|
219
|
-
|
|
220
|
-
<button ?disabled=${() => !f.isValid() || f.submitting()}>Save</button>
|
|
221
|
-
</form>
|
|
222
|
-
`;
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
Кастомная валидация — `validate: (values) => errors | null`. Никаких Yup-схем и зависимостей.
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
|
|
229
|
-
## Контекст = DI / dependency injection
|
|
230
|
-
|
|
231
|
-
Как в Go ты передаёшь `context.Context` через стек вызовов — так в Mado контекст пробрасывается через DOM-дерево.
|
|
232
|
-
|
|
233
|
-
```ts
|
|
234
|
-
import { createContext, provide, inject } from "@madojs/mado";
|
|
235
|
-
|
|
236
|
-
// объявляем "тип" зависимости
|
|
237
|
-
const ApiCtx = createContext<ApiClient>(defaultApiClient);
|
|
238
|
-
|
|
239
|
-
// в корневом компоненте — предоставляем
|
|
240
|
-
component("x-app", ({ host }) => {
|
|
241
|
-
provide(host, ApiCtx, new ApiClient("https://api.example.com"));
|
|
242
|
-
return () => html`<x-page/>`;
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
// в любом дочернем — забираем
|
|
246
|
-
component("x-page", ({ host }) => {
|
|
247
|
-
const api = inject(host, ApiCtx); // signal<ApiClient>
|
|
248
|
-
return () => html`<div>API version: ${() => api().version}</div>`;
|
|
249
|
-
});
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Это как `context.WithValue` / `ctx.Value` в Go, только реактивное.
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
## SEO — не SSR, а `bake` (как `templ generate` в Go)
|
|
257
|
-
|
|
258
|
-
Если ты привык к серверному рендерингу для SEO, в Mado это решается иначе: **prerender на build**.
|
|
259
|
-
|
|
260
|
-
```ts
|
|
261
|
-
// src/pages/product.ts
|
|
262
|
-
export default page({
|
|
263
|
-
bake: {
|
|
264
|
-
paths: () => api.allProductSlugs(), // build-time fetch
|
|
265
|
-
data: ({ slug }) => api.getProduct(slug),
|
|
266
|
-
revalidate: 3600,
|
|
267
|
-
},
|
|
268
|
-
head: ({ slug }, data) => ({
|
|
269
|
-
description: data.description,
|
|
270
|
-
canonical: `/product/${slug}`,
|
|
271
|
-
og: { title: data.name, image: data.image },
|
|
272
|
-
}),
|
|
273
|
-
view: ({ params }) => html`<x-product data-slug=${params.slug}/>`,
|
|
274
|
-
});
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
```bash
|
|
278
|
-
npm run bake # → out/product/iphone-15/index.html (+ sitemap)
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
Краулер видит готовый HTML с meta-тегами. Пользователь видит то же самое + интерактив после загрузки JS.
|
|
282
|
-
|
|
283
|
-
Подробнее: [`03-static-bake.md`](./03-static-bake.md).
|
|
284
|
-
|
|
285
|
-
---
|
|
286
|
-
|
|
287
|
-
## Типичные задачи бекендера — рецепты
|
|
288
|
-
|
|
289
|
-
### CRUD-страница со списком
|
|
290
|
-
|
|
291
|
-
```ts
|
|
292
|
-
import { page, html, resource, each, signal } from "@madojs/mado";
|
|
293
|
-
|
|
294
|
-
export default page({
|
|
295
|
-
view: () => {
|
|
296
|
-
const users = resource(() => "/api/users", jsonFetcher<User[]>());
|
|
297
|
-
|
|
298
|
-
return html`
|
|
299
|
-
${() => users.loading() ? html`<p>Loading…</p>` : null}
|
|
300
|
-
${() => users.error() ? html`<p>Error: ${users.error()!.message}</p>` : null}
|
|
301
|
-
<ul>
|
|
302
|
-
${() => each(users.data() ?? [], u => u.id, u => html`
|
|
303
|
-
<li><a href="/users/${u.id}" data-link>${u.name}</a></li>
|
|
304
|
-
`)}
|
|
305
|
-
</ul>
|
|
306
|
-
`;
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
### Форма с POST
|
|
312
|
-
|
|
313
|
-
```ts
|
|
314
|
-
import { useForm, mutation } from "@madojs/mado";
|
|
315
|
-
|
|
316
|
-
const createUser = mutation<NewUser, User>(
|
|
317
|
-
(u) => fetch("/api/users", { method: "POST", body: JSON.stringify(u) }).then(r => r.json()),
|
|
318
|
-
{ invalidates: ["/api/users*"] },
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
// в page.view:
|
|
322
|
-
const f = useForm({ name: { required: true } });
|
|
323
|
-
|
|
324
|
-
html`
|
|
325
|
-
<form @submit=${f.onSubmit(async (v) => {
|
|
326
|
-
await createUser.run(v);
|
|
327
|
-
navigate("/users");
|
|
328
|
-
})}>
|
|
329
|
-
<input name="name" @input=${f.onInput}>
|
|
330
|
-
<button>Create</button>
|
|
331
|
-
</form>
|
|
332
|
-
`;
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### Защищённая зона (auth middleware)
|
|
336
|
-
|
|
337
|
-
```ts
|
|
338
|
-
// src/layouts/auth-layout.ts
|
|
339
|
-
import { page, html, effect } from "@madojs/mado";
|
|
340
|
-
import { isAuthed, navigate } from "../lib/auth.js";
|
|
341
|
-
|
|
342
|
-
export default page({
|
|
343
|
-
view: ({ child }) => {
|
|
344
|
-
effect(() => {
|
|
345
|
-
if (!isAuthed()) navigate("/login");
|
|
346
|
-
});
|
|
347
|
-
return html`<div class="app-shell">${child}</div>`;
|
|
348
|
-
},
|
|
349
|
-
});
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
```ts
|
|
353
|
-
// src/app.routes.ts
|
|
354
|
-
import { layout, routes } from "@madojs/mado";
|
|
355
|
-
|
|
356
|
-
export default routes({
|
|
357
|
-
"/login": () => import("./pages/login.js"),
|
|
358
|
-
|
|
359
|
-
"/app": layout({
|
|
360
|
-
layout: () => import("./layouts/auth-layout.js"),
|
|
361
|
-
routes: {
|
|
362
|
-
"/dashboard": () => import("./pages/dashboard.js"),
|
|
363
|
-
"/users": () => import("./pages/users.js"),
|
|
364
|
-
},
|
|
365
|
-
}),
|
|
366
|
-
});
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
### Глобальный API-клиент (как singleton в Go)
|
|
370
|
-
|
|
371
|
-
```ts
|
|
372
|
-
// src/lib/api.ts
|
|
373
|
-
export class ApiClient {
|
|
374
|
-
constructor(private base: string) {}
|
|
375
|
-
get<T>(path: string): Promise<T> {
|
|
376
|
-
return fetch(this.base + path).then(r => r.json());
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export const api = new ApiClient("/api");
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
Используется напрямую `import { api } from '...'` либо через `createContext` для тестируемости.
|
|
384
|
-
|
|
385
|
-
---
|
|
386
|
-
|
|
387
|
-
## Что **не** нужно учить (хорошие новости)
|
|
388
|
-
|
|
389
|
-
- **Хуки и правила хуков.** Нет в Mado. Сигналы — обычные функции.
|
|
390
|
-
- **VDOM и reconciliation.** Нет. Сигналы обновляют DOM напрямую, точечно.
|
|
391
|
-
- **Webpack/Vite-конфиги.** Нет билда. `tsc → браузер`.
|
|
392
|
-
- **`useEffect` dependency arrays.** `effect()` сам видит, что ты прочитал.
|
|
393
|
-
- **State management библиотеки** (Redux/Zustand). Сигналы + context.
|
|
394
|
-
- **CSS-in-JS трансформации.** Shadow DOM + `css\`\`` + cssVars.
|
|
395
|
-
- **Routing v6 → v7 migration guide.** `routes()` — 500 строк, читается за 20 минут.
|
|
396
|
-
|
|
397
|
-
---
|
|
398
|
-
|
|
399
|
-
## Что **придётся** освоить (честно)
|
|
400
|
-
|
|
401
|
-
Это новые концепции. Не страшные, но плюс к React/Vue базе:
|
|
402
|
-
|
|
403
|
-
1. **Custom Elements / Shadow DOM.** `<x-foo>` — это не div, это полноценный элемент с собственным DOM. Слоты, scoped CSS. Один вечер чтения MDN.
|
|
404
|
-
2. **`attribute` vs `property`.** Attribute — это строка в HTML (`data-id="5"`), property — JS-свойство (`el.id = 5`). `?attr=${flag}` и `.prop=${value}` в шаблонах — это про разные вещи. Главное правило: **числа/объекты/массивы — через `.prop`, флаги — через `?attr`, строки — через `attr`**.
|
|
405
|
-
3. **Сигналы.** Если первый раз — на 10 минут зависнешь, потом проще, чем хуки.
|
|
406
|
-
4. **`html\`\``-шаблоны.** Это просто JS-функция с подсветкой через [lit-plugin](./04-ide-setup.md). Не магия.
|
|
407
|
-
|
|
408
|
-
Всё остальное — стандартный браузер + TypeScript.
|
|
409
|
-
|
|
410
|
-
---
|
|
411
|
-
|
|
412
|
-
## Чего не хватает (честно)
|
|
413
|
-
|
|
414
|
-
- Нет hot reload, только full reload через SSE. Достаточно для большинства, но не как у Vite.
|
|
415
|
-
- Нет dev-tools браузерного расширения. Используется `localStorage.madoDebug = '1'` + console.
|
|
416
|
-
- Нет StackBlitz-стартеров (пока).
|
|
417
|
-
- Нет AI-ассистента, который знает Mado так же хорошо, как React. Будут вопросы — читай `src/`, там не страшно.
|
|
418
|
-
|
|
419
|
-
---
|
|
420
|
-
|
|
421
|
-
## Дальше
|
|
422
|
-
|
|
423
|
-
- **[`01-routing.md`](./01-routing.md)** — детально про роутер.
|
|
424
|
-
- **[`02-project-layout.md`](./02-project-layout.md)** — структура проекта.
|
|
425
|
-
- **[`03-static-bake.md`](./03-static-bake.md)** — SEO без SSR.
|
|
426
|
-
- **Внешний workspace `madojs-examples`** — полные демо (landing + admin).
|
|
427
|
-
|
|
428
|
-
Если что-то непонятно — open issue, или просто открой исходник. Это правда читается за вечер.
|