@madojs/mado 0.10.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/CHANGELOG.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.10.1 - 2026-06-12
6
+
7
+ Patch release for the production bake/preview path.
8
+
9
+ ### Fixed
10
+
11
+ - **Baked pages now use the production bundle during `mado release`.** Release
12
+ now bakes from the already bundled `out/index.html`, so baked HTML references
13
+ `/assets/main-*.js` instead of the dev-only `/dist/main.js`.
14
+
15
+ - **Client boot no longer appends pages below baked HTML.** `mado bake` marks
16
+ the app container with `data-mado-baked`, and runtime `render()` replaces
17
+ that marked static DOM with live bindings on startup. This keeps bake as a
18
+ static first-paint/SEO shell, not SSR hydration, while avoiding duplicate
19
+ landing + route content after navigation.
20
+
21
+ - **`mado preview` now serves the final `out/` artifact exactly.** Release
22
+ promotes baked HTML into real route paths inside `out/`, and preview no
23
+ longer applies a preview-only virtual mapping from `out/baked/`.
24
+
25
+ - **`sitemap.xml` is promoted to the site root during release.** Standalone
26
+ `mado bake` still writes `out/baked/sitemap.xml`; `mado release` also copies
27
+ it to `out/sitemap.xml`, where static hosts and crawlers expect it.
28
+
29
+ ### Docs
30
+
31
+ - Updated EN/RU/FR bake, deployment and project-layout docs to describe the
32
+ production release artifact, promoted baked routes, root sitemap and
33
+ `data-mado-baked` client boot behavior.
34
+
5
35
  ## 0.10.0 - 2026-06-12
6
36
 
7
37
  Surface-cleanup and API-lock release from the v1 tracker Phase B: legacy public
package/README.md CHANGED
@@ -272,7 +272,7 @@ PoC in [`examples/cloudflare`](./examples/cloudflare/).
272
272
  ## Production
273
273
 
274
274
  ```bash
275
- mado release # typecheck + build + bundle + bake + copy public -> out/
275
+ mado release # typecheck + build + bundle + bake + promote baked HTML + copy public -> out/
276
276
  mado preview # serve out/ like a static host
277
277
  ```
278
278
 
@@ -109,6 +109,16 @@ export function render(result, container) {
109
109
  }
110
110
  existing.dispose();
111
111
  }
112
+ // `mado bake` writes static first-paint markup into #app and marks the
113
+ // container. That markup is not hydrated: once the client app starts, Mado
114
+ // owns the container again and replaces the baked DOM with live bindings.
115
+ const isBakedContainer = !existing &&
116
+ "hasAttribute" in container &&
117
+ container.hasAttribute("data-mado-baked");
118
+ if (isBakedContainer) {
119
+ container.replaceChildren();
120
+ container.removeAttribute("data-mado-baked");
121
+ }
112
122
  if (!existing && container.childNodes.length > 0) {
113
123
  warnOnce("render-unmanaged-dom", "render() called on a container with existing DOM that was not created by Mado. It will remain alongside the new render output.");
114
124
  }
@@ -1 +1 @@
1
- {"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/html/template.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/html/template.ts"],"names":[],"mappings":"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"}
@@ -36,7 +36,8 @@ One-liner to remember:
36
36
  > Develop with `mado dev`. To ship: run `mado release`, then upload `out/`.
37
37
 
38
38
  `mado release` = `typecheck` + `build` (tsc → `dist/`) + `bundle` (esbuild
39
- → `out/assets/`) + `bake` (HTML → `out/baked/`) + copy `public/*` `out/`.
39
+ → `out/assets/`) + `bake` (HTML → `out/baked/`) + promote baked HTML and
40
+ `sitemap.xml` into deployable `out/` paths + copy `public/*` → `out/`.
40
41
 
41
42
  You almost never need to look inside `dist/`. It exists so the dev browser can
42
43
  load native ESM modules without a bundler during development. In production
@@ -114,4 +115,4 @@ Precedence: built-in defaults < `mado.config.json` < CLI flags
114
115
  `process.env` and import that one module everywhere.
