@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/cjs/router.cjs
CHANGED
|
@@ -103,10 +103,68 @@ var RouterStateContext = import_react.default.createContext(null);
|
|
|
103
103
|
var OutletContext = import_react.default.createContext(null);
|
|
104
104
|
var MatchContext = import_react.default.createContext(null);
|
|
105
105
|
var MANAGED_HEAD_ATTRIBUTE = "data-richie-router-head";
|
|
106
|
-
var EMPTY_HEAD =
|
|
106
|
+
var EMPTY_HEAD = [];
|
|
107
|
+
function ensureLeadingSlash(value) {
|
|
108
|
+
return value.startsWith("/") ? value : `/${value}`;
|
|
109
|
+
}
|
|
110
|
+
function normalizeBasePath(basePath) {
|
|
111
|
+
if (!basePath) {
|
|
112
|
+
return "";
|
|
113
|
+
}
|
|
114
|
+
const trimmed = basePath.trim();
|
|
115
|
+
if (trimmed === "" || trimmed === "/") {
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
const normalized = ensureLeadingSlash(trimmed).replace(/\/+$/u, "");
|
|
119
|
+
return normalized === "/" ? "" : normalized;
|
|
120
|
+
}
|
|
121
|
+
function parseHref(href) {
|
|
122
|
+
if (href.startsWith("http://") || href.startsWith("https://")) {
|
|
123
|
+
return new URL(href);
|
|
124
|
+
}
|
|
125
|
+
return new URL(ensureLeadingSlash(href), "http://richie-router.local");
|
|
126
|
+
}
|
|
127
|
+
function stripBasePathFromPathname(pathname, basePath) {
|
|
128
|
+
if (!basePath) {
|
|
129
|
+
return pathname;
|
|
130
|
+
}
|
|
131
|
+
if (pathname === basePath) {
|
|
132
|
+
return "/";
|
|
133
|
+
}
|
|
134
|
+
return pathname.startsWith(`${basePath}/`) ? pathname.slice(basePath.length) || "/" : pathname;
|
|
135
|
+
}
|
|
136
|
+
function stripBasePathFromHref(href, basePath) {
|
|
137
|
+
const normalizedBasePath = normalizeBasePath(basePath);
|
|
138
|
+
if (!normalizedBasePath) {
|
|
139
|
+
return href;
|
|
140
|
+
}
|
|
141
|
+
const url = parseHref(href);
|
|
142
|
+
return `${stripBasePathFromPathname(url.pathname, normalizedBasePath)}${url.search}${url.hash}`;
|
|
143
|
+
}
|
|
144
|
+
function prependBasePathToPathname(pathname, basePath) {
|
|
145
|
+
if (!basePath) {
|
|
146
|
+
return pathname;
|
|
147
|
+
}
|
|
148
|
+
return pathname === "/" ? basePath : `${basePath}${ensureLeadingSlash(pathname)}`;
|
|
149
|
+
}
|
|
150
|
+
function prependBasePathToHref(href, basePath) {
|
|
151
|
+
const normalizedBasePath = normalizeBasePath(basePath);
|
|
152
|
+
if (!normalizedBasePath) {
|
|
153
|
+
return href;
|
|
154
|
+
}
|
|
155
|
+
const url = parseHref(href);
|
|
156
|
+
return `${prependBasePathToPathname(url.pathname, normalizedBasePath)}${url.search}${url.hash}`;
|
|
157
|
+
}
|
|
107
158
|
function routeHasRecord(value) {
|
|
108
159
|
return typeof value === "object" && value !== null;
|
|
109
160
|
}
|
|
161
|
+
function routeHasInlineHead(route) {
|
|
162
|
+
const headOption = route.options.head;
|
|
163
|
+
return Boolean(headOption && typeof headOption !== "string");
|
|
164
|
+
}
|
|
165
|
+
function matchesHaveInlineHead(matches) {
|
|
166
|
+
return matches.some((match) => routeHasInlineHead(match.route));
|
|
167
|
+
}
|
|
110
168
|
function resolveParamsInput(input, previous) {
|
|
111
169
|
if (input === undefined) {
|
|
112
170
|
return previous;
|
|
@@ -152,6 +210,8 @@ class Router {
|
|
|
152
210
|
headCache = new Map;
|
|
153
211
|
parseSearch;
|
|
154
212
|
stringifySearch;
|
|
213
|
+
basePath;
|
|
214
|
+
initialHeadSnapshot;
|
|
155
215
|
started = false;
|
|
156
216
|
unsubscribeHistory;
|
|
157
217
|
constructor(options) {
|
|
@@ -160,6 +220,7 @@ class Router {
|
|
|
160
220
|
this.history = options.history ?? (typeof window === "undefined" ? import_history.createMemoryHistory() : import_history.createBrowserHistory());
|
|
161
221
|
this.parseSearch = options.parseSearch ?? import_core.defaultParseSearch;
|
|
162
222
|
this.stringifySearch = options.stringifySearch ?? import_core.defaultStringifySearch;
|
|
223
|
+
this.basePath = normalizeBasePath(options.basePath);
|
|
163
224
|
for (const route of import_core.collectRoutes(this.routeTree)) {
|
|
164
225
|
this.routesByFullPath.set(route.fullPath, route);
|
|
165
226
|
}
|
|
@@ -167,15 +228,22 @@ class Router {
|
|
|
167
228
|
this.routesByTo.set(branch.leaf.to, branch.leaf);
|
|
168
229
|
}
|
|
169
230
|
const location = this.readLocation();
|
|
231
|
+
const initialMatches = this.buildMatches(location);
|
|
232
|
+
const rawHistoryHref = this.history.location.href;
|
|
170
233
|
const initialHeadSnapshot = typeof window !== "undefined" ? window.__RICHIE_ROUTER_HEAD__ : undefined;
|
|
171
|
-
const
|
|
234
|
+
const hasMatchingInitialHeadSnapshot = Boolean(initialHeadSnapshot && (initialHeadSnapshot.href === location.href || initialHeadSnapshot.href === rawHistoryHref));
|
|
235
|
+
const initialHead = hasMatchingInitialHeadSnapshot && initialHeadSnapshot ? initialHeadSnapshot.head : EMPTY_HEAD;
|
|
236
|
+
if (hasMatchingInitialHeadSnapshot && this.options.loadRouteHead === undefined && initialHeadSnapshot?.routeHeads !== undefined) {
|
|
237
|
+
this.seedHeadCacheFromRouteHeads(initialMatches, initialHeadSnapshot.routeHeads);
|
|
238
|
+
}
|
|
172
239
|
if (typeof window !== "undefined" && initialHeadSnapshot !== undefined) {
|
|
173
240
|
delete window.__RICHIE_ROUTER_HEAD__;
|
|
174
241
|
}
|
|
242
|
+
this.initialHeadSnapshot = hasMatchingInitialHeadSnapshot ? initialHeadSnapshot : undefined;
|
|
175
243
|
this.state = {
|
|
176
244
|
status: "loading",
|
|
177
245
|
location,
|
|
178
|
-
matches:
|
|
246
|
+
matches: initialMatches,
|
|
179
247
|
head: initialHead,
|
|
180
248
|
error: null
|
|
181
249
|
};
|
|
@@ -206,15 +274,17 @@ class Router {
|
|
|
206
274
|
}
|
|
207
275
|
async load(options) {
|
|
208
276
|
const nextLocation = this.readLocation();
|
|
277
|
+
const initialHeadSnapshot = this.initialHeadSnapshot?.href === nextLocation.href ? this.initialHeadSnapshot : undefined;
|
|
278
|
+
this.initialHeadSnapshot = undefined;
|
|
209
279
|
await this.commitLocation(nextLocation, {
|
|
210
280
|
request: options?.request,
|
|
211
281
|
replace: true,
|
|
212
|
-
writeHistory: false
|
|
282
|
+
writeHistory: false,
|
|
283
|
+
initialHeadSnapshot
|
|
213
284
|
});
|
|
214
285
|
}
|
|
215
286
|
async navigate(options) {
|
|
216
|
-
const
|
|
217
|
-
const location = import_core.createParsedLocation(href, options.state ?? null, this.parseSearch);
|
|
287
|
+
const location = this.buildLocation(options);
|
|
218
288
|
await this.commitLocation(location, {
|
|
219
289
|
replace: options.replace ?? false,
|
|
220
290
|
writeHistory: true,
|
|
@@ -222,8 +292,7 @@ class Router {
|
|
|
222
292
|
});
|
|
223
293
|
}
|
|
224
294
|
async preloadRoute(options) {
|
|
225
|
-
const
|
|
226
|
-
const location = import_core.createParsedLocation(href, options.state ?? null, this.parseSearch);
|
|
295
|
+
const location = this.buildLocation(options);
|
|
227
296
|
try {
|
|
228
297
|
await this.resolveLocation(location);
|
|
229
298
|
} catch {}
|
|
@@ -233,6 +302,12 @@ class Router {
|
|
|
233
302
|
await this.load();
|
|
234
303
|
}
|
|
235
304
|
buildHref(options) {
|
|
305
|
+
return prependBasePathToHref(this.buildLocationHref(options), this.basePath);
|
|
306
|
+
}
|
|
307
|
+
buildLocation(options) {
|
|
308
|
+
return import_core.createParsedLocation(this.buildLocationHref(options), options.state ?? null, this.parseSearch);
|
|
309
|
+
}
|
|
310
|
+
buildLocationHref(options) {
|
|
236
311
|
const targetRoute = this.routesByTo.get(options.to) ?? null;
|
|
237
312
|
const fromMatch = options.from ? this.findMatchByTo(options.from) : null;
|
|
238
313
|
const previousParams = fromMatch?.params ?? {};
|
|
@@ -247,7 +322,7 @@ class Router {
|
|
|
247
322
|
}
|
|
248
323
|
readLocation() {
|
|
249
324
|
const location = this.history.location;
|
|
250
|
-
return import_core.createParsedLocation(location.href, location.state, this.parseSearch);
|
|
325
|
+
return import_core.createParsedLocation(stripBasePathFromHref(location.href, this.basePath), location.state, this.parseSearch);
|
|
251
326
|
}
|
|
252
327
|
applyTrailingSlash(pathname, route) {
|
|
253
328
|
const trailingSlash = this.options.trailingSlash ?? "preserve";
|
|
@@ -342,29 +417,102 @@ class Router {
|
|
|
342
417
|
to: route.to
|
|
343
418
|
});
|
|
344
419
|
}
|
|
345
|
-
const head = await this.resolveLocationHead(matches, location, options?.request);
|
|
420
|
+
const head = await this.resolveLocationHead(matches, location, options?.request, options?.initialHeadSnapshot);
|
|
346
421
|
return { matches, head, error: null };
|
|
347
422
|
}
|
|
348
|
-
async resolveLocationHead(matches, location, request) {
|
|
423
|
+
async resolveLocationHead(matches, location, request, initialHeadSnapshot) {
|
|
349
424
|
const resolvedHeadByRoute = new Map;
|
|
350
|
-
|
|
351
|
-
|
|
425
|
+
const serverMatches = matches.filter((match) => match.route.serverHead);
|
|
426
|
+
if (serverMatches.length === 0) {
|
|
427
|
+
return import_core.resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
428
|
+
}
|
|
429
|
+
if (this.options.loadRouteHead !== undefined) {
|
|
430
|
+
for (const match of serverMatches) {
|
|
431
|
+
resolvedHeadByRoute.set(match.route.fullPath, await this.loadRouteHead(match.route, match.params, match.search, location, request));
|
|
432
|
+
}
|
|
433
|
+
return import_core.resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
434
|
+
}
|
|
435
|
+
if (initialHeadSnapshot?.href === location.href && initialHeadSnapshot.routeHeads === undefined && !matchesHaveInlineHead(matches)) {
|
|
436
|
+
return initialHeadSnapshot.head;
|
|
437
|
+
}
|
|
438
|
+
let needsDocumentHeadFetch = false;
|
|
439
|
+
for (const match of serverMatches) {
|
|
440
|
+
const cachedHead = this.getCachedRouteHead(match.route.fullPath, match.params, match.search);
|
|
441
|
+
if (cachedHead) {
|
|
442
|
+
resolvedHeadByRoute.set(match.route.fullPath, cachedHead);
|
|
352
443
|
continue;
|
|
353
444
|
}
|
|
354
|
-
|
|
445
|
+
needsDocumentHeadFetch = true;
|
|
446
|
+
}
|
|
447
|
+
if (!needsDocumentHeadFetch) {
|
|
448
|
+
return import_core.resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
449
|
+
}
|
|
450
|
+
const documentHead = await this.fetchDocumentHead(location);
|
|
451
|
+
if ((documentHead.routeHeads?.length ?? 0) === 0 && !matchesHaveInlineHead(matches)) {
|
|
452
|
+
return documentHead.head;
|
|
453
|
+
}
|
|
454
|
+
const routeHeadsById = this.cacheRouteHeadsFromDocument(matches, documentHead.routeHeads ?? []);
|
|
455
|
+
for (const match of serverMatches) {
|
|
456
|
+
const responseHead = routeHeadsById.get(match.route.fullPath);
|
|
457
|
+
if (responseHead) {
|
|
458
|
+
resolvedHeadByRoute.set(match.route.fullPath, responseHead);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const cachedHead = this.getCachedRouteHead(match.route.fullPath, match.params, match.search);
|
|
462
|
+
if (cachedHead) {
|
|
463
|
+
resolvedHeadByRoute.set(match.route.fullPath, cachedHead);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
const response = await this.fetchRouteHead(match.route, match.params, match.search);
|
|
467
|
+
this.setRouteHeadCache(match.route.fullPath, match.params, match.search, response);
|
|
468
|
+
resolvedHeadByRoute.set(match.route.fullPath, response.head);
|
|
355
469
|
}
|
|
356
470
|
return import_core.resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
357
471
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
routeId
|
|
472
|
+
getRouteHeadCacheKey(routeId, params, search) {
|
|
473
|
+
return JSON.stringify({
|
|
474
|
+
routeId,
|
|
361
475
|
params,
|
|
362
476
|
search
|
|
363
477
|
});
|
|
364
|
-
|
|
478
|
+
}
|
|
479
|
+
getCachedRouteHead(routeId, params, search) {
|
|
480
|
+
const cached = this.headCache.get(this.getRouteHeadCacheKey(routeId, params, search));
|
|
365
481
|
if (cached && cached.expiresAt > Date.now()) {
|
|
366
482
|
return cached.head;
|
|
367
483
|
}
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
setRouteHeadCache(routeId, params, search, response) {
|
|
487
|
+
this.headCache.set(this.getRouteHeadCacheKey(routeId, params, search), {
|
|
488
|
+
head: response.head,
|
|
489
|
+
expiresAt: Date.now() + (response.staleTime ?? 0)
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
seedHeadCacheFromRouteHeads(matches, routeHeads) {
|
|
493
|
+
this.cacheRouteHeadsFromDocument(matches, routeHeads);
|
|
494
|
+
}
|
|
495
|
+
cacheRouteHeadsFromDocument(matches, routeHeads) {
|
|
496
|
+
const routeHeadsById = new Map(routeHeads.map((entry) => [entry.routeId, entry]));
|
|
497
|
+
const resolvedHeadByRoute = new Map;
|
|
498
|
+
for (const match of matches) {
|
|
499
|
+
if (!match.route.serverHead) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
const entry = routeHeadsById.get(match.route.fullPath);
|
|
503
|
+
if (!entry) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
this.setRouteHeadCache(match.route.fullPath, match.params, match.search, entry);
|
|
507
|
+
resolvedHeadByRoute.set(match.route.fullPath, entry.head);
|
|
508
|
+
}
|
|
509
|
+
return resolvedHeadByRoute;
|
|
510
|
+
}
|
|
511
|
+
async loadRouteHead(route, params, search, location, request) {
|
|
512
|
+
const cachedHead = this.getCachedRouteHead(route.fullPath, params, search);
|
|
513
|
+
if (cachedHead) {
|
|
514
|
+
return cachedHead;
|
|
515
|
+
}
|
|
368
516
|
const response = this.options.loadRouteHead !== undefined ? await this.options.loadRouteHead({
|
|
369
517
|
route,
|
|
370
518
|
routeId: route.fullPath,
|
|
@@ -373,14 +521,11 @@ class Router {
|
|
|
373
521
|
location,
|
|
374
522
|
request
|
|
375
523
|
}) : await this.fetchRouteHead(route, params, search);
|
|
376
|
-
this.
|
|
377
|
-
head: response.head,
|
|
378
|
-
expiresAt: Date.now() + (response.staleTime ?? 0)
|
|
379
|
-
});
|
|
524
|
+
this.setRouteHeadCache(route.fullPath, params, search, response);
|
|
380
525
|
return response.head;
|
|
381
526
|
}
|
|
382
527
|
async fetchRouteHead(route, params, search) {
|
|
383
|
-
const basePath = this.options.headBasePath ?? "/head-api";
|
|
528
|
+
const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
|
|
384
529
|
const searchParams = new URLSearchParams({
|
|
385
530
|
routeId: route.fullPath,
|
|
386
531
|
params: JSON.stringify(params),
|
|
@@ -395,6 +540,20 @@ class Router {
|
|
|
395
540
|
}
|
|
396
541
|
return await response.json();
|
|
397
542
|
}
|
|
543
|
+
async fetchDocumentHead(location) {
|
|
544
|
+
const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
|
|
545
|
+
const searchParams = new URLSearchParams({
|
|
546
|
+
href: prependBasePathToHref(location.href, this.basePath)
|
|
547
|
+
});
|
|
548
|
+
const response = await fetch(`${basePath}?${searchParams.toString()}`);
|
|
549
|
+
if (!response.ok) {
|
|
550
|
+
if (response.status === 404) {
|
|
551
|
+
throw import_core.notFound();
|
|
552
|
+
}
|
|
553
|
+
throw new Error(`Failed to resolve server head for location "${location.href}"`);
|
|
554
|
+
}
|
|
555
|
+
return await response.json();
|
|
556
|
+
}
|
|
398
557
|
async commitLocation(location, options) {
|
|
399
558
|
this.state = {
|
|
400
559
|
...this.state,
|
|
@@ -404,13 +563,15 @@ class Router {
|
|
|
404
563
|
this.notify();
|
|
405
564
|
try {
|
|
406
565
|
const resolved = await this.resolveLocation(location, {
|
|
407
|
-
request: options.request
|
|
566
|
+
request: options.request,
|
|
567
|
+
initialHeadSnapshot: options.initialHeadSnapshot
|
|
408
568
|
});
|
|
569
|
+
const historyHref = prependBasePathToHref(location.href, this.basePath);
|
|
409
570
|
if (options.writeHistory) {
|
|
410
571
|
if (options.replace) {
|
|
411
|
-
this.history.replace(
|
|
572
|
+
this.history.replace(historyHref, location.state);
|
|
412
573
|
} else {
|
|
413
|
-
this.history.push(
|
|
574
|
+
this.history.push(historyHref, location.state);
|
|
414
575
|
}
|
|
415
576
|
}
|
|
416
577
|
this.state = {
|
|
@@ -431,11 +592,12 @@ class Router {
|
|
|
431
592
|
return;
|
|
432
593
|
}
|
|
433
594
|
const errorMatches = this.buildMatches(location);
|
|
595
|
+
const historyHref = prependBasePathToHref(location.href, this.basePath);
|
|
434
596
|
if (options.writeHistory) {
|
|
435
597
|
if (options.replace) {
|
|
436
|
-
this.history.replace(
|
|
598
|
+
this.history.replace(historyHref, location.state);
|
|
437
599
|
} else {
|
|
438
|
-
this.history.push(
|
|
600
|
+
this.history.push(historyHref, location.state);
|
|
439
601
|
}
|
|
440
602
|
}
|
|
441
603
|
this.state = {
|
|
@@ -525,61 +687,95 @@ function createManagedHeadElements(head) {
|
|
|
525
687
|
element.setAttribute(MANAGED_HEAD_ATTRIBUTE, "true");
|
|
526
688
|
return element;
|
|
527
689
|
};
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
690
|
+
const setAttributes = (element, attributes) => {
|
|
691
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
692
|
+
if (value === undefined || value === false) {
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
if (value === true) {
|
|
696
|
+
element.setAttribute(key, "");
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
element.setAttribute(key, value);
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
for (const element of head) {
|
|
703
|
+
if (element.tag === "title") {
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (element.tag === "meta") {
|
|
707
|
+
const tag2 = managed(document.createElement("meta"));
|
|
708
|
+
if ("charset" in element) {
|
|
709
|
+
tag2.setAttribute("charset", element.charset);
|
|
710
|
+
} else if ("name" in element) {
|
|
711
|
+
tag2.setAttribute("name", element.name);
|
|
712
|
+
tag2.setAttribute("content", element.content);
|
|
713
|
+
} else if ("property" in element) {
|
|
714
|
+
tag2.setAttribute("property", element.property);
|
|
715
|
+
tag2.setAttribute("content", element.content);
|
|
716
|
+
} else {
|
|
717
|
+
tag2.setAttribute("http-equiv", element.httpEquiv);
|
|
718
|
+
tag2.setAttribute("content", element.content);
|
|
719
|
+
}
|
|
720
|
+
elements.push(tag2);
|
|
533
721
|
continue;
|
|
534
722
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
723
|
+
if (element.tag === "link") {
|
|
724
|
+
const tag2 = managed(document.createElement("link"));
|
|
725
|
+
setAttributes(tag2, {
|
|
726
|
+
rel: element.rel,
|
|
727
|
+
href: element.href,
|
|
728
|
+
type: element.type,
|
|
729
|
+
media: element.media,
|
|
730
|
+
sizes: element.sizes,
|
|
731
|
+
crossorigin: element.crossorigin
|
|
732
|
+
});
|
|
733
|
+
elements.push(tag2);
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if (element.tag === "style") {
|
|
737
|
+
const tag2 = managed(document.createElement("style"));
|
|
738
|
+
if (element.media) {
|
|
739
|
+
tag2.setAttribute("media", element.media);
|
|
740
|
+
}
|
|
741
|
+
tag2.textContent = element.children;
|
|
742
|
+
elements.push(tag2);
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (element.tag === "script") {
|
|
746
|
+
const tag2 = managed(document.createElement("script"));
|
|
747
|
+
if (element.src) {
|
|
748
|
+
tag2.setAttribute("src", element.src);
|
|
749
|
+
}
|
|
750
|
+
if (element.type) {
|
|
751
|
+
tag2.setAttribute("type", element.type);
|
|
752
|
+
}
|
|
753
|
+
if (element.async) {
|
|
754
|
+
tag2.async = true;
|
|
755
|
+
}
|
|
756
|
+
if (element.defer) {
|
|
757
|
+
tag2.defer = true;
|
|
758
|
+
}
|
|
759
|
+
if (element.children) {
|
|
760
|
+
tag2.textContent = element.children;
|
|
761
|
+
}
|
|
762
|
+
elements.push(tag2);
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if (element.tag === "base") {
|
|
766
|
+
const tag2 = managed(document.createElement("base"));
|
|
767
|
+
tag2.setAttribute("href", element.href);
|
|
768
|
+
if (element.target) {
|
|
769
|
+
tag2.setAttribute("target", element.target);
|
|
770
|
+
}
|
|
771
|
+
elements.push(tag2);
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
const tag = managed(document.createElement(element.name));
|
|
775
|
+
setAttributes(tag, element.attrs ?? {});
|
|
776
|
+
if (element.children) {
|
|
777
|
+
tag.textContent = element.children;
|
|
547
778
|
}
|
|
548
|
-
elements.push(tag);
|
|
549
|
-
}
|
|
550
|
-
for (const link of head.links ?? []) {
|
|
551
|
-
const tag = managed(document.createElement("link"));
|
|
552
|
-
tag.setAttribute("rel", link.rel);
|
|
553
|
-
tag.setAttribute("href", link.href);
|
|
554
|
-
if (link.type)
|
|
555
|
-
tag.setAttribute("type", link.type);
|
|
556
|
-
if (link.media)
|
|
557
|
-
tag.setAttribute("media", link.media);
|
|
558
|
-
if (link.sizes)
|
|
559
|
-
tag.setAttribute("sizes", link.sizes);
|
|
560
|
-
if (link.crossorigin)
|
|
561
|
-
tag.setAttribute("crossorigin", link.crossorigin);
|
|
562
|
-
elements.push(tag);
|
|
563
|
-
}
|
|
564
|
-
for (const style of head.styles ?? []) {
|
|
565
|
-
const tag = managed(document.createElement("style"));
|
|
566
|
-
if (style.media)
|
|
567
|
-
tag.setAttribute("media", style.media);
|
|
568
|
-
tag.textContent = style.children;
|
|
569
|
-
elements.push(tag);
|
|
570
|
-
}
|
|
571
|
-
for (const script of head.scripts ?? []) {
|
|
572
|
-
const tag = managed(document.createElement("script"));
|
|
573
|
-
if (script.src)
|
|
574
|
-
tag.setAttribute("src", script.src);
|
|
575
|
-
if (script.type)
|
|
576
|
-
tag.setAttribute("type", script.type);
|
|
577
|
-
if (script.async)
|
|
578
|
-
tag.async = true;
|
|
579
|
-
if (script.defer)
|
|
580
|
-
tag.defer = true;
|
|
581
|
-
if (script.children)
|
|
582
|
-
tag.textContent = script.children;
|
|
583
779
|
elements.push(tag);
|
|
584
780
|
}
|
|
585
781
|
return elements;
|
|
@@ -588,6 +784,10 @@ function reconcileDocumentHead(head) {
|
|
|
588
784
|
if (typeof document === "undefined") {
|
|
589
785
|
return;
|
|
590
786
|
}
|
|
787
|
+
const title = [...head].reverse().find((element) => element.tag === "title");
|
|
788
|
+
if (title && title.tag === "title") {
|
|
789
|
+
document.title = title.children;
|
|
790
|
+
}
|
|
591
791
|
for (const element of Array.from(document.head.querySelectorAll(`[${MANAGED_HEAD_ATTRIBUTE}]`))) {
|
|
592
792
|
element.remove();
|
|
593
793
|
}
|
|
@@ -732,7 +932,7 @@ function useResolvedLink(props) {
|
|
|
732
932
|
const router = useRouterContext();
|
|
733
933
|
const href = router.buildHref(props);
|
|
734
934
|
const location = useLocation();
|
|
735
|
-
const pathOnly = href.split(/[?#]/u)[0] ?? href;
|
|
935
|
+
const pathOnly = stripBasePathFromHref(href, router.options.basePath).split(/[?#]/u)[0] ?? href;
|
|
736
936
|
const isActive = pathOnly === location.pathname;
|
|
737
937
|
return { href, isActive, router };
|
|
738
938
|
}
|