@richie-router/react 0.1.1 → 0.1.3
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/dist/cjs/router.cjs +288 -96
- package/dist/cjs/router.test.cjs +413 -0
- package/dist/esm/router.mjs +288 -96
- package/dist/esm/router.test.mjs +418 -0
- package/dist/types/router.d.ts +28 -19
- package/package.json +2 -2
package/dist/esm/router.mjs
CHANGED
|
@@ -26,13 +26,68 @@ var RouterStateContext = React.createContext(null);
|
|
|
26
26
|
var OutletContext = React.createContext(null);
|
|
27
27
|
var MatchContext = React.createContext(null);
|
|
28
28
|
var MANAGED_HEAD_ATTRIBUTE = "data-richie-router-head";
|
|
29
|
-
var EMPTY_HEAD =
|
|
30
|
-
function
|
|
31
|
-
return
|
|
29
|
+
var EMPTY_HEAD = [];
|
|
30
|
+
function ensureLeadingSlash(value) {
|
|
31
|
+
return value.startsWith("/") ? value : `/${value}`;
|
|
32
|
+
}
|
|
33
|
+
function normalizeBasePath(basePath) {
|
|
34
|
+
if (!basePath) {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
const trimmed = basePath.trim();
|
|
38
|
+
if (trimmed === "" || trimmed === "/") {
|
|
39
|
+
return "";
|
|
40
|
+
}
|
|
41
|
+
const normalized = ensureLeadingSlash(trimmed).replace(/\/+$/u, "");
|
|
42
|
+
return normalized === "/" ? "" : normalized;
|
|
43
|
+
}
|
|
44
|
+
function parseHref(href) {
|
|
45
|
+
if (href.startsWith("http://") || href.startsWith("https://")) {
|
|
46
|
+
return new URL(href);
|
|
47
|
+
}
|
|
48
|
+
return new URL(ensureLeadingSlash(href), "http://richie-router.local");
|
|
49
|
+
}
|
|
50
|
+
function stripBasePathFromPathname(pathname, basePath) {
|
|
51
|
+
if (!basePath) {
|
|
52
|
+
return pathname;
|
|
53
|
+
}
|
|
54
|
+
if (pathname === basePath) {
|
|
55
|
+
return "/";
|
|
56
|
+
}
|
|
57
|
+
return pathname.startsWith(`${basePath}/`) ? pathname.slice(basePath.length) || "/" : pathname;
|
|
58
|
+
}
|
|
59
|
+
function stripBasePathFromHref(href, basePath) {
|
|
60
|
+
const normalizedBasePath = normalizeBasePath(basePath);
|
|
61
|
+
if (!normalizedBasePath) {
|
|
62
|
+
return href;
|
|
63
|
+
}
|
|
64
|
+
const url = parseHref(href);
|
|
65
|
+
return `${stripBasePathFromPathname(url.pathname, normalizedBasePath)}${url.search}${url.hash}`;
|
|
66
|
+
}
|
|
67
|
+
function prependBasePathToPathname(pathname, basePath) {
|
|
68
|
+
if (!basePath) {
|
|
69
|
+
return pathname;
|
|
70
|
+
}
|
|
71
|
+
return pathname === "/" ? basePath : `${basePath}${ensureLeadingSlash(pathname)}`;
|
|
72
|
+
}
|
|
73
|
+
function prependBasePathToHref(href, basePath) {
|
|
74
|
+
const normalizedBasePath = normalizeBasePath(basePath);
|
|
75
|
+
if (!normalizedBasePath) {
|
|
76
|
+
return href;
|
|
77
|
+
}
|
|
78
|
+
const url = parseHref(href);
|
|
79
|
+
return `${prependBasePathToPathname(url.pathname, normalizedBasePath)}${url.search}${url.hash}`;
|
|
32
80
|
}
|
|
33
81
|
function routeHasRecord(value) {
|
|
34
82
|
return typeof value === "object" && value !== null;
|
|
35
83
|
}
|
|
84
|
+
function routeHasInlineHead(route) {
|
|
85
|
+
const headOption = route.options.head;
|
|
86
|
+
return Boolean(headOption && typeof headOption !== "string");
|
|
87
|
+
}
|
|
88
|
+
function matchesHaveInlineHead(matches) {
|
|
89
|
+
return matches.some((match) => routeHasInlineHead(match.route));
|
|
90
|
+
}
|
|
36
91
|
function resolveParamsInput(input, previous) {
|
|
37
92
|
if (input === undefined) {
|
|
38
93
|
return previous;
|
|
@@ -78,6 +133,8 @@ class Router {
|
|
|
78
133
|
headCache = new Map;
|
|
79
134
|
parseSearch;
|
|
80
135
|
stringifySearch;
|
|
136
|
+
basePath;
|
|
137
|
+
initialHeadSnapshot;
|
|
81
138
|
started = false;
|
|
82
139
|
unsubscribeHistory;
|
|
83
140
|
constructor(options) {
|
|
@@ -86,6 +143,7 @@ class Router {
|
|
|
86
143
|
this.history = options.history ?? (typeof window === "undefined" ? createMemoryHistory() : createBrowserHistory());
|
|
87
144
|
this.parseSearch = options.parseSearch ?? defaultParseSearch;
|
|
88
145
|
this.stringifySearch = options.stringifySearch ?? defaultStringifySearch;
|
|
146
|
+
this.basePath = normalizeBasePath(options.basePath);
|
|
89
147
|
for (const route of collectRoutes(this.routeTree)) {
|
|
90
148
|
this.routesByFullPath.set(route.fullPath, route);
|
|
91
149
|
}
|
|
@@ -93,15 +151,22 @@ class Router {
|
|
|
93
151
|
this.routesByTo.set(branch.leaf.to, branch.leaf);
|
|
94
152
|
}
|
|
95
153
|
const location = this.readLocation();
|
|
154
|
+
const initialMatches = this.buildMatches(location);
|
|
155
|
+
const rawHistoryHref = this.history.location.href;
|
|
96
156
|
const initialHeadSnapshot = typeof window !== "undefined" ? window.__RICHIE_ROUTER_HEAD__ : undefined;
|
|
97
|
-
const
|
|
157
|
+
const hasMatchingInitialHeadSnapshot = Boolean(initialHeadSnapshot && (initialHeadSnapshot.href === location.href || initialHeadSnapshot.href === rawHistoryHref));
|
|
158
|
+
const initialHead = hasMatchingInitialHeadSnapshot && initialHeadSnapshot ? initialHeadSnapshot.head : EMPTY_HEAD;
|
|
159
|
+
if (hasMatchingInitialHeadSnapshot && this.options.loadRouteHead === undefined && initialHeadSnapshot?.routeHeads !== undefined) {
|
|
160
|
+
this.seedHeadCacheFromRouteHeads(initialMatches, initialHeadSnapshot.routeHeads);
|
|
161
|
+
}
|
|
98
162
|
if (typeof window !== "undefined" && initialHeadSnapshot !== undefined) {
|
|
99
163
|
delete window.__RICHIE_ROUTER_HEAD__;
|
|
100
164
|
}
|
|
165
|
+
this.initialHeadSnapshot = hasMatchingInitialHeadSnapshot ? initialHeadSnapshot : undefined;
|
|
101
166
|
this.state = {
|
|
102
167
|
status: "loading",
|
|
103
168
|
location,
|
|
104
|
-
matches:
|
|
169
|
+
matches: initialMatches,
|
|
105
170
|
head: initialHead,
|
|
106
171
|
error: null
|
|
107
172
|
};
|
|
@@ -132,15 +197,17 @@ class Router {
|
|
|
132
197
|
}
|
|
133
198
|
async load(options) {
|
|
134
199
|
const nextLocation = this.readLocation();
|
|
200
|
+
const initialHeadSnapshot = this.initialHeadSnapshot?.href === nextLocation.href ? this.initialHeadSnapshot : undefined;
|
|
201
|
+
this.initialHeadSnapshot = undefined;
|
|
135
202
|
await this.commitLocation(nextLocation, {
|
|
136
203
|
request: options?.request,
|
|
137
204
|
replace: true,
|
|
138
|
-
writeHistory: false
|
|
205
|
+
writeHistory: false,
|
|
206
|
+
initialHeadSnapshot
|
|
139
207
|
});
|
|
140
208
|
}
|
|
141
209
|
async navigate(options) {
|
|
142
|
-
const
|
|
143
|
-
const location = createParsedLocation(href, options.state ?? null, this.parseSearch);
|
|
210
|
+
const location = this.buildLocation(options);
|
|
144
211
|
await this.commitLocation(location, {
|
|
145
212
|
replace: options.replace ?? false,
|
|
146
213
|
writeHistory: true,
|
|
@@ -148,8 +215,7 @@ class Router {
|
|
|
148
215
|
});
|
|
149
216
|
}
|
|
150
217
|
async preloadRoute(options) {
|
|
151
|
-
const
|
|
152
|
-
const location = createParsedLocation(href, options.state ?? null, this.parseSearch);
|
|
218
|
+
const location = this.buildLocation(options);
|
|
153
219
|
try {
|
|
154
220
|
await this.resolveLocation(location);
|
|
155
221
|
} catch {}
|
|
@@ -159,6 +225,12 @@ class Router {
|
|
|
159
225
|
await this.load();
|
|
160
226
|
}
|
|
161
227
|
buildHref(options) {
|
|
228
|
+
return prependBasePathToHref(this.buildLocationHref(options), this.basePath);
|
|
229
|
+
}
|
|
230
|
+
buildLocation(options) {
|
|
231
|
+
return createParsedLocation(this.buildLocationHref(options), options.state ?? null, this.parseSearch);
|
|
232
|
+
}
|
|
233
|
+
buildLocationHref(options) {
|
|
162
234
|
const targetRoute = this.routesByTo.get(options.to) ?? null;
|
|
163
235
|
const fromMatch = options.from ? this.findMatchByTo(options.from) : null;
|
|
164
236
|
const previousParams = fromMatch?.params ?? {};
|
|
@@ -173,7 +245,7 @@ class Router {
|
|
|
173
245
|
}
|
|
174
246
|
readLocation() {
|
|
175
247
|
const location = this.history.location;
|
|
176
|
-
return createParsedLocation(location.href, location.state, this.parseSearch);
|
|
248
|
+
return createParsedLocation(stripBasePathFromHref(location.href, this.basePath), location.state, this.parseSearch);
|
|
177
249
|
}
|
|
178
250
|
applyTrailingSlash(pathname, route) {
|
|
179
251
|
const trailingSlash = this.options.trailingSlash ?? "preserve";
|
|
@@ -226,13 +298,9 @@ class Router {
|
|
|
226
298
|
});
|
|
227
299
|
}
|
|
228
300
|
resolveSearch(route, rawSearch) {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
...routeHasRecord(fromHeadTagSchema) ? fromHeadTagSchema : {},
|
|
234
|
-
...routeHasRecord(fromRoute) ? fromRoute : {}
|
|
235
|
-
};
|
|
301
|
+
const fromSchema = route.searchSchema ? route.searchSchema.parse(rawSearch) : {};
|
|
302
|
+
if (routeHasRecord(fromSchema)) {
|
|
303
|
+
return fromSchema;
|
|
236
304
|
}
|
|
237
305
|
return rawSearch;
|
|
238
306
|
}
|
|
@@ -272,57 +340,140 @@ class Router {
|
|
|
272
340
|
to: route.to
|
|
273
341
|
});
|
|
274
342
|
}
|
|
275
|
-
const head = await this.resolveLocationHead(matches, location, options?.request);
|
|
343
|
+
const head = await this.resolveLocationHead(matches, location, options?.request, options?.initialHeadSnapshot);
|
|
276
344
|
return { matches, head, error: null };
|
|
277
345
|
}
|
|
278
|
-
async resolveLocationHead(matches, location, request) {
|
|
346
|
+
async resolveLocationHead(matches, location, request, initialHeadSnapshot) {
|
|
279
347
|
const resolvedHeadByRoute = new Map;
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
348
|
+
const serverMatches = matches.filter((match) => match.route.serverHead);
|
|
349
|
+
if (serverMatches.length === 0) {
|
|
350
|
+
return resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
351
|
+
}
|
|
352
|
+
if (this.options.loadRouteHead !== undefined) {
|
|
353
|
+
for (const match of serverMatches) {
|
|
354
|
+
resolvedHeadByRoute.set(match.route.fullPath, await this.loadRouteHead(match.route, match.params, match.search, location, request));
|
|
355
|
+
}
|
|
356
|
+
return resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
357
|
+
}
|
|
358
|
+
if (initialHeadSnapshot?.href === location.href && initialHeadSnapshot.routeHeads === undefined && !matchesHaveInlineHead(matches)) {
|
|
359
|
+
return initialHeadSnapshot.head;
|
|
360
|
+
}
|
|
361
|
+
let needsDocumentHeadFetch = false;
|
|
362
|
+
for (const match of serverMatches) {
|
|
363
|
+
const cachedHead = this.getCachedRouteHead(match.route.fullPath, match.params, match.search);
|
|
364
|
+
if (cachedHead) {
|
|
365
|
+
resolvedHeadByRoute.set(match.route.fullPath, cachedHead);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
needsDocumentHeadFetch = true;
|
|
369
|
+
}
|
|
370
|
+
if (!needsDocumentHeadFetch) {
|
|
371
|
+
return resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
372
|
+
}
|
|
373
|
+
const documentHead = await this.fetchDocumentHead(location);
|
|
374
|
+
if ((documentHead.routeHeads?.length ?? 0) === 0 && !matchesHaveInlineHead(matches)) {
|
|
375
|
+
return documentHead.head;
|
|
376
|
+
}
|
|
377
|
+
const routeHeadsById = this.cacheRouteHeadsFromDocument(matches, documentHead.routeHeads ?? []);
|
|
378
|
+
for (const match of serverMatches) {
|
|
379
|
+
const responseHead = routeHeadsById.get(match.route.fullPath);
|
|
380
|
+
if (responseHead) {
|
|
381
|
+
resolvedHeadByRoute.set(match.route.fullPath, responseHead);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const cachedHead = this.getCachedRouteHead(match.route.fullPath, match.params, match.search);
|
|
385
|
+
if (cachedHead) {
|
|
386
|
+
resolvedHeadByRoute.set(match.route.fullPath, cachedHead);
|
|
283
387
|
continue;
|
|
284
388
|
}
|
|
285
|
-
|
|
389
|
+
const response = await this.fetchRouteHead(match.route, match.params, match.search);
|
|
390
|
+
this.setRouteHeadCache(match.route.fullPath, match.params, match.search, response);
|
|
391
|
+
resolvedHeadByRoute.set(match.route.fullPath, response.head);
|
|
286
392
|
}
|
|
287
393
|
return resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
288
394
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
395
|
+
getRouteHeadCacheKey(routeId, params, search) {
|
|
396
|
+
return JSON.stringify({
|
|
397
|
+
routeId,
|
|
292
398
|
params,
|
|
293
399
|
search
|
|
294
400
|
});
|
|
295
|
-
|
|
401
|
+
}
|
|
402
|
+
getCachedRouteHead(routeId, params, search) {
|
|
403
|
+
const cached = this.headCache.get(this.getRouteHeadCacheKey(routeId, params, search));
|
|
296
404
|
if (cached && cached.expiresAt > Date.now()) {
|
|
297
405
|
return cached.head;
|
|
298
406
|
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
setRouteHeadCache(routeId, params, search, response) {
|
|
410
|
+
this.headCache.set(this.getRouteHeadCacheKey(routeId, params, search), {
|
|
411
|
+
head: response.head,
|
|
412
|
+
expiresAt: Date.now() + (response.staleTime ?? 0)
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
seedHeadCacheFromRouteHeads(matches, routeHeads) {
|
|
416
|
+
this.cacheRouteHeadsFromDocument(matches, routeHeads);
|
|
417
|
+
}
|
|
418
|
+
cacheRouteHeadsFromDocument(matches, routeHeads) {
|
|
419
|
+
const routeHeadsById = new Map(routeHeads.map((entry) => [entry.routeId, entry]));
|
|
420
|
+
const resolvedHeadByRoute = new Map;
|
|
421
|
+
for (const match of matches) {
|
|
422
|
+
if (!match.route.serverHead) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
const entry = routeHeadsById.get(match.route.fullPath);
|
|
426
|
+
if (!entry) {
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
this.setRouteHeadCache(match.route.fullPath, match.params, match.search, entry);
|
|
430
|
+
resolvedHeadByRoute.set(match.route.fullPath, entry.head);
|
|
431
|
+
}
|
|
432
|
+
return resolvedHeadByRoute;
|
|
433
|
+
}
|
|
434
|
+
async loadRouteHead(route, params, search, location, request) {
|
|
435
|
+
const cachedHead = this.getCachedRouteHead(route.fullPath, params, search);
|
|
436
|
+
if (cachedHead) {
|
|
437
|
+
return cachedHead;
|
|
438
|
+
}
|
|
299
439
|
const response = this.options.loadRouteHead !== undefined ? await this.options.loadRouteHead({
|
|
300
440
|
route,
|
|
301
|
-
|
|
441
|
+
routeId: route.fullPath,
|
|
302
442
|
params,
|
|
303
443
|
search,
|
|
304
444
|
location,
|
|
305
445
|
request
|
|
306
|
-
}) : await this.fetchRouteHead(route,
|
|
307
|
-
this.
|
|
308
|
-
head: response.head,
|
|
309
|
-
expiresAt: Date.now() + (response.staleTime ?? 0)
|
|
310
|
-
});
|
|
446
|
+
}) : await this.fetchRouteHead(route, params, search);
|
|
447
|
+
this.setRouteHeadCache(route.fullPath, params, search, response);
|
|
311
448
|
return response.head;
|
|
312
449
|
}
|
|
313
|
-
async fetchRouteHead(route,
|
|
314
|
-
const basePath = this.options.headBasePath ?? "/head-api";
|
|
450
|
+
async fetchRouteHead(route, params, search) {
|
|
451
|
+
const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
|
|
315
452
|
const searchParams = new URLSearchParams({
|
|
316
453
|
routeId: route.fullPath,
|
|
317
454
|
params: JSON.stringify(params),
|
|
318
455
|
search: JSON.stringify(search)
|
|
319
456
|
});
|
|
320
|
-
const response = await fetch(`${basePath}
|
|
457
|
+
const response = await fetch(`${basePath}?${searchParams.toString()}`);
|
|
458
|
+
if (!response.ok) {
|
|
459
|
+
if (response.status === 404) {
|
|
460
|
+
throw notFound();
|
|
461
|
+
}
|
|
462
|
+
throw new Error(`Failed to resolve server head for route "${route.fullPath}"`);
|
|
463
|
+
}
|
|
464
|
+
return await response.json();
|
|
465
|
+
}
|
|
466
|
+
async fetchDocumentHead(location) {
|
|
467
|
+
const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
|
|
468
|
+
const searchParams = new URLSearchParams({
|
|
469
|
+
href: prependBasePathToHref(location.href, this.basePath)
|
|
470
|
+
});
|
|
471
|
+
const response = await fetch(`${basePath}?${searchParams.toString()}`);
|
|
321
472
|
if (!response.ok) {
|
|
322
473
|
if (response.status === 404) {
|
|
323
474
|
throw notFound();
|
|
324
475
|
}
|
|
325
|
-
throw new Error(`Failed to resolve head
|
|
476
|
+
throw new Error(`Failed to resolve server head for location "${location.href}"`);
|
|
326
477
|
}
|
|
327
478
|
return await response.json();
|
|
328
479
|
}
|
|
@@ -335,13 +486,15 @@ class Router {
|
|
|
335
486
|
this.notify();
|
|
336
487
|
try {
|
|
337
488
|
const resolved = await this.resolveLocation(location, {
|
|
338
|
-
request: options.request
|
|
489
|
+
request: options.request,
|
|
490
|
+
initialHeadSnapshot: options.initialHeadSnapshot
|
|
339
491
|
});
|
|
492
|
+
const historyHref = prependBasePathToHref(location.href, this.basePath);
|
|
340
493
|
if (options.writeHistory) {
|
|
341
494
|
if (options.replace) {
|
|
342
|
-
this.history.replace(
|
|
495
|
+
this.history.replace(historyHref, location.state);
|
|
343
496
|
} else {
|
|
344
|
-
this.history.push(
|
|
497
|
+
this.history.push(historyHref, location.state);
|
|
345
498
|
}
|
|
346
499
|
}
|
|
347
500
|
this.state = {
|
|
@@ -362,11 +515,12 @@ class Router {
|
|
|
362
515
|
return;
|
|
363
516
|
}
|
|
364
517
|
const errorMatches = this.buildMatches(location);
|
|
518
|
+
const historyHref = prependBasePathToHref(location.href, this.basePath);
|
|
365
519
|
if (options.writeHistory) {
|
|
366
520
|
if (options.replace) {
|
|
367
|
-
this.history.replace(
|
|
521
|
+
this.history.replace(historyHref, location.state);
|
|
368
522
|
} else {
|
|
369
|
-
this.history.push(
|
|
523
|
+
this.history.push(historyHref, location.state);
|
|
370
524
|
}
|
|
371
525
|
}
|
|
372
526
|
this.state = {
|
|
@@ -456,61 +610,95 @@ function createManagedHeadElements(head) {
|
|
|
456
610
|
element.setAttribute(MANAGED_HEAD_ATTRIBUTE, "true");
|
|
457
611
|
return element;
|
|
458
612
|
};
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
613
|
+
const setAttributes = (element, attributes) => {
|
|
614
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
615
|
+
if (value === undefined || value === false) {
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if (value === true) {
|
|
619
|
+
element.setAttribute(key, "");
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
element.setAttribute(key, value);
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
for (const element of head) {
|
|
626
|
+
if (element.tag === "title") {
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
if (element.tag === "meta") {
|
|
630
|
+
const tag2 = managed(document.createElement("meta"));
|
|
631
|
+
if ("charset" in element) {
|
|
632
|
+
tag2.setAttribute("charset", element.charset);
|
|
633
|
+
} else if ("name" in element) {
|
|
634
|
+
tag2.setAttribute("name", element.name);
|
|
635
|
+
tag2.setAttribute("content", element.content);
|
|
636
|
+
} else if ("property" in element) {
|
|
637
|
+
tag2.setAttribute("property", element.property);
|
|
638
|
+
tag2.setAttribute("content", element.content);
|
|
639
|
+
} else {
|
|
640
|
+
tag2.setAttribute("http-equiv", element.httpEquiv);
|
|
641
|
+
tag2.setAttribute("content", element.content);
|
|
642
|
+
}
|
|
643
|
+
elements.push(tag2);
|
|
464
644
|
continue;
|
|
465
645
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
646
|
+
if (element.tag === "link") {
|
|
647
|
+
const tag2 = managed(document.createElement("link"));
|
|
648
|
+
setAttributes(tag2, {
|
|
649
|
+
rel: element.rel,
|
|
650
|
+
href: element.href,
|
|
651
|
+
type: element.type,
|
|
652
|
+
media: element.media,
|
|
653
|
+
sizes: element.sizes,
|
|
654
|
+
crossorigin: element.crossorigin
|
|
655
|
+
});
|
|
656
|
+
elements.push(tag2);
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
if (element.tag === "style") {
|
|
660
|
+
const tag2 = managed(document.createElement("style"));
|
|
661
|
+
if (element.media) {
|
|
662
|
+
tag2.setAttribute("media", element.media);
|
|
663
|
+
}
|
|
664
|
+
tag2.textContent = element.children;
|
|
665
|
+
elements.push(tag2);
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (element.tag === "script") {
|
|
669
|
+
const tag2 = managed(document.createElement("script"));
|
|
670
|
+
if (element.src) {
|
|
671
|
+
tag2.setAttribute("src", element.src);
|
|
672
|
+
}
|
|
673
|
+
if (element.type) {
|
|
674
|
+
tag2.setAttribute("type", element.type);
|
|
675
|
+
}
|
|
676
|
+
if (element.async) {
|
|
677
|
+
tag2.async = true;
|
|
678
|
+
}
|
|
679
|
+
if (element.defer) {
|
|
680
|
+
tag2.defer = true;
|
|
681
|
+
}
|
|
682
|
+
if (element.children) {
|
|
683
|
+
tag2.textContent = element.children;
|
|
684
|
+
}
|
|
685
|
+
elements.push(tag2);
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (element.tag === "base") {
|
|
689
|
+
const tag2 = managed(document.createElement("base"));
|
|
690
|
+
tag2.setAttribute("href", element.href);
|
|
691
|
+
if (element.target) {
|
|
692
|
+
tag2.setAttribute("target", element.target);
|
|
693
|
+
}
|
|
694
|
+
elements.push(tag2);
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
const tag = managed(document.createElement(element.name));
|
|
698
|
+
setAttributes(tag, element.attrs ?? {});
|
|
699
|
+
if (element.children) {
|
|
700
|
+
tag.textContent = element.children;
|
|
478
701
|
}
|
|
479
|
-
elements.push(tag);
|
|
480
|
-
}
|
|
481
|
-
for (const link of head.links ?? []) {
|
|
482
|
-
const tag = managed(document.createElement("link"));
|
|
483
|
-
tag.setAttribute("rel", link.rel);
|
|
484
|
-
tag.setAttribute("href", link.href);
|
|
485
|
-
if (link.type)
|
|
486
|
-
tag.setAttribute("type", link.type);
|
|
487
|
-
if (link.media)
|
|
488
|
-
tag.setAttribute("media", link.media);
|
|
489
|
-
if (link.sizes)
|
|
490
|
-
tag.setAttribute("sizes", link.sizes);
|
|
491
|
-
if (link.crossorigin)
|
|
492
|
-
tag.setAttribute("crossorigin", link.crossorigin);
|
|
493
|
-
elements.push(tag);
|
|
494
|
-
}
|
|
495
|
-
for (const style of head.styles ?? []) {
|
|
496
|
-
const tag = managed(document.createElement("style"));
|
|
497
|
-
if (style.media)
|
|
498
|
-
tag.setAttribute("media", style.media);
|
|
499
|
-
tag.textContent = style.children;
|
|
500
|
-
elements.push(tag);
|
|
501
|
-
}
|
|
502
|
-
for (const script of head.scripts ?? []) {
|
|
503
|
-
const tag = managed(document.createElement("script"));
|
|
504
|
-
if (script.src)
|
|
505
|
-
tag.setAttribute("src", script.src);
|
|
506
|
-
if (script.type)
|
|
507
|
-
tag.setAttribute("type", script.type);
|
|
508
|
-
if (script.async)
|
|
509
|
-
tag.async = true;
|
|
510
|
-
if (script.defer)
|
|
511
|
-
tag.defer = true;
|
|
512
|
-
if (script.children)
|
|
513
|
-
tag.textContent = script.children;
|
|
514
702
|
elements.push(tag);
|
|
515
703
|
}
|
|
516
704
|
return elements;
|
|
@@ -519,6 +707,10 @@ function reconcileDocumentHead(head) {
|
|
|
519
707
|
if (typeof document === "undefined") {
|
|
520
708
|
return;
|
|
521
709
|
}
|
|
710
|
+
const title = [...head].reverse().find((element) => element.tag === "title");
|
|
711
|
+
if (title && title.tag === "title") {
|
|
712
|
+
document.title = title.children;
|
|
713
|
+
}
|
|
522
714
|
for (const element of Array.from(document.head.querySelectorAll(`[${MANAGED_HEAD_ATTRIBUTE}]`))) {
|
|
523
715
|
element.remove();
|
|
524
716
|
}
|
|
@@ -663,7 +855,7 @@ function useResolvedLink(props) {
|
|
|
663
855
|
const router = useRouterContext();
|
|
664
856
|
const href = router.buildHref(props);
|
|
665
857
|
const location = useLocation();
|
|
666
|
-
const pathOnly = href.split(/[?#]/u)[0] ?? href;
|
|
858
|
+
const pathOnly = stripBasePathFromHref(href, router.options.basePath).split(/[?#]/u)[0] ?? href;
|
|
667
859
|
const isActive = pathOnly === location.pathname;
|
|
668
860
|
return { href, isActive, router };
|
|
669
861
|
}
|