@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,471 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
pathToFileURL,
|
|
6
|
+
} from "node:url";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
createElement,
|
|
10
|
+
type ComponentType,
|
|
11
|
+
} from "react";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
renderToStaticMarkup,
|
|
15
|
+
} from "react-dom/server";
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
ResolvedMetadata,
|
|
19
|
+
} from "../../client/src/metadata.js";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
matchRoute,
|
|
23
|
+
scanRoutes,
|
|
24
|
+
} from "../../router/src/index.js";
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
resolveRouteMetadata,
|
|
28
|
+
type MetadataSource,
|
|
29
|
+
} from "./metadata.js";
|
|
30
|
+
|
|
31
|
+
export interface NavigationPayload {
|
|
32
|
+
route: string;
|
|
33
|
+
params: Record<string, string>;
|
|
34
|
+
clientScript: string;
|
|
35
|
+
clientImports: string[];
|
|
36
|
+
title: string;
|
|
37
|
+
metadata: ResolvedMetadata;
|
|
38
|
+
version: number;
|
|
39
|
+
loadingHtml: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface NavigationPayloadOptions {
|
|
43
|
+
resolveClientScript?: (
|
|
44
|
+
pathname: string
|
|
45
|
+
) => string | null;
|
|
46
|
+
|
|
47
|
+
resolveClientImports?: (
|
|
48
|
+
pathname: string
|
|
49
|
+
) => string[];
|
|
50
|
+
|
|
51
|
+
title?: string;
|
|
52
|
+
|
|
53
|
+
version?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function resolveNavigationPayload(
|
|
57
|
+
rootDirectory: string,
|
|
58
|
+
pathname: string,
|
|
59
|
+
options: NavigationPayloadOptions = {}
|
|
60
|
+
): Promise<NavigationPayload | null> {
|
|
61
|
+
const appDirectory =
|
|
62
|
+
path.join(
|
|
63
|
+
rootDirectory,
|
|
64
|
+
"app"
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const routes =
|
|
68
|
+
scanRoutes(
|
|
69
|
+
appDirectory
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const match =
|
|
73
|
+
matchRoute(
|
|
74
|
+
routes,
|
|
75
|
+
normalizePathname(
|
|
76
|
+
pathname
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (!match) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const loadingFile =
|
|
85
|
+
findNearestLoadingFile(
|
|
86
|
+
appDirectory,
|
|
87
|
+
match.route.filePath
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const loadingHtml =
|
|
91
|
+
loadingFile
|
|
92
|
+
? await renderLoadingFile(
|
|
93
|
+
loadingFile,
|
|
94
|
+
match.params
|
|
95
|
+
)
|
|
96
|
+
: null;
|
|
97
|
+
|
|
98
|
+
const clientScript =
|
|
99
|
+
options.resolveClientScript
|
|
100
|
+
? options.resolveClientScript(
|
|
101
|
+
match.route.pathname
|
|
102
|
+
)
|
|
103
|
+
: `/_bcp/client/${routeToBundleName(
|
|
104
|
+
match.route.pathname
|
|
105
|
+
)}`;
|
|
106
|
+
|
|
107
|
+
if (!clientScript) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const metadata =
|
|
112
|
+
await resolveDevMetadata(
|
|
113
|
+
[
|
|
114
|
+
...match.route.layouts,
|
|
115
|
+
match.route.filePath,
|
|
116
|
+
],
|
|
117
|
+
match.params,
|
|
118
|
+
rootDirectory,
|
|
119
|
+
options.title ??
|
|
120
|
+
"BCP Framework"
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
route:
|
|
125
|
+
match.route.pathname,
|
|
126
|
+
params:
|
|
127
|
+
match.params,
|
|
128
|
+
clientScript,
|
|
129
|
+
clientImports:
|
|
130
|
+
options.resolveClientImports?.(
|
|
131
|
+
match.route.pathname
|
|
132
|
+
) ?? [],
|
|
133
|
+
title:
|
|
134
|
+
metadata.title,
|
|
135
|
+
metadata,
|
|
136
|
+
version:
|
|
137
|
+
options.version ??
|
|
138
|
+
Date.now(),
|
|
139
|
+
loadingHtml,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function sendNavigationPayload(
|
|
144
|
+
res: http.ServerResponse,
|
|
145
|
+
payload: NavigationPayload,
|
|
146
|
+
statusCode = 200
|
|
147
|
+
): void {
|
|
148
|
+
sendJson(
|
|
149
|
+
res,
|
|
150
|
+
statusCode,
|
|
151
|
+
payload
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function sendNavigationError(
|
|
156
|
+
res: http.ServerResponse,
|
|
157
|
+
statusCode: number,
|
|
158
|
+
message: string
|
|
159
|
+
): void {
|
|
160
|
+
sendJson(
|
|
161
|
+
res,
|
|
162
|
+
statusCode,
|
|
163
|
+
{
|
|
164
|
+
error:
|
|
165
|
+
message,
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function resolveDevMetadata(
|
|
171
|
+
files: string[],
|
|
172
|
+
params: Record<string, string>,
|
|
173
|
+
rootDirectory: string,
|
|
174
|
+
fallbackTitle: string
|
|
175
|
+
): Promise<ResolvedMetadata> {
|
|
176
|
+
const sources:
|
|
177
|
+
MetadataSource[] = [];
|
|
178
|
+
|
|
179
|
+
for (const filePath of files) {
|
|
180
|
+
const moduleUrl =
|
|
181
|
+
pathToFileURL(
|
|
182
|
+
filePath
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
moduleUrl.searchParams.set(
|
|
186
|
+
"bcp-metadata",
|
|
187
|
+
String(
|
|
188
|
+
Date.now()
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const module =
|
|
193
|
+
await import(
|
|
194
|
+
moduleUrl.href
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
sources.push({
|
|
198
|
+
label:
|
|
199
|
+
path.relative(
|
|
200
|
+
rootDirectory,
|
|
201
|
+
filePath
|
|
202
|
+
)
|
|
203
|
+
.replace(
|
|
204
|
+
/\\/g,
|
|
205
|
+
"/"
|
|
206
|
+
),
|
|
207
|
+
metadata:
|
|
208
|
+
module.metadata,
|
|
209
|
+
generateMetadata:
|
|
210
|
+
module.generateMetadata,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return resolveRouteMetadata(
|
|
215
|
+
sources,
|
|
216
|
+
params,
|
|
217
|
+
fallbackTitle
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function renderLoadingFile(
|
|
222
|
+
filePath: string,
|
|
223
|
+
params: Record<string, string>
|
|
224
|
+
): Promise<string> {
|
|
225
|
+
const moduleUrl =
|
|
226
|
+
pathToFileURL(
|
|
227
|
+
filePath
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
moduleUrl.searchParams.set(
|
|
231
|
+
"bcp-loading",
|
|
232
|
+
String(
|
|
233
|
+
Date.now()
|
|
234
|
+
)
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const loadingModule =
|
|
238
|
+
await import(
|
|
239
|
+
moduleUrl.href
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const Loading =
|
|
243
|
+
loadingModule.default as
|
|
244
|
+
ComponentType<{
|
|
245
|
+
params: Record<string, string>;
|
|
246
|
+
}>;
|
|
247
|
+
|
|
248
|
+
if (
|
|
249
|
+
typeof Loading !==
|
|
250
|
+
"function"
|
|
251
|
+
) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
`BCP Framework: loading file "${filePath}" must export a default React component.`
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return renderToStaticMarkup(
|
|
258
|
+
createElement(
|
|
259
|
+
Loading,
|
|
260
|
+
{
|
|
261
|
+
params,
|
|
262
|
+
}
|
|
263
|
+
)
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function findNearestLoadingFile(
|
|
268
|
+
appDirectory: string,
|
|
269
|
+
pageFilePath: string
|
|
270
|
+
): string | null {
|
|
271
|
+
const appRoot =
|
|
272
|
+
path.resolve(
|
|
273
|
+
appDirectory
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
let currentDirectory =
|
|
277
|
+
path.dirname(
|
|
278
|
+
path.resolve(
|
|
279
|
+
pageFilePath
|
|
280
|
+
)
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
while (true) {
|
|
284
|
+
const loadingFile =
|
|
285
|
+
findLoadingFile(
|
|
286
|
+
currentDirectory
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
if (loadingFile) {
|
|
290
|
+
return loadingFile;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (
|
|
294
|
+
currentDirectory ===
|
|
295
|
+
appRoot
|
|
296
|
+
) {
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const parent =
|
|
301
|
+
path.dirname(
|
|
302
|
+
currentDirectory
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
if (
|
|
306
|
+
parent ===
|
|
307
|
+
currentDirectory ||
|
|
308
|
+
!isInsideDirectory(
|
|
309
|
+
appRoot,
|
|
310
|
+
parent
|
|
311
|
+
)
|
|
312
|
+
) {
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
currentDirectory =
|
|
317
|
+
parent;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function findLoadingFile(
|
|
324
|
+
directory: string
|
|
325
|
+
): string | null {
|
|
326
|
+
const tsxFile =
|
|
327
|
+
path.join(
|
|
328
|
+
directory,
|
|
329
|
+
"loading.tsx"
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const tsFile =
|
|
333
|
+
path.join(
|
|
334
|
+
directory,
|
|
335
|
+
"loading.ts"
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const hasTsx =
|
|
339
|
+
fs.existsSync(
|
|
340
|
+
tsxFile
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
const hasTs =
|
|
344
|
+
fs.existsSync(
|
|
345
|
+
tsFile
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
if (
|
|
349
|
+
hasTsx &&
|
|
350
|
+
hasTs
|
|
351
|
+
) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
`BCP Framework: both loading.ts and loading.tsx exist in "${directory}".`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (hasTsx) {
|
|
358
|
+
return tsxFile;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (hasTs) {
|
|
362
|
+
return tsFile;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function isInsideDirectory(
|
|
369
|
+
rootDirectory: string,
|
|
370
|
+
candidate: string
|
|
371
|
+
): boolean {
|
|
372
|
+
const relative =
|
|
373
|
+
path.relative(
|
|
374
|
+
rootDirectory,
|
|
375
|
+
candidate
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
relative === "" ||
|
|
380
|
+
(
|
|
381
|
+
!relative.startsWith("..") &&
|
|
382
|
+
!path.isAbsolute(
|
|
383
|
+
relative
|
|
384
|
+
)
|
|
385
|
+
)
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function sendJson(
|
|
390
|
+
res: http.ServerResponse,
|
|
391
|
+
statusCode: number,
|
|
392
|
+
value: unknown
|
|
393
|
+
): void {
|
|
394
|
+
const body =
|
|
395
|
+
JSON.stringify(
|
|
396
|
+
value
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
res.writeHead(
|
|
400
|
+
statusCode,
|
|
401
|
+
{
|
|
402
|
+
"Content-Type":
|
|
403
|
+
"application/json; charset=utf-8",
|
|
404
|
+
"Cache-Control":
|
|
405
|
+
"no-store",
|
|
406
|
+
"X-Content-Type-Options":
|
|
407
|
+
"nosniff",
|
|
408
|
+
"Content-Length":
|
|
409
|
+
Buffer.byteLength(
|
|
410
|
+
body
|
|
411
|
+
),
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
res.end(
|
|
416
|
+
body
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function routeToBundleName(
|
|
421
|
+
pathname: string
|
|
422
|
+
): string {
|
|
423
|
+
if (pathname === "/") {
|
|
424
|
+
return "index.js";
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const name =
|
|
428
|
+
pathname
|
|
429
|
+
.slice(1)
|
|
430
|
+
.split("/")
|
|
431
|
+
.map(
|
|
432
|
+
sanitizeSegment
|
|
433
|
+
)
|
|
434
|
+
.join("__");
|
|
435
|
+
|
|
436
|
+
return `${name}.js`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function sanitizeSegment(
|
|
440
|
+
segment: string
|
|
441
|
+
): string {
|
|
442
|
+
const dynamicMatch =
|
|
443
|
+
/^\[([A-Za-z_][A-Za-z0-9_]*)\]$/.exec(
|
|
444
|
+
segment
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
if (dynamicMatch) {
|
|
448
|
+
return `param-${dynamicMatch[1]}`;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return segment.replace(
|
|
452
|
+
/[^A-Za-z0-9._-]/g,
|
|
453
|
+
"_"
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function normalizePathname(
|
|
458
|
+
pathname: string
|
|
459
|
+
): string {
|
|
460
|
+
if (
|
|
461
|
+
pathname.length > 1 &&
|
|
462
|
+
pathname.endsWith("/")
|
|
463
|
+
) {
|
|
464
|
+
return pathname.slice(
|
|
465
|
+
0,
|
|
466
|
+
-1
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return pathname || "/";
|
|
471
|
+
}
|