@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.
- package/AGENTS.md +116 -32
- package/CHANGELOG.md +216 -1
- package/README.md +158 -173
- package/dist/src/component.d.ts +6 -3
- package/dist/src/component.d.ts.map +1 -0
- package/dist/src/component.js +163 -36
- package/dist/src/component.js.map +1 -1
- package/dist/src/context.d.ts +13 -32
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +73 -48
- package/dist/src/context.js.map +1 -1
- package/dist/src/css.d.ts +1 -0
- package/dist/src/css.d.ts.map +1 -0
- package/dist/src/css.js +34 -5
- package/dist/src/css.js.map +1 -1
- package/dist/src/devtools-hook.d.ts +14 -0
- package/dist/src/devtools-hook.d.ts.map +1 -0
- package/dist/src/devtools-hook.js +27 -0
- package/dist/src/devtools-hook.js.map +1 -0
- package/dist/src/devtools.d.ts +24 -22
- package/dist/src/devtools.d.ts.map +1 -0
- package/dist/src/devtools.js +230 -58
- package/dist/src/devtools.js.map +1 -1
- package/dist/src/diagnostics.d.ts +12 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +68 -8
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/each.d.ts +1 -5
- package/dist/src/each.d.ts.map +1 -0
- package/dist/src/each.js +0 -8
- package/dist/src/each.js.map +1 -1
- package/dist/src/forms.d.ts +27 -96
- package/dist/src/forms.d.ts.map +1 -0
- package/dist/src/forms.js +184 -394
- package/dist/src/forms.js.map +1 -1
- package/dist/src/head.d.ts +7 -6
- package/dist/src/head.d.ts.map +1 -0
- package/dist/src/head.js +8 -7
- package/dist/src/head.js.map +1 -1
- package/dist/src/html/bindings.d.ts +1 -0
- package/dist/src/html/bindings.d.ts.map +1 -0
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html/parser.d.ts +4 -5
- package/dist/src/html/parser.d.ts.map +1 -0
- package/dist/src/html/parser.js +64 -15
- package/dist/src/html/parser.js.map +1 -1
- package/dist/src/html/template-types.d.ts +1 -0
- package/dist/src/html/template-types.d.ts.map +1 -0
- package/dist/src/html/template-types.js.map +1 -1
- package/dist/src/html/template.d.ts +5 -1
- package/dist/src/html/template.d.ts.map +1 -0
- package/dist/src/html/template.js +102 -13
- package/dist/src/html/template.js.map +1 -1
- package/dist/src/index.d.ts +15 -12
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +18 -10
- package/dist/src/index.js.map +1 -1
- package/dist/src/json.d.ts +3 -0
- package/dist/src/json.d.ts.map +1 -0
- package/dist/src/json.js +14 -0
- package/dist/src/json.js.map +1 -0
- package/dist/src/lifecycle.d.ts +1 -0
- package/dist/src/lifecycle.d.ts.map +1 -0
- package/dist/src/lifecycle.js +3 -2
- package/dist/src/lifecycle.js.map +1 -1
- package/dist/src/page.d.ts +63 -28
- package/dist/src/page.d.ts.map +1 -0
- package/dist/src/page.js +5 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/persisted.d.ts +6 -1
- package/dist/src/persisted.d.ts.map +1 -0
- package/dist/src/persisted.js +17 -11
- package/dist/src/persisted.js.map +1 -1
- package/dist/src/resource.d.ts +2 -1
- package/dist/src/resource.d.ts.map +1 -0
- package/dist/src/resource.js +128 -50
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/base.d.ts +66 -0
- package/dist/src/router/base.d.ts.map +1 -0
- package/dist/src/router/base.js +168 -0
- package/dist/src/router/base.js.map +1 -0
- package/dist/src/router/manifest.d.ts +1 -0
- package/dist/src/router/manifest.d.ts.map +1 -0
- package/dist/src/router/manifest.js +156 -171
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +17 -2
- package/dist/src/router/match.d.ts.map +1 -0
- package/dist/src/router/match.js +60 -6
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.d.ts +1 -0
- package/dist/src/router/navigation.d.ts.map +1 -0
- package/dist/src/router/navigation.js +58 -9
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/signal.d.ts +1 -0
- package/dist/src/signal.d.ts.map +1 -0
- package/dist/src/signal.js +46 -17
- package/dist/src/signal.js.map +1 -1
- package/dist/src/static-runtime.d.ts +82 -0
- package/dist/src/static-runtime.d.ts.map +1 -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 +32 -3
- package/dist/src/vite/index.d.ts.map +1 -0
- package/dist/src/vite/index.js +49 -17
- 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/architecture/v1-roadmap.md +19 -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 +279 -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 +227 -0
- package/docs/en/14-forms.md +91 -0
- package/docs/en/15-static-snapshots.md +181 -0
- package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +56 -4
- package/docs/en/{13-deployment.md → 20-deployment.md} +79 -17
- package/docs/en/{15-error-handling.md → 21-error-handling.md} +9 -6
- package/docs/en/{14-testing.md → 22-testing.md} +6 -5
- package/docs/en/23-cookbook.md +292 -0
- package/docs/en/24-devtools-and-diagnostics.md +45 -0
- package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +17 -9
- package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +5 -5
- package/docs/en/33-migration-0.12-0.13.md +56 -0
- package/docs/en/40-llm-guide.md +774 -0
- package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +28 -25
- package/docs/en/{05-why-mado.md → 42-why-mado.md} +3 -3
- package/docs/en/README.md +99 -27
- package/docs/recipes/nginx/Containerfile +26 -0
- package/docs/recipes/nginx/nginx.conf +42 -0
- package/llms.txt +263 -210
- package/package.json +51 -23
- package/scripts/clean.mjs +8 -0
- package/scripts/cli/generate.mjs +57 -10
- package/scripts/cli/help.mjs +13 -5
- package/scripts/cli/index.mjs +31 -7
- package/scripts/cli/init.mjs +19 -19
- package/scripts/cli/release.mjs +38 -83
- package/scripts/cli/run.mjs +5 -3
- package/scripts/logger.mjs +117 -0
- package/scripts/output-guard.mjs +128 -0
- package/scripts/preview.mjs +105 -24
- package/scripts/static/browser.mjs +695 -0
- package/scripts/static/discover.mjs +284 -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 +205 -0
- package/starters/default/README.md +57 -55
- package/starters/default/package.json +6 -12
- 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/tsconfig.node.json +13 -0
- package/starters/default/vite.config.ts +14 -4
- 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 +9 -7
- 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/auth/login.page.ts +5 -12
- package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +3 -4
- package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +4 -4
- 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/{default → modular}/src/shared/styles/reset.css +21 -1
- package/starters/modular/src/styles.d.ts +1 -0
- package/starters/modular/src/vite-env.d.ts +1 -0
- package/starters/modular/tsconfig.json +33 -0
- package/starters/modular/tsconfig.node.json +13 -0
- package/starters/modular/vite.config.ts +132 -0
- package/TODO.md +0 -79
- package/dist/src/lazy.d.ts +0 -38
- package/dist/src/lazy.js +0 -73
- package/dist/src/lazy.js.map +0 -1
- 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/scripts/bake.mjs +0 -570
- package/scripts/package-smoke.mjs +0 -77
- package/scripts/release-notes.mjs +0 -66
- package/scripts/size-budget.mjs +0 -91
- package/starters/default/src/modules/home/not-found.page.ts +0 -11
- package/starters/default/src/shared/ui/x-button.component.ts +0 -49
- /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/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/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/shell.css +0 -0
- /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
- /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# ADR 0001 — Browser-rendered static snapshots
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted (0.12.0)
|
|
4
|
+
**Date:** 2026-06-22
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Mado started as a calm browser-native SPA framework for internal tools
|
|
9
|
+
and admin panels. Two new product requirements made that scope too
|
|
10
|
+
narrow:
|
|
11
|
+
|
|
12
|
+
1. Public-facing surfaces (landing pages, docs sites, product pages)
|
|
13
|
+
need search engines and social preview bots to see a fully rendered
|
|
14
|
+
document on first request, without running JavaScript.
|
|
15
|
+
2. The same component model that powers the live SPA must produce that
|
|
16
|
+
document — we do not want a second component runtime, a hydration
|
|
17
|
+
protocol, or a server renderer.
|
|
18
|
+
|
|
19
|
+
The conventional answers (Next/Nuxt SSR, Astro, Eleventy, Lit SSR,
|
|
20
|
+
Solid Start, …) each ship at least one of:
|
|
21
|
+
|
|
22
|
+
- a separate component model for the server,
|
|
23
|
+
- a hydration boundary that introduces a second source of truth,
|
|
24
|
+
- a build-time JSX/Vue/Svelte compiler,
|
|
25
|
+
- a Node runtime in production.
|
|
26
|
+
|
|
27
|
+
Mado is positioned as "calm native-first": we cannot adopt any of
|
|
28
|
+
those without breaking the positioning.
|
|
29
|
+
|
|
30
|
+
## Decision
|
|
31
|
+
|
|
32
|
+
Adopt **browser-rendered static snapshots with Declarative Shadow
|
|
33
|
+
DOM** as the canonical SEO/static path:
|
|
34
|
+
|
|
35
|
+
1. `mado release` runs `vite build`, then opens each declared static
|
|
36
|
+
route in a real headless Chromium pointed at the build output, and
|
|
37
|
+
serialises the rendered document — including the Shadow DOM as
|
|
38
|
+
Declarative Shadow DOM — into one HTML file per route under
|
|
39
|
+
`out/`.
|
|
40
|
+
2. The client runtime treats the snapshot as an **atomic takeover**
|
|
41
|
+
target: a Mado component instance reattaches to the existing host
|
|
42
|
+
in place, with no second tree, no hydration, no diff.
|
|
43
|
+
3. Routes that are not declared `static` stay SPA-only and are served
|
|
44
|
+
through the `_mado/spa.html` fallback the same way they were
|
|
45
|
+
before.
|
|
46
|
+
4. Vite's `base` is the single source of truth for the public URL
|
|
47
|
+
prefix; the runtime reads it from `import.meta.env.BASE_URL`, the
|
|
48
|
+
CLI reads it from the build bridge (`_mado/build.json`) for
|
|
49
|
+
sitemap / canonical / preview, and the build bridge is dropped
|
|
50
|
+
before the artifact ships.
|
|
51
|
+
5. Build-time seeds for static routes are strict `JsonValue` only,
|
|
52
|
+
validated by a path-aware walker so a `Date`, `Map`, `undefined`
|
|
53
|
+
field or cycle fails the build instead of silently corrupting the
|
|
54
|
+
first client render.
|
|
55
|
+
|
|
56
|
+
## Alternatives rejected
|
|
57
|
+
|
|
58
|
+
- **SSR (Next / Nuxt / Solid Start).** Ships a second component model
|
|
59
|
+
and a Node production runtime. Hydration boundary is the largest
|
|
60
|
+
source of recurring bug classes in the ecosystem. Mado's
|
|
61
|
+
positioning rejects both.
|
|
62
|
+
|
|
63
|
+
- **Hydration via an island / partial hydration model (Astro,
|
|
64
|
+
Marko).** Splits the component graph into "static" and "interactive"
|
|
65
|
+
sub-graphs with framework-specific compilers. Adds a second mental
|
|
66
|
+
model for a public-site benefit that DSD already provides.
|
|
67
|
+
|
|
68
|
+
- **Author-time prerendering with `linkedom`/`jsdom`.** Cannot run
|
|
69
|
+
Shadow DOM, adoptedStyleSheets, layout, or any browser API.
|
|
70
|
+
Snapshots would diverge from what the browser actually renders.
|
|
71
|
+
|
|
72
|
+
- **External prerender service (Prerender.io and friends).** Adds an
|
|
73
|
+
external dependency, runs on each request, and Mado has no canonical
|
|
74
|
+
way to detect when to flush its cache.
|
|
75
|
+
|
|
76
|
+
- **Embed a Node runtime that pretends to be a browser.** Same
|
|
77
|
+
divergence problem as `linkedom`, plus a custom DOM polyfill we
|
|
78
|
+
would have to maintain forever.
|
|
79
|
+
|
|
80
|
+
## Consequences
|
|
81
|
+
|
|
82
|
+
- A compatible Chromium is required at **release time** (not at
|
|
83
|
+
request time, not in production) for projects that declare any
|
|
84
|
+
static route. `mado release` documents how to install one
|
|
85
|
+
(Playwright-managed Chromium, system Chrome, or operator-provided
|
|
86
|
+
path) and CI gates the round-trip with `npx playwright install
|
|
87
|
+
--with-deps chromium` plus `MADO_REQUIRE_BROWSER=1`.
|
|
88
|
+
|
|
89
|
+
- `page.static.paths()` and `page.static.initialData()` must be
|
|
90
|
+
**browser-safe and secret-free**: they execute during the discovery
|
|
91
|
+
pass AND remain in the client bundle. The strict `JsonValue`
|
|
92
|
+
validator catches the common shapes that would otherwise leak.
|
|
93
|
+
|
|
94
|
+
- The framework now has a documented HTTP policy for snapshot capture:
|
|
95
|
+
documents, scripts, stylesheets and tracked resources are fatal;
|
|
96
|
+
favicon-class endpoints are ignored; fonts and paint frames are
|
|
97
|
+
quality hints with bounded timeouts.
|
|
98
|
+
|
|
99
|
+
- The product surface widens from "internal tools / admin panels" to
|
|
100
|
+
"sites and apps". Marketing copy, package metadata, GitHub topics
|
|
101
|
+
and the default starter all change accordingly. The previous
|
|
102
|
+
modular reference architecture is preserved as
|
|
103
|
+
`mado init --starter modular` for long-lived business apps.
|
|
104
|
+
|
|
105
|
+
## Known limits
|
|
106
|
+
|
|
107
|
+
- Static routes cannot use `Date`, `Map`, `Set`, class instances,
|
|
108
|
+
`undefined` values, cycles, or non-finite numbers in their
|
|
109
|
+
`initialData`. The validator gives a path-aware error.
|
|
110
|
+
|
|
111
|
+
- `mado release` snapshots are deterministic only in a pinned
|
|
112
|
+
environment (same Chromium revision, same source tree). We document
|
|
113
|
+
that determinism contract; reproducing it across CI vendors is the
|
|
114
|
+
operator's responsibility.
|
|
115
|
+
|
|
116
|
+
- Static `paths()` runs at build time. We do not currently support
|
|
117
|
+
incremental re-snapshot from a webhook; a static-content change
|
|
118
|
+
requires a fresh release.
|
|
119
|
+
|
|
120
|
+
- Public client-only state inside a static route (canvas, media
|
|
121
|
+
controllers, transient timers) survives takeover but is not part of
|
|
122
|
+
the snapshot — it materialises only after JS executes.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Original implementation plan (for history)
|
|
127
|
+
|
|
128
|
+
The remainder of this document is the original implementation plan
|
|
129
|
+
that drove the work up to 0.12.0. It is preserved verbatim for
|
|
130
|
+
historical context.
|
|
131
|
+
|
|
132
|
+
---
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Mado v1 release roadmap
|
|
2
|
+
|
|
3
|
+
The implementation hardening lands in 0.13.0: safe output ownership,
|
|
4
|
+
serialization, lifecycle teardown, transactional navigation, native forms,
|
|
5
|
+
stable data semantics, the frozen API, devtools, deterministic capture and a
|
|
6
|
+
reproducible package.
|
|
7
|
+
|
|
8
|
+
Release progression:
|
|
9
|
+
|
|
10
|
+
1. Publish 0.13.0 as the final pre-v1 breaking release.
|
|
11
|
+
2. Dogfood it on one static/content site and one authenticated modular app.
|
|
12
|
+
3. Fix discovered contract defects and publish 1.0.0-rc.1.
|
|
13
|
+
4. Publish another RC whenever a public contract changes.
|
|
14
|
+
5. Publish 1.0.0 after an RC has no P0/P1 issues, no API changes and the full
|
|
15
|
+
Node/browser/package matrix is green.
|
|
16
|
+
|
|
17
|
+
The gate is `npm run verify:release`, plus Node 22/24, Chromium/Firefox/WebKit,
|
|
18
|
+
Windows package smoke, a 17 KiB gzip runtime budget and a separately measured
|
|
19
|
+
24 KiB devtools budget. Runtime dependencies remain zero.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Quickstart
|
|
2
|
+
|
|
3
|
+
> Goal: have a Mado app running locally, edited from VS Code, and shipped to
|
|
4
|
+
> a static host — in under 10 minutes.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Scaffold
|
|
9
|
+
|
|
10
|
+
Mado 0.12 ships with two starters: the **universal** starter (default) and
|
|
11
|
+
the **modular** reference architecture.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Universal starter (default) — ~15 files, one shared component
|
|
15
|
+
npm exec --package @madojs/mado -- mado init my-app
|
|
16
|
+
|
|
17
|
+
# Modular reference starter — auth shell, guarded zones, billing module
|
|
18
|
+
npm exec --package @madojs/mado -- mado init my-app --starter modular
|
|
19
|
+
|
|
20
|
+
cd my-app
|
|
21
|
+
npm install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
What you get either way:
|
|
25
|
+
|
|
26
|
+
- exactly one runtime dependency: `@madojs/mado`;
|
|
27
|
+
- `vite.config.ts` with `mado()` from `@madojs/mado/vite`;
|
|
28
|
+
- `src/main.ts` mounting the router into `#app`;
|
|
29
|
+
- `src/app.routes.ts` (a default export + a named `manifest` export);
|
|
30
|
+
- a `package.json` whose scripts wrap the Mado CLI.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 2. The dev loop
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm run dev # Vite dev server, fast HMR for templates/styles
|
|
38
|
+
npm run typecheck # tsc --noEmit
|
|
39
|
+
npm run test # node --test (if your project has tests)
|
|
40
|
+
npm run build # SPA build only (out/ without snapshots)
|
|
41
|
+
npm run release # typecheck + vite build + static snapshots + deploy files
|
|
42
|
+
npm run preview # serve out/ like a real static host
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`mado release` is the single command you ship. It produces one folder,
|
|
46
|
+
`out/`, that you can `rsync`/upload to any static host:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm run release
|
|
50
|
+
rsync -avz out/ user@server:/var/www/my-app/
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
See [20-deployment.md](./20-deployment.md) for nginx / Cloudflare Pages /
|
|
54
|
+
GitHub Pages / S3 recipes.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 3. Your first page
|
|
59
|
+
|
|
60
|
+
Two primitives, that's it (see [10-pages-and-components.md](./10-pages-and-components.md)
|
|
61
|
+
for the full mental model):
|
|
62
|
+
|
|
63
|
+
- **`page()`** — a route. Lives at a URL. Renders in light DOM. Has
|
|
64
|
+
`title` / `head` / `load` / `view` / optional `static`.
|
|
65
|
+
- **`component()`** — a reusable `<x-tag>`. Shadow DOM by default.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// src/pages/home.page.ts
|
|
69
|
+
import { html, page } from "@madojs/mado";
|
|
70
|
+
|
|
71
|
+
export default page({
|
|
72
|
+
title: "Home",
|
|
73
|
+
head: () => ({ description: "Hello from Mado" }),
|
|
74
|
+
view: () => html`<h1>Hello, Mado</h1>`,
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// src/app.routes.ts
|
|
80
|
+
import { routes } from "@madojs/mado";
|
|
81
|
+
|
|
82
|
+
export const manifest = {
|
|
83
|
+
"/": () => import("./pages/home.page"),
|
|
84
|
+
"*": () => import("./pages/not-found.page"),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default routes(manifest);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Always export both `default` (for the router) and `manifest` (so
|
|
91
|
+
`mado static` can discover bakeable pages).
|
|
92
|
+
|
|
93
|
+
Internal links must be base-aware:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { routeUrl } from "@madojs/mado";
|
|
97
|
+
|
|
98
|
+
html`<a data-link href=${routeUrl("/about")}>About</a>`;
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`data-link` lets the router intercept the click — including across
|
|
102
|
+
Shadow DOM. `routeUrl()` resolves against `import.meta.env.BASE_URL`,
|
|
103
|
+
so the same code works whether your app is hosted at `/` or at
|
|
104
|
+
`/docs/`.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 4. IDE setup
|
|
109
|
+
|
|
110
|
+
Out of the box `html` and `css` are tagged-template strings. TypeScript
|
|
111
|
+
and most editors don't highlight them by default. The lit ecosystem's
|
|
112
|
+
tools work because Mado uses the same tag names and binding shapes.
|
|
113
|
+
|
|
114
|
+
### VS Code (recommended)
|
|
115
|
+
|
|
116
|
+
1. **Extensions → install [lit-plugin](https://marketplace.visualstudio.com/items?itemName=runem.lit-plugin)** (by `runem`).
|
|
117
|
+
2. Import `html` / `css` directly — no rename:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { html, css } from "@madojs/mado";
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
3. (Optional) Recommended settings for Mado in `.vscode/settings.json`:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"lit-plugin.rules": {
|
|
128
|
+
"no-unknown-attribute": "off",
|
|
129
|
+
"no-incompatible-type-binding": "off"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`no-incompatible-type-binding: off` is needed because Mado bindings
|
|
135
|
+
accept signals (`Signal<T>`) where the plugin expects raw values.
|
|
136
|
+
|
|
137
|
+
You get: HTML/CSS highlighting inside templates, attribute/event
|
|
138
|
+
auto-complete, go-to-definition for custom elements, typo checking.
|
|
139
|
+
|
|
140
|
+
### WebStorm / IntelliJ
|
|
141
|
+
|
|
142
|
+
Native support for lit-style template literals since 2021. Nothing to
|
|
143
|
+
install. If highlighting is missing, restart the TS server.
|
|
144
|
+
|
|
145
|
+
### Neovim / Helix
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npm install -g lit-html-server
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Wire it into your LSP config (`lit_html` in `lspconfig`).
|
|
152
|
+
|
|
153
|
+
### Optional extras
|
|
154
|
+
|
|
155
|
+
- **Custom Elements Manifest** for auto-complete on your own
|
|
156
|
+
`<x-*>` components: `npx cem analyze --globs "src/**/*.ts"`.
|
|
157
|
+
- **Prettier 3+** formats `html`/`css` template literals via
|
|
158
|
+
`embeddedLanguageFormatting: "auto"` (default).
|
|
159
|
+
- **eslint-plugin-lit** + **eslint-plugin-wc** provide
|
|
160
|
+
template-aware lint rules. Not required.
|
|
161
|
+
|
|
162
|
+
### What does NOT work today
|
|
163
|
+
|
|
164
|
+
- Type-checking of signal bindings: `<input .value=${count}>` is
|
|
165
|
+
flagged because the plugin expects a string, not a `Signal<number>`.
|
|
166
|
+
Suppress with `// @ts-expect-error` or the settings override above.
|
|
167
|
+
- The `each()` directive is recognised as a plain function — no
|
|
168
|
+
special-case checking inside.
|
|
169
|
+
|
|
170
|
+
Mado works without any IDE plugin — the templates remain valid TS,
|
|
171
|
+
they just won't be syntax-highlighted as HTML.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 5. Where to go next
|
|
176
|
+
|
|
177
|
+
- [10 — Pages and components](./10-pages-and-components.md) — the one rule.
|
|
178
|
+
- [11 — Templates and signals](./11-templates-and-signals.md) — binding shapes.
|
|
179
|
+
- [12 — Routing](./12-routing.md) — manifest, layouts, guards, prefetch.
|
|
180
|
+
- [13 — Data](./13-data.md) — `resource()` / `mutation()` / auth.
|
|
181
|
+
- [14 — Forms](./14-forms.md) — `useForm()` + Shadow DOM input recipes.
|
|
182
|
+
- [15 — Static snapshots](./15-static-snapshots.md) — SEO without SSR.
|
|
183
|
+
- [16 — App architecture](./16-app-architecture.md) — the modular starter, ESLint boundaries.
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# Pages and Components
|
|
2
|
+
|
|
3
|
+
> Mado has exactly two primitives. You decide which one to use by
|
|
4
|
+
> looking at the URL bar, not at the DOM tree.
|
|
5
|
+
|
|
6
|
+
This is the document that removes the most common Mado design
|
|
7
|
+
question. By the end of it you should never again have to think
|
|
8
|
+
"should this be a page or a component?", "should this be Shadow DOM
|
|
9
|
+
or Light DOM?", or "how do I share styles across components?". You
|
|
10
|
+
just write the thing.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## The one rule
|
|
15
|
+
|
|
16
|
+
| If the unit is… | Use |
|
|
17
|
+
| -------------------------------------------------------- | ----------- |
|
|
18
|
+
| Something the URL points to (a route, a layout, a 404) | `page()` |
|
|
19
|
+
| Something you would copy-paste under multiple URLs | `component()` |
|
|
20
|
+
|
|
21
|
+
That's it. Pages are the URLs your app exposes. Components are
|
|
22
|
+
re-usable Web Components.
|
|
23
|
+
|
|
24
|
+
A page can render any number of components. A component can never
|
|
25
|
+
participate in routing. Pages live in `src/pages/` (universal
|
|
26
|
+
starter) or in `src/modules/<name>/pages/` (modular starter).
|
|
27
|
+
Components live in `src/components/` or `src/modules/<name>/components/`.
|
|
28
|
+
|
|
29
|
+
You **never** wrap a page in `component()` — that breaks form
|
|
30
|
+
participation, shared CSS and the static snapshot capture. You
|
|
31
|
+
**never** route to a `component()` — only `page()` shows up in the
|
|
32
|
+
router manifest.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## You write the same TypeScript either way
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { html, page, signal } from "@madojs/mado";
|
|
40
|
+
|
|
41
|
+
// PAGE — has a URL, owns route + load + head + view
|
|
42
|
+
export default page({
|
|
43
|
+
title: "Counter",
|
|
44
|
+
view: () => {
|
|
45
|
+
const n = signal(0);
|
|
46
|
+
return html`
|
|
47
|
+
<main>
|
|
48
|
+
<h1>Counter</h1>
|
|
49
|
+
<button @click=${() => n.set(n() + 1)}>Clicks: ${n}</button>
|
|
50
|
+
</main>
|
|
51
|
+
`;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { component, css, html, signal } from "@madojs/mado";
|
|
58
|
+
|
|
59
|
+
// COMPONENT — reusable, has no URL
|
|
60
|
+
component(
|
|
61
|
+
"x-counter",
|
|
62
|
+
() => {
|
|
63
|
+
const n = signal(0);
|
|
64
|
+
return () => html`
|
|
65
|
+
<button @click=${() => n.set(n() + 1)}>Clicks: ${n}</button>
|
|
66
|
+
`;
|
|
67
|
+
},
|
|
68
|
+
{ styles: css`button { padding: .5rem 1rem; }` },
|
|
69
|
+
);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Same signals. Same `html\`\`` templates. Same lifecycle. Same data
|
|
73
|
+
model (`resource()`, `mutation()`, `useForm()`, `effect()`). The
|
|
74
|
+
only thing that changes is *where* the DOM lives.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## What changes under the hood
|
|
79
|
+
|
|
80
|
+
| Property | `page()` | `component()` (default) |
|
|
81
|
+
| ---------------------------------------------- | ------------ | ------------------------------ |
|
|
82
|
+
| DOM location | Light DOM | Open Shadow DOM |
|
|
83
|
+
| Sees global CSS (`shell.css`, `content.css`) | Yes | No (Shadow boundary) |
|
|
84
|
+
| Can use `<slot>` | No (uses `child`) | Yes |
|
|
85
|
+
| Participates in a parent `<form>` | Yes | Only via `attachInternals()` or `shadow:false` |
|
|
86
|
+
| Is captured by `mado static` | If `static: true \| { ... }` | Yes (DSD serialised) |
|
|
87
|
+
| Has a hyphenated custom-element tag | No | Yes (`x-foo`, `my-bar`) |
|
|
88
|
+
| Owns its own CSS isolation | No | Yes (via `css\`\``) |
|
|
89
|
+
|
|
90
|
+
That is the entire decision matrix. Anything beyond it is detail.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## `{ shadow: false }` — the only escape hatch you need
|
|
95
|
+
|
|
96
|
+
Sometimes a custom element MUST live in the light DOM. Two real
|
|
97
|
+
cases:
|
|
98
|
+
|
|
99
|
+
1. **Form participation without `attachInternals()`.** A custom
|
|
100
|
+
input that should be submitted as part of a regular `<form>` and
|
|
101
|
+
whose author does not want to wire up `ElementInternals`.
|
|
102
|
+
2. **Host-level CSS that the document must address by tag name**
|
|
103
|
+
(rare; usually solved by passing class attributes instead).
|
|
104
|
+
|
|
105
|
+
For both, declare `{ shadow: false }`:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
component(
|
|
109
|
+
"x-custom-input",
|
|
110
|
+
() => () => html`<input name="email" />`,
|
|
111
|
+
{ shadow: false, styles: css`x-custom-input input { width: 100%; }` },
|
|
112
|
+
);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
That is the *only* place this option is justified. If you reach for
|
|
116
|
+
`shadow: false` because you want to share document-level CSS classes,
|
|
117
|
+
**stop** — you wanted a `page()`, not a component.
|
|
118
|
+
|
|
119
|
+
Page-shaped wrappers (layouts, route shells) are written with
|
|
120
|
+
`page({ view: ({ child }) => ... })`. They are not components.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Decision table
|
|
125
|
+
|
|
126
|
+
| You are writing… | Reach for |
|
|
127
|
+
| --------------------------------------------- | --------------------------------------------- |
|
|
128
|
+
| A landing page | `page({ static: true, view })` |
|
|
129
|
+
| A dynamic SEO page (`/product/:slug`) | `page({ static: { paths, initialData } })` |
|
|
130
|
+
| An app screen behind auth | `page({ view })` (no `static`) |
|
|
131
|
+
| A shared shell that wraps several pages | `page({ view: ({ child }) => html\`...\` })` |
|
|
132
|
+
| A reusable button / badge / card / modal | `component("x-foo", setup, { styles })` |
|
|
133
|
+
| A custom form input | `component("x-input", setup, { shadow: false })` |
|
|
134
|
+
| A small inline render helper (no state) | a plain `(arg) => html\`...\`` function |
|
|
135
|
+
|
|
136
|
+
When in doubt, ask: **does this thing have a URL?** Yes → page.
|
|
137
|
+
No → component.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Anti-patterns
|
|
142
|
+
|
|
143
|
+
These are the four mistakes Mado generators and AI assistants tend
|
|
144
|
+
to produce. Avoid them.
|
|
145
|
+
|
|
146
|
+
### 1. Page inside a `component()`
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// ❌ Don't
|
|
150
|
+
component(
|
|
151
|
+
"x-home-page",
|
|
152
|
+
() => () => html`<h1>Home</h1><p>...</p>`,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// ✅ Do
|
|
156
|
+
export default page({
|
|
157
|
+
static: true,
|
|
158
|
+
title: "Home",
|
|
159
|
+
view: () => html`<h1>Home</h1><p>...</p>`,
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Why: the page becomes invisible to the router, the static snapshot
|
|
164
|
+
pipeline cannot find it, and shared CSS (`content.css`) does not
|
|
165
|
+
cross the Shadow boundary.
|
|
166
|
+
|
|
167
|
+
## Synchronous `page.load`
|
|
168
|
+
|
|
169
|
+
`page.load(params, seed)` must return a value or a `Resource` synchronously.
|
|
170
|
+
Returning a Promise is rejected because navigation commit and rollback need a
|
|
171
|
+
clear lifecycle owner. Put asynchronous work in `resource()` and render its
|
|
172
|
+
`loading()`, `error()` and `data()` signals. Guards may remain async; head/title
|
|
173
|
+
are committed only after guards succeed.
|
|
174
|
+
|
|
175
|
+
### 2. Component used as a route
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
// ❌ Don't
|
|
179
|
+
import "./components/billing-screen.component.js";
|
|
180
|
+
const manifest = { "/billing": () => html`<x-billing-screen/>` };
|
|
181
|
+
|
|
182
|
+
// ✅ Do
|
|
183
|
+
import billing from "./pages/billing.page.js";
|
|
184
|
+
const manifest = { "/billing": billing };
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Why: the router gives you `page.head()`, `page.load()`, the static
|
|
188
|
+
snapshot pipeline, the `data-mado-head` lifecycle and the seed
|
|
189
|
+
contract. A component does not.
|
|
190
|
+
|
|
191
|
+
### 3. `shadow: false` to "fix" CSS in a route layout
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
// ❌ Don't — this is a page wearing the wrong hat
|
|
195
|
+
component(
|
|
196
|
+
"x-app-shell",
|
|
197
|
+
() => ({ child }) => html`<header>...</header><main>${child}</main>`,
|
|
198
|
+
{ shadow: false, styles: css`...` },
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// ✅ Do
|
|
202
|
+
export default page({
|
|
203
|
+
view: ({ child }) => html`<header>...</header><main>${child}</main>`,
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Why: light-DOM components do not get the route `child`, are not
|
|
208
|
+
recognised by `layout()`, and the manifest cannot wrap them.
|
|
209
|
+
|
|
210
|
+
### 4. Route-id state in layout view locals
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
// ❌ Don't — `current` is shared across every page rendered by this layout
|
|
214
|
+
const current = signal<User | null>(null);
|
|
215
|
+
export default page({
|
|
216
|
+
view: ({ child }) => {
|
|
217
|
+
effect(() => loadUser(routePath()).then(current.set));
|
|
218
|
+
return html`<x-shell .user=${current}>${child}</x-shell>`;
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ✅ Do — put the resource in the page that needs it
|
|
223
|
+
const user = resource(() => `/api/users/${userId()}`, jsonFetcher<User>());
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Layouts are stateless wrappers. Per-page state belongs in the page
|
|
227
|
+
itself or in a `resource()` whose key is the page's identity.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## What about styles?
|
|
232
|
+
|
|
233
|
+
- `page()` lives in the light DOM. Use `content.css` / `shell.css`
|
|
234
|
+
from `src/styles/` (universal starter) or `src/shared/styles/`
|
|
235
|
+
(modular starter). Class selectors work normally.
|
|
236
|
+
- `component()` carries its own `css\`\``. CSS custom properties
|
|
237
|
+
(`--accent`, `--bg`) cross the Shadow boundary; class selectors
|
|
238
|
+
from the document do not.
|
|
239
|
+
- The shared design tokens (colours, spacing, type) live in
|
|
240
|
+
`tokens.css`. They are CSS custom properties and reach both pages
|
|
241
|
+
and components through `var(--...)`.
|
|
242
|
+
|
|
243
|
+
For the long form on style boundaries, see
|
|
244
|
+
[20-deployment.md](./20-deployment.md) (production tuning) and the
|
|
245
|
+
starter README files.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## What about routing and links?
|
|
250
|
+
|
|
251
|
+
Internal navigation is base-aware. Always use `routeUrl()` and
|
|
252
|
+
`data-link`:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { html, routeUrl } from "@madojs/mado";
|
|
256
|
+
|
|
257
|
+
html`<a data-link href=${routeUrl("/billing/invoices")}>Invoices</a>`;
|
|
258
|
+
html`<a data-link href=${routeUrl("/")}>Home</a>`; // → "/mado/" under base
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`data-link` opts the anchor into SPA navigation. A bare `<a href>`
|
|
262
|
+
performs a full document load — that is intentional for foreign
|
|
263
|
+
links and downloads. The router intercepts links inside Shadow DOM
|
|
264
|
+
too (it uses `event.composedPath()`).
|
|
265
|
+
|
|
266
|
+
Full router contract: [12-routing.md](./12-routing.md).
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Further reading
|
|
271
|
+
|
|
272
|
+
- [11-templates-and-signals.md](./11-templates-and-signals.md) — the
|
|
273
|
+
`html\`\`` parser, signals, `each`, reactive bindings.
|
|
274
|
+
- [12-routing.md](./12-routing.md) — `routes()`, layouts, guards,
|
|
275
|
+
`routeUrl`, `navigate`, prefetch.
|
|
276
|
+
- [15-static-snapshots.md](./15-static-snapshots.md) — when to mark
|
|
277
|
+
a page `static: true`.
|
|
278
|
+
- [14-forms.md](./14-forms.md) — `useForm()` + the only Shadow DOM
|
|
279
|
+
case where `shadow: false` is right.
|