115
116
  - ❌ Tests mixed with code — put them in `test/`.
116
117
  - ❌ `examples/` folder — the framework repository has examples, your app
117
- does not need one.
118
+ does not need one.
@@ -12,9 +12,11 @@ out/
12
12
  ├── assets/ ← hashed bundles (main-ABC.js, chunk-XYZ.js, …)
13
13
  │ ├── *.gz ← precompressed gzip (gzip_static / Accept-Encoding)
14
14
  │ └── *.br ← precompressed brotli (brotli_static / Accept-Encoding)
15
- ├── baked/ ← prerendered SEO HTML (mado bake)
15
+ ├── baked/ ← bake output copy for inspection/debugging
16
16
  │ ├── <route>/index.html
17
17
  │ └── sitemap.xml
18
+ ├── <route>/index.html ← prerendered SEO HTML promoted for static hosts
19
+ ├── sitemap.xml ← sitemap promoted to the site root
18
20
  ├── favicon.svg ← your public/ assets copied verbatim
19
21
  ├── _redirects ← Cloudflare Pages / Netlify SPA fallback
20
22
  └── _headers ← Cloudflare Pages / Netlify cache rules
@@ -31,9 +33,9 @@ mado release
31
33
  mado preview # http://localhost:4173 — serves out/ exactly as a static host would
