@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/cjs/router.cjs
CHANGED
|
@@ -103,13 +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 =
|
|
107
|
-
function
|
|
108
|
-
return
|
|
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}`;
|
|
109
157
|
}
|
|
110
158
|
function routeHasRecord(value) {
|
|
111
159
|
return typeof value === "object" && value !== null;
|
|
112
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
|
+
}
|
|
113
168
|
function resolveParamsInput(input, previous) {
|
|
114
169
|
if (input === undefined) {
|
|
115
170
|
return previous;
|
|
@@ -155,6 +210,8 @@ class Router {
|
|
|
155
210
|
headCache = new Map;
|
|
156
211
|
parseSearch;
|
|
157
212
|
stringifySearch;
|
|
213
|
+
basePath;
|
|
214
|
+
initialHeadSnapshot;
|
|
158
215
|
started = false;
|
|
159
216
|
unsubscribeHistory;
|
|
160
217
|
constructor(options) {
|
|
@@ -163,6 +220,7 @@ class Router {
|
|
|
163
220
|
this.history = options.history ?? (typeof window === "undefined" ? import_history.createMemoryHistory() : import_history.createBrowserHistory());
|
|
164
221
|
this.parseSearch = options.parseSearch ?? import_core.defaultParseSearch;
|
|
165
222
|
this.stringifySearch = options.stringifySearch ?? import_core.defaultStringifySearch;
|
|
223
|
+
this.basePath = normalizeBasePath(options.basePath);
|
|
166
224
|
for (const route of import_core.collectRoutes(this.routeTree)) {
|
|
167
225
|
this.routesByFullPath.set(route.fullPath, route);
|
|
168
226
|
}
|
|
@@ -170,15 +228,22 @@ class Router {
|
|
|
170
228
|
this.routesByTo.set(branch.leaf.to, branch.leaf);
|
|
171
229
|
}
|
|
172
230
|
const location = this.readLocation();
|
|
231
|
+
const initialMatches = this.buildMatches(location);
|
|
232
|
+
const rawHistoryHref = this.history.location.href;
|
|
173
233
|
const initialHeadSnapshot = typeof window !== "undefined" ? window.__RICHIE_ROUTER_HEAD__ : undefined;
|
|
174
|
-
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
|
+
}
|
|
175
239
|
if (typeof window !== "undefined" && initialHeadSnapshot !== undefined) {
|
|
176
240
|
delete window.__RICHIE_ROUTER_HEAD__;
|
|
177
241
|
}
|
|
242
|
+
this.initialHeadSnapshot = hasMatchingInitialHeadSnapshot ? initialHeadSnapshot : undefined;
|
|
178
243
|
this.state = {
|
|
179
244
|
status: "loading",
|
|
180
245
|
location,
|
|
181
|
-
matches:
|
|
246
|
+
matches: initialMatches,
|
|
182
247
|
head: initialHead,
|
|
183
248
|
error: null
|
|
184
249
|
};
|
|
@@ -209,15 +274,17 @@ class Router {
|
|
|
209
274
|
}
|
|
210
275
|
async load(options) {
|
|
211
276
|
const nextLocation = this.readLocation();
|
|
277
|
+
const initialHeadSnapshot = this.initialHeadSnapshot?.href === nextLocation.href ? this.initialHeadSnapshot : undefined;
|
|
278
|
+
this.initialHeadSnapshot = undefined;
|
|
212
279
|
await this.commitLocation(nextLocation, {
|
|
213
280
|
request: options?.request,
|
|
214
281
|
replace: true,
|
|
215
|
-
writeHistory: false
|
|
282
|
+
writeHistory: false,
|
|
283
|
+
initialHeadSnapshot
|
|
216
284
|
});
|
|
217
285
|
}
|
|
218
286
|
async navigate(options) {
|
|
219
|
-
const
|
|
220
|
-
const location = import_core.createParsedLocation(href, options.state ?? null, this.parseSearch);
|
|
287
|
+
const location = this.buildLocation(options);
|
|
221
288
|
await this.commitLocation(location, {
|
|
222
289
|
replace: options.replace ?? false,
|
|
223
290
|
writeHistory: true,
|
|
@@ -225,8 +292,7 @@ class Router {
|
|
|
225
292
|
});
|
|
226
293
|
}
|
|
227
294
|
async preloadRoute(options) {
|
|
228
|
-
const
|
|
229
|
-
const location = import_core.createParsedLocation(href, options.state ?? null, this.parseSearch);
|
|
295
|
+
const location = this.buildLocation(options);
|
|
230
296
|
try {
|
|
231
297
|
await this.resolveLocation(location);
|
|
232
298
|
} catch {}
|
|
@@ -236,6 +302,12 @@ class Router {
|
|
|
236
302
|
await this.load();
|
|
237
303
|
}
|
|
238
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) {
|
|
239
311
|
const targetRoute = this.routesByTo.get(options.to) ?? null;
|
|
240
312
|
const fromMatch = options.from ? this.findMatchByTo(options.from) : null;
|
|
241
313
|
const previousParams = fromMatch?.params ?? {};
|
|
@@ -250,7 +322,7 @@ class Router {
|
|
|
250
322
|
}
|
|
251
323
|
readLocation() {
|
|
252
324
|
const location = this.history.location;
|
|
253
|
-
return import_core.createParsedLocation(location.href, location.state, this.parseSearch);
|
|
325
|
+
return import_core.createParsedLocation(stripBasePathFromHref(location.href, this.basePath), location.state, this.parseSearch);
|
|
254
326
|
}
|
|
255
327
|
applyTrailingSlash(pathname, route) {
|
|
256
328
|
const trailingSlash = this.options.trailingSlash ?? "preserve";
|
|
@@ -303,13 +375,9 @@ class Router {
|
|
|
303
375
|
});
|
|
304
376
|
}
|
|
305
377
|
resolveSearch(route, rawSearch) {
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
return {
|
|
310
|
-
...routeHasRecord(fromHeadTagSchema) ? fromHeadTagSchema : {},
|
|
311
|
-
...routeHasRecord(fromRoute) ? fromRoute : {}
|
|
312
|
-
};
|
|
378
|
+
const fromSchema = route.searchSchema ? route.searchSchema.parse(rawSearch) : {};
|
|
379
|
+
if (routeHasRecord(fromSchema)) {
|
|
380
|
+
return fromSchema;
|
|
313
381
|
}
|
|
314
382
|
return rawSearch;
|
|
315
383
|
}
|
|
@@ -349,57 +417,140 @@ class Router {
|
|
|
349
417
|
to: route.to
|
|
350
418
|
});
|
|
351
419
|
}
|
|
352
|
-
const head = await this.resolveLocationHead(matches, location, options?.request);
|
|
420
|
+
const head = await this.resolveLocationHead(matches, location, options?.request, options?.initialHeadSnapshot);
|
|
353
421
|
return { matches, head, error: null };
|
|
354
422
|
}
|
|
355
|
-
async resolveLocationHead(matches, location, request) {
|
|
423
|
+
async resolveLocationHead(matches, location, request, initialHeadSnapshot) {
|
|
356
424
|
const resolvedHeadByRoute = new Map;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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);
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
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);
|
|
360
464
|
continue;
|
|
361
465
|
}
|
|
362
|
-
|
|
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);
|
|
363
469
|
}
|
|
364
470
|
return import_core.resolveHeadConfig(matches, resolvedHeadByRoute);
|
|
365
471
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
472
|
+
getRouteHeadCacheKey(routeId, params, search) {
|
|
473
|
+
return JSON.stringify({
|
|
474
|
+
routeId,
|
|
369
475
|
params,
|
|
370
476
|
search
|
|
371
477
|
});
|
|
372
|
-
|
|
478
|
+
}
|
|
479
|
+
getCachedRouteHead(routeId, params, search) {
|
|
480
|
+
const cached = this.headCache.get(this.getRouteHeadCacheKey(routeId, params, search));
|
|
373
481
|
if (cached && cached.expiresAt > Date.now()) {
|
|
374
482
|
return cached.head;
|
|
375
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
|
+
}
|
|
376
516
|
const response = this.options.loadRouteHead !== undefined ? await this.options.loadRouteHead({
|
|
377
517
|
route,
|
|
378
|
-
|
|
518
|
+
routeId: route.fullPath,
|
|
379
519
|
params,
|
|
380
520
|
search,
|
|
381
521
|
location,
|
|
382
522
|
request
|
|
383
|
-
}) : await this.fetchRouteHead(route,
|
|
384
|
-
this.
|
|
385
|
-
head: response.head,
|
|
386
|
-
expiresAt: Date.now() + (response.staleTime ?? 0)
|
|
387
|
-
});
|
|
523
|
+
}) : await this.fetchRouteHead(route, params, search);
|
|
524
|
+
this.setRouteHeadCache(route.fullPath, params, search, response);
|
|
388
525
|
return response.head;
|
|
389
526
|
}
|
|
390
|
-
async fetchRouteHead(route,
|
|
391
|
-
const basePath = this.options.headBasePath ?? "/head-api";
|
|
527
|
+
async fetchRouteHead(route, params, search) {
|
|
528
|
+
const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
|
|
392
529
|
const searchParams = new URLSearchParams({
|
|
393
530
|
routeId: route.fullPath,
|
|
394
531
|
params: JSON.stringify(params),
|
|
395
532
|
search: JSON.stringify(search)
|
|
396
533
|
});
|
|
397
|
-
const response = await fetch(`${basePath}
|
|
534
|
+
const response = await fetch(`${basePath}?${searchParams.toString()}`);
|
|
535
|
+
if (!response.ok) {
|
|
536
|
+
if (response.status === 404) {
|
|
537
|
+
throw import_core.notFound();
|
|
538
|
+
}
|
|
539
|
+
throw new Error(`Failed to resolve server head for route "${route.fullPath}"`);
|
|
540
|
+
}
|
|
541
|
+
return await response.json();
|
|
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()}`);
|
|
398
549
|
if (!response.ok) {
|
|
399
550
|
if (response.status === 404) {
|
|
400
551
|
throw import_core.notFound();
|
|
401
552
|
}
|
|
402
|
-
throw new Error(`Failed to resolve head
|
|
553
|
+
throw new Error(`Failed to resolve server head for location "${location.href}"`);
|
|
403
554
|
}
|
|
404
555
|
return await response.json();
|
|
405
556
|
}
|
|
@@ -412,13 +563,15 @@ class Router {
|
|
|
412
563
|
this.notify();
|
|
413
564
|
try {
|
|
414
565
|
const resolved = await this.resolveLocation(location, {
|
|
415
|
-
request: options.request
|
|
566
|
+
request: options.request,
|
|
567
|
+
initialHeadSnapshot: options.initialHeadSnapshot
|
|
416
568
|
});
|
|
569
|
+
const historyHref = prependBasePathToHref(location.href, this.basePath);
|
|
417
570
|
if (options.writeHistory) {
|
|
418
571
|
if (options.replace) {
|
|
419
|
-
this.history.replace(
|
|
572
|
+
this.history.replace(historyHref, location.state);
|
|
420
573
|
} else {
|
|
421
|
-
this.history.push(
|
|
574
|
+
this.history.push(historyHref, location.state);
|
|
422
575
|
}
|
|
423
576
|
}
|
|
424
577
|
this.state = {
|
|
@@ -439,11 +592,12 @@ class Router {
|
|
|
439
592
|
return;
|
|
440
593
|
}
|
|
441
594
|
const errorMatches = this.buildMatches(location);
|
|
595
|
+
const historyHref = prependBasePathToHref(location.href, this.basePath);
|
|
442
596
|
if (options.writeHistory) {
|
|
443
597
|
if (options.replace) {
|
|
444
|
-
this.history.replace(
|
|
598
|
+
this.history.replace(historyHref, location.state);
|
|
445
599
|
} else {
|
|
446
|
-
this.history.push(
|
|
600
|
+
this.history.push(historyHref, location.state);
|
|
447
601
|
}
|
|
448
602
|
}
|
|
449
603
|
this.state = {
|
|
@@ -533,61 +687,95 @@ function createManagedHeadElements(head) {
|
|
|
533
687
|
element.setAttribute(MANAGED_HEAD_ATTRIBUTE, "true");
|
|
534
688
|
return element;
|
|
535
689
|
};
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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);
|
|
541
721
|
continue;
|
|
542
722
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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;
|
|
555
778
|
}
|
|
556
|
-
elements.push(tag);
|
|
557
|
-
}
|
|
558
|
-
for (const link of head.links ?? []) {
|
|
559
|
-
const tag = managed(document.createElement("link"));
|
|
560
|
-
tag.setAttribute("rel", link.rel);
|
|
561
|
-
tag.setAttribute("href", link.href);
|
|
562
|
-
if (link.type)
|
|
563
|
-
tag.setAttribute("type", link.type);
|
|
564
|
-
if (link.media)
|
|
565
|
-
tag.setAttribute("media", link.media);
|
|
566
|
-
if (link.sizes)
|
|
567
|
-
tag.setAttribute("sizes", link.sizes);
|
|
568
|
-
if (link.crossorigin)
|
|
569
|
-
tag.setAttribute("crossorigin", link.crossorigin);
|
|
570
|
-
elements.push(tag);
|
|
571
|
-
}
|
|
572
|
-
for (const style of head.styles ?? []) {
|
|
573
|
-
const tag = managed(document.createElement("style"));
|
|
574
|
-
if (style.media)
|
|
575
|
-
tag.setAttribute("media", style.media);
|
|
576
|
-
tag.textContent = style.children;
|
|
577
|
-
elements.push(tag);
|
|
578
|
-
}
|
|
579
|
-
for (const script of head.scripts ?? []) {
|
|
580
|
-
const tag = managed(document.createElement("script"));
|
|
581
|
-
if (script.src)
|
|
582
|
-
tag.setAttribute("src", script.src);
|
|
583
|
-
if (script.type)
|
|
584
|
-
tag.setAttribute("type", script.type);
|
|
585
|
-
if (script.async)
|
|
586
|
-
tag.async = true;
|
|
587
|
-
if (script.defer)
|
|
588
|
-
tag.defer = true;
|
|
589
|
-
if (script.children)
|
|
590
|
-
tag.textContent = script.children;
|
|
591
779
|
elements.push(tag);
|
|
592
780
|
}
|
|
593
781
|
return elements;
|
|
@@ -596,6 +784,10 @@ function reconcileDocumentHead(head) {
|
|
|
596
784
|
if (typeof document === "undefined") {
|
|
597
785
|
return;
|
|
598
786
|
}
|
|
787
|
+
const title = [...head].reverse().find((element) => element.tag === "title");
|
|
788
|
+
if (title && title.tag === "title") {
|
|
789
|
+
document.title = title.children;
|
|
790
|
+
}
|
|
599
791
|
for (const element of Array.from(document.head.querySelectorAll(`[${MANAGED_HEAD_ATTRIBUTE}]`))) {
|
|
600
792
|
element.remove();
|
|
601
793
|
}
|
|
@@ -740,7 +932,7 @@ function useResolvedLink(props) {
|
|
|
740
932
|
const router = useRouterContext();
|
|
741
933
|
const href = router.buildHref(props);
|
|
742
934
|
const location = useLocation();
|
|
743
|
-
const pathOnly = href.split(/[?#]/u)[0] ?? href;
|
|
935
|
+
const pathOnly = stripBasePathFromHref(href, router.options.basePath).split(/[?#]/u)[0] ?? href;
|
|
744
936
|
const isActive = pathOnly === location.pathname;
|
|
745
937
|
return { href, isActive, router };
|
|
746
938
|
}
|