@chidchanun/bcp 0.1.0
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 +49 -0
- package/LICENSE +21 -0
- package/README.md +241 -0
- package/docs/caching.md +76 -0
- package/docs/configuration.md +97 -0
- package/docs/deployment.md +74 -0
- package/docs/getting-started.md +82 -0
- package/docs/middleware.md +58 -0
- package/docs/releasing.md +299 -0
- package/docs/routing.md +103 -0
- package/docs/security.md +57 -0
- package/package.json +68 -0
- package/packages/bundler/src/client-islands.ts +1457 -0
- package/packages/bundler/src/incremental-context.ts +206 -0
- package/packages/bundler/src/index.ts +1991 -0
- package/packages/bundler/src/module-graph.ts +317 -0
- package/packages/bundler/src/partial-hydration.ts +414 -0
- package/packages/bundler/src/production.ts +974 -0
- package/packages/bundler/src/server-production-middleware.ts +447 -0
- package/packages/bundler/src/server-production.ts +1193 -0
- package/packages/bundler/src/special-files.ts +131 -0
- package/packages/cache/src/index.ts +761 -0
- package/packages/cli/bin/bcp.mjs +93 -0
- package/packages/cli/src/args.ts +305 -0
- package/packages/cli/src/bootstrap.ts +514 -0
- package/packages/cli/src/index.ts +504 -0
- package/packages/cli/src/version.ts +45 -0
- package/packages/client/src/cache.ts +11 -0
- package/packages/client/src/config.ts +18 -0
- package/packages/client/src/error-boundary.tsx +149 -0
- package/packages/client/src/hydration.ts +3 -0
- package/packages/client/src/index.tsx +57 -0
- package/packages/client/src/islands.tsx +315 -0
- package/packages/client/src/metadata.ts +281 -0
- package/packages/client/src/navigation-loading.ts +52 -0
- package/packages/client/src/navigation-state.ts +80 -0
- package/packages/client/src/not-found.ts +34 -0
- package/packages/client/src/persistent-layout-runtime.ts +273 -0
- package/packages/client/src/router-v2.tsx +969 -0
- package/packages/client/src/router.tsx +1 -0
- package/packages/config/src/index.ts +1038 -0
- package/packages/env/src/index.ts +593 -0
- package/packages/router/src/advanced-router.ts +1032 -0
- package/packages/router/src/index.ts +1 -0
- package/packages/server/src/compression.ts +249 -0
- package/packages/server/src/dev-document-metadata.ts +154 -0
- package/packages/server/src/dev-hmr.ts +225 -0
- package/packages/server/src/index.ts +2265 -0
- package/packages/server/src/metadata.ts +478 -0
- package/packages/server/src/middleware-dev-server.ts +260 -0
- package/packages/server/src/middleware-loader.ts +140 -0
- package/packages/server/src/middleware-proxy.ts +516 -0
- package/packages/server/src/middleware.ts +704 -0
- package/packages/server/src/navigation-payload.ts +471 -0
- package/packages/server/src/production-server.ts +1746 -0
- package/packages/server/src/response-cache-proxy.ts +828 -0
- package/packages/server/src/security-proxy.ts +406 -0
- package/packages/server/src/security.ts +451 -0
- package/packages/server/src/standalone-production-runtime-v2.ts +2047 -0
- package/packages/server/src/standalone-production-runtime-v3.ts +250 -0
- package/packages/server/src/standalone-production-runtime-v4.ts +289 -0
- package/packages/server/src/standalone-production-runtime-v5.ts +289 -0
- package/packages/server/src/standalone-production-runtime.ts +1951 -0
- package/packages/server/src/standalone-production-server.ts +6 -0
- package/packages/server/src/static-assets.ts +262 -0
- package/packages/server/src/static-dev-server.ts +854 -0
|
@@ -0,0 +1,969 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
type AnchorHTMLAttributes,
|
|
4
|
+
type MouseEvent,
|
|
5
|
+
type ReactNode,
|
|
6
|
+
} from "react";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
applyResolvedMetadata,
|
|
10
|
+
type ResolvedMetadata,
|
|
11
|
+
} from "./metadata.js";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
hideNavigationLoading,
|
|
15
|
+
showNavigationLoading,
|
|
16
|
+
} from "./navigation-loading.js";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
getNavigationState,
|
|
20
|
+
resetNavigationState,
|
|
21
|
+
setNavigationState,
|
|
22
|
+
} from "./navigation-state.js";
|
|
23
|
+
|
|
24
|
+
export interface NavigationOptions {
|
|
25
|
+
replace?: boolean;
|
|
26
|
+
scroll?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Router {
|
|
30
|
+
readonly isNavigating: boolean;
|
|
31
|
+
push(
|
|
32
|
+
href: string,
|
|
33
|
+
options?: Omit<NavigationOptions, "replace">
|
|
34
|
+
): Promise<void>;
|
|
35
|
+
replace(
|
|
36
|
+
href: string,
|
|
37
|
+
options?: Omit<NavigationOptions, "replace">
|
|
38
|
+
): Promise<void>;
|
|
39
|
+
back(): void;
|
|
40
|
+
forward(): void;
|
|
41
|
+
refresh(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface LinkProps
|
|
45
|
+
extends Omit<
|
|
46
|
+
AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
47
|
+
"href"
|
|
48
|
+
> {
|
|
49
|
+
href: string;
|
|
50
|
+
replace?: boolean;
|
|
51
|
+
scroll?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface NavigationPayload {
|
|
55
|
+
route?: string;
|
|
56
|
+
params?: Record<string, string>;
|
|
57
|
+
clientScript: string;
|
|
58
|
+
clientImports: string[];
|
|
59
|
+
title: string;
|
|
60
|
+
metadata: ResolvedMetadata | null;
|
|
61
|
+
version: number;
|
|
62
|
+
loadingHtml: string | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface BcpDevConfig {
|
|
66
|
+
eventsPath: string;
|
|
67
|
+
clientScript: string;
|
|
68
|
+
route: string;
|
|
69
|
+
version: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface BcpReactRoot {
|
|
73
|
+
render(
|
|
74
|
+
tree: ReactNode
|
|
75
|
+
): void;
|
|
76
|
+
unmount(): void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare global {
|
|
80
|
+
var __BCP_DEV_CONFIG__:
|
|
81
|
+
| BcpDevConfig
|
|
82
|
+
| undefined;
|
|
83
|
+
var __BCP_NAVIGATION__:
|
|
84
|
+
| Router
|
|
85
|
+
| undefined;
|
|
86
|
+
var __BCP_HMR_SOURCE__:
|
|
87
|
+
| EventSource
|
|
88
|
+
| undefined;
|
|
89
|
+
var __BCP_REACT_ROOT__:
|
|
90
|
+
| BcpReactRoot
|
|
91
|
+
| undefined;
|
|
92
|
+
|
|
93
|
+
interface Window {
|
|
94
|
+
__BCP_DEV_CONFIG__?: BcpDevConfig;
|
|
95
|
+
__BCP_NAVIGATION__?: Router;
|
|
96
|
+
__BCP_HMR_SOURCE__?: EventSource;
|
|
97
|
+
__BCP_REACT_ROOT__?: BcpReactRoot;
|
|
98
|
+
__BCP_ROUTER_INSTALLED__?: boolean;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let navigationController:
|
|
103
|
+
AbortController | null = null;
|
|
104
|
+
|
|
105
|
+
export async function navigate(
|
|
106
|
+
href: string,
|
|
107
|
+
options: NavigationOptions = {}
|
|
108
|
+
): Promise<void> {
|
|
109
|
+
if (typeof window === "undefined") {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const target =
|
|
114
|
+
new URL(
|
|
115
|
+
href,
|
|
116
|
+
window.location.href
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (
|
|
120
|
+
target.origin !==
|
|
121
|
+
window.location.origin
|
|
122
|
+
) {
|
|
123
|
+
window.location.assign(
|
|
124
|
+
target.href
|
|
125
|
+
);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const currentWithoutHash =
|
|
130
|
+
`${window.location.pathname}${window.location.search}`;
|
|
131
|
+
const targetWithoutHash =
|
|
132
|
+
`${target.pathname}${target.search}`;
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
currentWithoutHash ===
|
|
136
|
+
targetWithoutHash &&
|
|
137
|
+
target.hash !==
|
|
138
|
+
window.location.hash
|
|
139
|
+
) {
|
|
140
|
+
updateHistory(
|
|
141
|
+
target,
|
|
142
|
+
Boolean(
|
|
143
|
+
options.replace
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
scrollToHash(
|
|
147
|
+
target.hash
|
|
148
|
+
);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
target.href ===
|
|
154
|
+
window.location.href
|
|
155
|
+
) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await loadNavigation(
|
|
160
|
+
target,
|
|
161
|
+
options
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function useRouter(): Router {
|
|
166
|
+
ensureNavigationRuntime();
|
|
167
|
+
|
|
168
|
+
if (
|
|
169
|
+
typeof window !== "undefined" &&
|
|
170
|
+
window.__BCP_NAVIGATION__
|
|
171
|
+
) {
|
|
172
|
+
return window.__BCP_NAVIGATION__;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return createRouter();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function Link({
|
|
179
|
+
href,
|
|
180
|
+
replace = false,
|
|
181
|
+
scroll = true,
|
|
182
|
+
onClick,
|
|
183
|
+
...props
|
|
184
|
+
}: LinkProps) {
|
|
185
|
+
useEffect(
|
|
186
|
+
() => {
|
|
187
|
+
ensureNavigationRuntime();
|
|
188
|
+
},
|
|
189
|
+
[]
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
function handleClick(
|
|
193
|
+
event: MouseEvent<HTMLAnchorElement>
|
|
194
|
+
) {
|
|
195
|
+
onClick?.(event);
|
|
196
|
+
|
|
197
|
+
if (
|
|
198
|
+
event.defaultPrevented ||
|
|
199
|
+
!shouldInterceptClick(
|
|
200
|
+
event
|
|
201
|
+
)
|
|
202
|
+
) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
event.preventDefault();
|
|
207
|
+
|
|
208
|
+
void navigate(
|
|
209
|
+
href,
|
|
210
|
+
{
|
|
211
|
+
replace,
|
|
212
|
+
scroll,
|
|
213
|
+
}
|
|
214
|
+
).catch(
|
|
215
|
+
(error) => {
|
|
216
|
+
if (isAbortError(error)) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.error(
|
|
221
|
+
"[BCP Router] navigation failed:",
|
|
222
|
+
error
|
|
223
|
+
);
|
|
224
|
+
window.location.assign(
|
|
225
|
+
href
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<a
|
|
233
|
+
{...props}
|
|
234
|
+
href={href}
|
|
235
|
+
onClick={handleClick}
|
|
236
|
+
/>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function createRouter(): Router {
|
|
241
|
+
return {
|
|
242
|
+
get isNavigating() {
|
|
243
|
+
return getNavigationState()
|
|
244
|
+
.isNavigating;
|
|
245
|
+
},
|
|
246
|
+
push(
|
|
247
|
+
href,
|
|
248
|
+
options = {}
|
|
249
|
+
) {
|
|
250
|
+
return navigate(
|
|
251
|
+
href,
|
|
252
|
+
{
|
|
253
|
+
...options,
|
|
254
|
+
replace: false,
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
},
|
|
258
|
+
replace(
|
|
259
|
+
href,
|
|
260
|
+
options = {}
|
|
261
|
+
) {
|
|
262
|
+
return navigate(
|
|
263
|
+
href,
|
|
264
|
+
{
|
|
265
|
+
...options,
|
|
266
|
+
replace: true,
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
},
|
|
270
|
+
back() {
|
|
271
|
+
if (
|
|
272
|
+
typeof window !==
|
|
273
|
+
"undefined"
|
|
274
|
+
) {
|
|
275
|
+
window.history.back();
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
forward() {
|
|
279
|
+
if (
|
|
280
|
+
typeof window !==
|
|
281
|
+
"undefined"
|
|
282
|
+
) {
|
|
283
|
+
window.history.forward();
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
refresh() {
|
|
287
|
+
if (
|
|
288
|
+
typeof window ===
|
|
289
|
+
"undefined"
|
|
290
|
+
) {
|
|
291
|
+
return Promise.resolve();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return loadNavigation(
|
|
295
|
+
new URL(
|
|
296
|
+
window.location.href
|
|
297
|
+
),
|
|
298
|
+
{
|
|
299
|
+
replace: true,
|
|
300
|
+
scroll: false,
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function ensureNavigationRuntime(): void {
|
|
308
|
+
if (typeof window === "undefined") {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!window.__BCP_NAVIGATION__) {
|
|
313
|
+
window.__BCP_NAVIGATION__ =
|
|
314
|
+
createRouter();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (window.__BCP_ROUTER_INSTALLED__) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
window.__BCP_ROUTER_INSTALLED__ =
|
|
322
|
+
true;
|
|
323
|
+
|
|
324
|
+
window.addEventListener(
|
|
325
|
+
"popstate",
|
|
326
|
+
() => {
|
|
327
|
+
void loadNavigation(
|
|
328
|
+
new URL(
|
|
329
|
+
window.location.href
|
|
330
|
+
),
|
|
331
|
+
{
|
|
332
|
+
replace: true,
|
|
333
|
+
scroll: true,
|
|
334
|
+
fromHistory: true,
|
|
335
|
+
}
|
|
336
|
+
).catch(
|
|
337
|
+
(error) => {
|
|
338
|
+
if (isAbortError(error)) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
console.error(
|
|
343
|
+
"[BCP Router] history navigation failed:",
|
|
344
|
+
error
|
|
345
|
+
);
|
|
346
|
+
window.location.reload();
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function loadNavigation(
|
|
354
|
+
target: URL,
|
|
355
|
+
options: NavigationOptions & {
|
|
356
|
+
fromHistory?: boolean;
|
|
357
|
+
}
|
|
358
|
+
): Promise<void> {
|
|
359
|
+
ensureNavigationRuntime();
|
|
360
|
+
navigationController?.abort();
|
|
361
|
+
|
|
362
|
+
const controller =
|
|
363
|
+
new AbortController();
|
|
364
|
+
navigationController =
|
|
365
|
+
controller;
|
|
366
|
+
|
|
367
|
+
hideNavigationLoading();
|
|
368
|
+
setNavigationState({
|
|
369
|
+
isNavigating: true,
|
|
370
|
+
targetHref:
|
|
371
|
+
target.href,
|
|
372
|
+
targetRoute: null,
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const targetPath =
|
|
377
|
+
`${target.pathname}${target.search}`;
|
|
378
|
+
|
|
379
|
+
const response =
|
|
380
|
+
await fetch(
|
|
381
|
+
`/_bcp/navigation?url=${encodeURIComponent(
|
|
382
|
+
targetPath
|
|
383
|
+
)}`,
|
|
384
|
+
{
|
|
385
|
+
method: "GET",
|
|
386
|
+
headers: {
|
|
387
|
+
Accept:
|
|
388
|
+
"application/json",
|
|
389
|
+
"X-BCP-Navigation":
|
|
390
|
+
"1",
|
|
391
|
+
},
|
|
392
|
+
signal:
|
|
393
|
+
controller.signal,
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
if (!response.ok) {
|
|
398
|
+
window.location.assign(
|
|
399
|
+
target.href
|
|
400
|
+
);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const payload =
|
|
405
|
+
await parseNavigationPayload(
|
|
406
|
+
response
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
if (!payload) {
|
|
410
|
+
window.location.assign(
|
|
411
|
+
target.href
|
|
412
|
+
);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
setNavigationState({
|
|
417
|
+
targetRoute:
|
|
418
|
+
payload.route ??
|
|
419
|
+
target.pathname,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
showNavigationLoading(
|
|
423
|
+
payload.loadingHtml
|
|
424
|
+
);
|
|
425
|
+
preloadClientImports(
|
|
426
|
+
payload.clientImports
|
|
427
|
+
);
|
|
428
|
+
applyNavigationPayload(
|
|
429
|
+
payload
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
if (!options.fromHistory) {
|
|
433
|
+
updateHistory(
|
|
434
|
+
target,
|
|
435
|
+
Boolean(
|
|
436
|
+
options.replace
|
|
437
|
+
)
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
updateDevConfig(
|
|
442
|
+
payload,
|
|
443
|
+
target
|
|
444
|
+
);
|
|
445
|
+
await restartDevRuntime();
|
|
446
|
+
await importClientBundle(
|
|
447
|
+
payload.clientScript
|
|
448
|
+
);
|
|
449
|
+
await waitForNextPaint();
|
|
450
|
+
|
|
451
|
+
if (options.scroll !== false) {
|
|
452
|
+
if (target.hash) {
|
|
453
|
+
scrollToHash(
|
|
454
|
+
target.hash
|
|
455
|
+
);
|
|
456
|
+
} else {
|
|
457
|
+
window.scrollTo(
|
|
458
|
+
0,
|
|
459
|
+
0
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
} finally {
|
|
464
|
+
if (
|
|
465
|
+
navigationController ===
|
|
466
|
+
controller
|
|
467
|
+
) {
|
|
468
|
+
navigationController =
|
|
469
|
+
null;
|
|
470
|
+
hideNavigationLoading();
|
|
471
|
+
resetNavigationState();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async function parseNavigationPayload(
|
|
477
|
+
response: Response
|
|
478
|
+
): Promise<NavigationPayload | null> {
|
|
479
|
+
try {
|
|
480
|
+
const payload =
|
|
481
|
+
await response.json() as
|
|
482
|
+
Partial<NavigationPayload>;
|
|
483
|
+
|
|
484
|
+
if (
|
|
485
|
+
typeof payload.clientScript !==
|
|
486
|
+
"string" ||
|
|
487
|
+
typeof payload.title !==
|
|
488
|
+
"string" ||
|
|
489
|
+
typeof payload.version !==
|
|
490
|
+
"number" ||
|
|
491
|
+
!(
|
|
492
|
+
payload.loadingHtml === null ||
|
|
493
|
+
typeof payload.loadingHtml ===
|
|
494
|
+
"string"
|
|
495
|
+
) ||
|
|
496
|
+
!(
|
|
497
|
+
payload.clientImports ===
|
|
498
|
+
undefined ||
|
|
499
|
+
(
|
|
500
|
+
Array.isArray(
|
|
501
|
+
payload.clientImports
|
|
502
|
+
) &&
|
|
503
|
+
payload.clientImports.every(
|
|
504
|
+
(item) =>
|
|
505
|
+
typeof item ===
|
|
506
|
+
"string"
|
|
507
|
+
)
|
|
508
|
+
)
|
|
509
|
+
) ||
|
|
510
|
+
!(
|
|
511
|
+
payload.metadata === undefined ||
|
|
512
|
+
payload.metadata === null ||
|
|
513
|
+
isResolvedMetadata(
|
|
514
|
+
payload.metadata
|
|
515
|
+
)
|
|
516
|
+
)
|
|
517
|
+
) {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return {
|
|
522
|
+
route:
|
|
523
|
+
payload.route,
|
|
524
|
+
params:
|
|
525
|
+
payload.params ?? {},
|
|
526
|
+
clientScript:
|
|
527
|
+
stripQuery(
|
|
528
|
+
payload.clientScript
|
|
529
|
+
),
|
|
530
|
+
clientImports:
|
|
531
|
+
(
|
|
532
|
+
payload.clientImports ??
|
|
533
|
+
[]
|
|
534
|
+
).map(
|
|
535
|
+
stripQuery
|
|
536
|
+
),
|
|
537
|
+
title:
|
|
538
|
+
payload.title,
|
|
539
|
+
metadata:
|
|
540
|
+
payload.metadata ??
|
|
541
|
+
null,
|
|
542
|
+
version:
|
|
543
|
+
payload.version,
|
|
544
|
+
loadingHtml:
|
|
545
|
+
payload.loadingHtml,
|
|
546
|
+
};
|
|
547
|
+
} catch {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function applyNavigationPayload(
|
|
553
|
+
payload: NavigationPayload
|
|
554
|
+
): void {
|
|
555
|
+
if (
|
|
556
|
+
!document.getElementById(
|
|
557
|
+
"root"
|
|
558
|
+
)
|
|
559
|
+
) {
|
|
560
|
+
throw new Error(
|
|
561
|
+
"BCP Framework: root element was not found during navigation."
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const dataElement =
|
|
566
|
+
document.getElementById(
|
|
567
|
+
"__BCP_DATA__"
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
if (!dataElement) {
|
|
571
|
+
throw new Error(
|
|
572
|
+
"BCP Framework: framework data was not found during navigation."
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
dataElement.textContent =
|
|
577
|
+
serializeFrameworkData({
|
|
578
|
+
route:
|
|
579
|
+
payload.route ??
|
|
580
|
+
window.location.pathname,
|
|
581
|
+
params:
|
|
582
|
+
payload.params ?? {},
|
|
583
|
+
metadata:
|
|
584
|
+
payload.metadata,
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
if (payload.metadata) {
|
|
588
|
+
applyResolvedMetadata(
|
|
589
|
+
payload.metadata
|
|
590
|
+
);
|
|
591
|
+
} else {
|
|
592
|
+
document.title =
|
|
593
|
+
payload.title ||
|
|
594
|
+
document.title;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function preloadClientImports(
|
|
599
|
+
imports: string[]
|
|
600
|
+
): void {
|
|
601
|
+
for (const importPath of imports) {
|
|
602
|
+
const target =
|
|
603
|
+
new URL(
|
|
604
|
+
importPath,
|
|
605
|
+
window.location.href
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
if (
|
|
609
|
+
target.origin !==
|
|
610
|
+
window.location.origin
|
|
611
|
+
) {
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const alreadyPreloaded =
|
|
616
|
+
Array.from(
|
|
617
|
+
document.querySelectorAll<HTMLLinkElement>(
|
|
618
|
+
'link[rel="modulepreload"]'
|
|
619
|
+
)
|
|
620
|
+
).some(
|
|
621
|
+
(link) =>
|
|
622
|
+
link.href ===
|
|
623
|
+
target.href
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
if (alreadyPreloaded) {
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const link =
|
|
631
|
+
document.createElement(
|
|
632
|
+
"link"
|
|
633
|
+
);
|
|
634
|
+
link.rel =
|
|
635
|
+
"modulepreload";
|
|
636
|
+
link.href =
|
|
637
|
+
target.href;
|
|
638
|
+
link.dataset.bcpPreload =
|
|
639
|
+
"true";
|
|
640
|
+
document.head.appendChild(
|
|
641
|
+
link
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function updateDevConfig(
|
|
647
|
+
payload: NavigationPayload,
|
|
648
|
+
target: URL
|
|
649
|
+
): void {
|
|
650
|
+
const config =
|
|
651
|
+
window.__BCP_DEV_CONFIG__;
|
|
652
|
+
|
|
653
|
+
if (!config) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
window.__BCP_DEV_CONFIG__ = {
|
|
658
|
+
...config,
|
|
659
|
+
clientScript:
|
|
660
|
+
payload.clientScript,
|
|
661
|
+
route:
|
|
662
|
+
payload.route ??
|
|
663
|
+
target.pathname,
|
|
664
|
+
version:
|
|
665
|
+
payload.version,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function updateHistory(
|
|
670
|
+
target: URL,
|
|
671
|
+
replace: boolean
|
|
672
|
+
): void {
|
|
673
|
+
const state = {
|
|
674
|
+
__bcp: true,
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
if (replace) {
|
|
678
|
+
window.history.replaceState(
|
|
679
|
+
state,
|
|
680
|
+
"",
|
|
681
|
+
target.href
|
|
682
|
+
);
|
|
683
|
+
} else {
|
|
684
|
+
window.history.pushState(
|
|
685
|
+
state,
|
|
686
|
+
"",
|
|
687
|
+
target.href
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async function restartDevRuntime(): Promise<void> {
|
|
693
|
+
if (
|
|
694
|
+
typeof window === "undefined" ||
|
|
695
|
+
!window.__BCP_DEV_CONFIG__
|
|
696
|
+
) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
window.__BCP_HMR_SOURCE__?.close();
|
|
701
|
+
delete window.__BCP_HMR_SOURCE__;
|
|
702
|
+
|
|
703
|
+
document
|
|
704
|
+
.querySelector(
|
|
705
|
+
"script[data-bcp-runtime]"
|
|
706
|
+
)
|
|
707
|
+
?.remove();
|
|
708
|
+
|
|
709
|
+
const script =
|
|
710
|
+
document.createElement(
|
|
711
|
+
"script"
|
|
712
|
+
);
|
|
713
|
+
script.dataset.bcpRuntime =
|
|
714
|
+
"true";
|
|
715
|
+
script.src =
|
|
716
|
+
`/_bcp/dev-runtime.js?bcp-nav-runtime=${Date.now()}`;
|
|
717
|
+
script.async = false;
|
|
718
|
+
|
|
719
|
+
await new Promise<void>(
|
|
720
|
+
(
|
|
721
|
+
resolve,
|
|
722
|
+
reject
|
|
723
|
+
) => {
|
|
724
|
+
script.addEventListener(
|
|
725
|
+
"load",
|
|
726
|
+
() => resolve(),
|
|
727
|
+
{
|
|
728
|
+
once: true,
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
script.addEventListener(
|
|
732
|
+
"error",
|
|
733
|
+
() => reject(
|
|
734
|
+
new Error(
|
|
735
|
+
"BCP Framework: failed to reload the dev runtime."
|
|
736
|
+
)
|
|
737
|
+
),
|
|
738
|
+
{
|
|
739
|
+
once: true,
|
|
740
|
+
}
|
|
741
|
+
);
|
|
742
|
+
document.head.appendChild(
|
|
743
|
+
script
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
async function importClientBundle(
|
|
750
|
+
clientScript: string
|
|
751
|
+
): Promise<void> {
|
|
752
|
+
const separator =
|
|
753
|
+
clientScript.includes("?")
|
|
754
|
+
? "&"
|
|
755
|
+
: "?";
|
|
756
|
+
|
|
757
|
+
await import(
|
|
758
|
+
clientScript +
|
|
759
|
+
separator +
|
|
760
|
+
"bcp-nav=" +
|
|
761
|
+
Date.now()
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function shouldInterceptClick(
|
|
766
|
+
event: MouseEvent<HTMLAnchorElement>
|
|
767
|
+
): boolean {
|
|
768
|
+
if (
|
|
769
|
+
event.defaultPrevented ||
|
|
770
|
+
event.button !== 0 ||
|
|
771
|
+
event.metaKey ||
|
|
772
|
+
event.ctrlKey ||
|
|
773
|
+
event.shiftKey ||
|
|
774
|
+
event.altKey
|
|
775
|
+
) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const anchor =
|
|
780
|
+
event.currentTarget;
|
|
781
|
+
|
|
782
|
+
if (
|
|
783
|
+
anchor.target &&
|
|
784
|
+
anchor.target !== "_self"
|
|
785
|
+
) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (
|
|
790
|
+
anchor.hasAttribute(
|
|
791
|
+
"download"
|
|
792
|
+
)
|
|
793
|
+
) {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
return (
|
|
798
|
+
new URL(
|
|
799
|
+
anchor.href,
|
|
800
|
+
window.location.href
|
|
801
|
+
).origin ===
|
|
802
|
+
window.location.origin
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function scrollToHash(
|
|
807
|
+
hash: string
|
|
808
|
+
): void {
|
|
809
|
+
const element =
|
|
810
|
+
document.getElementById(
|
|
811
|
+
decodeURIComponent(
|
|
812
|
+
hash.slice(1)
|
|
813
|
+
)
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
if (element) {
|
|
817
|
+
element.scrollIntoView();
|
|
818
|
+
} else {
|
|
819
|
+
window.scrollTo(
|
|
820
|
+
0,
|
|
821
|
+
0
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function stripQuery(
|
|
827
|
+
value: string
|
|
828
|
+
): string {
|
|
829
|
+
const url =
|
|
830
|
+
new URL(
|
|
831
|
+
value,
|
|
832
|
+
window.location.href
|
|
833
|
+
);
|
|
834
|
+
url.search = "";
|
|
835
|
+
return `${url.pathname}${url.hash}`;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function serializeFrameworkData(
|
|
839
|
+
value: unknown
|
|
840
|
+
): string {
|
|
841
|
+
return JSON.stringify(
|
|
842
|
+
value
|
|
843
|
+
)
|
|
844
|
+
.replace(
|
|
845
|
+
/</g,
|
|
846
|
+
"\\u003c"
|
|
847
|
+
)
|
|
848
|
+
.replace(
|
|
849
|
+
/\u2028/g,
|
|
850
|
+
"\\u2028"
|
|
851
|
+
)
|
|
852
|
+
.replace(
|
|
853
|
+
/\u2029/g,
|
|
854
|
+
"\\u2029"
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function waitForNextPaint(): Promise<void> {
|
|
859
|
+
return new Promise(
|
|
860
|
+
(resolve) => {
|
|
861
|
+
requestAnimationFrame(
|
|
862
|
+
() => resolve()
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function isAbortError(
|
|
869
|
+
error: unknown
|
|
870
|
+
): boolean {
|
|
871
|
+
return (
|
|
872
|
+
error instanceof DOMException &&
|
|
873
|
+
error.name === "AbortError"
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function isResolvedMetadata(
|
|
878
|
+
value: unknown
|
|
879
|
+
): value is ResolvedMetadata {
|
|
880
|
+
if (
|
|
881
|
+
typeof value !== "object" ||
|
|
882
|
+
value === null
|
|
883
|
+
) {
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const metadata =
|
|
888
|
+
value as Partial<ResolvedMetadata>;
|
|
889
|
+
|
|
890
|
+
if (
|
|
891
|
+
typeof metadata.title !==
|
|
892
|
+
"string" ||
|
|
893
|
+
!isNullableString(
|
|
894
|
+
metadata.description
|
|
895
|
+
) ||
|
|
896
|
+
!isNullableString(
|
|
897
|
+
metadata.robots
|
|
898
|
+
) ||
|
|
899
|
+
!isNullableString(
|
|
900
|
+
metadata.canonical
|
|
901
|
+
) ||
|
|
902
|
+
typeof metadata.icons !==
|
|
903
|
+
"object" ||
|
|
904
|
+
metadata.icons === null
|
|
905
|
+
) {
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (
|
|
910
|
+
!isNullableString(
|
|
911
|
+
metadata.icons.icon
|
|
912
|
+
) ||
|
|
913
|
+
!isNullableString(
|
|
914
|
+
metadata.icons.shortcut
|
|
915
|
+
) ||
|
|
916
|
+
!isNullableString(
|
|
917
|
+
metadata.icons.apple
|
|
918
|
+
)
|
|
919
|
+
) {
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (metadata.openGraph === null) {
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (
|
|
928
|
+
typeof metadata.openGraph !==
|
|
929
|
+
"object" ||
|
|
930
|
+
metadata.openGraph === null
|
|
931
|
+
) {
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
return (
|
|
936
|
+
isNullableString(
|
|
937
|
+
metadata.openGraph.title
|
|
938
|
+
) &&
|
|
939
|
+
isNullableString(
|
|
940
|
+
metadata.openGraph.description
|
|
941
|
+
) &&
|
|
942
|
+
isNullableString(
|
|
943
|
+
metadata.openGraph.url
|
|
944
|
+
) &&
|
|
945
|
+
isNullableString(
|
|
946
|
+
metadata.openGraph.type
|
|
947
|
+
) &&
|
|
948
|
+
isNullableString(
|
|
949
|
+
metadata.openGraph.siteName
|
|
950
|
+
) &&
|
|
951
|
+
Array.isArray(
|
|
952
|
+
metadata.openGraph.images
|
|
953
|
+
) &&
|
|
954
|
+
metadata.openGraph.images.every(
|
|
955
|
+
(image) =>
|
|
956
|
+
typeof image ===
|
|
957
|
+
"string"
|
|
958
|
+
)
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function isNullableString(
|
|
963
|
+
value: unknown
|
|
964
|
+
): value is string | null {
|
|
965
|
+
return (
|
|
966
|
+
value === null ||
|
|
967
|
+
typeof value === "string"
|
|
968
|
+
);
|
|
969
|
+
}
|