32
34
  ```
33
35
 
34
- `mado preview` mirrors the behavior described below: it picks `.br` over
35
- `.gz` over raw, prefers baked HTML at `/<route>/`, and falls back to
36
- `index.html` for unknown paths.
36
+ `mado preview` serves the final `out/` directory like a static host: it picks
37
+ `.br` over `.gz` over raw, serves promoted baked HTML when a route has an
38
+ `index.html`, and falls back to `index.html` for unknown SPA paths.
37
39
 
38
40
  ---
39
41
 
@@ -79,8 +81,9 @@ npx wrangler pages deploy out --project-name=myapp
79
81
  - The generated `_redirects` (`/* /index.html 200`) gives you SPA fallback.
80
82
  - The generated `_headers` (immutable cache for `/assets/*`, `no-cache` for
81
83
  HTML) is honored by CF Pages.
82
- - Baked routes (`out/baked/<route>/index.html`) take priority over the SPA
83
- fallback because CF Pages matches static files first.
84
+ - Baked routes are promoted to real route files (`out/<route>/index.html`),
85
+ so they take priority over the SPA fallback because CF Pages matches static
86
+ files first.
84
87
 
85
88
  For preview branches, set the same build command in the CF Pages project:
86
89
 
@@ -189,4 +192,4 @@ jobs:
189
192
 
190
193
  See also: [`02-project-layout.md`](./02-project-layout.md) for the
191
194
  `src/`/`dist/`/`public/`/`out/` model and [`03-static-bake.md`](./03-static-bake.md)
192
- for the SEO bake mechanics.
195
+ for the SEO bake mechanics.
@@ -55,8 +55,10 @@ export default routes(manifest);
55
55
 
56
56
  ## Output
57
57
 
58
- By default app-mode writes baked pages under `out/baked/`. `mado release`
59
- produces the final deploy artifact:
58
+ By default standalone `mado bake` writes baked pages under `out/baked/`.
59
+ `mado release` uses the bundled production shell, keeps that `out/baked/` copy
60
+ for inspection, promotes baked HTML into real route paths inside `out/`, and
61
+ copies the generated sitemap to `out/sitemap.xml`:
60
62
 
61
63
  ```bash
62
64
  mado release
@@ -65,6 +67,12 @@ tree out
65
67
 
66
68
  The deployable folder is `out/`, not `dist/`.
67
69
 
70
+ ## Client boot
71
+
72
+ Baked HTML marks `#app` with `data-mado-baked`. This is not hydration: when the
73
+ client app starts, `render()` replaces the baked DOM with live bindings. The
74
+ first response contains SEO/first-paint HTML, then the SPA takes over normally.
75
+
68
76
  ## Unsupported values
69
77
 
70
78
  Bake intentionally fails loudly instead of writing `[object Object]`. If a baked
@@ -4,20 +4,14 @@ Chaque nouveau projet Mado a la même structure. C'est une convention **obligato
4
4
 
5
5
  ```
6
6
  my-app/
7
- ├── package.json # exactement 1 dép : typescript (esbuild optionnel)
8
- ├── tsconfig.json # avec paths "@madojs/mado" import sans chemins relatifs
9
- ├── Dockerfile + nginx.conf # copiés depuis Mado/ lors du scaffold
10
- ├── .gitlab-ci.yml | .github/workflows/ci.yml
11
- ├── server/serve.mjs # dev-server de Mado, sans dépendances
12
- ├── scripts/
13
- │ ├── bundle.mjs # bundle de production esbuild
14
- │ └── new.mjs # générateur de pages
15
- ├── templates/ # templates pour new.mjs
16
- ├── docs/ # documentation du projet (vous pouvez copier nos guides)
17
- ├── public/ # assets statiques (favicon, manifests)
7
+ ├── package.json # runtime dep: @madojs/mado
8
+ ├── tsconfig.json # strict TS, ES2022, Bundler resolution
9
+ ├── mado.config.json # configuration dev/build/bake/bundle
10
+ ├── index.html # shell SPA et template pour bake
11
+ ├── public/ # assets statiques (favicon, images, robots.txt)
18
12
  └── src/
19
- ├── main.ts # entrée : providers + montage de <x-app>
20
- ├── routes.ts # manifeste de routes
13
+ ├── main.ts # entrée : mount router dans #app
14
+ ├── routes.ts # route manifest (default + named manifest)
21
15
  ├── pages/ # une page = un fichier = `export default page({...})`
22
16
  ├── components/ # composants réutilisables (x-*)
23
17
  ├── layouts/ # pages de layout (pour nested)
@@ -28,6 +22,19 @@ my-app/
28
22
  └── ... # utilitaires, types, règles métier
29
23
  ```
30
24
 
25
+ ## États Des Artefacts
26
+
27
+ | Dossier | Ce que c'est | Écrit par | Déployer ? |
28
+ |---|---|---|---|
29
+ | `src/` | sources TypeScript | vous | non |
30
+ | `dist/` | output `tsc`, native ESM pour dev | `mado build` | non |
31
+ | `public/` | assets statiques écrits par vous | vous | via `out/` |
32
+ | `out/` | artefact déployable : shell SPA + bundles + HTML baked promu | `mado release` | oui |
33
+
34
+ `mado release` = `typecheck` + `build` (`dist/`) + `bundle`
35
+ (`out/assets/`) + `bake` (`out/baked/`) + promotion du HTML baked et de
36
+ `sitemap.xml` dans les chemins déployables de `out/` + copie de `public/*`.
37
+
31
38
  ## Où mettre un nouveau fichier ?
32
39
 
33
40
  | Quoi | Où |
@@ -1,6 +1,6 @@
1
1
  # Déploiement
2
2
 
3
- Une commande, un artefact :
3
+ Une commande, un artefact déployable :
4
4
 
5
5
  ```bash
6
6
  mado release
@@ -10,30 +10,51 @@ Résultat :
10
10
 
11
11
  ```txt
12
12
  out/
13
- ├── index.html
14
- ├── assets/
15
- ├── baked/
16
- ├── _redirects
17
- └── _headers
13
+ ├── index.html ← shell SPA ou HTML baked promu pour /
14
+ ├── assets/ ← bundles hashés (main-ABC.js, chunk-XYZ.js, ...)
15
+ ├── *.gz ← gzip précompressé
16
+ │ └── *.br ← brotli précompressé
17
+ ├── baked/ ← copie de bake pour inspection/debugging
18
+ │ ├── <route>/index.html
19
+ │ └── sitemap.xml
20
+ ├── <route>/index.html ← HTML baked promu pour les hébergeurs statiques
21
+ ├── sitemap.xml ← sitemap à la racine du site
22
+ ├── _redirects ← fallback SPA Cloudflare Pages / Netlify
23
+ └── _headers ← règles de cache
18
24
  ```
19
25
 
20
26
  Déploie `out/` sur nginx, Cloudflare Pages, Netlify, S3/CloudFront ou GitHub
21
- Pages.
27
+ Pages. Ne déploie pas `dist/` : c'est un output interne.
28
+
29
+ ## Preview
22
30
 
23
31
  ```bash
24
32
  mado release
25
33
  mado preview
26
34
  ```
27
35
 
28
- `mado preview` sert `out/` comme un hébergeur statique : HTML baked d'abord,
29
- fallback SPA ensuite.
36
+ `mado preview` sert le `out/` final comme un hébergeur statique : fichiers réels
37
+ d'abord (`/<route>/index.html` si la route est baked), fallback SPA ensuite.
38
+ Preview ne fait plus de mapping virtuel depuis `out/baked/`, donc il vérifie
39
+ exactement ce qui sera déployé.
30
40
 
31
- Pour VPS + nginx :
41
+ ## VPS + nginx
32
42
 
33
43
  ```bash
34
44
  mado release
35
45
  rsync -avz --delete out/ user@server:/var/www/myapp/
36
46
  ```
37
47
 
38
- Le `nginx.conf` fourni gère cache immutable pour les bundles hashés, no-cache
39
- pour HTML, et fallback SPA pour les deep links.
48
+ Le `nginx.conf` fourni gère le cache immutable pour les bundles hashés, no-cache
49
+ pour HTML, et le fallback SPA pour les deep links.
50
+
51
+ ## Cloudflare / Netlify
52
+
53
+ ```bash
54
+ mado release
55
+ npx wrangler pages deploy out --project-name=myapp
56
+ ```
57
+
58
+ `_redirects` et `_headers` sont générés automatiquement si tu n'en fournis pas.
59
+ Les routes baked sont promues en vrais fichiers (`out/<route>/index.html`), donc
60
+ l'hébergeur statique les sert avant le fallback SPA.
@@ -1,8 +1,10 @@
1
- # Bake cookbook
1
+ # Bake Cookbook
2
2
 
3
3
  `mado bake` rend certaines routes en HTML statique. C'est pour le SEO et un
4
4
  premier rendu rapide, pas pour SSR + hydration.
5
5
 
6
+ ## Page Minimale
7
+
6
8
  ```ts
7
9
  export default page({
8
10
  head: () => ({ title: "Products", description: "Catalog" }),
@@ -21,15 +23,66 @@ export default page({
21
23
  ```
22
24
 
23
25
  Dans les vues baked, préfère les tableaux simples (`items.map(...)`). Les
24
- directives runtime comme `each()` sont pour le navigateur.
26
+ directives runtime comme keyed `each()` sont pour le navigateur.
27
+
28
+ ## Routes Dynamiques
29
+
30
+ ```ts
31
+ export default page<{ slug: string }>({
32
+ head: ({ slug }, data) => ({ title: data.title, canonical: `/blog/${slug}` }),
33
+ view: ({ data }) => html`<article>${unsafeHTML(data.html)}</article>`,
34
+ bake: {
35
+ paths: async () => (await api.posts()).map((p) => ({ slug: p.slug })),
36
+ data: ({ slug }) => api.post(slug),
37
+ },
38
+ });
39
+ ```
40
+
41
+ Utilise `unsafeHTML()` seulement pour du HTML fiable ou déjà nettoyé.
42
+
43
+ ## Route Manifest
44
+
45
+ `mado bake` a besoin du manifest source :
25
46
 
26
47
  ```ts
27
48
  export const manifest = {
28
49
  "/": () => import("./pages/home.js"),
29
50
  "/blog/:slug": () => import("./pages/blog-post.js"),
30
51
  };
52
+
31
53
  export default routes(manifest);
32
54
  ```
33
55
 
34
- `mado release` produit l'artefact déployable dans `out/`. Si bake signale une
35
- valeur non supportée, remplace la partie runtime-only ou rends-la côté client.
56
+ ## Output
57
+
58
+ Standalone `mado bake` écrit les pages baked dans `out/baked/` par défaut.
59
+ `mado release` utilise le shell de production bundlé, garde cette copie
60
+ `out/baked/` pour inspection, promeut le HTML dans les vrais chemins de route
61
+ dans `out/`, et copie le sitemap vers `out/sitemap.xml`.
62
+
63
+ ```bash
64
+ mado release
65
+ tree out
66
+ ```
67
+
68
+ Le dossier déployable est `out/`, pas `dist/`.
69
+
70
+ ## Client Boot
71
+
72
+ Le HTML baked marque `#app` avec `data-mado-baked`. Ce n'est pas de
73
+ l'hydration : au démarrage, `render()` remplace le DOM baked par les bindings
74
+ vivants de l'application.
75
+
76
+ ## Valeurs Non Supportées
77
+
78
+ Bake échoue volontairement au lieu d'écrire `[object Object]`. Si une vue baked
79
+ signale une directive non supportée :
80
+
81
+ - remplace `each()` par `items.map(...)` dans le markup baked ;
82
+ - garde les widgets interactifs dans des routes client-only ;
83
+ - assure-toi que chaque valeur peut être sérialisée en HTML statique.
84
+
85
+ ## Canonical Links
86
+
87
+ Passe `--base-url` ou configure `bake.baseUrl` dans `mado.config.json` pour que
88
+ les liens canonical et le sitemap pointent vers la production.
@@ -1,23 +1,17 @@
1
- # Project layout
1
+ # Project Layout
2
2
 
3
3
  Каждый new-проект на Mado имеет одинаковую структуру. Это **обязательное** соглашение.
4
4
 
5
5
  ```
6
6
  my-app/
7
- ├── package.json # ровно 1 dep: typescript (esbuild опц.)
8
- ├── tsconfig.json # с paths "@madojs/mado" импорт без относительных путей
9
- ├── Dockerfile + nginx.conf # копируем из Mado/ при scaffold
10
- ├── .gitlab-ci.yml | .github/workflows/ci.yml
11
- ├── server/serve.mjs # dev-сервер из Mado, без deps
12
- ├── scripts/
13
- │ ├── bundle.mjs # esbuild прод-бандл
14
- │ └── new.mjs # скаффолд страницы
15
- ├── templates/ # шаблоны для new.mjs
16
- ├── docs/ # проектные доки (можно копировать наши гайды)
17
- ├── public/ # статика (favicon, манифесты)
7
+ ├── package.json # runtime dep: @madojs/mado
8
+ ├── tsconfig.json # strict TS, ES2022, Bundler resolution
9
+ ├── mado.config.json # dev/build/bake/bundle config
10
+ ├── index.html # SPA shell и template для bake
11
+ ├── public/ # статика (favicon, images, robots.txt)
18
12
  └── src/
19
- ├── main.ts # точка входа: провайдеры + монтаж <x-app>
20
- ├── routes.ts # манифест роутов
13
+ ├── main.ts # точка входа: mount router в #app
14
+ ├── routes.ts # route manifest (default + named manifest)
21
15
  ├── pages/ # одна страница = один файл = `export default page({...})`
22
16
  ├── components/ # переиспользуемые компоненты (x-*)
23
17
  ├── layouts/ # layout-страницы (для nested)
@@ -28,6 +22,19 @@ my-app/
28
22
  └── ... # утилиты, типы, бизнес-правила
29
23
  ```
30
24
 
25
+ ## Artifact States
26
+
27
+ | Folder | Что это | Кто пишет | Deploy? |
28
+ |---|---|---|---|
29
+ | `src/` | исходники TypeScript | ты | no |
30
+ | `dist/` | output `tsc`, native ESM для dev | `mado build` | no |
31
+ | `public/` | авторская статика | ты | через `out/` |
32
+ | `out/` | deploy artifact: SPA shell + bundles + promoted baked HTML | `mado release` | yes |
33
+
34
+ `mado release` = `typecheck` + `build` (`dist/`) + `bundle`
35
+ (`out/assets/`) + `bake` (`out/baked/`) + promote baked HTML и
36
+ `sitemap.xml` в deployable `out/` paths + copy `public/*`.
37
+
31
38
  ## Куда положить новый файл?
32
39
 
33
40
  | Что | Куда |
@@ -54,4 +61,4 @@ my-app/
54
61
 
55
62
  - ❌ Конфиги билдеров (webpack, rollup, vite) — у нас их нет.
56
63
  - ❌ `.env`-файлы — env читается из `process.env`/`import.meta.env` в `lib/config.ts`.
57
- - ❌ Тесты вперемешку с кодом — все в `test/`.
64
+ - ❌ Тесты вперемешку с кодом — все в `test/`.
@@ -1,6 +1,6 @@
1
1
  # Deployment
2
2
 
3
- Один command, один artifact:
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
- ├── baked/
16
- ├── _redirects
17
- └── _headers
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/` как статический хост: baked HTML имеет приоритет,
31
- а неизвестные пути падают в SPA fallback.
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
- immutably, HTML идет с `no-cache`, deep links работают через SPA fallback.
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 rules
62
+ ## Cache Rules
53
63
 
54
64
  | Path | Cache-Control |
55
65
  |---|---|
@@ -1,9 +1,9 @@
1
- # Bake cookbook
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 routes
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 и output
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
- `mado release` создает deploy artifact в `out/`. Если bake ругается на
54
- unsupported value, значит в статическую страницу попало runtime-only значение:
55
- замени `each()` на `items.map(...)` или вынеси интерактивный кусок в клиент.
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madojs/mado",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Mado — a calm browser-native SPA framework for internal tools, admin panels and business apps. Routing, forms, state and data fetching without frontend infrastructure overhead.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/scripts/bake.mjs CHANGED
@@ -533,7 +533,10 @@ function buildHtml({ template, bodyHtml, head, bakedData, revalidate, canonical
533
533
  }
534
534
 
535
535
  const app = document.getElementById("app");
536
- if (app) app.innerHTML = bodyHtml;
536
+ if (app) {
537
+ app.setAttribute("data-mado-baked", "");
538
+ app.innerHTML = bodyHtml;
539
+ }
537
540
 
538
541
  return "<!doctype html>\n" + document.documentElement.outerHTML;
539
542
  }
package/scripts/cli.mjs CHANGED
@@ -241,15 +241,28 @@ async function runRelease(rawArgs) {
241
241
  // → rm -rf out/ (unless --no-clean)
242
242
  // → mado typecheck
243
243
  // → mado build (tsc → dist/)
244
- // → mado bundle (esbuild → out/assets/, also copies index.html)
245
- // → mado bake (HTML → out/baked/)
244
+ // → mado bundle (esbuild → out/assets/, also writes out/index.html)
245
+ // → mado bake (HTML → out/baked/, using bundled out/index.html)
246
246
  // → copy public/* → out/
247
+ // → promote baked HTML + sitemap into out/ route paths
247
248
  //
248
249
  // Flags are forwarded to bake/bundle.
249
250
  const { flags: releaseFlags } = parseFlags(rawArgs);
250
251
  const cfg = loadConfig({ projectRoot: PROJECT_ROOT });
251
- const outDir = resolve(cfg.projectRoot, cfg.build.out ?? "out");
252
+ const outDir = resolve(
253
+ cfg.projectRoot,
254
+ typeof releaseFlags.out === "string" ? releaseFlags.out : cfg.build.out ?? "out",
255
+ );
252
256
  const publicDir = resolve(cfg.projectRoot, cfg.build.publicDir ?? "public");
257
+ const bundledHtml = join(outDir, "index.html");
258
+ const bakedDir = resolve(
259
+ cfg.projectRoot,
260
+ cfg.bake.outDir ??
261
+ join(
262
+ typeof releaseFlags.out === "string" ? releaseFlags.out : cfg.build.out ?? "out",
263
+ "baked",
264
+ ),
265
+ );
253
266
 
254
267
  console.log(`[release] context: ${cfg.context}`);
255
268
  console.log(`[release] artifact: ${outDir}`);
@@ -276,8 +289,14 @@ async function runRelease(rawArgs) {
276
289
  console.log("[release] step 3/5 bundle (esbuild → out/assets/)");
277
290
  await runNodeScript("scripts/bundle.mjs", rawArgs);
278
291
 
279
- console.log("[release] step 4/5 bake (out/baked/)");
280
- await runNodeScript("scripts/bake.mjs", rawArgs);
292
+ console.log("[release] step 4/5 bake (out/baked/, bundled shell)");
293
+ await runNodeScript("scripts/bake.mjs", [
294
+ ...rawArgs,
295
+ "--template",
296
+ bundledHtml,
297
+ "--out",
298
+ bakedDir,
299
+ ]);
281
300
 
282
301
  console.log("[release] step 5/5 copy public/ → out/");
283
302
  if (existsSync(publicDir)) {
@@ -288,6 +307,14 @@ async function runRelease(rawArgs) {
288
307
  console.log(`[release] no ${publicDir}, skipping`);
289
308
  }
290
309
 
310
+ const promoted = await promoteBakedHtml(bakedDir, outDir);
311
+ if (promoted.html > 0) {
312
+ console.log(`[release] promoted ${promoted.html} baked HTML page(s) into out/`);
313
+ }
314
+ if (promoted.sitemap) {
315
+ console.log(`[release] copied sitemap.xml → ${join(outDir, "sitemap.xml")}`);
316
+ }
317
+
291
318
  // Optional CDN config files. Generated only when not already provided.
292
319
  await writeIfMissing(
293
320
  join(outDir, "_redirects"),
@@ -319,6 +346,40 @@ async function writeIfMissing(path, content) {
319
346
  console.log(`[release] wrote ${path}`);
320
347
  }
321
348
 
349
+ async function promoteBakedHtml(bakedDir, outDir) {
350
+ if (!existsSync(bakedDir)) return { html: 0, sitemap: false };
351
+
352
+ let html = 0;
353
+
354
+ async function walk(dir, rel = "") {
355
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
356
+ const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
357
+ const source = join(dir, entry.name);
358
+ if (entry.isDirectory()) {
359
+ await walk(source, nextRel);
360
+ continue;
361
+ }
362
+ if (!entry.isFile() || !entry.name.endsWith(".html")) continue;
363
+ const target = join(outDir, nextRel);
364
+ await mkdir(dirname(target), { recursive: true });
365
+ await copyFile(source, target);
366
+ html++;
367
+ }
368
+ }
369
+
370
+ await walk(bakedDir);
371
+
372
+ const bakedSitemap = join(bakedDir, "sitemap.xml");
373
+ const rootSitemap = join(outDir, "sitemap.xml");
374
+ let sitemap = false;
375
+ if (existsSync(bakedSitemap)) {
376
+ await copyFile(bakedSitemap, rootSitemap);
377
+ sitemap = true;
378
+ }
379
+
380
+ return { html, sitemap };
381
+ }
382
+
322
383
  async function copyCanonicalAgentFiles(target) {
323
384
  for (const file of ["AGENTS.md", "llms.txt"]) {
324
385
  const source = join(PACKAGE_ROOT, file);
@@ -11,7 +11,7 @@
11
11
  // 3. Starts a static server with:
12
12
  // - immutable cache for hashed bundles;
13
13
  // - SPA fallback to index.html;
14
- // - baked HTML priority over the SPA shell;
14
+ // - exact `out/` route files before SPA fallback;
15
15
  // - precompressed .gz / .br serving via Accept-Encoding.
16
16
  //
17
17
  // Goal: see production-like output locally without Docker/nginx, identical to
@@ -58,9 +58,9 @@ const OUT = resolve(
58
58
  process.env.OUT_DIR ?? cfg.build.out ?? "out",
59
59
  );
60
60
  // Baked HTML lives in <out>/baked/ by default (see scripts/bake.mjs and
61
- // mado.config.json bake.outDir). Preview serves it with priority over the
62
- // SPA shell so URLs that have a prerendered HTML page render real markup
63
- // instead of an empty <div id="app"></div>.
61
+ // mado.config.json bake.outDir). `mado release` promotes those HTML files into
62
+ // real route paths inside <out>/, so preview can serve exactly what a static
63
+ // host sees instead of applying a preview-only virtual mapping.
64
64
  const BAKED = resolve(
65
65
  ROOT,
66
66
  process.env.BAKED_DIR ?? cfg.bake?.outDir ?? join(cfg.build.out ?? "out", "baked"),
@@ -215,30 +215,10 @@ function basenameSafe(p) {
215
215
  async function resolveTarget(pathname) {
216
216
  if (pathname === "/") pathname = "/index.html";
217
217
 
218
- // 1) Baked HTML wins. `mado bake` writes prerendered pages into
219
- // <out>/baked/<path>/index.html. Serve them with priority over the
220
- // SPA shell so search engines AND human users hitting a prerendered
221
- // URL see real content immediately. Without this branch preview
222
- // served the empty SPA shell for every URL, which looked like a
223
- // "blank page" bug even when bake had succeeded.
224
- if (await exists(BAKED)) {
225
- if (!extname(pathname) || pathname.endsWith("/index.html")) {
226
- const bakedDir = join(BAKED, pathname.replace(/\/index\.html$/, ""));
227
- const bakedIdx = join(bakedDir, "index.html");
228
- if (await exists(bakedIdx)) return bakedIdx;
229
- }
230
- // Direct file (sitemap.xml etc.) from the baked dir.
231
- const bakedFile = resolve(join(BAKED, pathname));
232
- if (bakedFile.startsWith(BAKED + sep) && (await exists(bakedFile))) {
233
- const s = await stat(bakedFile);
234
- if (!s.isDirectory()) return bakedFile;
235
- }
236
- }
237
-
238
218
  const candidate = resolve(join(OUT, pathname));
239
219
  if (!candidate.startsWith(OUT + sep) && candidate !== OUT) return null;
240
220
 
241
- // 2) Exact match inside out/.
221
+ // 1) Exact match inside out/.
242
222
  if (await exists(candidate)) {
243
223
  const s = await stat(candidate);
244
224
  if (s.isDirectory()) {
@@ -249,13 +229,13 @@ async function resolveTarget(pathname) {
249
229
  }
250
230
  }
251
231
 
252
- // 3) /foo → /foo/index.html (for sub-folders without trailing slash).
232
+ // 2) /foo → /foo/index.html (for sub-folders without trailing slash).
253
233
  if (!extname(pathname)) {
254
234
  const asDir = join(OUT, pathname, "index.html");
255
235
  if (await exists(asDir)) return asDir;
256
236
  }
257
237
 
258
- // 4) SPA-fallback: any non-asset path falls back to the SPA shell so
238
+ // 3) SPA-fallback: any non-asset path falls back to the SPA shell so
259
239
  // client-side routing handles it. Asset-looking paths (with an
260
240
  // extension) deliberately 404 instead — otherwise a 200 on
261
241
  // /missing.png would mask real bugs.
@@ -28,7 +28,7 @@ npm run build # tsc → dist/
28
28
  npm run typecheck # tsc --noEmit
29
29
  npm run bundle # esbuild → out/assets/
30
30
  npm run bake # prerender baked routes → out/baked/
31
- npm run release # typecheck + build + bundle + bake + copy public/ → out/
31
+ npm run release # typecheck + build + bundle + bake + promote baked HTML + copy public/ → out/
32
32
  npm run preview # serve out/ locally (production rehearsal)
33
33
  ```
34
34
 
@@ -60,4 +60,4 @@ Change `mado.config.json#dev.proxy` to point at your backend in development.
60
60
  See the framework docs:
61
61
  [`docs/en/11-layouts.md`](https://github.com/madojs/mado/blob/main/docs/en/11-layouts.md),
62
62
  [`docs/en/12-auth-and-api.md`](https://github.com/madojs/mado/blob/main/docs/en/12-auth-and-api.md),
63
- [`docs/en/13-deployment.md`](https://github.com/madojs/mado/blob/main/docs/en/13-deployment.md).
63
+ [`docs/en/13-deployment.md`](https://github.com/madojs/mado/blob/main/docs/en/13-deployment.md).