@madojs/mado 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +102 -25
- package/CHANGELOG.md +185 -1
- package/README.md +137 -172
- package/dist/src/component.d.ts +3 -2
- package/dist/src/component.js +98 -6
- package/dist/src/component.js.map +1 -1
- package/dist/src/css.js +34 -5
- package/dist/src/css.js.map +1 -1
- package/dist/src/head.d.ts +6 -6
- package/dist/src/head.js +6 -6
- package/dist/src/html/bindings.js +3 -3
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html/parser.d.ts +1 -1
- package/dist/src/html/parser.js +1 -1
- package/dist/src/html/template.js +24 -10
- package/dist/src/html/template.js.map +1 -1
- package/dist/src/index.d.ts +8 -4
- package/dist/src/index.js +13 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/page.d.ts +62 -28
- package/dist/src/page.js +5 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/resource.js +2 -1
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/base.d.ts +65 -0
- package/dist/src/router/base.js +168 -0
- package/dist/src/router/base.js.map +1 -0
- package/dist/src/router/manifest.js +75 -31
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +16 -2
- package/dist/src/router/match.js +41 -1
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.js +58 -9
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/static-runtime.d.ts +81 -0
- package/dist/src/static-runtime.js +209 -0
- package/dist/src/static-runtime.js.map +1 -0
- package/dist/src/vite/index.d.ts +31 -3
- package/dist/src/vite/index.js +49 -18
- package/dist/src/vite/index.js.map +1 -1
- package/docs/README.md +5 -8
- package/docs/architecture/adr/0001-browser-static-snapshots.md +132 -0
- package/docs/en/00-the-mado-way.md +1 -1
- package/docs/en/01-quickstart.md +183 -0
- package/docs/en/10-pages-and-components.md +271 -0
- package/docs/en/11-templates-and-signals.md +211 -0
- package/docs/en/12-routing.md +229 -0
- package/docs/en/13-data.md +225 -0
- package/docs/en/14-forms.md +244 -0
- package/docs/en/15-static-snapshots.md +181 -0
- package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +53 -2
- package/docs/en/{13-deployment.md → 20-deployment.md} +59 -10
- package/docs/en/{14-testing.md → 22-testing.md} +6 -5
- package/docs/en/23-cookbook.md +292 -0
- package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +12 -3
- package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +3 -3
- package/docs/en/40-llm-guide.md +776 -0
- package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +15 -16
- package/docs/en/{05-why-mado.md → 42-why-mado.md} +1 -1
- package/docs/en/README.md +97 -27
- package/docs/recipes/nginx/Containerfile +26 -0
- package/docs/recipes/nginx/nginx.conf +42 -0
- package/llms.txt +246 -211
- package/package.json +17 -10
- package/scripts/bake.mjs +4 -568
- package/scripts/cli/generate.mjs +55 -5
- package/scripts/cli/help.mjs +11 -5
- package/scripts/cli/index.mjs +20 -4
- package/scripts/cli/init.mjs +7 -2
- package/scripts/cli/release.mjs +20 -68
- package/scripts/docs-lint.mjs +170 -0
- package/scripts/package-smoke.mjs +67 -9
- package/scripts/preview.mjs +95 -6
- package/scripts/size-budget.mjs +5 -1
- package/scripts/static/browser.mjs +654 -0
- package/scripts/static/discover.mjs +281 -0
- package/scripts/static/output.mjs +141 -0
- package/scripts/static/serialize.mjs +212 -0
- package/scripts/static/server.mjs +167 -0
- package/scripts/static.mjs +191 -0
- package/starters/default/README.md +57 -55
- package/starters/default/package.json +3 -9
- package/starters/default/src/app.routes.ts +20 -33
- package/starters/default/src/components/feature-card.component.ts +40 -0
- package/starters/default/src/components/live-counter.component.ts +57 -0
- package/starters/default/src/content/guides.ts +55 -0
- package/starters/default/src/main.ts +9 -13
- package/starters/default/src/pages/app.page.ts +41 -0
- package/starters/default/src/pages/guide.page.ts +40 -0
- package/starters/default/src/pages/home.page.ts +49 -0
- package/starters/default/src/pages/not-found.page.ts +18 -0
- package/starters/default/src/styles/document.css +39 -0
- package/starters/default/src/styles/reset.css +37 -0
- package/starters/default/src/styles/tokens.css +30 -0
- package/starters/default/src/styles.d.ts +3 -1
- package/starters/default/src/vite-env.d.ts +1 -1
- package/starters/default/vite.config.ts +14 -1
- package/starters/modular/README.md +142 -0
- package/starters/modular/index.html +13 -0
- package/starters/modular/package.json +30 -0
- package/starters/modular/src/app.routes.ts +39 -0
- package/starters/{default → modular}/src/layouts/app-shell.layout.ts +8 -5
- package/starters/{default → modular}/src/layouts/auth-shell.layout.ts +3 -3
- package/starters/modular/src/main.ts +16 -0
- package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +3 -3
- package/starters/{default → modular}/src/modules/home/home.page.ts +5 -7
- package/starters/modular/src/modules/home/not-found.page.ts +14 -0
- package/starters/modular/src/styles.d.ts +1 -0
- package/starters/modular/src/vite-env.d.ts +1 -0
- package/starters/modular/tsconfig.json +24 -0
- package/starters/modular/vite.config.ts +131 -0
- package/TODO.md +0 -79
- package/docs/en/01-routing.md +0 -152
- package/docs/en/02-project-layout.md +0 -124
- package/docs/en/03-static-bake.md +0 -249
- package/docs/en/04-ide-setup.md +0 -162
- package/docs/en/07-llm-pitfalls.md +0 -724
- package/docs/en/08-llm-zero-history-test.md +0 -56
- package/docs/en/09-shadow-vs-light-dom.md +0 -174
- package/docs/en/11-layouts.md +0 -113
- package/docs/en/12-auth-and-api.md +0 -124
- package/docs/en/16-bake-cookbook.md +0 -100
- package/docs/en/17-shadow-dom-forms.md +0 -192
- package/docs/fr/00-the-mado-way.md +0 -85
- package/docs/fr/01-routing.md +0 -120
- package/docs/fr/02-project-layout.md +0 -84
- package/docs/fr/03-static-bake.md +0 -289
- package/docs/fr/04-ide-setup.md +0 -162
- package/docs/fr/05-why-mado.md +0 -193
- package/docs/fr/06-for-backenders.md +0 -438
- package/docs/fr/07-llm-pitfalls.md +0 -625
- package/docs/fr/08-llm-zero-history-test.md +0 -38
- package/docs/fr/09-shadow-vs-light-dom.md +0 -65
- package/docs/fr/10-app-architecture.md +0 -138
- package/docs/fr/11-layouts.md +0 -47
- package/docs/fr/12-auth-and-api.md +0 -76
- package/docs/fr/13-deployment.md +0 -57
- package/docs/fr/14-testing.md +0 -41
- package/docs/fr/15-error-handling.md +0 -50
- package/docs/fr/16-bake-cookbook.md +0 -88
- package/docs/fr/17-shadow-dom-forms.md +0 -196
- package/docs/fr/18-api-freeze-map.md +0 -63
- package/docs/fr/19-reactivity-ordering.md +0 -97
- package/docs/fr/20-v1-stability.md +0 -88
- package/docs/fr/README.md +0 -27
- package/docs/ru/00-the-mado-way.md +0 -84
- package/docs/ru/01-routing.md +0 -119
- package/docs/ru/02-project-layout.md +0 -84
- package/docs/ru/03-static-bake.md +0 -250
- package/docs/ru/04-ide-setup.md +0 -144
- package/docs/ru/05-why-mado.md +0 -193
- package/docs/ru/06-for-backenders.md +0 -428
- package/docs/ru/07-llm-pitfalls.md +0 -624
- package/docs/ru/08-llm-zero-history-test.md +0 -57
- package/docs/ru/09-shadow-vs-light-dom.md +0 -63
- package/docs/ru/10-app-architecture.md +0 -152
- package/docs/ru/11-layouts.md +0 -47
- package/docs/ru/12-auth-and-api.md +0 -75
- package/docs/ru/13-deployment.md +0 -66
- package/docs/ru/14-testing.md +0 -50
- package/docs/ru/15-error-handling.md +0 -56
- package/docs/ru/16-bake-cookbook.md +0 -95
- package/docs/ru/17-shadow-dom-forms.md +0 -193
- package/docs/ru/18-api-freeze-map.md +0 -64
- package/docs/ru/19-reactivity-ordering.md +0 -95
- package/docs/ru/20-v1-stability.md +0 -82
- package/docs/ru/README.md +0 -25
- package/docs/uk/00-the-mado-way.md +0 -82
- package/docs/uk/01-routing.md +0 -76
- package/docs/uk/02-project-layout.md +0 -73
- package/docs/uk/03-static-bake.md +0 -48
- package/docs/uk/04-ide-setup.md +0 -26
- package/docs/uk/05-why-mado.md +0 -34
- package/docs/uk/06-for-backenders.md +0 -55
- package/docs/uk/07-llm-pitfalls.md +0 -145
- package/docs/uk/08-llm-zero-history-test.md +0 -34
- package/docs/uk/09-shadow-vs-light-dom.md +0 -58
- package/docs/uk/10-app-architecture.md +0 -97
- package/docs/uk/11-layouts.md +0 -47
- package/docs/uk/12-auth-and-api.md +0 -70
- package/docs/uk/13-deployment.md +0 -40
- package/docs/uk/14-testing.md +0 -34
- package/docs/uk/15-error-handling.md +0 -32
- package/docs/uk/16-bake-cookbook.md +0 -36
- package/docs/uk/17-shadow-dom-forms.md +0 -193
- package/docs/uk/18-api-freeze-map.md +0 -61
- package/docs/uk/19-reactivity-ordering.md +0 -95
- package/docs/uk/20-v1-stability.md +0 -83
- package/docs/uk/README.md +0 -27
- package/starters/default/src/modules/home/not-found.page.ts +0 -11
- /package/docs/en/{15-error-handling.md → 21-error-handling.md} +0 -0
- /package/docs/en/{19-reactivity-ordering.md → 31-reactivity-ordering.md} +0 -0
- /package/starters/{default → modular}/.editorconfig +0 -0
- /package/starters/{default → modular}/eslint.config.mjs +0 -0
- /package/starters/{default → modular}/public/favicon.svg +0 -0
- /package/starters/{default → modular}/src/modules/auth/_contracts/auth-api.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.connector.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.guard.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.public.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.routes.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.service.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/auth.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/auth/login.page.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/_contracts/stripe.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/api/stripe.connector.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.public.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.routes.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/billing.types.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/components/invoice-status-badge.component.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/data/invoices.resource.ts +0 -0
- /package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/http-client.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/http-error.ts +0 -0
- /package/starters/{default → modular}/src/shared/http/interceptors.ts +0 -0
- /package/starters/{default → modular}/src/shared/lib/format-date.ts +0 -0
- /package/starters/{default → modular}/src/shared/styles/content.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/reset.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/shell.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
- /package/starters/{default → modular}/src/shared/ui/x-button.component.ts +0 -0
- /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
|
@@ -4,7 +4,58 @@ The official starter is the canonical production shape for Mado apps. It is
|
|
|
4
4
|
not a demo architecture and not a framework inside the framework: it is plain
|
|
5
5
|
files, imports, Mado primitives, and ESLint boundaries.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Project layout (universal starter)
|
|
8
|
+
|
|
9
|
+
Both starters share the same top-level shape. The universal starter is the
|
|
10
|
+
minimum:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
my-app/
|
|
14
|
+
├── package.json # exactly one runtime dep: @madojs/mado
|
|
15
|
+
├── tsconfig.json # strict TS, ES2022, Bundler resolution
|
|
16
|
+
├── vite.config.ts # mado() from @madojs/mado/vite
|
|
17
|
+
├── index.html # Vite entry + SPA shell
|
|
18
|
+
├── public/ # static assets (favicons, images, robots.txt)
|
|
19
|
+
└── src/
|
|
20
|
+
├── main.ts # entry: mount router into #app
|
|
21
|
+
├── app.routes.ts # one app map (default + named `manifest`)
|
|
22
|
+
├── pages/ # *.page.ts files
|
|
23
|
+
├── components/ # reusable <x-tag> components
|
|
24
|
+
└── shared/ # http/, lib/, styles/, ui/
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`index.html` belongs at the project root because Vite treats it as an entry
|
|
28
|
+
template, not a static public file. Put only copy-as-is files in `public/`.
|
|
29
|
+
|
|
30
|
+
### The three artifact states
|
|
31
|
+
|
|
32
|
+
| Folder | What it is | Who writes | Who reads | Deploy? |
|
|
33
|
+
| --------- | ----------------------------------------------------------- | --------------- | ----------------------- | ------------- |
|
|
34
|
+
| `src/` | your TypeScript source | you | Vite, `tsc --noEmit` | ❌ no |
|
|
35
|
+
| `public/` | static assets copied as-is | you | Vite build | ✅ via `out/` |
|
|
36
|
+
| `out/` | **the deploy artifact**: SPA shell + assets + snapshots | `mado release` | nginx / CDN / CF Pages | ✅ **yes** |
|
|
37
|
+
|
|
38
|
+
One-liner: develop with `mado dev`, ship with `mado release`, upload `out/`.
|
|
39
|
+
|
|
40
|
+
### Naming rules
|
|
41
|
+
|
|
42
|
+
| What | Style | Example |
|
|
43
|
+
| -------------------------- | ------------------ | ---------------------- |
|
|
44
|
+
| File | kebab-case | `user-profile.ts` |
|
|
45
|
+
| Component tag | `x-` + kebab | `<x-user-profile>` |
|
|
46
|
+
| Context | PascalCase + `Ctx` | `ThemeCtx`, `AuthCtx` |
|
|
47
|
+
| Signal | camelCase | `userId`, `isLoggedIn` |
|
|
48
|
+
| Page-internal element | `x-<route>-page` | `<x-posts-page>` |
|
|
49
|
+
|
|
50
|
+
### What does NOT belong in `src/`
|
|
51
|
+
|
|
52
|
+
- ❌ Build-tool configs beyond `vite.config.ts` with `mado()`.
|
|
53
|
+
- ❌ `.env` files — read env in `src/shared/lib/config.ts` from
|
|
54
|
+
`import.meta.env` and import that one module everywhere.
|
|
55
|
+
- ❌ Tests mixed with code — put them in `test/`.
|
|
56
|
+
- ❌ `examples/` folder — keep large demos outside the app repo.
|
|
57
|
+
|
|
58
|
+
## File tree (modular reference starter)
|
|
8
59
|
|
|
9
60
|
```txt
|
|
10
61
|
src/
|
|
@@ -68,7 +119,7 @@ export default routes(manifest);
|
|
|
68
119
|
|
|
69
120
|
Rules:
|
|
70
121
|
|
|
71
|
-
- Export `manifest` so `mado
|
|
122
|
+
- Export `manifest` so `mado static` can discover bakeable pages.
|
|
72
123
|
- Modules never call `layout()`.
|
|
73
124
|
- Layouts describe app zones, not domains.
|
|
74
125
|
- Do not hide the router inside a custom element or a second shell in
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
out/
|
|
11
|
-
├── index.html ←
|
|
11
|
+
├── index.html ← captured snapshot for / (or SPA shell)
|
|
12
12
|
├── assets/ ← Vite hashed assets
|
|
13
13
|
│ ├── *.gz ← precompressed gzip (gzip_static / Accept-Encoding)
|
|
14
14
|
│ └── *.br ← precompressed brotli (brotli_static / Accept-Encoding)
|
|
15
|
-
├── <route>/index.html ←
|
|
15
|
+
├── <route>/index.html ← captured snapshot per static route
|
|
16
|
+
├── _mado/spa.html ← SPA fallback shell (noindex)
|
|
16
17
|
├── sitemap.xml ← generated sitemap
|
|
17
18
|
├── favicon.svg ← your public/ assets copied verbatim
|
|
18
19
|
├── _redirects ← Cloudflare Pages / Netlify SPA fallback
|
|
@@ -89,8 +90,9 @@ Build command: npm ci && npx mado release
|
|
|
89
90
|
Output directory: out
|
|
90
91
|
```
|
|
91
92
|
|
|
92
|
-
For catalogs too big to
|
|
93
|
-
the external examples workspace rather than in the core
|
|
93
|
+
For catalogs too big to snapshot at release time, keep on-demand snapshot
|
|
94
|
+
experiments in the external examples workspace rather than in the core
|
|
95
|
+
package.
|
|
94
96
|
|
|
95
97
|
---
|
|
96
98
|
|
|
@@ -182,10 +184,57 @@ jobs:
|
|
|
182
184
|
- **`/assets/*` files change but the browser keeps the old one.** They
|
|
183
185
|
should not — the filename is hashed by Vite during `mado release`. If you bypassed
|
|
184
186
|
build and shipped your own unhashed JS, give it a hash or short cache.
|
|
185
|
-
- **
|
|
186
|
-
|
|
187
|
-
upgrade `@madojs/mado` and re-run
|
|
187
|
+
- **Captured snapshot shows `[object Object]`.** `mado static` rejects
|
|
188
|
+
non-JSON-serialisable seeds at build time and points at the bad
|
|
189
|
+
field. If you still see it, upgrade `@madojs/mado` and re-run
|
|
190
|
+
`mado release`.
|
|
191
|
+
|
|
192
|
+
## Sub-path deployments and `base`
|
|
193
|
+
|
|
194
|
+
A deployment under a sub-path (`https://your.site/docs/`, GitHub
|
|
195
|
+
Pages project URL, an admin panel mounted under `/admin/`, …) only
|
|
196
|
+
requires two changes:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
// vite.config.ts
|
|
200
|
+
import { defineConfig } from "vite";
|
|
201
|
+
import { mado } from "@madojs/mado/vite";
|
|
202
|
+
|
|
203
|
+
export default defineConfig({
|
|
204
|
+
base: "/docs/", // ← active prefix
|
|
205
|
+
plugins: [mado({ site: "https://your.site" })],
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Then make every internal anchor go through `routeUrl()` + `data-link`
|
|
210
|
+
so the URL is correct under any base:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { html, routeUrl } from "@madojs/mado";
|
|
214
|
+
|
|
215
|
+
html`<a data-link href=${routeUrl("/")}>Home</a>`; // → "/docs/"
|
|
216
|
+
html`<a data-link href=${routeUrl("/guides/intro")}>Intro</a>`;
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`mado release` honours `base` end-to-end:
|
|
220
|
+
|
|
221
|
+
- captured snapshots load assets through `/docs/assets/...`;
|
|
222
|
+
- sitemap URLs are `https://your.site/docs/...`;
|
|
223
|
+
- canonicals and `og:url` include the prefix;
|
|
224
|
+
- `mado preview` redirects bare `/` to `/docs/` and serves SPA
|
|
225
|
+
fallback under `/docs/<anything>`.
|
|
226
|
+
|
|
227
|
+
On the production host:
|
|
228
|
+
|
|
229
|
+
- **nginx** — keep `try_files $uri $uri/ /index.html;` but mount the
|
|
230
|
+
app under the matching prefix (`location /docs/ { ... }`).
|
|
231
|
+
- **Cloudflare Pages / Netlify** — `_redirects` is generated with the
|
|
232
|
+
base prefix already baked in.
|
|
233
|
+
- **GitHub Pages (project site)** — set `base: "/<repo>/"` and use
|
|
234
|
+
the `gh-pages` deploy path; canonical URLs land at
|
|
235
|
+
`https://<user>.github.io/<repo>/...`.
|
|
188
236
|
|
|
189
|
-
See also: [`
|
|
190
|
-
`src
|
|
191
|
-
for the
|
|
237
|
+
See also: [`16-app-architecture.md`](./16-app-architecture.md) for the
|
|
238
|
+
`src/` / `public/` / `out/` model and
|
|
239
|
+
[`15-static-snapshots.md`](./15-static-snapshots.md) for the static
|
|
240
|
+
snapshot mechanics.
|
|
@@ -48,7 +48,7 @@ Test behavior, not internal implementation details:
|
|
|
48
48
|
- route guards, redirects, scroll/focus behavior, error boundaries;
|
|
49
49
|
- forms: validation, async validation races, field arrays;
|
|
50
50
|
- resources/mutations: cache keys, invalidation, lifecycle cleanup;
|
|
51
|
-
- CLI flows: `mado release`, `mado
|
|
51
|
+
- CLI flows: `mado release`, `mado static`, `mado preview`.
|
|
52
52
|
|
|
53
53
|
## Browser smoke tests
|
|
54
54
|
|
|
@@ -74,9 +74,10 @@ services from framework tests. For app tests, make the API client injectable via
|
|
|
74
74
|
npm run typecheck
|
|
75
75
|
npm run build
|
|
76
76
|
npm test
|
|
77
|
-
npm run
|
|
77
|
+
npm run release
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
`mado release` runs the production path for an app: typecheck, Vite
|
|
81
|
-
|
|
82
|
-
`npm run build` still emits `dist/src`
|
|
80
|
+
`mado release` runs the production path for an app: typecheck, Vite
|
|
81
|
+
build, static-snapshot capture, compression and deploy helper files.
|
|
82
|
+
In the framework repository, `npm run build` still emits `dist/src`
|
|
83
|
+
for package tests and publishing.
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# Static snapshots cookbook
|
|
2
|
+
|
|
3
|
+
> Concrete recipes for `mado static`. Concept and API in
|
|
4
|
+
> [15-static-snapshots.md](./15-static-snapshots.md); failure modes and CI
|
|
5
|
+
> guidance at the end of this file.
|
|
6
|
+
|
|
7
|
+
Every recipe assumes:
|
|
8
|
+
|
|
9
|
+
- Vite is the build transport (`@madojs/mado/vite` plugin).
|
|
10
|
+
- A public origin is configured (`mado({ site })` or
|
|
11
|
+
`MADO_SITE=https://your.site mado release`).
|
|
12
|
+
- `mado release` is the release command (it runs `vite build` →
|
|
13
|
+
`mado static` → writes deployment files into `out/`).
|
|
14
|
+
|
|
15
|
+
## 1. Single static landing page
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// src/pages/home.page.ts
|
|
19
|
+
import { html, page } from "@madojs/mado";
|
|
20
|
+
|
|
21
|
+
export default page({
|
|
22
|
+
static: true,
|
|
23
|
+
title: "Home",
|
|
24
|
+
head: () => ({
|
|
25
|
+
description: "Welcome to the Mado App.",
|
|
26
|
+
}),
|
|
27
|
+
view: () => html`
|
|
28
|
+
<main>
|
|
29
|
+
<h1>Mado App</h1>
|
|
30
|
+
<p>One component model. One page model. One release command.</p>
|
|
31
|
+
</main>
|
|
32
|
+
`,
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
After `mado release`:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
out/
|
|
40
|
+
index.html ← captured snapshot
|
|
41
|
+
assets/...
|
|
42
|
+
_mado/spa.html ← SPA fallback for other routes
|
|
43
|
+
sitemap.xml
|
|
44
|
+
_headers / _redirects
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 2. Blog with file-system content
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// src/content/posts.ts
|
|
51
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
52
|
+
import { resolve } from "node:path";
|
|
53
|
+
|
|
54
|
+
export interface Post {
|
|
55
|
+
slug: string;
|
|
56
|
+
title: string;
|
|
57
|
+
date: string;
|
|
58
|
+
html: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const dir = resolve(process.cwd(), "content/posts");
|
|
62
|
+
|
|
63
|
+
export function listPosts(): Post[] {
|
|
64
|
+
return readdirSync(dir)
|
|
65
|
+
.filter((f) => f.endsWith(".md"))
|
|
66
|
+
.map((file) => loadPost(file));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function loadPost(file: string): Post {
|
|
70
|
+
// Replace with your real Markdown pipeline.
|
|
71
|
+
return JSON.parse(readFileSync(resolve(dir, file), "utf8"));
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// src/pages/post.page.ts
|
|
77
|
+
import { html, page } from "@madojs/mado";
|
|
78
|
+
import { listPosts, loadPost, type Post } from "../content/posts";
|
|
79
|
+
|
|
80
|
+
export default page<{ slug: string }, Post>({
|
|
81
|
+
static: {
|
|
82
|
+
paths: async () =>
|
|
83
|
+
listPosts().map((p) => ({ slug: p.slug })),
|
|
84
|
+
initialData: async ({ slug }) =>
|
|
85
|
+
loadPost(`${slug}.json`),
|
|
86
|
+
},
|
|
87
|
+
title: (_, seed) => seed?.title ?? "Post",
|
|
88
|
+
head: (_, seed) => ({
|
|
89
|
+
description: seed?.title,
|
|
90
|
+
}),
|
|
91
|
+
view: (ctx) => html`
|
|
92
|
+
<article>
|
|
93
|
+
<h1>${ctx.data.title}</h1>
|
|
94
|
+
<time>${ctx.data.date}</time>
|
|
95
|
+
${html([ctx.data.html] as unknown as TemplateStringsArray)}
|
|
96
|
+
</article>
|
|
97
|
+
`,
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Output: one `out/posts/<slug>/index.html` per post, each carrying its
|
|
102
|
+
own canonical and `og:url`.
|
|
103
|
+
|
|
104
|
+
> File-system reads happen during *discovery* (Node side). They never
|
|
105
|
+
> ship in the client bundle. The captured *initial data* does ship —
|
|
106
|
+
> keep it free of secrets.
|
|
107
|
+
|
|
108
|
+
## 3. Product catalogue from JSON
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { html, page } from "@madojs/mado";
|
|
112
|
+
import products from "../content/products.json" with { type: "json" };
|
|
113
|
+
|
|
114
|
+
interface Product {
|
|
115
|
+
slug: string;
|
|
116
|
+
name: string;
|
|
117
|
+
price: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export default page<{ slug: string }, Product>({
|
|
121
|
+
static: {
|
|
122
|
+
paths: async () => products.map((p) => ({ slug: p.slug })),
|
|
123
|
+
initialData: async ({ slug }) =>
|
|
124
|
+
products.find((p) => p.slug === slug)!,
|
|
125
|
+
},
|
|
126
|
+
title: (_, seed) => seed?.name ?? "Product",
|
|
127
|
+
view: (ctx) => html`
|
|
128
|
+
<main>
|
|
129
|
+
<h1>${ctx.data.name}</h1>
|
|
130
|
+
<p>$${ctx.data.price.toFixed(2)}</p>
|
|
131
|
+
</main>
|
|
132
|
+
`,
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## 4. Documentation site
|
|
137
|
+
|
|
138
|
+
A docs site is typically:
|
|
139
|
+
|
|
140
|
+
- Many static guides keyed by `slug`.
|
|
141
|
+
- A few hand-written landing pages.
|
|
142
|
+
- One SPA route for the search modal (kept dynamic).
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
// src/app.routes.ts
|
|
146
|
+
import { routes } from "@madojs/mado";
|
|
147
|
+
import home from "./pages/home.page";
|
|
148
|
+
import guide from "./pages/guide.page";
|
|
149
|
+
import search from "./pages/search.page";
|
|
150
|
+
|
|
151
|
+
export const manifest = {
|
|
152
|
+
"/": home, // static
|
|
153
|
+
"/guides/:slug": guide, // dynamic static
|
|
154
|
+
"/search": search, // SPA-only (no `static` field)
|
|
155
|
+
"*": () => import("./pages/not-found.page"),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export default routes(manifest);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Only `home` and `guide` are captured. `/search` is served from the
|
|
162
|
+
SPA fallback (`_mado/spa.html`).
|
|
163
|
+
|
|
164
|
+
## 5. Build-time API call
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { html, page } from "@madojs/mado";
|
|
168
|
+
|
|
169
|
+
interface Release { tag: string; date: string }
|
|
170
|
+
|
|
171
|
+
export default page<{ tag: string }, Release>({
|
|
172
|
+
static: {
|
|
173
|
+
paths: async () => {
|
|
174
|
+
const res = await fetch("https://api.github.com/repos/madojs/mado/releases");
|
|
175
|
+
const list = (await res.json()) as Release[];
|
|
176
|
+
return list.map((r) => ({ tag: r.tag }));
|
|
177
|
+
},
|
|
178
|
+
initialData: async ({ tag }) => {
|
|
179
|
+
const res = await fetch(
|
|
180
|
+
`https://api.github.com/repos/madojs/mado/releases/tags/${tag}`,
|
|
181
|
+
);
|
|
182
|
+
return (await res.json()) as Release;
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
title: (_, seed) => `Release ${seed?.tag ?? ""}`,
|
|
186
|
+
view: (ctx) => html`
|
|
187
|
+
<article>
|
|
188
|
+
<h1>${ctx.data.tag}</h1>
|
|
189
|
+
<time>${ctx.data.date}</time>
|
|
190
|
+
</article>
|
|
191
|
+
`,
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Network calls in `paths()` / `initialData()` are perfectly fine — they
|
|
196
|
+
run in the discovery Node process, not in the browser at capture.
|
|
197
|
+
|
|
198
|
+
## 6. Sub-path deployment
|
|
199
|
+
|
|
200
|
+
`mado release` honours Vite's `base`. Configure it once:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
// vite.config.ts
|
|
204
|
+
import { defineConfig } from "vite";
|
|
205
|
+
import { mado } from "@madojs/mado/vite";
|
|
206
|
+
|
|
207
|
+
export default defineConfig({
|
|
208
|
+
base: "/docs/",
|
|
209
|
+
plugins: [mado({ site: "https://your.site" })],
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Every link in your app must go through `routeUrl()` so it picks up
|
|
214
|
+
the prefix:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { html, routeUrl } from "@madojs/mado";
|
|
218
|
+
|
|
219
|
+
html`<a data-link href=${routeUrl("/guides/intro")}>Intro</a>`;
|
|
220
|
+
html`<a data-link href=${routeUrl("/")}>Home</a>`; // → "/docs/"
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Captured canonicals and `og:url` will be `https://your.site/docs/...`.
|
|
224
|
+
|
|
225
|
+
## 7. SPA-only fallback
|
|
226
|
+
|
|
227
|
+
Any route that does NOT declare `static` is served from
|
|
228
|
+
`out/_mado/spa.html`. That is the right place for authenticated
|
|
229
|
+
zones, search, dashboards, admin tools.
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
// src/pages/app.page.ts
|
|
233
|
+
import { html, page } from "@madojs/mado";
|
|
234
|
+
|
|
235
|
+
// No `static` field — SPA only.
|
|
236
|
+
export default page({
|
|
237
|
+
title: "App",
|
|
238
|
+
view: () => html`<x-app-shell></x-app-shell>`,
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The fallback is automatically marked `noindex` so search engines do
|
|
243
|
+
not capture the SPA shell.
|
|
244
|
+
|
|
245
|
+
## 8. Failure modes
|
|
246
|
+
|
|
247
|
+
`mado static` fails the snapshot (non-zero exit) when:
|
|
248
|
+
|
|
249
|
+
- A script, stylesheet, custom element or tracked `resource()` fetch
|
|
250
|
+
fails.
|
|
251
|
+
- A custom element is referenced in the rendered DOM but never
|
|
252
|
+
defined.
|
|
253
|
+
- A page declares `static: true` on a route with `:params` but no
|
|
254
|
+
`paths()`.
|
|
255
|
+
- Two pages produce the same generated URL.
|
|
256
|
+
- Any guard runs on a static route or its layout chain.
|
|
257
|
+
- `initialData()` returns a value that is not strictly JSON-serialisable
|
|
258
|
+
(Date, Map, Set, class instance, `undefined` field, NaN, Infinity,
|
|
259
|
+
cycles, non-plain prototypes). The validator points at the bad
|
|
260
|
+
field by path.
|
|
261
|
+
|
|
262
|
+
A failed snapshot leaves the previous `out/` untouched: capture
|
|
263
|
+
writes to a temp tree and only promotes once every route survived.
|
|
264
|
+
|
|
265
|
+
## 9. CI guidance
|
|
266
|
+
|
|
267
|
+
```yaml
|
|
268
|
+
# .github/workflows/ci.yml — extract
|
|
269
|
+
- name: Install Playwright Chromium
|
|
270
|
+
run: node node_modules/playwright-core/cli.js install --with-deps chromium
|
|
271
|
+
|
|
272
|
+
- name: Release smoke
|
|
273
|
+
env:
|
|
274
|
+
MADO_REQUIRE_BROWSER: "1"
|
|
275
|
+
MADO_SITE: "https://your.site"
|
|
276
|
+
run: npm run release
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`MADO_REQUIRE_BROWSER=1` forces `mado static` to fail loudly if no
|
|
280
|
+
Chromium is resolvable (instead of silently producing a SPA-only
|
|
281
|
+
build).
|
|
282
|
+
|
|
283
|
+
## 10. Quick checklist before pushing
|
|
284
|
+
|
|
285
|
+
- [ ] `mado({ site })` set (or `MADO_SITE` / `--base-url`).
|
|
286
|
+
- [ ] Every static page declares `static: true | { paths, initialData }`.
|
|
287
|
+
- [ ] No `paths()` / `initialData()` reads secrets.
|
|
288
|
+
- [ ] No static page is behind a guard.
|
|
289
|
+
- [ ] Internal links go through `routeUrl()` + `data-link`.
|
|
290
|
+
- [ ] `mado release` succeeds locally with Playwright Chromium
|
|
291
|
+
installed.
|
|
292
|
+
- [ ] `mado preview` serves the artefact correctly.
|
|
@@ -30,14 +30,16 @@ These names are public and protected by SemVer once v1 ships:
|
|
|
30
30
|
`ref`, `classMap`, `styleMap`.
|
|
31
31
|
- Components and CSS: `component`, `css`, `cssVars`.
|
|
32
32
|
- Routing and pages: `routes`, `router`, `page`, `layout`,
|
|
33
|
-
`navigate`, `queryParam`, `prefetchPath`.
|
|
33
|
+
`navigate`, `queryParam`, `prefetchPath`, `routeUrl`, `appBase`.
|
|
34
34
|
- Data: `resource`, `mutation`, `invalidate`, `jsonFetcher`, `HttpError`.
|
|
35
35
|
- Forms: `useForm`.
|
|
36
36
|
- Head and persistence: `applyHead`, `persisted`.
|
|
37
37
|
- Context: `createContext`, `provide`, `inject`.
|
|
38
38
|
- Advanced lifecycle helpers: `createLifecycle`, `runInLifecycle`,
|
|
39
39
|
`getCurrentLifecycle`.
|
|
40
|
-
- Public TypeScript types exported from `@madojs/mado
|
|
40
|
+
- Public TypeScript types exported from `@madojs/mado`, including
|
|
41
|
+
`StaticPageConfig` (the `static: true | { ... }` option on `page()`)
|
|
42
|
+
and `HeadMeta` (the head metadata shape returned by pages).
|
|
41
43
|
|
|
42
44
|
## Internal or unstable
|
|
43
45
|
|
|
@@ -48,7 +50,14 @@ These are not public API:
|
|
|
48
50
|
- Template parser/binding internals such as `html/parser.js`,
|
|
49
51
|
`html/bindings.js`, `ChildState`, and `EachEntry`.
|
|
50
52
|
- Router implementation modules such as `router/match.js`,
|
|
51
|
-
`router/navigation.js`, and `router/
|
|
53
|
+
`router/navigation.js`, `router/manifest.js`, and `router/base.js`.
|
|
54
|
+
- Base-path helpers other than `routeUrl` / `appBase`: `normalizeBase`,
|
|
55
|
+
`stripBase` and `withBase` are intentionally not exported from
|
|
56
|
+
`@madojs/mado`. Use `routeUrl()` for `<a href>` values and `appBase`
|
|
57
|
+
if you need the raw active prefix.
|
|
58
|
+
- The static snapshot pipeline (`scripts/static.mjs`, the
|
|
59
|
+
`_mado/build.json` bridge, the temp capture server). `mado static` is
|
|
60
|
+
a CLI command; its internals can change between minor versions.
|
|
52
61
|
- Diagnostics internals and all `_testHooks`.
|
|
53
62
|
- Exact generated bundle text, chunk names, and internal file layout.
|
|
54
63
|
|
|
@@ -8,8 +8,8 @@ copy, or diagnostic string is frozen forever.
|
|
|
8
8
|
|
|
9
9
|
Read this together with:
|
|
10
10
|
|
|
11
|
-
- [API freeze map](./
|
|
12
|
-
- [Reactivity ordering](./
|
|
11
|
+
- [API freeze map](./30-api-freeze-map.md)
|
|
12
|
+
- [Reactivity ordering](./31-reactivity-ordering.md)
|
|
13
13
|
|
|
14
14
|
## Stable under SemVer
|
|
15
15
|
|
|
@@ -25,7 +25,7 @@ After v1, Mado treats these as SemVer-protected:
|
|
|
25
25
|
teardown for same-tick moves, cleanup via `ctx.onDispose`.
|
|
26
26
|
- Router/page/resource/form contracts documented in the English docs.
|
|
27
27
|
- CLI command names and broad command intent (`build`, `dev`, `release`,
|
|
28
|
-
`
|
|
28
|
+
`static`, `preview`, `init`, `new`).
|
|
29
29
|
|
|
30
30
|
Breaking these requires a major version.
|
|
31
31
|
|