@madojs/mado 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +24 -26
- package/CHANGELOG.md +95 -0
- package/README.md +22 -47
- package/TODO.md +52 -48
- package/dist/src/component.d.ts +2 -1
- package/dist/src/component.js +5 -2
- package/dist/src/component.js.map +1 -1
- package/dist/src/each.d.ts +1 -1
- package/dist/src/each.js +1 -1
- package/dist/src/each.js.map +1 -1
- package/dist/src/html/bindings.js +3 -3
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/index.d.ts +11 -6
- package/dist/src/index.js +5 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/lazy.d.ts +1 -1
- package/dist/src/lazy.js +1 -1
- package/dist/src/lazy.js.map +1 -1
- package/dist/src/page.d.ts +17 -21
- package/dist/src/page.js +7 -12
- package/dist/src/page.js.map +1 -1
- package/dist/src/router/manifest.d.ts +1 -1
- package/dist/src/router/manifest.js +21 -13
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +2 -2
- package/dist/src/router/match.js +3 -3
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.js +1 -1
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/vite/index.d.ts +10 -0
- package/dist/src/vite/index.js +33 -0
- package/dist/src/vite/index.js.map +1 -0
- package/docs/en/00-the-mado-way.md +25 -12
- package/docs/en/01-routing.md +90 -142
- package/docs/en/02-project-layout.md +59 -53
- package/docs/en/03-static-bake.md +5 -6
- package/docs/en/05-why-mado.md +6 -6
- package/docs/en/06-for-backenders.md +18 -22
- package/docs/en/08-llm-zero-history-test.md +9 -14
- package/docs/en/09-shadow-vs-light-dom.md +28 -36
- package/docs/en/10-app-architecture.md +158 -96
- package/docs/en/11-layouts.md +22 -24
- package/docs/en/12-auth-and-api.md +89 -182
- package/docs/en/13-deployment.md +18 -22
- package/docs/en/14-testing.md +4 -4
- package/docs/en/16-bake-cookbook.md +11 -12
- package/docs/en/18-api-freeze-map.md +6 -4
- package/docs/en/20-v1-stability.md +1 -1
- package/docs/fr/00-the-mado-way.md +55 -90
- package/docs/fr/01-routing.md +70 -152
- package/docs/fr/02-project-layout.md +61 -42
- package/docs/fr/03-static-bake.md +1 -1
- package/docs/fr/05-why-mado.md +6 -6
- package/docs/fr/06-for-backenders.md +7 -7
- package/docs/fr/08-llm-zero-history-test.md +21 -48
- package/docs/fr/09-shadow-vs-light-dom.md +43 -162
- package/docs/fr/10-app-architecture.md +110 -33
- package/docs/fr/11-layouts.md +24 -12
- package/docs/fr/12-auth-and-api.md +63 -22
- package/docs/fr/13-deployment.md +7 -10
- package/docs/fr/14-testing.md +1 -1
- package/docs/fr/16-bake-cookbook.md +2 -2
- package/docs/fr/18-api-freeze-map.md +1 -1
- package/docs/fr/20-v1-stability.md +1 -1
- package/docs/recipes/nginx/README.md +13 -0
- package/docs/ru/00-the-mado-way.md +53 -75
- package/docs/ru/01-routing.md +68 -143
- package/docs/ru/02-project-layout.md +61 -41
- package/docs/ru/03-static-bake.md +2 -2
- package/docs/ru/05-why-mado.md +6 -6
- package/docs/ru/06-for-backenders.md +7 -7
- package/docs/ru/08-llm-zero-history-test.md +9 -14
- package/docs/ru/09-shadow-vs-light-dom.md +43 -178
- package/docs/ru/10-app-architecture.md +115 -63
- package/docs/ru/11-layouts.md +24 -24
- package/docs/ru/12-auth-and-api.md +57 -35
- package/docs/ru/13-deployment.md +7 -11
- package/docs/ru/14-testing.md +1 -1
- package/docs/ru/16-bake-cookbook.md +12 -6
- package/docs/ru/18-api-freeze-map.md +5 -3
- package/docs/ru/20-v1-stability.md +1 -1
- package/docs/uk/00-the-mado-way.md +70 -44
- package/docs/uk/01-routing.md +41 -47
- package/docs/uk/02-project-layout.md +68 -41
- package/docs/uk/03-static-bake.md +1 -2
- package/docs/uk/06-for-backenders.md +3 -3
- package/docs/uk/08-llm-zero-history-test.md +22 -24
- package/docs/uk/09-shadow-vs-light-dom.md +37 -86
- package/docs/uk/10-app-architecture.md +72 -31
- package/docs/uk/11-layouts.md +25 -12
- package/docs/uk/12-auth-and-api.md +58 -22
- package/docs/uk/13-deployment.md +4 -3
- package/docs/uk/14-testing.md +1 -1
- package/docs/uk/18-api-freeze-map.md +1 -1
- package/docs/uk/20-v1-stability.md +1 -1
- package/llms.txt +14 -15
- package/package.json +18 -11
- package/scripts/_config.mjs +15 -161
- package/scripts/bake.mjs +74 -63
- package/scripts/cli/generate.mjs +348 -0
- package/scripts/cli/help.mjs +27 -0
- package/scripts/cli/index.mjs +79 -0
- package/scripts/cli/init.mjs +153 -0
- package/scripts/cli/release.mjs +152 -0
- package/scripts/cli/run.mjs +96 -0
- package/scripts/cli.mjs +2 -621
- package/scripts/package-smoke.mjs +4 -1
- package/scripts/preview.mjs +13 -37
- package/scripts/size-budget.mjs +5 -2
- package/scripts/vite.default.mjs +11 -0
- package/starters/default/.editorconfig +12 -0
- package/starters/default/README.md +74 -0
- package/starters/default/eslint.config.mjs +256 -0
- package/starters/default/index.html +13 -0
- package/starters/default/package.json +30 -0
- package/starters/default/public/favicon.svg +4 -0
- package/starters/default/src/app.routes.ts +39 -0
- package/starters/default/src/layouts/app-shell.layout.ts +35 -0
- package/starters/default/src/layouts/auth-shell.layout.ts +17 -0
- package/starters/default/src/main.ts +16 -0
- package/starters/default/src/modules/auth/_contracts/auth-api.types.ts +17 -0
- package/starters/default/src/modules/auth/auth.connector.ts +45 -0
- package/starters/default/src/modules/auth/auth.guard.ts +22 -0
- package/starters/default/src/modules/auth/auth.public.ts +9 -0
- package/starters/default/src/modules/auth/auth.routes.ts +8 -0
- package/starters/default/src/modules/auth/auth.service.ts +71 -0
- package/starters/default/src/modules/auth/auth.types.ts +15 -0
- package/starters/default/src/modules/auth/login.page.ts +62 -0
- package/starters/default/src/modules/billing/_contracts/stripe.types.ts +17 -0
- package/starters/default/src/modules/billing/api/stripe.connector.ts +71 -0
- package/starters/default/src/modules/billing/billing.public.ts +5 -0
- package/starters/default/src/modules/billing/billing.routes.ts +9 -0
- package/starters/default/src/modules/billing/billing.types.ts +15 -0
- package/starters/default/src/modules/billing/components/invoice-status-badge.component.ts +43 -0
- package/starters/default/src/modules/billing/data/invoices.resource.ts +35 -0
- package/starters/default/src/modules/billing/pages/invoice-detail.page.ts +70 -0
- package/starters/default/src/modules/billing/pages/invoices-list.page.ts +73 -0
- package/starters/default/src/modules/home/home.page.ts +34 -0
- package/starters/default/src/modules/home/not-found.page.ts +11 -0
- package/starters/default/src/shared/http/http-client.ts +86 -0
- package/starters/default/src/shared/http/http-error.ts +37 -0
- package/starters/default/src/shared/http/interceptors.ts +59 -0
- package/starters/default/src/shared/lib/format-date.ts +19 -0
- package/starters/default/src/shared/styles/content.css +70 -0
- package/starters/default/src/shared/styles/reset.css +32 -0
- package/starters/default/src/shared/styles/shell.css +57 -0
- package/starters/default/src/shared/styles/tokens.css +44 -0
- package/starters/default/src/shared/ui/x-button.component.ts +49 -0
- package/starters/default/src/shared/ui/x-spinner.component.ts +22 -0
- package/starters/default/src/styles.d.ts +1 -0
- package/starters/default/src/vite-env.d.ts +1 -0
- package/starters/default/tsconfig.json +24 -0
- package/starters/default/vite.config.ts +9 -0
- package/MADO_V1_PLAN.md +0 -179
- package/ROADMAP.md +0 -178
- package/dist/src/html.d.ts +0 -18
- package/dist/src/html.js +0 -17
- package/dist/src/html.js.map +0 -1
- package/dist/src/router.d.ts +0 -13
- package/dist/src/router.js +0 -13
- package/dist/src/router.js.map +0 -1
- package/scripts/bundle.mjs +0 -212
- package/scripts/llm-zero-history-smoke.mjs +0 -93
- package/scripts/new.mjs +0 -80
- package/scripts/showcase-regression.mjs +0 -392
- package/server/serve.mjs +0 -455
- package/starters/admin/README.md +0 -63
- package/starters/admin/index.html +0 -28
- package/starters/admin/mado.config.json +0 -22
- package/starters/admin/package.json +0 -24
- package/starters/admin/public/favicon.svg +0 -4
- package/starters/admin/src/components/x-button.ts +0 -82
- package/starters/admin/src/components/x-input.ts +0 -105
- package/starters/admin/src/layouts/app.ts +0 -101
- package/starters/admin/src/layouts/auth.ts +0 -41
- package/starters/admin/src/lib/api.ts +0 -184
- package/starters/admin/src/lib/auth.ts +0 -83
- package/starters/admin/src/main.ts +0 -15
- package/starters/admin/src/pages/admin/dashboard.ts +0 -48
- package/starters/admin/src/pages/admin/order-detail.ts +0 -80
- package/starters/admin/src/pages/admin/orders.ts +0 -117
- package/starters/admin/src/pages/home.ts +0 -34
- package/starters/admin/src/pages/login.ts +0 -70
- package/starters/admin/src/pages/not-found.ts +0 -12
- package/starters/admin/src/routes.ts +0 -40
- package/starters/admin/src/styles/global.ts +0 -86
- package/starters/admin/tsconfig.json +0 -15
- package/starters/crud/README.md +0 -33
- package/starters/crud/index.html +0 -28
- package/starters/crud/mado.config.json +0 -20
- package/starters/crud/package.json +0 -24
- package/starters/crud/src/components/app-shell.ts +0 -56
- package/starters/crud/src/components/ticket-detail.ts +0 -33
- package/starters/crud/src/components/ticket-form.ts +0 -69
- package/starters/crud/src/components/ticket-list.ts +0 -66
- package/starters/crud/src/lib/api.ts +0 -76
- package/starters/crud/src/main.ts +0 -9
- package/starters/crud/src/pages/home.ts +0 -34
- package/starters/crud/src/pages/not-found.ts +0 -12
- package/starters/crud/src/pages/ticket-detail.ts +0 -7
- package/starters/crud/src/pages/ticket-new.ts +0 -7
- package/starters/crud/src/pages/tickets.ts +0 -7
- package/starters/crud/src/routes.ts +0 -11
- package/starters/crud/src/styles/global.ts +0 -155
- package/starters/crud/tsconfig.json +0 -15
- package/starters/minimal/README.md +0 -21
- package/starters/minimal/index.html +0 -28
- package/starters/minimal/mado.config.json +0 -20
- package/starters/minimal/package.json +0 -24
- package/starters/minimal/src/components/app-counter.ts +0 -31
- package/starters/minimal/src/main.ts +0 -9
- package/starters/minimal/src/pages/home.ts +0 -35
- package/starters/minimal/src/pages/not-found.ts +0 -14
- package/starters/minimal/src/routes.ts +0 -8
- package/starters/minimal/src/styles/global.ts +0 -60
- package/starters/minimal/tsconfig.json +0 -15
- package/templates/page-detail.ts +0 -63
- package/templates/page-form.ts +0 -94
- package/templates/page-list.ts +0 -79
package/MADO_V1_PLAN.md
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
# MadoJS — Road to v1 (completed v0.6 tracker)
|
|
2
|
-
|
|
3
|
-
> Completed tracker for the v0.6 product-surface push. The work agreed during
|
|
4
|
-
> the v1 audit is now closed and squash-merged into `main` as
|
|
5
|
-
> `feat: add v1 product surface`.
|
|
6
|
-
>
|
|
7
|
-
> Convention: every commit/PR that advances v1 references task IDs in its message,
|
|
8
|
-
> e.g. `[v1 F1.2] bake.mjs: app-mode defaults + --entry/--template/--out flags`.
|
|
9
|
-
> When a task is completed, tick its box here in the same commit.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## 0. Mental model (write this into docs first)
|
|
14
|
-
|
|
15
|
-
Three artifact states. One deploy artifact. End of story.
|
|
16
|
-
|
|
17
|
-
| Folder | What it is | Who writes | Who reads | Deployed? |
|
|
18
|
-
|-------------|----------------------------------------------------------------|-------------------|----------------------------|-------------------|
|
|
19
|
-
| `src/` | your source (TS) | you | `tsc`, `esbuild` | no |
|
|
20
|
-
| `dist/` | `tsc` output (native ESM JS for the browser) | `mado build` | `mado dev`, dev browser | no (internal) |
|
|
21
|
-
| `public/` | static assets (favicons, images, robots.txt) | you | `mado bundle` copies it | as part of `out/` |
|
|
22
|
-
| `out/` | **the only deploy artifact**: SPA shell + bundles + baked HTML | `mado release` | nginx / CDN / CF / VPS | ✅ yes |
|
|
23
|
-
|
|
24
|
-
One-liner for users:
|
|
25
|
-
> Develop with `mado dev`. To deploy: run `mado release`, then upload `out/` anywhere.
|
|
26
|
-
|
|
27
|
-
`mado release` = `typecheck` + `build` + `bundle` + `bake` + copy `public/*` → `out/`.
|
|
28
|
-
One command. One artifact. Zero questions about where things live.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## 1. Problem map (consolidated)
|
|
33
|
-
|
|
34
|
-
### Layer A — Core (from the audit)
|
|
35
|
-
- **A1** Computed leaks: no refcount/owner; once read, computed stays subscribed to deps forever.
|
|
36
|
-
- **A2** No cycle-detection in `effect` (e.g. `effect(() => x.set(x()+1))` runs forever).
|
|
37
|
-
- **A3** No `equals` option on `computed`.
|
|
38
|
-
- **A4** No template directives: `unsafeHTML`, `ref`, `classMap`, `styleMap`.
|
|
39
|
-
- **A5** `useForm`: no async validators, no field-arrays.
|
|
40
|
-
- **A6** `resource`/`invalidate`: no typed key relationship; pattern miss is silent.
|
|
41
|
-
- **A7** Router: no scroll restoration / focus management / error boundary.
|
|
42
|
-
- **A8** `head()`: not obvious how dedup/cleanup happens across navigation.
|
|
43
|
-
|
|
44
|
-
### Layer B — DX, tooling, product surface (from dogfooding)
|
|
45
|
-
- **B1** CLI/bake hardcoded to `examples/` and alias `@madojs/mado → src/index.ts` — repo-mode leaks into user apps.
|
|
46
|
-
- **B2** No single blessed way for **layouts** (LLM and humans both guess).
|
|
47
|
-
- **B3** No blessed way for **auth + API client + dev-proxy**.
|
|
48
|
-
- **B4** No single config file (`mado.config.json`); everything is env vars.
|
|
49
|
-
- **B5** Unclear model for `dist/` vs `out/` vs `public/`.
|
|
50
|
-
- **B6** No `mado release` (one deploy artifact).
|
|
51
|
-
- **B7** Bake silently renders `[object Object]` for `each()`.
|
|
52
|
-
- **B8** Bake lives "next to" the app instead of inside `mado dev`.
|
|
53
|
-
- **B9** No route guard API.
|
|
54
|
-
- **B10** `crud` starter looks like raw scaffold; no admin-template.
|
|
55
|
-
- **B11** Docs missing: app-architecture, layouts, auth-and-api, deployment, testing, error-handling, bake-cookbook.
|
|
56
|
-
- **B12** Starter scaffolds contain `examples/` legacy.
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## 2. Phases (deliver in order; phase 5 runs in parallel)
|
|
61
|
-
|
|
62
|
-
### 🟦 Phase 1 — Repo-vs-app split (P0)
|
|
63
|
-
|
|
64
|
-
Goal: kill `examples/` leakage; make app-mode the default.
|
|
65
|
-
|
|
66
|
-
### 🟩 Phase 2 — One blessed way (P0)
|
|
67
|
-
|
|
68
|
-
Goal: conventions over flexibility. Layouts, guards, auth, api — one recipe each.
|
|
69
|
-
|
|
70
|
-
### 🟨 Phase 3 — Bake first-class + Release pipeline (P0)
|
|
71
|
-
|
|
72
|
-
Goal: pragmatic path from `mado dev` to production on VPS / Cloudflare / static CDN.
|
|
73
|
-
|
|
74
|
-
### 🟪 Phase 4 — Core hardening (P1)
|
|
75
|
-
|
|
76
|
-
Goal: long-term correctness of signals, html, router, forms, head.
|
|
77
|
-
|
|
78
|
-
### 🟫 Phase 5 — Documentation (P0/P1, in parallel)
|
|
79
|
-
|
|
80
|
-
Goal: a backender can ship an admin app reading only the docs.
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## 3. Acceptance scenario for v1
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
npm exec --package @madojs/mado@latest -- mado init dashboard --starter admin
|
|
88
|
-
cd dashboard
|
|
89
|
-
npm install
|
|
90
|
-
mado dev
|
|
91
|
-
# / → public landing
|
|
92
|
-
# /admin → redirected to /login (guard works)
|
|
93
|
-
# log in → admin shell with sidebar / topbar / dashboard / orders table
|
|
94
|
-
|
|
95
|
-
mado release
|
|
96
|
-
rsync -avz out/ user@vps:/var/www/dashboard/
|
|
97
|
-
# done
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
If a backend developer reaches production **without reading `bake.mjs`,
|
|
101
|
-
without setting env vars, without asking "where does layout live?",
|
|
102
|
-
and without an `examples/` folder in their app** — v1 is done.
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## 4. Tracker
|
|
107
|
-
|
|
108
|
-
### Phase 1 — Repo-vs-app split ✅
|
|
109
|
-
- [x] **F1.1** `mado.config.json` schema + loader (`scripts/_config.mjs`)
|
|
110
|
-
- [x] **F1.2** `scripts/bake.mjs`: flags `--entry/--template/--out/--base-url`, app-mode defaults, no `examples/` alias in app-mode, loud errors instead of silent `[object Object]`
|
|
111
|
-
- [x] **F1.3** `scripts/cli.mjs`: detect repo vs app, read `mado.config.json`, redesigned help, new `mado release` command
|
|
112
|
-
- [x] **F1.4** Starters cleanup: `mado.config.json` and blessed CLI scripts in `starters/minimal` and `starters/crud`; no `examples/` references
|
|
113
|
-
- [x] **F1.5** `docs/en/02-project-layout.md` rewritten around the canonical `src/`/`dist/`/`public/`/`out/` model and `mado.config.json`
|
|
114
|
-
- [x] **F1.6** `test/config-loader.test.mjs` + `test/bake-cli.test.mjs`.
|
|
115
|
-
|
|
116
|
-
### Phase 2 — One blessed way ✅
|
|
117
|
-
- [x] **F2.1** `layout()` factory (alias of `nested()`) exported from `src/page.ts`; flatten propagates layouts outer → inner.
|
|
118
|
-
- [x] **F2.2** `Guard` type + guard chain in `src/router/manifest.ts`. Verdicts: void / `{ halt: true }` / `{ redirect, replace? }`. Async-aware with sync fast path; throwing guard is treated as `halt` with a `console.error`.
|
|
119
|
-
- [x] **F2.3** `layout`, `Guard`, `GuardResult`, `nested`, `navigate` exported from `src/index.ts`.
|
|
120
|
-
- [x] **F2.4** New starter `starters/admin/` with `layouts/{app,auth}.ts`, `lib/{api,auth}.ts`, `components/{x-button,x-input}.ts`, `pages/{home,login,not-found,admin/{dashboard,orders,order-detail}}.ts`, `styles/global.ts`, `mado.config.json`, `public/favicon.svg`. End-to-end scaffold + typecheck + build verified locally.
|
|
121
|
-
- [x] **F2.5** CLI advertises `--starter admin` (`mado init my-app --starter admin`) and adds it to the help screen.
|
|
122
|
-
- [x] **F2.6** Doc `docs/en/11-layouts.md` (one path + 2 alternatives with caveats).
|
|
123
|
-
- [x] **F2.7** Doc `docs/en/12-auth-and-api.md` (blessed `api`/`auth` recipes, backend contract, dev-proxy hint).
|
|
124
|
-
- [x] **F2.8** `test/guards-layouts.test.mjs` covers the public `layout()` alias, sync pass/halt/redirect verdicts, async guards, parent→page guard order, and throwing-guard fallback (7 tests, all green).
|
|
125
|
-
|
|
126
|
-
### Phase 3 — Bake first-class + Release pipeline ✅ (core)
|
|
127
|
-
- [x] **F3.1** `mado release` command: typecheck + build + bundle + bake + copy `public/` → `out/` + write `_headers` / `_redirects` (`scripts/cli.mjs`).
|
|
128
|
-
- [x] **F3.2** Deferred to v0.7: `mado dev` serves baked routes inline. Current v1 path is `mado release && mado preview`.
|
|
129
|
-
- [x] **F3.3** `mado preview` reads `mado.config.json`, refuses to auto-build in app-mode (opt in with `PREVIEW_AUTOBUILD=1`), and emulates nginx fallback (`scripts/preview.mjs`).
|
|
130
|
-
- [x] **F3.4** Bake raises a loud, file/route-targeted error on unsupported render values (e.g. `each()` directive objects). Covered by `test/bake-cli.test.mjs`.
|
|
131
|
-
- [x] **F3.5** Deferred to v0.7: `mado check` runs bake-safety scan on `bake:` routes. Current v1 coverage is the loud-error contract from F3.4.
|
|
132
|
-
- [x] **F3.6** Dev-proxy in `server/serve.mjs`: reads `dev.proxy` from `mado.config.json` and forwards matching prefixes to the upstream backend without external deps.
|
|
133
|
-
- [x] **F3.7** `mado release` generates `out/_redirects` (`/* /index.html 200`) and `out/_headers` (immutable for `/assets/*`, no-cache for `/*.html`) when those files do not exist.
|
|
134
|
-
- [x] **F3.8** Doc `docs/en/13-deployment.md` (VPS + nginx, Cloudflare Pages, S3/CloudFront, Netlify, GitHub Pages, CI sketch, cache matrix, troubleshooting).
|
|
135
|
-
- [x] **F3.9** Production `nginx.conf` next to the docs (already shipped, with `gzip_static`, immutable hashed bundles, SPA fallback). Verified referenced from the deployment doc.
|
|
136
|
-
- [x] **F3.10** `test/release-pipeline.test.mjs`: end-to-end CLI test scaffolds an app, runs `mado release`, and asserts `out/index.html`, `out/baked/index.html`, `out/_headers`, `out/_redirects`, public-asset copy, and sitemap. Also `scripts/bundle.mjs` is app-mode aware (no `examples/`-leak when run from a user app).
|
|
137
|
-
|
|
138
|
-
### Phase 4 — Core hardening
|
|
139
|
-
- [x] **F4.1** Refcount/GC for `computed` in `src/signal.ts` + tests (`computed` releases dep subscriptions after unobserved reads and after the last subscriber is disposed).
|
|
140
|
-
- [x] **F4.2** Cycle-detection in `effect` + `test/signal-cycle.test.mjs`
|
|
141
|
-
- [x] **F4.3** `equals` option on `computed`
|
|
142
|
-
- [x] **F4.4** Directives: `unsafeHTML`, `ref`, `classMap`, `styleMap` in `src/html/bindings.ts` + public exports + `test/html-directives.test.mjs`.
|
|
143
|
-
- [x] **F4.5** `useForm`: async validators (`validateAsync`, field-level `validateAsync`, `validating`) + field arrays (`form.array()`, dotted paths, wildcard schema) + tests.
|
|
144
|
-
- [x] **F4.6** Router: scroll restoration + focus management
|
|
145
|
-
- [x] **F4.7** Router: `errorPage:` in manifest + error boundary
|
|
146
|
-
- [x] **F4.8** `src/head.ts`: guaranteed cleanup on navigation (`data-mado-head` removed before re-applying)
|
|
147
|
-
- [x] **F4.9** Tests for everything above: `test/computed-lazy.test.mjs` (F4.1/F4.3), `test/signal-cycle.test.mjs` (F4.2), `test/html-directives.test.mjs` (F4.4), `test/forms.test.mjs` (F4.5), `test/router-isolation.test.mjs` (F4.6), `test/router-error-boundary.test.mjs` (F4.7), `test/head.test.mjs` (F4.8). Final `typecheck + build + test` pass verified.
|
|
148
|
-
|
|
149
|
-
### Phase 5 — Documentation (parallel to phases 2-4)
|
|
150
|
-
English docs for project layout, layouts, auth/API and deployment exist now.
|
|
151
|
-
The remaining work is deeper recipes plus translation sync.
|
|
152
|
-
|
|
153
|
-
- [x] **F5.1** `docs/en/10-app-architecture.md` (end-to-end admin)
|
|
154
|
-
- [x] **F5.2** `docs/en/14-testing.md`
|
|
155
|
-
- [x] **F5.3** `docs/en/15-error-handling.md`
|
|
156
|
-
- [x] **F5.4** `docs/en/16-bake-cookbook.md`
|
|
157
|
-
- [x] **F5.5** `AGENTS.md`: section "App architecture for LLM"
|
|
158
|
-
- [x] **F5.6** `llms.txt`: new anchors
|
|
159
|
-
- [x] **F5.7** ru/fr/uk translations for new docs (`10` through `16`)
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## 5. Working loop
|
|
164
|
-
|
|
165
|
-
1. Pick the first unchecked box.
|
|
166
|
-
2. Implement it (small, focused change).
|
|
167
|
-
3. Run `npm test` (and `npm run typecheck` when touching `src/`).
|
|
168
|
-
4. Tick the box in this file in the same commit.
|
|
169
|
-
5. Add a one-line entry under "Unreleased" in `CHANGELOG.md`.
|
|
170
|
-
6. After each phase finishes, pause and review.
|
|
171
|
-
|
|
172
|
-
## 6. Context-loss safety
|
|
173
|
-
|
|
174
|
-
- This file = ground truth on disk.
|
|
175
|
-
- `task_progress` in chat = current phase/task.
|
|
176
|
-
- Git commits tagged `[v1 Fx.y]` = time machine.
|
|
177
|
-
|
|
178
|
-
If future work resumes from this plan, treat it as archive context. New tasks
|
|
179
|
-
belong in `ROADMAP.md`, `TODO.md`, or a fresh tracker.
|
package/ROADMAP.md
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
# Roadmap to v1
|
|
2
|
-
|
|
3
|
-
Mado is a calm frontend stack for internal tools, admin panels and business
|
|
4
|
-
SPA. The path to v1 is not about adding features — it is about proving the
|
|
5
|
-
framework works reliably for its intended use case and earning trust through
|
|
6
|
-
demonstrated quality.
|
|
7
|
-
|
|
8
|
-
## Positioning
|
|
9
|
-
|
|
10
|
-
**For:** small teams and solo developers building admin panels, internal tools,
|
|
11
|
-
backoffice apps and CRUD-heavy SPA.
|
|
12
|
-
|
|
13
|
-
**Promise:** easy to build, boring to maintain. Complete app stack (routing,
|
|
14
|
-
forms, state, data, prerender) without frontend infrastructure overhead.
|
|
15
|
-
|
|
16
|
-
**Not for:** SEO-heavy sites, large teams optimizing for hiring, projects
|
|
17
|
-
needing SSR hydration or mature plugin ecosystems.
|
|
18
|
-
|
|
19
|
-
## Current state: v0.8
|
|
20
|
-
|
|
21
|
-
The core API is feature-complete for the target use case:
|
|
22
|
-
- Signals, computed, effects with GC and cycle detection
|
|
23
|
-
- Tagged-template `html` with directives (ref, classMap, styleMap, unsafeHTML)
|
|
24
|
-
- Web Components with Shadow DOM
|
|
25
|
-
- Router with nested routes, guards, lazy loading, error boundary, scroll restoration
|
|
26
|
-
- Resource + mutation with cache, invalidation, abort, optimistic updates
|
|
27
|
-
- Forms with schema validation, async validators, field arrays
|
|
28
|
-
- Persisted state, context/DI, head management
|
|
29
|
-
- Static prerender (bake) for SEO without hydration
|
|
30
|
-
- CLI: init, dev, build, release, preview
|
|
31
|
-
- Starters: minimal, crud, admin
|
|
32
|
-
|
|
33
|
-
The API surface is feature-complete — but a line-by-line audit
|
|
34
|
-
([`FABLE_REPORT.md`](./FABLE_REPORT.md)) found correctness defects that break
|
|
35
|
-
three central promises (*keyed `each` preserves state*, *glitch-free signals*,
|
|
36
|
-
*cross-tab sync works*) plus one re-bind model that is cheaper to fix before
|
|
37
|
-
the API freeze than after. So what is missing is, in order: **correctness,
|
|
38
|
-
then surface lockdown, then proof and polish.**
|
|
39
|
-
|
|
40
|
-
## Milestone 0: Correctness & surface lockdown (gate to v1)
|
|
41
|
-
|
|
42
|
-
Before any of the product-surface milestones below mean anything, the
|
|
43
|
-
foundation must be correct and the public surface must be freezable. This work
|
|
44
|
-
is tracked task-by-task (TDD: reproducing test → fix) in
|
|
45
|
-
[`MADO_V1_TRACKER.md`](./MADO_V1_TRACKER.md):
|
|
46
|
-
|
|
47
|
-
- [ ] **v0.9 — Correctness release:** fix audit findings C1–C8 (each-reorder
|
|
48
|
-
teardown, persisted cross-tab loop, `update()` nested re-bind, `equals` +
|
|
49
|
-
`batch` atomicity, stale form async validation, `mutation` abort semantics,
|
|
50
|
-
silent parser drops, lifecycle/router defect pack). Fixes only, zero features,
|
|
51
|
-
each with a regression test that was red first.
|
|
52
|
-
- [ ] **v0.10 — Surface & cleanup:** remove legacy attribute→property reflection
|
|
53
|
-
and `observedAttributes`, replace `exports "./*"` with an explicit subpath map,
|
|
54
|
-
close `_testHooks`, publish the API freeze map, add CI size budgets and a
|
|
55
|
-
package-level export test, sync `llms.txt`/`AGENTS.md` with the real API.
|
|
56
|
-
|
|
57
|
-
The milestones below (live demo, reliability, recipes, docs) execute **after**
|
|
58
|
-
Milestone 0, during the `v1.0-rc` dogfooding phase.
|
|
59
|
-
|
|
60
|
-
## V1 milestones (in priority order)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
### 1. Live demo — prove it works
|
|
64
|
-
|
|
65
|
-
**Goal:** a deployed, publicly accessible app that looks like real work.
|
|
66
|
-
|
|
67
|
-
The showcase already has: auth, tables with filters/sort/pagination, record
|
|
68
|
-
details, forms, nested routes, context services, role guards, loading/error
|
|
69
|
-
states. What it needs:
|
|
70
|
-
|
|
71
|
-
- [ ] Deploy to a public URL (Cloudflare Pages or similar)
|
|
72
|
-
- [ ] Add responsive sidebar + mobile breakpoints
|
|
73
|
-
- [ ] Add toast/feedback system
|
|
74
|
-
- [ ] Add optimistic update example
|
|
75
|
-
- [ ] Add persisted filters example
|
|
76
|
-
- [ ] Polish visual quality to "not embarrassing" level
|
|
77
|
-
- [ ] Add a comparison section: bundle size, file count, cold start vs React+Vite equivalent
|
|
78
|
-
|
|
79
|
-
This is the single most important deliverable. A working app proves more than
|
|
80
|
-
any README.
|
|
81
|
-
|
|
82
|
-
### 2. Boring reliability — earn trust
|
|
83
|
-
|
|
84
|
-
**Goal:** no surprises in production.
|
|
85
|
-
|
|
86
|
-
- [ ] Browser compatibility pass: Chrome, Edge, Firefox, Safari (current)
|
|
87
|
-
- [ ] Accessibility pass: focus management, ARIA, keyboard navigation in examples
|
|
88
|
-
- [ ] Public API audit: names, warnings, lifecycle rules, docs coverage
|
|
89
|
-
- [ ] Public exports audit: lock the root import; decide on subpaths
|
|
90
|
-
- [ ] Error messages audit: every runtime error should point to a fix
|
|
91
|
-
- [ ] Long session stability test: memory leaks, listener cleanup, route churn
|
|
92
|
-
- [ ] Migration notes template: document how to upgrade between versions
|
|
93
|
-
|
|
94
|
-
### 3. Release hygiene — look professional
|
|
95
|
-
|
|
96
|
-
**Goal:** signal stability and care.
|
|
97
|
-
|
|
98
|
-
- [ ] npm provenance / Trusted Publishing
|
|
99
|
-
- [ ] GitHub repo metadata: description, topics, social preview
|
|
100
|
-
- [ ] Semantic versioning discipline with changelog
|
|
101
|
-
- [ ] Size reporting in CI (ESM + bundled + gzip + brotli)
|
|
102
|
-
- [ ] Compatibility matrix in docs (browsers, TS versions)
|
|
103
|
-
- [ ] Issue labels and triage process
|
|
104
|
-
|
|
105
|
-
### 4. Business-app recipes — solve real problems
|
|
106
|
-
|
|
107
|
-
**Goal:** answer "how do I..." for common admin/internal app patterns.
|
|
108
|
-
|
|
109
|
-
- [ ] Auth recipe: login, token refresh, route guards, logout
|
|
110
|
-
- [ ] Table recipe: pagination, sort, filters, empty state, loading skeleton
|
|
111
|
-
- [ ] Form recipe: create/edit, validation, async submit, error display
|
|
112
|
-
- [ ] CRUD recipe: list → detail → edit → delete with optimistic updates
|
|
113
|
-
- [ ] API integration recipe: typed client, error handling, retry
|
|
114
|
-
- [ ] Deployment recipe: VPS, Cloudflare Pages, static CDN, Docker
|
|
115
|
-
|
|
116
|
-
### 5. External validation — prove others can use it
|
|
117
|
-
|
|
118
|
-
**Goal:** at least 3 people outside the author build real apps.
|
|
119
|
-
|
|
120
|
-
- [ ] Invite 2-3 developers to build internal tools with Mado
|
|
121
|
-
- [ ] Collect friction reports and fix blockers
|
|
122
|
-
- [ ] Get at least 1 public case study or blog post
|
|
123
|
-
- [ ] Address real issues that emerge from pressure testing
|
|
124
|
-
|
|
125
|
-
### 6. Documentation rewrite — job-to-be-done first
|
|
126
|
-
|
|
127
|
-
**Goal:** docs answer "how to build X" not "how API Y works".
|
|
128
|
-
|
|
129
|
-
- [ ] Getting started guide focused on building an admin panel
|
|
130
|
-
- [ ] "Build a backoffice app" tutorial (30 min, end-to-end)
|
|
131
|
-
- [ ] Troubleshooting guide for common mistakes
|
|
132
|
-
- [ ] API reference (generated or manually curated)
|
|
133
|
-
- [ ] Remove or archive docs that don't serve the target audience
|
|
134
|
-
|
|
135
|
-
## Explicitly out of scope
|
|
136
|
-
|
|
137
|
-
These will not happen before v1. They are not weaknesses — they are focus.
|
|
138
|
-
|
|
139
|
-
- Runtime dependencies
|
|
140
|
-
- UI component library / design system
|
|
141
|
-
- SSR with hydration
|
|
142
|
-
- Plugin ecosystem
|
|
143
|
-
- Framework-specific build tool
|
|
144
|
-
- Broad general-purpose narrative
|
|
145
|
-
- Synthetic benchmark marketing
|
|
146
|
-
|
|
147
|
-
## After v1
|
|
148
|
-
|
|
149
|
-
Ideas live in `TODO.md`. They become commitments only when a real app or
|
|
150
|
-
user issue proves the need. The general direction:
|
|
151
|
-
|
|
152
|
-
- Optimistic mutation primitives
|
|
153
|
-
- Live data (SSE/WebSocket) resource
|
|
154
|
-
- i18n helper
|
|
155
|
-
- Accessibility utilities (focus trap, live region)
|
|
156
|
-
- Optional tiny utility stylesheet
|
|
157
|
-
- PWA scaffold
|
|
158
|
-
- eslint-plugin-mado
|
|
159
|
-
|
|
160
|
-
## Success criteria for v1
|
|
161
|
-
|
|
162
|
-
Not stars or hype. Instead:
|
|
163
|
-
|
|
164
|
-
1. **3+ people** have built real internal/admin apps with Mado
|
|
165
|
-
2. **1 polished live demo** is deployed and maintained
|
|
166
|
-
3. **Zero known browser bugs** in Chrome/Edge/Firefox/Safari current
|
|
167
|
-
4. **API is stable** — no breaking changes needed for known use cases
|
|
168
|
-
5. **Docs answer 90% of questions** without asking the author
|
|
169
|
-
|
|
170
|
-
## Release History
|
|
171
|
-
|
|
172
|
-
| Date | Version | Notes |
|
|
173
|
-
|---|---|---|
|
|
174
|
-
| 2026-06-03 | v0.1-v0.3 | Core stabilization, docs foundation, publish readiness, Cloudflare PoC |
|
|
175
|
-
| 2026-06-05 | v0.4 | Showcase CRM pressure app, browser regression suite |
|
|
176
|
-
| 2026-06-06 | v0.5 | Unified CLI, rebrand to @madojs/mado, first npm release |
|
|
177
|
-
| 2026-06-07 | v0.6 | App-mode CLI/config, admin starter, release pipeline, core hardening |
|
|
178
|
-
| 2026-06-09 | v0.8 | Product surface complete, positioning pivot to internal tools focus |
|
package/dist/src/html.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* html module entry point. The implementation lives in `./html/`:
|
|
3
|
-
*
|
|
4
|
-
* ./html/parser.ts — state-machine tokenizer + ParsedTemplate cache
|
|
5
|
-
* ./html/bindings.ts — bindChild / bindAttr + keyed each
|
|
6
|
-
* ./html/template.ts — html`` tag, instantiate(), render()
|
|
7
|
-
* ./html/template-types.ts — shared types (TemplateResult, InstantiatedTemplate)
|
|
8
|
-
*
|
|
9
|
-
* This file keeps compatibility for internal imports from `"./html.js"`.
|
|
10
|
-
* The public barrel (`src/index.ts`) also goes through this file.
|
|
11
|
-
*
|
|
12
|
-
* If the shell is ever removed, switch imports to `./html/template.js`.
|
|
13
|
-
* For now it has no runtime cost.
|
|
14
|
-
*/
|
|
15
|
-
export { html, render, instantiate } from "./html/template.js";
|
|
16
|
-
export { unsafeHTML, ref, classMap, styleMap, isHtmlDirective, } from "./html/bindings.js";
|
|
17
|
-
export type { TemplateResult } from "./html/template-types.js";
|
|
18
|
-
export type { HtmlDirective, UnsafeHTMLDirective, RefCallback, RefDirective, ClassMap, ClassMapDirective, StyleMap, StyleMapDirective, } from "./html/bindings.js";
|
package/dist/src/html.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* html module entry point. The implementation lives in `./html/`:
|
|
3
|
-
*
|
|
4
|
-
* ./html/parser.ts — state-machine tokenizer + ParsedTemplate cache
|
|
5
|
-
* ./html/bindings.ts — bindChild / bindAttr + keyed each
|
|
6
|
-
* ./html/template.ts — html`` tag, instantiate(), render()
|
|
7
|
-
* ./html/template-types.ts — shared types (TemplateResult, InstantiatedTemplate)
|
|
8
|
-
*
|
|
9
|
-
* This file keeps compatibility for internal imports from `"./html.js"`.
|
|
10
|
-
* The public barrel (`src/index.ts`) also goes through this file.
|
|
11
|
-
*
|
|
12
|
-
* If the shell is ever removed, switch imports to `./html/template.js`.
|
|
13
|
-
* For now it has no runtime cost.
|
|
14
|
-
*/
|
|
15
|
-
export { html, render, instantiate } from "./html/template.js";
|
|
16
|
-
export { unsafeHTML, ref, classMap, styleMap, isHtmlDirective, } from "./html/bindings.js";
|
|
17
|
-
//# sourceMappingURL=html.js.map
|
package/dist/src/html.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EACL,UAAU,EACV,GAAG,EACH,QAAQ,EACR,QAAQ,EACR,eAAe,GAChB,MAAM,oBAAoB,CAAC"}
|
package/dist/src/router.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Router module entry point. The implementation lives in `./router/`:
|
|
3
|
-
*
|
|
4
|
-
* ./router/match.ts — pure pattern matching (compile/flatten/normalize)
|
|
5
|
-
* ./router/navigation.ts — router() + navigate() + queryParam (touches window/history)
|
|
6
|
-
* ./router/manifest.ts — routes() + prefetchPath + lazy loading + sync-fast-path
|
|
7
|
-
*
|
|
8
|
-
* This file keeps compatibility for internal imports from `"./router.js"`.
|
|
9
|
-
* The public barrel (`src/index.ts`) also goes through this file.
|
|
10
|
-
*/
|
|
11
|
-
export { router, navigate, queryParam, type RouterApi, type RouterOptions, type QueryParam, type QuerySignal, } from "./router/navigation.js";
|
|
12
|
-
export type { RouteHandler, RouteParams, Routes, RoutesMap, } from "./router/match.js";
|
|
13
|
-
export { routes, prefetchPath, type RoutesOptions, } from "./router/manifest.js";
|
package/dist/src/router.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Router module entry point. The implementation lives in `./router/`:
|
|
3
|
-
*
|
|
4
|
-
* ./router/match.ts — pure pattern matching (compile/flatten/normalize)
|
|
5
|
-
* ./router/navigation.ts — router() + navigate() + queryParam (touches window/history)
|
|
6
|
-
* ./router/manifest.ts — routes() + prefetchPath + lazy loading + sync-fast-path
|
|
7
|
-
*
|
|
8
|
-
* This file keeps compatibility for internal imports from `"./router.js"`.
|
|
9
|
-
* The public barrel (`src/index.ts`) also goes through this file.
|
|
10
|
-
*/
|
|
11
|
-
export { router, navigate, queryParam, } from "./router/navigation.js";
|
|
12
|
-
export { routes, prefetchPath, } from "./router/manifest.js";
|
|
13
|
-
//# sourceMappingURL=router.js.map
|
package/dist/src/router.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,MAAM,EACN,QAAQ,EACR,UAAU,GAKX,MAAM,wBAAwB,CAAC;AAShC,OAAO,EACL,MAAM,EACN,YAAY,GAEb,MAAM,sBAAsB,CAAC"}
|
package/scripts/bundle.mjs
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
// Production bundle through esbuild. No build config files.
|
|
2
|
-
//
|
|
3
|
-
// Usage:
|
|
4
|
-
// mado bundle
|
|
5
|
-
// mado bundle --entry src/main.ts --html index.html --out out
|
|
6
|
-
//
|
|
7
|
-
// Configuration precedence: built-in defaults < mado.config.json < CLI flags
|
|
8
|
-
// < legacy env vars (ENTRY, HTML, OUT_DIR).
|
|
9
|
-
//
|
|
10
|
-
// What it does:
|
|
11
|
-
// 1. Bundles `entry` with code splitting (each dynamic import → chunk),
|
|
12
|
-
// writing hashed `main-<hash>.js` and `chunk-<hash>.js` into <out>/assets/.
|
|
13
|
-
// 2. Computes SRI for the entry bundle.
|
|
14
|
-
// 3. Rewrites `html` so its <script type=module> points at the hashed entry,
|
|
15
|
-
// removes the dev importmap, and adds <link rel=modulepreload> for the
|
|
16
|
-
// entry and all chunks. Writes the result to <out>/index.html.
|
|
17
|
-
// 4. Pre-compresses every .js into .gz and .br for nginx_gzip_static and
|
|
18
|
-
// Cloudflare/Netlify Accept-Encoding.
|
|
19
|
-
// 5. Copies optional `favicon.ico`/`favicon.svg`/`assets/` from the project
|
|
20
|
-
// root if they exist (kept for backwards compatibility; new apps should
|
|
21
|
-
// put public assets in `public/` so `mado release` copies them).
|
|
22
|
-
//
|
|
23
|
-
// In repo-mode (the framework repo itself) the defaults still point at
|
|
24
|
-
// examples/showcase so the framework can dogfood its bundle pipeline against
|
|
25
|
-
// its biggest example.
|
|
26
|
-
|
|
27
|
-
import { build } from "esbuild";
|
|
28
|
-
import { readFile, writeFile, mkdir, cp, stat, readdir, rm } from "node:fs/promises";
|
|
29
|
-
import { createHash } from "node:crypto";
|
|
30
|
-
import { gzipSync, brotliCompressSync, constants as zlibConst } from "node:zlib";
|
|
31
|
-
import { join, basename, resolve, dirname } from "node:path";
|
|
32
|
-
import { existsSync } from "node:fs";
|
|
33
|
-
|
|
34
|
-
import { loadConfig, parseFlags, resolveProjectPath } from "./_config.mjs";
|
|
35
|
-
|
|
36
|
-
const { flags } = parseFlags(process.argv.slice(2));
|
|
37
|
-
const cfg = loadConfig({});
|
|
38
|
-
|
|
39
|
-
// Defaults are context-aware: in repo-mode they continue to bundle the
|
|
40
|
-
// showcase example; in app-mode they assume the canonical layout.
|
|
41
|
-
const defaultEntry = cfg.context === "repo"
|
|
42
|
-
? "examples/showcase/main.ts"
|
|
43
|
-
: "src/main.ts";
|
|
44
|
-
const defaultHtml = cfg.context === "repo"
|
|
45
|
-
? "examples/showcase/index.html"
|
|
46
|
-
: "index.html";
|
|
47
|
-
|
|
48
|
-
const ENTRY = resolveProjectPath(
|
|
49
|
-
cfg,
|
|
50
|
-
typeof flags.entry === "string" ? flags.entry : process.env.ENTRY ?? defaultEntry,
|
|
51
|
-
);
|
|
52
|
-
const HTML = resolveProjectPath(
|
|
53
|
-
cfg,
|
|
54
|
-
typeof flags.html === "string" ? flags.html : process.env.HTML ?? defaultHtml,
|
|
55
|
-
);
|
|
56
|
-
const OUT_DIR = resolveProjectPath(
|
|
57
|
-
cfg,
|
|
58
|
-
typeof flags.out === "string" ? flags.out : process.env.OUT_DIR ?? cfg.build.out ?? "out",
|
|
59
|
-
);
|
|
60
|
-
// Where the hashed bundles land. Apps want them under /assets/* to match
|
|
61
|
-
// nginx.conf and _headers; in repo-mode we keep the historical out/main-*.js
|
|
62
|
-
// layout so existing showcase pages continue to work.
|
|
63
|
-
const ASSETS_REL = cfg.context === "repo" ? "" : "assets";
|
|
64
|
-
const ASSETS_DIR = ASSETS_REL ? join(OUT_DIR, ASSETS_REL) : OUT_DIR;
|
|
65
|
-
|
|
66
|
-
if (!existsSync(ENTRY)) {
|
|
67
|
-
console.error(`[bundle] entry not found: ${ENTRY}`);
|
|
68
|
-
console.error("[bundle] set bundle entry in mado.config.json or pass --entry <file>");
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
if (!existsSync(HTML)) {
|
|
72
|
-
console.error(`[bundle] html template not found: ${HTML}`);
|
|
73
|
-
console.error("[bundle] pass --html <file> or place index.html at the project root");
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Clean stale assets from previous bundles.
|
|
78
|
-
//
|
|
79
|
-
// Without this step, hashed chunks from prior runs (main-<oldhash>.js,
|
|
80
|
-
// chunk-<oldhash>.js) accumulate in ASSETS_DIR. We later list ASSETS_DIR
|
|
81
|
-
// (via readdir below) and emit <link rel="modulepreload"> for every
|
|
82
|
-
// .js file we find — so stale chunks would be preloaded as if they were
|
|
83
|
-
// still part of the app, polluting the production HTML and shipping dead
|
|
84
|
-
// code over the wire. SRI is also only computed for the fresh entry, so
|
|
85
|
-
// stale preloads would lack integrity checks.
|
|
86
|
-
//
|
|
87
|
-
// In app-mode the entire <out>/assets/ folder is owned by the bundler,
|
|
88
|
-
// so wiping it is safe. In repo-mode the historical layout drops assets
|
|
89
|
-
// directly into <out>/ alongside non-bundle artifacts, so we only remove
|
|
90
|
-
// the recognisable hashed files there.
|
|
91
|
-
if (ASSETS_REL) {
|
|
92
|
-
await rm(ASSETS_DIR, { recursive: true, force: true });
|
|
93
|
-
} else if (existsSync(ASSETS_DIR)) {
|
|
94
|
-
for (const f of await readdir(ASSETS_DIR)) {
|
|
95
|
-
if (/^(main|chunk|asset)-[A-Z0-9]+\.(js|css)(\.map|\.gz|\.br)?$/i.test(f)) {
|
|
96
|
-
await rm(join(ASSETS_DIR, f), { force: true });
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
await mkdir(ASSETS_DIR, { recursive: true });
|
|
101
|
-
|
|
102
|
-
console.log(`[bundle] entry: ${ENTRY}`);
|
|
103
|
-
console.log(`[bundle] html: ${HTML}`);
|
|
104
|
-
console.log(`[bundle] out: ${OUT_DIR}`);
|
|
105
|
-
if (ASSETS_REL) console.log(`[bundle] assets: ${ASSETS_DIR}`);
|
|
106
|
-
|
|
107
|
-
const result = await build({
|
|
108
|
-
entryPoints: [ENTRY],
|
|
109
|
-
bundle: true,
|
|
110
|
-
minify: true,
|
|
111
|
-
sourcemap: true,
|
|
112
|
-
format: "esm",
|
|
113
|
-
target: "es2022",
|
|
114
|
-
splitting: true,
|
|
115
|
-
outdir: ASSETS_DIR,
|
|
116
|
-
entryNames: "main-[hash]",
|
|
117
|
-
chunkNames: "chunk-[hash]",
|
|
118
|
-
assetNames: "asset-[hash]",
|
|
119
|
-
metafile: true,
|
|
120
|
-
legalComments: "none",
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// With splitting: true, esbuild marks all dynamic-import chunks as having
|
|
124
|
-
// entryPoint. We identify the real app entry by the `entryNames` prefix "main-".
|
|
125
|
-
const mainBundle = (await readdir(ASSETS_DIR))
|
|
126
|
-
.find((f) => f.startsWith("main-") && f.endsWith(".js") && !f.endsWith(".js.map"));
|
|
127
|
-
if (!mainBundle) {
|
|
128
|
-
console.error("[bundle] entry not found in outputs (no main-*.js in assets dir)");
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Collect all js chunks in the assets dir.
|
|
133
|
-
const allJs = (await readdir(ASSETS_DIR)).filter((f) => f.endsWith(".js"));
|
|
134
|
-
|
|
135
|
-
// Pre-compress every .js into .gz and .br.
|
|
136
|
-
let totalRaw = 0;
|
|
137
|
-
let totalGz = 0;
|
|
138
|
-
let totalBr = 0;
|
|
139
|
-
for (const f of allJs) {
|
|
140
|
-
const p = join(ASSETS_DIR, f);
|
|
141
|
-
const buf = await readFile(p);
|
|
142
|
-
totalRaw += buf.length;
|
|
143
|
-
const gz = gzipSync(buf, { level: 9 });
|
|
144
|
-
await writeFile(`${p}.gz`, gz);
|
|
145
|
-
totalGz += gz.length;
|
|
146
|
-
const br = brotliCompressSync(buf, { params: { [zlibConst.BROTLI_PARAM_QUALITY]: 11 } });
|
|
147
|
-
await writeFile(`${p}.br`, br);
|
|
148
|
-
totalBr += br.length;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// SRI for the main bundle.
|
|
152
|
-
const mainBuf = await readFile(join(ASSETS_DIR, mainBundle));
|
|
153
|
-
const sri = "sha384-" + createHash("sha384").update(mainBuf).digest("base64");
|
|
154
|
-
|
|
155
|
-
// Rewrite HTML: drop dev importmap, swap the <script src>, add preloads.
|
|
156
|
-
const urlPrefix = ASSETS_REL ? `/${ASSETS_REL}/` : "/";
|
|
157
|
-
let html = await readFile(HTML, "utf8");
|
|
158
|
-
|
|
159
|
-
html = html.replace(/<script type="importmap">[\s\S]*?<\/script>/, "");
|
|
160
|
-
|
|
161
|
-
const preloads = allJs
|
|
162
|
-
.map(
|
|
163
|
-
(f) =>
|
|
164
|
-
` <link rel="modulepreload" href="${urlPrefix}${f}"${
|
|
165
|
-
f === mainBundle ? ` integrity="${sri}" crossorigin="anonymous"` : ""
|
|
166
|
-
} />`,
|
|
167
|
-
)
|
|
168
|
-
.join("\n");
|
|
169
|
-
|
|
170
|
-
const scriptTag = `<script type="module" src="${urlPrefix}${mainBundle}" integrity="${sri}" crossorigin="anonymous"></script>`;
|
|
171
|
-
if (/<script\s+type="module"\s+src="[^"]+"[^>]*><\/script>/.test(html)) {
|
|
172
|
-
html = html.replace(
|
|
173
|
-
/<script\s+type="module"\s+src="[^"]+"[^>]*><\/script>/,
|
|
174
|
-
scriptTag,
|
|
175
|
-
);
|
|
176
|
-
} else {
|
|
177
|
-
// No matching dev <script> in the template: inject one before </body>.
|
|
178
|
-
html = html.replace(/<\/body>/i, ` ${scriptTag}\n </body>`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
html = html.replace(/<\/head>/, `${preloads}\n </head>`);
|
|
182
|
-
|
|
183
|
-
await mkdir(OUT_DIR, { recursive: true });
|
|
184
|
-
await writeFile(join(OUT_DIR, "index.html"), html);
|
|
185
|
-
|
|
186
|
-
// Backwards-compatible asset copy (repo-mode only). In app-mode the
|
|
187
|
-
// `mado release` command copies the entire `public/` tree, which is the
|
|
188
|
-
// recommended path for new apps.
|
|
189
|
-
if (cfg.context === "repo") {
|
|
190
|
-
for (const name of ["favicon.ico", "favicon.svg", "assets"]) {
|
|
191
|
-
const src = join(cfg.projectRoot, "examples", name);
|
|
192
|
-
if (!existsSync(src)) continue;
|
|
193
|
-
const s = await stat(src);
|
|
194
|
-
if (s.isDirectory()) {
|
|
195
|
-
await cp(src, join(OUT_DIR, name), { recursive: true });
|
|
196
|
-
} else {
|
|
197
|
-
await cp(src, join(OUT_DIR, name));
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Stats
|
|
203
|
-
const kib = (n) => (n / 1024).toFixed(1);
|
|
204
|
-
console.log(`[bundle] chunks: ${allJs.length}`);
|
|
205
|
-
for (const f of allJs.sort()) {
|
|
206
|
-
const sz = (await stat(join(ASSETS_DIR, f))).size;
|
|
207
|
-
const gz = (await stat(join(ASSETS_DIR, `${f}.gz`))).size;
|
|
208
|
-
const star = f === mainBundle ? " *" : "";
|
|
209
|
-
console.log(` ${f.padEnd(24)} ${kib(sz).padStart(6)} KB raw, ${kib(gz).padStart(5)} KB gz${star}`);
|
|
210
|
-
}
|
|
211
|
-
console.log(`[bundle] total: ${kib(totalRaw)} KB raw / ${kib(totalGz)} KB gz / ${kib(totalBr)} KB br`);
|
|
212
|
-
console.log(`[bundle] entry SRI: ${sri}`);
|