@askrjs/askr 0.2.3 → 0.3.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/CHANGELOG.md +11 -0
- package/dist/access-CEA2pOry.js +1046 -0
- package/dist/actions/index.d.ts +16 -2
- package/dist/actions/index.js +5 -5
- package/dist/{activity-ZxJXI9hF.js → activity-oy2YFTFF.js} +22 -16
- package/dist/benchmark.js +8 -9
- package/dist/boot/index.d.ts +21 -9
- package/dist/boot/index.js +1 -1
- package/dist/{boot-D4GaLA0d.js → boot-BMBMWUGI.js} +680 -440
- package/dist/capabilities-Bbf0mlp9.js +42 -0
- package/dist/components/index.d.ts +11 -8
- package/dist/components/index.js +1 -1
- package/dist/{compose-ref-Bfaf4kUC.js → compose-ref-v0qGlHoN.js} +7 -1
- package/dist/control/index.d.ts +20 -2
- package/dist/control/index.js +1 -1
- package/dist/{control-57x3wa4J.js → control-BR_mrX__.js} +4 -4
- package/dist/core.d.ts +163 -0
- package/dist/csp-nonce-BfFWcd0w.js +173 -0
- package/dist/data/index.d.ts +74 -2
- package/dist/data/index.js +3 -3
- package/dist/{data-B7XCDeC8.js → data-2edW0jhF.js} +133 -138
- package/dist/{data-runtime-7t_Y0iv3.js → data-runtime-CxoY98lX.js} +22 -15
- package/dist/{deferred-BELyvMsC.js → deferred-CbgNhf67.js} +5 -5
- package/dist/dom-renderer.d.ts +77 -0
- package/dist/domains/actions.d.ts +70 -0
- package/dist/domains/component.d.ts +119 -0
- package/dist/domains/context.d.ts +73 -0
- package/dist/domains/control.d.ts +328 -0
- package/dist/domains/data.d.ts +461 -0
- package/dist/domains/diagnostics.d.ts +21 -0
- package/dist/domains/lifecycle.d.ts +112 -0
- package/dist/domains/refs.d.ts +18 -0
- package/dist/domains/renderer.d.ts +127 -0
- package/dist/domains/routing.d.ts +501 -0
- package/dist/domains/scheduler.d.ts +79 -0
- package/dist/domains/server.d.ts +112 -0
- package/dist/domains/state.d.ts +129 -0
- package/dist/domains/telemetry.d.ts +29 -0
- package/dist/eager-control.d.ts +8 -0
- package/dist/{effect-4LwBwV58.js → effect-C3Zsc1k6.js} +14 -41
- package/dist/{types-DkVJhVs9.d.ts → elements.d.ts} +84 -42
- package/dist/{for-internal-DeoSg_AV.js → for-state-BDykeOJL.js} +623 -511
- package/dist/foundations/icon/index.d.ts +67 -16
- package/dist/foundations/icon/index.js +1 -1
- package/dist/foundations/index.d.ts +26 -3
- package/dist/foundations/index.js +1 -1
- package/dist/foundations/interactions/index.d.ts +73 -34
- package/dist/foundations/interactions/index.js +1 -1
- package/dist/foundations/state/index.d.ts +12 -5
- package/dist/foundations/state/index.js +1 -1
- package/dist/foundations/structures/index.d.ts +44 -3
- package/dist/foundations/structures/index.js +1 -1
- package/dist/foundations/utilities/index.d.ts +34 -3
- package/dist/foundations/utilities/index.js +1 -1
- package/dist/fx/index.d.ts +72 -25
- package/dist/fx/index.js +8 -8
- package/dist/index.d.ts +121 -4
- package/dist/index.js +316 -47
- package/dist/{component-internal-Dl02WQKS.js → instance-VAFYVJOa.js} +495 -283
- package/dist/{interactions-D102JxBR.js → interactions-CTeACdT_.js} +1 -1
- package/dist/jsx-dev-runtime.d.ts +51 -11
- package/dist/jsx-globals.d.ts +2 -0
- package/dist/jsx-runtime-CPJXAOBF.js +43 -0
- package/dist/jsx-runtime.d.ts +17 -3
- package/dist/jsx-runtime.js +2 -42
- package/dist/jsx.d.ts +108 -0
- package/dist/{manifest-D6rfj_jC.js → manifest-D1P3UNWV.js} +4 -9
- package/dist/{navigate-C46GtScG.js → navigate-prC5V8XV.js} +204 -476
- package/dist/{navigate-wel1e6cU.d.ts → navigation.d.ts} +10 -8
- package/dist/{readable-CEcteIDE.js → notify-DxxuDPeY.js} +96 -34
- package/dist/{query-registry-Bua3cDyb.js → query-registry-LrYpQsDJ.js} +1 -1
- package/dist/{compose-ref-DOJHoW5X.d.ts → refs.d.ts} +11 -7
- package/dist/{ssr-BaC5qHEw.js → render-resolved-BC0W7_5b.js} +165 -558
- package/dist/{renderer-BhXofszH.js → renderer-CT0HeY58.js} +6866 -5715
- package/dist/{resolution-t8J0yNaU.js → resolution-3DskUkjG.js} +189 -180
- package/dist/{resource-operation-B8q-QRUx.js → resource-18-sFRl9.js} +98 -58
- package/dist/resources/index.d.ts +64 -46
- package/dist/resources/index.js +215 -9
- package/dist/route-activity.d.ts +21 -0
- package/dist/{route-matching-Bo6UqG-5.js → route-matching-CUzZ31gT.js} +68 -82
- package/dist/router/index.d.ts +378 -79
- package/dist/router/index.js +9 -9
- package/dist/router-internal-7nuNEHfG.js +36 -0
- package/dist/scope-Cq_iYbTR.js +403 -0
- package/dist/{selector-C7rIpOnN.js → selector-BSPVkjKP.js} +41 -41
- package/dist/{shared-CGrx2AOL.js → shared-kU8yvryp.js} +3 -6
- package/dist/{snapshot-source-BB42xgwb.js → snapshot-source-An4iKajG.js} +1 -1
- package/dist/ssg/index.d.ts +51 -15
- package/dist/ssg/index.js +269 -209
- package/dist/ssr/index.d.ts +121 -68
- package/dist/ssr/index.js +2 -1
- package/dist/ssr-CiNm6Vzl.js +393 -0
- package/dist/{ssr-DSHd_Q9K.js → ssr-EPjwJH5R.js} +5 -1
- package/dist/{state-BzhOW2TX.js → state-BXJfudq1.js} +10 -15
- package/dist/{structures-BwfeO4Iz.js → structures-CjEzfj4h.js} +37 -57
- package/dist/{index-r5THqzPN.d.ts → structures.d.ts} +57 -36
- package/dist/testing/index.d.ts +125 -24
- package/dist/testing/index.js +26 -62
- package/dist/timer-C7uqnJVt.js +85 -0
- package/dist/{index-B1gu0_m0.d.ts → utilities.d.ts} +24 -13
- package/dist/{verify-hydration-_Q6avFp0.js → verify-hydration-ikaxotf7.js} +8 -5
- package/package.json +13 -15
- package/dist/access-Rr7CMa06.js +0 -507
- package/dist/activity-CAJ5eho-.d.ts +0 -14
- package/dist/benchmark.d.ts +0 -26
- package/dist/cleanup-DsrbV-rr.js +0 -535
- package/dist/component-cleanup-DjpwO9WX.js +0 -143
- package/dist/component-scope-4I3yEMkb.js +0 -195
- package/dist/control-DdFw-Caz.d.ts +0 -8
- package/dist/csp-nonce-C9OuML0U.js +0 -72
- package/dist/css-BuPaTOfe.js +0 -70
- package/dist/fastlane-Buz1Rvlr.js +0 -166
- package/dist/index-DSqXWc77.d.ts +0 -1402
- package/dist/jsx-runtime-BTzDdBJ_.d.ts +0 -2
- package/dist/jsx-runtime-Cdd6Gkb0.d.ts +0 -38
- package/dist/lifecycle-batch-Be-CLDjV.js +0 -253
- package/dist/lifecycle-operations-C5m03NQc.js +0 -195
|
@@ -1,177 +1,317 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { T as isProductionEnvironment, i as getRuntimeCleanup, l as installRuntimeRenderer, r as flushRuntimeScheduler, v as logger, w as isDevelopmentEnvironment } from "./access-CEA2pOry.js";
|
|
2
|
+
import { D as createComponentOwnership, E as cleanupComponentGeneration, F as captureGenerationExecution, N as assertSyncComponentResult, T as cleanupComponent, k as resetComponentWork, n as createComponentInstance, r as executeComponent } from "./instance-VAFYVJOa.js";
|
|
3
|
+
import { a as registerDeferredHydrationBoundary, c as beginHydrationDirectListenerMode, d as commitHydrationListenerTransaction, f as discardHydrationListenerTransaction, g as teardownNodeSubtree, h as removeAllListeners, i as clearDeferredHydrationBoundaries, l as endHydrationDirectListenerMode, n as activateHydrationBoundary$1, o as setStaticChildSlotsCacheEnabled, s as withIntrinsicHydrationAdoption, t as createRendererCapabilities, u as beginHydrationListenerTransaction } from "./renderer-CT0HeY58.js";
|
|
4
|
+
import { b as attachOwnership } from "./scope-Cq_iYbTR.js";
|
|
5
|
+
import { m as cleanupReadableSubscriptionSources } from "./notify-DxxuDPeY.js";
|
|
2
6
|
import { n as Fragment, t as ELEMENT_TYPE } from "./jsx-DRS-qjiN.js";
|
|
3
|
-
import {
|
|
4
|
-
import { t as cleanupComponent } from "./component-cleanup-DjpwO9WX.js";
|
|
5
|
-
import { p as isPromiseLike } from "./lifecycle-batch-Be-CLDjV.js";
|
|
6
|
-
import { _ as teardownNodeSubtree, d as removeAllListeners } from "./cleanup-DsrbV-rr.js";
|
|
7
|
-
import { r as assertExecutionModel, t as _applyManifest } from "./manifest-D6rfj_jC.js";
|
|
7
|
+
import { r as assertExecutionModel, t as _applyManifest } from "./manifest-D1P3UNWV.js";
|
|
8
8
|
import { d as stopHydrationRenderPhase, l as startHydrationRenderPhase, y as pageRenderEnvelope } from "./render-context-B7ohlh6t.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { a as
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (payload[DEFERRED_PAYLOAD] === "rejected") return reviveDeferredValue("rejected", void 0, payload.error);
|
|
37
|
-
return value;
|
|
38
|
-
}
|
|
39
|
-
function takeHydrationRenderData(rootElement) {
|
|
40
|
-
for (const child of Array.from(rootElement.children)) if (child instanceof HTMLScriptElement && child.getAttribute("data-askr-render-data") === "true") {
|
|
41
|
-
const raw = child.textContent ?? "";
|
|
42
|
-
child.remove();
|
|
43
|
-
if (!raw) return pageRenderEnvelope(void 0);
|
|
44
|
-
try {
|
|
45
|
-
return pageRenderEnvelope(JSON.parse(raw, hydrationReviver));
|
|
46
|
-
} catch (err) {
|
|
47
|
-
const error = /* @__PURE__ */ new Error("[Askr] Failed to parse embedded SSR render data during hydration.");
|
|
48
|
-
error.cause = err;
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
9
|
+
import { r as validateCspNonce, t as CspNonceScope } from "./csp-nonce-BfFWcd0w.js";
|
|
10
|
+
import { i as getDefaultDataRuntime } from "./data-runtime-CxoY98lX.js";
|
|
11
|
+
import { a as hydrateDataRuntime } from "./query-registry-LrYpQsDJ.js";
|
|
12
|
+
import { _ as configureRootUpdateHost, f as registerAppInstance, g as unregisterAppInstance, i as resolveRouteMeta, o as configureScrollRestoration, r as reconcileRouteMeta, t as initializeNavigation } from "./navigate-prC5V8XV.js";
|
|
13
|
+
import { n as disposeRegisteredDefaultPortalScope, r as getDefaultPortalHost, t as clearRegisteredDefaultPortalForInstance } from "./default-portal-runtime-DvMvWJVp.js";
|
|
14
|
+
import { a as clearStagedAppRenderRouteLocation, o as createAppRenderRuntime, r as setServerLocation, s as stageAppRenderRouteLocation } from "./activity-oy2YFTFF.js";
|
|
15
|
+
import { C as reviveDeferredValue, a as resolveRouteRequest, i as hasRouteRenderData, n as getRouteRenderContext, r as getRouteRenderData, y as withoutRouteHydrationMetadata } from "./resolution-3DskUkjG.js";
|
|
16
|
+
import { F as hasRegisteredRoutes, L as lockRouteRegistration, _ as clearRouteState, m as _setActiveRouteAuthOptions } from "./route-matching-CUzZ31gT.js";
|
|
17
|
+
import "./ssr-EPjwJH5R.js";
|
|
18
|
+
//#region src/runtime/component/generation.ts
|
|
19
|
+
/** Reuse an execution record after boot has retired its previous lifetime. */
|
|
20
|
+
function restartComponentGeneration(instance, fn, preserveState) {
|
|
21
|
+
instance.owner = createComponentOwnership(instance);
|
|
22
|
+
instance.fn = fn;
|
|
23
|
+
instance.evaluationGeneration++;
|
|
24
|
+
instance.expectedStateIndices = [];
|
|
25
|
+
instance.firstRenderComplete = false;
|
|
26
|
+
if (!preserveState) {
|
|
27
|
+
resetComponentWork(instance);
|
|
28
|
+
instance.stateValues = [];
|
|
29
|
+
instance.stateIndexCheck = -1;
|
|
30
|
+
instance._currentRenderToken = void 0;
|
|
31
|
+
instance.lastRenderToken = 0;
|
|
32
|
+
instance.errorBoundaryState = void 0;
|
|
33
|
+
instance._portalErrorParent = void 0;
|
|
34
|
+
instance._portalErrorParentGeneration = void 0;
|
|
35
|
+
instance.devWarningsEmitted = void 0;
|
|
51
36
|
}
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
function markSkippedElements(root, skipSelectors) {
|
|
55
|
-
if (skipSelectors.length === 0) return;
|
|
56
|
-
const selectorList = Array.from(new Set(skipSelectors)).join(", ");
|
|
57
|
-
root.querySelectorAll(selectorList).forEach((el) => el.setAttribute("data-skip-hydrate", "true"));
|
|
58
37
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
38
|
+
/** Runtime-owned preparation and retirement. A host may restore its own state
|
|
39
|
+
* between disposing provisional work and restoring the previous execution. */
|
|
40
|
+
function captureComponentGeneration(instance) {
|
|
41
|
+
const restoreHostIndex = getRuntimeCleanup().captureComponentHost(instance);
|
|
42
|
+
const snapshot = captureGenerationExecution(instance);
|
|
43
|
+
const reads = instance.owner.reads;
|
|
44
|
+
const readerEntries = Array.from(reads ?? [], (source) => ({
|
|
45
|
+
source,
|
|
46
|
+
reader: source._readers?.get(instance)
|
|
47
|
+
}));
|
|
48
|
+
let settled = false;
|
|
49
|
+
return {
|
|
50
|
+
prepare(fn, props) {
|
|
51
|
+
if (settled || instance.owner !== snapshot.owner) throw new Error("[Askr] component generation is no longer available for preparation");
|
|
52
|
+
const owner = createComponentOwnership(instance);
|
|
53
|
+
owner.mounted = snapshot.owner.mounted;
|
|
54
|
+
instance.owner = owner;
|
|
55
|
+
attachOwnership(owner, snapshot.owner.parent);
|
|
56
|
+
instance.fn = fn;
|
|
57
|
+
instance.props = props;
|
|
58
|
+
resetComponentWork(instance);
|
|
59
|
+
instance.stateValues = [];
|
|
60
|
+
instance.expectedStateIndices = [];
|
|
61
|
+
instance.firstRenderComplete = false;
|
|
62
|
+
instance.stateIndexCheck = -1;
|
|
63
|
+
instance.evaluationGeneration++;
|
|
64
|
+
instance.lifecycleGeneration++;
|
|
65
|
+
instance.mountOperations = [];
|
|
66
|
+
instance.commitOperations = [];
|
|
67
|
+
instance.lifecycleSlots = [];
|
|
68
|
+
instance._currentRenderToken = void 0;
|
|
69
|
+
instance.lastRenderToken = 0;
|
|
70
|
+
},
|
|
71
|
+
rollback(restoreHost) {
|
|
72
|
+
if (settled) return [];
|
|
73
|
+
settled = true;
|
|
74
|
+
const errors = [];
|
|
75
|
+
const provisional = instance.owner;
|
|
76
|
+
if (provisional !== snapshot.owner) {
|
|
77
|
+
try {
|
|
78
|
+
cleanupReadableSubscriptionSources(instance, provisional.reads, provisional.identity);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
errors.push(error);
|
|
81
|
+
}
|
|
82
|
+
provisional.reads = void 0;
|
|
83
|
+
try {
|
|
84
|
+
cleanupComponent(instance);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
errors.push(error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
errors.push(...restoreHost());
|
|
91
|
+
} catch (error) {
|
|
92
|
+
errors.push(error);
|
|
93
|
+
}
|
|
94
|
+
Object.assign(instance, snapshot);
|
|
95
|
+
try {
|
|
96
|
+
restoreHostIndex?.();
|
|
97
|
+
} catch (error) {
|
|
98
|
+
errors.push(error);
|
|
99
|
+
}
|
|
100
|
+
snapshot.owner.reads = reads;
|
|
101
|
+
for (const { source, reader } of readerEntries) {
|
|
102
|
+
if (!reader) {
|
|
103
|
+
if (source._readers?.delete(instance) && false);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const readers = source._readers ??= /* @__PURE__ */ new Map();
|
|
107
|
+
readers.has(instance);
|
|
108
|
+
readers.set(instance, reader);
|
|
109
|
+
}
|
|
110
|
+
return errors;
|
|
111
|
+
},
|
|
112
|
+
retire() {
|
|
113
|
+
if (settled) return;
|
|
114
|
+
settled = true;
|
|
115
|
+
cleanupComponentGeneration(instance, snapshot.owner);
|
|
71
116
|
}
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
return boundaries;
|
|
117
|
+
};
|
|
75
118
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/renderer/ownership/root-snapshot.ts
|
|
121
|
+
function captureRootTree(root) {
|
|
122
|
+
if (!root) return null;
|
|
123
|
+
const nodes = [];
|
|
124
|
+
const visit = (node) => {
|
|
125
|
+
nodes.push({
|
|
126
|
+
node,
|
|
127
|
+
children: Array.from(node.childNodes),
|
|
128
|
+
attributes: node instanceof Element ? Array.from(node.attributes).map((attribute) => [attribute.name, attribute.value]) : null,
|
|
129
|
+
nodeValue: node.nodeValue
|
|
130
|
+
});
|
|
131
|
+
for (const child of Array.from(node.childNodes)) visit(child);
|
|
132
|
+
};
|
|
133
|
+
visit(root);
|
|
86
134
|
return {
|
|
87
|
-
|
|
88
|
-
|
|
135
|
+
root,
|
|
136
|
+
nodes
|
|
89
137
|
};
|
|
90
138
|
}
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}, { timeout: 2e3 });
|
|
139
|
+
function collectProvisionalRootNodes(snapshot) {
|
|
140
|
+
const originalNodes = new Set(snapshot.nodes.map((entry) => entry.node));
|
|
141
|
+
const provisional = [];
|
|
142
|
+
const visit = (node) => {
|
|
143
|
+
if (!originalNodes.has(node)) {
|
|
144
|
+
provisional.push(node);
|
|
98
145
|
return;
|
|
99
146
|
}
|
|
100
|
-
|
|
101
|
-
work();
|
|
102
|
-
resolve();
|
|
103
|
-
}, 0);
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
function shouldVerifyHydrationMarkup(config) {
|
|
107
|
-
const explicit = config.hydrate?.verifyMarkup;
|
|
108
|
-
if (typeof explicit === "boolean") return explicit;
|
|
109
|
-
return !isProductionEnvironment();
|
|
110
|
-
}
|
|
111
|
-
async function applySelectiveHydration(rootElement, resolved, path, cleanupStrict, hydrateOptions, source, hooks, interactionReplay) {
|
|
112
|
-
const hasBelowFoldDeferral = !!hydrateOptions.deferBelowFold;
|
|
113
|
-
let staticChildSlotsCacheSuspended = false;
|
|
114
|
-
let releaseSelectiveHydrationResources = () => {};
|
|
115
|
-
const restoreStaticChildSlotsCache = () => {
|
|
116
|
-
if (!staticChildSlotsCacheSuspended) return;
|
|
117
|
-
setStaticChildSlotsCacheEnabled(true);
|
|
118
|
-
staticChildSlotsCacheSuspended = false;
|
|
147
|
+
for (const child of Array.from(node.childNodes)) visit(child);
|
|
119
148
|
};
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
let selectiveHydrationResourcesReleased = false;
|
|
129
|
-
let unregisterRootCleanupCallback = () => {};
|
|
130
|
-
function handleScroll() {
|
|
131
|
-
const { activated, remaining } = activateVisibleDeferredBoundaries(rootElement, deferredBoundaries, foldY, hooks.activateHydrationBoundary);
|
|
132
|
-
if (!activated) return;
|
|
133
|
-
if (remaining === 0) releaseSelectiveHydrationResources();
|
|
134
|
-
}
|
|
135
|
-
releaseSelectiveHydrationResources = () => {
|
|
136
|
-
if (selectiveHydrationResourcesReleased) return;
|
|
137
|
-
selectiveHydrationResourcesReleased = true;
|
|
138
|
-
unregisterRootCleanupCallback();
|
|
139
|
-
window.removeEventListener("scroll", handleScroll);
|
|
140
|
-
restoreStaticChildSlotsCache();
|
|
141
|
-
interactionReplay.clearDeferredBoundaries();
|
|
142
|
-
};
|
|
143
|
-
interactionReplay.setOnDeferredBoundariesDrained(releaseSelectiveHydrationResources);
|
|
144
|
-
unregisterRootCleanupCallback = hooks.registerRootCleanupCallback(rootElement, releaseSelectiveHydrationResources);
|
|
145
|
-
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
146
|
-
}
|
|
147
|
-
if (hydrateOptions.deferUntilIdle && !hasBelowFoldDeferral) {
|
|
148
|
-
await queueIdleWork(() => {
|
|
149
|
-
hooks.mountOrUpdate(rootElement, (() => resolved.handler(resolved.params)), {
|
|
150
|
-
cleanupStrict,
|
|
151
|
-
appRuntime: source?.runtime
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
await hooks.registerAppNavigation(rootElement, path, source);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
try {
|
|
158
|
-
hooks.mountOrUpdate(rootElement, (() => resolved.handler(resolved.params)), {
|
|
159
|
-
cleanupStrict,
|
|
160
|
-
appRuntime: source?.runtime
|
|
161
|
-
});
|
|
162
|
-
await hooks.registerAppNavigation(rootElement, path, source);
|
|
149
|
+
for (const child of Array.from(snapshot.root.childNodes)) visit(child);
|
|
150
|
+
return provisional;
|
|
151
|
+
}
|
|
152
|
+
function restoreRootTree(snapshot) {
|
|
153
|
+
if (!snapshot) return [];
|
|
154
|
+
const errors = [];
|
|
155
|
+
for (const node of collectProvisionalRootNodes(snapshot)) try {
|
|
156
|
+
teardownNodeSubtree(node, { strict: true });
|
|
163
157
|
} catch (error) {
|
|
164
|
-
|
|
165
|
-
throw error;
|
|
158
|
+
errors.push(error);
|
|
166
159
|
}
|
|
167
|
-
|
|
160
|
+
for (const entry of snapshot.nodes) {
|
|
161
|
+
const { node, attributes } = entry;
|
|
168
162
|
try {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
if (attributes && node instanceof Element) {
|
|
164
|
+
const expected = new Map(attributes);
|
|
165
|
+
for (const attribute of Array.from(node.attributes)) if (!expected.has(attribute.name)) node.removeAttribute(attribute.name);
|
|
166
|
+
for (const [name, value] of attributes) if (node.getAttribute(name) !== value) node.setAttribute(name, value);
|
|
167
|
+
} else if (!(node instanceof Element)) node.nodeValue = entry.nodeValue;
|
|
168
|
+
if (node instanceof Element || node instanceof DocumentFragment) node.replaceChildren(...entry.children);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
errors.push(error);
|
|
172
171
|
}
|
|
172
|
+
}
|
|
173
|
+
return errors;
|
|
174
|
+
}
|
|
175
|
+
function captureRootHost(root) {
|
|
176
|
+
const snapshot = captureRootTree(root);
|
|
177
|
+
return { restore: () => restoreRootTree(snapshot) };
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/boot/root-handler.ts
|
|
181
|
+
function wrapRootRouteHandler(componentFn, cspNonce) {
|
|
182
|
+
const wrappedFn = (props, ctx) => {
|
|
183
|
+
const out = componentFn(props, ctx);
|
|
184
|
+
assertSyncComponentResult(out);
|
|
185
|
+
const portalVNode = {
|
|
186
|
+
$$typeof: ELEMENT_TYPE,
|
|
187
|
+
type: getDefaultPortalHost(),
|
|
188
|
+
props: { __askrAutoDefaultPortal: true },
|
|
189
|
+
key: "__default_portal"
|
|
190
|
+
};
|
|
191
|
+
const root = {
|
|
192
|
+
$$typeof: ELEMENT_TYPE,
|
|
193
|
+
type: Fragment,
|
|
194
|
+
props: { children: out === void 0 || out === null ? [portalVNode] : [out, portalVNode] }
|
|
195
|
+
};
|
|
196
|
+
return cspNonce === void 0 ? root : CspNonceScope({
|
|
197
|
+
value: cspNonce,
|
|
198
|
+
children: root
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
Object.defineProperty(wrappedFn, "name", { value: componentFn.name || "Component" });
|
|
202
|
+
return wrappedFn;
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/boot/root-update.ts
|
|
206
|
+
function prepare(root, input) {
|
|
207
|
+
const instance = root;
|
|
208
|
+
const generation = captureComponentGeneration(instance);
|
|
209
|
+
const host = captureRootHost(instance.target);
|
|
210
|
+
const current = instance._appRenderRuntime;
|
|
211
|
+
const runtime = createAppRenderRuntime({
|
|
212
|
+
framework: withoutRouteHydrationMetadata(current?.framework),
|
|
213
|
+
dataRuntime: current?.dataRuntime,
|
|
214
|
+
routeRegistry: current?.routeRegistry,
|
|
215
|
+
routeAuth: current?.routeAuth,
|
|
216
|
+
route: input.routeData,
|
|
217
|
+
hasRoute: input.hasRouteData
|
|
173
218
|
});
|
|
174
|
-
|
|
219
|
+
let applied = false;
|
|
220
|
+
let settled = false;
|
|
221
|
+
return {
|
|
222
|
+
apply() {
|
|
223
|
+
if (applied) return;
|
|
224
|
+
applied = true;
|
|
225
|
+
stageAppRenderRouteLocation(runtime, input.href);
|
|
226
|
+
instance._appRenderRuntime = runtime;
|
|
227
|
+
if (input.replaceLifetime) {
|
|
228
|
+
clearRegisteredDefaultPortalForInstance(instance);
|
|
229
|
+
generation.prepare(wrapRootRouteHandler(input.handler, instance._cspNonce), {});
|
|
230
|
+
executeComponent(instance);
|
|
231
|
+
} else instance._enqueueRun?.();
|
|
232
|
+
},
|
|
233
|
+
publish() {
|
|
234
|
+
clearStagedAppRenderRouteLocation(runtime);
|
|
235
|
+
},
|
|
236
|
+
rollback() {
|
|
237
|
+
if (settled) return [];
|
|
238
|
+
settled = true;
|
|
239
|
+
clearStagedAppRenderRouteLocation(runtime);
|
|
240
|
+
return applied ? generation.rollback(() => host.restore()) : [];
|
|
241
|
+
},
|
|
242
|
+
retire() {
|
|
243
|
+
if (settled) return [];
|
|
244
|
+
settled = true;
|
|
245
|
+
const errors = [];
|
|
246
|
+
if (applied && input.replaceLifetime) try {
|
|
247
|
+
generation.retire();
|
|
248
|
+
} catch (error) {
|
|
249
|
+
errors.push(error);
|
|
250
|
+
}
|
|
251
|
+
return errors;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function installRootUpdateHost() {
|
|
256
|
+
configureRootUpdateHost({ prepare });
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/boot/runtime-wiring.ts
|
|
260
|
+
let bridged = false;
|
|
261
|
+
/**
|
|
262
|
+
* Browser composition owns the connection between execution and rendering.
|
|
263
|
+
*
|
|
264
|
+
* Installs unconditionally: importing the boot entry means native boot owns the
|
|
265
|
+
* renderer, replacing whatever a consumer had configured before that point.
|
|
266
|
+
*/
|
|
267
|
+
function installRendererBridge() {
|
|
268
|
+
bridged = true;
|
|
269
|
+
installRuntimeRenderer(createRendererCapabilities());
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Connect execution to the DOM renderer only if nothing has yet.
|
|
274
|
+
*
|
|
275
|
+
* Unlike the install above this leaves an already-installed host alone, so a
|
|
276
|
+
* renderer a consumer configured after composing boot survives. Kept in this
|
|
277
|
+
* module rather than the composition root so a caller that only needs
|
|
278
|
+
* rendering — driving the runtime directly, as the jsdom test environment does
|
|
279
|
+
* — does not pull root-lifetime wiring into its module graph.
|
|
280
|
+
*/
|
|
281
|
+
function ensureRendererBridge() {
|
|
282
|
+
if (bridged) return;
|
|
283
|
+
installRendererBridge();
|
|
284
|
+
}
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/boot/composition.ts
|
|
287
|
+
let rootUpdateHosted = false;
|
|
288
|
+
function ensureRootUpdateHost() {
|
|
289
|
+
if (rootUpdateHosted) return;
|
|
290
|
+
rootUpdateHosted = true;
|
|
291
|
+
installRootUpdateHost();
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Browser composition root, owned by the boot entry.
|
|
295
|
+
*
|
|
296
|
+
* Wiring a host is composition, not an import side effect of an implementation
|
|
297
|
+
* module: when these calls lived at the top of root-lifecycle.ts, the import
|
|
298
|
+
* order of unrelated modules decided whether a host existed. An entry barrel is
|
|
299
|
+
* where composition belongs, so importing `@askrjs/askr/boot` installs the
|
|
300
|
+
* native renderer — that is the contract consumers rely on.
|
|
301
|
+
*/
|
|
302
|
+
function composeBrowserRuntime() {
|
|
303
|
+
installRendererBridge();
|
|
304
|
+
ensureRootUpdateHost();
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Compose whatever is still missing, for a mount reached without the boot entry.
|
|
308
|
+
*
|
|
309
|
+
* Non-clobbering: a renderer the consumer configured after composing boot stays
|
|
310
|
+
* installed across mounts.
|
|
311
|
+
*/
|
|
312
|
+
function ensureBrowserRuntime() {
|
|
313
|
+
ensureRendererBridge();
|
|
314
|
+
ensureRootUpdateHost();
|
|
175
315
|
}
|
|
176
316
|
//#endregion
|
|
177
317
|
//#region src/boot/root-element.ts
|
|
@@ -182,18 +322,13 @@ function resolveRootElement(root) {
|
|
|
182
322
|
}
|
|
183
323
|
//#endregion
|
|
184
324
|
//#region src/boot/root-lifecycle.ts
|
|
185
|
-
installRendererBridge();
|
|
186
325
|
let componentIdCounter = 0;
|
|
187
326
|
const instancesByRoot = /* @__PURE__ */ new WeakMap();
|
|
188
327
|
const routedRoots = /* @__PURE__ */ new Set();
|
|
189
328
|
const CLEANUP_SYMBOL = Symbol.for("__askrCleanup__");
|
|
190
329
|
const ROOT_CLEANUP_CALLBACKS_SYMBOL = Symbol.for("__askrRootCleanupCallbacks__");
|
|
191
330
|
function clearRootCleanupCallbacks(rootElement) {
|
|
192
|
-
|
|
193
|
-
delete rootElement[ROOT_CLEANUP_CALLBACKS_SYMBOL];
|
|
194
|
-
} catch {
|
|
195
|
-
rootElement[ROOT_CLEANUP_CALLBACKS_SYMBOL]?.clear();
|
|
196
|
-
}
|
|
331
|
+
if (!Reflect.deleteProperty(rootElement, ROOT_CLEANUP_CALLBACKS_SYMBOL)) rootElement[ROOT_CLEANUP_CALLBACKS_SYMBOL]?.clear();
|
|
197
332
|
}
|
|
198
333
|
function registerRootCleanupCallback(rootElement, callback) {
|
|
199
334
|
const elementWithCleanup = rootElement;
|
|
@@ -202,44 +337,31 @@ function registerRootCleanupCallback(rootElement, callback) {
|
|
|
202
337
|
elementWithCleanup[ROOT_CLEANUP_CALLBACKS_SYMBOL] = callbacks;
|
|
203
338
|
return () => {
|
|
204
339
|
callbacks.delete(callback);
|
|
205
|
-
if (callbacks.size === 0) clearRootCleanupCallbacks(rootElement);
|
|
340
|
+
if (callbacks.size === 0 && elementWithCleanup[ROOT_CLEANUP_CALLBACKS_SYMBOL] === callbacks) clearRootCleanupCallbacks(rootElement);
|
|
206
341
|
};
|
|
207
342
|
}
|
|
208
|
-
function runRootCleanupCallbacks(rootElement, errors) {
|
|
209
|
-
const callbacks = rootElement[ROOT_CLEANUP_CALLBACKS_SYMBOL];
|
|
210
|
-
if (!callbacks || callbacks.size === 0) {
|
|
211
|
-
clearRootCleanupCallbacks(rootElement);
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
clearRootCleanupCallbacks(rootElement);
|
|
215
|
-
for (const callback of callbacks) try {
|
|
216
|
-
callback();
|
|
217
|
-
} catch (e) {
|
|
218
|
-
errors.push(e);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
343
|
function cleanupRootInstance(rootElement, instance, options) {
|
|
344
|
+
if (instancesByRoot.get(rootElement) !== instance) return;
|
|
345
|
+
const element = rootElement;
|
|
346
|
+
const cleanup = element[CLEANUP_SYMBOL];
|
|
347
|
+
const callbacks = element[ROOT_CLEANUP_CALLBACKS_SYMBOL];
|
|
348
|
+
const wasRoutedRoot = routedRoots.delete(rootElement);
|
|
349
|
+
instancesByRoot.delete(rootElement);
|
|
350
|
+
clearRootCleanupCallbacks(rootElement);
|
|
351
|
+
Reflect.deleteProperty(element, CLEANUP_SYMBOL);
|
|
352
|
+
clearDeferredHydrationBoundaries(rootElement);
|
|
222
353
|
const errors = [];
|
|
223
|
-
try {
|
|
224
|
-
|
|
225
|
-
} catch (e) {
|
|
226
|
-
errors.push(e);
|
|
227
|
-
}
|
|
228
|
-
try {
|
|
229
|
-
cleanupComponent(instance);
|
|
354
|
+
for (const phase of [[() => teardownNodeSubtree(rootElement), () => cleanupComponent(instance)], callbacks ?? []]) for (const callback of phase) try {
|
|
355
|
+
callback();
|
|
230
356
|
} catch (e) {
|
|
231
357
|
errors.push(e);
|
|
232
358
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
delete rootElement[CLEANUP_SYMBOL];
|
|
241
|
-
} catch {}
|
|
242
|
-
}
|
|
359
|
+
if (options?.preserveInstance && !instancesByRoot.has(rootElement)) {
|
|
360
|
+
instancesByRoot.set(rootElement, instance);
|
|
361
|
+
element[CLEANUP_SYMBOL] = cleanup;
|
|
362
|
+
if (wasRoutedRoot) routedRoots.add(rootElement);
|
|
363
|
+
} else unregisterAppInstance(instance);
|
|
364
|
+
if (wasRoutedRoot && routedRoots.size === 0) clearRouteState();
|
|
243
365
|
if (errors.length > 0) {
|
|
244
366
|
if (instance.cleanupStrict) throw new AggregateError(errors, `cleanup failed for app root`);
|
|
245
367
|
else if (isDevelopmentEnvironment()) for (const err of errors) logger.warn("[Askr] cleanup error:", err);
|
|
@@ -249,45 +371,36 @@ function attachCleanupForRoot(rootElement, instance) {
|
|
|
249
371
|
rootElement[CLEANUP_SYMBOL] = (options) => {
|
|
250
372
|
cleanupRootInstance(rootElement, instance, options);
|
|
251
373
|
};
|
|
374
|
+
registerRootCleanupCallback(rootElement, () => {
|
|
375
|
+
disposeRegisteredDefaultPortalScope(instance.portalScope ?? instance);
|
|
376
|
+
});
|
|
252
377
|
try {
|
|
253
378
|
const descriptor = Object.getOwnPropertyDescriptor(rootElement, "innerHTML") || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(rootElement), "innerHTML") || Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML");
|
|
254
379
|
if (descriptor && (descriptor.get || descriptor.set)) Object.defineProperty(rootElement, "innerHTML", {
|
|
255
|
-
get:
|
|
256
|
-
return descriptor.get
|
|
257
|
-
}
|
|
380
|
+
get: function() {
|
|
381
|
+
return descriptor.get?.call(this);
|
|
382
|
+
},
|
|
258
383
|
set: function(value) {
|
|
259
|
-
if (value === "" && instancesByRoot.get(this) === instance)
|
|
260
|
-
|
|
384
|
+
if (value === "" && instancesByRoot.get(this) === instance) {
|
|
385
|
+
cleanupRootInstance(rootElement, instance);
|
|
386
|
+
if (instancesByRoot.has(this)) return;
|
|
387
|
+
}
|
|
388
|
+
return descriptor.set?.call(this, value);
|
|
261
389
|
},
|
|
262
390
|
configurable: true
|
|
263
391
|
});
|
|
264
392
|
} catch {}
|
|
265
393
|
}
|
|
266
394
|
function mountOrUpdate(rootElement, componentFn, options) {
|
|
395
|
+
ensureBrowserRuntime();
|
|
267
396
|
const nonce = validateCspNonce(options?.cspNonce);
|
|
268
|
-
const wrappedFn = (
|
|
269
|
-
const out = componentFn(props, ctx);
|
|
270
|
-
if (isPromiseLike(out)) throw new Error("Async components are not supported. Components must return synchronously.");
|
|
271
|
-
const portalVNode = {
|
|
272
|
-
$$typeof: ELEMENT_TYPE,
|
|
273
|
-
type: getDefaultPortalHost(),
|
|
274
|
-
props: { __askrAutoDefaultPortal: true },
|
|
275
|
-
key: "__default_portal"
|
|
276
|
-
};
|
|
277
|
-
const root = {
|
|
278
|
-
$$typeof: ELEMENT_TYPE,
|
|
279
|
-
type: Fragment,
|
|
280
|
-
props: { children: out === void 0 || out === null ? [portalVNode] : [out, portalVNode] }
|
|
281
|
-
};
|
|
282
|
-
return nonce === void 0 ? root : CspNonceScope({
|
|
283
|
-
value: nonce,
|
|
284
|
-
children: root
|
|
285
|
-
});
|
|
286
|
-
};
|
|
287
|
-
Object.defineProperty(wrappedFn, "name", { value: componentFn.name || "Component" });
|
|
397
|
+
const wrappedFn = wrapRootRouteHandler(componentFn, nonce);
|
|
288
398
|
const existingCleanup = rootElement[CLEANUP_SYMBOL];
|
|
289
399
|
const reusedExistingInstance = typeof existingCleanup === "function";
|
|
290
|
-
if (reusedExistingInstance)
|
|
400
|
+
if (reusedExistingInstance) {
|
|
401
|
+
existingCleanup({ preserveInstance: true });
|
|
402
|
+
if (rootElement[CLEANUP_SYMBOL] !== existingCleanup) return;
|
|
403
|
+
}
|
|
291
404
|
let instance = instancesByRoot.get(rootElement);
|
|
292
405
|
if (instance) {
|
|
293
406
|
const shouldResetHookState = instance._rootComponentFn !== componentFn;
|
|
@@ -299,51 +412,19 @@ function mountOrUpdate(rootElement, componentFn, options) {
|
|
|
299
412
|
if (isDevelopmentEnvironment()) logger.warn("[Askr] prior cleanup threw:", e);
|
|
300
413
|
}
|
|
301
414
|
}
|
|
302
|
-
instance
|
|
303
|
-
instance._rootComponentFn = componentFn;
|
|
304
|
-
instance.evaluationGeneration++;
|
|
305
|
-
instance.mounted = false;
|
|
306
|
-
instance.expectedStateIndices = [];
|
|
307
|
-
instance.firstRenderComplete = false;
|
|
308
|
-
instance.isRoot = true;
|
|
309
|
-
instance.portalScope = instance;
|
|
310
|
-
instance._appRenderRuntime = options?.appRuntime;
|
|
311
|
-
instance._cspNonce = nonce;
|
|
312
|
-
if (shouldResetHookState) {
|
|
313
|
-
instance.stateValues = [];
|
|
314
|
-
instance.hasPendingUpdate = false;
|
|
315
|
-
instance.notifyUpdate = null;
|
|
316
|
-
instance.stateIndexCheck = -1;
|
|
317
|
-
instance.mountOperations = void 0;
|
|
318
|
-
instance.commitOperations = void 0;
|
|
319
|
-
instance.lifecycleSlots = void 0;
|
|
320
|
-
instance.cleanupFns = void 0;
|
|
321
|
-
instance._currentRenderToken = void 0;
|
|
322
|
-
instance.lastRenderToken = 0;
|
|
323
|
-
instance._pendingReadSources = void 0;
|
|
324
|
-
instance._lastReadSources = void 0;
|
|
325
|
-
instance._placeholder = void 0;
|
|
326
|
-
instance.errorBoundaryState = void 0;
|
|
327
|
-
instance._portalErrorParent = void 0;
|
|
328
|
-
instance._portalErrorParentGeneration = void 0;
|
|
329
|
-
instance.devWarningsEmitted = void 0;
|
|
330
|
-
}
|
|
331
|
-
if (options && typeof options.cleanupStrict === "boolean") instance.cleanupStrict = options.cleanupStrict;
|
|
415
|
+
restartComponentGeneration(instance, wrappedFn, !shouldResetHookState);
|
|
332
416
|
} else {
|
|
333
417
|
const componentId = String(++componentIdCounter);
|
|
334
|
-
instance = createComponentInstance(componentId, wrappedFn, {}, rootElement);
|
|
418
|
+
instance = createComponentInstance(componentId, wrappedFn, {}, rootElement, null);
|
|
335
419
|
instancesByRoot.set(rootElement, instance);
|
|
336
|
-
instance.isRoot = true;
|
|
337
|
-
instance._rootComponentFn = componentFn;
|
|
338
|
-
instance.portalScope = instance;
|
|
339
|
-
instance._appRenderRuntime = options?.appRuntime;
|
|
340
|
-
instance._cspNonce = nonce;
|
|
341
|
-
if (options && typeof options.cleanupStrict === "boolean") instance.cleanupStrict = options.cleanupStrict;
|
|
342
420
|
}
|
|
421
|
+
instance.isRoot = true;
|
|
422
|
+
instance._rootComponentFn = componentFn;
|
|
423
|
+
instance.portalScope = instance;
|
|
424
|
+
instance._appRenderRuntime = options?.appRuntime;
|
|
425
|
+
instance._cspNonce = nonce;
|
|
426
|
+
if (options && typeof options.cleanupStrict === "boolean") instance.cleanupStrict = options.cleanupStrict;
|
|
343
427
|
attachCleanupForRoot(rootElement, instance);
|
|
344
|
-
registerRootCleanupCallback(rootElement, () => {
|
|
345
|
-
disposeRegisteredDefaultPortalScope(instance.portalScope ?? instance);
|
|
346
|
-
});
|
|
347
428
|
executeComponent(instance);
|
|
348
429
|
flushRuntimeScheduler();
|
|
349
430
|
}
|
|
@@ -355,6 +436,7 @@ function activateHydrationBoundary(rootElement, boundary) {
|
|
|
355
436
|
return activated;
|
|
356
437
|
}
|
|
357
438
|
async function registerAppNavigation(rootElement, path, source) {
|
|
439
|
+
ensureBrowserRuntime();
|
|
358
440
|
const instance = instancesByRoot.get(rootElement);
|
|
359
441
|
if (!instance) throw new Error("Internal error: app instance missing");
|
|
360
442
|
routedRoots.add(rootElement);
|
|
@@ -370,75 +452,346 @@ function cleanupApp(root) {
|
|
|
370
452
|
const rootElement = resolveRootElement(root);
|
|
371
453
|
if (!rootElement) return;
|
|
372
454
|
const cleanupFn = rootElement[CLEANUP_SYMBOL];
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
455
|
+
if (typeof cleanupFn === "function") cleanupFn();
|
|
456
|
+
else clearRootCleanupCallbacks(rootElement);
|
|
457
|
+
}
|
|
458
|
+
/** Check whether an app instance is currently mounted at `root`. */
|
|
459
|
+
function hasApp(root) {
|
|
460
|
+
const rootElement = resolveRootElement(root);
|
|
461
|
+
if (!rootElement) return false;
|
|
462
|
+
return instancesByRoot.has(rootElement);
|
|
463
|
+
}
|
|
464
|
+
//#endregion
|
|
465
|
+
//#region src/boot/islands.ts
|
|
466
|
+
/**
|
|
467
|
+
* createIsland: Enhances existing DOM (no router, mounts once)
|
|
468
|
+
*/
|
|
469
|
+
function createIsland(config) {
|
|
470
|
+
assertExecutionModel("islands");
|
|
471
|
+
if (!config || typeof config !== "object") throw new Error("createIsland requires a config object");
|
|
472
|
+
if (typeof config.component !== "function") throw new Error("createIsland: component must be a function");
|
|
473
|
+
validateCspNonce(config.cspNonce);
|
|
474
|
+
const rootElement = resolveRootElement(config.root);
|
|
475
|
+
if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
|
|
476
|
+
if ("routes" in config) throw new Error("createIsland does not accept routes; use createSPA for routed apps");
|
|
477
|
+
if (hasRegisteredRoutes()) throw new Error("Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.");
|
|
478
|
+
mountOrUpdate(rootElement, config.component, {
|
|
479
|
+
cleanupStrict: config.cleanupStrict,
|
|
480
|
+
cspNonce: config.cspNonce,
|
|
481
|
+
appRuntime: createAppRenderRuntime({ dataRuntime: config.dataRuntime ?? getDefaultDataRuntime() })
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* createIslands: Enhances one or more existing DOM roots (no router).
|
|
486
|
+
* The only public islands constructor.
|
|
487
|
+
*/
|
|
488
|
+
function createIslands(config) {
|
|
489
|
+
assertExecutionModel("islands");
|
|
490
|
+
if (!config || typeof config !== "object") throw new Error("createIslands requires a config object");
|
|
491
|
+
validateCspNonce(config.cspNonce);
|
|
492
|
+
if (!Array.isArray(config.islands) || config.islands.length === 0) throw new Error("createIslands requires a non-empty islands array");
|
|
493
|
+
for (const island of config.islands) createIsland({
|
|
494
|
+
...island,
|
|
495
|
+
cspNonce: island.cspNonce ?? config.cspNonce
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region src/boot/route-startup.ts
|
|
500
|
+
const MAX_INITIAL_ROUTE_REDIRECTS = 20;
|
|
501
|
+
function bindResolvedRouteHandler(resolved) {
|
|
502
|
+
return () => resolved.handler(resolved.params);
|
|
503
|
+
}
|
|
504
|
+
function createDeniedStatusNode(status) {
|
|
505
|
+
return {
|
|
506
|
+
type: "div",
|
|
507
|
+
props: { "data-route-denied": String(status) },
|
|
508
|
+
children: [String(status)]
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function bindDeniedStatus(status) {
|
|
512
|
+
return () => createDeniedStatusNode(status);
|
|
513
|
+
}
|
|
514
|
+
function bindDeniedRouteHandler(status) {
|
|
515
|
+
return () => createDeniedStatusNode(status);
|
|
516
|
+
}
|
|
517
|
+
async function reconcileInitialRouteMetadata(resolved) {
|
|
518
|
+
if (resolved?.kind !== "render" || !resolved.record) return;
|
|
519
|
+
if (!resolved.record.options.title && !resolved.record.options.meta && !resolved.record.metaChain?.length) return;
|
|
520
|
+
const context = getRouteRenderContext(resolved);
|
|
521
|
+
if (!context) return;
|
|
522
|
+
reconcileRouteMeta(await resolveRouteMeta(resolved.record, context));
|
|
523
|
+
}
|
|
524
|
+
async function resolveInitialRoute(auth, source) {
|
|
525
|
+
let path = typeof window !== "undefined" ? window.location.pathname : "/";
|
|
526
|
+
let href = typeof window !== "undefined" ? `${window.location.pathname}${window.location.search}${window.location.hash}` : path;
|
|
527
|
+
const visited = /* @__PURE__ */ new Set();
|
|
528
|
+
for (let redirects = 0; redirects <= MAX_INITIAL_ROUTE_REDIRECTS; redirects++) {
|
|
529
|
+
if (visited.has(href)) throw new Error(`[Askr] Route redirect cycle detected at ${href}.`);
|
|
530
|
+
visited.add(href);
|
|
531
|
+
if (!source?.registry) throw new TypeError("resolveInitialRoute requires a route registry.");
|
|
532
|
+
const resolved = await resolveRouteRequest(href, {
|
|
533
|
+
registry: source.registry,
|
|
534
|
+
auth,
|
|
535
|
+
load: source.load
|
|
536
|
+
});
|
|
537
|
+
if (typeof window === "undefined" || !resolved || resolved.kind !== "redirect") return {
|
|
538
|
+
path,
|
|
539
|
+
href,
|
|
540
|
+
resolved
|
|
541
|
+
};
|
|
542
|
+
const redirectTarget = new URL(resolved.to, window.location.href);
|
|
543
|
+
const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;
|
|
544
|
+
window.history.replaceState({ path: redirectHref }, "", redirectHref);
|
|
545
|
+
path = redirectTarget.pathname;
|
|
546
|
+
href = redirectHref;
|
|
547
|
+
}
|
|
548
|
+
throw new Error(`[Askr] Route redirect limit exceeded (${MAX_INITIAL_ROUTE_REDIRECTS}).`);
|
|
549
|
+
}
|
|
550
|
+
//#endregion
|
|
551
|
+
//#region src/boot/spa.ts
|
|
552
|
+
/**
|
|
553
|
+
* App bootstrap and mount
|
|
554
|
+
*/
|
|
555
|
+
/**
|
|
556
|
+
* createSPA: Initializes the router and mounts the app with an explicit route registry.
|
|
557
|
+
* ```ts
|
|
558
|
+
* import { createRouteRegistry } from '@askrjs/askr/router';
|
|
559
|
+
* const registry = createRouteRegistry(() => { ... });
|
|
560
|
+
* await createSPA({ root: '#app', registry });
|
|
561
|
+
* ```
|
|
562
|
+
*
|
|
563
|
+
*/
|
|
564
|
+
async function createSPA(config) {
|
|
565
|
+
assertExecutionModel("spa");
|
|
566
|
+
if (!config || typeof config !== "object") throw new Error("createSPA requires a config object");
|
|
567
|
+
validateCspNonce(config.cspNonce);
|
|
568
|
+
if (!config.registry) throw new Error("createSPA requires a route registry.");
|
|
569
|
+
const { manifest } = config.registry;
|
|
570
|
+
if (manifest.records.length === 0) throw new Error("createSPA requires a route registry with at least one route. If you are enhancing existing HTML, use createIsland instead.");
|
|
571
|
+
const rootElement = resolveRootElement(config.root);
|
|
572
|
+
if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
|
|
573
|
+
configureScrollRestoration(config.scrollRestoration);
|
|
574
|
+
clearRouteState();
|
|
575
|
+
_applyManifest(manifest);
|
|
576
|
+
const routeAuth = config.auth ?? manifest.auth;
|
|
577
|
+
const appRouteSource = {
|
|
578
|
+
registry: config.registry,
|
|
579
|
+
auth: routeAuth
|
|
580
|
+
};
|
|
581
|
+
_setActiveRouteAuthOptions(routeAuth);
|
|
582
|
+
if (isProductionEnvironment()) lockRouteRegistration();
|
|
583
|
+
const { path, href: currentUrl, resolved } = await resolveInitialRoute(routeAuth, { registry: config.registry });
|
|
584
|
+
setServerLocation(currentUrl);
|
|
585
|
+
const appRuntime = createAppRenderRuntime({
|
|
586
|
+
...resolved?.kind === "render" && hasRouteRenderData(resolved) ? {
|
|
587
|
+
route: getRouteRenderData(resolved),
|
|
588
|
+
hasRoute: true
|
|
589
|
+
} : {},
|
|
590
|
+
dataRuntime: config.dataRuntime ?? getDefaultDataRuntime(),
|
|
591
|
+
routeRegistry: config.registry,
|
|
592
|
+
routeAuth
|
|
593
|
+
});
|
|
594
|
+
if (!resolved) {
|
|
595
|
+
mountOrUpdate(rootElement, () => ({
|
|
596
|
+
type: "div",
|
|
597
|
+
children: []
|
|
598
|
+
}), {
|
|
599
|
+
cleanupStrict: config.cleanupStrict,
|
|
600
|
+
appRuntime,
|
|
601
|
+
cspNonce: config.cspNonce
|
|
602
|
+
});
|
|
603
|
+
await registerAppNavigation(rootElement, path, {
|
|
604
|
+
...appRouteSource,
|
|
605
|
+
runtime: appRuntime
|
|
606
|
+
});
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (resolved.kind === "redirect") {
|
|
610
|
+
mountOrUpdate(rootElement, () => ({
|
|
611
|
+
type: "div",
|
|
612
|
+
children: []
|
|
613
|
+
}), {
|
|
614
|
+
cleanupStrict: config.cleanupStrict,
|
|
615
|
+
appRuntime,
|
|
616
|
+
cspNonce: config.cspNonce
|
|
617
|
+
});
|
|
618
|
+
await registerAppNavigation(rootElement, path, {
|
|
619
|
+
...appRouteSource,
|
|
620
|
+
runtime: appRuntime
|
|
621
|
+
});
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
await reconcileInitialRouteMetadata(resolved);
|
|
625
|
+
mountOrUpdate(rootElement, resolved.kind === "deny" ? bindDeniedStatus(resolved.status) : bindResolvedRouteHandler({
|
|
626
|
+
handler: resolved.handler,
|
|
627
|
+
params: resolved.params
|
|
628
|
+
}), {
|
|
629
|
+
cleanupStrict: config.cleanupStrict,
|
|
630
|
+
appRuntime,
|
|
631
|
+
cspNonce: config.cspNonce
|
|
632
|
+
});
|
|
633
|
+
await registerAppNavigation(rootElement, path, {
|
|
634
|
+
...appRouteSource,
|
|
635
|
+
runtime: appRuntime
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
//#endregion
|
|
639
|
+
//#region src/boot/hydration.ts
|
|
640
|
+
const DEFERRED_PAYLOAD = "__askr_deferred__";
|
|
641
|
+
function applyDeferredStreamPatches(rootElement) {
|
|
642
|
+
const patches = Array.from(rootElement.querySelectorAll("template[data-askr-deferred-patch]"));
|
|
643
|
+
for (const template of patches) {
|
|
644
|
+
const id = template.getAttribute("data-askr-deferred-patch");
|
|
645
|
+
if (!id) continue;
|
|
646
|
+
const boundary = Array.from(rootElement.querySelectorAll("askr-resolve[data-askr-deferred]")).find((candidate) => candidate.getAttribute("data-askr-deferred") === id);
|
|
647
|
+
if (boundary) boundary.replaceWith(template.content.cloneNode(true));
|
|
648
|
+
template.remove();
|
|
649
|
+
for (const script of Array.from(rootElement.querySelectorAll("script[data-askr-deferred-apply]"))) if (script.getAttribute("data-askr-deferred-apply") === id) script.remove();
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function hydrationReviver(_key, value) {
|
|
653
|
+
if (!value || typeof value !== "object") return value;
|
|
654
|
+
const payload = value;
|
|
655
|
+
if (payload[DEFERRED_PAYLOAD] === "fulfilled") return reviveDeferredValue("fulfilled", payload.value, void 0);
|
|
656
|
+
if (payload[DEFERRED_PAYLOAD] === "rejected") return reviveDeferredValue("rejected", void 0, payload.error);
|
|
657
|
+
return value;
|
|
658
|
+
}
|
|
659
|
+
function takeHydrationRenderData(rootElement) {
|
|
660
|
+
for (const child of Array.from(rootElement.children)) if (child instanceof HTMLScriptElement && child.getAttribute("data-askr-render-data") === "true") {
|
|
661
|
+
const raw = child.textContent ?? "";
|
|
662
|
+
child.remove();
|
|
663
|
+
if (!raw) return pageRenderEnvelope(void 0);
|
|
380
664
|
try {
|
|
381
|
-
|
|
665
|
+
return pageRenderEnvelope(JSON.parse(raw, hydrationReviver));
|
|
666
|
+
} catch (err) {
|
|
667
|
+
const error = /* @__PURE__ */ new Error("[Askr] Failed to parse embedded SSR render data during hydration.");
|
|
668
|
+
error.cause = err;
|
|
669
|
+
throw error;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
function markSkippedElements(root, skipSelectors) {
|
|
675
|
+
if (skipSelectors.length === 0) return;
|
|
676
|
+
const selectorList = Array.from(new Set(skipSelectors)).join(", ");
|
|
677
|
+
root.querySelectorAll(selectorList).forEach((el) => el.setAttribute("data-skip-hydrate", "true"));
|
|
678
|
+
}
|
|
679
|
+
function collectDeferredBelowFoldBoundaries(root, foldY) {
|
|
680
|
+
const boundaries = [];
|
|
681
|
+
const stack = [];
|
|
682
|
+
for (let index = root.children.length - 1; index >= 0; index -= 1) stack.push(root.children[index]);
|
|
683
|
+
while (stack.length > 0) {
|
|
684
|
+
const element = stack.pop();
|
|
685
|
+
if (element.hasAttribute("data-skip-hydrate")) continue;
|
|
686
|
+
if (element.getBoundingClientRect().top >= foldY) {
|
|
687
|
+
element.setAttribute("data-skip-hydrate", "true");
|
|
688
|
+
registerDeferredHydrationBoundary(root, element);
|
|
689
|
+
boundaries.push(element);
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
for (let index = element.children.length - 1; index >= 0; index -= 1) stack.push(element.children[index]);
|
|
693
|
+
}
|
|
694
|
+
return boundaries;
|
|
695
|
+
}
|
|
696
|
+
function activateVisibleDeferredBoundaries(root, boundaries, foldY, activate) {
|
|
697
|
+
let activated = false;
|
|
698
|
+
let remaining = 0;
|
|
699
|
+
for (const element of boundaries) {
|
|
700
|
+
if (!element.hasAttribute("data-skip-hydrate")) continue;
|
|
701
|
+
if (element.getBoundingClientRect().top < foldY) try {
|
|
702
|
+
if (activate(root, element)) activated = true;
|
|
382
703
|
} catch {}
|
|
704
|
+
if (element.hasAttribute("data-skip-hydrate")) remaining += 1;
|
|
383
705
|
}
|
|
384
|
-
}
|
|
385
|
-
/** Check whether an app instance is currently mounted at `root`. */
|
|
386
|
-
function hasApp(root) {
|
|
387
|
-
const rootElement = resolveRootElement(root);
|
|
388
|
-
if (!rootElement) return false;
|
|
389
|
-
return instancesByRoot.has(rootElement);
|
|
390
|
-
}
|
|
391
|
-
//#endregion
|
|
392
|
-
//#region src/boot/route-startup.ts
|
|
393
|
-
const MAX_INITIAL_ROUTE_REDIRECTS = 20;
|
|
394
|
-
function bindResolvedRouteHandler(resolved) {
|
|
395
|
-
return () => resolved.handler(resolved.params);
|
|
396
|
-
}
|
|
397
|
-
function createDeniedStatusNode(status) {
|
|
398
706
|
return {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
children: [String(status)]
|
|
707
|
+
activated,
|
|
708
|
+
remaining
|
|
402
709
|
};
|
|
403
710
|
}
|
|
404
|
-
function
|
|
405
|
-
return () =>
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
711
|
+
function queueIdleWork(work) {
|
|
712
|
+
return new Promise((resolve) => {
|
|
713
|
+
if (typeof requestIdleCallback !== "undefined") {
|
|
714
|
+
requestIdleCallback(() => {
|
|
715
|
+
work();
|
|
716
|
+
resolve();
|
|
717
|
+
}, { timeout: 2e3 });
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
setTimeout(() => {
|
|
721
|
+
work();
|
|
722
|
+
resolve();
|
|
723
|
+
}, 0);
|
|
724
|
+
});
|
|
409
725
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (
|
|
413
|
-
|
|
414
|
-
if (!context) return;
|
|
415
|
-
reconcileRouteMeta(await resolveRouteMeta(resolved.record, context));
|
|
726
|
+
function shouldVerifyHydrationMarkup(config) {
|
|
727
|
+
const explicit = config.hydrate?.verifyMarkup;
|
|
728
|
+
if (typeof explicit === "boolean") return explicit;
|
|
729
|
+
return !isProductionEnvironment();
|
|
416
730
|
}
|
|
417
|
-
async function
|
|
418
|
-
|
|
419
|
-
let
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
731
|
+
async function applySelectiveHydration(rootElement, resolved, path, cleanupStrict, hydrateOptions, source, hooks, interactionReplay) {
|
|
732
|
+
const hasBelowFoldDeferral = !!hydrateOptions.deferBelowFold;
|
|
733
|
+
let staticChildSlotsCacheSuspended = false;
|
|
734
|
+
let releaseSelectiveHydrationResources = () => {};
|
|
735
|
+
const restoreStaticChildSlotsCache = () => {
|
|
736
|
+
if (!staticChildSlotsCacheSuspended) return;
|
|
737
|
+
setStaticChildSlotsCacheEnabled(true);
|
|
738
|
+
staticChildSlotsCacheSuspended = false;
|
|
739
|
+
};
|
|
740
|
+
if (hydrateOptions.skipSelectors?.length) markSkippedElements(rootElement, hydrateOptions.skipSelectors);
|
|
741
|
+
let deferredBoundaries = [];
|
|
742
|
+
if (hydrateOptions.deferBelowFold) {
|
|
743
|
+
setStaticChildSlotsCacheEnabled(false);
|
|
744
|
+
staticChildSlotsCacheSuspended = true;
|
|
745
|
+
const foldY = hydrateOptions.foldThreshold ?? window.innerHeight;
|
|
746
|
+
deferredBoundaries = collectDeferredBelowFoldBoundaries(rootElement, foldY);
|
|
747
|
+
interactionReplay.registerDeferredBoundaries(deferredBoundaries);
|
|
748
|
+
let selectiveHydrationResourcesReleased = false;
|
|
749
|
+
let unregisterRootCleanupCallback = () => {};
|
|
750
|
+
function handleScroll() {
|
|
751
|
+
const { activated, remaining } = activateVisibleDeferredBoundaries(rootElement, deferredBoundaries, foldY, hooks.activateHydrationBoundary);
|
|
752
|
+
if (!activated) return;
|
|
753
|
+
if (remaining === 0) releaseSelectiveHydrationResources();
|
|
754
|
+
}
|
|
755
|
+
releaseSelectiveHydrationResources = () => {
|
|
756
|
+
if (selectiveHydrationResourcesReleased) return;
|
|
757
|
+
selectiveHydrationResourcesReleased = true;
|
|
758
|
+
unregisterRootCleanupCallback();
|
|
759
|
+
window.removeEventListener("scroll", handleScroll);
|
|
760
|
+
restoreStaticChildSlotsCache();
|
|
761
|
+
interactionReplay.clearDeferredBoundaries();
|
|
434
762
|
};
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
window.
|
|
438
|
-
path = redirectTarget.pathname;
|
|
439
|
-
href = redirectHref;
|
|
763
|
+
interactionReplay.setOnDeferredBoundariesDrained(releaseSelectiveHydrationResources);
|
|
764
|
+
unregisterRootCleanupCallback = hooks.registerRootCleanupCallback(rootElement, releaseSelectiveHydrationResources);
|
|
765
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
440
766
|
}
|
|
441
|
-
|
|
767
|
+
if (hydrateOptions.deferUntilIdle && !hasBelowFoldDeferral) {
|
|
768
|
+
await queueIdleWork(() => {
|
|
769
|
+
hooks.mountOrUpdate(rootElement, (() => resolved.handler(resolved.params)), {
|
|
770
|
+
cleanupStrict,
|
|
771
|
+
appRuntime: source?.runtime
|
|
772
|
+
});
|
|
773
|
+
});
|
|
774
|
+
await hooks.registerAppNavigation(rootElement, path, source);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
try {
|
|
778
|
+
hooks.mountOrUpdate(rootElement, (() => resolved.handler(resolved.params)), {
|
|
779
|
+
cleanupStrict,
|
|
780
|
+
appRuntime: source?.runtime
|
|
781
|
+
});
|
|
782
|
+
await hooks.registerAppNavigation(rootElement, path, source);
|
|
783
|
+
} catch (error) {
|
|
784
|
+
releaseSelectiveHydrationResources();
|
|
785
|
+
throw error;
|
|
786
|
+
}
|
|
787
|
+
if (hydrateOptions.deferUntilIdle && deferredBoundaries.length > 0) await queueIdleWork(() => {
|
|
788
|
+
try {
|
|
789
|
+
activateVisibleDeferredBoundaries(rootElement, deferredBoundaries, Number.POSITIVE_INFINITY, hooks.activateHydrationBoundary);
|
|
790
|
+
} finally {
|
|
791
|
+
releaseSelectiveHydrationResources();
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
if (deferredBoundaries.length === 0) releaseSelectiveHydrationResources();
|
|
442
795
|
}
|
|
443
796
|
//#endregion
|
|
444
797
|
//#region src/boot/hydration-interaction-replay.ts
|
|
@@ -610,125 +963,11 @@ function beginHydrationInteractionReplay(root, activateBoundary, permanentSkipSe
|
|
|
610
963
|
};
|
|
611
964
|
}
|
|
612
965
|
//#endregion
|
|
613
|
-
//#region src/boot/
|
|
966
|
+
//#region src/boot/hydrate-spa.ts
|
|
614
967
|
/**
|
|
615
968
|
* App bootstrap and mount
|
|
616
969
|
*/
|
|
617
970
|
/**
|
|
618
|
-
* createIsland: Enhances existing DOM (no router, mounts once)
|
|
619
|
-
*/
|
|
620
|
-
function createIsland(config) {
|
|
621
|
-
assertExecutionModel("islands");
|
|
622
|
-
if (!config || typeof config !== "object") throw new Error("createIsland requires a config object");
|
|
623
|
-
if (typeof config.component !== "function") throw new Error("createIsland: component must be a function");
|
|
624
|
-
validateCspNonce(config.cspNonce);
|
|
625
|
-
const rootElement = resolveRootElement(config.root);
|
|
626
|
-
if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
|
|
627
|
-
if ("routes" in config) throw new Error("createIsland does not accept routes; use createSPA for routed apps");
|
|
628
|
-
if (hasRegisteredRoutes()) throw new Error("Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.");
|
|
629
|
-
mountOrUpdate(rootElement, config.component, {
|
|
630
|
-
cleanupStrict: config.cleanupStrict,
|
|
631
|
-
cspNonce: config.cspNonce
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* createIslands: Enhances one or more existing DOM roots (no router).
|
|
636
|
-
* The only public islands constructor.
|
|
637
|
-
*/
|
|
638
|
-
function createIslands(config) {
|
|
639
|
-
assertExecutionModel("islands");
|
|
640
|
-
if (!config || typeof config !== "object") throw new Error("createIslands requires a config object");
|
|
641
|
-
validateCspNonce(config.cspNonce);
|
|
642
|
-
if (!Array.isArray(config.islands) || config.islands.length === 0) throw new Error("createIslands requires a non-empty islands array");
|
|
643
|
-
for (const island of config.islands) createIsland({
|
|
644
|
-
...island,
|
|
645
|
-
cspNonce: island.cspNonce ?? config.cspNonce
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* createSPA: Initializes the router and mounts the app with an explicit route registry.
|
|
650
|
-
* ```ts
|
|
651
|
-
* import { createRouteRegistry } from '@askrjs/askr/router';
|
|
652
|
-
* const registry = createRouteRegistry(() => { ... });
|
|
653
|
-
* await createSPA({ root: '#app', registry });
|
|
654
|
-
* ```
|
|
655
|
-
*
|
|
656
|
-
*/
|
|
657
|
-
async function createSPA(config) {
|
|
658
|
-
assertExecutionModel("spa");
|
|
659
|
-
if (!config || typeof config !== "object") throw new Error("createSPA requires a config object");
|
|
660
|
-
validateCspNonce(config.cspNonce);
|
|
661
|
-
if (!config.registry) throw new Error("createSPA requires a route registry.");
|
|
662
|
-
const { manifest } = config.registry;
|
|
663
|
-
if (manifest.records.length === 0) throw new Error("createSPA requires a route registry with at least one route. If you are enhancing existing HTML, use createIsland instead.");
|
|
664
|
-
const rootElement = resolveRootElement(config.root);
|
|
665
|
-
if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
|
|
666
|
-
configureScrollRestoration(config.scrollRestoration);
|
|
667
|
-
clearRouteState();
|
|
668
|
-
_applyManifest(manifest);
|
|
669
|
-
const routeAuth = config.auth ?? manifest.auth;
|
|
670
|
-
const appRouteSource = {
|
|
671
|
-
registry: config.registry,
|
|
672
|
-
auth: routeAuth
|
|
673
|
-
};
|
|
674
|
-
_setActiveRouteAuthOptions(routeAuth);
|
|
675
|
-
if (isProductionEnvironment()) lockRouteRegistration();
|
|
676
|
-
const { path, href: currentUrl, resolved } = await resolveInitialRoute(routeAuth, { registry: config.registry });
|
|
677
|
-
setServerLocation(currentUrl);
|
|
678
|
-
const appRuntime = createAppRenderRuntime({
|
|
679
|
-
...resolved?.kind === "render" && hasRouteRenderData(resolved) ? {
|
|
680
|
-
route: getRouteRenderData(resolved),
|
|
681
|
-
hasRoute: true
|
|
682
|
-
} : {},
|
|
683
|
-
dataRuntime: config.dataRuntime ?? getDefaultDataRuntime(),
|
|
684
|
-
routeRegistry: config.registry,
|
|
685
|
-
routeAuth
|
|
686
|
-
});
|
|
687
|
-
if (!resolved) {
|
|
688
|
-
mountOrUpdate(rootElement, () => ({
|
|
689
|
-
type: "div",
|
|
690
|
-
children: []
|
|
691
|
-
}), {
|
|
692
|
-
cleanupStrict: config.cleanupStrict,
|
|
693
|
-
appRuntime,
|
|
694
|
-
cspNonce: config.cspNonce
|
|
695
|
-
});
|
|
696
|
-
await registerAppNavigation(rootElement, path, {
|
|
697
|
-
...appRouteSource,
|
|
698
|
-
runtime: appRuntime
|
|
699
|
-
});
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
if (resolved.kind === "redirect") {
|
|
703
|
-
mountOrUpdate(rootElement, () => ({
|
|
704
|
-
type: "div",
|
|
705
|
-
children: []
|
|
706
|
-
}), {
|
|
707
|
-
cleanupStrict: config.cleanupStrict,
|
|
708
|
-
appRuntime,
|
|
709
|
-
cspNonce: config.cspNonce
|
|
710
|
-
});
|
|
711
|
-
await registerAppNavigation(rootElement, path, {
|
|
712
|
-
...appRouteSource,
|
|
713
|
-
runtime: appRuntime
|
|
714
|
-
});
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
await reconcileInitialRouteMetadata(resolved);
|
|
718
|
-
mountOrUpdate(rootElement, resolved.kind === "deny" ? bindDeniedStatus(resolved.status) : bindResolvedRouteHandler({
|
|
719
|
-
handler: resolved.handler,
|
|
720
|
-
params: resolved.params
|
|
721
|
-
}), {
|
|
722
|
-
cleanupStrict: config.cleanupStrict,
|
|
723
|
-
appRuntime,
|
|
724
|
-
cspNonce: config.cspNonce
|
|
725
|
-
});
|
|
726
|
-
await registerAppNavigation(rootElement, path, {
|
|
727
|
-
...appRouteSource,
|
|
728
|
-
runtime: appRuntime
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
/**
|
|
732
971
|
* hydrateSPA: Hydrate server-rendered HTML with an explicit route registry.
|
|
733
972
|
*/
|
|
734
973
|
async function hydrateSPA(config) {
|
|
@@ -775,16 +1014,13 @@ async function hydrateSPA(config) {
|
|
|
775
1014
|
const hydrationResolved = resolved.kind === "deny" ? {
|
|
776
1015
|
handler: bindDeniedRouteHandler(resolved.status),
|
|
777
1016
|
params: {}
|
|
778
|
-
} :
|
|
779
|
-
handler: resolved.handler,
|
|
780
|
-
params: resolved.params
|
|
781
|
-
};
|
|
1017
|
+
} : resolved;
|
|
782
1018
|
const mountHydratedRoot = (...args) => withIntrinsicHydrationAdoption(() => mountOrUpdate(args[0], args[1], {
|
|
783
1019
|
...args[2],
|
|
784
1020
|
cspNonce: config.cspNonce
|
|
785
1021
|
}));
|
|
786
1022
|
if (shouldVerifyHydrationMarkup(config)) {
|
|
787
|
-
const { verifyHydrationSyncForUrl } = await import("./verify-hydration-
|
|
1023
|
+
const { verifyHydrationSyncForUrl } = await import("./verify-hydration-ikaxotf7.js");
|
|
788
1024
|
if (!verifyHydrationSyncForUrl({
|
|
789
1025
|
root: rootElement,
|
|
790
1026
|
url: currentUrl,
|
|
@@ -841,4 +1077,8 @@ async function hydrateSPA(config) {
|
|
|
841
1077
|
}
|
|
842
1078
|
}
|
|
843
1079
|
//#endregion
|
|
844
|
-
|
|
1080
|
+
//#region src/boot/index.ts
|
|
1081
|
+
/** Stable bootstrap exports; each mounting mode owns its orchestration. */
|
|
1082
|
+
composeBrowserRuntime();
|
|
1083
|
+
//#endregion
|
|
1084
|
+
export { cleanupApp as a, createIslands as i, createSPA as n, hasApp as o, createIsland as r, installRendererBridge as s, hydrateSPA as t };
|