@madojs/mado 0.5.1 → 0.6.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 +26 -0
- package/CHANGELOG.md +265 -0
- package/MADO_V1_PLAN.md +179 -0
- package/README.md +31 -13
- package/ROADMAP.md +28 -7
- package/TODO.md +72 -0
- package/dist/src/forms.d.ts +37 -4
- package/dist/src/forms.js +331 -57
- package/dist/src/forms.js.map +1 -1
- package/dist/src/html/bindings.d.ts +41 -0
- package/dist/src/html/bindings.js +163 -6
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html.d.ts +2 -0
- package/dist/src/html.js +1 -0
- package/dist/src/html.js.map +1 -1
- package/dist/src/index.d.ts +6 -6
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/page.d.ts +56 -0
- package/dist/src/page.js +17 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/resource.js +11 -0
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/manifest.d.ts +16 -1
- package/dist/src/router/manifest.js +210 -40
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +7 -2
- package/dist/src/router/match.js +14 -4
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.d.ts +10 -0
- package/dist/src/router/navigation.js +71 -3
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/signal.d.ts +15 -1
- package/dist/src/signal.js +112 -16
- package/dist/src/signal.js.map +1 -1
- package/docs/en/02-project-layout.md +99 -40
- package/docs/en/10-app-architecture.md +141 -0
- package/docs/en/11-layouts.md +115 -0
- package/docs/en/12-auth-and-api.md +217 -0
- package/docs/en/13-deployment.md +192 -0
- package/docs/en/14-testing.md +82 -0
- package/docs/en/15-error-handling.md +100 -0
- package/docs/en/16-bake-cookbook.md +93 -0
- package/docs/en/README.md +7 -0
- package/docs/fr/10-app-architecture.md +61 -0
- package/docs/fr/11-layouts.md +35 -0
- package/docs/fr/12-auth-and-api.md +35 -0
- package/docs/fr/13-deployment.md +39 -0
- package/docs/fr/14-testing.md +41 -0
- package/docs/fr/15-error-handling.md +50 -0
- package/docs/fr/16-bake-cookbook.md +35 -0
- package/docs/fr/README.md +7 -0
- package/docs/ru/10-app-architecture.md +100 -0
- package/docs/ru/11-layouts.md +47 -0
- package/docs/ru/12-auth-and-api.md +53 -0
- package/docs/ru/13-deployment.md +60 -0
- package/docs/ru/14-testing.md +50 -0
- package/docs/ru/15-error-handling.md +56 -0
- package/docs/ru/16-bake-cookbook.md +55 -0
- package/docs/ru/README.md +7 -0
- package/docs/uk/10-app-architecture.md +56 -0
- package/docs/uk/11-layouts.md +34 -0
- package/docs/uk/12-auth-and-api.md +34 -0
- package/docs/uk/13-deployment.md +39 -0
- package/docs/uk/14-testing.md +34 -0
- package/docs/uk/15-error-handling.md +32 -0
- package/docs/uk/16-bake-cookbook.md +36 -0
- package/docs/uk/README.md +7 -0
- package/llms.txt +9 -1
- package/package.json +3 -1
- package/scripts/_config.mjs +224 -0
- package/scripts/bake.mjs +266 -121
- package/scripts/bundle.mjs +133 -67
- package/scripts/cli.mjs +195 -27
- package/scripts/preview.mjs +125 -21
- package/server/serve.mjs +161 -10
- package/starters/admin/README.md +63 -0
- package/starters/admin/index.html +28 -0
- package/starters/admin/mado.config.json +22 -0
- package/starters/admin/package.json +24 -0
- package/starters/admin/public/favicon.svg +4 -0
- package/starters/admin/src/components/x-button.ts +55 -0
- package/starters/admin/src/components/x-input.ts +74 -0
- package/starters/admin/src/layouts/app.ts +101 -0
- package/starters/admin/src/layouts/auth.ts +41 -0
- package/starters/admin/src/lib/api.ts +133 -0
- package/starters/admin/src/lib/auth.ts +83 -0
- package/starters/admin/src/main.ts +15 -0
- package/starters/admin/src/pages/admin/dashboard.ts +48 -0
- package/starters/admin/src/pages/admin/order-detail.ts +80 -0
- package/starters/admin/src/pages/admin/orders.ts +117 -0
- package/starters/admin/src/pages/home.ts +34 -0
- package/starters/admin/src/pages/login.ts +70 -0
- package/starters/admin/src/pages/not-found.ts +12 -0
- package/starters/admin/src/routes.ts +40 -0
- package/starters/admin/src/styles/global.ts +86 -0
- package/starters/admin/tsconfig.json +15 -0
- package/starters/crud/index.html +12 -4
- package/starters/crud/mado.config.json +20 -0
- package/starters/crud/package.json +9 -3
- package/starters/crud/src/pages/home.ts +16 -0
- package/starters/crud/src/routes.ts +4 -2
- package/starters/minimal/index.html +12 -4
- package/starters/minimal/mado.config.json +20 -0
- package/starters/minimal/package.json +9 -3
- package/starters/minimal/src/pages/home.ts +17 -0
- package/starters/minimal/src/routes.ts +4 -2
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Orders list. Demonstrates queryParam() filters and each() keyed table rows.
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
each,
|
|
5
|
+
html,
|
|
6
|
+
jsonFetcher,
|
|
7
|
+
page,
|
|
8
|
+
queryParam,
|
|
9
|
+
resource,
|
|
10
|
+
} from "@madojs/mado";
|
|
11
|
+
|
|
12
|
+
interface Order {
|
|
13
|
+
id: string;
|
|
14
|
+
customer: string;
|
|
15
|
+
total: number;
|
|
16
|
+
status: "open" | "paid" | "shipped" | "cancelled";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default page({
|
|
20
|
+
title: "Orders",
|
|
21
|
+
view: () => {
|
|
22
|
+
const status = queryParam("status", "");
|
|
23
|
+
const search = queryParam("q", "");
|
|
24
|
+
|
|
25
|
+
const orders = resource(
|
|
26
|
+
() => {
|
|
27
|
+
const params = new URLSearchParams();
|
|
28
|
+
if (status()) params.set("status", status());
|
|
29
|
+
if (search()) params.set("q", search());
|
|
30
|
+
const qs = params.toString();
|
|
31
|
+
return `/api/admin/orders${qs ? `?${qs}` : ""}`;
|
|
32
|
+
},
|
|
33
|
+
jsonFetcher<Order[]>(),
|
|
34
|
+
{ staleTime: 5_000 },
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return html`
|
|
38
|
+
<header style="display:flex;align-items:center;gap:var(--space-3);margin:0 0 16px;">
|
|
39
|
+
<h1 style="margin:0;">Orders</h1>
|
|
40
|
+
<span class="spacer"></span>
|
|
41
|
+
<input
|
|
42
|
+
type="search"
|
|
43
|
+
placeholder="Search…"
|
|
44
|
+
.value=${search}
|
|
45
|
+
@input=${(e: Event) => {
|
|
46
|
+
const t = e.target as HTMLInputElement;
|
|
47
|
+
search.set(t.value);
|
|
48
|
+
}}
|
|
49
|
+
style="padding:6px 10px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg);color:var(--fg);"
|
|
50
|
+
>
|
|
51
|
+
<select
|
|
52
|
+
.value=${status}
|
|
53
|
+
@change=${(e: Event) => {
|
|
54
|
+
const t = e.target as HTMLSelectElement;
|
|
55
|
+
status.set(t.value);
|
|
56
|
+
}}
|
|
57
|
+
style="padding:6px 10px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg);color:var(--fg);"
|
|
58
|
+
>
|
|
59
|
+
<option value="">All statuses</option>
|
|
60
|
+
<option value="open">Open</option>
|
|
61
|
+
<option value="paid">Paid</option>
|
|
62
|
+
<option value="shipped">Shipped</option>
|
|
63
|
+
<option value="cancelled">Cancelled</option>
|
|
64
|
+
</select>
|
|
65
|
+
</header>
|
|
66
|
+
|
|
67
|
+
<div class="card" style="padding:0;overflow:hidden;">
|
|
68
|
+
${() => {
|
|
69
|
+
if (orders.loading() && !orders.data())
|
|
70
|
+
return html`<p class="muted" style="padding:16px;">Loading…</p>`;
|
|
71
|
+
if (orders.error())
|
|
72
|
+
return html`<p style="color:var(--danger);padding:16px;">
|
|
73
|
+
${orders.error()?.message}
|
|
74
|
+
</p>`;
|
|
75
|
+
const list = orders.data() ?? [];
|
|
76
|
+
if (list.length === 0)
|
|
77
|
+
return html`<p class="muted" style="padding:24px;text-align:center;">
|
|
78
|
+
No orders match the current filters.
|
|
79
|
+
</p>`;
|
|
80
|
+
return html`
|
|
81
|
+
<table style="width:100%;border-collapse:collapse;">
|
|
82
|
+
<thead>
|
|
83
|
+
<tr style="text-align:left;border-bottom:1px solid var(--border);">
|
|
84
|
+
<th style="padding:10px 14px;">ID</th>
|
|
85
|
+
<th style="padding:10px 14px;">Customer</th>
|
|
86
|
+
<th style="padding:10px 14px;">Status</th>
|
|
87
|
+
<th style="padding:10px 14px;text-align:right;">Total</th>
|
|
88
|
+
</tr>
|
|
89
|
+
</thead>
|
|
90
|
+
<tbody>
|
|
91
|
+
${() =>
|
|
92
|
+
each(
|
|
93
|
+
list,
|
|
94
|
+
(o) => o.id,
|
|
95
|
+
(o) => html`
|
|
96
|
+
<tr style="border-bottom:1px solid var(--border);">
|
|
97
|
+
<td style="padding:10px 14px;">
|
|
98
|
+
<a href="/admin/orders/${o.id}" data-link>${o.id}</a>
|
|
99
|
+
</td>
|
|
100
|
+
<td style="padding:10px 14px;">${o.customer}</td>
|
|
101
|
+
<td style="padding:10px 14px;">
|
|
102
|
+
<span class="muted">${o.status}</span>
|
|
103
|
+
</td>
|
|
104
|
+
<td style="padding:10px 14px;text-align:right;font-variant-numeric:tabular-nums;">
|
|
105
|
+
$${o.total.toFixed(2)}
|
|
106
|
+
</td>
|
|
107
|
+
</tr>
|
|
108
|
+
`,
|
|
109
|
+
)}
|
|
110
|
+
</tbody>
|
|
111
|
+
</table>
|
|
112
|
+
`;
|
|
113
|
+
}}
|
|
114
|
+
</div>
|
|
115
|
+
`;
|
|
116
|
+
},
|
|
117
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Public landing page. Demonstrates that the marketing surface can live
|
|
2
|
+
// alongside the admin app without a guard, and can be baked for SEO.
|
|
3
|
+
//
|
|
4
|
+
// `bake` is declared so that `mado bake` (and `mado release`) actually
|
|
5
|
+
// prerender at least one static page out of the box. Without it the
|
|
6
|
+
// release output ships only the SPA shell with no SEO-friendly HTML
|
|
7
|
+
// for crawlers landing on "/".
|
|
8
|
+
|
|
9
|
+
import { html, page } from "@madojs/mado";
|
|
10
|
+
|
|
11
|
+
export default page({
|
|
12
|
+
title: "Welcome",
|
|
13
|
+
head: () => ({
|
|
14
|
+
description: "An admin app scaffold built with Mado.",
|
|
15
|
+
og: {
|
|
16
|
+
title: "__APP_NAME__",
|
|
17
|
+
description: "An admin app scaffold built with Mado.",
|
|
18
|
+
type: "website",
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
bake: {
|
|
22
|
+
paths: () => [{}],
|
|
23
|
+
data: () => ({}),
|
|
24
|
+
},
|
|
25
|
+
view: () => html`
|
|
26
|
+
<main style="max-width:720px;margin:0 auto;padding:64px 24px;">
|
|
27
|
+
<h1>__APP_NAME__</h1>
|
|
28
|
+
<p>This is the public landing page.</p>
|
|
29
|
+
<p>
|
|
30
|
+
<a href="/admin" data-link>Open the admin app →</a>
|
|
31
|
+
</p>
|
|
32
|
+
</main>
|
|
33
|
+
`,
|
|
34
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Login page. Reads `?return=` to bounce the user back where they were
|
|
2
|
+
// blocked by `requireAuth`.
|
|
3
|
+
|
|
4
|
+
import { html, navigate, page, queryParam, signal, useForm } from "@madojs/mado";
|
|
5
|
+
import { ApiError } from "../lib/api.js";
|
|
6
|
+
import { login } from "../lib/auth.js";
|
|
7
|
+
import "../components/x-input.js";
|
|
8
|
+
import "../components/x-button.js";
|
|
9
|
+
|
|
10
|
+
export default page({
|
|
11
|
+
title: "Sign in",
|
|
12
|
+
view: () => {
|
|
13
|
+
const returnTo = queryParam("return", "/admin");
|
|
14
|
+
const serverError = signal<string | null>(null);
|
|
15
|
+
|
|
16
|
+
const form = useForm({
|
|
17
|
+
email: { required: true, type: "email" as const },
|
|
18
|
+
password: { required: true, min: 4 },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const onSubmit = form.onSubmit(async (values) => {
|
|
22
|
+
serverError.set(null);
|
|
23
|
+
try {
|
|
24
|
+
await login({
|
|
25
|
+
email: String(values.email ?? ""),
|
|
26
|
+
password: String(values.password ?? ""),
|
|
27
|
+
});
|
|
28
|
+
navigate(returnTo(), { replace: true });
|
|
29
|
+
} catch (e) {
|
|
30
|
+
if (e instanceof ApiError && e.status === 401) {
|
|
31
|
+
serverError.set("Invalid email or password.");
|
|
32
|
+
} else {
|
|
33
|
+
serverError.set("Something went wrong. Try again.");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return html`
|
|
39
|
+
<h1 style="margin:0 0 16px;">Sign in</h1>
|
|
40
|
+
<p class="muted" style="margin:0 0 24px;">
|
|
41
|
+
Enter your credentials to continue.
|
|
42
|
+
</p>
|
|
43
|
+
<form @submit=${onSubmit} class="stack">
|
|
44
|
+
<x-input
|
|
45
|
+
label="Email"
|
|
46
|
+
name="email"
|
|
47
|
+
type="email"
|
|
48
|
+
required
|
|
49
|
+
@input=${form.onInput}
|
|
50
|
+
@blur=${form.onBlur}
|
|
51
|
+
></x-input>
|
|
52
|
+
<x-input
|
|
53
|
+
label="Password"
|
|
54
|
+
name="password"
|
|
55
|
+
type="password"
|
|
56
|
+
required
|
|
57
|
+
@input=${form.onInput}
|
|
58
|
+
@blur=${form.onBlur}
|
|
59
|
+
></x-input>
|
|
60
|
+
${() =>
|
|
61
|
+
serverError()
|
|
62
|
+
? html`<small style="color:var(--danger);">${serverError()}</small>`
|
|
63
|
+
: null}
|
|
64
|
+
<x-button
|
|
65
|
+
?disabled=${() => !form.isValid() || form.submitting()}
|
|
66
|
+
>${() => (form.submitting() ? "Signing in…" : "Sign in")}</x-button>
|
|
67
|
+
</form>
|
|
68
|
+
`;
|
|
69
|
+
},
|
|
70
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { html, page } from "@madojs/mado";
|
|
2
|
+
|
|
3
|
+
export default page({
|
|
4
|
+
title: "Not found",
|
|
5
|
+
view: () => html`
|
|
6
|
+
<main style="max-width:560px;margin:0 auto;padding:80px 24px;text-align:center;">
|
|
7
|
+
<h1 style="margin:0 0 12px;">404</h1>
|
|
8
|
+
<p class="muted">This page does not exist.</p>
|
|
9
|
+
<p><a href="/" data-link>← Back home</a></p>
|
|
10
|
+
</main>
|
|
11
|
+
`,
|
|
12
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// The blessed routes manifest for an admin app.
|
|
2
|
+
//
|
|
3
|
+
// "/" → public landing
|
|
4
|
+
// "/login" → centered auth layout
|
|
5
|
+
// "/admin/*" → admin shell with sidebar/topbar, guarded by requireAuth
|
|
6
|
+
// "*" → 404
|
|
7
|
+
//
|
|
8
|
+
// Layouts and guards live inside the layout() blocks. There is exactly one
|
|
9
|
+
// canonical place to put a shell: a layout() in this manifest. Do not wrap
|
|
10
|
+
// route output in main.ts or in custom-element wrappers — that path causes
|
|
11
|
+
// the "shell-below-content" bug described in the v1 plan.
|
|
12
|
+
//
|
|
13
|
+
// Bake: `manifest` is exported separately for `mado bake` to discover pages
|
|
14
|
+
// that declare `bake: { paths, data }`. Without this named export `mado bake`
|
|
15
|
+
// fails with a clear error.
|
|
16
|
+
|
|
17
|
+
import { layout, routes } from "@madojs/mado";
|
|
18
|
+
import { requireAuth } from "./lib/auth.js";
|
|
19
|
+
|
|
20
|
+
export const manifest = {
|
|
21
|
+
"/": () => import("./pages/home.js"),
|
|
22
|
+
"/login": layout({
|
|
23
|
+
layout: () => import("./layouts/auth.js"),
|
|
24
|
+
routes: {
|
|
25
|
+
"/": () => import("./pages/login.js"),
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
"/admin": layout({
|
|
29
|
+
layout: () => import("./layouts/app.js"),
|
|
30
|
+
guard: requireAuth,
|
|
31
|
+
routes: {
|
|
32
|
+
"/": () => import("./pages/admin/dashboard.js"),
|
|
33
|
+
"/orders": () => import("./pages/admin/orders.js"),
|
|
34
|
+
"/orders/:id": () => import("./pages/admin/order-detail.js"),
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
"*": () => import("./pages/not-found.js"),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default routes(manifest);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Global design tokens + a tiny utility layer for admin UIs.
|
|
2
|
+
//
|
|
3
|
+
// Tokens are CSS custom properties on :root so layouts and components can pick
|
|
4
|
+
// them up without a CSS preprocessor. Light/dark via prefers-color-scheme.
|
|
5
|
+
//
|
|
6
|
+
// Keep this file intentionally small. Components should style themselves with
|
|
7
|
+
// Shadow DOM. Light DOM utilities (.row, .stack, .card) are here only because
|
|
8
|
+
// they show up in every admin page and would be noisy to redefine per component.
|
|
9
|
+
|
|
10
|
+
const css = `
|
|
11
|
+
:root {
|
|
12
|
+
color-scheme: light dark;
|
|
13
|
+
|
|
14
|
+
--bg: #ffffff;
|
|
15
|
+
--bg-elevated: #f7f8fa;
|
|
16
|
+
--fg: #0f172a;
|
|
17
|
+
--fg-muted: #475569;
|
|
18
|
+
--border: #e2e8f0;
|
|
19
|
+
--accent: #1f6feb;
|
|
20
|
+
--accent-fg: #ffffff;
|
|
21
|
+
--danger: #b91c1c;
|
|
22
|
+
--success: #15803d;
|
|
23
|
+
|
|
24
|
+
--radius: 8px;
|
|
25
|
+
--radius-sm: 6px;
|
|
26
|
+
--space-1: 4px;
|
|
27
|
+
--space-2: 8px;
|
|
28
|
+
--space-3: 12px;
|
|
29
|
+
--space-4: 16px;
|
|
30
|
+
--space-5: 24px;
|
|
31
|
+
--space-6: 32px;
|
|
32
|
+
|
|
33
|
+
--font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI",
|
|
34
|
+
Roboto, Inter, Helvetica, Arial, sans-serif;
|
|
35
|
+
|
|
36
|
+
--shadow-1: 0 1px 2px rgba(15, 23, 42, .05),
|
|
37
|
+
0 1px 1px rgba(15, 23, 42, .04);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@media (prefers-color-scheme: dark) {
|
|
41
|
+
:root {
|
|
42
|
+
--bg: #0b1220;
|
|
43
|
+
--bg-elevated: #111a2e;
|
|
44
|
+
--fg: #e6eefc;
|
|
45
|
+
--fg-muted: #9aa6bd;
|
|
46
|
+
--border: #1f2a44;
|
|
47
|
+
--accent: #3b82f6;
|
|
48
|
+
--accent-fg: #0b1220;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
* { box-sizing: border-box; }
|
|
53
|
+
html, body { margin: 0; padding: 0; }
|
|
54
|
+
body {
|
|
55
|
+
background: var(--bg);
|
|
56
|
+
color: var(--fg);
|
|
57
|
+
font-family: var(--font-sans);
|
|
58
|
+
font-size: 14px;
|
|
59
|
+
line-height: 1.5;
|
|
60
|
+
min-height: 100vh;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
a { color: var(--accent); text-decoration: none; }
|
|
64
|
+
a:hover { text-decoration: underline; }
|
|
65
|
+
|
|
66
|
+
.row { display: flex; align-items: center; gap: var(--space-3); }
|
|
67
|
+
.stack { display: flex; flex-direction: column; gap: var(--space-3); }
|
|
68
|
+
.spacer { flex: 1; }
|
|
69
|
+
|
|
70
|
+
.card {
|
|
71
|
+
background: var(--bg-elevated);
|
|
72
|
+
border: 1px solid var(--border);
|
|
73
|
+
border-radius: var(--radius);
|
|
74
|
+
padding: var(--space-5);
|
|
75
|
+
box-shadow: var(--shadow-1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.muted { color: var(--fg-muted); }
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
if (typeof document !== "undefined" && !document.getElementById("admin-global-style")) {
|
|
82
|
+
const tag = document.createElement("style");
|
|
83
|
+
tag.id = "admin-global-style";
|
|
84
|
+
tag.textContent = css;
|
|
85
|
+
document.head.appendChild(tag);
|
|
86
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022", "DOM"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": false,
|
|
12
|
+
"sourceMap": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*.ts"]
|
|
15
|
+
}
|
package/starters/crud/index.html
CHANGED
|
@@ -4,17 +4,25 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>__APP_NAME__</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
8
|
+
<!--
|
|
9
|
+
Paths below MUST be root-absolute (start with "/"), not "./...".
|
|
10
|
+
Mado is an SPA: hard-refreshing /users/42 still serves this same
|
|
11
|
+
index.html, and the browser resolves "./dist/main.js" against the
|
|
12
|
+
current URL → /users/dist/main.js → 404 → blank page.
|
|
13
|
+
Root-absolute paths always resolve to /dist/main.js regardless of route.
|
|
14
|
+
-->
|
|
7
15
|
<script type="importmap">
|
|
8
16
|
{
|
|
9
17
|
"imports": {
|
|
10
|
-
"@madojs/mado": "
|
|
11
|
-
"@madojs/mado/": "
|
|
18
|
+
"@madojs/mado": "/node_modules/@madojs/mado/dist/src/index.js",
|
|
19
|
+
"@madojs/mado/": "/node_modules/@madojs/mado/dist/src/"
|
|
12
20
|
}
|
|
13
21
|
}
|
|
14
22
|
</script>
|
|
15
23
|
</head>
|
|
16
24
|
<body>
|
|
17
25
|
<div id="app"></div>
|
|
18
|
-
<script type="module" src="
|
|
26
|
+
<script type="module" src="/dist/main.js"></script>
|
|
19
27
|
</body>
|
|
20
|
-
</html>
|
|
28
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dev": {
|
|
3
|
+
"port": 5173,
|
|
4
|
+
"proxy": {}
|
|
5
|
+
},
|
|
6
|
+
"build": {
|
|
7
|
+
"out": "out",
|
|
8
|
+
"dist": "dist",
|
|
9
|
+
"publicDir": "public"
|
|
10
|
+
},
|
|
11
|
+
"bake": {
|
|
12
|
+
"entry": "src/routes.ts",
|
|
13
|
+
"template": "index.html",
|
|
14
|
+
"baseUrl": "https://example.com"
|
|
15
|
+
},
|
|
16
|
+
"bundle": {
|
|
17
|
+
"splitting": true,
|
|
18
|
+
"compress": ["gz", "br"]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -4,15 +4,21 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"build": "
|
|
8
|
-
"typecheck": "
|
|
7
|
+
"build": "mado build",
|
|
8
|
+
"typecheck": "mado typecheck",
|
|
9
|
+
"dev": "mado dev",
|
|
9
10
|
"serve": "mado serve",
|
|
10
|
-
"
|
|
11
|
+
"bundle": "mado bundle",
|
|
12
|
+
"bake": "mado bake",
|
|
13
|
+
"release": "mado release",
|
|
14
|
+
"preview": "mado preview"
|
|
11
15
|
},
|
|
12
16
|
"dependencies": {
|
|
13
17
|
"@madojs/mado": "__MADOJS_VERSION__"
|
|
14
18
|
},
|
|
15
19
|
"devDependencies": {
|
|
20
|
+
"esbuild": "^0.24.0",
|
|
21
|
+
"linkedom": "^0.18.0",
|
|
16
22
|
"typescript": "^6.0.3"
|
|
17
23
|
}
|
|
18
24
|
}
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
+
// Public landing page. `bake` is declared so that `mado bake` (and
|
|
2
|
+
// `mado release`) actually prerender at least one SEO-friendly HTML page
|
|
3
|
+
// out of the box. Without it the build output ships only the SPA shell.
|
|
4
|
+
|
|
1
5
|
import { html, page } from "@madojs/mado";
|
|
2
6
|
|
|
3
7
|
export default page({
|
|
4
8
|
title: "__APP_NAME__",
|
|
9
|
+
head: () => ({
|
|
10
|
+
description: "A CRUD scaffold built with Mado.",
|
|
11
|
+
og: {
|
|
12
|
+
title: "__APP_NAME__",
|
|
13
|
+
description: "A CRUD scaffold built with Mado.",
|
|
14
|
+
type: "website",
|
|
15
|
+
},
|
|
16
|
+
}),
|
|
17
|
+
bake: {
|
|
18
|
+
paths: () => [{}],
|
|
19
|
+
data: () => ({}),
|
|
20
|
+
},
|
|
5
21
|
view: () => html`
|
|
6
22
|
<section class="page">
|
|
7
23
|
<div class="hero">
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { routes } from "@madojs/mado";
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export const manifest = {
|
|
4
4
|
"/": () => import("./pages/home.js"),
|
|
5
5
|
"/tickets": () => import("./pages/tickets.js"),
|
|
6
6
|
"/tickets/new": () => import("./pages/ticket-new.js"),
|
|
7
7
|
"/tickets/:id": () => import("./pages/ticket-detail.js"),
|
|
8
8
|
"*": () => import("./pages/not-found.js"),
|
|
9
|
-
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default routes(manifest);
|
|
@@ -4,17 +4,25 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>__APP_NAME__</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
8
|
+
<!--
|
|
9
|
+
Paths below MUST be root-absolute (start with "/"), not "./...".
|
|
10
|
+
Mado is an SPA: hard-refreshing any nested route still serves this same
|
|
11
|
+
index.html, and the browser resolves "./dist/main.js" against the
|
|
12
|
+
current URL → /some/nested/dist/main.js → 404 → blank page.
|
|
13
|
+
Root-absolute paths always resolve to /dist/main.js regardless of route.
|
|
14
|
+
-->
|
|
7
15
|
<script type="importmap">
|
|
8
16
|
{
|
|
9
17
|
"imports": {
|
|
10
|
-
"@madojs/mado": "
|
|
11
|
-
"@madojs/mado/": "
|
|
18
|
+
"@madojs/mado": "/node_modules/@madojs/mado/dist/src/index.js",
|
|
19
|
+
"@madojs/mado/": "/node_modules/@madojs/mado/dist/src/"
|
|
12
20
|
}
|
|
13
21
|
}
|
|
14
22
|
</script>
|
|
15
23
|
</head>
|
|
16
24
|
<body>
|
|
17
25
|
<div id="app"></div>
|
|
18
|
-
<script type="module" src="
|
|
26
|
+
<script type="module" src="/dist/main.js"></script>
|
|
19
27
|
</body>
|
|
20
|
-
</html>
|
|
28
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dev": {
|
|
3
|
+
"port": 5173,
|
|
4
|
+
"proxy": {}
|
|
5
|
+
},
|
|
6
|
+
"build": {
|
|
7
|
+
"out": "out",
|
|
8
|
+
"dist": "dist",
|
|
9
|
+
"publicDir": "public"
|
|
10
|
+
},
|
|
11
|
+
"bake": {
|
|
12
|
+
"entry": "src/routes.ts",
|
|
13
|
+
"template": "index.html",
|
|
14
|
+
"baseUrl": "https://example.com"
|
|
15
|
+
},
|
|
16
|
+
"bundle": {
|
|
17
|
+
"splitting": true,
|
|
18
|
+
"compress": ["gz", "br"]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -4,15 +4,21 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"build": "
|
|
8
|
-
"typecheck": "
|
|
7
|
+
"build": "mado build",
|
|
8
|
+
"typecheck": "mado typecheck",
|
|
9
|
+
"dev": "mado dev",
|
|
9
10
|
"serve": "mado serve",
|
|
10
|
-
"
|
|
11
|
+
"bundle": "mado bundle",
|
|
12
|
+
"bake": "mado bake",
|
|
13
|
+
"release": "mado release",
|
|
14
|
+
"preview": "mado preview"
|
|
11
15
|
},
|
|
12
16
|
"dependencies": {
|
|
13
17
|
"@madojs/mado": "__MADOJS_VERSION__"
|
|
14
18
|
},
|
|
15
19
|
"devDependencies": {
|
|
20
|
+
"esbuild": "^0.24.0",
|
|
21
|
+
"linkedom": "^0.18.0",
|
|
16
22
|
"typescript": "^6.0.3"
|
|
17
23
|
}
|
|
18
24
|
}
|
|
@@ -1,7 +1,24 @@
|
|
|
1
|
+
// Public landing page. `bake` is declared so that `mado bake` (and
|
|
2
|
+
// `mado release`) actually prerender a static HTML page out of the box.
|
|
3
|
+
// Without it the build output ships only the SPA shell — bots that hit
|
|
4
|
+
// "/" see an empty <body> instead of the welcome content.
|
|
5
|
+
|
|
1
6
|
import { html, page } from "@madojs/mado";
|
|
2
7
|
|
|
3
8
|
export default page({
|
|
4
9
|
title: "__APP_NAME__",
|
|
10
|
+
head: () => ({
|
|
11
|
+
description: "A minimal Mado starter app.",
|
|
12
|
+
og: {
|
|
13
|
+
title: "__APP_NAME__",
|
|
14
|
+
description: "A minimal Mado starter app.",
|
|
15
|
+
type: "website",
|
|
16
|
+
},
|
|
17
|
+
}),
|
|
18
|
+
bake: {
|
|
19
|
+
paths: () => [{}],
|
|
20
|
+
data: () => ({}),
|
|
21
|
+
},
|
|
5
22
|
view: () => html`
|
|
6
23
|
<main class="shell">
|
|
7
24
|
<section class="panel">
|