@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.
Files changed (298) hide show
  1. package/AGENTS.md +116 -32
  2. package/CHANGELOG.md +216 -1
  3. package/README.md +158 -173
  4. package/dist/src/component.d.ts +6 -3
  5. package/dist/src/component.d.ts.map +1 -0
  6. package/dist/src/component.js +163 -36
  7. package/dist/src/component.js.map +1 -1
  8. package/dist/src/context.d.ts +13 -32
  9. package/dist/src/context.d.ts.map +1 -0
  10. package/dist/src/context.js +73 -48
  11. package/dist/src/context.js.map +1 -1
  12. package/dist/src/css.d.ts +1 -0
  13. package/dist/src/css.d.ts.map +1 -0
  14. package/dist/src/css.js +34 -5
  15. package/dist/src/css.js.map +1 -1
  16. package/dist/src/devtools-hook.d.ts +14 -0
  17. package/dist/src/devtools-hook.d.ts.map +1 -0
  18. package/dist/src/devtools-hook.js +27 -0
  19. package/dist/src/devtools-hook.js.map +1 -0
  20. package/dist/src/devtools.d.ts +24 -22
  21. package/dist/src/devtools.d.ts.map +1 -0
  22. package/dist/src/devtools.js +230 -58
  23. package/dist/src/devtools.js.map +1 -1
  24. package/dist/src/diagnostics.d.ts +12 -0
  25. package/dist/src/diagnostics.d.ts.map +1 -0
  26. package/dist/src/diagnostics.js +68 -8
  27. package/dist/src/diagnostics.js.map +1 -1
  28. package/dist/src/each.d.ts +1 -5
  29. package/dist/src/each.d.ts.map +1 -0
  30. package/dist/src/each.js +0 -8
  31. package/dist/src/each.js.map +1 -1
  32. package/dist/src/forms.d.ts +27 -96
  33. package/dist/src/forms.d.ts.map +1 -0
  34. package/dist/src/forms.js +184 -394
  35. package/dist/src/forms.js.map +1 -1
  36. package/dist/src/head.d.ts +7 -6
  37. package/dist/src/head.d.ts.map +1 -0
  38. package/dist/src/head.js +8 -7
  39. package/dist/src/head.js.map +1 -1
  40. package/dist/src/html/bindings.d.ts +1 -0
  41. package/dist/src/html/bindings.d.ts.map +1 -0
  42. package/dist/src/html/bindings.js.map +1 -1
  43. package/dist/src/html/parser.d.ts +4 -5
  44. package/dist/src/html/parser.d.ts.map +1 -0
  45. package/dist/src/html/parser.js +64 -15
  46. package/dist/src/html/parser.js.map +1 -1
  47. package/dist/src/html/template-types.d.ts +1 -0
  48. package/dist/src/html/template-types.d.ts.map +1 -0
  49. package/dist/src/html/template-types.js.map +1 -1
  50. package/dist/src/html/template.d.ts +5 -1
  51. package/dist/src/html/template.d.ts.map +1 -0
  52. package/dist/src/html/template.js +102 -13
  53. package/dist/src/html/template.js.map +1 -1
  54. package/dist/src/index.d.ts +15 -12
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/index.js +18 -10
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/json.d.ts +3 -0
  59. package/dist/src/json.d.ts.map +1 -0
  60. package/dist/src/json.js +14 -0
  61. package/dist/src/json.js.map +1 -0
  62. package/dist/src/lifecycle.d.ts +1 -0
  63. package/dist/src/lifecycle.d.ts.map +1 -0
  64. package/dist/src/lifecycle.js +3 -2
  65. package/dist/src/lifecycle.js.map +1 -1
  66. package/dist/src/page.d.ts +63 -28
  67. package/dist/src/page.d.ts.map +1 -0
  68. package/dist/src/page.js +5 -0
  69. package/dist/src/page.js.map +1 -1
  70. package/dist/src/persisted.d.ts +6 -1
  71. package/dist/src/persisted.d.ts.map +1 -0
  72. package/dist/src/persisted.js +17 -11
  73. package/dist/src/persisted.js.map +1 -1
  74. package/dist/src/resource.d.ts +2 -1
  75. package/dist/src/resource.d.ts.map +1 -0
  76. package/dist/src/resource.js +128 -50
  77. package/dist/src/resource.js.map +1 -1
  78. package/dist/src/router/base.d.ts +66 -0
  79. package/dist/src/router/base.d.ts.map +1 -0
  80. package/dist/src/router/base.js +168 -0
  81. package/dist/src/router/base.js.map +1 -0
  82. package/dist/src/router/manifest.d.ts +1 -0
  83. package/dist/src/router/manifest.d.ts.map +1 -0
  84. package/dist/src/router/manifest.js +156 -171
  85. package/dist/src/router/manifest.js.map +1 -1
  86. package/dist/src/router/match.d.ts +17 -2
  87. package/dist/src/router/match.d.ts.map +1 -0
  88. package/dist/src/router/match.js +60 -6
  89. package/dist/src/router/match.js.map +1 -1
  90. package/dist/src/router/navigation.d.ts +1 -0
  91. package/dist/src/router/navigation.d.ts.map +1 -0
  92. package/dist/src/router/navigation.js +58 -9
  93. package/dist/src/router/navigation.js.map +1 -1
  94. package/dist/src/signal.d.ts +1 -0
  95. package/dist/src/signal.d.ts.map +1 -0
  96. package/dist/src/signal.js +46 -17
  97. package/dist/src/signal.js.map +1 -1
  98. package/dist/src/static-runtime.d.ts +82 -0
  99. package/dist/src/static-runtime.d.ts.map +1 -0
  100. package/dist/src/static-runtime.js +209 -0
  101. package/dist/src/static-runtime.js.map +1 -0
  102. package/dist/src/vite/index.d.ts +32 -3
  103. package/dist/src/vite/index.d.ts.map +1 -0
  104. package/dist/src/vite/index.js +49 -17
  105. package/dist/src/vite/index.js.map +1 -1
  106. package/docs/README.md +5 -8
  107. package/docs/architecture/adr/0001-browser-static-snapshots.md +132 -0
  108. package/docs/architecture/v1-roadmap.md +19 -0
  109. package/docs/en/00-the-mado-way.md +1 -1
  110. package/docs/en/01-quickstart.md +183 -0
  111. package/docs/en/10-pages-and-components.md +279 -0
  112. package/docs/en/11-templates-and-signals.md +211 -0
  113. package/docs/en/12-routing.md +229 -0
  114. package/docs/en/13-data.md +227 -0
  115. package/docs/en/14-forms.md +91 -0
  116. package/docs/en/15-static-snapshots.md +181 -0
  117. package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +56 -4
  118. package/docs/en/{13-deployment.md → 20-deployment.md} +79 -17
  119. package/docs/en/{15-error-handling.md → 21-error-handling.md} +9 -6
  120. package/docs/en/{14-testing.md → 22-testing.md} +6 -5
  121. package/docs/en/23-cookbook.md +292 -0
  122. package/docs/en/24-devtools-and-diagnostics.md +45 -0
  123. package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +17 -9
  124. package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +5 -5
  125. package/docs/en/33-migration-0.12-0.13.md +56 -0
  126. package/docs/en/40-llm-guide.md +774 -0
  127. package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +28 -25
  128. package/docs/en/{05-why-mado.md → 42-why-mado.md} +3 -3
  129. package/docs/en/README.md +99 -27
  130. package/docs/recipes/nginx/Containerfile +26 -0
  131. package/docs/recipes/nginx/nginx.conf +42 -0
  132. package/llms.txt +263 -210
  133. package/package.json +51 -23
  134. package/scripts/clean.mjs +8 -0
  135. package/scripts/cli/generate.mjs +57 -10
  136. package/scripts/cli/help.mjs +13 -5
  137. package/scripts/cli/index.mjs +31 -7
  138. package/scripts/cli/init.mjs +19 -19
  139. package/scripts/cli/release.mjs +38 -83
  140. package/scripts/cli/run.mjs +5 -3
  141. package/scripts/logger.mjs +117 -0
  142. package/scripts/output-guard.mjs +128 -0
  143. package/scripts/preview.mjs +105 -24
  144. package/scripts/static/browser.mjs +695 -0
  145. package/scripts/static/discover.mjs +284 -0
  146. package/scripts/static/output.mjs +141 -0
  147. package/scripts/static/serialize.mjs +212 -0
  148. package/scripts/static/server.mjs +167 -0
  149. package/scripts/static.mjs +205 -0
  150. package/starters/default/README.md +57 -55
  151. package/starters/default/package.json +6 -12
  152. package/starters/default/src/app.routes.ts +20 -33
  153. package/starters/default/src/components/feature-card.component.ts +40 -0
  154. package/starters/default/src/components/live-counter.component.ts +57 -0
  155. package/starters/default/src/content/guides.ts +55 -0
  156. package/starters/default/src/main.ts +9 -13
  157. package/starters/default/src/pages/app.page.ts +41 -0
  158. package/starters/default/src/pages/guide.page.ts +40 -0
  159. package/starters/default/src/pages/home.page.ts +49 -0
  160. package/starters/default/src/pages/not-found.page.ts +18 -0
  161. package/starters/default/src/styles/document.css +39 -0
  162. package/starters/default/src/styles/reset.css +37 -0
  163. package/starters/default/src/styles/tokens.css +30 -0
  164. package/starters/default/src/styles.d.ts +3 -1
  165. package/starters/default/src/vite-env.d.ts +1 -1
  166. package/starters/default/tsconfig.node.json +13 -0
  167. package/starters/default/vite.config.ts +14 -4
  168. package/starters/modular/README.md +142 -0
  169. package/starters/modular/index.html +13 -0
  170. package/starters/modular/package.json +30 -0
  171. package/starters/modular/src/app.routes.ts +39 -0
  172. package/starters/{default → modular}/src/layouts/app-shell.layout.ts +9 -7
  173. package/starters/{default → modular}/src/layouts/auth-shell.layout.ts +3 -3
  174. package/starters/modular/src/main.ts +16 -0
  175. package/starters/{default → modular}/src/modules/auth/login.page.ts +5 -12
  176. package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +3 -4
  177. package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +4 -4
  178. package/starters/{default → modular}/src/modules/home/home.page.ts +5 -7
  179. package/starters/modular/src/modules/home/not-found.page.ts +14 -0
  180. package/starters/{default → modular}/src/shared/styles/reset.css +21 -1
  181. package/starters/modular/src/styles.d.ts +1 -0
  182. package/starters/modular/src/vite-env.d.ts +1 -0
  183. package/starters/modular/tsconfig.json +33 -0
  184. package/starters/modular/tsconfig.node.json +13 -0
  185. package/starters/modular/vite.config.ts +132 -0
  186. package/TODO.md +0 -79
  187. package/dist/src/lazy.d.ts +0 -38
  188. package/dist/src/lazy.js +0 -73
  189. package/dist/src/lazy.js.map +0 -1
  190. package/docs/en/01-routing.md +0 -152
  191. package/docs/en/02-project-layout.md +0 -124
  192. package/docs/en/03-static-bake.md +0 -249
  193. package/docs/en/04-ide-setup.md +0 -162
  194. package/docs/en/07-llm-pitfalls.md +0 -724
  195. package/docs/en/08-llm-zero-history-test.md +0 -56
  196. package/docs/en/09-shadow-vs-light-dom.md +0 -174
  197. package/docs/en/11-layouts.md +0 -113
  198. package/docs/en/12-auth-and-api.md +0 -124
  199. package/docs/en/16-bake-cookbook.md +0 -100
  200. package/docs/en/17-shadow-dom-forms.md +0 -192
  201. package/docs/fr/00-the-mado-way.md +0 -85
  202. package/docs/fr/01-routing.md +0 -120
  203. package/docs/fr/02-project-layout.md +0 -84
  204. package/docs/fr/03-static-bake.md +0 -289
  205. package/docs/fr/04-ide-setup.md +0 -162
  206. package/docs/fr/05-why-mado.md +0 -193
  207. package/docs/fr/06-for-backenders.md +0 -438
  208. package/docs/fr/07-llm-pitfalls.md +0 -625
  209. package/docs/fr/08-llm-zero-history-test.md +0 -38
  210. package/docs/fr/09-shadow-vs-light-dom.md +0 -65
  211. package/docs/fr/10-app-architecture.md +0 -138
  212. package/docs/fr/11-layouts.md +0 -47
  213. package/docs/fr/12-auth-and-api.md +0 -76
  214. package/docs/fr/13-deployment.md +0 -57
  215. package/docs/fr/14-testing.md +0 -41
  216. package/docs/fr/15-error-handling.md +0 -50
  217. package/docs/fr/16-bake-cookbook.md +0 -88
  218. package/docs/fr/17-shadow-dom-forms.md +0 -196
  219. package/docs/fr/18-api-freeze-map.md +0 -63
  220. package/docs/fr/19-reactivity-ordering.md +0 -97
  221. package/docs/fr/20-v1-stability.md +0 -88
  222. package/docs/fr/README.md +0 -27
  223. package/docs/ru/00-the-mado-way.md +0 -84
  224. package/docs/ru/01-routing.md +0 -119
  225. package/docs/ru/02-project-layout.md +0 -84
  226. package/docs/ru/03-static-bake.md +0 -250
  227. package/docs/ru/04-ide-setup.md +0 -144
  228. package/docs/ru/05-why-mado.md +0 -193
  229. package/docs/ru/06-for-backenders.md +0 -428
  230. package/docs/ru/07-llm-pitfalls.md +0 -624
  231. package/docs/ru/08-llm-zero-history-test.md +0 -57
  232. package/docs/ru/09-shadow-vs-light-dom.md +0 -63
  233. package/docs/ru/10-app-architecture.md +0 -152
  234. package/docs/ru/11-layouts.md +0 -47
  235. package/docs/ru/12-auth-and-api.md +0 -75
  236. package/docs/ru/13-deployment.md +0 -66
  237. package/docs/ru/14-testing.md +0 -50
  238. package/docs/ru/15-error-handling.md +0 -56
  239. package/docs/ru/16-bake-cookbook.md +0 -95
  240. package/docs/ru/17-shadow-dom-forms.md +0 -193
  241. package/docs/ru/18-api-freeze-map.md +0 -64
  242. package/docs/ru/19-reactivity-ordering.md +0 -95
  243. package/docs/ru/20-v1-stability.md +0 -82
  244. package/docs/ru/README.md +0 -25
  245. package/docs/uk/00-the-mado-way.md +0 -82
  246. package/docs/uk/01-routing.md +0 -76
  247. package/docs/uk/02-project-layout.md +0 -73
  248. package/docs/uk/03-static-bake.md +0 -48
  249. package/docs/uk/04-ide-setup.md +0 -26
  250. package/docs/uk/05-why-mado.md +0 -34
  251. package/docs/uk/06-for-backenders.md +0 -55
  252. package/docs/uk/07-llm-pitfalls.md +0 -145
  253. package/docs/uk/08-llm-zero-history-test.md +0 -34
  254. package/docs/uk/09-shadow-vs-light-dom.md +0 -58
  255. package/docs/uk/10-app-architecture.md +0 -97
  256. package/docs/uk/11-layouts.md +0 -47
  257. package/docs/uk/12-auth-and-api.md +0 -70
  258. package/docs/uk/13-deployment.md +0 -40
  259. package/docs/uk/14-testing.md +0 -34
  260. package/docs/uk/15-error-handling.md +0 -32
  261. package/docs/uk/16-bake-cookbook.md +0 -36
  262. package/docs/uk/17-shadow-dom-forms.md +0 -193
  263. package/docs/uk/18-api-freeze-map.md +0 -61
  264. package/docs/uk/19-reactivity-ordering.md +0 -95
  265. package/docs/uk/20-v1-stability.md +0 -83
  266. package/docs/uk/README.md +0 -27
  267. package/scripts/bake.mjs +0 -570
  268. package/scripts/package-smoke.mjs +0 -77
  269. package/scripts/release-notes.mjs +0 -66
  270. package/scripts/size-budget.mjs +0 -91
  271. package/starters/default/src/modules/home/not-found.page.ts +0 -11
  272. package/starters/default/src/shared/ui/x-button.component.ts +0 -49
  273. /package/docs/en/{19-reactivity-ordering.md → 31-reactivity-ordering.md} +0 -0
  274. /package/starters/{default → modular}/.editorconfig +0 -0
  275. /package/starters/{default → modular}/eslint.config.mjs +0 -0
  276. /package/starters/{default → modular}/public/favicon.svg +0 -0
  277. /package/starters/{default → modular}/src/modules/auth/_contracts/auth-api.types.ts +0 -0
  278. /package/starters/{default → modular}/src/modules/auth/auth.connector.ts +0 -0
  279. /package/starters/{default → modular}/src/modules/auth/auth.guard.ts +0 -0
  280. /package/starters/{default → modular}/src/modules/auth/auth.public.ts +0 -0
  281. /package/starters/{default → modular}/src/modules/auth/auth.routes.ts +0 -0
  282. /package/starters/{default → modular}/src/modules/auth/auth.service.ts +0 -0
  283. /package/starters/{default → modular}/src/modules/auth/auth.types.ts +0 -0
  284. /package/starters/{default → modular}/src/modules/billing/_contracts/stripe.types.ts +0 -0
  285. /package/starters/{default → modular}/src/modules/billing/api/stripe.connector.ts +0 -0
  286. /package/starters/{default → modular}/src/modules/billing/billing.public.ts +0 -0
  287. /package/starters/{default → modular}/src/modules/billing/billing.routes.ts +0 -0
  288. /package/starters/{default → modular}/src/modules/billing/billing.types.ts +0 -0
  289. /package/starters/{default → modular}/src/modules/billing/components/invoice-status-badge.component.ts +0 -0
  290. /package/starters/{default → modular}/src/modules/billing/data/invoices.resource.ts +0 -0
  291. /package/starters/{default → modular}/src/shared/http/http-client.ts +0 -0
  292. /package/starters/{default → modular}/src/shared/http/http-error.ts +0 -0
  293. /package/starters/{default → modular}/src/shared/http/interceptors.ts +0 -0
  294. /package/starters/{default → modular}/src/shared/lib/format-date.ts +0 -0
  295. /package/starters/{default → modular}/src/shared/styles/content.css +0 -0
  296. /package/starters/{default → modular}/src/shared/styles/shell.css +0 -0
  297. /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
  298. /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-фреймворк.
@@ -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-кодом, всё компилируется и запускается. Просто строка внутри подсвечивается как строка.