@vesk/runtime 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/action.d.ts +43 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/action.js +84 -0
- package/dist/bindings.d.ts +4 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +137 -0
- package/dist/context.d.ts +15 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +34 -0
- package/dist/experiment.d.ts +17 -0
- package/dist/experiment.d.ts.map +1 -0
- package/dist/experiment.js +71 -0
- package/dist/form.d.ts +36 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +278 -0
- package/dist/hmr-client.d.ts +1 -0
- package/dist/hmr-client.d.ts.map +1 -0
- package/dist/hmr-client.js +273 -0
- package/dist/hydrate.d.ts +34 -0
- package/dist/hydrate.d.ts.map +1 -0
- package/dist/hydrate.js +248 -0
- package/dist/image.d.ts +19 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +51 -0
- package/dist/index-client.d.ts +16 -0
- package/dist/index-client.d.ts.map +1 -0
- package/dist/index-client.js +15 -0
- package/dist/index-server.d.ts +15 -0
- package/dist/index-server.d.ts.map +1 -0
- package/dist/index-server.js +14 -0
- package/dist/isr.d.ts +27 -0
- package/dist/isr.d.ts.map +1 -0
- package/dist/isr.js +143 -0
- package/dist/md.d.ts +17 -0
- package/dist/md.d.ts.map +1 -0
- package/dist/md.js +507 -0
- package/dist/package.json +23 -0
- package/dist/portal.d.ts +8 -0
- package/dist/portal.d.ts.map +1 -0
- package/dist/portal.js +20 -0
- package/dist/reconcile.d.ts +3 -0
- package/dist/reconcile.d.ts.map +1 -0
- package/dist/reconcile.js +66 -0
- package/dist/request.d.ts +188 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +624 -0
- package/dist/resource.d.ts +47 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +502 -0
- package/dist/ripple-blocks.d.ts +18 -0
- package/dist/ripple-blocks.d.ts.map +1 -0
- package/dist/ripple-blocks.js +184 -0
- package/dist/ripple-constants.d.ts +42 -0
- package/dist/ripple-constants.d.ts.map +1 -0
- package/dist/ripple-constants.js +41 -0
- package/dist/ripple-runtime.d.ts +125 -0
- package/dist/ripple-runtime.d.ts.map +1 -0
- package/dist/ripple-runtime.js +891 -0
- package/dist/ripple-utils.d.ts +12 -0
- package/dist/ripple-utils.d.ts.map +1 -0
- package/dist/ripple-utils.js +10 -0
- package/dist/router-components.d.ts +83 -0
- package/dist/router-components.d.ts.map +1 -0
- package/dist/router-components.js +320 -0
- package/dist/router-match.d.ts +40 -0
- package/dist/router-match.d.ts.map +1 -0
- package/dist/router-match.js +158 -0
- package/dist/router.d.ts +56 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +1330 -0
- package/dist/seo.d.ts +94 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +138 -0
- package/dist/suspense.d.ts +1 -0
- package/dist/suspense.d.ts.map +1 -0
- package/dist/suspense.js +14 -0
- package/dist/track.d.ts +20 -0
- package/dist/track.d.ts.map +1 -0
- package/dist/track.js +124 -0
- package/package.json +55 -0
package/dist/router.js
ADDED
|
@@ -0,0 +1,1330 @@
|
|
|
1
|
+
import { get, set, scope, set_active_block } from '@vesk/runtime/src/ripple-runtime';
|
|
2
|
+
import { root } from '@vesk/runtime/src/ripple-blocks';
|
|
3
|
+
import { createHydrateWalker, hydrateViewport, hydrateIdle, hydrateOnInteraction } from '@vesk/runtime/src/hydrate';
|
|
4
|
+
import { matchRoute, flattenLayoutChain, buildTreeFromMap } from '@vesk/runtime/src/router-match';
|
|
5
|
+
import { setIsHydrating, _state, _scrollPositions, _isPopStateNavigation, setIsPopStateNavigation, setCurrentRouter, showLoadingInContainer, handleScroll, applyHead, findLoadingComponent, findErrorComponent, findNotFoundComponent, Outlet, Link, NavLink, useNavigate, useParams, usePathname, useSearchParams, useRouter, Redirect, redirect, permanentRedirect, NotFoundError, notFound, } from '@vesk/runtime/src/router-components';
|
|
6
|
+
export { Outlet, Link, NavLink, useNavigate, useParams, usePathname, useSearchParams, useRouter, Redirect, redirect, permanentRedirect, NotFoundError, notFound, ensureChunk, };
|
|
7
|
+
export { matchRoute } from '@vesk/runtime/src/router-match';
|
|
8
|
+
const loadedChunks = new Set();
|
|
9
|
+
const failedChunks = new Map();
|
|
10
|
+
function chunkLoadError(chunkUrl) {
|
|
11
|
+
return failedChunks.get(chunkUrl);
|
|
12
|
+
}
|
|
13
|
+
function ensureChunk(chunkUrl) {
|
|
14
|
+
if (!chunkUrl || loadedChunks.has(chunkUrl))
|
|
15
|
+
return Promise.resolve();
|
|
16
|
+
loadedChunks.add(chunkUrl);
|
|
17
|
+
if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
|
|
18
|
+
return Promise.resolve();
|
|
19
|
+
}
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const s = document.createElement('script');
|
|
22
|
+
s.src = chunkUrl;
|
|
23
|
+
s.onload = () => {
|
|
24
|
+
failedChunks.delete(chunkUrl);
|
|
25
|
+
resolve();
|
|
26
|
+
};
|
|
27
|
+
s.onerror = () => {
|
|
28
|
+
loadedChunks.delete(chunkUrl);
|
|
29
|
+
const err = new Error(`Failed to load chunk: ${chunkUrl}`);
|
|
30
|
+
failedChunks.set(chunkUrl, err);
|
|
31
|
+
reject(err);
|
|
32
|
+
};
|
|
33
|
+
document.head.appendChild(s);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** Loads pending chunks without letting a single failed chunk abort the flow. */
|
|
37
|
+
function loadChunksQuietly(urls) {
|
|
38
|
+
return Promise.all(urls.map((u) => ensureChunk(u).catch(() => undefined))).then(() => undefined);
|
|
39
|
+
}
|
|
40
|
+
function hasPendingChunks(nodes) {
|
|
41
|
+
const urls = [];
|
|
42
|
+
function walk(n) {
|
|
43
|
+
if (n._chunk && !loadedChunks.has(n._chunk))
|
|
44
|
+
urls.push(n._chunk);
|
|
45
|
+
if (n.children)
|
|
46
|
+
n.children.forEach(walk);
|
|
47
|
+
}
|
|
48
|
+
nodes.forEach(walk);
|
|
49
|
+
return urls;
|
|
50
|
+
}
|
|
51
|
+
const _dataPromises = new Map();
|
|
52
|
+
async function fetchRouteData(path) {
|
|
53
|
+
try {
|
|
54
|
+
const res = await fetch(path, { headers: { 'X-Vesk-Data': '1' }, credentials: 'same-origin' });
|
|
55
|
+
if (res.redirected && res.url) {
|
|
56
|
+
const finalUrl = new URL(res.url);
|
|
57
|
+
const requested = new URL(path, window.location.origin);
|
|
58
|
+
if (finalUrl.pathname !== requested.pathname || finalUrl.search !== requested.search) {
|
|
59
|
+
return { redirect: finalUrl.pathname + finalUrl.search };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (res.status === 404)
|
|
63
|
+
return { notFound: true };
|
|
64
|
+
const ct = res.headers.get('content-type') || '';
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
// The server's x-vesk-data error payload is `{ error: message }`.
|
|
67
|
+
// Surface it so the route's error component can render instead of
|
|
68
|
+
// silently falling back to an HTML fetch.
|
|
69
|
+
if (ct.includes('application/json')) {
|
|
70
|
+
try {
|
|
71
|
+
const body = await res.json();
|
|
72
|
+
if (body && typeof body === 'object' && typeof body.error === 'string') {
|
|
73
|
+
return { error: body.error, statusCode: res.status };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
/* fall through to null */
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
if (!ct.includes('application/json'))
|
|
83
|
+
return null;
|
|
84
|
+
return (await res.json());
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function getRouteData(path, scope) {
|
|
91
|
+
const key = scope === undefined ? path : path + '\u0000' + scope;
|
|
92
|
+
const existing = _dataPromises.get(key);
|
|
93
|
+
if (existing)
|
|
94
|
+
return existing;
|
|
95
|
+
const p = fetchRouteData(path).finally(() => { _dataPromises.delete(key); });
|
|
96
|
+
_dataPromises.set(key, p);
|
|
97
|
+
return p;
|
|
98
|
+
}
|
|
99
|
+
function findPageNode(match) {
|
|
100
|
+
const chain = match.matchChain;
|
|
101
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
102
|
+
if (chain[i].page)
|
|
103
|
+
return chain[i];
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A route whose component chunk failed to load (or failed to compile) must
|
|
109
|
+
* surface as an error page for that route only — never as a 404, and never
|
|
110
|
+
* as a thrown exception that breaks the rest of the app.
|
|
111
|
+
*/
|
|
112
|
+
function findPageNodeOrFailed(match) {
|
|
113
|
+
const chain = match.matchChain;
|
|
114
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
115
|
+
if (chain[i].page || chain[i]._pageName)
|
|
116
|
+
return chain[i];
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
function chunkErrorForNode(node) {
|
|
121
|
+
if (!node)
|
|
122
|
+
return undefined;
|
|
123
|
+
if (node._chunk) {
|
|
124
|
+
const err = chunkLoadError(node._chunk);
|
|
125
|
+
if (err)
|
|
126
|
+
return err;
|
|
127
|
+
}
|
|
128
|
+
if (node._chunkError)
|
|
129
|
+
return new Error(String(node._chunkError));
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
async function renderNotFound(router, match, container) {
|
|
133
|
+
const chain = match.matchChain;
|
|
134
|
+
const paramValues = match.params;
|
|
135
|
+
const notFoundFn = findNotFoundComponent(chain);
|
|
136
|
+
if (notFoundFn) {
|
|
137
|
+
const tempRoot = document.createDocumentFragment();
|
|
138
|
+
const walker = createHydrateWalker(tempRoot, []);
|
|
139
|
+
const nfProps = { params: paramValues, url: match.pathname || window.location.pathname };
|
|
140
|
+
const nfDom = await runInBlockWindow(() => notFoundFn(nfProps, new Map(), walker));
|
|
141
|
+
if (nfDom && typeof nfDom === 'object' && nfDom.nodeType) {
|
|
142
|
+
if (container.replaceChildren)
|
|
143
|
+
container.replaceChildren(nfDom);
|
|
144
|
+
else {
|
|
145
|
+
container.innerHTML = '';
|
|
146
|
+
container.appendChild(nfDom);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else if (typeof nfDom === 'string') {
|
|
150
|
+
container.innerHTML = nfDom;
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (container.replaceChildren)
|
|
155
|
+
container.replaceChildren();
|
|
156
|
+
else
|
|
157
|
+
container.innerHTML = '';
|
|
158
|
+
container.innerHTML = '<h1>404 — Not Found</h1>';
|
|
159
|
+
router._currentMatch = match;
|
|
160
|
+
}
|
|
161
|
+
async function applyRouteData(router, match, data, container, render = renderMatch) {
|
|
162
|
+
if (data.notFound) {
|
|
163
|
+
await renderNotFound(router, match, container);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (data.error) {
|
|
167
|
+
const err = new Error(data.error);
|
|
168
|
+
err.statusCode = data.statusCode || 500;
|
|
169
|
+
await renderErrorPage(router, match, container, err);
|
|
170
|
+
router._currentMatch = match;
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const pathname = match.pathname || window.location.pathname;
|
|
174
|
+
const pageNode = findPageNodeOrFailed(match);
|
|
175
|
+
if (pageNode && data.props) {
|
|
176
|
+
pageNode.props = data.props;
|
|
177
|
+
pageNode._dataPath = pathname;
|
|
178
|
+
pageNode._dataFetchedAt = Date.now();
|
|
179
|
+
}
|
|
180
|
+
if (data.head) {
|
|
181
|
+
applyHead(data.head);
|
|
182
|
+
if (pageNode)
|
|
183
|
+
pageNode._head = data.head;
|
|
184
|
+
}
|
|
185
|
+
const hasRealProps = data.props
|
|
186
|
+
? Object.keys(data.props).some(k => k !== 'params')
|
|
187
|
+
: false;
|
|
188
|
+
if (!hasRealProps) {
|
|
189
|
+
// Head-only/params-only payload: the page is already rendered by the
|
|
190
|
+
// initial renderMatch. Cache the fresh marker (_dataPath) so revisits
|
|
191
|
+
// skip the fetch, but don't re-render — an async page's pending render
|
|
192
|
+
// continuation could otherwise land after a later navigation and
|
|
193
|
+
// clobber the newly mounted DOM.
|
|
194
|
+
router._currentMatch = match;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
let swapped = false;
|
|
198
|
+
try {
|
|
199
|
+
const fresh = await renderPageOnly(router, match);
|
|
200
|
+
if (fresh !== undefined) {
|
|
201
|
+
swapped = replacePageContent(container, fresh);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
swapped = false;
|
|
206
|
+
}
|
|
207
|
+
if (!swapped) {
|
|
208
|
+
await render(router, match, container);
|
|
209
|
+
}
|
|
210
|
+
router._currentMatch = match;
|
|
211
|
+
}
|
|
212
|
+
function shouldFetchData(router, match) {
|
|
213
|
+
// Use findPageNodeOrFailed: on lazy/chunked routes the page component is
|
|
214
|
+
// wired only after its chunk finishes loading, but the route node exists
|
|
215
|
+
// from the start (via _pageName). Requiring an already-loaded `.page`
|
|
216
|
+
// (findPageNode) makes shouldFetchData return false for a first visit to a
|
|
217
|
+
// chunked route — fetchData runs before the chunk resolves — silently
|
|
218
|
+
// dropping the X-Vesk-Data fetch for that visit.
|
|
219
|
+
const pageNode = findPageNodeOrFailed(match);
|
|
220
|
+
if (!pageNode)
|
|
221
|
+
return false;
|
|
222
|
+
const pathname = match.pathname || window.location.pathname;
|
|
223
|
+
if (pageNode._dataPath !== pathname)
|
|
224
|
+
return true;
|
|
225
|
+
const ttl = router._routeDataCache ?? 0;
|
|
226
|
+
if (ttl <= 0)
|
|
227
|
+
return true;
|
|
228
|
+
const fetchedAt = pageNode._dataFetchedAt ?? 0;
|
|
229
|
+
return Date.now() - fetchedAt >= ttl;
|
|
230
|
+
}
|
|
231
|
+
function hasRealPageData(data) {
|
|
232
|
+
if (data.notFound || data.redirect || data.error)
|
|
233
|
+
return true;
|
|
234
|
+
if (data.head && data.head.trim().length > 0)
|
|
235
|
+
return true;
|
|
236
|
+
const props = data.props;
|
|
237
|
+
if (!props)
|
|
238
|
+
return false;
|
|
239
|
+
return Object.keys(props).some(k => k !== 'params');
|
|
240
|
+
}
|
|
241
|
+
function storePrefetchedData(match, data) {
|
|
242
|
+
if (!data || data.notFound || data.redirect)
|
|
243
|
+
return;
|
|
244
|
+
const pathname = match.pathname || window.location.pathname;
|
|
245
|
+
const pageNode = findPageNodeOrFailed(match);
|
|
246
|
+
if (pageNode && data.props) {
|
|
247
|
+
pageNode.props = data.props;
|
|
248
|
+
pageNode._dataPath = pathname;
|
|
249
|
+
pageNode._dataFetchedAt = Date.now();
|
|
250
|
+
}
|
|
251
|
+
if (data.head && pageNode)
|
|
252
|
+
pageNode._head = data.head;
|
|
253
|
+
}
|
|
254
|
+
const PAGE_START_MARKER = 'vesk:page';
|
|
255
|
+
const PAGE_END_MARKER = '/vesk:page';
|
|
256
|
+
function wrapPageContent(result) {
|
|
257
|
+
const frag = document.createDocumentFragment();
|
|
258
|
+
frag.appendChild(document.createComment(PAGE_START_MARKER));
|
|
259
|
+
if (result && typeof result === 'object' && result.nodeType) {
|
|
260
|
+
frag.appendChild(result);
|
|
261
|
+
}
|
|
262
|
+
else if (typeof result === 'string') {
|
|
263
|
+
frag.appendChild(document.createTextNode(result));
|
|
264
|
+
}
|
|
265
|
+
frag.appendChild(document.createComment(PAGE_END_MARKER));
|
|
266
|
+
return frag;
|
|
267
|
+
}
|
|
268
|
+
async function renderPageOnly(router, match) {
|
|
269
|
+
const pageNode = findPageNodeOrFailed(match);
|
|
270
|
+
if (!pageNode || !pageNode.page)
|
|
271
|
+
return undefined;
|
|
272
|
+
const tempRoot = document.createDocumentFragment();
|
|
273
|
+
const walker = createHydrateWalker(tempRoot, []);
|
|
274
|
+
const pageProps = { params: match.params, ...pageNode.props };
|
|
275
|
+
const result = await runInBlockWindow(() => pageNode.page(pageProps, new Map(), walker));
|
|
276
|
+
if (result && result.nodeType === 1 && pageNode._pageName && router.__componentInstances) {
|
|
277
|
+
const list = router.__componentInstances.get(pageNode._pageName);
|
|
278
|
+
if (list && list.length > 0)
|
|
279
|
+
list[list.length - 1].root = result;
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Runs a render call with an active root block that stays active across
|
|
285
|
+
* `await` suspensions. Async components suspend at `await useFetch(...)` and
|
|
286
|
+
* resume in a microtask continuation; without a persistent active block every
|
|
287
|
+
* `track()`/`effect()` created after the await would be orphaned (a null owner
|
|
288
|
+
* block) and its updates silently dropped by `schedule_update`.
|
|
289
|
+
*
|
|
290
|
+
* Fully synchronous renders return synchronously (so `navigate()` keeps its
|
|
291
|
+
* synchronous behavior for plain pages); only thenable results suspend.
|
|
292
|
+
*/
|
|
293
|
+
function runInBlockWindow(fn) {
|
|
294
|
+
const previous = scope();
|
|
295
|
+
const block = root(() => { });
|
|
296
|
+
set_active_block(block);
|
|
297
|
+
let result;
|
|
298
|
+
try {
|
|
299
|
+
result = fn();
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
set_active_block(previous);
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
if (result && typeof result.then === 'function') {
|
|
306
|
+
return result.then((value) => { set_active_block(previous); return value; }, (error) => { set_active_block(previous); throw error; });
|
|
307
|
+
}
|
|
308
|
+
set_active_block(previous);
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
function replacePageContent(container, newContent) {
|
|
312
|
+
if (newContent === undefined || newContent === null)
|
|
313
|
+
return false;
|
|
314
|
+
let start = null;
|
|
315
|
+
let end = null;
|
|
316
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_COMMENT);
|
|
317
|
+
while (walker.nextNode()) {
|
|
318
|
+
const n = walker.currentNode;
|
|
319
|
+
if (!start && n.nodeValue === PAGE_START_MARKER)
|
|
320
|
+
start = n;
|
|
321
|
+
else if (start && n.nodeValue === PAGE_END_MARKER) {
|
|
322
|
+
end = n;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (!start || !end || !end.parentNode)
|
|
327
|
+
return false;
|
|
328
|
+
let cur = start.nextSibling;
|
|
329
|
+
while (cur && cur !== end) {
|
|
330
|
+
const next = cur.nextSibling;
|
|
331
|
+
cur.remove();
|
|
332
|
+
cur = next;
|
|
333
|
+
}
|
|
334
|
+
if (typeof newContent === 'string') {
|
|
335
|
+
end.parentNode.insertBefore(document.createTextNode(newContent), end);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
end.parentNode.insertBefore(newContent, end);
|
|
339
|
+
}
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
function renderMatch(router, match, container) {
|
|
343
|
+
const chain = match.matchChain;
|
|
344
|
+
const paramValues = match.params;
|
|
345
|
+
let pageNode = findPageNodeOrFailed(match);
|
|
346
|
+
const mountNotFound = (nfDom) => {
|
|
347
|
+
if (nfDom && typeof nfDom === 'object' && nfDom.nodeType) {
|
|
348
|
+
if (container.replaceChildren)
|
|
349
|
+
container.replaceChildren(nfDom);
|
|
350
|
+
else {
|
|
351
|
+
container.innerHTML = '';
|
|
352
|
+
container.appendChild(nfDom);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
else if (typeof nfDom === 'string') {
|
|
356
|
+
container.innerHTML = nfDom;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
if (!pageNode) {
|
|
360
|
+
const notFoundFn = findNotFoundComponent(chain);
|
|
361
|
+
if (notFoundFn) {
|
|
362
|
+
const tempRoot = document.createDocumentFragment();
|
|
363
|
+
const walker = createHydrateWalker(tempRoot, []);
|
|
364
|
+
const nfProps = { params: paramValues, url: match.pathname || window.location.pathname };
|
|
365
|
+
const nfDom = runInBlockWindow(() => notFoundFn(nfProps, new Map(), walker));
|
|
366
|
+
if (nfDom && typeof nfDom.then === 'function') {
|
|
367
|
+
return nfDom.then(mountNotFound);
|
|
368
|
+
}
|
|
369
|
+
mountNotFound(nfDom);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (container.replaceChildren) {
|
|
373
|
+
container.replaceChildren();
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
container.innerHTML = '';
|
|
377
|
+
}
|
|
378
|
+
container.innerHTML = '<h1>404 — Not Found</h1>';
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
// The route matched and has a page name, but its component chunk failed
|
|
382
|
+
// to load or compile. Render the route's error boundary (or a generic
|
|
383
|
+
// error page) without touching any other route's components.
|
|
384
|
+
if (!pageNode.page && pageNode._pageName) {
|
|
385
|
+
const chunkErr = chunkErrorForNode(pageNode);
|
|
386
|
+
return renderErrorPage(router, match, container, chunkErr || new Error(`Component "${pageNode._pageName}" is unavailable`));
|
|
387
|
+
}
|
|
388
|
+
const layoutNodes = chain.filter(n => n.layout);
|
|
389
|
+
const tempRoot = document.createDocumentFragment();
|
|
390
|
+
const clientWalker = createHydrateWalker(tempRoot, []);
|
|
391
|
+
// Renders the layout+page chain. Returns the DOM node synchronously when
|
|
392
|
+
// every component is synchronous; returns a Promise when any component
|
|
393
|
+
// suspends (async page / `await useFetch`). Sync throws propagate to the
|
|
394
|
+
// caller so `navigate()` keeps throwing synchronously for plain pages.
|
|
395
|
+
function renderLayoutChain(index) {
|
|
396
|
+
return renderLayoutWith(index, pageStep);
|
|
397
|
+
}
|
|
398
|
+
// Renders the chain of `layoutNodes` from `index` down, calling `renderPage`
|
|
399
|
+
// at the innermost step. The real page step and the error step (see
|
|
400
|
+
// `renderErrorInLayout`) both go through here so an error page keeps the
|
|
401
|
+
// layout/nav instead of replacing the whole container.
|
|
402
|
+
function renderLayoutWith(index, renderPage) {
|
|
403
|
+
if (index >= layoutNodes.length) {
|
|
404
|
+
return renderPage();
|
|
405
|
+
}
|
|
406
|
+
const node = layoutNodes[index];
|
|
407
|
+
const childDom = renderLayoutWith(index + 1, renderPage);
|
|
408
|
+
if (childDom && typeof childDom.then === 'function') {
|
|
409
|
+
return childDom.then((resolved) => {
|
|
410
|
+
const layoutProps = { children: resolved, params: paramValues };
|
|
411
|
+
const result = node.layout(layoutProps, new Map(), clientWalker);
|
|
412
|
+
if (result && typeof result.then === 'function') {
|
|
413
|
+
return result.then((res) => {
|
|
414
|
+
storeLayoutInstance(router, node, layoutProps, res);
|
|
415
|
+
return res;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
storeLayoutInstance(router, node, layoutProps, result);
|
|
419
|
+
return result;
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
const layoutProps = { children: childDom, params: paramValues };
|
|
423
|
+
const result = node.layout(layoutProps, new Map(), clientWalker);
|
|
424
|
+
if (result && typeof result.then === 'function') {
|
|
425
|
+
return result.then((res) => {
|
|
426
|
+
storeLayoutInstance(router, node, layoutProps, res);
|
|
427
|
+
return res;
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
storeLayoutInstance(router, node, layoutProps, result);
|
|
431
|
+
return result;
|
|
432
|
+
}
|
|
433
|
+
// The real page step: sets router state and invokes the page component.
|
|
434
|
+
const pageStep = () => {
|
|
435
|
+
set(_state.params, paramValues);
|
|
436
|
+
set(_state.path, match.pathname || window.location.pathname);
|
|
437
|
+
set(_state.search, window.location.search || '');
|
|
438
|
+
const pageProps = { params: paramValues, ...pageNode.props };
|
|
439
|
+
const result = pageNode.page(pageProps, new Map(), clientWalker);
|
|
440
|
+
if (result && typeof result.then === 'function') {
|
|
441
|
+
return result.then((res) => {
|
|
442
|
+
storePageInstance(router, pageNode, pageProps, res);
|
|
443
|
+
return wrapPageContent(res);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
storePageInstance(router, pageNode, pageProps, result);
|
|
447
|
+
return wrapPageContent(result);
|
|
448
|
+
};
|
|
449
|
+
// Renders the route's error component inside the layout chain so the error
|
|
450
|
+
// page keeps the site nav. Throws when no error component exists.
|
|
451
|
+
function renderErrorInLayout(error) {
|
|
452
|
+
const errorFn = findErrorComponent(chain);
|
|
453
|
+
if (!errorFn)
|
|
454
|
+
throw error;
|
|
455
|
+
const retry = () => {
|
|
456
|
+
if (router && router.navigate) {
|
|
457
|
+
router.navigate(window.location.pathname, { replace: true });
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
const errorProps = { error, retry, params: paramValues, statusCode: error?.statusCode ?? 500, stack: error instanceof Error ? error.stack : String(error), url: typeof window !== 'undefined' ? window.location.pathname : '' };
|
|
461
|
+
return runInBlockWindow(() => renderLayoutWith(0, () => {
|
|
462
|
+
const result = errorFn(errorProps, new Map(), clientWalker);
|
|
463
|
+
if (result && typeof result.then === 'function') {
|
|
464
|
+
return result.then((res) => wrapPageContent(res));
|
|
465
|
+
}
|
|
466
|
+
return wrapPageContent(result);
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
// Renders the error component into the container directly (used only when
|
|
470
|
+
// re-rendering through the layout also throws, e.g. the layout is the
|
|
471
|
+
// component that failed).
|
|
472
|
+
function mountStandaloneError(error) {
|
|
473
|
+
const errorFn = findErrorComponent(chain);
|
|
474
|
+
if (!errorFn)
|
|
475
|
+
throw error;
|
|
476
|
+
const retry = () => {
|
|
477
|
+
if (router && router.navigate) {
|
|
478
|
+
router.navigate(window.location.pathname, { replace: true });
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
const errorProps = { error, retry, params: paramValues, statusCode: error?.statusCode ?? 500, stack: error instanceof Error ? error.stack : String(error), url: typeof window !== 'undefined' ? window.location.pathname : '' };
|
|
482
|
+
const errDom = runInBlockWindow(() => errorFn(errorProps, new Map(), clientWalker));
|
|
483
|
+
if (errDom && typeof errDom.then === 'function') {
|
|
484
|
+
errDom.then(mountDom);
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
mountDom(errDom);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function storePageInstance(router, pageNode, pageProps, result) {
|
|
491
|
+
if (!router || !pageNode._pageName || !result || result.nodeType !== 1)
|
|
492
|
+
return;
|
|
493
|
+
if (!router.__componentInstances)
|
|
494
|
+
router.__componentInstances = new Map();
|
|
495
|
+
const name = pageNode._pageName;
|
|
496
|
+
if (!router.__componentInstances.has(name))
|
|
497
|
+
router.__componentInstances.set(name, []);
|
|
498
|
+
router.__componentInstances.get(name).push({ root: result, props: pageProps, node: pageNode, type: 'page' });
|
|
499
|
+
}
|
|
500
|
+
function storeLayoutInstance(router, node, layoutProps, result) {
|
|
501
|
+
if (!router || !node._layoutName || !result || result.nodeType !== 1)
|
|
502
|
+
return;
|
|
503
|
+
if (!router.__componentInstances)
|
|
504
|
+
router.__componentInstances = new Map();
|
|
505
|
+
const name = node._layoutName;
|
|
506
|
+
if (!router.__componentInstances.has(name))
|
|
507
|
+
router.__componentInstances.set(name, []);
|
|
508
|
+
router.__componentInstances.get(name).push({ root: result, props: layoutProps, node, type: 'layout' });
|
|
509
|
+
}
|
|
510
|
+
const mountDom = (dom) => {
|
|
511
|
+
if (dom && typeof dom === 'object' && dom.nodeType) {
|
|
512
|
+
if (container.replaceChildren)
|
|
513
|
+
container.replaceChildren(dom);
|
|
514
|
+
else {
|
|
515
|
+
container.innerHTML = '';
|
|
516
|
+
container.appendChild(dom);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
else if (typeof dom === 'string') {
|
|
520
|
+
container.innerHTML = dom;
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
let rootDom;
|
|
524
|
+
try {
|
|
525
|
+
rootDom = runInBlockWindow(() => renderLayoutChain(0));
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
if (error && error.name === 'NotFoundError') {
|
|
529
|
+
return renderNotFound(router, match, container);
|
|
530
|
+
}
|
|
531
|
+
let errDom;
|
|
532
|
+
try {
|
|
533
|
+
errDom = renderErrorInLayout(error);
|
|
534
|
+
}
|
|
535
|
+
catch (err2) {
|
|
536
|
+
mountStandaloneError(err2);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (errDom && typeof errDom.then === 'function') {
|
|
540
|
+
return errDom.then(mountDom, (e) => { mountStandaloneError(e); });
|
|
541
|
+
}
|
|
542
|
+
mountDom(errDom);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
if (rootDom && typeof rootDom.then === 'function') {
|
|
546
|
+
return rootDom.then((resolved) => { mountDom(resolved); }, (error) => {
|
|
547
|
+
if (error && error.name === 'NotFoundError') {
|
|
548
|
+
return renderNotFound(router, match, container);
|
|
549
|
+
}
|
|
550
|
+
return Promise.resolve(renderErrorInLayout(error)).then(mountDom, (e) => { mountStandaloneError(e); });
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
mountDom(rootDom);
|
|
554
|
+
}
|
|
555
|
+
// Constructs the error object used when the server already rendered an error
|
|
556
|
+
// page for this route (the DOM carries the `vesk-ssr-error` marker). The
|
|
557
|
+
// marker optionally carries the URI-encoded server error message so the
|
|
558
|
+
// client-side error boundary can render the real reason instead of a generic
|
|
559
|
+
// one.
|
|
560
|
+
function makeSsrError(message) {
|
|
561
|
+
const e = new Error(message || 'Internal Server Error');
|
|
562
|
+
e.statusCode = 500;
|
|
563
|
+
return e;
|
|
564
|
+
}
|
|
565
|
+
// Client-side render of the route's error component (used after a hydration
|
|
566
|
+
// failure or when the SSR output is already an error page). The error
|
|
567
|
+
// component renders in place of the page inside the layout chain so the error
|
|
568
|
+
// page keeps the site nav.
|
|
569
|
+
async function renderErrorPage(router, match, container, error) {
|
|
570
|
+
const chain = match.matchChain;
|
|
571
|
+
const paramValues = match.params;
|
|
572
|
+
const errorFn = findErrorComponent(chain);
|
|
573
|
+
if (!errorFn) {
|
|
574
|
+
if (container.replaceChildren)
|
|
575
|
+
container.replaceChildren();
|
|
576
|
+
else
|
|
577
|
+
container.innerHTML = '';
|
|
578
|
+
container.innerHTML = '<h1>500 — Internal Server Error</h1>';
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const layoutNodes = chain.filter(n => n.layout);
|
|
582
|
+
const tempRoot = document.createDocumentFragment();
|
|
583
|
+
const walker = createHydrateWalker(tempRoot, []);
|
|
584
|
+
const retry = () => {
|
|
585
|
+
if (router && router.navigate) {
|
|
586
|
+
router.navigate(window.location.pathname, { replace: true });
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
const errorProps = { error, retry, params: paramValues, statusCode: error?.statusCode ?? 500, stack: error instanceof Error ? error.stack : String(error), url: typeof window !== 'undefined' ? window.location.pathname : '' };
|
|
590
|
+
const renderErrorChain = (index) => {
|
|
591
|
+
if (index >= layoutNodes.length) {
|
|
592
|
+
const result = errorFn(errorProps, new Map(), walker);
|
|
593
|
+
if (result && typeof result.then === 'function') {
|
|
594
|
+
return result.then((res) => wrapPageContent(res));
|
|
595
|
+
}
|
|
596
|
+
return wrapPageContent(result);
|
|
597
|
+
}
|
|
598
|
+
const node = layoutNodes[index];
|
|
599
|
+
const childDom = renderErrorChain(index + 1);
|
|
600
|
+
if (childDom && typeof childDom.then === 'function') {
|
|
601
|
+
return childDom.then((resolved) => {
|
|
602
|
+
const layoutProps = { children: resolved, params: paramValues };
|
|
603
|
+
const result = node.layout(layoutProps, new Map(), walker);
|
|
604
|
+
if (result && typeof result.then === 'function') {
|
|
605
|
+
return result.then((res) => res);
|
|
606
|
+
}
|
|
607
|
+
return result;
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
const layoutProps = { children: childDom, params: paramValues };
|
|
611
|
+
const result = node.layout(layoutProps, new Map(), walker);
|
|
612
|
+
if (result && typeof result.then === 'function') {
|
|
613
|
+
return result.then((res) => res);
|
|
614
|
+
}
|
|
615
|
+
return result;
|
|
616
|
+
};
|
|
617
|
+
const dom = await runInBlockWindow(() => renderErrorChain(0));
|
|
618
|
+
if (dom && typeof dom === 'object' && dom.nodeType) {
|
|
619
|
+
if (container.replaceChildren)
|
|
620
|
+
container.replaceChildren(dom);
|
|
621
|
+
else {
|
|
622
|
+
container.innerHTML = '';
|
|
623
|
+
container.appendChild(dom);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
else if (typeof dom === 'string') {
|
|
627
|
+
container.innerHTML = dom;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
async function hydrateInitial(router, match, container, strategy) {
|
|
631
|
+
const chain = match.matchChain;
|
|
632
|
+
const paramValues = match.params;
|
|
633
|
+
let pageNode = findPageNodeOrFailed(match);
|
|
634
|
+
if (!pageNode) {
|
|
635
|
+
const notFoundFn = findNotFoundComponent(chain);
|
|
636
|
+
if (notFoundFn) {
|
|
637
|
+
const nfProps = { params: paramValues, url: match.pathname || window.location.pathname };
|
|
638
|
+
const walker2 = createHydrateWalker(container);
|
|
639
|
+
root(() => {
|
|
640
|
+
notFoundFn(nfProps, new Map(), walker2);
|
|
641
|
+
});
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
container.innerHTML = '<h1>404 — Not Found</h1>';
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
// When SSR rendered an error page for this route (the page component threw
|
|
648
|
+
// on the server) the DOM holds error-component markup, not page markup.
|
|
649
|
+
// Rendering the error component client-side instead of hydrating the page
|
|
650
|
+
// against mismatched markers keeps the initial load from exploding.
|
|
651
|
+
const ssrErrorMarker = (() => {
|
|
652
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_COMMENT);
|
|
653
|
+
while (walker.nextNode()) {
|
|
654
|
+
const v = walker.currentNode.nodeValue;
|
|
655
|
+
if (v && v.startsWith('vesk-ssr-error')) {
|
|
656
|
+
return v.slice('vesk-ssr-error'.length);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return null;
|
|
660
|
+
})();
|
|
661
|
+
set(_state.params, paramValues);
|
|
662
|
+
set(_state.path, match.pathname || window.location.pathname);
|
|
663
|
+
set(_state.search, window.location.search || '');
|
|
664
|
+
if (ssrErrorMarker !== null) {
|
|
665
|
+
let message = 'Internal Server Error';
|
|
666
|
+
if (ssrErrorMarker.startsWith(':')) {
|
|
667
|
+
try {
|
|
668
|
+
message = decodeURIComponent(ssrErrorMarker.slice(1));
|
|
669
|
+
}
|
|
670
|
+
catch {
|
|
671
|
+
// malformed payload — keep the generic message
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
await renderErrorPage(router, match, container, makeSsrError(message));
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const layoutNodes = chain.filter(n => n.layout);
|
|
678
|
+
const hydrators = router.__hydrators;
|
|
679
|
+
if (!pageNode.page && !(hydrators && pageNode._pageName && hydrators[pageNode._pageName])) {
|
|
680
|
+
// The route's component chunk failed to load or compile. Render the
|
|
681
|
+
// error boundary for this route instead of hydrating nothing.
|
|
682
|
+
const chunkErr = chunkErrorForNode(pageNode);
|
|
683
|
+
await renderErrorPage(router, match, container, chunkErr || new Error(`Component "${pageNode._pageName}" is unavailable`));
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const hydPage = hydrators && pageNode._pageName
|
|
687
|
+
? (hydrators[pageNode._pageName] || pageNode.page)
|
|
688
|
+
: pageNode.page;
|
|
689
|
+
const hydLayouts = layoutNodes.map(n => {
|
|
690
|
+
if (hydrators && n._layoutName) {
|
|
691
|
+
return hydrators[n._layoutName] || n.layout;
|
|
692
|
+
}
|
|
693
|
+
return n.layout;
|
|
694
|
+
});
|
|
695
|
+
try {
|
|
696
|
+
if (layoutNodes.length === 0) {
|
|
697
|
+
const pageProps = { params: paramValues, ...pageNode.props };
|
|
698
|
+
if (!strategy || strategy === 'full') {
|
|
699
|
+
const walker = createHydrateWalker(container);
|
|
700
|
+
setIsHydrating(true);
|
|
701
|
+
await runInBlockWindow(() => hydPage(pageProps, new Map(), walker));
|
|
702
|
+
setIsHydrating(false);
|
|
703
|
+
}
|
|
704
|
+
else if (strategy === 'viewport') {
|
|
705
|
+
root(() => { hydrateViewport(container, hydPage, pageProps); });
|
|
706
|
+
}
|
|
707
|
+
else if (strategy === 'idle') {
|
|
708
|
+
root(() => { hydrateIdle(container, hydPage, pageProps); });
|
|
709
|
+
}
|
|
710
|
+
else if (strategy === 'interaction') {
|
|
711
|
+
root(() => { hydrateOnInteraction(container, hydPage, pageProps); });
|
|
712
|
+
}
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
setIsHydrating(true);
|
|
716
|
+
// Keep the hydration chain synchronous so every track()/effect() created by
|
|
717
|
+
// the layout and page hydrators runs inside the active block below. Unlike a
|
|
718
|
+
// bare root() block, runInBlockWindow keeps that block active across `await`
|
|
719
|
+
// suspensions, so an async page resuming from `await useFetch(...)` still
|
|
720
|
+
// attaches its track()/effect() blocks to the root instead of orphaning them
|
|
721
|
+
// (orphaned effects never flush — async pages would hydrate with empty lists).
|
|
722
|
+
function renderLayoutChain(index) {
|
|
723
|
+
if (index >= layoutNodes.length) {
|
|
724
|
+
return (subWalker) => {
|
|
725
|
+
if (!strategy || strategy === 'full') {
|
|
726
|
+
return hydPage({ params: paramValues, ...pageNode.props }, new Map(), subWalker);
|
|
727
|
+
}
|
|
728
|
+
else if (strategy === 'viewport') {
|
|
729
|
+
hydrateViewport(subWalker.root, hydPage, { params: paramValues, ...pageNode.props });
|
|
730
|
+
}
|
|
731
|
+
else if (strategy === 'idle') {
|
|
732
|
+
hydrateIdle(subWalker.root, hydPage, { params: paramValues, ...pageNode.props });
|
|
733
|
+
}
|
|
734
|
+
else if (strategy === 'interaction') {
|
|
735
|
+
hydrateOnInteraction(subWalker.root, hydPage, { params: paramValues, ...pageNode.props });
|
|
736
|
+
}
|
|
737
|
+
return undefined;
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const node = layoutNodes[index];
|
|
741
|
+
const hydLayout = hydLayouts[index];
|
|
742
|
+
const childHydrator = renderLayoutChain(index + 1);
|
|
743
|
+
const layoutProps = { children: childHydrator, params: paramValues };
|
|
744
|
+
return hydLayout(layoutProps, new Map(), walker);
|
|
745
|
+
}
|
|
746
|
+
const walker = createHydrateWalker(container);
|
|
747
|
+
await runInBlockWindow(() => renderLayoutChain(0));
|
|
748
|
+
setIsHydrating(false);
|
|
749
|
+
}
|
|
750
|
+
catch (error) {
|
|
751
|
+
setIsHydrating(false);
|
|
752
|
+
// Hydration failed — most often the page component throwing on the
|
|
753
|
+
// client after a clean SSR. Re-render through renderMatch so the error
|
|
754
|
+
// component replaces just the page slot and the layout/nav survives.
|
|
755
|
+
if (error && error.name === 'NotFoundError') {
|
|
756
|
+
await renderNotFound(router, match, container);
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
await renderMatch(router, match, container);
|
|
761
|
+
}
|
|
762
|
+
catch (err2) {
|
|
763
|
+
await renderErrorPage(router, match, container, err2);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
export function createRouter(routes, options = {}) {
|
|
768
|
+
const container = options.container || document.getElementById('root');
|
|
769
|
+
const prefetch = options.prefetch !== false;
|
|
770
|
+
const hydrateStrategy = options.hydrate || 'full';
|
|
771
|
+
const routeTree = Array.isArray(routes) ? routes : buildTreeFromMap(routes, options);
|
|
772
|
+
const router = {
|
|
773
|
+
routeTree,
|
|
774
|
+
container,
|
|
775
|
+
_currentMatch: null,
|
|
776
|
+
_outletPlaceholders: [],
|
|
777
|
+
_currentSegments: null,
|
|
778
|
+
_depth: 0,
|
|
779
|
+
_routeDataCache: options.routeDataCache ?? 0,
|
|
780
|
+
start() {
|
|
781
|
+
setCurrentRouter(this);
|
|
782
|
+
if (typeof window !== 'undefined' && 'scrollRestoration' in window.history) {
|
|
783
|
+
window.history.scrollRestoration = 'manual';
|
|
784
|
+
}
|
|
785
|
+
if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
|
|
786
|
+
let _scrollTimer = null;
|
|
787
|
+
window.addEventListener('scroll', () => {
|
|
788
|
+
if (_scrollTimer)
|
|
789
|
+
return;
|
|
790
|
+
_scrollTimer = setTimeout(() => {
|
|
791
|
+
if (window.scrollY !== undefined) {
|
|
792
|
+
_scrollPositions.set(window.location.pathname, window.scrollY);
|
|
793
|
+
}
|
|
794
|
+
_scrollTimer = null;
|
|
795
|
+
}, 100);
|
|
796
|
+
}, { passive: true });
|
|
797
|
+
}
|
|
798
|
+
document.addEventListener('click', (e) => {
|
|
799
|
+
const link = e.target?.nodeType === 1 ? e.target.closest('a[href]') : null;
|
|
800
|
+
if (!link)
|
|
801
|
+
return;
|
|
802
|
+
if (link.hostname && link.hostname !== window.location.hostname)
|
|
803
|
+
return;
|
|
804
|
+
const href = link.getAttribute('href');
|
|
805
|
+
if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:'))
|
|
806
|
+
return;
|
|
807
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0)
|
|
808
|
+
return;
|
|
809
|
+
e.preventDefault();
|
|
810
|
+
this.navigate(href);
|
|
811
|
+
});
|
|
812
|
+
window.addEventListener('popstate', () => {
|
|
813
|
+
setIsPopStateNavigation(true);
|
|
814
|
+
this.navigate(window.location.href, { replace: true });
|
|
815
|
+
});
|
|
816
|
+
if (prefetch) {
|
|
817
|
+
document.addEventListener('mouseenter', (e) => {
|
|
818
|
+
const link = e.target?.nodeType === 1 ? e.target.closest('a[href]') : null;
|
|
819
|
+
if (link)
|
|
820
|
+
this.prefetch(link.getAttribute('href'));
|
|
821
|
+
}, { passive: true });
|
|
822
|
+
}
|
|
823
|
+
const path = window.location.pathname + window.location.search;
|
|
824
|
+
if (container.children.length > 0) {
|
|
825
|
+
const url = new URL(path, window.location.origin);
|
|
826
|
+
const match = matchRoute(this.routeTree, url.pathname);
|
|
827
|
+
if (match) {
|
|
828
|
+
match.pathname = url.pathname;
|
|
829
|
+
hydrateInitial(this, match, container, hydrateStrategy);
|
|
830
|
+
this._currentMatch = match;
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
this.navigate(path, { replace: true });
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
this.navigate(path, { replace: true });
|
|
838
|
+
}
|
|
839
|
+
return this;
|
|
840
|
+
},
|
|
841
|
+
navigate(path, opts = {}) {
|
|
842
|
+
const url = new URL(path, window.location.origin);
|
|
843
|
+
const match = matchRoute(this.routeTree, url.pathname);
|
|
844
|
+
if (!match) {
|
|
845
|
+
window.location.href = path;
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
match.pathname = url.pathname;
|
|
849
|
+
if (!_isPopStateNavigation) {
|
|
850
|
+
_scrollPositions.set(window.location.pathname, window.scrollY);
|
|
851
|
+
}
|
|
852
|
+
const loadingFn = findLoadingComponent(match.matchChain);
|
|
853
|
+
this._navToken = (this._navToken || 0) + 1;
|
|
854
|
+
const navToken = this._navToken;
|
|
855
|
+
let firstRenderFailed = false;
|
|
856
|
+
const updateUrl = () => {
|
|
857
|
+
if (!opts.replace) {
|
|
858
|
+
window.history.pushState({ path: url.pathname }, '', url.pathname);
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
window.history.replaceState({ path: url.pathname }, '', url.pathname);
|
|
862
|
+
}
|
|
863
|
+
set(_state.path, url.pathname);
|
|
864
|
+
set(_state.search, url.search);
|
|
865
|
+
};
|
|
866
|
+
const renderContent = () => {
|
|
867
|
+
try {
|
|
868
|
+
renderMatch(this, match, this.container);
|
|
869
|
+
}
|
|
870
|
+
catch (e) {
|
|
871
|
+
firstRenderFailed = true;
|
|
872
|
+
throw e;
|
|
873
|
+
}
|
|
874
|
+
this._currentMatch = match;
|
|
875
|
+
handleScroll(url.pathname, opts.replace);
|
|
876
|
+
};
|
|
877
|
+
const doRender = () => {
|
|
878
|
+
updateUrl();
|
|
879
|
+
renderContent();
|
|
880
|
+
};
|
|
881
|
+
const fetchData = () => {
|
|
882
|
+
if (firstRenderFailed)
|
|
883
|
+
return;
|
|
884
|
+
if (!shouldFetchData(this, match))
|
|
885
|
+
return;
|
|
886
|
+
getRouteData(url.pathname + url.search, 'nav' + navToken).then(async (data) => {
|
|
887
|
+
if (navToken !== this._navToken)
|
|
888
|
+
return;
|
|
889
|
+
if (!data)
|
|
890
|
+
return;
|
|
891
|
+
if (data.redirect) {
|
|
892
|
+
this.navigate(data.redirect, { replace: true });
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (!hasRealPageData(data))
|
|
896
|
+
return;
|
|
897
|
+
await applyRouteData(this, match, data, this.container);
|
|
898
|
+
});
|
|
899
|
+
};
|
|
900
|
+
if (loadingFn) {
|
|
901
|
+
showLoadingInContainer(this.container, loadingFn, match.params);
|
|
902
|
+
Promise.resolve().then(() => { try {
|
|
903
|
+
doRender();
|
|
904
|
+
}
|
|
905
|
+
finally {
|
|
906
|
+
fetchData();
|
|
907
|
+
} });
|
|
908
|
+
}
|
|
909
|
+
else {
|
|
910
|
+
try {
|
|
911
|
+
doRender();
|
|
912
|
+
}
|
|
913
|
+
finally {
|
|
914
|
+
fetchData();
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
},
|
|
918
|
+
prefetch(path) {
|
|
919
|
+
const url = new URL(path, window.location.origin);
|
|
920
|
+
const match = matchRoute(this.routeTree, url.pathname);
|
|
921
|
+
if (!match)
|
|
922
|
+
return;
|
|
923
|
+
match.pathname = url.pathname;
|
|
924
|
+
this._prefetched = this._prefetched || new Map();
|
|
925
|
+
this._prefetched.set(url.pathname, match);
|
|
926
|
+
if (typeof document === 'undefined')
|
|
927
|
+
return;
|
|
928
|
+
getRouteData(url.pathname + url.search, 'prefetch').then((data) => {
|
|
929
|
+
if (data)
|
|
930
|
+
storePrefetchedData(match, data);
|
|
931
|
+
});
|
|
932
|
+
},
|
|
933
|
+
get currentPath() {
|
|
934
|
+
return get(_state.path);
|
|
935
|
+
},
|
|
936
|
+
hmrUpdate() {
|
|
937
|
+
const updated = globalThis.__updatedComponents;
|
|
938
|
+
if (!updated || updated.size === 0)
|
|
939
|
+
return;
|
|
940
|
+
globalThis.__updatedComponents = new Set();
|
|
941
|
+
if (typeof this.__updateComponents === 'function') {
|
|
942
|
+
this.__updateComponents(this.routeTree);
|
|
943
|
+
}
|
|
944
|
+
const instances = this.__componentInstances;
|
|
945
|
+
if (instances && instances.size > 0) {
|
|
946
|
+
let didUpdate = false;
|
|
947
|
+
for (const [name, nameInstances] of instances) {
|
|
948
|
+
if (updated.has(name)) {
|
|
949
|
+
for (const inst of nameInstances) {
|
|
950
|
+
try {
|
|
951
|
+
const isPage = inst.type === 'page';
|
|
952
|
+
const newFn = isPage ? inst.node.page : inst.node.layout;
|
|
953
|
+
if (!newFn)
|
|
954
|
+
continue;
|
|
955
|
+
const walker = createHydrateWalker(document.createDocumentFragment(), []);
|
|
956
|
+
let newDom;
|
|
957
|
+
root(() => {
|
|
958
|
+
newDom = newFn(inst.props, new Map(), walker);
|
|
959
|
+
});
|
|
960
|
+
if (newDom && newDom.nodeType === 1 && inst.root && inst.root.parentNode) {
|
|
961
|
+
inst.root.parentNode.replaceChild(newDom, inst.root);
|
|
962
|
+
inst.root = newDom;
|
|
963
|
+
didUpdate = true;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
catch (e) {
|
|
967
|
+
console.error('HMR update error:', e);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if (!didUpdate) {
|
|
973
|
+
const path = window.location.pathname + window.location.search;
|
|
974
|
+
this.navigate(path, { replace: true });
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
const path = window.location.pathname + window.location.search;
|
|
979
|
+
this.navigate(path, { replace: true });
|
|
980
|
+
}
|
|
981
|
+
},
|
|
982
|
+
};
|
|
983
|
+
return router;
|
|
984
|
+
}
|
|
985
|
+
export function createFileRouter(routeTree, options = {}) {
|
|
986
|
+
const container = options.container || document.getElementById('root');
|
|
987
|
+
const middleware = options.middleware || null;
|
|
988
|
+
const renderFn = options.render || renderMatch;
|
|
989
|
+
const hydrateStrategy = options.hydrate || 'full';
|
|
990
|
+
const router = {
|
|
991
|
+
_hydrateStrategy: hydrateStrategy,
|
|
992
|
+
routeTree,
|
|
993
|
+
container,
|
|
994
|
+
_currentMatch: null,
|
|
995
|
+
_outletPlaceholders: [],
|
|
996
|
+
_currentSegments: null,
|
|
997
|
+
_depth: 0,
|
|
998
|
+
_routeDataCache: options.routeDataCache ?? 0,
|
|
999
|
+
start() {
|
|
1000
|
+
setCurrentRouter(this);
|
|
1001
|
+
if (typeof window !== 'undefined' && 'scrollRestoration' in window.history) {
|
|
1002
|
+
window.history.scrollRestoration = 'manual';
|
|
1003
|
+
}
|
|
1004
|
+
if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
|
|
1005
|
+
let _scrollTimer = null;
|
|
1006
|
+
const _onScroll = () => {
|
|
1007
|
+
if (_scrollTimer)
|
|
1008
|
+
return;
|
|
1009
|
+
_scrollTimer = setTimeout(() => {
|
|
1010
|
+
if (window.scrollY !== undefined) {
|
|
1011
|
+
_scrollPositions.set(window.location.pathname, window.scrollY);
|
|
1012
|
+
}
|
|
1013
|
+
_scrollTimer = null;
|
|
1014
|
+
}, 100);
|
|
1015
|
+
};
|
|
1016
|
+
window.addEventListener('scroll', _onScroll, { passive: true });
|
|
1017
|
+
}
|
|
1018
|
+
window.addEventListener('popstate', () => {
|
|
1019
|
+
setIsPopStateNavigation(true);
|
|
1020
|
+
router.navigate(window.location.pathname + window.location.search, { replace: true });
|
|
1021
|
+
});
|
|
1022
|
+
if (options.prefetch !== false) {
|
|
1023
|
+
document.addEventListener('mouseenter', (e) => {
|
|
1024
|
+
const link = e.target?.nodeType === 1 ? e.target.closest('a[href]') : null;
|
|
1025
|
+
if (link)
|
|
1026
|
+
router.prefetch(link.getAttribute('href'));
|
|
1027
|
+
}, { passive: true });
|
|
1028
|
+
}
|
|
1029
|
+
const path = window.location.pathname;
|
|
1030
|
+
if (container.children.length > 0) {
|
|
1031
|
+
const match = matchRoute(routeTree, path);
|
|
1032
|
+
if (match) {
|
|
1033
|
+
match.pathname = path;
|
|
1034
|
+
hydrateInitial(router, match, container, hydrateStrategy);
|
|
1035
|
+
router._currentMatch = match;
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
router.navigate(path, { replace: true });
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
router.navigate(path, { replace: true });
|
|
1043
|
+
}
|
|
1044
|
+
return router;
|
|
1045
|
+
},
|
|
1046
|
+
navigate(pathname, opts = {}) {
|
|
1047
|
+
const url = pathname instanceof URL ? pathname : new URL(pathname, window.location.origin);
|
|
1048
|
+
const match = matchRoute(routeTree, url.pathname);
|
|
1049
|
+
if (!match) {
|
|
1050
|
+
const chain = flattenLayoutChain(routeTree, url.pathname.split('/').filter(Boolean));
|
|
1051
|
+
const notFoundFn = findNotFoundComponent(chain);
|
|
1052
|
+
if (notFoundFn) {
|
|
1053
|
+
void (async () => {
|
|
1054
|
+
const tempRoot = document.createDocumentFragment();
|
|
1055
|
+
const walker = createHydrateWalker(tempRoot, []);
|
|
1056
|
+
const nfProps = { params: {}, url: url.pathname };
|
|
1057
|
+
const nfDom = await runInBlockWindow(() => notFoundFn(nfProps, new Map(), walker));
|
|
1058
|
+
if (nfDom && typeof nfDom === 'object' && nfDom.nodeType) {
|
|
1059
|
+
if (container.replaceChildren)
|
|
1060
|
+
container.replaceChildren(nfDom);
|
|
1061
|
+
else {
|
|
1062
|
+
container.innerHTML = '';
|
|
1063
|
+
container.appendChild(nfDom);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
else if (typeof nfDom === 'string') {
|
|
1067
|
+
container.innerHTML = nfDom;
|
|
1068
|
+
}
|
|
1069
|
+
})();
|
|
1070
|
+
}
|
|
1071
|
+
else {
|
|
1072
|
+
container.innerHTML = '<h1>404 — Not Found</h1>';
|
|
1073
|
+
}
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
match.pathname = url.pathname;
|
|
1077
|
+
if (!_isPopStateNavigation) {
|
|
1078
|
+
_scrollPositions.set(window.location.pathname, window.scrollY);
|
|
1079
|
+
}
|
|
1080
|
+
const loadingFn = findLoadingComponent(match.matchChain);
|
|
1081
|
+
router._navToken = (router._navToken || 0) + 1;
|
|
1082
|
+
const navToken = router._navToken;
|
|
1083
|
+
const middlewareFns = Array.isArray(middleware) ? middleware : (middleware ? [middleware] : []);
|
|
1084
|
+
let firstRenderFailed = false;
|
|
1085
|
+
const updateUrl = () => {
|
|
1086
|
+
const fullUrl = url.pathname + url.search;
|
|
1087
|
+
if (!opts.replace) {
|
|
1088
|
+
window.history.pushState({ path: fullUrl }, '', fullUrl);
|
|
1089
|
+
}
|
|
1090
|
+
else {
|
|
1091
|
+
window.history.replaceState({ path: fullUrl }, '', fullUrl);
|
|
1092
|
+
}
|
|
1093
|
+
set(_state.path, url.pathname);
|
|
1094
|
+
set(_state.search, url.search);
|
|
1095
|
+
};
|
|
1096
|
+
const renderContent = () => {
|
|
1097
|
+
// Data isolation: route nodes are shared singletons. When the
|
|
1098
|
+
// navigation changes the path a node was last hydrated with, its
|
|
1099
|
+
// cached props must not render on the new path (a params-only or
|
|
1100
|
+
// failed data payload would otherwise merge stale foreign data
|
|
1101
|
+
// into the new page).
|
|
1102
|
+
const propsNode = findPageNode(match);
|
|
1103
|
+
if (propsNode && propsNode._dataPath !== url.pathname) {
|
|
1104
|
+
propsNode.props = undefined;
|
|
1105
|
+
}
|
|
1106
|
+
try {
|
|
1107
|
+
renderFn(router, match, container);
|
|
1108
|
+
}
|
|
1109
|
+
catch (e) {
|
|
1110
|
+
firstRenderFailed = true;
|
|
1111
|
+
throw e;
|
|
1112
|
+
}
|
|
1113
|
+
router._currentMatch = match;
|
|
1114
|
+
handleScroll(url.pathname, opts.replace);
|
|
1115
|
+
};
|
|
1116
|
+
const doRender = () => {
|
|
1117
|
+
updateUrl();
|
|
1118
|
+
renderContent();
|
|
1119
|
+
};
|
|
1120
|
+
const fetchData = () => {
|
|
1121
|
+
if (firstRenderFailed)
|
|
1122
|
+
return;
|
|
1123
|
+
if (!shouldFetchData(router, match))
|
|
1124
|
+
return;
|
|
1125
|
+
getRouteData(url.pathname + url.search, 'nav' + navToken).then(async (data) => {
|
|
1126
|
+
if (navToken !== router._navToken)
|
|
1127
|
+
return;
|
|
1128
|
+
if (!data)
|
|
1129
|
+
return;
|
|
1130
|
+
if (data.redirect) {
|
|
1131
|
+
router.navigate(data.redirect, { replace: true });
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
if (!hasRealPageData(data))
|
|
1135
|
+
return;
|
|
1136
|
+
const pending = hasPendingChunks(match.matchChain);
|
|
1137
|
+
if (pending.length > 0) {
|
|
1138
|
+
await loadChunksQuietly(pending);
|
|
1139
|
+
if (navToken !== router._navToken)
|
|
1140
|
+
return;
|
|
1141
|
+
if (typeof router.__updateComponents === 'function') {
|
|
1142
|
+
router.__updateComponents(match.matchChain);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
await applyRouteData(router, match, data, container, renderFn);
|
|
1146
|
+
});
|
|
1147
|
+
};
|
|
1148
|
+
const pendingChunks = hasPendingChunks(match.matchChain);
|
|
1149
|
+
const doRenderWithChunks = pendingChunks.length > 0
|
|
1150
|
+
? () => {
|
|
1151
|
+
updateUrl();
|
|
1152
|
+
return loadChunksQuietly(pendingChunks).then(() => {
|
|
1153
|
+
// A newer navigation (e.g. popstate back while the chunk
|
|
1154
|
+
// was still loading) may have superseded this one; don't
|
|
1155
|
+
// paint the stale route over the current URL.
|
|
1156
|
+
if (navToken !== router._navToken)
|
|
1157
|
+
return;
|
|
1158
|
+
if (typeof router.__updateComponents === 'function') {
|
|
1159
|
+
router.__updateComponents(match.matchChain);
|
|
1160
|
+
}
|
|
1161
|
+
renderContent();
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
: (() => { doRender(); });
|
|
1165
|
+
async function runMwChain(index) {
|
|
1166
|
+
if (index >= middlewareFns.length) {
|
|
1167
|
+
await doRenderWithChunks();
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
const fn = middlewareFns[index];
|
|
1171
|
+
const ctx = { url: url.pathname, params: match.params, router, locals: {} };
|
|
1172
|
+
async function next(rewrite) {
|
|
1173
|
+
if (rewrite) {
|
|
1174
|
+
match.pathname = rewrite;
|
|
1175
|
+
url.pathname = rewrite;
|
|
1176
|
+
}
|
|
1177
|
+
return runMwChain(index + 1);
|
|
1178
|
+
}
|
|
1179
|
+
try {
|
|
1180
|
+
const result = await fn(ctx, next);
|
|
1181
|
+
if (result && result.redirect) {
|
|
1182
|
+
router.navigate(result.redirect, { replace: true });
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
catch (e) {
|
|
1187
|
+
if (e && e.name === 'Redirect') {
|
|
1188
|
+
router.navigate(e.url, { replace: true });
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
if (middlewareFns.length > 0 || loadingFn) {
|
|
1194
|
+
if (loadingFn) {
|
|
1195
|
+
showLoadingInContainer(container, loadingFn, match.params);
|
|
1196
|
+
}
|
|
1197
|
+
Promise.resolve().then(() => {
|
|
1198
|
+
try {
|
|
1199
|
+
if (middlewareFns.length > 0) {
|
|
1200
|
+
runMwChain(0);
|
|
1201
|
+
}
|
|
1202
|
+
else {
|
|
1203
|
+
doRenderWithChunks();
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
finally {
|
|
1207
|
+
fetchData();
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
else {
|
|
1212
|
+
try {
|
|
1213
|
+
doRenderWithChunks();
|
|
1214
|
+
}
|
|
1215
|
+
finally {
|
|
1216
|
+
fetchData();
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
},
|
|
1220
|
+
prefetch(path) {
|
|
1221
|
+
const url = new URL(path, window.location.origin);
|
|
1222
|
+
const match = matchRoute(routeTree, url.pathname);
|
|
1223
|
+
if (!match)
|
|
1224
|
+
return;
|
|
1225
|
+
match.pathname = url.pathname;
|
|
1226
|
+
router._prefetched = router._prefetched || new Map();
|
|
1227
|
+
router._prefetched.set(url.pathname, match);
|
|
1228
|
+
const preloadUrls = hasPendingChunks(match.matchChain);
|
|
1229
|
+
preloadUrls.forEach(ensureChunk);
|
|
1230
|
+
if (typeof document === 'undefined')
|
|
1231
|
+
return;
|
|
1232
|
+
getRouteData(url.pathname + url.search, 'prefetch').then((data) => {
|
|
1233
|
+
if (data)
|
|
1234
|
+
storePrefetchedData(match, data);
|
|
1235
|
+
});
|
|
1236
|
+
},
|
|
1237
|
+
get currentPath() {
|
|
1238
|
+
return get(_state.path);
|
|
1239
|
+
},
|
|
1240
|
+
hmrUpdate() {
|
|
1241
|
+
const updated = globalThis.__updatedComponents;
|
|
1242
|
+
if (!updated || updated.size === 0)
|
|
1243
|
+
return;
|
|
1244
|
+
globalThis.__updatedComponents = new Set();
|
|
1245
|
+
if (typeof router.__updateComponents === 'function') {
|
|
1246
|
+
router.__updateComponents(router.routeTree);
|
|
1247
|
+
}
|
|
1248
|
+
const instances = router.__componentInstances;
|
|
1249
|
+
if (instances && instances.size > 0) {
|
|
1250
|
+
let didUpdate = false;
|
|
1251
|
+
for (const [name, nameInstances] of instances) {
|
|
1252
|
+
if (updated.has(name)) {
|
|
1253
|
+
for (const inst of nameInstances) {
|
|
1254
|
+
try {
|
|
1255
|
+
const isPage = inst.type === 'page';
|
|
1256
|
+
const newFn = isPage ? inst.node.page : inst.node.layout;
|
|
1257
|
+
if (!newFn)
|
|
1258
|
+
continue;
|
|
1259
|
+
const walker = createHydrateWalker(document.createDocumentFragment(), []);
|
|
1260
|
+
let newDom;
|
|
1261
|
+
root(() => {
|
|
1262
|
+
newDom = newFn(inst.props, new Map(), walker);
|
|
1263
|
+
});
|
|
1264
|
+
if (newDom && newDom.nodeType === 1 && inst.root && inst.root.parentNode) {
|
|
1265
|
+
inst.root.parentNode.replaceChild(newDom, inst.root);
|
|
1266
|
+
inst.root = newDom;
|
|
1267
|
+
didUpdate = true;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
catch (e) {
|
|
1271
|
+
console.error('HMR update error:', e);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
if (!didUpdate) {
|
|
1277
|
+
const path = window.location.pathname + window.location.search;
|
|
1278
|
+
router.navigate(path, { replace: true });
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
const path = window.location.pathname + window.location.search;
|
|
1283
|
+
router.navigate(path, { replace: true });
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
};
|
|
1287
|
+
return router;
|
|
1288
|
+
}
|
|
1289
|
+
export function defineRoute(path, config) {
|
|
1290
|
+
return { path, ...config };
|
|
1291
|
+
}
|
|
1292
|
+
export function buildRouteTree(definitions) {
|
|
1293
|
+
const tree = [];
|
|
1294
|
+
for (const def of definitions) {
|
|
1295
|
+
const parts = (def.path || '').split('/').filter(Boolean);
|
|
1296
|
+
const isDynamic = parts.some(p => p.startsWith(':'));
|
|
1297
|
+
const isCatchAll = parts.some(p => p === '*');
|
|
1298
|
+
const node = {
|
|
1299
|
+
path: parts[parts.length - 1] || '',
|
|
1300
|
+
fullPath: def.fullPath || def.path,
|
|
1301
|
+
isGroup: false,
|
|
1302
|
+
isDynamic,
|
|
1303
|
+
isCatchAll,
|
|
1304
|
+
page: def.page || null,
|
|
1305
|
+
layout: def.layout || null,
|
|
1306
|
+
loading: def.loading || null,
|
|
1307
|
+
error: def.error || null,
|
|
1308
|
+
notFound: def.notFound || null,
|
|
1309
|
+
children: (def.children || []).map(c => {
|
|
1310
|
+
const cParts = (c.path || '').split('/').filter(Boolean);
|
|
1311
|
+
return {
|
|
1312
|
+
...c,
|
|
1313
|
+
path: cParts[cParts.length - 1] || '',
|
|
1314
|
+
fullPath: ((def.path || '') + (c.path ? '/' + c.path : '')).replace(/\/+/g, '/'),
|
|
1315
|
+
isDynamic: cParts.some(p => p.startsWith(':')),
|
|
1316
|
+
isCatchAll: cParts.some(p => p === '*'),
|
|
1317
|
+
isGroup: false,
|
|
1318
|
+
loading: null,
|
|
1319
|
+
error: null,
|
|
1320
|
+
notFound: null,
|
|
1321
|
+
segmentCount: Math.max(1, cParts.length),
|
|
1322
|
+
children: [],
|
|
1323
|
+
};
|
|
1324
|
+
}),
|
|
1325
|
+
segmentCount: Math.max(1, parts.length),
|
|
1326
|
+
};
|
|
1327
|
+
tree.push(node);
|
|
1328
|
+
}
|
|
1329
|
+
return tree;
|
|
1330
|
+
}
|