@madojs/mado 0.5.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 +291 -0
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +371 -0
- package/ROADMAP.md +52 -0
- package/dist/src/component.d.ts +48 -0
- package/dist/src/component.js +140 -0
- package/dist/src/component.js.map +1 -0
- package/dist/src/context.d.ts +40 -0
- package/dist/src/context.js +67 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/css.d.ts +54 -0
- package/dist/src/css.js +137 -0
- package/dist/src/css.js.map +1 -0
- package/dist/src/devtools.d.ts +22 -0
- package/dist/src/devtools.js +63 -0
- package/dist/src/devtools.js.map +1 -0
- package/dist/src/diagnostics.d.ts +11 -0
- package/dist/src/diagnostics.js +28 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/each.d.ts +39 -0
- package/dist/src/each.js +35 -0
- package/dist/src/each.js.map +1 -0
- package/dist/src/forms.d.ts +71 -0
- package/dist/src/forms.js +161 -0
- package/dist/src/forms.js.map +1 -0
- package/dist/src/head.d.ts +19 -0
- package/dist/src/head.js +97 -0
- package/dist/src/head.js.map +1 -0
- package/dist/src/html/bindings.d.ts +78 -0
- package/dist/src/html/bindings.js +304 -0
- package/dist/src/html/bindings.js.map +1 -0
- package/dist/src/html/parser.d.ts +64 -0
- package/dist/src/html/parser.js +521 -0
- package/dist/src/html/parser.js.map +1 -0
- package/dist/src/html/template-types.d.ts +27 -0
- package/dist/src/html/template-types.js +8 -0
- package/dist/src/html/template-types.js.map +1 -0
- package/dist/src/html/template.d.ts +45 -0
- package/dist/src/html/template.js +119 -0
- package/dist/src/html/template.js.map +1 -0
- package/dist/src/html.d.ts +16 -0
- package/dist/src/html.js +16 -0
- package/dist/src/html.js.map +1 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.js +39 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lazy.d.ts +38 -0
- package/dist/src/lazy.js +73 -0
- package/dist/src/lazy.js.map +1 -0
- package/dist/src/lifecycle.d.ts +45 -0
- package/dist/src/lifecycle.js +66 -0
- package/dist/src/lifecycle.js.map +1 -0
- package/dist/src/page.d.ts +161 -0
- package/dist/src/page.js +38 -0
- package/dist/src/page.js.map +1 -0
- package/dist/src/persisted.d.ts +47 -0
- package/dist/src/persisted.js +119 -0
- package/dist/src/persisted.js.map +1 -0
- package/dist/src/resource.d.ts +120 -0
- package/dist/src/resource.js +275 -0
- package/dist/src/resource.js.map +1 -0
- package/dist/src/router/manifest.d.ts +56 -0
- package/dist/src/router/manifest.js +302 -0
- package/dist/src/router/manifest.js.map +1 -0
- package/dist/src/router/match.d.ts +62 -0
- package/dist/src/router/match.js +117 -0
- package/dist/src/router/match.js.map +1 -0
- package/dist/src/router/navigation.d.ts +89 -0
- package/dist/src/router/navigation.js +263 -0
- package/dist/src/router/navigation.js.map +1 -0
- package/dist/src/router.d.ts +13 -0
- package/dist/src/router.js +13 -0
- package/dist/src/router.js.map +1 -0
- package/dist/src/signal.d.ts +67 -0
- package/dist/src/signal.js +238 -0
- package/dist/src/signal.js.map +1 -0
- package/docs/README.md +12 -0
- package/docs/en/00-the-mado-way.md +106 -0
- package/docs/en/01-routing.md +204 -0
- package/docs/en/02-project-layout.md +58 -0
- package/docs/en/03-static-bake.md +251 -0
- package/docs/en/04-ide-setup.md +162 -0
- package/docs/en/05-why-mado.md +193 -0
- package/docs/en/06-for-backenders.md +422 -0
- package/docs/en/07-llm-pitfalls.md +486 -0
- package/docs/en/08-llm-zero-history-test.md +56 -0
- package/docs/en/09-shadow-vs-light-dom.md +122 -0
- package/docs/en/README.md +16 -0
- package/docs/fr/00-the-mado-way.md +108 -0
- package/docs/fr/01-routing.md +202 -0
- package/docs/fr/02-project-layout.md +58 -0
- package/docs/fr/03-static-bake.md +290 -0
- package/docs/fr/04-ide-setup.md +162 -0
- package/docs/fr/05-why-mado.md +193 -0
- package/docs/fr/06-for-backenders.md +432 -0
- package/docs/fr/07-llm-pitfalls.md +487 -0
- package/docs/fr/08-llm-zero-history-test.md +60 -0
- package/docs/fr/09-shadow-vs-light-dom.md +121 -0
- package/docs/fr/README.md +16 -0
- package/docs/ru/00-the-mado-way.md +93 -0
- package/docs/ru/01-routing.md +194 -0
- package/docs/ru/02-project-layout.md +57 -0
- package/docs/ru/03-static-bake.md +251 -0
- package/docs/ru/04-ide-setup.md +144 -0
- package/docs/ru/05-why-mado.md +193 -0
- package/docs/ru/06-for-backenders.md +422 -0
- package/docs/ru/07-llm-pitfalls.md +485 -0
- package/docs/ru/08-llm-zero-history-test.md +56 -0
- package/docs/ru/09-shadow-vs-light-dom.md +122 -0
- package/docs/ru/README.md +14 -0
- package/docs/uk/00-the-mado-way.md +54 -0
- package/docs/uk/01-routing.md +82 -0
- package/docs/uk/02-project-layout.md +46 -0
- package/docs/uk/03-static-bake.md +49 -0
- package/docs/uk/04-ide-setup.md +26 -0
- package/docs/uk/05-why-mado.md +34 -0
- package/docs/uk/06-for-backenders.md +50 -0
- package/docs/uk/07-llm-pitfalls.md +82 -0
- package/docs/uk/08-llm-zero-history-test.md +31 -0
- package/docs/uk/09-shadow-vs-light-dom.md +40 -0
- package/docs/uk/README.md +16 -0
- package/llms.txt +155 -0
- package/package.json +81 -0
- package/scripts/bake.mjs +406 -0
- package/scripts/bundle.mjs +146 -0
- package/scripts/cli.mjs +382 -0
- package/scripts/new.mjs +80 -0
- package/scripts/preview.mjs +176 -0
- package/scripts/release-notes.mjs +66 -0
- package/scripts/showcase-regression.mjs +392 -0
- package/server/serve.mjs +292 -0
- package/starters/crud/README.md +21 -0
- package/starters/crud/index.html +20 -0
- package/starters/crud/package.json +17 -0
- package/starters/crud/src/components/app-shell.ts +51 -0
- package/starters/crud/src/components/ticket-detail.ts +33 -0
- package/starters/crud/src/components/ticket-form.ts +69 -0
- package/starters/crud/src/components/ticket-list.ts +66 -0
- package/starters/crud/src/lib/api.ts +76 -0
- package/starters/crud/src/main.ts +12 -0
- package/starters/crud/src/pages/home.ts +18 -0
- package/starters/crud/src/pages/not-found.ts +12 -0
- package/starters/crud/src/pages/ticket-detail.ts +6 -0
- package/starters/crud/src/pages/ticket-new.ts +6 -0
- package/starters/crud/src/pages/tickets.ts +6 -0
- package/starters/crud/src/routes.ts +9 -0
- package/starters/crud/src/styles/global.ts +155 -0
- package/starters/crud/tsconfig.json +15 -0
- package/starters/minimal/README.md +19 -0
- package/starters/minimal/index.html +20 -0
- package/starters/minimal/package.json +17 -0
- package/starters/minimal/src/components/app-counter.ts +31 -0
- package/starters/minimal/src/main.ts +9 -0
- package/starters/minimal/src/pages/home.ts +18 -0
- package/starters/minimal/src/pages/not-found.ts +14 -0
- package/starters/minimal/src/routes.ts +6 -0
- package/starters/minimal/src/styles/global.ts +60 -0
- package/starters/minimal/tsconfig.json +15 -0
- package/templates/page-detail.ts +63 -0
- package/templates/page-form.ts +94 -0
- package/templates/page-list.ts +79 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template instantiation + public `html\`\`` tag + `render()`.
|
|
3
|
+
*
|
|
4
|
+
* Data flow:
|
|
5
|
+
* html`...` → TemplateResult { strings, values }
|
|
6
|
+
* parseTemplate(strings) → ParsedTemplate { template, bindings } (cached by strings)
|
|
7
|
+
* instantiate(result) → InstantiatedTemplate { fragment, nodes, update, dispose }
|
|
8
|
+
* render(result, host) → clones or reuses instance in host
|
|
9
|
+
*
|
|
10
|
+
* Only the glue lives here: parser and bindings are in neighbour files.
|
|
11
|
+
*/
|
|
12
|
+
import { warnOnce } from "../diagnostics.js";
|
|
13
|
+
import { parseTemplate, resolvePath, } from "./parser.js";
|
|
14
|
+
import { bindAttr, bindChild, createChildState, disposeChildState, } from "./bindings.js";
|
|
15
|
+
/**
|
|
16
|
+
* `html\`<div>${value}</div>\`` → template descriptor.
|
|
17
|
+
*
|
|
18
|
+
* By itself renders and parses NOTHING — this simply captures
|
|
19
|
+
* { strings, values } from the tagged-template literal. The heavy work
|
|
20
|
+
* (parsing + cloning) happens on the first `render()` /
|
|
21
|
+
* `instantiate()` call and is cached by `strings` identity.
|
|
22
|
+
*/
|
|
23
|
+
export function html(strings, ...values) {
|
|
24
|
+
return { _mado: true, strings, values };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a ready template instance: clones the pre-parsed template,
|
|
28
|
+
* resolves all BindingSpec → concrete DOM nodes of the clone, binds
|
|
29
|
+
* initial values. The returned object is self-contained: update(values)
|
|
30
|
+
* patches only what actually changed, dispose() cleans up effects
|
|
31
|
+
* and removes nodes from the DOM.
|
|
32
|
+
*
|
|
33
|
+
* Exported (not only used from render()) so that
|
|
34
|
+
* keyed `each` reconciliation can manage instance lifetimes directly.
|
|
35
|
+
*/
|
|
36
|
+
export function instantiate(result) {
|
|
37
|
+
const parsed = parseTemplate(result.strings);
|
|
38
|
+
const fragment = parsed.template.content.cloneNode(true);
|
|
39
|
+
const disposers = [];
|
|
40
|
+
const childStates = new Map();
|
|
41
|
+
const attrBound = new Map();
|
|
42
|
+
// Resolve all BindingSpec.path → concrete nodes of the cloned
|
|
43
|
+
// fragment. This is done ONCE, in the instance creation phase.
|
|
44
|
+
for (const b of parsed.bindings) {
|
|
45
|
+
if (b.type === "child") {
|
|
46
|
+
const parent = resolvePath(fragment, b.path);
|
|
47
|
+
const placeholder = parent.childNodes[b.childIndex];
|
|
48
|
+
childStates.set(b.id, createChildState(placeholder));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const el = resolvePath(fragment, b.path);
|
|
52
|
+
attrBound.set(b.id, { el, spec: b });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const update = (values) => {
|
|
56
|
+
// Before each update unsubscribe from the previous pass's subscriptions.
|
|
57
|
+
// This is needed because one of the values might have been a signal
|
|
58
|
+
// but now is not (or vice versa), and we need a fresh re-subscribe.
|
|
59
|
+
for (const d of disposers.splice(0))
|
|
60
|
+
d();
|
|
61
|
+
for (const b of parsed.bindings) {
|
|
62
|
+
if (b.type === "child") {
|
|
63
|
+
const st = childStates.get(b.id);
|
|
64
|
+
bindChild(st, values[b.slot], disposers, instantiate);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const ab = attrBound.get(b.id);
|
|
68
|
+
bindAttr(ab.el, ab.spec, values, disposers);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
update(result.values);
|
|
73
|
+
const nodes = [...fragment.childNodes];
|
|
74
|
+
return {
|
|
75
|
+
fragment,
|
|
76
|
+
nodes,
|
|
77
|
+
update,
|
|
78
|
+
dispose() {
|
|
79
|
+
for (const d of disposers.splice(0))
|
|
80
|
+
d();
|
|
81
|
+
for (const st of childStates.values())
|
|
82
|
+
disposeChildState(st);
|
|
83
|
+
for (const n of nodes)
|
|
84
|
+
n.parentNode?.removeChild(n);
|
|
85
|
+
},
|
|
86
|
+
_strings: result.strings,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// ---------- Public render ----------
|
|
90
|
+
const rendered = new WeakMap();
|
|
91
|
+
/**
|
|
92
|
+
* Render a TemplateResult into a container.
|
|
93
|
+
*
|
|
94
|
+
* Semantics:
|
|
95
|
+
* - first call → instantiate + appendChild;
|
|
96
|
+
* - repeated call with the same tagged literal (same strings identity) →
|
|
97
|
+
* update(values), DOM is not recreated;
|
|
98
|
+
* - repeated call with a different literal → dispose old + new instantiate.
|
|
99
|
+
*
|
|
100
|
+
* Container can be either an Element or a ShadowRoot (used
|
|
101
|
+
* in component() for rendering into a shadow tree).
|
|
102
|
+
*/
|
|
103
|
+
export function render(result, container) {
|
|
104
|
+
const existing = rendered.get(container);
|
|
105
|
+
if (existing && existing.nodes[0]?.parentNode === container) {
|
|
106
|
+
if (existing._strings === result.strings) {
|
|
107
|
+
existing.update(result.values);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
existing.dispose();
|
|
111
|
+
}
|
|
112
|
+
if (!existing && container.childNodes.length > 0) {
|
|
113
|
+
warnOnce("render-unmanaged-dom", "render() called on a container with existing DOM that was not created by Mado. It will remain alongside the new render output.");
|
|
114
|
+
}
|
|
115
|
+
const inst = instantiate(result);
|
|
116
|
+
container.appendChild(inst.fragment);
|
|
117
|
+
rendered.set(container, inst);
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/html/template.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
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 type { TemplateResult } from "./html/template-types.js";
|
package/dist/src/html.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
//# sourceMappingURL=html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mado — public API.
|
|
3
|
+
*
|
|
4
|
+
* Import everything from one place:
|
|
5
|
+
* import { signal, computed, effect, component, html, router } from '@madojs/mado';
|
|
6
|
+
*
|
|
7
|
+
* In the browser: connected via <script type="importmap">.
|
|
8
|
+
* In Node: via `tsconfig.paths` (resolves to "./src/index.ts").
|
|
9
|
+
*/
|
|
10
|
+
export { signal, computed, effect, untracked, batch, flushSync, } from "./signal.js";
|
|
11
|
+
export type { Signal, Computed, Disposer } from "./signal.js";
|
|
12
|
+
export { html, render } from "./html.js";
|
|
13
|
+
export type { TemplateResult } from "./html.js";
|
|
14
|
+
export { each, list } from "./each.js";
|
|
15
|
+
export { component } from "./component.js";
|
|
16
|
+
export type { ComponentContext, ComponentOptions, SetupFn, StyleInput, } from "./component.js";
|
|
17
|
+
export { css, cssVars, adopt, scopeStyles } from "./css.js";
|
|
18
|
+
export type { CSSResult } from "./css.js";
|
|
19
|
+
export { router, routes, queryParam, prefetchPath, navigate, } from "./router.js";
|
|
20
|
+
export type { RouterApi, RouteHandler, RouteParams, Routes, RoutesMap, RoutesOptions, QueryParam, QuerySignal, } from "./router.js";
|
|
21
|
+
export { page, nested, isPage, isNested } from "./page.js";
|
|
22
|
+
export type { Page, PageContext, PageData, RouteEntry, NestedRoutes, HeadMeta, BakeConfig, } from "./page.js";
|
|
23
|
+
export { applyHead } from "./head.js";
|
|
24
|
+
export { resource, mutation, invalidate, jsonFetcher, HttpError, } from "./resource.js";
|
|
25
|
+
export type { Resource, ResourceOptions, Mutation, MutationOptions, } from "./resource.js";
|
|
26
|
+
export { useForm } from "./forms.js";
|
|
27
|
+
export type { FormApi, FormValues, FormErrors, FieldSchema, Schema, UseFormOptions, } from "./forms.js";
|
|
28
|
+
export { createContext, provide, inject } from "./context.js";
|
|
29
|
+
export type { Context } from "./context.js";
|
|
30
|
+
export { persisted } from "./persisted.js";
|
|
31
|
+
export type { PersistedOptions, PersistedSignal } from "./persisted.js";
|
|
32
|
+
export { lazy } from "./lazy.js";
|
|
33
|
+
export type { LazyOptions } from "./lazy.js";
|
|
34
|
+
export { getCurrentLifecycle, runInLifecycle, createLifecycle, } from "./lifecycle.js";
|
|
35
|
+
export type { Lifecycle, LifecycleHandle } from "./lifecycle.js";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mado — public API.
|
|
3
|
+
*
|
|
4
|
+
* Import everything from one place:
|
|
5
|
+
* import { signal, computed, effect, component, html, router } from '@madojs/mado';
|
|
6
|
+
*
|
|
7
|
+
* In the browser: connected via <script type="importmap">.
|
|
8
|
+
* In Node: via `tsconfig.paths` (resolves to "./src/index.ts").
|
|
9
|
+
*/
|
|
10
|
+
// --- core ---
|
|
11
|
+
export { signal, computed, effect, untracked, batch, flushSync, } from "./signal.js";
|
|
12
|
+
// --- rendering ---
|
|
13
|
+
export { html, render } from "./html.js";
|
|
14
|
+
export { each, list } from "./each.js";
|
|
15
|
+
// --- components ---
|
|
16
|
+
export { component } from "./component.js";
|
|
17
|
+
// --- styles ---
|
|
18
|
+
export { css, cssVars, adopt, scopeStyles } from "./css.js";
|
|
19
|
+
// --- routing ---
|
|
20
|
+
export { router, routes, queryParam, prefetchPath, navigate, } from "./router.js";
|
|
21
|
+
export { page, nested, isPage, isNested } from "./page.js";
|
|
22
|
+
export { applyHead } from "./head.js";
|
|
23
|
+
// --- data ---
|
|
24
|
+
export { resource, mutation, invalidate, jsonFetcher, HttpError, } from "./resource.js";
|
|
25
|
+
// --- forms ---
|
|
26
|
+
export { useForm } from "./forms.js";
|
|
27
|
+
// --- DI ---
|
|
28
|
+
export { createContext, provide, inject } from "./context.js";
|
|
29
|
+
// --- persistence ---
|
|
30
|
+
export { persisted } from "./persisted.js";
|
|
31
|
+
// --- lazy ---
|
|
32
|
+
export { lazy } from "./lazy.js";
|
|
33
|
+
// --- lifecycle (advanced API) ---
|
|
34
|
+
//
|
|
35
|
+
// Normally you don't need this: inside component() the lifecycle is created
|
|
36
|
+
// automatically. Only needed if you want to use resource() or
|
|
37
|
+
// other helpers outside a component and still get cleanup.
|
|
38
|
+
export { getCurrentLifecycle, runInLifecycle, createLifecycle, } from "./lifecycle.js";
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAe;AACf,OAAO,EACL,MAAM,EACN,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,SAAS,GACV,MAAM,aAAa,CAAC;AAGrB,oBAAoB;AACpB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGzC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEvC,qBAAqB;AACrB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAQ3C,iBAAiB;AACjB,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAG5D,kBAAkB;AAClB,OAAO,EACL,MAAM,EACN,MAAM,EACN,UAAU,EACV,YAAY,EACZ,QAAQ,GACT,MAAM,aAAa,CAAC;AAYrB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAW3D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,eAAe;AACf,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,WAAW,EACX,SAAS,GACV,MAAM,eAAe,CAAC;AAQvB,gBAAgB;AAChB,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAUrC,aAAa;AACb,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG9D,sBAAsB;AACtB,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,eAAe;AACf,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,mCAAmC;AACnC,EAAE;AACF,4EAA4E;AAC5E,8DAA8D;AAC9D,2DAA2D;AAC3D,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,eAAe,GAChB,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lazy() — deferred module loading with a reactive placeholder.
|
|
3
|
+
*
|
|
4
|
+
* Why: heavy components (charts, code editors, modals)
|
|
5
|
+
* don't need to be loaded on startup. lazy() gives a TemplateResult function
|
|
6
|
+
* that returns a fallback until loaded, then the real template.
|
|
7
|
+
*
|
|
8
|
+
* const Chart = lazy(
|
|
9
|
+
* () => import('./components/chart.js'),
|
|
10
|
+
* { fallback: () => html`<x-spinner/>` },
|
|
11
|
+
* );
|
|
12
|
+
*
|
|
13
|
+
* html`<div>${Chart({ data })}</div>`
|
|
14
|
+
*
|
|
15
|
+
* Module contract: must default-export a function
|
|
16
|
+
* (props) => TemplateResult.
|
|
17
|
+
*
|
|
18
|
+
* Loading is cached (one import() per loader reference).
|
|
19
|
+
*/
|
|
20
|
+
import { type TemplateResult } from "./html.js";
|
|
21
|
+
export interface LazyOptions<P> {
|
|
22
|
+
/** What to show while the module is loading. */
|
|
23
|
+
fallback?: (props: P) => TemplateResult;
|
|
24
|
+
/** What to show if the import failed. Default — empty string. */
|
|
25
|
+
error?: (err: Error, props: P) => TemplateResult;
|
|
26
|
+
}
|
|
27
|
+
type Renderer<P> = (props: P) => TemplateResult;
|
|
28
|
+
/**
|
|
29
|
+
* Create a lazy component.
|
|
30
|
+
* Takes a dynamic import, returns a render function.
|
|
31
|
+
*
|
|
32
|
+
* const Modal = lazy(() => import('./modal.js'));
|
|
33
|
+
* html`${open() ? Modal({ onClose }) : null}`
|
|
34
|
+
*/
|
|
35
|
+
export declare function lazy<P = unknown>(loader: () => Promise<{
|
|
36
|
+
default: Renderer<P>;
|
|
37
|
+
}>, options?: LazyOptions<P>): (props: P) => TemplateResult;
|
|
38
|
+
export {};
|
package/dist/src/lazy.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lazy() — deferred module loading with a reactive placeholder.
|
|
3
|
+
*
|
|
4
|
+
* Why: heavy components (charts, code editors, modals)
|
|
5
|
+
* don't need to be loaded on startup. lazy() gives a TemplateResult function
|
|
6
|
+
* that returns a fallback until loaded, then the real template.
|
|
7
|
+
*
|
|
8
|
+
* const Chart = lazy(
|
|
9
|
+
* () => import('./components/chart.js'),
|
|
10
|
+
* { fallback: () => html`<x-spinner/>` },
|
|
11
|
+
* );
|
|
12
|
+
*
|
|
13
|
+
* html`<div>${Chart({ data })}</div>`
|
|
14
|
+
*
|
|
15
|
+
* Module contract: must default-export a function
|
|
16
|
+
* (props) => TemplateResult.
|
|
17
|
+
*
|
|
18
|
+
* Loading is cached (one import() per loader reference).
|
|
19
|
+
*/
|
|
20
|
+
import { signal } from "./signal.js";
|
|
21
|
+
import { html } from "./html.js";
|
|
22
|
+
const cache = new WeakMap();
|
|
23
|
+
/**
|
|
24
|
+
* Create a lazy component.
|
|
25
|
+
* Takes a dynamic import, returns a render function.
|
|
26
|
+
*
|
|
27
|
+
* const Modal = lazy(() => import('./modal.js'));
|
|
28
|
+
* html`${open() ? Modal({ onClose }) : null}`
|
|
29
|
+
*/
|
|
30
|
+
export function lazy(loader, options = {}) {
|
|
31
|
+
return (props) => {
|
|
32
|
+
// One signal trigger per call instance; the html binder will resubscribe
|
|
33
|
+
// when the state changes.
|
|
34
|
+
const trig = signal(0);
|
|
35
|
+
const state = ensureLoaded(loader);
|
|
36
|
+
if (state.state === "loading") {
|
|
37
|
+
// subscribe: when state transitions to ready/error, trigger trig
|
|
38
|
+
const wait = waitFor(loader);
|
|
39
|
+
wait.then(() => trig.update((n) => n + 1), () => trig.update((n) => n + 1));
|
|
40
|
+
}
|
|
41
|
+
return html `${() => {
|
|
42
|
+
trig(); // subscription
|
|
43
|
+
const s = ensureLoaded(loader);
|
|
44
|
+
if (s.state === "ready" && s.renderer)
|
|
45
|
+
return s.renderer(props);
|
|
46
|
+
if (s.state === "error" && s.err) {
|
|
47
|
+
return options.error?.(s.err, props) ?? "";
|
|
48
|
+
}
|
|
49
|
+
return options.fallback?.(props) ?? "";
|
|
50
|
+
}}`;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function ensureLoaded(loader) {
|
|
54
|
+
const existing = cache.get(loader);
|
|
55
|
+
if (existing)
|
|
56
|
+
return existing;
|
|
57
|
+
const fresh = { state: "loading" };
|
|
58
|
+
cache.set(loader, fresh);
|
|
59
|
+
loader().then((mod) => {
|
|
60
|
+
fresh.state = "ready";
|
|
61
|
+
fresh.renderer = mod.default;
|
|
62
|
+
}, (err) => {
|
|
63
|
+
fresh.state = "error";
|
|
64
|
+
fresh.err = err instanceof Error ? err : new Error(String(err));
|
|
65
|
+
});
|
|
66
|
+
return fresh;
|
|
67
|
+
}
|
|
68
|
+
function waitFor(loader) {
|
|
69
|
+
// return the same Promise as ensureLoaded — otherwise we'd create a new one.
|
|
70
|
+
// The browser actually caches the same module, so the second call returns instantly.
|
|
71
|
+
return loader();
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=lazy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy.js","sourceRoot":"","sources":["../../src/lazy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,IAAI,EAAuB,MAAM,WAAW,CAAC;AAiBtD,MAAM,KAAK,GAAG,IAAI,OAAO,EAA8B,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,UAAU,IAAI,CAClB,MAA+C,EAC/C,UAA0B,EAAE;IAE5B,OAAO,CAAC,KAAQ,EAAE,EAAE;QAClB,yEAAyE;QACzE,0BAA0B;QAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACvB,MAAM,KAAK,GAAG,YAAY,CAAI,MAAM,CAAC,CAAC;QAEtC,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC9B,iEAAiE;YACjE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;YAC7B,IAAI,CAAC,IAAI,CACP,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAC/B,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAChC,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAA,GAAG,GAAG,EAAE;YACjB,IAAI,EAAE,CAAC,CAAC,eAAe;YACvB,MAAM,CAAC,GAAG,YAAY,CAAI,MAAM,CAAC,CAAC;YAClC,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,IAAI,CAAC,CAAC,QAAQ;gBAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAChE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;gBACjC,OAAO,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;YAC7C,CAAC;YACD,OAAO,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACzC,CAAC,EAAE,CAAC;IACN,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CACnB,MAA+C;IAE/C,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,MAA2B,CAEzC,CAAC;IACd,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,KAAK,GAAiB,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACjD,KAAK,CAAC,GAAG,CAAC,MAA2B,EAAE,KAA2B,CAAC,CAAC;IACpE,MAAM,EAAE,CAAC,IAAI,CACX,CAAC,GAAG,EAAE,EAAE;QACN,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC;QACtB,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC;IAC/B,CAAC,EACD,CAAC,GAAY,EAAE,EAAE;QACf,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC;QACtB,KAAK,CAAC,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,CAAC,CACF,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,OAAO,CACd,MAA+C;IAE/C,6EAA6E;IAC7E,qFAAqF;IACrF,OAAO,MAAM,EAAE,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle context for auto-cleanup of resources inside a component.
|
|
3
|
+
*
|
|
4
|
+
* Core idea: when a component-setup runs, we push the current
|
|
5
|
+
* "lifecycle" — an object with onDispose() — onto a module-local stack.
|
|
6
|
+
* Any function like resource() that creates long-lived subscriptions
|
|
7
|
+
* (timers, listeners, network subscriptions) can call getCurrentLifecycle()
|
|
8
|
+
* and register its own cleanup.
|
|
9
|
+
*
|
|
10
|
+
* This avoids leaks on component unmount — without explicitly threading
|
|
11
|
+
* ComponentContext into every helper.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
*
|
|
15
|
+
* // in component.ts
|
|
16
|
+
* runInLifecycle(myLifecycle, () => setup(ctx));
|
|
17
|
+
*
|
|
18
|
+
* // in resource.ts
|
|
19
|
+
* const lc = getCurrentLifecycle();
|
|
20
|
+
* if (lc) lc.onDispose(() => abort.abort());
|
|
21
|
+
* else console.warn('[mado] resource() outside component — cleanup must be manual');
|
|
22
|
+
*/
|
|
23
|
+
import type { Disposer } from "./signal.js";
|
|
24
|
+
export interface Lifecycle {
|
|
25
|
+
/** Register a cleanup function. Called when the lifecycle is disposed. */
|
|
26
|
+
onDispose(fn: Disposer): void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Return the currently active lifecycle, or null if code runs
|
|
30
|
+
* outside a component setup.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getCurrentLifecycle(): Lifecycle | null;
|
|
33
|
+
/**
|
|
34
|
+
* Execute fn with the given lifecycle set. Supports nesting:
|
|
35
|
+
* the previous lifecycle is restored after fn returns (including exceptions).
|
|
36
|
+
*/
|
|
37
|
+
export declare function runInLifecycle<T>(lc: Lifecycle, fn: () => T): T;
|
|
38
|
+
/**
|
|
39
|
+
* Create a new lifecycle. Returns the Lifecycle interface and a
|
|
40
|
+
* dispose() method that runs all registered cleanup callbacks.
|
|
41
|
+
*/
|
|
42
|
+
export interface LifecycleHandle extends Lifecycle {
|
|
43
|
+
dispose(): void;
|
|
44
|
+
}
|
|
45
|
+
export declare function createLifecycle(): LifecycleHandle;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle context for auto-cleanup of resources inside a component.
|
|
3
|
+
*
|
|
4
|
+
* Core idea: when a component-setup runs, we push the current
|
|
5
|
+
* "lifecycle" — an object with onDispose() — onto a module-local stack.
|
|
6
|
+
* Any function like resource() that creates long-lived subscriptions
|
|
7
|
+
* (timers, listeners, network subscriptions) can call getCurrentLifecycle()
|
|
8
|
+
* and register its own cleanup.
|
|
9
|
+
*
|
|
10
|
+
* This avoids leaks on component unmount — without explicitly threading
|
|
11
|
+
* ComponentContext into every helper.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
*
|
|
15
|
+
* // in component.ts
|
|
16
|
+
* runInLifecycle(myLifecycle, () => setup(ctx));
|
|
17
|
+
*
|
|
18
|
+
* // in resource.ts
|
|
19
|
+
* const lc = getCurrentLifecycle();
|
|
20
|
+
* if (lc) lc.onDispose(() => abort.abort());
|
|
21
|
+
* else console.warn('[mado] resource() outside component — cleanup must be manual');
|
|
22
|
+
*/
|
|
23
|
+
let current = null;
|
|
24
|
+
/**
|
|
25
|
+
* Return the currently active lifecycle, or null if code runs
|
|
26
|
+
* outside a component setup.
|
|
27
|
+
*/
|
|
28
|
+
export function getCurrentLifecycle() {
|
|
29
|
+
return current;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Execute fn with the given lifecycle set. Supports nesting:
|
|
33
|
+
* the previous lifecycle is restored after fn returns (including exceptions).
|
|
34
|
+
*/
|
|
35
|
+
export function runInLifecycle(lc, fn) {
|
|
36
|
+
const prev = current;
|
|
37
|
+
current = lc;
|
|
38
|
+
try {
|
|
39
|
+
return fn();
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
current = prev;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function createLifecycle() {
|
|
46
|
+
const disposers = [];
|
|
47
|
+
return {
|
|
48
|
+
onDispose(fn) {
|
|
49
|
+
disposers.push(fn);
|
|
50
|
+
},
|
|
51
|
+
dispose() {
|
|
52
|
+
// reverse order — LIFO, like a stack
|
|
53
|
+
for (let i = disposers.length - 1; i >= 0; i--) {
|
|
54
|
+
try {
|
|
55
|
+
disposers[i]();
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
// eslint-disable-next-line no-console
|
|
59
|
+
console.error("[mado] cleanup threw:", err);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
disposers.length = 0;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AASH,IAAI,OAAO,GAAqB,IAAI,CAAC;AAErC;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAI,EAAa,EAAE,EAAW;IAC1D,MAAM,IAAI,GAAG,OAAO,CAAC;IACrB,OAAO,GAAG,EAAE,CAAC;IACb,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;AACH,CAAC;AAUD,MAAM,UAAU,eAAe;IAC7B,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,OAAO;QACL,SAAS,CAAC,EAAE;YACV,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC;QACD,OAAO;YACL,qCAAqC;YACrC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC/C,IAAI,CAAC;oBACH,SAAS,CAAC,CAAC,CAAE,EAAE,CAAC;gBAClB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,sCAAsC;oBACtC,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;YACD,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page contract — a uniform format for describing a page.
|
|
3
|
+
*
|
|
4
|
+
* // pages/user-profile.ts
|
|
5
|
+
* import { page, html, resource, jsonFetcher } from '@madojs/mado';
|
|
6
|
+
*
|
|
7
|
+
* export default page({
|
|
8
|
+
* title: ({ id }) => `User #${id}`,
|
|
9
|
+
* load: ({ id }) => resource(() => `/api/users/${id}`, jsonFetcher()),
|
|
10
|
+
* view: ({ params, data }) => html`
|
|
11
|
+
* <h1>${() => data?.data()?.name ?? '\u2026'}</h1>
|
|
12
|
+
* `,
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* // routes.ts
|
|
16
|
+
* import { routes } from '@madojs/mado';
|
|
17
|
+
*
|
|
18
|
+
* export default routes({
|
|
19
|
+
* '/users/:id': () => import('./pages/user-profile.js'),
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* The contract is strict: exactly three slots — title / load / view.
|
|
23
|
+
* If a page exports something other than Page — tsc will stop it before build.
|
|
24
|
+
* "One right way" — as in Rust: fewer branches, fewer bugs.
|
|
25
|
+
*/
|
|
26
|
+
import type { TemplateResult } from "./html.js";
|
|
27
|
+
/** Route params (values from :placeholders). */
|
|
28
|
+
export type RouteParams = Record<string, string>;
|
|
29
|
+
/**
|
|
30
|
+
* Loaded data. If load returned a Resource — this is `Resource<T>`,
|
|
31
|
+
* otherwise — a plain value. The view decides what to do with it.
|
|
32
|
+
*/
|
|
33
|
+
export type PageData<D> = D;
|
|
34
|
+
export interface PageContext<P extends RouteParams, D> {
|
|
35
|
+
/** URL parameters. */
|
|
36
|
+
params: P;
|
|
37
|
+
/** Result of load() — usually Resource<T> with data/error/loading. */
|
|
38
|
+
data: D;
|
|
39
|
+
/** Current path (as a signal function). */
|
|
40
|
+
path: () => string;
|
|
41
|
+
/**
|
|
42
|
+
* Child view (for layout components).
|
|
43
|
+
* For regular pages always null.
|
|
44
|
+
*/
|
|
45
|
+
child: TemplateResult | null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Metadata for <head>. Baked into HTML at bake(), and in SPA runtime
|
|
49
|
+
* updated on the fly on route changes.
|
|
50
|
+
*/
|
|
51
|
+
export interface HeadMeta {
|
|
52
|
+
/** If set — overrides page.title. */
|
|
53
|
+
title?: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
/** Canonical URL — important for SEO with duplicate content. */
|
|
56
|
+
canonical?: string;
|
|
57
|
+
/** OpenGraph for social networks. */
|
|
58
|
+
og?: {
|
|
59
|
+
title?: string;
|
|
60
|
+
description?: string;
|
|
61
|
+
image?: string;
|
|
62
|
+
type?: string;
|
|
63
|
+
url?: string;
|
|
64
|
+
};
|
|
65
|
+
/** Twitter Cards (inherits og.* if not set). */
|
|
66
|
+
twitter?: {
|
|
67
|
+
card?: "summary" | "summary_large_image";
|
|
68
|
+
title?: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
image?: string;
|
|
71
|
+
};
|
|
72
|
+
/** Arbitrary meta tags: { name: 'robots', content: 'index,follow' } */
|
|
73
|
+
meta?: Array<{
|
|
74
|
+
name?: string;
|
|
75
|
+
property?: string;
|
|
76
|
+
content: string;
|
|
77
|
+
}>;
|
|
78
|
+
/** Arbitrary link tags: { rel: 'alternate', href: '/en/...' } */
|
|
79
|
+
link?: Array<{
|
|
80
|
+
rel: string;
|
|
81
|
+
href: string;
|
|
82
|
+
hreflang?: string;
|
|
83
|
+
}>;
|
|
84
|
+
/** JSON-LD structure (Schema.org). Will be inserted as <script type="application/ld+json">. */
|
|
85
|
+
jsonLd?: unknown;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Bake configuration: gives the build script enough information
|
|
89
|
+
* to pre-render static HTML for all instances of a page.
|
|
90
|
+
*/
|
|
91
|
+
export interface BakeConfig<P extends RouteParams, D> {
|
|
92
|
+
/**
|
|
93
|
+
* List of all params for which to bake a page.
|
|
94
|
+
* Return a ready array (may fetch from API).
|
|
95
|
+
*/
|
|
96
|
+
paths: () => Promise<P[]> | P[];
|
|
97
|
+
/**
|
|
98
|
+
* Data for specific params. Must be JSON-serialisable —
|
|
99
|
+
* it is also embedded in the HTML inside
|
|
100
|
+
* `<script type="application/json" id="bake">`
|
|
101
|
+
* and used as `initialData` during hydration.
|
|
102
|
+
*/
|
|
103
|
+
data: (params: P) => Promise<D> | D;
|
|
104
|
+
/**
|
|
105
|
+
* How many seconds before the data is considered stale (for CDN/edge cache).
|
|
106
|
+
* Optional. Metadata only.
|
|
107
|
+
*/
|
|
108
|
+
revalidate?: number;
|
|
109
|
+
}
|
|
110
|
+
export interface Page<P extends RouteParams = RouteParams, D = unknown> {
|
|
111
|
+
readonly _page: true;
|
|
112
|
+
title?: string | ((params: P) => string);
|
|
113
|
+
load?: (params: P, baked?: D) => D;
|
|
114
|
+
view: (ctx: PageContext<P, D>) => TemplateResult;
|
|
115
|
+
/**
|
|
116
|
+
* <head> metadata. Receives params and (opt.) data if pre-loaded
|
|
117
|
+
* via bake.
|
|
118
|
+
*/
|
|
119
|
+
head?: (params: P, data?: D) => HeadMeta;
|
|
120
|
+
/**
|
|
121
|
+
* Static HTML bake config. Used only in the bake script.
|
|
122
|
+
* Ignored at runtime.
|
|
123
|
+
*/
|
|
124
|
+
bake?: BakeConfig<P, D>;
|
|
125
|
+
/**
|
|
126
|
+
* Local error boundary. Catches errors from view() and load() of this page.
|
|
127
|
+
* If not set — the global `error` from routes() is used.
|
|
128
|
+
*/
|
|
129
|
+
errorView?: (err: Error, params: P) => TemplateResult;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Page factory. A typed wrapper over a plain object —
|
|
133
|
+
* needed only for type inference and the single "_page" stamp.
|
|
134
|
+
*/
|
|
135
|
+
export declare function page<P extends RouteParams = RouteParams, D = unknown>(spec: Omit<Page<P, D>, "_page">): Page<P, D>;
|
|
136
|
+
export declare const isPage: (v: unknown) => v is Page;
|
|
137
|
+
/**
|
|
138
|
+
* Manifest entry — what should respond to a path.
|
|
139
|
+
* - Page → serve immediately, without import (for eager pages)
|
|
140
|
+
* - () => Promise<{default:Page}> → lazy via dynamic import
|
|
141
|
+
* - NestedRoutes → a group with a shared layout
|
|
142
|
+
*
|
|
143
|
+
* Using Page<any, any> so that user-defined page<{slug:string}>
|
|
144
|
+
* can be assigned here (TS disallows Page<{slug:string}> → Page<RouteParams>
|
|
145
|
+
* due to parameter contravariance).
|
|
146
|
+
*/
|
|
147
|
+
export type AnyPage = Page<any, any>;
|
|
148
|
+
export type RouteEntry = AnyPage | (() => Promise<{
|
|
149
|
+
default: AnyPage;
|
|
150
|
+
}>) | NestedRoutes;
|
|
151
|
+
export interface NestedRoutes {
|
|
152
|
+
readonly _nested: true;
|
|
153
|
+
/** Layout page wrapping children. Will receive child=TemplateResult. */
|
|
154
|
+
layout?: AnyPage | (() => Promise<{
|
|
155
|
+
default: AnyPage;
|
|
156
|
+
}>);
|
|
157
|
+
/** Sub-routes. Keys are relative ("" , "users", "users/:id"). */
|
|
158
|
+
routes: Record<string, RouteEntry>;
|
|
159
|
+
}
|
|
160
|
+
export declare function nested(spec: Omit<NestedRoutes, "_nested">): NestedRoutes;
|
|
161
|
+
export declare const isNested: (v: unknown) => v is NestedRoutes;
|