@chidchanun/bcp 0.1.8 → 0.1.9
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/CHANGELOG.md +41 -0
- package/README.md +82 -4
- package/docs/application-modules.md +63 -3
- package/docs/server-data-loaders.md +240 -0
- package/package.json +1 -1
- package/packages/bundler/src/server-production.ts +25 -0
- package/packages/client/src/index.tsx +4 -0
- package/packages/client/src/loader-data.tsx +99 -0
- package/packages/client/src/router-v2.tsx +254 -29
- package/packages/server/src/dev-navigation-target.ts +188 -0
- package/packages/server/src/index.ts +209 -119
- package/packages/server/src/navigation-payload.ts +24 -1
- package/packages/server/src/navigation-response.ts +284 -0
- package/packages/server/src/page-loader.ts +346 -0
- package/packages/server/src/standalone-production-runtime-v2-navigation.ts +785 -0
- package/packages/server/src/standalone-production-runtime-v2.ts +131 -19
- package/packages/server/src/standalone-production-runtime-v3.ts +1 -1
- package/packages/server/src/standalone-production-runtime-v4.ts +42 -2
- package/packages/server/src/static-dev-server.ts +21 -17
|
@@ -51,9 +51,9 @@ export interface LinkProps
|
|
|
51
51
|
scroll?: boolean;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
interface
|
|
54
|
+
interface NavigationRoutePayload {
|
|
55
55
|
route?: string;
|
|
56
|
-
params?: Record<string,
|
|
56
|
+
params?: Record<string, unknown>;
|
|
57
57
|
clientScript: string;
|
|
58
58
|
clientImports: string[];
|
|
59
59
|
title: string;
|
|
@@ -62,6 +62,17 @@ interface NavigationPayload {
|
|
|
62
62
|
loadingHtml: string | null;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
interface NavigationRedirectPayload {
|
|
66
|
+
redirect: {
|
|
67
|
+
location: string;
|
|
68
|
+
status: number;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type NavigationPayload =
|
|
73
|
+
| NavigationRoutePayload
|
|
74
|
+
| NavigationRedirectPayload;
|
|
75
|
+
|
|
65
76
|
interface BcpDevConfig {
|
|
66
77
|
eventsPath: string;
|
|
67
78
|
clientScript: string;
|
|
@@ -102,6 +113,12 @@ declare global {
|
|
|
102
113
|
let navigationController:
|
|
103
114
|
AbortController | null = null;
|
|
104
115
|
|
|
116
|
+
let navigationSequence =
|
|
117
|
+
0;
|
|
118
|
+
|
|
119
|
+
const MAX_REDIRECTS =
|
|
120
|
+
10;
|
|
121
|
+
|
|
105
122
|
export async function navigate(
|
|
106
123
|
href: string,
|
|
107
124
|
options: NavigationOptions = {}
|
|
@@ -354,13 +371,31 @@ async function loadNavigation(
|
|
|
354
371
|
target: URL,
|
|
355
372
|
options: NavigationOptions & {
|
|
356
373
|
fromHistory?: boolean;
|
|
374
|
+
redirectCount?: number;
|
|
357
375
|
}
|
|
358
376
|
): Promise<void> {
|
|
359
377
|
ensureNavigationRuntime();
|
|
378
|
+
|
|
379
|
+
const redirectCount =
|
|
380
|
+
options.redirectCount ??
|
|
381
|
+
0;
|
|
382
|
+
|
|
383
|
+
if (
|
|
384
|
+
redirectCount >
|
|
385
|
+
MAX_REDIRECTS
|
|
386
|
+
) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`BCP Framework: navigation exceeded ${MAX_REDIRECTS} redirects.`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
360
392
|
navigationController?.abort();
|
|
361
393
|
|
|
362
394
|
const controller =
|
|
363
395
|
new AbortController();
|
|
396
|
+
const navigationId =
|
|
397
|
+
++navigationSequence;
|
|
398
|
+
|
|
364
399
|
navigationController =
|
|
365
400
|
controller;
|
|
366
401
|
|
|
@@ -383,6 +418,8 @@ async function loadNavigation(
|
|
|
383
418
|
)}`,
|
|
384
419
|
{
|
|
385
420
|
method: "GET",
|
|
421
|
+
credentials:
|
|
422
|
+
"same-origin",
|
|
386
423
|
headers: {
|
|
387
424
|
Accept:
|
|
388
425
|
"application/json",
|
|
@@ -394,6 +431,11 @@ async function loadNavigation(
|
|
|
394
431
|
}
|
|
395
432
|
);
|
|
396
433
|
|
|
434
|
+
assertCurrentNavigation(
|
|
435
|
+
controller,
|
|
436
|
+
navigationId
|
|
437
|
+
);
|
|
438
|
+
|
|
397
439
|
if (!response.ok) {
|
|
398
440
|
window.location.assign(
|
|
399
441
|
target.href
|
|
@@ -406,6 +448,11 @@ async function loadNavigation(
|
|
|
406
448
|
response
|
|
407
449
|
);
|
|
408
450
|
|
|
451
|
+
assertCurrentNavigation(
|
|
452
|
+
controller,
|
|
453
|
+
navigationId
|
|
454
|
+
);
|
|
455
|
+
|
|
409
456
|
if (!payload) {
|
|
410
457
|
window.location.assign(
|
|
411
458
|
target.href
|
|
@@ -413,6 +460,54 @@ async function loadNavigation(
|
|
|
413
460
|
return;
|
|
414
461
|
}
|
|
415
462
|
|
|
463
|
+
if (
|
|
464
|
+
isNavigationRedirectPayload(
|
|
465
|
+
payload
|
|
466
|
+
)
|
|
467
|
+
) {
|
|
468
|
+
const redirectTarget =
|
|
469
|
+
new URL(
|
|
470
|
+
payload.redirect.location,
|
|
471
|
+
target.href
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
if (
|
|
475
|
+
redirectTarget.origin !==
|
|
476
|
+
window.location.origin
|
|
477
|
+
) {
|
|
478
|
+
window.location.assign(
|
|
479
|
+
redirectTarget.href
|
|
480
|
+
);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const redirectedOptions =
|
|
485
|
+
options.fromHistory
|
|
486
|
+
? {
|
|
487
|
+
replace:
|
|
488
|
+
true,
|
|
489
|
+
scroll:
|
|
490
|
+
options.scroll,
|
|
491
|
+
fromHistory:
|
|
492
|
+
false,
|
|
493
|
+
redirectCount:
|
|
494
|
+
redirectCount +
|
|
495
|
+
1,
|
|
496
|
+
}
|
|
497
|
+
: {
|
|
498
|
+
...options,
|
|
499
|
+
redirectCount:
|
|
500
|
+
redirectCount +
|
|
501
|
+
1,
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
await loadNavigation(
|
|
505
|
+
redirectTarget,
|
|
506
|
+
redirectedOptions
|
|
507
|
+
);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
416
511
|
setNavigationState({
|
|
417
512
|
targetRoute:
|
|
418
513
|
payload.route ??
|
|
@@ -425,8 +520,15 @@ async function loadNavigation(
|
|
|
425
520
|
preloadClientImports(
|
|
426
521
|
payload.clientImports
|
|
427
522
|
);
|
|
523
|
+
|
|
524
|
+
assertCurrentNavigation(
|
|
525
|
+
controller,
|
|
526
|
+
navigationId
|
|
527
|
+
);
|
|
528
|
+
|
|
428
529
|
applyNavigationPayload(
|
|
429
|
-
payload
|
|
530
|
+
payload,
|
|
531
|
+
navigationId
|
|
430
532
|
);
|
|
431
533
|
|
|
432
534
|
if (!options.fromHistory) {
|
|
@@ -443,11 +545,29 @@ async function loadNavigation(
|
|
|
443
545
|
target
|
|
444
546
|
);
|
|
445
547
|
await restartDevRuntime();
|
|
548
|
+
|
|
549
|
+
assertCurrentNavigation(
|
|
550
|
+
controller,
|
|
551
|
+
navigationId
|
|
552
|
+
);
|
|
553
|
+
|
|
446
554
|
await importClientBundle(
|
|
447
|
-
payload.clientScript
|
|
555
|
+
payload.clientScript,
|
|
556
|
+
navigationId
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
assertCurrentNavigation(
|
|
560
|
+
controller,
|
|
561
|
+
navigationId
|
|
448
562
|
);
|
|
563
|
+
|
|
449
564
|
await waitForNextPaint();
|
|
450
565
|
|
|
566
|
+
assertCurrentNavigation(
|
|
567
|
+
controller,
|
|
568
|
+
navigationId
|
|
569
|
+
);
|
|
570
|
+
|
|
451
571
|
if (options.scroll !== false) {
|
|
452
572
|
if (target.hash) {
|
|
453
573
|
scrollToHash(
|
|
@@ -463,7 +583,9 @@ async function loadNavigation(
|
|
|
463
583
|
} finally {
|
|
464
584
|
if (
|
|
465
585
|
navigationController ===
|
|
466
|
-
|
|
586
|
+
controller &&
|
|
587
|
+
navigationId ===
|
|
588
|
+
navigationSequence
|
|
467
589
|
) {
|
|
468
590
|
navigationController =
|
|
469
591
|
null;
|
|
@@ -479,28 +601,41 @@ async function parseNavigationPayload(
|
|
|
479
601
|
try {
|
|
480
602
|
const payload =
|
|
481
603
|
await response.json() as
|
|
482
|
-
|
|
604
|
+
Record<string, unknown>;
|
|
483
605
|
|
|
484
606
|
if (
|
|
485
|
-
|
|
607
|
+
isNavigationRedirectPayload(
|
|
608
|
+
payload
|
|
609
|
+
)
|
|
610
|
+
) {
|
|
611
|
+
return payload;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const routePayload =
|
|
615
|
+
payload as Partial<
|
|
616
|
+
NavigationRoutePayload
|
|
617
|
+
>;
|
|
618
|
+
|
|
619
|
+
if (
|
|
620
|
+
typeof routePayload.clientScript !==
|
|
486
621
|
"string" ||
|
|
487
|
-
typeof
|
|
622
|
+
typeof routePayload.title !==
|
|
488
623
|
"string" ||
|
|
489
|
-
typeof
|
|
624
|
+
typeof routePayload.version !==
|
|
490
625
|
"number" ||
|
|
491
626
|
!(
|
|
492
|
-
|
|
493
|
-
typeof
|
|
627
|
+
routePayload.loadingHtml === null ||
|
|
628
|
+
typeof routePayload.loadingHtml ===
|
|
494
629
|
"string"
|
|
495
630
|
) ||
|
|
496
631
|
!(
|
|
497
|
-
|
|
632
|
+
routePayload.clientImports ===
|
|
498
633
|
undefined ||
|
|
499
634
|
(
|
|
500
635
|
Array.isArray(
|
|
501
|
-
|
|
636
|
+
routePayload.clientImports
|
|
502
637
|
) &&
|
|
503
|
-
|
|
638
|
+
routePayload.clientImports.every(
|
|
504
639
|
(item) =>
|
|
505
640
|
typeof item ===
|
|
506
641
|
"string"
|
|
@@ -508,10 +643,22 @@ async function parseNavigationPayload(
|
|
|
508
643
|
)
|
|
509
644
|
) ||
|
|
510
645
|
!(
|
|
511
|
-
|
|
512
|
-
|
|
646
|
+
routePayload.metadata === undefined ||
|
|
647
|
+
routePayload.metadata === null ||
|
|
513
648
|
isResolvedMetadata(
|
|
514
|
-
|
|
649
|
+
routePayload.metadata
|
|
650
|
+
)
|
|
651
|
+
) ||
|
|
652
|
+
!(
|
|
653
|
+
routePayload.params === undefined ||
|
|
654
|
+
(
|
|
655
|
+
typeof routePayload.params ===
|
|
656
|
+
"object" &&
|
|
657
|
+
routePayload.params !==
|
|
658
|
+
null &&
|
|
659
|
+
!Array.isArray(
|
|
660
|
+
routePayload.params
|
|
661
|
+
)
|
|
515
662
|
)
|
|
516
663
|
)
|
|
517
664
|
) {
|
|
@@ -520,29 +667,29 @@ async function parseNavigationPayload(
|
|
|
520
667
|
|
|
521
668
|
return {
|
|
522
669
|
route:
|
|
523
|
-
|
|
670
|
+
routePayload.route,
|
|
524
671
|
params:
|
|
525
|
-
|
|
672
|
+
routePayload.params ?? {},
|
|
526
673
|
clientScript:
|
|
527
674
|
stripQuery(
|
|
528
|
-
|
|
675
|
+
routePayload.clientScript
|
|
529
676
|
),
|
|
530
677
|
clientImports:
|
|
531
678
|
(
|
|
532
|
-
|
|
679
|
+
routePayload.clientImports ??
|
|
533
680
|
[]
|
|
534
681
|
).map(
|
|
535
682
|
stripQuery
|
|
536
683
|
),
|
|
537
684
|
title:
|
|
538
|
-
|
|
685
|
+
routePayload.title,
|
|
539
686
|
metadata:
|
|
540
|
-
|
|
687
|
+
routePayload.metadata ??
|
|
541
688
|
null,
|
|
542
689
|
version:
|
|
543
|
-
|
|
690
|
+
routePayload.version,
|
|
544
691
|
loadingHtml:
|
|
545
|
-
|
|
692
|
+
routePayload.loadingHtml,
|
|
546
693
|
};
|
|
547
694
|
} catch {
|
|
548
695
|
return null;
|
|
@@ -550,7 +697,8 @@ async function parseNavigationPayload(
|
|
|
550
697
|
}
|
|
551
698
|
|
|
552
699
|
function applyNavigationPayload(
|
|
553
|
-
payload:
|
|
700
|
+
payload: NavigationRoutePayload,
|
|
701
|
+
navigationId: number
|
|
554
702
|
): void {
|
|
555
703
|
if (
|
|
556
704
|
!document.getElementById(
|
|
@@ -582,6 +730,7 @@ function applyNavigationPayload(
|
|
|
582
730
|
payload.params ?? {},
|
|
583
731
|
metadata:
|
|
584
732
|
payload.metadata,
|
|
733
|
+
navigationId,
|
|
585
734
|
});
|
|
586
735
|
|
|
587
736
|
if (payload.metadata) {
|
|
@@ -644,7 +793,7 @@ function preloadClientImports(
|
|
|
644
793
|
}
|
|
645
794
|
|
|
646
795
|
function updateDevConfig(
|
|
647
|
-
payload:
|
|
796
|
+
payload: NavigationRoutePayload,
|
|
648
797
|
target: URL
|
|
649
798
|
): void {
|
|
650
799
|
const config =
|
|
@@ -747,7 +896,8 @@ async function restartDevRuntime(): Promise<void> {
|
|
|
747
896
|
}
|
|
748
897
|
|
|
749
898
|
async function importClientBundle(
|
|
750
|
-
clientScript: string
|
|
899
|
+
clientScript: string,
|
|
900
|
+
navigationId: number
|
|
751
901
|
): Promise<void> {
|
|
752
902
|
const separator =
|
|
753
903
|
clientScript.includes("?")
|
|
@@ -758,7 +908,82 @@ async function importClientBundle(
|
|
|
758
908
|
clientScript +
|
|
759
909
|
separator +
|
|
760
910
|
"bcp-nav=" +
|
|
761
|
-
|
|
911
|
+
encodeURIComponent(
|
|
912
|
+
navigationId
|
|
913
|
+
)
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function assertCurrentNavigation(
|
|
918
|
+
controller: AbortController,
|
|
919
|
+
navigationId: number
|
|
920
|
+
): void {
|
|
921
|
+
if (
|
|
922
|
+
controller.signal.aborted ||
|
|
923
|
+
navigationController !==
|
|
924
|
+
controller ||
|
|
925
|
+
navigationId !==
|
|
926
|
+
navigationSequence
|
|
927
|
+
) {
|
|
928
|
+
throw new DOMException(
|
|
929
|
+
"Navigation aborted.",
|
|
930
|
+
"AbortError"
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function isNavigationRedirectPayload(
|
|
936
|
+
value: unknown
|
|
937
|
+
): value is NavigationRedirectPayload {
|
|
938
|
+
if (
|
|
939
|
+
typeof value !==
|
|
940
|
+
"object" ||
|
|
941
|
+
value === null
|
|
942
|
+
) {
|
|
943
|
+
return false;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const redirect =
|
|
947
|
+
(
|
|
948
|
+
value as {
|
|
949
|
+
redirect?: unknown;
|
|
950
|
+
}
|
|
951
|
+
).redirect;
|
|
952
|
+
|
|
953
|
+
if (
|
|
954
|
+
typeof redirect !==
|
|
955
|
+
"object" ||
|
|
956
|
+
redirect === null
|
|
957
|
+
) {
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const candidate =
|
|
962
|
+
redirect as {
|
|
963
|
+
location?: unknown;
|
|
964
|
+
status?: unknown;
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
return (
|
|
968
|
+
typeof candidate.location ===
|
|
969
|
+
"string" &&
|
|
970
|
+
typeof candidate.status ===
|
|
971
|
+
"number" &&
|
|
972
|
+
isRedirectStatus(
|
|
973
|
+
candidate.status
|
|
974
|
+
)
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function isRedirectStatus(
|
|
979
|
+
status: number
|
|
980
|
+
): boolean {
|
|
981
|
+
return (
|
|
982
|
+
status === 301 ||
|
|
983
|
+
status === 302 ||
|
|
984
|
+
status === 303 ||
|
|
985
|
+
status === 307 ||
|
|
986
|
+
status === 308
|
|
762
987
|
);
|
|
763
988
|
}
|
|
764
989
|
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
matchRoute,
|
|
6
|
+
scanRoutes,
|
|
7
|
+
} from "../../router/src/index.js";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
resolveNavigationPayload,
|
|
11
|
+
} from "./navigation-payload.js";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
attachLoaderDataToParams,
|
|
15
|
+
executePageLoader,
|
|
16
|
+
} from "./page-loader.js";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
createNavigationJsonResponse,
|
|
20
|
+
createNavigationRedirectResponse,
|
|
21
|
+
} from "./navigation-response.js";
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
applyResponseCookies,
|
|
25
|
+
runWithRequestContext,
|
|
26
|
+
} from "./request-context.js";
|
|
27
|
+
|
|
28
|
+
export async function createDevNavigationTargetResponse(
|
|
29
|
+
req: http.IncomingMessage,
|
|
30
|
+
rootDirectory: string,
|
|
31
|
+
target: URL
|
|
32
|
+
): Promise<Response | null> {
|
|
33
|
+
const routes =
|
|
34
|
+
scanRoutes(
|
|
35
|
+
path.join(
|
|
36
|
+
rootDirectory,
|
|
37
|
+
"app"
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const match =
|
|
42
|
+
matchRoute(
|
|
43
|
+
routes,
|
|
44
|
+
normalizePathname(
|
|
45
|
+
target.pathname
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (!match) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const request =
|
|
54
|
+
createNavigationRequest(
|
|
55
|
+
req,
|
|
56
|
+
target
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return runWithRequestContext(
|
|
60
|
+
request,
|
|
61
|
+
async () => {
|
|
62
|
+
const loaderExecution =
|
|
63
|
+
await executePageLoader(
|
|
64
|
+
match.route.filePath,
|
|
65
|
+
match.params,
|
|
66
|
+
target,
|
|
67
|
+
Date.now()
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
loaderExecution.response
|
|
72
|
+
) {
|
|
73
|
+
const loaderResponse =
|
|
74
|
+
applyResponseCookies(
|
|
75
|
+
loaderExecution.response
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
createNavigationRedirectResponse(
|
|
80
|
+
loaderResponse
|
|
81
|
+
) ??
|
|
82
|
+
loaderResponse
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const payload =
|
|
87
|
+
await resolveNavigationPayload(
|
|
88
|
+
rootDirectory,
|
|
89
|
+
target.pathname,
|
|
90
|
+
{
|
|
91
|
+
allowLoaderRoute:
|
|
92
|
+
true,
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (!payload) {
|
|
97
|
+
return createNavigationJsonResponse(
|
|
98
|
+
{
|
|
99
|
+
error:
|
|
100
|
+
"Route not found.",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
status:
|
|
104
|
+
404,
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
payload.params =
|
|
110
|
+
attachLoaderDataToParams(
|
|
111
|
+
match.params,
|
|
112
|
+
loaderExecution
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
return applyResponseCookies(
|
|
116
|
+
createNavigationJsonResponse(
|
|
117
|
+
payload
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
remoteAddress:
|
|
123
|
+
req.socket
|
|
124
|
+
.remoteAddress ??
|
|
125
|
+
null,
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createNavigationRequest(
|
|
131
|
+
req: http.IncomingMessage,
|
|
132
|
+
target: URL
|
|
133
|
+
): Request {
|
|
134
|
+
const headers =
|
|
135
|
+
new Headers();
|
|
136
|
+
|
|
137
|
+
for (
|
|
138
|
+
const [key, value]
|
|
139
|
+
of Object.entries(
|
|
140
|
+
req.headers
|
|
141
|
+
)
|
|
142
|
+
) {
|
|
143
|
+
if (value === undefined) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (Array.isArray(value)) {
|
|
148
|
+
for (const item of value) {
|
|
149
|
+
headers.append(
|
|
150
|
+
key,
|
|
151
|
+
item
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
headers.set(
|
|
156
|
+
key,
|
|
157
|
+
value
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return new Request(
|
|
163
|
+
target,
|
|
164
|
+
{
|
|
165
|
+
method:
|
|
166
|
+
"GET",
|
|
167
|
+
headers,
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function normalizePathname(
|
|
173
|
+
pathname: string
|
|
174
|
+
): string {
|
|
175
|
+
if (
|
|
176
|
+
pathname.length > 1 &&
|
|
177
|
+
pathname.endsWith(
|
|
178
|
+
"/"
|
|
179
|
+
)
|
|
180
|
+
) {
|
|
181
|
+
return pathname.slice(
|
|
182
|
+
0,
|
|
183
|
+
-1
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return pathname || "/";
|
|
188
|
+
}
|