@madojs/mado 0.9.0 → 0.10.1
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 +58 -7
- package/CHANGELOG.md +121 -1
- package/README.md +21 -5
- package/dist/src/component.d.ts +2 -12
- package/dist/src/component.js +2 -29
- package/dist/src/component.js.map +1 -1
- package/dist/src/diagnostics.d.ts +0 -4
- package/dist/src/diagnostics.js +1 -0
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/html/bindings.js +3 -0
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html/template.js +10 -0
- package/dist/src/html/template.js.map +1 -1
- package/dist/src/resource.d.ts +3 -6
- package/dist/src/resource.js +59 -10
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/manifest.d.ts +0 -3
- package/dist/src/router/manifest.js +1 -0
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router.d.ts +1 -1
- package/dist/src/router.js +1 -1
- package/dist/src/router.js.map +1 -1
- package/dist/src/signal.d.ts +0 -4
- package/dist/src/signal.js +1 -0
- package/dist/src/signal.js.map +1 -1
- package/docs/en/02-project-layout.md +3 -2
- package/docs/en/03-static-bake.md +1 -2
- package/docs/en/06-for-backenders.md +5 -0
- package/docs/en/08-llm-zero-history-test.md +5 -0
- package/docs/en/13-deployment.md +10 -7
- package/docs/en/16-bake-cookbook.md +10 -2
- package/docs/en/18-api-freeze-map.md +63 -0
- package/docs/en/19-reactivity-ordering.md +93 -0
- package/docs/en/20-v1-stability.md +83 -0
- package/docs/en/README.md +3 -0
- package/docs/fr/02-project-layout.md +20 -13
- package/docs/fr/03-static-bake.md +1 -2
- package/docs/fr/06-for-backenders.md +6 -0
- package/docs/fr/08-llm-zero-history-test.md +5 -0
- package/docs/fr/13-deployment.md +33 -12
- package/docs/fr/16-bake-cookbook.md +57 -4
- package/docs/fr/18-api-freeze-map.md +63 -0
- package/docs/fr/19-reactivity-ordering.md +97 -0
- package/docs/fr/20-v1-stability.md +88 -0
- package/docs/fr/README.md +3 -0
- package/docs/ru/02-project-layout.md +22 -15
- package/docs/ru/03-static-bake.md +2 -3
- package/docs/ru/06-for-backenders.md +6 -0
- package/docs/ru/08-llm-zero-history-test.md +5 -0
- package/docs/ru/13-deployment.md +23 -13
- package/docs/ru/16-bake-cookbook.md +42 -8
- package/docs/ru/18-api-freeze-map.md +62 -0
- package/docs/ru/19-reactivity-ordering.md +95 -0
- package/docs/ru/20-v1-stability.md +82 -0
- package/docs/ru/README.md +3 -0
- package/docs/uk/06-for-backenders.md +5 -0
- package/docs/uk/08-llm-zero-history-test.md +5 -0
- package/docs/uk/18-api-freeze-map.md +61 -0
- package/docs/uk/19-reactivity-ordering.md +95 -0
- package/docs/uk/20-v1-stability.md +83 -0
- package/docs/uk/README.md +3 -0
- package/llms.txt +59 -5
- package/package.json +8 -3
- package/scripts/bake.mjs +4 -2
- package/scripts/cli.mjs +83 -5
- package/scripts/llm-zero-history-smoke.mjs +93 -0
- package/scripts/new.mjs +1 -1
- package/scripts/package-smoke.mjs +74 -0
- package/scripts/preview.mjs +7 -27
- package/scripts/size-budget.mjs +88 -0
- package/starters/admin/README.md +2 -2
- package/starters/admin/package.json +2 -2
- package/starters/crud/package.json +2 -2
- package/starters/minimal/package.json +2 -2
|
@@ -129,7 +129,6 @@ out/
|
|
|
129
129
|
{"@context":"https://schema.org","@type":"Product","..."}
|
|
130
130
|
</script>
|
|
131
131
|
<meta name="bake-revalidate" content="3600" data-mado-head="baked">
|
|
132
|
-
<meta name="bake-stamp" content="1234567890" data-mado-head="baked">
|
|
133
132
|
</head>
|
|
134
133
|
<body>
|
|
135
134
|
<div id="app">
|
|
@@ -212,7 +211,7 @@ export default page<{ slug: string }>({
|
|
|
212
211
|
|
|
213
212
|
## Revalidate / CDN
|
|
214
213
|
|
|
215
|
-
`bake.revalidate: 3600` пишет в HTML `<meta name="bake-revalidate" content="3600"
|
|
214
|
+
`bake.revalidate: 3600` пишет в HTML `<meta name="bake-revalidate" content="3600">`. Это **метаданные** — фреймворк сам ничего не перевыпекает. Стратегии:
|
|
216
215
|
|
|
217
216
|
1. **Простейший вариант**: cron в CI — `npm run bake && rsync out/ origin:/var/www/`.
|
|
218
217
|
2. **Через CDN** (Cloudflare/Fastly): кладёте HTML с `Cache-Control: max-age=3600`. CDN сам инвалидирует.
|
|
@@ -248,4 +247,4 @@ export default page<{ slug: string }>({
|
|
|
248
247
|
|
|
249
248
|
Если страница **общая для всех пользователей**, имеет **относительно стабильный набор URL'ов** и важен **SEO + первый paint** — добавьте `bake: { paths, data }` и получите статический HTML с meta/JSON-LD/sitemap за миллисекунды. Без node-сервера, без Chrome, без магии.
|
|
250
249
|
|
|
251
|
-
Если страница персонализирована, или URL'ов миллион, или контент меняется в реальном времени — `bake` не ваш инструмент. Оставляйте SPA или подключайте отдельный SSR-фреймворк.
|
|
250
|
+
Если страница персонализирована, или URL'ов миллион, или контент меняется в реальном времени — `bake` не ваш инструмент. Оставляйте SPA или подключайте отдельный SSR-фреймворк.
|
|
@@ -150,6 +150,12 @@ await save.run(newUser);
|
|
|
150
150
|
// автоматически: user.data() обновится, если совпал glob
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
Ключи `resource()` — это identity кеша. Включайте endpoint, query params и
|
|
154
|
+
форму данных в ключ: два живых `resource()` с одинаковым ключом разделяют кеш
|
|
155
|
+
и in-flight request. Если один и тот же ключ используется с другим fetcher,
|
|
156
|
+
Mado предупреждает, потому что обычно это значит, что ключ кеша слишком
|
|
157
|
+
широкий.
|
|
158
|
+
|
|
153
159
|
Если бы такая абстракция была в Go-мире для серверных кешей — мы бы все плакали от счастья.
|
|
154
160
|
|
|
155
161
|
---
|
|
@@ -51,6 +51,11 @@
|
|
|
51
51
|
Текущая реализация `examples/tickets` не потребовала новых публичных API или
|
|
52
52
|
runtime-зависимостей.
|
|
53
53
|
|
|
54
|
+
CI запускает `npm run llm:smoke` как детерминированный proxy для этой задачи:
|
|
55
|
+
проверяет, что `llms.txt` всё ещё содержит ключевые правила, сверяет
|
|
56
|
+
зафиксированный артефакт `examples/tickets` с нужной Mado API surface и
|
|
57
|
+
failure patterns, затем собирает проект и запускает `test/tickets-smoke.test.mjs`.
|
|
58
|
+
|
|
54
59
|
Основная болевая точка в документации остается lifecycle: старые примеры могут
|
|
55
60
|
создать впечатление, что создание `resource()` прямо в `page.view()` допустимо.
|
|
56
61
|
Пример tickets использует page-level wrapper-компоненты вместо этого, поэтому
|
package/docs/ru/13-deployment.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Deployment
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Одна команда, один deploy artifact:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
mado release
|
|
@@ -10,15 +10,21 @@ mado release
|
|
|
10
10
|
|
|
11
11
|
```txt
|
|
12
12
|
out/
|
|
13
|
-
├── index.html
|
|
14
|
-
├── assets/
|
|
15
|
-
├──
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
├── index.html ← SPA shell или promoted baked HTML для /
|
|
14
|
+
├── assets/ ← hashed bundles (main-ABC.js, chunk-XYZ.js, ...)
|
|
15
|
+
│ ├── *.gz ← precompressed gzip
|
|
16
|
+
│ └── *.br ← precompressed brotli
|
|
17
|
+
├── baked/ ← копия результата bake для inspection/debugging
|
|
18
|
+
│ ├── <route>/index.html
|
|
19
|
+
│ └── sitemap.xml
|
|
20
|
+
├── <route>/index.html ← promoted baked HTML для static hosts
|
|
21
|
+
├── sitemap.xml ← sitemap в root сайта
|
|
22
|
+
├── _redirects ← Cloudflare Pages / Netlify SPA fallback
|
|
23
|
+
└── _headers ← cache rules
|
|
18
24
|
```
|
|
19
25
|
|
|
20
26
|
`out/` можно деплоить на nginx, Cloudflare Pages, Netlify, S3/CloudFront или
|
|
21
|
-
GitHub Pages.
|
|
27
|
+
GitHub Pages. Не деплой `dist/`: это внутренний output для dev/build.
|
|
22
28
|
|
|
23
29
|
## Preview
|
|
24
30
|
|
|
@@ -27,8 +33,10 @@ mado release
|
|
|
27
33
|
mado preview
|
|
28
34
|
```
|
|
29
35
|
|
|
30
|
-
`mado preview` сервит `out/` как
|
|
31
|
-
|
|
36
|
+
`mado preview` сервит финальный `out/` как обычный static host: сначала реальные
|
|
37
|
+
файлы (`/<route>/index.html`, если route был baked), потом SPA fallback в
|
|
38
|
+
`index.html`. Preview больше не делает отдельную виртуальную подстановку из
|
|
39
|
+
`out/baked/`, поэтому он проверяет ровно то, что будет загружено на хостинг.
|
|
32
40
|
|
|
33
41
|
## VPS + nginx
|
|
34
42
|
|
|
@@ -37,8 +45,8 @@ mado release
|
|
|
37
45
|
rsync -avz --delete out/ user@server:/var/www/myapp/
|
|
38
46
|
```
|
|
39
47
|
|
|
40
|
-
В репозитории есть production `nginx.conf`: hashed bundles кешируются
|
|
41
|
-
|
|
48
|
+
В репозитории есть production `nginx.conf`: hashed bundles кешируются immutable,
|
|
49
|
+
HTML идет с `no-cache`, deep links работают через SPA fallback.
|
|
42
50
|
|
|
43
51
|
## Cloudflare / Netlify
|
|
44
52
|
|
|
@@ -47,9 +55,11 @@ mado release
|
|
|
47
55
|
npx wrangler pages deploy out --project-name=myapp
|
|
48
56
|
```
|
|
49
57
|
|
|
50
|
-
`_redirects` и `_headers` генерируются
|
|
58
|
+
`_redirects` и `_headers` генерируются автоматически, если ты не положил свои.
|
|
59
|
+
Baked routes промотируются в реальные файлы (`out/<route>/index.html`), поэтому
|
|
60
|
+
static host отдаст их до SPA fallback.
|
|
51
61
|
|
|
52
|
-
## Cache
|
|
62
|
+
## Cache Rules
|
|
53
63
|
|
|
54
64
|
| Path | Cache-Control |
|
|
55
65
|
|---|---|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# Bake
|
|
1
|
+
# Bake Cookbook
|
|
2
2
|
|
|
3
3
|
`mado bake` рендерит выбранные роуты в статический HTML. Это для SEO и быстрого
|
|
4
4
|
первого ответа, не SSR с hydration.
|
|
5
5
|
|
|
6
|
-
## Минимальная
|
|
6
|
+
## Минимальная Страница
|
|
7
7
|
|
|
8
8
|
```ts
|
|
9
9
|
export default page({
|
|
@@ -23,9 +23,9 @@ export default page({
|
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
В baked views лучше использовать обычные массивы (`items.map(...)`). Runtime
|
|
26
|
-
директивы вроде `each()` нужны браузеру.
|
|
26
|
+
директивы вроде keyed `each()` нужны браузеру.
|
|
27
27
|
|
|
28
|
-
## Dynamic
|
|
28
|
+
## Dynamic Routes
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
31
|
export default page<{ slug: string }>({
|
|
@@ -40,16 +40,50 @@ export default page<{ slug: string }>({
|
|
|
40
40
|
|
|
41
41
|
`unsafeHTML()` используй только для доверенного или заранее очищенного HTML.
|
|
42
42
|
|
|
43
|
-
## Manifest
|
|
43
|
+
## Route Manifest
|
|
44
|
+
|
|
45
|
+
`mado bake` нужен source manifest:
|
|
44
46
|
|
|
45
47
|
```ts
|
|
46
48
|
export const manifest = {
|
|
47
49
|
"/": () => import("./pages/home.js"),
|
|
48
50
|
"/blog/:slug": () => import("./pages/blog-post.js"),
|
|
49
51
|
};
|
|
52
|
+
|
|
50
53
|
export default routes(manifest);
|
|
51
54
|
```
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
## Output
|
|
57
|
+
|
|
58
|
+
Standalone `mado bake` по умолчанию пишет baked pages в `out/baked/`.
|
|
59
|
+
`mado release` использует bundled production shell, оставляет копию в
|
|
60
|
+
`out/baked/` для inspection, промотирует HTML в реальные route paths внутри
|
|
61
|
+
`out/` и копирует sitemap в `out/sitemap.xml`.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
mado release
|
|
65
|
+
tree out
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Deployable folder — `out/`, не `dist/`.
|
|
69
|
+
|
|
70
|
+
## Client Boot
|
|
71
|
+
|
|
72
|
+
Baked HTML помечает `#app` атрибутом `data-mado-baked`. Это не hydration:
|
|
73
|
+
клиентский `render()` заменяет baked DOM живыми bindings при старте приложения.
|
|
74
|
+
Так первый ответ содержит SEO/first-paint HTML, а SPA после загрузки работает
|
|
75
|
+
как обычно.
|
|
76
|
+
|
|
77
|
+
## Unsupported Values
|
|
78
|
+
|
|
79
|
+
Bake намеренно падает громко вместо записи `[object Object]`. Если baked view
|
|
80
|
+
ругается на unsupported directive:
|
|
81
|
+
|
|
82
|
+
- замени `each()` на `items.map(...)` в baked markup;
|
|
83
|
+
- интерактивные виджеты оставь в client-only routes;
|
|
84
|
+
- убедись, что все значения сериализуются в статический HTML.
|
|
85
|
+
|
|
86
|
+
## Canonical Links
|
|
87
|
+
|
|
88
|
+
Передай `--base-url` или задай `bake.baseUrl` в `mado.config.json`, чтобы
|
|
89
|
+
canonical links и sitemap указывали на production.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Карта заморозки API
|
|
2
|
+
|
|
3
|
+
> Что публично, что внутреннее, и что SemVer будет защищать в v1.
|
|
4
|
+
|
|
5
|
+
Контракт Mado v1 намеренно небольшой. Код приложения импортирует API из корня
|
|
6
|
+
пакета:
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { component, html, resource, routes, signal } from "@madojs/mado";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Единственный публичный subpath — side-effect модуль devtools:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import "@madojs/mado/devtools.js";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Все остальное под `dist/src/` — деталь реализации, даже если файл виден в
|
|
19
|
+
репозитории.
|
|
20
|
+
|
|
21
|
+
## Стабильный публичный API
|
|
22
|
+
|
|
23
|
+
Эти имена публичны и будут защищены SemVer после v1:
|
|
24
|
+
|
|
25
|
+
- Reactivity: `signal`, `computed`, `effect`, `untracked`, `batch`,
|
|
26
|
+
`flushSync`.
|
|
27
|
+
- Templates и directives: `html`, `render`, `each`, `list`, `unsafeHTML`,
|
|
28
|
+
`ref`, `classMap`, `styleMap`.
|
|
29
|
+
- Components и CSS: `component`, `css`, `cssVars`.
|
|
30
|
+
- Routing и pages: `routes`, `router`, `page`, `layout`, `nested`,
|
|
31
|
+
`navigate`, `queryParam`, `prefetchPath`.
|
|
32
|
+
- Data: `resource`, `mutation`, `invalidate`, `jsonFetcher`, `HttpError`.
|
|
33
|
+
- Forms: `useForm`.
|
|
34
|
+
- Head и persistence: `applyHead`, `persisted`.
|
|
35
|
+
- Context: `createContext`, `provide`, `inject`.
|
|
36
|
+
- Advanced lifecycle helpers: `createLifecycle`, `runInLifecycle`,
|
|
37
|
+
`getCurrentLifecycle`.
|
|
38
|
+
- Публичные TypeScript-типы, экспортируемые из `@madojs/mado`.
|
|
39
|
+
|
|
40
|
+
## Внутреннее или нестабильное
|
|
41
|
+
|
|
42
|
+
Это не публичный API:
|
|
43
|
+
|
|
44
|
+
- Package subpaths кроме `@madojs/mado` и `@madojs/mado/devtools.js`.
|
|
45
|
+
- Internals парсера/биндингов: `html/parser.js`, `html/bindings.js`,
|
|
46
|
+
`ChildState`, `EachEntry`.
|
|
47
|
+
- Internals роутера: `router/match.js`, `router/navigation.js`,
|
|
48
|
+
`router/manifest.js`.
|
|
49
|
+
- Diagnostics internals и все `_testHooks`.
|
|
50
|
+
- Точный текст bundle, имена chunks и внутренняя структура файлов.
|
|
51
|
+
|
|
52
|
+
Тесты репозитория могут импортировать внутренние файлы через относительные
|
|
53
|
+
пути `dist/`. Код приложений так делать не должен.
|
|
54
|
+
|
|
55
|
+
## Что может меняться
|
|
56
|
+
|
|
57
|
+
Patch и minor релизы могут добавлять root exports, опции, diagnostics, docs или
|
|
58
|
+
starter files. Они также могут менять internals, форму bundle и детали
|
|
59
|
+
реализации, если стабильный API и задокументированное поведение остаются
|
|
60
|
+
совместимыми.
|
|
61
|
+
|
|
62
|
+
Ломающие изменения стабильного API требуют major version.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Порядок reactivity
|
|
2
|
+
|
|
3
|
+
> Небольшой набор ordering-гарантий, которые Mado считает публичным поведением.
|
|
4
|
+
|
|
5
|
+
Reactivity в Mado синхронна для чтения и планирует side effects. Цель —
|
|
6
|
+
предсказуемые UI-обновления без большой scheduling-модели.
|
|
7
|
+
|
|
8
|
+
## Signals
|
|
9
|
+
|
|
10
|
+
`signal(value)` возвращает getter-функцию. `set(next)` меняет значение сразу,
|
|
11
|
+
если `Object.is(previous, next)` не вернул `true`.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const count = signal(0);
|
|
15
|
+
count.set(1);
|
|
16
|
+
count(); // 1, сразу
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`computed` помечается до запуска effects, поэтому effect, читающий computed,
|
|
20
|
+
видит актуальные dependencies, а не старый кеш.
|
|
21
|
+
|
|
22
|
+
## Effects
|
|
23
|
+
|
|
24
|
+
`effect(fn)` запускается один раз сразу. Последующие изменения dependencies
|
|
25
|
+
планируют один запуск effect в microtask. В тестах можно вызвать `flushSync()`,
|
|
26
|
+
чтобы синхронно очистить очередь.
|
|
27
|
+
|
|
28
|
+
Если effect возвращает cleanup-функцию, Mado запускает ее перед следующим
|
|
29
|
+
запуском effect и еще раз при вызове disposer.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const stop = effect(() => {
|
|
33
|
+
const id = setInterval(tick, 1000);
|
|
34
|
+
return () => clearInterval(id);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
stop();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
В компонентах и pages для unmount cleanup предпочитайте `ctx.onDispose()` /
|
|
41
|
+
page `onDispose()`. Cleanup effect — это cleanup между запусками.
|
|
42
|
+
|
|
43
|
+
## Batch
|
|
44
|
+
|
|
45
|
+
`batch(fn)` группирует записи signals в один subscriber pass. Effects не
|
|
46
|
+
запускаются до выхода из внешнего batch, включая вложенные batches.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
batch(() => {
|
|
50
|
+
first.set("Ada");
|
|
51
|
+
batch(() => last.set("Lovelace"));
|
|
52
|
+
});
|
|
53
|
+
// effects увидят только финальную пару
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Наблюдаемые `computed({ equals })` также сохраняют batch atomicity: они
|
|
57
|
+
пересчитываются один раз после внешнего batch на полностью примененном state.
|
|
58
|
+
Они не должны видеть половинчатый batch вроде `(new x, old y)`.
|
|
59
|
+
|
|
60
|
+
## DOM updates
|
|
61
|
+
|
|
62
|
+
`render(result, container)` переиспользует существующий template instance, когда
|
|
63
|
+
следующий render имеет те же template strings. Для child bindings, возвращающих
|
|
64
|
+
вложенный `html```, действует то же правило: те же strings обновляются на
|
|
65
|
+
месте, другие strings пересобирают ветку.
|
|
66
|
+
|
|
67
|
+
Это значит, что несвязанные изменения signals не пересоздают `<input>` внутри
|
|
68
|
+
стабильного вложенного template, поэтому focus, DOM state и listeners
|
|
69
|
+
сохраняются.
|
|
70
|
+
|
|
71
|
+
Списки должны использовать `each(items, key, renderItem)`. Keys задают DOM
|
|
72
|
+
identity. Duplicate keys предупреждают в development и получают positional
|
|
73
|
+
suffix, чтобы все элементы все равно отрендерились, но duplicate keys — это
|
|
74
|
+
ошибка данных.
|
|
75
|
+
|
|
76
|
+
## Teardown компонентов
|
|
77
|
+
|
|
78
|
+
Custom elements могут получить `disconnectedCallback()`, а затем
|
|
79
|
+
`connectedCallback()` во время same-tick move. Mado откладывает teardown
|
|
80
|
+
компонента до microtask и отменяет его при reconnect, поэтому keyed reorders
|
|
81
|
+
сохраняют state компонента. Настоящее удаление все равно запускает cleanup на
|
|
82
|
+
следующей microtask.
|
|
83
|
+
|
|
84
|
+
## Не гарантируется
|
|
85
|
+
|
|
86
|
+
Mado не гарантирует точное число внутренних scheduler microtasks, порядок
|
|
87
|
+
независимых effects без общих dependencies, форму generated bundle или
|
|
88
|
+
внутреннюю структуру modules. Это детали реализации.
|
|
89
|
+
|
|
90
|
+
Invariant tests для этого контракта:
|
|
91
|
+
|
|
92
|
+
- `test/reactivity-ordering.test.mjs`
|
|
93
|
+
- `test/signal-batch-equals.test.mjs`
|
|
94
|
+
- `test/update-nested-reuse.test.mjs`
|
|
95
|
+
- `test/each-component-state.test.mjs`
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Стабильность v1
|
|
2
|
+
|
|
3
|
+
> Что Mado обещает после v1, и что остается свободным для развития.
|
|
4
|
+
|
|
5
|
+
Mado v1 означает, что публичный app-facing contract достаточно стабилен для
|
|
6
|
+
реальных business apps. Это не значит, что каждый внутренний файл, generated
|
|
7
|
+
byte, starter copy или diagnostic string заморожены навсегда.
|
|
8
|
+
|
|
9
|
+
Читайте вместе с:
|
|
10
|
+
|
|
11
|
+
- [Карта заморозки API](./18-api-freeze-map.md)
|
|
12
|
+
- [Порядок reactivity](./19-reactivity-ordering.md)
|
|
13
|
+
|
|
14
|
+
## Стабильно под SemVer
|
|
15
|
+
|
|
16
|
+
После v1 Mado считает SemVer-protected:
|
|
17
|
+
|
|
18
|
+
- Public exports из `@madojs/mado`.
|
|
19
|
+
- Public TypeScript types из `@madojs/mado`.
|
|
20
|
+
- Side-effect subpath `@madojs/mado/devtools.js`.
|
|
21
|
+
- Template binding syntax: child `${}`, `@event`, `.prop`, `?boolean`,
|
|
22
|
+
attribute bindings, directives и `each()`.
|
|
23
|
+
- Signal semantics, описанные в reactivity ordering guide.
|
|
24
|
+
- Component lifecycle semantics: setup один раз за connection lifetime,
|
|
25
|
+
deferred teardown для same-tick moves, cleanup через `ctx.onDispose`.
|
|
26
|
+
- Router/page/resource/form contracts, описанные в English docs.
|
|
27
|
+
- Имена CLI commands и широкий смысл команд (`build`, `dev`, `release`,
|
|
28
|
+
`bake`, `bundle`, `preview`, `init`, `new`).
|
|
29
|
+
|
|
30
|
+
Ломать это можно только в major version.
|
|
31
|
+
|
|
32
|
+
## Разрешено в minor releases
|
|
33
|
+
|
|
34
|
+
Minor releases могут добавлять:
|
|
35
|
+
|
|
36
|
+
- New root exports.
|
|
37
|
+
- New options на существующих API.
|
|
38
|
+
- New diagnostics и warnings.
|
|
39
|
+
- New starters, examples, docs и CLI flags.
|
|
40
|
+
- Performance improvements и внутренние rewrites.
|
|
41
|
+
|
|
42
|
+
Minor release не должен требовать изменений в уже корректных apps.
|
|
43
|
+
|
|
44
|
+
## Разрешено в patch releases
|
|
45
|
+
|
|
46
|
+
Patch releases могут исправлять bugs, ужесточать diagnostics, улучшать docs и
|
|
47
|
+
делать совместимые implementation changes. Patch может изменить timing только
|
|
48
|
+
когда старый timing был незадокументированным bug и новое поведение сохраняет
|
|
49
|
+
reactivity ordering contract.
|
|
50
|
+
|
|
51
|
+
## Нестабильно
|
|
52
|
+
|
|
53
|
+
Это намеренно не защищено SemVer:
|
|
54
|
+
|
|
55
|
+
- Internal package subpaths кроме `@madojs/mado/devtools.js`.
|
|
56
|
+
- Файлы под `src/`, `dist/src/` и implementation module boundaries.
|
|
57
|
+
- `_testHooks`, diagnostics internals и warning codes.
|
|
58
|
+
- Точный JavaScript output, chunk names, sourcemap content и bundle byte layout.
|
|
59
|
+
- Internal parser, binding, router и resource cache data structures.
|
|
60
|
+
- Visual copy и demo data в starters.
|
|
61
|
+
|
|
62
|
+
Apps не должны импортировать internal files или проверять точный bundle output.
|
|
63
|
+
|
|
64
|
+
## Bundle и release output
|
|
65
|
+
|
|
66
|
+
Mado держит size budget и deterministic release tests, но v1 stability не
|
|
67
|
+
замораживает byte-for-byte bundler output. Hashes, chunk boundaries и asset
|
|
68
|
+
names могут меняться, если задокументированный deployment contract продолжает
|
|
69
|
+
работать.
|
|
70
|
+
|
|
71
|
+
## Если релиз вас сломал
|
|
72
|
+
|
|
73
|
+
Если update ломает код, который использует только public exports и
|
|
74
|
+
задокументированное поведение, считайте это bug. Откройте issue и укажите:
|
|
75
|
+
|
|
76
|
+
- версию Mado до и после;
|
|
77
|
+
- задействованный public API;
|
|
78
|
+
- минимальную репродукцию;
|
|
79
|
+
- это runtime behaviour, TypeScript types, CLI output или docs.
|
|
80
|
+
|
|
81
|
+
Если поломка зависит от internal subpath или точного generated output, ее все
|
|
82
|
+
равно можно зарепортить, но это не считается SemVer break.
|
package/docs/ru/README.md
CHANGED
|
@@ -20,3 +20,6 @@
|
|
|
20
20
|
| Обработка ошибок | [15-error-handling.md](./15-error-handling.md) |
|
|
21
21
|
| Рецепты bake | [16-bake-cookbook.md](./16-bake-cookbook.md) |
|
|
22
22
|
| Shadow DOM + формы | [17-shadow-dom-forms.md](./17-shadow-dom-forms.md) |
|
|
23
|
+
| Карта заморозки API | [18-api-freeze-map.md](./18-api-freeze-map.md) |
|
|
24
|
+
| Порядок reactivity | [19-reactivity-ordering.md](./19-reactivity-ordering.md) |
|
|
25
|
+
| Стабильность v1 | [20-v1-stability.md](./20-v1-stability.md) |
|
|
@@ -26,6 +26,11 @@ const save = mutation(api.saveUser, {
|
|
|
26
26
|
});
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
Ключі `resource()` — це identity кешу. Додавайте endpoint, query params і форму
|
|
30
|
+
даних у ключ: два живі `resource()` з однаковим ключем ділять cache та
|
|
31
|
+
in-flight request. Якщо той самий ключ використано з іншим fetcher, Mado
|
|
32
|
+
попереджає, бо зазвичай це означає, що ключ кешу занадто широкий.
|
|
33
|
+
|
|
29
34
|
## Форми
|
|
30
35
|
|
|
31
36
|
```ts
|
|
@@ -27,5 +27,10 @@ Mado CRUD, не перетворюючи його на React у tagged templates
|
|
|
27
27
|
resources, mutations, invalidation, `queryParam`, `computed`, `signal` і
|
|
28
28
|
keyed lists.
|
|
29
29
|
|
|
30
|
+
CI запускає `npm run llm:smoke` як детермінований proxy для цієї задачі:
|
|
31
|
+
перевіряє, що `llms.txt` містить ключові правила, звіряє закомічений артефакт
|
|
32
|
+
`examples/tickets` з потрібною Mado API surface та failure patterns, потім
|
|
33
|
+
збирає проєкт і запускає `test/tickets-smoke.test.mjs`.
|
|
34
|
+
|
|
30
35
|
Критерій успіху: код виглядає як Mado, а не як React/Vue, переодягнений у
|
|
31
36
|
template strings.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Карта замороження API
|
|
2
|
+
|
|
3
|
+
> Що є публічним, що внутрішнім, і що SemVer захищатиме у v1.
|
|
4
|
+
|
|
5
|
+
Контракт Mado v1 навмисно невеликий. Код застосунку імпортує API з кореня
|
|
6
|
+
пакета:
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { component, html, resource, routes, signal } from "@madojs/mado";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Єдиний публічний subpath — side-effect модуль devtools:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import "@madojs/mado/devtools.js";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Усе інше під `dist/src/` — деталь реалізації, навіть якщо файл видно в
|
|
19
|
+
репозиторії.
|
|
20
|
+
|
|
21
|
+
## Стабільний публічний API
|
|
22
|
+
|
|
23
|
+
Ці імена публічні й захищаються SemVer після v1:
|
|
24
|
+
|
|
25
|
+
- Reactivity: `signal`, `computed`, `effect`, `untracked`, `batch`,
|
|
26
|
+
`flushSync`.
|
|
27
|
+
- Templates і directives: `html`, `render`, `each`, `list`, `unsafeHTML`,
|
|
28
|
+
`ref`, `classMap`, `styleMap`.
|
|
29
|
+
- Components і CSS: `component`, `css`, `cssVars`.
|
|
30
|
+
- Routing і pages: `routes`, `router`, `page`, `layout`, `nested`,
|
|
31
|
+
`navigate`, `queryParam`, `prefetchPath`.
|
|
32
|
+
- Data: `resource`, `mutation`, `invalidate`, `jsonFetcher`, `HttpError`.
|
|
33
|
+
- Forms: `useForm`.
|
|
34
|
+
- Head і persistence: `applyHead`, `persisted`.
|
|
35
|
+
- Context: `createContext`, `provide`, `inject`.
|
|
36
|
+
- Advanced lifecycle helpers: `createLifecycle`, `runInLifecycle`,
|
|
37
|
+
`getCurrentLifecycle`.
|
|
38
|
+
- Публічні TypeScript-типи, експортовані з `@madojs/mado`.
|
|
39
|
+
|
|
40
|
+
## Внутрішнє або нестабільне
|
|
41
|
+
|
|
42
|
+
Це не публічний API:
|
|
43
|
+
|
|
44
|
+
- Package subpaths крім `@madojs/mado` і `@madojs/mado/devtools.js`.
|
|
45
|
+
- Internals parser/binding: `html/parser.js`, `html/bindings.js`,
|
|
46
|
+
`ChildState`, `EachEntry`.
|
|
47
|
+
- Internals router: `router/match.js`, `router/navigation.js`,
|
|
48
|
+
`router/manifest.js`.
|
|
49
|
+
- Diagnostics internals і всі `_testHooks`.
|
|
50
|
+
- Точний текст bundle, назви chunks і внутрішня структура файлів.
|
|
51
|
+
|
|
52
|
+
Тести репозиторію можуть імпортувати internal files через відносні шляхи
|
|
53
|
+
`dist/`. Код застосунків не повинен цього робити.
|
|
54
|
+
|
|
55
|
+
## Що може змінюватися
|
|
56
|
+
|
|
57
|
+
Patch і minor releases можуть додавати root exports, options, diagnostics, docs
|
|
58
|
+
або starter files. Вони також можуть змінювати internals, форму bundle і деталі
|
|
59
|
+
реалізації, якщо stable API та задокументована поведінка лишаються сумісними.
|
|
60
|
+
|
|
61
|
+
Breaking changes стабільного API потребують major version.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Порядок reactivity
|
|
2
|
+
|
|
3
|
+
> Малий набір ordering-гарантій, які Mado вважає публічною поведінкою.
|
|
4
|
+
|
|
5
|
+
Reactivity у Mado синхронна для читання і планована для side effects. Мета —
|
|
6
|
+
передбачувані UI updates без великої scheduling-моделі.
|
|
7
|
+
|
|
8
|
+
## Signals
|
|
9
|
+
|
|
10
|
+
`signal(value)` повертає getter-функцію. `set(next)` змінює значення одразу,
|
|
11
|
+
якщо `Object.is(previous, next)` не дорівнює `true`.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const count = signal(0);
|
|
15
|
+
count.set(1);
|
|
16
|
+
count(); // 1, одразу
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Computed values позначаються до запуску effects, тому effect, який читає
|
|
20
|
+
computed, бачить актуальні dependencies, а не застарілий cache.
|
|
21
|
+
|
|
22
|
+
## Effects
|
|
23
|
+
|
|
24
|
+
`effect(fn)` запускається один раз одразу. Подальші зміни dependencies планують
|
|
25
|
+
один запуск effect у microtask. Тести можуть викликати `flushSync()`, щоб
|
|
26
|
+
синхронно очистити чергу.
|
|
27
|
+
|
|
28
|
+
Якщо effect повертає cleanup-функцію, Mado запускає її перед наступним запуском
|
|
29
|
+
effect і ще раз при виклику disposer.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const stop = effect(() => {
|
|
33
|
+
const id = setInterval(tick, 1000);
|
|
34
|
+
return () => clearInterval(id);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
stop();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
У компонентах і pages для unmount cleanup надавайте перевагу
|
|
41
|
+
`ctx.onDispose()` / page `onDispose()`. Cleanup effect — це cleanup між runs.
|
|
42
|
+
|
|
43
|
+
## Batch
|
|
44
|
+
|
|
45
|
+
`batch(fn)` групує записи signals в один subscriber pass. Effects не
|
|
46
|
+
запускаються до виходу з найзовнішнього batch, включно з вкладеними batches.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
batch(() => {
|
|
50
|
+
first.set("Ada");
|
|
51
|
+
batch(() => last.set("Lovelace"));
|
|
52
|
+
});
|
|
53
|
+
// effects бачать тільки фінальну пару
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Спостережувані `computed({ equals })` також зберігають batch atomicity: вони
|
|
57
|
+
перераховуються один раз після зовнішнього batch на повністю застосованому
|
|
58
|
+
state. Вони не повинні бачити напівзастосований batch на кшталт
|
|
59
|
+
`(new x, old y)`.
|
|
60
|
+
|
|
61
|
+
## DOM updates
|
|
62
|
+
|
|
63
|
+
`render(result, container)` перевикористовує наявний template instance, коли
|
|
64
|
+
наступний render має ті самі template strings. Для child bindings, що
|
|
65
|
+
повертають вкладений `html```, діє те саме правило: ті самі strings оновлюються
|
|
66
|
+
на місці, інші strings перебудовують гілку.
|
|
67
|
+
|
|
68
|
+
Це означає, що непов'язані зміни signals не пересоздають `<input>` у
|
|
69
|
+
стабільному вкладеному template, тому focus, DOM state і listeners
|
|
70
|
+
зберігаються.
|
|
71
|
+
|
|
72
|
+
Списки повинні використовувати `each(items, key, renderItem)`. Keys задають DOM
|
|
73
|
+
identity. Duplicate keys попереджають у development і отримують positional
|
|
74
|
+
suffix, щоб кожен item все одно рендерився, але duplicate keys — це data bug.
|
|
75
|
+
|
|
76
|
+
## Teardown компонентів
|
|
77
|
+
|
|
78
|
+
Custom elements можуть отримати `disconnectedCallback()`, а потім
|
|
79
|
+
`connectedCallback()` під час same-tick move. Mado відкладає teardown компонента
|
|
80
|
+
до microtask і скасовує його при reconnect, тому keyed reorders зберігають
|
|
81
|
+
state компонента. Справжнє видалення все одно запускає lifecycle cleanup на
|
|
82
|
+
наступній microtask.
|
|
83
|
+
|
|
84
|
+
## Не гарантується
|
|
85
|
+
|
|
86
|
+
Mado не гарантує точну кількість internal scheduler microtasks, порядок
|
|
87
|
+
незалежних effects без спільних dependencies, форму generated bundle або
|
|
88
|
+
внутрішній module layout. Це implementation details.
|
|
89
|
+
|
|
90
|
+
Invariant tests для цього контракту:
|
|
91
|
+
|
|
92
|
+
- `test/reactivity-ordering.test.mjs`
|
|
93
|
+
- `test/signal-batch-equals.test.mjs`
|
|
94
|
+
- `test/update-nested-reuse.test.mjs`
|
|
95
|
+
- `test/each-component-state.test.mjs`
|