@richie-router/react 0.1.2 → 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 +281 -81
- package/dist/cjs/router.test.cjs +413 -0
- package/dist/esm/router.mjs +281 -81
- package/dist/esm/router.test.mjs +418 -0
- package/dist/types/router.d.ts +12 -0
- package/package.json +2 -2
package/dist/esm/router.mjs
CHANGED
|
@@ -26,10 +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 =
|
|
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}`;
|
|
80
|
+
}
|
|
30
81
|
function routeHasRecord(value) {
|
|
31
82
|
return typeof value === "object" && value !== null;
|
|
32
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
|
+
}
|
|
33
91
|
function resolveParamsInput(input, previous) {
|
|
34
92
|
if (input === undefined) {
|
|
35
93
|
return previous;
|
|
@@ -75,6 +133,8 @@ class Router {
|
|
|
75
133
|
headCache = new Map;
|
|
76
134
|
parseSearch;
|
|
77
135
|
stringifySearch;
|
|
136
|
+
basePath;
|
|
137
|
+
initialHeadSnapshot;
|
|
78
138
|
started = false;
|
|
79
139
|
unsubscribeHistory;
|
|
80
140
|
constructor(options) {
|
|
@@ -83,6 +143,7 @@ class Router {
|
|
|
83
143
|
this.history = options.history ?? (typeof window === "undefined" ? createMemoryHistory() : createBrowserHistory());
|
|
84
144
|
this.parseSearch = options.parseSearch ?? defaultParseSearch;
|
|
85
145
|
this.stringifySearch = options.stringifySearch ?? defaultStringifySearch;
|
|
146
|
+
this.basePath = normalizeBasePath(options.basePath);
|
|
86
147
|
for (const route of collectRoutes(this.routeTree)) {
|
|
87
148
|
this.routesByFullPath.set(route.fullPath, route);
|
|
88
149
|
}
|
|
@@ -90,15 +151,22 @@ class Router {
|
|
|
90
151
|
this.routesByTo.set(branch.leaf.to, branch.leaf);
|
|
91
152
|
}
|
|
92
153
|
const location = this.readLocation();
|
|
154
|
+
const initialMatches = this.buildMatches(location);
|
|
155
|
+
const rawHistoryHref = this.history.location.href;
|
|
93
156
|
const initialHeadSnapshot = typeof window !== "undefined" ? window.__RICHIE_ROUTER_HEAD__ : undefined;
|
|
94
|
-
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
|
+
}
|
|
95
162
|
if (typeof window !== "undefined" && initialHeadSnapshot !== undefined) {
|
|
96
163
|
delete window.__RICHIE_ROUTER_HEAD__;
|
|
97
164
|
}
|
|
165
|
+
this.initialHeadSnapshot = hasMatchingInitialHeadSnapshot ? initialHeadSnapshot : undefined;
|
|
98
166
|
this.state = {
|
|
99
167
|
status: "loading",
|
|
100
168
|
location,
|
|
101
|
-
matches:
|
|
169
|
+
matches: initialMatches,
|
|
102
170
|
head: initialHead,
|
|
103
171
|
error: null
|
|
104
172
|
};
|
|
@@ -129,15 +197,17 @@ class Router {
|
|
|
129
197
|
}
|
|
130
198
|
async load(options) {
|
|
131
199
|
const nextLocation = this.readLocation();
|
|
200
|
+
const initialHeadSnapshot = this.initialHeadSnapshot?.href === nextLocation.href ? this.initialHeadSnapshot : undefined;
|
|
201
|
+
this.initialHeadSnapshot = undefined;
|
|
132
202
|
await this.commitLocation(nextLocation, {
|
|
133
203
|
request: options?.request,
|
|
134
204
|
replace: true,
|
|
135
|
-
writeHistory: false
|
|
205
|
+
writeHistory: false,
|
|
206
|
+
initialHeadSnapshot
|
|
136
207
|
});
|
|
137
208
|
}
|
|
138
209
|
async navigate(options) {
|
|
139
|
-
const
|
|
140
|
-
const location = createParsedLocation(href, options.state ?? null, this.parseSearch);
|
|
210
|
+
const location = this.buildLocation(options);
|
|
141
211
|
await this.commitLocation(location, {
|
|
142
212
|
replace: options.replace ?? false,
|
|
143
213
|
writeHistory: true,
|
|
@@ -145,8 +215,7 @@ class Router {
|
|
|
145
215
|
});
|
|
146
216
|
}
|
|
147
217
|
async preloadRoute(options) {
|
|
148
|
-
const
|
|
149
|
-
const location = createParsedLocation(href, options.state ?? null, this.parseSearch);
|
|
218
|
+
const location = this.buildLocation(options);
|
|
150
219
|
try {
|
|
151
220
|
await this.resolveLocation(location);
|
|
152
221
|
} catch {}
|
|
@@ -156,6 +225,12 @@ class Router {
|
|
|
156
225
|
await this.load();
|
|
157
226
|
}
|
|
158
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) {
|
|
159
234
|
const targetRoute = this.routesByTo.get(options.to) ?? null;
|
|
160
235
|
const fromMatch = options.from ? this.findMatchByTo(options.from) : null;
|
|
161
236
|
const previousParams = fromMatch?.params ?? {};
|
|
@@ -170,7 +245,7 @@ class Router {
|
|
|
170
245
|
}
|
|
171
246
|
readLocation() {
|
|
172
247
|
const location = this.history.location;
|
|
173
|
-
return createParsedLocation(location.href, location.state, this.parseSearch);
|
|
248
|
+
return createParsedLocation(stripBasePathFromHref(location.href, this.basePath), location.state, this.parseSearch);
|
|
174
249
|
}
|
|
175
250
|
applyTrailingSlash(pathname, route) {
|
|
176
251
|
const trailingSlash = this.options.trailingSlash ?? "preserve";
|
|
@@ -265,29 +340,102 @@ class Router {
|
|
|
265
340
|
to: route.to
|
|
266
341
|
});
|
|
267
342
|
}
|
|
268
|
-
const head = await this.resolveLocationHead(matches, location, options?.request);
|
|
343
|
+
const head = await this.resolveLocationHead(matches, location, options?.request, options?.initialHeadSnapshot);
|
|
269
344
|
return { matches, head, error: null };
|
|
270
345
|
}
|
|
271
|
-
async resolveLocationHead(matches, location, request) {
|
|
346
|
+
async resolveLocationHead(matches, location, request, initialHeadSnapshot) {
|
|
272
347
|
const resolvedHeadByRoute = new Map;
|
|
273
|
-
|
|
274
|
-
|
|
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);
|
|
275
366
|
continue;
|
|
276
367
|
}
|
|
277
|
-
|
|
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);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
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);
|
|
278
392
|
}
|
|
279
393
|
return resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
280
394
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
routeId
|
|
395
|
+
getRouteHeadCacheKey(routeId, params, search) {
|
|
396
|
+
return JSON.stringify({
|
|
397
|
+
routeId,
|
|
284
398
|
params,
|
|
285
399
|
search
|
|
286
400
|
});
|
|
287
|
-
|
|
401
|
+
}
|
|
402
|
+
getCachedRouteHead(routeId, params, search) {
|
|
403
|
+
const cached = this.headCache.get(this.getRouteHeadCacheKey(routeId, params, search));
|
|
288
404
|
if (cached && cached.expiresAt > Date.now()) {
|
|
289
405
|
return cached.head;
|
|
290
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
|
+
}
|
|
291
439
|
const response = this.options.loadRouteHead !== undefined ? await this.options.loadRouteHead({
|
|
292
440
|
route,
|
|
293
441
|
routeId: route.fullPath,
|
|
@@ -296,14 +444,11 @@ class Router {
|
|
|
296
444
|
location,
|
|
297
445
|
request
|
|
298
446
|
}) : await this.fetchRouteHead(route, params, search);
|
|
299
|
-
this.
|
|
300
|
-
head: response.head,
|
|
301
|
-
expiresAt: Date.now() + (response.staleTime ?? 0)
|
|
302
|
-
});
|
|
447
|
+
this.setRouteHeadCache(route.fullPath, params, search, response);
|
|
303
448
|
return response.head;
|
|
304
449
|
}
|
|
305
450
|
async fetchRouteHead(route, params, search) {
|
|
306
|
-
const basePath = this.options.headBasePath ?? "/head-api";
|
|
451
|
+
const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
|
|
307
452
|
const searchParams = new URLSearchParams({
|
|
308
453
|
routeId: route.fullPath,
|
|
309
454
|
params: JSON.stringify(params),
|
|
@@ -318,6 +463,20 @@ class Router {
|
|
|
318
463
|
}
|
|
319
464
|
return await response.json();
|
|
320
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()}`);
|
|
472
|
+
if (!response.ok) {
|
|
473
|
+
if (response.status === 404) {
|
|
474
|
+
throw notFound();
|
|
475
|
+
}
|
|
476
|
+
throw new Error(`Failed to resolve server head for location "${location.href}"`);
|
|
477
|
+
}
|
|
478
|
+
return await response.json();
|
|
479
|
+
}
|
|
321
480
|
async commitLocation(location, options) {
|
|
322
481
|
this.state = {
|
|
323
482
|
...this.state,
|
|
@@ -327,13 +486,15 @@ class Router {
|
|
|
327
486
|
this.notify();
|
|
328
487
|
try {
|
|
329
488
|
const resolved = await this.resolveLocation(location, {
|
|
330
|
-
request: options.request
|
|
489
|
+
request: options.request,
|
|
490
|
+
initialHeadSnapshot: options.initialHeadSnapshot
|
|
331
491
|
});
|
|
492
|
+
const historyHref = prependBasePathToHref(location.href, this.basePath);
|
|
332
493
|
if (options.writeHistory) {
|
|
333
494
|
if (options.replace) {
|
|
334
|
-
this.history.replace(
|
|
495
|
+
this.history.replace(historyHref, location.state);
|
|
335
496
|
} else {
|
|
336
|
-
this.history.push(
|
|
497
|
+
this.history.push(historyHref, location.state);
|
|
337
498
|
}
|
|
338
499
|
}
|
|
339
500
|
this.state = {
|
|
@@ -354,11 +515,12 @@ class Router {
|
|
|
354
515
|
return;
|
|
355
516
|
}
|
|
356
517
|
const errorMatches = this.buildMatches(location);
|
|
518
|
+
const historyHref = prependBasePathToHref(location.href, this.basePath);
|
|
357
519
|
if (options.writeHistory) {
|
|
358
520
|
if (options.replace) {
|
|
359
|
-
this.history.replace(
|
|
521
|
+
this.history.replace(historyHref, location.state);
|
|
360
522
|
} else {
|
|
361
|
-
this.history.push(
|
|
523
|
+
this.history.push(historyHref, location.state);
|
|
362
524
|
}
|
|
363
525
|
}
|
|
364
526
|
this.state = {
|
|
@@ -448,61 +610,95 @@ function createManagedHeadElements(head) {
|
|
|
448
610
|
element.setAttribute(MANAGED_HEAD_ATTRIBUTE, "true");
|
|
449
611
|
return element;
|
|
450
612
|
};
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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);
|
|
456
644
|
continue;
|
|
457
645
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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;
|
|
470
701
|
}
|
|
471
|
-
elements.push(tag);
|
|
472
|
-
}
|
|
473
|
-
for (const link of head.links ?? []) {
|
|
474
|
-
const tag = managed(document.createElement("link"));
|
|
475
|
-
tag.setAttribute("rel", link.rel);
|
|
476
|
-
tag.setAttribute("href", link.href);
|
|
477
|
-
if (link.type)
|
|
478
|
-
tag.setAttribute("type", link.type);
|
|
479
|
-
if (link.media)
|
|
480
|
-
tag.setAttribute("media", link.media);
|
|
481
|
-
if (link.sizes)
|
|
482
|
-
tag.setAttribute("sizes", link.sizes);
|
|
483
|
-
if (link.crossorigin)
|
|
484
|
-
tag.setAttribute("crossorigin", link.crossorigin);
|
|
485
|
-
elements.push(tag);
|
|
486
|
-
}
|
|
487
|
-
for (const style of head.styles ?? []) {
|
|
488
|
-
const tag = managed(document.createElement("style"));
|
|
489
|
-
if (style.media)
|
|
490
|
-
tag.setAttribute("media", style.media);
|
|
491
|
-
tag.textContent = style.children;
|
|
492
|
-
elements.push(tag);
|
|
493
|
-
}
|
|
494
|
-
for (const script of head.scripts ?? []) {
|
|
495
|
-
const tag = managed(document.createElement("script"));
|
|
496
|
-
if (script.src)
|
|
497
|
-
tag.setAttribute("src", script.src);
|
|
498
|
-
if (script.type)
|
|
499
|
-
tag.setAttribute("type", script.type);
|
|
500
|
-
if (script.async)
|
|
501
|
-
tag.async = true;
|
|
502
|
-
if (script.defer)
|
|
503
|
-
tag.defer = true;
|
|
504
|
-
if (script.children)
|
|
505
|
-
tag.textContent = script.children;
|
|
506
702
|
elements.push(tag);
|
|
507
703
|
}
|
|
508
704
|
return elements;
|
|
@@ -511,6 +707,10 @@ function reconcileDocumentHead(head) {
|
|
|
511
707
|
if (typeof document === "undefined") {
|
|
512
708
|
return;
|
|
513
709
|
}
|
|
710
|
+
const title = [...head].reverse().find((element) => element.tag === "title");
|
|
711
|
+
if (title && title.tag === "title") {
|
|
712
|
+
document.title = title.children;
|
|
713
|
+
}
|
|
514
714
|
for (const element of Array.from(document.head.querySelectorAll(`[${MANAGED_HEAD_ATTRIBUTE}]`))) {
|
|
515
715
|
element.remove();
|
|
516
716
|
}
|
|
@@ -655,7 +855,7 @@ function useResolvedLink(props) {
|
|
|
655
855
|
const router = useRouterContext();
|
|
656
856
|
const href = router.buildHref(props);
|
|
657
857
|
const location = useLocation();
|
|
658
|
-
const pathOnly = href.split(/[?#]/u)[0] ?? href;
|
|
858
|
+
const pathOnly = stripBasePathFromHref(href, router.options.basePath).split(/[?#]/u)[0] ?? href;
|
|
659
859
|
const isActive = pathOnly === location.pathname;
|
|
660
860
|
return { href, isActive, router };
|
|
661
861
|
}
|