@chidchanun/bcp 0.1.8 → 0.1.10
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 +82 -0
- package/README.md +218 -7
- package/docs/application-modules.md +63 -3
- package/docs/releasing.md +34 -19
- package/docs/route-guards.md +240 -0
- package/docs/server-data-loaders.md +240 -0
- package/docs/updating.md +130 -0
- package/package.json +1 -1
- package/packages/bundler/src/server-production-guards.ts +497 -0
- package/packages/bundler/src/server-production-middleware.ts +33 -8
- package/packages/bundler/src/server-production.ts +25 -0
- package/packages/cli/src/args.ts +58 -0
- package/packages/cli/src/index.ts +41 -13
- package/packages/cli/src/update.ts +860 -0
- package/packages/client/src/index.tsx +5 -0
- package/packages/client/src/loader-data.tsx +229 -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-guard.ts +529 -0
- package/packages/server/src/page-loader.ts +643 -0
- package/packages/server/src/standalone-production-runtime-v2-guard.ts +815 -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
|
@@ -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
|
+
}
|
|
@@ -18,6 +18,10 @@ import {
|
|
|
18
18
|
renderToString,
|
|
19
19
|
} from "react-dom/server";
|
|
20
20
|
|
|
21
|
+
import {
|
|
22
|
+
BcpLoaderDataProvider,
|
|
23
|
+
} from "../../client/src/loader-data.js";
|
|
24
|
+
|
|
21
25
|
import {
|
|
22
26
|
assertNoPageApiConflicts,
|
|
23
27
|
matchApiRoute,
|
|
@@ -41,7 +45,15 @@ import {
|
|
|
41
45
|
DevHmrServer,
|
|
42
46
|
} from "./dev-hmr.js";
|
|
43
47
|
|
|
44
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
attachLoaderDataToParams,
|
|
50
|
+
executePageLoader,
|
|
51
|
+
} from "./page-loader.js";
|
|
52
|
+
|
|
53
|
+
import {
|
|
54
|
+
runWithRequestContext,
|
|
55
|
+
applyResponseCookies,
|
|
56
|
+
} from "./request-context.ts";
|
|
45
57
|
|
|
46
58
|
/*
|
|
47
59
|
* =====================================
|
|
@@ -71,7 +83,7 @@ interface FrameworkData {
|
|
|
71
83
|
|
|
72
84
|
params: Record<
|
|
73
85
|
string,
|
|
74
|
-
|
|
86
|
+
any
|
|
75
87
|
>;
|
|
76
88
|
}
|
|
77
89
|
|
|
@@ -501,124 +513,15 @@ export function createDevServer(
|
|
|
501
513
|
);
|
|
502
514
|
}
|
|
503
515
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
* Server Components Import
|
|
507
|
-
* =================================
|
|
508
|
-
*/
|
|
509
|
-
|
|
510
|
-
const importStartedAt =
|
|
511
|
-
performance.now();
|
|
512
|
-
|
|
513
|
-
const Page =
|
|
514
|
-
await importComponent(
|
|
515
|
-
route.filePath,
|
|
516
|
-
"Page",
|
|
517
|
-
moduleVersion
|
|
518
|
-
);
|
|
519
|
-
|
|
520
|
-
const Layouts:
|
|
521
|
-
ComponentType<any>[] =
|
|
522
|
-
[];
|
|
523
|
-
|
|
524
|
-
for (
|
|
525
|
-
const layoutFile
|
|
526
|
-
of route.layouts
|
|
527
|
-
) {
|
|
528
|
-
const Layout =
|
|
529
|
-
await importComponent(
|
|
530
|
-
layoutFile,
|
|
531
|
-
"Layout",
|
|
532
|
-
moduleVersion
|
|
533
|
-
);
|
|
534
|
-
|
|
535
|
-
Layouts.push(
|
|
536
|
-
Layout
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
console.log(
|
|
541
|
-
`Import: ${(performance.now() - importStartedAt).toFixed(2)}ms`
|
|
542
|
-
);
|
|
543
|
-
|
|
544
|
-
/*
|
|
545
|
-
* =================================
|
|
546
|
-
* React Tree
|
|
547
|
-
* =================================
|
|
548
|
-
*/
|
|
549
|
-
|
|
550
|
-
const renderStartedAt =
|
|
551
|
-
performance.now();
|
|
552
|
-
|
|
553
|
-
let element:
|
|
554
|
-
ReactElement =
|
|
555
|
-
createElement(
|
|
556
|
-
Page,
|
|
557
|
-
{
|
|
558
|
-
params,
|
|
559
|
-
}
|
|
560
|
-
);
|
|
561
|
-
|
|
562
|
-
for (
|
|
563
|
-
let index =
|
|
564
|
-
Layouts.length -
|
|
565
|
-
1;
|
|
566
|
-
|
|
567
|
-
index >= 0;
|
|
568
|
-
|
|
569
|
-
index--
|
|
570
|
-
) {
|
|
571
|
-
const Layout =
|
|
572
|
-
Layouts[index];
|
|
573
|
-
|
|
574
|
-
element =
|
|
575
|
-
createElement(
|
|
576
|
-
Layout,
|
|
577
|
-
{
|
|
578
|
-
params,
|
|
579
|
-
},
|
|
580
|
-
element
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const content =
|
|
585
|
-
renderToString(
|
|
586
|
-
element
|
|
587
|
-
);
|
|
588
|
-
|
|
589
|
-
console.log(
|
|
590
|
-
`SSR: ${(performance.now() - renderStartedAt).toFixed(2)}ms`
|
|
591
|
-
);
|
|
592
|
-
|
|
593
|
-
/*
|
|
594
|
-
* =================================
|
|
595
|
-
* Framework Data
|
|
596
|
-
* =================================
|
|
597
|
-
*/
|
|
598
|
-
|
|
599
|
-
const frameworkData:
|
|
600
|
-
FrameworkData = {
|
|
601
|
-
route:
|
|
602
|
-
route.pathname,
|
|
603
|
-
|
|
604
|
-
params,
|
|
605
|
-
};
|
|
606
|
-
|
|
607
|
-
const html =
|
|
608
|
-
renderDocument(
|
|
609
|
-
content,
|
|
610
|
-
|
|
611
|
-
pageClientBundle
|
|
612
|
-
.publicPath,
|
|
613
|
-
|
|
614
|
-
frameworkData,
|
|
615
|
-
|
|
616
|
-
clientVersion
|
|
617
|
-
);
|
|
618
|
-
|
|
619
|
-
sendHtml(
|
|
516
|
+
await handlePageRoute(
|
|
517
|
+
req,
|
|
620
518
|
res,
|
|
621
|
-
|
|
519
|
+
requestUrl,
|
|
520
|
+
route,
|
|
521
|
+
params,
|
|
522
|
+
pageClientBundle,
|
|
523
|
+
moduleVersion,
|
|
524
|
+
clientVersion
|
|
622
525
|
);
|
|
623
526
|
} catch (error) {
|
|
624
527
|
console.error("");
|
|
@@ -1335,6 +1238,193 @@ export function createDevServer(
|
|
|
1335
1238
|
};
|
|
1336
1239
|
}
|
|
1337
1240
|
|
|
1241
|
+
/*
|
|
1242
|
+
* =====================================
|
|
1243
|
+
* Page Route
|
|
1244
|
+
* =====================================
|
|
1245
|
+
*/
|
|
1246
|
+
|
|
1247
|
+
async function handlePageRoute(
|
|
1248
|
+
req: http.IncomingMessage,
|
|
1249
|
+
res: http.ServerResponse,
|
|
1250
|
+
requestUrl: URL,
|
|
1251
|
+
route: Route,
|
|
1252
|
+
params: Record<string, any>,
|
|
1253
|
+
pageClientBundle: ClientBundle,
|
|
1254
|
+
moduleVersion: number,
|
|
1255
|
+
clientVersion: number
|
|
1256
|
+
): Promise<void> {
|
|
1257
|
+
const request =
|
|
1258
|
+
await createWebRequest(
|
|
1259
|
+
req,
|
|
1260
|
+
requestUrl
|
|
1261
|
+
);
|
|
1262
|
+
|
|
1263
|
+
const response =
|
|
1264
|
+
await runWithRequestContext(
|
|
1265
|
+
request,
|
|
1266
|
+
async () => {
|
|
1267
|
+
const loaderExecution =
|
|
1268
|
+
await executePageLoader(
|
|
1269
|
+
route.filePath,
|
|
1270
|
+
params,
|
|
1271
|
+
requestUrl,
|
|
1272
|
+
moduleVersion
|
|
1273
|
+
);
|
|
1274
|
+
|
|
1275
|
+
if (
|
|
1276
|
+
loaderExecution.response
|
|
1277
|
+
) {
|
|
1278
|
+
return applyResponseCookies(
|
|
1279
|
+
loaderExecution.response
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
const frameworkParams =
|
|
1284
|
+
attachLoaderDataToParams(
|
|
1285
|
+
params,
|
|
1286
|
+
loaderExecution
|
|
1287
|
+
);
|
|
1288
|
+
|
|
1289
|
+
const importStartedAt =
|
|
1290
|
+
performance.now();
|
|
1291
|
+
|
|
1292
|
+
const Page =
|
|
1293
|
+
await importComponent(
|
|
1294
|
+
route.filePath,
|
|
1295
|
+
"Page",
|
|
1296
|
+
moduleVersion
|
|
1297
|
+
);
|
|
1298
|
+
|
|
1299
|
+
const Layouts:
|
|
1300
|
+
ComponentType<any>[] = [];
|
|
1301
|
+
|
|
1302
|
+
for (
|
|
1303
|
+
const layoutFile
|
|
1304
|
+
of route.layouts
|
|
1305
|
+
) {
|
|
1306
|
+
const Layout =
|
|
1307
|
+
await importComponent(
|
|
1308
|
+
layoutFile,
|
|
1309
|
+
"Layout",
|
|
1310
|
+
moduleVersion
|
|
1311
|
+
);
|
|
1312
|
+
|
|
1313
|
+
Layouts.push(
|
|
1314
|
+
Layout
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
console.log(
|
|
1319
|
+
`Import: ${(performance.now() - importStartedAt).toFixed(2)}ms`
|
|
1320
|
+
);
|
|
1321
|
+
|
|
1322
|
+
const renderStartedAt =
|
|
1323
|
+
performance.now();
|
|
1324
|
+
|
|
1325
|
+
let element:
|
|
1326
|
+
ReactElement =
|
|
1327
|
+
createElement(
|
|
1328
|
+
Page,
|
|
1329
|
+
{
|
|
1330
|
+
params:
|
|
1331
|
+
frameworkParams,
|
|
1332
|
+
}
|
|
1333
|
+
);
|
|
1334
|
+
|
|
1335
|
+
for (
|
|
1336
|
+
let index =
|
|
1337
|
+
Layouts.length - 1;
|
|
1338
|
+
index >= 0;
|
|
1339
|
+
index--
|
|
1340
|
+
) {
|
|
1341
|
+
const Layout =
|
|
1342
|
+
Layouts[index];
|
|
1343
|
+
|
|
1344
|
+
element =
|
|
1345
|
+
createElement(
|
|
1346
|
+
Layout,
|
|
1347
|
+
{
|
|
1348
|
+
params:
|
|
1349
|
+
frameworkParams,
|
|
1350
|
+
},
|
|
1351
|
+
element
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
if (
|
|
1356
|
+
loaderExecution.hasLoader
|
|
1357
|
+
) {
|
|
1358
|
+
element =
|
|
1359
|
+
createElement(
|
|
1360
|
+
BcpLoaderDataProvider,
|
|
1361
|
+
{
|
|
1362
|
+
data:
|
|
1363
|
+
loaderExecution.data,
|
|
1364
|
+
},
|
|
1365
|
+
element
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
const content =
|
|
1370
|
+
renderToString(
|
|
1371
|
+
element
|
|
1372
|
+
);
|
|
1373
|
+
|
|
1374
|
+
console.log(
|
|
1375
|
+
`SSR: ${(performance.now() - renderStartedAt).toFixed(2)}ms`
|
|
1376
|
+
);
|
|
1377
|
+
|
|
1378
|
+
const frameworkData:
|
|
1379
|
+
FrameworkData = {
|
|
1380
|
+
route:
|
|
1381
|
+
route.pathname,
|
|
1382
|
+
params:
|
|
1383
|
+
frameworkParams,
|
|
1384
|
+
};
|
|
1385
|
+
|
|
1386
|
+
const html =
|
|
1387
|
+
renderDocument(
|
|
1388
|
+
content,
|
|
1389
|
+
pageClientBundle
|
|
1390
|
+
.publicPath,
|
|
1391
|
+
frameworkData,
|
|
1392
|
+
clientVersion
|
|
1393
|
+
);
|
|
1394
|
+
|
|
1395
|
+
return applyResponseCookies(
|
|
1396
|
+
new Response(
|
|
1397
|
+
html,
|
|
1398
|
+
{
|
|
1399
|
+
status:
|
|
1400
|
+
200,
|
|
1401
|
+
headers: {
|
|
1402
|
+
"Content-Type":
|
|
1403
|
+
"text/html; charset=utf-8",
|
|
1404
|
+
"Cache-Control":
|
|
1405
|
+
"no-store",
|
|
1406
|
+
},
|
|
1407
|
+
}
|
|
1408
|
+
)
|
|
1409
|
+
);
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
remoteAddress:
|
|
1413
|
+
req.socket
|
|
1414
|
+
.remoteAddress ??
|
|
1415
|
+
null,
|
|
1416
|
+
}
|
|
1417
|
+
);
|
|
1418
|
+
|
|
1419
|
+
await sendWebResponse(
|
|
1420
|
+
res,
|
|
1421
|
+
response,
|
|
1422
|
+
normalizeHttpMethod(
|
|
1423
|
+
req.method
|
|
1424
|
+
) === "HEAD"
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1338
1428
|
/*
|
|
1339
1429
|
* =====================================
|
|
1340
1430
|
* API Route
|
|
@@ -28,9 +28,13 @@ import {
|
|
|
28
28
|
type MetadataSource,
|
|
29
29
|
} from "./metadata.js";
|
|
30
30
|
|
|
31
|
+
import {
|
|
32
|
+
routeHasPageLoader,
|
|
33
|
+
} from "./page-loader.js";
|
|
34
|
+
|
|
31
35
|
export interface NavigationPayload {
|
|
32
36
|
route: string;
|
|
33
|
-
params: Record<string,
|
|
37
|
+
params: Record<string, unknown>;
|
|
34
38
|
clientScript: string;
|
|
35
39
|
clientImports: string[];
|
|
36
40
|
title: string;
|
|
@@ -51,6 +55,8 @@ export interface NavigationPayloadOptions {
|
|
|
51
55
|
title?: string;
|
|
52
56
|
|
|
53
57
|
version?: number;
|
|
58
|
+
|
|
59
|
+
allowLoaderRoute?: boolean;
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
export async function resolveNavigationPayload(
|
|
@@ -81,6 +87,23 @@ export async function resolveNavigationPayload(
|
|
|
81
87
|
return null;
|
|
82
88
|
}
|
|
83
89
|
|
|
90
|
+
/*
|
|
91
|
+
* A loader-backed route must opt in here because this helper only knows
|
|
92
|
+
* how to build the route/navigation metadata. Step 2 navigation callers
|
|
93
|
+
* execute the loader in a request context first and then attach the fresh
|
|
94
|
+
* loader data to params before sending the payload. Document metadata
|
|
95
|
+
* resolution also opts in because the page loader has already run in the
|
|
96
|
+
* normal SSR request pipeline.
|
|
97
|
+
*/
|
|
98
|
+
if (
|
|
99
|
+
!options.allowLoaderRoute &&
|
|
100
|
+
routeHasPageLoader(
|
|
101
|
+
match.route.filePath
|
|
102
|
+
)
|
|
103
|
+
) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
84
107
|
const loadingFile =
|
|
85
108
|
findNearestLoadingFile(
|
|
86
109
|
appDirectory,
|