@madojs/mado 0.11.1 → 0.12.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 +102 -25
- package/CHANGELOG.md +158 -1
- package/README.md +135 -172
- package/dist/src/component.d.ts +3 -2
- package/dist/src/component.js +98 -6
- package/dist/src/component.js.map +1 -1
- package/dist/src/css.js +34 -5
- package/dist/src/css.js.map +1 -1
- package/dist/src/head.d.ts +6 -6
- package/dist/src/head.js +6 -6
- package/dist/src/html/parser.d.ts +1 -1
- package/dist/src/html/parser.js +1 -1
- package/dist/src/html/template.js +24 -10
- package/dist/src/html/template.js.map +1 -1
- package/dist/src/index.d.ts +8 -4
- package/dist/src/index.js +13 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/page.d.ts +62 -28
- package/dist/src/page.js +5 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/resource.js +2 -1
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/base.d.ts +65 -0
- package/dist/src/router/base.js +168 -0
- package/dist/src/router/base.js.map +1 -0
- package/dist/src/router/manifest.js +75 -31
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +16 -2
- package/dist/src/router/match.js +41 -1
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.js +58 -9
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/static-runtime.d.ts +81 -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 +31 -3
- package/dist/src/vite/index.js +49 -18
- 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/en/00-the-mado-way.md +1 -1
- package/docs/en/01-quickstart.md +183 -0
- package/docs/en/10-pages-and-components.md +271 -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 +225 -0
- package/docs/en/14-forms.md +244 -0
- package/docs/en/15-static-snapshots.md +181 -0
- package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +53 -2
- package/docs/en/{13-deployment.md → 20-deployment.md} +59 -10
- package/docs/en/{14-testing.md → 22-testing.md} +6 -5
- package/docs/en/23-cookbook.md +292 -0
- package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +12 -3
- package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +3 -3
- package/docs/en/40-llm-guide.md +776 -0
- package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +15 -16
- package/docs/en/{05-why-mado.md → 42-why-mado.md} +1 -1
- package/docs/en/README.md +97 -27
- package/docs/recipes/nginx/Containerfile +26 -0
- package/docs/recipes/nginx/nginx.conf +42 -0
- package/llms.txt +246 -211
- package/package.json +17 -10
- package/scripts/bake.mjs +4 -569
- package/scripts/cli/generate.mjs +55 -5
- package/scripts/cli/help.mjs +11 -5
- package/scripts/cli/index.mjs +20 -4
- package/scripts/cli/init.mjs +7 -2
- package/scripts/cli/release.mjs +20 -68
- package/scripts/docs-lint.mjs +170 -0
- package/scripts/package-smoke.mjs +67 -9
- package/scripts/preview.mjs +95 -6
- package/scripts/size-budget.mjs +5 -1
- package/scripts/static/browser.mjs +654 -0
- package/scripts/static/discover.mjs +281 -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 +191 -0
- package/starters/default/README.md +57 -55
- package/starters/default/package.json +3 -9
- 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/vite.config.ts +14 -1
- 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 +8 -5
- 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/billing/pages/invoices-list.page.ts +3 -3
- 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/modular/src/styles.d.ts +1 -0
- package/starters/modular/src/vite-env.d.ts +1 -0
- package/starters/modular/tsconfig.json +24 -0
- package/starters/modular/vite.config.ts +131 -0
- package/TODO.md +0 -79
- 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/starters/default/src/modules/home/not-found.page.ts +0 -11
- /package/docs/en/{15-error-handling.md → 21-error-handling.md} +0 -0
- /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/auth/login.page.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/modules/billing/pages/invoice-detail.page.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/reset.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-button.component.ts +0 -0
- /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# Project Layout
|
|
2
|
-
|
|
3
|
-
Каждое Mado-приложение использует одну каноничную форму. Это не примерная
|
|
4
|
-
рекомендация, а соглашение, чтобы люди и AI-ассистенты одинаково понимали,
|
|
5
|
-
куда класть код.
|
|
6
|
-
|
|
7
|
-
```txt
|
|
8
|
-
my-app/
|
|
9
|
-
├── package.json # runtime dep: @madojs/mado
|
|
10
|
-
├── tsconfig.json # strict TS, ES2022, Bundler resolution
|
|
11
|
-
├── vite.config.ts # mado() from @madojs/mado/vite
|
|
12
|
-
├── index.html # Vite entry + SPA shell
|
|
13
|
-
├── public/ # статика: favicon, images, robots.txt
|
|
14
|
-
└── src/
|
|
15
|
-
├── main.ts # импорт CSS и mount router в #app
|
|
16
|
-
├── app.routes.ts # app map: manifest + default routes(...)
|
|
17
|
-
├── layouts/ # layouts app-зон, не доменные модули
|
|
18
|
-
├── shared/ # ui, http, lib, styles
|
|
19
|
-
└── modules/ # bounded contexts
|
|
20
|
-
└── billing/
|
|
21
|
-
├── billing.routes.ts
|
|
22
|
-
├── billing.public.ts
|
|
23
|
-
├── billing.types.ts
|
|
24
|
-
├── pages/
|
|
25
|
-
├── data/
|
|
26
|
-
├── api/
|
|
27
|
-
└── _contracts/
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Artifact States
|
|
31
|
-
|
|
32
|
-
| Folder | Что это | Кто пишет | Deploy? |
|
|
33
|
-
| --- | --- | --- | --- |
|
|
34
|
-
| `src/` | исходники TypeScript | ты | no |
|
|
35
|
-
| `public/` | статика, копируется как есть | ты | через `out/` |
|
|
36
|
-
| `out/` | deploy artifact: SPA shell + assets + baked HTML | `mado release` | yes |
|
|
37
|
-
|
|
38
|
-
`mado release` = `typecheck` + Vite build (`out/index.html`, `out/assets/`,
|
|
39
|
-
`public/*`) + `bake` прямо в route paths + `sitemap.xml` + precompression.
|
|
40
|
-
|
|
41
|
-
`index.html` лежит в корне, потому что для Vite это entry template. В
|
|
42
|
-
`public/` кладите только файлы, которые нужно скопировать как есть.
|
|
43
|
-
|
|
44
|
-
## Куда положить новый файл?
|
|
45
|
-
|
|
46
|
-
| Что | Куда |
|
|
47
|
-
| --- | --- |
|
|
48
|
-
| Страница на новый URL | `src/modules/<module>/pages/<name>.page.ts` + module routes |
|
|
49
|
-
| Module route map | `src/modules/<module>/<module>.routes.ts` |
|
|
50
|
-
| App shell/layout | `src/layouts/<zone>.layout.ts` |
|
|
51
|
-
| Shared UI widget | `src/shared/ui/<x-name>.component.ts` |
|
|
52
|
-
| Module-only UI widget | `src/modules/<module>/components/<name>.component.ts` |
|
|
53
|
-
| API connector | `src/modules/<module>/api/<provider>.connector.ts` |
|
|
54
|
-
| Data resource/mutation | `src/modules/<module>/data/<name>.resource.ts` |
|
|
55
|
-
| Auth/session | `src/modules/auth/` |
|
|
56
|
-
| Public module surface | `src/modules/<module>/<module>.public.ts` |
|
|
57
|
-
| Pure function без UI | `src/shared/lib/<name>.ts` |
|
|
58
|
-
| Static image / favicon | `public/<file>` |
|
|
59
|
-
| App-zone shell CSS | `src/shared/styles/shell.css` |
|
|
60
|
-
| Page-level CSS | `src/shared/styles/content.css` |
|
|
61
|
-
|
|
62
|
-
## Vite Config
|
|
63
|
-
|
|
64
|
-
```ts
|
|
65
|
-
import { defineConfig } from "vite";
|
|
66
|
-
import { mado } from "@madojs/mado/vite";
|
|
67
|
-
|
|
68
|
-
export default defineConfig({
|
|
69
|
-
plugins: [mado()],
|
|
70
|
-
css: {
|
|
71
|
-
transformer: "lightningcss",
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Starter включает Vite Lightning CSS transformer. Mado не владеет prefixing,
|
|
77
|
-
CSS lowering или minification.
|
|
78
|
-
|
|
79
|
-
## Что НЕ кладём в `src/`
|
|
80
|
-
|
|
81
|
-
- Лишние build-tool configs — настройка живет в `vite.config.ts`.
|
|
82
|
-
- `.env` files — читайте env в `src/shared/lib/config.ts` и импортируйте этот
|
|
83
|
-
модуль.
|
|
84
|
-
- Tests рядом с кодом — держите их в `test/`.
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
# Smart Static (`bake`)
|
|
2
|
-
|
|
3
|
-
> Выпекание HTML для SEO без runtime SSR. **Идея: данные и view в одном файле, статика на выходе.**
|
|
4
|
-
|
|
5
|
-
`bake` — это **build-time prerender**, не SSR. На выходе — статические `*.html` файлы, которые любой nginx/Cloudflare раздаёт как обычную статику. На клиенте Web Components оживают и работает SPA-навигация дальше.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Когда `bake` подходит
|
|
10
|
-
|
|
11
|
-
- **Маркетинговые страницы**: лендинги, landing/sub-landings под рекламные кампании, страницы продуктов.
|
|
12
|
-
- **Каталог с относительно стабильным набором страниц**: блог, документация, портфолио, e-commerce до **~10k SKU** с обновлениями реже раза в час.
|
|
13
|
-
- **Контент, общий для всех пользователей**: вся страница одинакова для гостя и для авторизованного юзера (или авторизация дорисовывается через `effect()` уже на клиенте).
|
|
14
|
-
- Нужен **хороший SEO** (rich snippets, OG, JSON-LD) и **быстрый first paint** без поднятия node-серверов в проде.
|
|
15
|
-
|
|
16
|
-
## Когда `bake` **не** подходит
|
|
17
|
-
|
|
18
|
-
- **Сотни тысяч страниц с частыми изменениями**. `bake` обходит все `paths` синхронно за один прогон. На 100k+ страниц это минуты ребилда, и инвалидация одной страницы либо требует полного rebake, либо отдельной CI-логики (см. ниже про точечный revalidate).
|
|
19
|
-
- **Персонализированный контент в HTML**. Если на странице должно быть "Привет, Иван" в `<title>` или в meta — это не для `bake`. Авторизованный кабинет, личный фид, корзина с реальными ценами для юзера — оставляйте SPA.
|
|
20
|
-
- **Нужны server-only API в render**: cookies, headers, реальные сетевые запросы к закрытым API. На bake-стороне доступен только `linkedom`, никакого Node-окружения для компонентов.
|
|
21
|
-
- **A/B-тесты и flag'и, которые меняют разметку в первом paint**. `bake` зафиксирует один вариант. Динамику делайте на клиенте через `effect()`.
|
|
22
|
-
- **Real-time / часто меняющиеся данные** (биржевые котировки, остатки на складе минута-в-минуту). `bake.revalidate` — это метаданные, не runtime: фреймворк сам ничего не перевыпекает.
|
|
23
|
-
- Контент **под авторизацией** (admin, internal tools). Незачем; используйте SPA-режим.
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Концепция
|
|
28
|
-
|
|
29
|
-
`page({...})` имеет четыре опциональных слота, относящихся к `bake`:
|
|
30
|
-
|
|
31
|
-
- `head` — meta, OG, JSON-LD.
|
|
32
|
-
- `bake.paths` — список URL-параметров для генерации (build-time, может быть `async`).
|
|
33
|
-
- `bake.data` — данные для конкретного URL (build-time, может быть `async`).
|
|
34
|
-
- `bake.revalidate` — через сколько секунд кэш устарел (записывается в `<meta>`, реальную инвалидацию решает ваша CI/CDN).
|
|
35
|
-
|
|
36
|
-
Команда `npm run bake` обходит все `page` с `bake`, генерит HTML через `linkedom`, складывает в `out/<path>/index.html`. **Никакого Chromium не нужно** — `linkedom` весит ~50 КБ.
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## Пример
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
// src/pages/product.ts
|
|
44
|
-
import { page, component, html } from "@madojs/mado";
|
|
45
|
-
import { findProduct, products, type Product } from "../lib/products.js";
|
|
46
|
-
|
|
47
|
-
component("x-product-page", ({ host }) => {
|
|
48
|
-
return () => {
|
|
49
|
-
const p = findProduct(host.dataset.slug);
|
|
50
|
-
return p
|
|
51
|
-
? html`<h1>${p.name}</h1><p>${p.description}</p>`
|
|
52
|
-
: html`<p>Не найдено.</p>`;
|
|
53
|
-
};
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
export default page<{ slug: string }, Product | undefined>({
|
|
57
|
-
title: ({ slug }) => `${findProduct(slug)?.name} — MyShop`,
|
|
58
|
-
|
|
59
|
-
head: ({ slug }, baked) => {
|
|
60
|
-
const p = baked ?? findProduct(slug);
|
|
61
|
-
if (!p) return {};
|
|
62
|
-
return {
|
|
63
|
-
description: p.description,
|
|
64
|
-
canonical: `/product/${p.slug}`,
|
|
65
|
-
og: { title: p.name, image: p.image, type: "product" },
|
|
66
|
-
jsonLd: {
|
|
67
|
-
"@context": "https://schema.org",
|
|
68
|
-
"@type": "Product",
|
|
69
|
-
name: p.name,
|
|
70
|
-
offers: { "@type": "Offer", price: p.price, priceCurrency: p.currency },
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
bake: {
|
|
76
|
-
paths: () => products.map((p) => ({ slug: p.slug })),
|
|
77
|
-
data: ({ slug }) => findProduct(slug),
|
|
78
|
-
revalidate: 3600,
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
view: ({ params }) =>
|
|
82
|
-
html`<x-product-page data-slug=${params.slug}></x-product-page>`,
|
|
83
|
-
});
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
```ts
|
|
87
|
-
// src/routes.ts
|
|
88
|
-
import { routes, type RoutesMap } from "@madojs/mado";
|
|
89
|
-
|
|
90
|
-
// Экспортируем И default (RouterApi для рантайма), И manifest (для bake-скрипта).
|
|
91
|
-
export const manifest: RoutesMap = {
|
|
92
|
-
"/": () => import("./pages/home.js"),
|
|
93
|
-
"/product/:slug": () => import("./pages/product.js"),
|
|
94
|
-
"*": () => import("./pages/not-found.js"),
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
export default routes(manifest, { titleSuffix: " · MyShop" });
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
## Запуск
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
npm install -D linkedom vite
|
|
104
|
-
npm run build
|
|
105
|
-
npm run bake
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Получаешь:
|
|
109
|
-
|
|
110
|
-
```
|
|
111
|
-
out/
|
|
112
|
-
├── product/
|
|
113
|
-
│ ├── mado-mug/index.html ← HTML с meta + JSON-LD
|
|
114
|
-
│ ├── raw-bundler/index.html
|
|
115
|
-
│ └── shadow-dom/index.html
|
|
116
|
-
└── sitemap.xml
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
## Что внутри сгенерированного HTML
|
|
120
|
-
|
|
121
|
-
```html
|
|
122
|
-
<head>
|
|
123
|
-
<title>Mado-кружка — MyShop</title>
|
|
124
|
-
<meta name="description" content="..." data-mado-head="baked">
|
|
125
|
-
<link rel="canonical" href="/product/mado-mug" data-mado-head="baked">
|
|
126
|
-
<meta property="og:title" content="Mado-кружка" data-mado-head="baked">
|
|
127
|
-
<meta property="og:image" content="..." data-mado-head="baked">
|
|
128
|
-
<script type="application/ld+json" data-mado-head="baked">
|
|
129
|
-
{"@context":"https://schema.org","@type":"Product","..."}
|
|
130
|
-
</script>
|
|
131
|
-
<meta name="bake-revalidate" content="3600" data-mado-head="baked">
|
|
132
|
-
</head>
|
|
133
|
-
<body>
|
|
134
|
-
<div id="app">
|
|
135
|
-
<x-product-page data-slug="mado-mug">
|
|
136
|
-
<h1>Mado-кружка</h1>
|
|
137
|
-
<p>Кружка с надписью «zero dependencies».</p>
|
|
138
|
-
<strong>12 EUR</strong>
|
|
139
|
-
</x-product-page>
|
|
140
|
-
</div>
|
|
141
|
-
|
|
142
|
-
<!-- предзагруженные данные для гидрации -->
|
|
143
|
-
<script id="bake" type="application/json">
|
|
144
|
-
{"slug":"mado-mug","name":"Mado-кружка","price":12,"..."}
|
|
145
|
-
</script>
|
|
146
|
-
|
|
147
|
-
<script type="module" src="/assets/index-HASH.js"></script>
|
|
148
|
-
</body>
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
После загрузки JS:
|
|
152
|
-
|
|
153
|
-
1. Web Components `<x-product-page>` оживают (браузер уже знает кастомные элементы).
|
|
154
|
-
2. `page.load()` получает `baked` как initialData — никаких лишних `fetch`.
|
|
155
|
-
3. SPA-навигация работает дальше как обычно.
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## Cookbook: типовые сценарии
|
|
160
|
-
|
|
161
|
-
### Блог (Markdown → bake)
|
|
162
|
-
|
|
163
|
-
```ts
|
|
164
|
-
// src/lib/posts.ts
|
|
165
|
-
import { readdirSync, readFileSync } from "node:fs";
|
|
166
|
-
// (этот модуль импортируется только из bake-скрипта,
|
|
167
|
-
// в браузере не должен попасть в граф — выносите в lib/server/ или ifdef'ьте)
|
|
168
|
-
|
|
169
|
-
export const allPosts = () =>
|
|
170
|
-
readdirSync("content/blog").map((file) => ({
|
|
171
|
-
slug: file.replace(/\.md$/, ""),
|
|
172
|
-
...parseFrontmatter(readFileSync(`content/blog/${file}`, "utf-8")),
|
|
173
|
-
}));
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
```ts
|
|
177
|
-
// src/pages/blog-post.ts
|
|
178
|
-
import { page, html } from "@madojs/mado";
|
|
179
|
-
import { allPosts } from "../lib/posts.js";
|
|
180
|
-
|
|
181
|
-
export default page<{ slug: string }>({
|
|
182
|
-
title: ({ slug }) => allPosts().find((p) => p.slug === slug)?.title ?? slug,
|
|
183
|
-
head: ({ slug }, post) => ({
|
|
184
|
-
description: post?.excerpt,
|
|
185
|
-
canonical: `/blog/${slug}`,
|
|
186
|
-
og: { title: post?.title, type: "article" },
|
|
187
|
-
}),
|
|
188
|
-
bake: {
|
|
189
|
-
paths: () => allPosts().map(({ slug }) => ({ slug })),
|
|
190
|
-
data: ({ slug }) => allPosts().find((p) => p.slug === slug),
|
|
191
|
-
},
|
|
192
|
-
view: ({ params }) =>
|
|
193
|
-
html`<x-blog-post data-slug=${params.slug}></x-blog-post>`,
|
|
194
|
-
});
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Каталог продуктов (e-commerce, ≤ 10k SKU)
|
|
198
|
-
|
|
199
|
-
- `bake.paths` → SELECT slug FROM products
|
|
200
|
-
- `bake.data` → SELECT * FROM products WHERE slug=?
|
|
201
|
-
- Полная инвалидация раз в N часов через cron + `npm run bake`
|
|
202
|
-
- Точечная инвалидация одного товара: webhook → пересборка только этого `paths`
|
|
203
|
-
|
|
204
|
-
### Документация
|
|
205
|
-
|
|
206
|
-
- `bake.paths` — обход файловой системы `docs/**/*.md`
|
|
207
|
-
- `bake.data` — парсинг Markdown
|
|
208
|
-
- `head.jsonLd` — `TechArticle`
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
## Revalidate / CDN
|
|
213
|
-
|
|
214
|
-
`bake.revalidate: 3600` пишет в HTML `<meta name="bake-revalidate" content="3600">`. Это **метаданные** — фреймворк сам ничего не перевыпекает. Стратегии:
|
|
215
|
-
|
|
216
|
-
1. **Простейший вариант**: cron в CI — `npm run bake && rsync out/ origin:/var/www/`.
|
|
217
|
-
2. **Через CDN** (Cloudflare/Fastly): кладёте HTML с `Cache-Control: max-age=3600`. CDN сам инвалидирует.
|
|
218
|
-
3. **Webhook-триггер**: API magazin → POST `/_revalidate?path=/product/mado-mug` → CI пере-выпекает только эту страницу (можно сделать `bake.paths`, который возвращает точечный список по env-параметру).
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## Сравнение с альтернативами
|
|
223
|
-
|
|
224
|
-
| | Next.js SSG/ISR | playwright-prerender | **mado bake** |
|
|
225
|
-
|--------------------------|--------------------|----------------------|-----------------|
|
|
226
|
-
| Нужен Chrome в CI | нет | **да** (~300 МБ) | **нет** |
|
|
227
|
-
| Нужен node в проде | для ISR | нет | **нет** |
|
|
228
|
-
| Время на 1000 страниц | минуты | минуты | **секунды** |
|
|
229
|
-
| Magic-уровень | high | low | **zero** |
|
|
230
|
-
| HTML-парсер | React-renderer | браузер | linkedom (~50 КБ) |
|
|
231
|
-
| Конфигурация | next.config.js + … | один скрипт | один скрипт |
|
|
232
|
-
| Источник правды view+data | разные | страница | **одна страница** |
|
|
233
|
-
|
|
234
|
-
---
|
|
235
|
-
|
|
236
|
-
## Ограничения и подводные камни
|
|
237
|
-
|
|
238
|
-
- **В bake-стороне нет браузера.** Не работают: `setTimeout` (точнее работает, но bake завершается раньше), `fetch` к относительным URL, любые `effect()`/`signal()` побочки, реальные `requestAnimationFrame`. Render-функция должна быть детерминирована от `params` / `data`.
|
|
239
|
-
- **`linkedom` ≠ браузер.** Не все API DOM поддержаны (например, `HTMLElement.click()` ведёт себя проще). Тяжёлая логика в Web Components будет выполнена только в браузере после `connectedCallback`; в baked HTML попадёт только то, что синхронно отрендерилось при первом проходе.
|
|
240
|
-
- **Динамичный контент дорисовывайте на клиенте.** Текущее время, A/B-тест, гео-баннер, корзина юзера — не должны быть в выпеченном HTML. Используйте `effect()` для дорисовки.
|
|
241
|
-
- **Серверные импорты не должны попадать в клиентский граф.** Если `lib/posts.ts` импортит `node:fs`, его нельзя импортить из `view`. Держите такие модули в отдельной папке (`lib/build/`) и используйте только из `bake.paths`/`bake.data`.
|
|
242
|
-
- **`paths` и `data` исполняются на каждый bake-прогон.** Если за ними тяжёлый запрос к БД — кешируйте на уровне скрипта.
|
|
243
|
-
|
|
244
|
-
---
|
|
245
|
-
|
|
246
|
-
## TL;DR
|
|
247
|
-
|
|
248
|
-
Если страница **общая для всех пользователей**, имеет **относительно стабильный набор URL'ов** и важен **SEO + первый paint** — добавьте `bake: { paths, data }` и получите статический HTML с meta/JSON-LD/sitemap за миллисекунды. Без node-сервера, без Chrome, без магии.
|
|
249
|
-
|
|
250
|
-
Если страница персонализирована, или URL'ов миллион, или контент меняется в реальном времени — `bake` не ваш инструмент. Оставляйте SPA или подключайте отдельный SSR-фреймворк.
|
package/docs/ru/04-ide-setup.md
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
# IDE-поддержка `html\`\`` и `css\`\``
|
|
2
|
-
|
|
3
|
-
Из коробки `html\`...\`` и `css\`...\`` — это просто tagged-template-строки. TypeScript их не знает, IDE их не подсвечивает. Это **сознательный компромисс** ради нулевых рантайм-зависимостей и отсутствия билд-плагинов.
|
|
4
|
-
|
|
5
|
-
Хорошая новость: Mado использует те же конвенции, что и [lit](https://lit.dev), поэтому работают **готовые** IDE-инструменты от lit-экосистемы.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## VS Code (рекомендованный сетап)
|
|
10
|
-
|
|
11
|
-
### 1. Установить [lit-plugin](https://marketplace.visualstudio.com/items?itemName=runem.lit-plugin)
|
|
12
|
-
|
|
13
|
-
VS Code → Extensions → найти **"lit-plugin"** (от runem) → Install.
|
|
14
|
-
|
|
15
|
-
Что появится:
|
|
16
|
-
|
|
17
|
-
- Подсветка HTML внутри `html\`\``.
|
|
18
|
-
- Подсветка CSS внутри `css\`\``.
|
|
19
|
-
- Авто-комплит HTML-тегов, атрибутов, событий.
|
|
20
|
-
- Проверка опечаток в атрибутах.
|
|
21
|
-
- Go-to-definition для кастомных элементов (если описаны через `customElements.json` или JSDoc).
|
|
22
|
-
- Diagnostics на неверные binding'и.
|
|
23
|
-
|
|
24
|
-
### 2. Указать имена тегов
|
|
25
|
-
|
|
26
|
-
`lit-plugin` ищет идентификаторы `html` и `css` в импортах. Если вы не переименовываете их при импорте — настройка нулевая, всё работает:
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
import { html, css } from "@madojs/mado";
|
|
30
|
-
|
|
31
|
-
const tpl = html`<button @click=${fn}>${label}</button>`;
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### 3. (Опционально) Свои `customElements.json`
|
|
35
|
-
|
|
36
|
-
Если хотите авто-комплит по своим `<x-*>`-компонентам, опишите их через [Custom Elements Manifest](https://github.com/webcomponents/custom-elements-manifest):
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npm install --save-dev @custom-elements-manifest/analyzer
|
|
40
|
-
npx cem analyze --globs "src/components/**/*.ts"
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Создаст `custom-elements.json`, который `lit-plugin` подхватит автоматически.
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## WebStorm / IntelliJ
|
|
48
|
-
|
|
49
|
-
WebStorm понимает `html\`\`` и `css\`\`` **из коробки** — нативная поддержка lit-style template-литералов с 2021 года. Никаких плагинов ставить не нужно.
|
|
50
|
-
|
|
51
|
-
Если подсветка не появляется:
|
|
52
|
-
|
|
53
|
-
- Settings → Languages & Frameworks → JavaScript → проверить, что включено "Use types from server"
|
|
54
|
-
- Перезагрузить TS-сервер: ⌘+⇧+P → "Restart TypeScript Server"
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## Neovim / Helix
|
|
59
|
-
|
|
60
|
-
Используйте [`lit-html-server`](https://github.com/runem/lit-analyzer/tree/master/packages/lit-html-server) (LSP-сервер от того же автора, что и lit-plugin):
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
npm install -g lit-html-server
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
`init.lua` (для `lspconfig`):
|
|
67
|
-
|
|
68
|
-
```lua
|
|
69
|
-
require('lspconfig').lit_html.setup{
|
|
70
|
-
cmd = { 'lit-html-server', '--stdio' },
|
|
71
|
-
filetypes = { 'typescript', 'javascript' },
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
## Что НЕ работает (известные ограничения)
|
|
78
|
-
|
|
79
|
-
- **Type-check биндингов через сигналы.** `html\`<input .value=${count}>\`` — `lit-plugin` ожидает строку, а `count` это `Signal<number>`. Подавляется через `// @ts-expect-error` или комментарием `<!-- @ts-ignore -->`. Будет улучшено в Phase 3+.
|
|
80
|
-
- **Кастомные директивы (`each`)** распознаются как обычные функции — без специальной семантики у плагина.
|
|
81
|
-
- **Атрибуты с префиксами `@`, `.`, `?`** иногда подсвечиваются как ошибки, если в `lit-plugin` отключён `"no-unknown-attribute": false`. В `.vscode/settings.json`:
|
|
82
|
-
|
|
83
|
-
```json
|
|
84
|
-
{
|
|
85
|
-
"lit-plugin.rules": {
|
|
86
|
-
"no-unknown-attribute": "off",
|
|
87
|
-
"no-incompatible-type-binding": "off"
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## JSDoc-типизация компонентов
|
|
95
|
-
|
|
96
|
-
Чтобы IDE подхватывал кастомные элементы внутри `html\`\``, аннотируйте `component()`-определение через JSDoc:
|
|
97
|
-
|
|
98
|
-
```ts
|
|
99
|
-
/**
|
|
100
|
-
* @element x-counter
|
|
101
|
-
* @attr {number} initial - стартовое значение
|
|
102
|
-
* @fires {CustomEvent<number>} change - на каждое изменение
|
|
103
|
-
*/
|
|
104
|
-
component("x-counter", () => {
|
|
105
|
-
/* ... */
|
|
106
|
-
});
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
`lit-plugin` это распознаёт и предлагает атрибуты при печати `<x-counter ...>`.
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## Prettier / форматирование
|
|
114
|
-
|
|
115
|
-
Prettier с **3.0+** форматирует `html\`\`` через [`@prettier/plugin-xml`](https://github.com/prettier/plugin-xml) или встроенный режим. Минимальный `.prettierrc`:
|
|
116
|
-
|
|
117
|
-
```json
|
|
118
|
-
{
|
|
119
|
-
"semi": true,
|
|
120
|
-
"singleQuote": false,
|
|
121
|
-
"embeddedLanguageFormatting": "auto"
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
Опция `embeddedLanguageFormatting: "auto"` (default) форматирует содержимое tagged-template-литералов с известными тегами (`html`, `css`).
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## ESLint
|
|
130
|
-
|
|
131
|
-
Если используете ESLint, плагин [`eslint-plugin-lit`](https://github.com/43081j/eslint-plugin-lit) даёт правила специально под tagged-template-html (а правила [`eslint-plugin-wc`](https://github.com/43081j/eslint-plugin-wc) — под Web Components в целом). Конфигурация — на ваш вкус, не required.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## TL;DR
|
|
136
|
-
|
|
137
|
-
| Редактор | Setup | Уровень DX |
|
|
138
|
-
|---|---|---|
|
|
139
|
-
| **VS Code** | поставить `lit-plugin` | ★★★★ |
|
|
140
|
-
| **WebStorm** | ничего | ★★★★ |
|
|
141
|
-
| **Neovim/Helix** | `lit-html-server` через LSP | ★★★ |
|
|
142
|
-
| **Vim без LSP** | вручную | ★ |
|
|
143
|
-
|
|
144
|
-
Без IDE-плагина Mado тоже работает: `html\`\`` остаётся валидным TS-кодом, всё компилируется и запускается. Просто строка внутри подсвечивается как строка.
|