@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
package/llms.txt
CHANGED
|
@@ -1,281 +1,316 @@
|
|
|
1
1
|
# Mado
|
|
2
2
|
|
|
3
|
-
> A calm
|
|
4
|
-
>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- **
|
|
20
|
-
|
|
21
|
-
|
|
3
|
+
> A calm native-first web framework for both static sites and live
|
|
4
|
+
> SPAs. One Web Component model, one page model, one release command.
|
|
5
|
+
> Browser-native source, Vite as the canonical development and delivery
|
|
6
|
+
> transport, zero runtime dependencies.
|
|
7
|
+
|
|
8
|
+
Current stable release: **0.12.0**.
|
|
9
|
+
|
|
10
|
+
Mado deliberately rejects compilers (no JSX, no special template
|
|
11
|
+
language), VDOM diffing, and SSR/hydration. Static documents are
|
|
12
|
+
captured by a real headless Chromium during `mado release`, including
|
|
13
|
+
the Shadow DOM through Declarative Shadow DOM (DSD), and the live SPA
|
|
14
|
+
then atomically replaces the static tree with no hydration protocol or
|
|
15
|
+
node reconciliation.
|
|
16
|
+
|
|
17
|
+
## What an AI assistant must never invent
|
|
18
|
+
|
|
19
|
+
- **This is NOT React, NOT Next, NOT Lit, NOT Solid.** Do not write
|
|
20
|
+
JSX, `useState`, `useEffect`, decorators, classes-with-render or
|
|
21
|
+
`requestUpdate`. Those patterns will not compile.
|
|
22
|
+
- **Vite is the canonical transport.** Every starter, the CLI and the
|
|
23
|
+
static snapshot pipeline run through `@madojs/mado/vite`. The
|
|
24
|
+
framework runtime reads `import.meta.env.BASE_URL` once and exposes
|
|
25
|
+
it through `appBase` / `routeUrl()`.
|
|
26
|
+
<!-- docs-lint:allow-legacy-mention -->
|
|
27
|
+
- **There is no `bake` API.** The old `bake.paths`, `bake.data`,
|
|
28
|
+
`bake.revalidate`, `npm run bake`, "Smart Static" and "no Chromium"
|
|
29
|
+
language refer to a removed system. A page becomes static by
|
|
30
|
+
declaring `static: true` (or `static: { paths, initialData }`); the
|
|
31
|
+
CLI command is `mado static`, normally invoked as part of
|
|
32
|
+
`mado release`.
|
|
33
|
+
<!-- /docs-lint:allow-legacy-mention -->
|
|
34
|
+
- **`page()` is a semantic document.** It renders in the light DOM and
|
|
35
|
+
owns route + load + head + view + optional static declaration.
|
|
36
|
+
`component()` is the unit of Web Component encapsulation; by default
|
|
37
|
+
it attaches an open Shadow DOM. `{ shadow: false }` exists as an
|
|
38
|
+
advanced escape hatch when a custom element MUST live in the light
|
|
39
|
+
DOM (e.g. to participate in a parent form). Pages are never built
|
|
40
|
+
with `component()` and components never participate in routing.
|
|
41
|
+
<!-- docs-lint:allow-legacy-mention -->
|
|
42
|
+
- **A compatible Chromium is required at release time.** `mado static`
|
|
43
|
+
spawns Playwright (`playwright-core`) and captures real DOM. There is
|
|
44
|
+
no `linkedom`-based renderer, no `npm run bake`, no "meta-shell".
|
|
45
|
+
<!-- /docs-lint:allow-legacy-mention -->
|
|
22
46
|
|
|
23
47
|
## Critical template rules
|
|
24
48
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
// ❌ throws: RAW_TEXT elements cannot host child slots
|
|
59
|
-
html`<textarea>${draft}</textarea>`;
|
|
60
|
-
|
|
61
|
-
// ✅ bind the DOM property instead
|
|
62
|
-
html`<textarea .value=${draft}></textarea>`;
|
|
63
|
-
|
|
64
|
-
// ❌ throws: nested SVG template loses namespace context
|
|
65
|
-
html`<svg>${html`<circle r="5"></circle>`}</svg>`;
|
|
66
|
-
|
|
67
|
-
// ✅ keep SVG internals in one template
|
|
68
|
-
html`<svg viewBox="0 0 10 10"><circle r="5"></circle></svg>`;
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Canonical imports
|
|
49
|
+
`html\`...\`` is a tagged template parser, not JSX. Only these bindings
|
|
50
|
+
are recognised:
|
|
51
|
+
|
|
52
|
+
- `${value}` — child content (text, nodes, arrays, nested templates, `each`)
|
|
53
|
+
- `attr=${v}` — HTML attribute (string/number)
|
|
54
|
+
- `.prop=${v}` — DOM property (objects, arrays, numbers without serialisation)
|
|
55
|
+
- `?attr=${flag}` — boolean attribute (toggle)
|
|
56
|
+
- `@event=${fn}` — event listener
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// ❌ not reactive — `count()` runs once at render
|
|
60
|
+
html`<div>${count() * 2}</div>`
|
|
61
|
+
|
|
62
|
+
// ✅ subscription — the function is re-run on signal change
|
|
63
|
+
html`<div>${() => count() * 2}</div>`
|
|
64
|
+
|
|
65
|
+
// ✅ the signal itself works because it is already a function
|
|
66
|
+
html`<div>${count}</div>`
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Lists must use `each(items, keyFn, renderFn)` for stable identity:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { each } from "@madojs/mado";
|
|
73
|
+
html`<ul>${() => each(users(), (u) => u.id, (u) => html`<li>${u.name}</li>`)}</ul>`
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The parser refuses dynamic `${...}` child slots inside `<script>`,
|
|
77
|
+
`<style>`, `<textarea>`, `<title>`, and refuses nested SVG-only `html`
|
|
78
|
+
templates inside an outer `<svg>` (namespace context is lost).
|
|
79
|
+
|
|
80
|
+
## Public API surface
|
|
72
81
|
|
|
73
82
|
```ts
|
|
74
|
-
// Everything you normally need — from one place:
|
|
75
83
|
import {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
// reactivity
|
|
85
|
+
signal, computed, effect, untracked, batch, flushSync,
|
|
86
|
+
// rendering + templates
|
|
87
|
+
html, render, each, list, unsafeHTML, ref, classMap, styleMap,
|
|
88
|
+
// components
|
|
89
|
+
component, css, cssVars,
|
|
90
|
+
// routing
|
|
81
91
|
routes, router, navigate, queryParam, prefetchPath,
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
routeUrl, appBase,
|
|
93
|
+
// pages
|
|
94
|
+
page, layout, applyHead,
|
|
95
|
+
// data
|
|
96
|
+
resource, mutation, invalidate, jsonFetcher, HttpError,
|
|
97
|
+
// forms / context / persistence / lazy
|
|
84
98
|
useForm,
|
|
85
99
|
createContext, provide, inject,
|
|
86
|
-
persisted,
|
|
87
|
-
lazy,
|
|
100
|
+
persisted, lazy,
|
|
88
101
|
} from "@madojs/mado";
|
|
102
|
+
|
|
103
|
+
// Side-effect-only:
|
|
104
|
+
import "@madojs/mado/devtools.js";
|
|
105
|
+
|
|
106
|
+
// Vite plugin:
|
|
107
|
+
import { mado } from "@madojs/mado/vite";
|
|
89
108
|
```
|
|
90
109
|
|
|
91
|
-
|
|
110
|
+
Everything else under the package is internal and subject to change
|
|
111
|
+
without notice (see `docs/en/30-api-freeze-map.md`).
|
|
112
|
+
|
|
113
|
+
## Pages and the static snapshot pipeline
|
|
114
|
+
|
|
115
|
+
A page declares its participation in static capture through the
|
|
116
|
+
`static` field on `page({ ... })`:
|
|
92
117
|
|
|
93
118
|
```ts
|
|
94
|
-
|
|
95
|
-
const variant = attr("variant", "default"); // Signal<string>, auto-updates
|
|
96
|
-
const size = attr("size", "md");
|
|
119
|
+
import { html, page } from "@madojs/mado";
|
|
97
120
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
121
|
+
export default page({
|
|
122
|
+
static: true, // capture as HTML at release
|
|
123
|
+
title: "Mado Keyboard",
|
|
124
|
+
head: () => ({ description: "Canonical product page." }),
|
|
125
|
+
view: () => html`<h1>Welcome</h1>`,
|
|
101
126
|
});
|
|
102
127
|
```
|
|
103
128
|
|
|
104
|
-
|
|
105
|
-
plain attribute changes and the component re-renders reactively.
|
|
129
|
+
Dynamic static routes provide `paths()` and `initialData()`:
|
|
106
130
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
131
|
+
```ts
|
|
132
|
+
export default page({
|
|
133
|
+
static: {
|
|
134
|
+
paths: async () => [{ slug: "alpha" }, { slug: "beta" }],
|
|
135
|
+
initialData: async ({ slug }) => loadGuide(slug),
|
|
136
|
+
},
|
|
137
|
+
title: ({ slug }) => `Guide: ${slug}`,
|
|
138
|
+
view: (ctx) => html`<article>${ctx.data}</article>`,
|
|
139
|
+
});
|
|
140
|
+
```
|
|
110
141
|
|
|
111
|
-
|
|
112
|
-
```ts
|
|
113
|
-
Object.defineProperty(host, "name", { get: () => host.getAttribute("name") ?? "" });
|
|
114
|
-
Object.defineProperty(host, "value", { get: () => host.shadowRoot?.querySelector("input")?.value ?? "" });
|
|
115
|
-
```
|
|
142
|
+
`mado release` does the full pipeline:
|
|
116
143
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
144
|
+
1. Vite builds the SPA.
|
|
145
|
+
2. `mado static` discovers every `static: true | { ... }` page through
|
|
146
|
+
the routes manifest.
|
|
147
|
+
3. A real headless Chromium navigates each page through an internal
|
|
148
|
+
capture server.
|
|
149
|
+
4. The captured HTML — including DSD-serialised Shadow DOM, canonical /
|
|
150
|
+
`og:url` fallback and the seed `<script type="application/json">` —
|
|
151
|
+
is written into `out/<route>/index.html`.
|
|
152
|
+
5. SPA-only routes keep the `_mado/spa.html` fallback.
|
|
153
|
+
6. The internal `_mado/build.json` bridge is dropped from the deployed
|
|
154
|
+
artefact.
|
|
121
155
|
|
|
122
|
-
|
|
156
|
+
On first paint the live SPA takes over atomically: Mado replaces the
|
|
157
|
+
static tree with the live tree, no hydration protocol or node
|
|
158
|
+
reconciliation. The seed JSON is consumed once so `page.load()` returns
|
|
159
|
+
the same data the server already rendered.
|
|
123
160
|
|
|
124
|
-
##
|
|
161
|
+
## Internal links and base path
|
|
125
162
|
|
|
126
|
-
|
|
163
|
+
Vite's `base` flows automatically into `appBase` and `routeUrl()`. App
|
|
164
|
+
code must use `routeUrl()` for every internal anchor so the URL stays
|
|
165
|
+
correct under `/`, `/mado/` or any sub-path deployment:
|
|
127
166
|
|
|
128
167
|
```ts
|
|
129
|
-
import {
|
|
130
|
-
import { apiFetcher } from "../lib/api.js";
|
|
168
|
+
import { routeUrl } from "@madojs/mado";
|
|
131
169
|
|
|
132
|
-
|
|
170
|
+
html`<a data-link href=${routeUrl("/users/42")}>User</a>`;
|
|
171
|
+
html`<a data-link href=${routeUrl("/")}>Home</a>`; // → "/mado/"
|
|
133
172
|
```
|
|
134
173
|
|
|
135
|
-
|
|
136
|
-
|
|
174
|
+
The router only intercepts anchors that opt in with `data-link`. Bare
|
|
175
|
+
`<a href>` performs a full document load (intentional, for foreign URLs
|
|
176
|
+
and downloads).
|
|
177
|
+
|
|
178
|
+
## Components are Shadow DOM by default
|
|
137
179
|
|
|
138
|
-
|
|
180
|
+
```ts
|
|
181
|
+
import { component, css, html, signal } from "@madojs/mado";
|
|
139
182
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
183
|
+
component(
|
|
184
|
+
"x-counter",
|
|
185
|
+
() => {
|
|
186
|
+
const n = signal(0);
|
|
187
|
+
return () => html`<button @click=${() => n.set(n() + 1)}>${n}</button>`;
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
styles: css`
|
|
191
|
+
:host { display: inline-block; }
|
|
192
|
+
button { padding: .5rem 1rem; }
|
|
193
|
+
`,
|
|
194
|
+
},
|
|
195
|
+
);
|
|
196
|
+
```
|
|
147
197
|
|
|
148
|
-
|
|
198
|
+
- Custom element names MUST include a hyphen.
|
|
199
|
+
- Setup runs once per host; the returned function is the reactive view.
|
|
200
|
+
- `ctx.attr("name", "default")` returns a `Signal<string>` that
|
|
201
|
+
auto-updates on attribute changes.
|
|
202
|
+
- `ctx.onDispose(fn)` registers cleanup for raw APIs (`setInterval`,
|
|
203
|
+
`WebSocket`); `resource()` / `effect()` clean themselves up.
|
|
204
|
+
- `{ shadow: false }` exists for components that MUST live in the light
|
|
205
|
+
DOM (form participation, host-level styling). It is an escape hatch,
|
|
206
|
+
not the default.
|
|
149
207
|
|
|
150
|
-
|
|
151
|
-
route maps; app routes decide which layout and guard wrap each zone. A layout
|
|
152
|
-
view should be a pure wrapper around `${child}` and shared chrome:
|
|
208
|
+
## Resources and mutations
|
|
153
209
|
|
|
154
210
|
```ts
|
|
155
|
-
|
|
156
|
-
|
|
211
|
+
const users = resource(
|
|
212
|
+
() => `/api/users?page=${page()}`,
|
|
213
|
+
jsonFetcher<User[]>(),
|
|
214
|
+
{ staleTime: 30_000 },
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const create = mutation((u) => api.create(u), {
|
|
218
|
+
invalidates: ["/api/users*"],
|
|
157
219
|
});
|
|
158
220
|
```
|
|
159
221
|
|
|
160
|
-
|
|
161
|
-
|
|
222
|
+
The key is cache identity: same key → shared cache and de-duped
|
|
223
|
+
in-flight request. `mutation().run()` is concurrent by default;
|
|
224
|
+
`{ abortPrevious: true }` keeps only the latest in flight.
|
|
225
|
+
`invalidates` runs after a successful mutation and is best-effort.
|
|
226
|
+
|
|
227
|
+
## Static snapshot constraints
|
|
162
228
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
229
|
+
- `paths()` and `initialData()` run during discovery AND ship in the
|
|
230
|
+
client bundle. Keep them browser-safe; never read secrets.
|
|
231
|
+
- Pages marked `static` cannot use guards (route- or layout-level) —
|
|
232
|
+
they are public by definition.
|
|
233
|
+
- `*` and other wildcard routes cannot be static.
|
|
234
|
+
- Discovery uses Vite SSR as a control plane only; nothing is rendered
|
|
235
|
+
in Node. The actual capture happens in headless Chromium.
|
|
167
236
|
|
|
168
|
-
## Canonical "
|
|
237
|
+
## Canonical "hello world"
|
|
169
238
|
|
|
170
239
|
```ts
|
|
171
240
|
// src/main.ts
|
|
172
241
|
import { html, render } from "@madojs/mado";
|
|
173
|
-
import
|
|
174
|
-
render(html`${
|
|
242
|
+
import appRoutes from "./app.routes";
|
|
243
|
+
render(html`${appRoutes.view}`, document.getElementById("app")!);
|
|
175
244
|
|
|
176
245
|
// src/app.routes.ts
|
|
177
246
|
import { routes } from "@madojs/mado";
|
|
247
|
+
import home from "./pages/home.page";
|
|
178
248
|
export const manifest = {
|
|
179
|
-
"/":
|
|
180
|
-
"*":
|
|
249
|
+
"/": home,
|
|
250
|
+
"*": () => import("./pages/not-found.page"),
|
|
181
251
|
};
|
|
182
252
|
export default routes(manifest);
|
|
183
253
|
|
|
184
|
-
// src/
|
|
185
|
-
import {
|
|
186
|
-
|
|
187
|
-
component("x-counter", () => {
|
|
188
|
-
const count = signal(0);
|
|
189
|
-
return () => html`
|
|
190
|
-
<button @click=${() => count.update(n => n + 1)}>${count}</button>
|
|
191
|
-
`;
|
|
192
|
-
}, { styles: css`button { padding: .5rem 1rem; }` });
|
|
193
|
-
|
|
254
|
+
// src/pages/home.page.ts
|
|
255
|
+
import { html, page, routeUrl } from "@madojs/mado";
|
|
194
256
|
export default page({
|
|
257
|
+
static: true,
|
|
195
258
|
title: "Home",
|
|
196
|
-
view: () => html
|
|
259
|
+
view: () => html`
|
|
260
|
+
<main>
|
|
261
|
+
<h1>Mado App</h1>
|
|
262
|
+
<a data-link href=${routeUrl("/about")}>About</a>
|
|
263
|
+
</main>
|
|
264
|
+
`,
|
|
197
265
|
});
|
|
198
266
|
```
|
|
199
267
|
|
|
200
|
-
##
|
|
201
|
-
|
|
202
|
-
```ts
|
|
203
|
-
// main.ts: global shell/provider components only
|
|
204
|
-
import "./components/app-shell.js";
|
|
205
|
-
|
|
206
|
-
// pages/tickets.ts: feature/page-owned components
|
|
207
|
-
import "../components/ticket-list.js";
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
Do not bulk-import every component in `main.ts` "just in case". Import the
|
|
211
|
-
registration near the route/page that owns the tag. This preserves lazy loading
|
|
212
|
-
and makes the code readable to humans and LLMs.
|
|
268
|
+
## Starters
|
|
213
269
|
|
|
214
|
-
|
|
270
|
+
- `mado init my-app` — universal starter (default). One Shadow Component
|
|
271
|
+
shared between a static landing page and a live SPA route. Folders:
|
|
272
|
+
`src/pages`, `src/components`, `src/content`.
|
|
273
|
+
- `mado init my-app --starter modular` — long-lived reference
|
|
274
|
+
architecture: auth shell, guarded zones, billing module, HTTP client,
|
|
275
|
+
module boundaries. Folders: `src/modules/<name>/`, `src/layouts/`,
|
|
276
|
+
`src/shared/`. A dev-only mock API is mounted from
|
|
277
|
+
`vite.config.ts`; disable with `MADO_MOCK_API=0`.
|
|
215
278
|
|
|
216
|
-
|
|
217
|
-
import { page, html, resource, mutation, each, useForm, navigate } from "@madojs/mado";
|
|
218
|
-
import { api } from "../lib/api.js";
|
|
279
|
+
## Documentation
|
|
219
280
|
|
|
220
|
-
|
|
221
|
-
const create = mutation((u) => api.create(u), { invalidates: ["/api/users*"] });
|
|
281
|
+
Canonical docs (English only): https://github.com/madojs/mado/tree/main/docs/en
|
|
222
282
|
|
|
223
|
-
|
|
224
|
-
view: () => {
|
|
225
|
-
const f = useForm({ name: { required: true } });
|
|
226
|
-
return html`
|
|
227
|
-
<form @submit=${f.onSubmit(async v => { await create.run(v); navigate("/"); })}>
|
|
228
|
-
<input name="name" @input=${f.onInput}>
|
|
229
|
-
<button ?disabled=${() => !f.isValid()}>Create</button>
|
|
230
|
-
</form>
|
|
231
|
-
${() => each(users.data() ?? [], u => u.id, u => html`<li>${u.name}</li>`)}
|
|
232
|
-
`;
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
```
|
|
283
|
+
Highlights:
|
|
236
284
|
|
|
237
|
-
|
|
285
|
+
- `docs/en/00-the-mado-way.md` — philosophy and boundaries
|
|
286
|
+
- `docs/en/01-quickstart.md` — scaffold + dev loop + IDE setup
|
|
287
|
+
- `docs/en/10-pages-and-components.md` — the one rule (page vs component)
|
|
288
|
+
- `docs/en/11-templates-and-signals.md` — bindings, signals, lifecycle
|
|
289
|
+
- `docs/en/12-routing.md` — router, layouts, guards, `routeUrl()`
|
|
290
|
+
- `docs/en/13-data.md` — `resource()` / `mutation()` / auth
|
|
291
|
+
- `docs/en/14-forms.md` — `useForm()` + Shadow DOM inputs
|
|
292
|
+
- `docs/en/15-static-snapshots.md` — static snapshots guide
|
|
293
|
+
- `docs/en/16-app-architecture.md` — canonical app layout
|
|
294
|
+
- `docs/en/20-deployment.md` — deployment recipes (including base)
|
|
295
|
+
- `docs/en/23-cookbook.md` — static snapshots cookbook
|
|
296
|
+
- `docs/en/30-api-freeze-map.md` — stable public API vs internal
|
|
297
|
+
- `docs/en/40-llm-guide.md` — LLM pitfalls and zero-history test
|
|
238
298
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
- docs/en/01-routing.md — detailed router documentation
|
|
242
|
-
- docs/en/02-project-layout.md — recommended project structure
|
|
243
|
-
- docs/en/03-static-bake.md — SEO without SSR (static build / edge prerender)
|
|
244
|
-
- docs/en/04-ide-setup.md — VS Code / WebStorm configuration
|
|
245
|
-
- docs/en/05-why-mado.md — honest comparison with Lit / Solid / Svelte / htmx / Alpine / React
|
|
246
|
-
- docs/en/06-for-backenders.md — mental model in 10 minutes for Go/Rust/.NET/Java developers
|
|
247
|
-
- docs/en/07-llm-pitfalls.md — common mistakes when generating Mado code
|
|
248
|
-
- docs/en/10-app-architecture.md — canonical app file layout, routes, auth/API and release shape
|
|
249
|
-
- docs/en/11-layouts.md — blessed layout recipe
|
|
250
|
-
- docs/en/12-auth-and-api.md — blessed auth/API client recipe
|
|
251
|
-
- docs/en/13-deployment.md — deployment recipes and cache rules
|
|
252
|
-
- docs/en/14-testing.md — testing strategy and commands
|
|
253
|
-
- docs/en/15-error-handling.md — route/data/action error boundaries
|
|
254
|
-
- docs/en/16-bake-cookbook.md — static bake recipes and failure modes
|
|
255
|
-
- docs/en/17-shadow-dom-forms.md — Shadow DOM + useForm() patterns (proxy properties, form submit bridge)
|
|
256
|
-
- docs/en/18-api-freeze-map.md — stable public API vs internal implementation details
|
|
257
|
-
- docs/en/19-reactivity-ordering.md — signal ordering, batching and teardown guarantees
|
|
258
|
-
- docs/en/20-v1-stability.md — v1 SemVer contract and what remains internal
|
|
259
|
-
- starters/default/ — canonical generated app shape
|
|
299
|
+
The repository is English-only. Older `docs/fr`, `docs/uk`, `docs/ru`
|
|
300
|
+
trees were removed at v0.12 and survive only in git history.
|
|
260
301
|
|
|
261
302
|
## What Mado does NOT do (intentionally)
|
|
262
303
|
|
|
263
|
-
- ❌ JSX
|
|
264
|
-
- ❌ Virtual DOM
|
|
265
|
-
- ❌ SSR with hydration
|
|
266
|
-
- ❌
|
|
267
|
-
- ❌
|
|
268
|
-
- ❌
|
|
269
|
-
- ❌ React-
|
|
270
|
-
- ❌
|
|
271
|
-
- ❌
|
|
272
|
-
- ❌
|
|
273
|
-
|
|
274
|
-
## Version
|
|
275
|
-
|
|
276
|
-
`0.10.0` — pre-1.0 API-lock release. Phase B closed the public surface,
|
|
277
|
-
package exports, docs and CI gates. SemVer is not guaranteed on minor versions
|
|
278
|
-
before 1.0.
|
|
304
|
+
- ❌ JSX → tagged template `html\`...\``
|
|
305
|
+
- ❌ Virtual DOM → fine-grained signal updates
|
|
306
|
+
- ❌ SSR with hydration → browser-rendered static snapshots + atomic takeover
|
|
307
|
+
- ❌ React-style hooks → signals
|
|
308
|
+
- ❌ Framework-specific compiler → none; the source is plain TS / Web Components
|
|
309
|
+
- ❌ Runtime framework dependencies → zero (Vite is dev/build tooling)
|
|
310
|
+
- ❌ React-Router / TanStack → built-in `routes()`
|
|
311
|
+
- ❌ React-Query / SWR → built-in `resource()` / `mutation()`
|
|
312
|
+
- ❌ Formik / RHF → built-in `useForm()`
|
|
313
|
+
- ❌ Redux / Zustand → `signal()` + `createContext`
|
|
279
314
|
|
|
280
315
|
## License
|
|
281
316
|
|
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@madojs/mado",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.12.0",
|
|
4
|
+
"description": "A calm native-first web framework for sites and apps. Web Components, signals, routing, data, forms and browser-rendered static snapshots. Zero runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"framework",
|
|
9
|
-
"
|
|
9
|
+
"native-first",
|
|
10
10
|
"web-components",
|
|
11
|
+
"declarative-shadow-dom",
|
|
11
12
|
"signals",
|
|
12
13
|
"tagged-templates",
|
|
13
14
|
"router",
|
|
14
15
|
"vite",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"solid-alternative"
|
|
16
|
+
"static-snapshot",
|
|
17
|
+
"spa",
|
|
18
|
+
"minimal"
|
|
19
19
|
],
|
|
20
20
|
"homepage": "https://github.com/madojs/mado#readme",
|
|
21
21
|
"repository": {
|
|
@@ -53,20 +53,23 @@
|
|
|
53
53
|
"README.md",
|
|
54
54
|
"LICENSE",
|
|
55
55
|
"CHANGELOG.md",
|
|
56
|
-
"TODO.md",
|
|
57
56
|
"AGENTS.md",
|
|
58
57
|
"llms.txt",
|
|
59
|
-
"docs
|
|
58
|
+
"docs/README.md",
|
|
59
|
+
"docs/en/**/*.md",
|
|
60
|
+
"docs/architecture/**/*.md",
|
|
61
|
+
"docs/recipes/**/*"
|
|
60
62
|
],
|
|
61
63
|
"scripts": {
|
|
62
64
|
"build": "node scripts/cli.mjs build",
|
|
63
65
|
"watch": "node scripts/cli.mjs watch",
|
|
64
66
|
"dev": "node scripts/cli.mjs dev",
|
|
65
|
-
"
|
|
67
|
+
"static": "node scripts/cli.mjs static",
|
|
66
68
|
"preview": "node scripts/cli.mjs preview",
|
|
67
69
|
"new": "node scripts/cli.mjs new",
|
|
68
70
|
"size": "node scripts/size-budget.mjs",
|
|
69
71
|
"package:smoke": "node scripts/package-smoke.mjs",
|
|
72
|
+
"docs:lint": "node scripts/docs-lint.mjs",
|
|
70
73
|
"test": "node scripts/cli.mjs test",
|
|
71
74
|
"typecheck": "node scripts/cli.mjs typecheck",
|
|
72
75
|
"clean": "rm -rf dist out",
|
|
@@ -82,9 +85,13 @@
|
|
|
82
85
|
"vite": "^8.0.16"
|
|
83
86
|
},
|
|
84
87
|
"peerDependencies": {
|
|
88
|
+
"playwright-core": "^1.47.0",
|
|
85
89
|
"vite": "^8.0.0"
|
|
86
90
|
},
|
|
87
91
|
"peerDependenciesMeta": {
|
|
92
|
+
"playwright-core": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
88
95
|
"vite": {
|
|
89
96
|
"optional": true
|
|
90
97
|
}
|