@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,974 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
brotliCompressSync,
|
|
5
|
+
constants,
|
|
6
|
+
gzipSync,
|
|
7
|
+
} from "node:zlib";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
build,
|
|
11
|
+
type Metafile,
|
|
12
|
+
} from "esbuild";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
createClientEnvironmentDefines,
|
|
16
|
+
} from "../../env/src/index.js";
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
Route,
|
|
20
|
+
} from "../../router/src/index.js";
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
findNearestSpecialFile,
|
|
24
|
+
} from "./special-files.js";
|
|
25
|
+
|
|
26
|
+
export interface ProductionAssetSize {
|
|
27
|
+
raw: number;
|
|
28
|
+
gzip: number;
|
|
29
|
+
brotli: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ProductionAssetInfo {
|
|
33
|
+
path: string;
|
|
34
|
+
size: ProductionAssetSize;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ProductionRouteAsset {
|
|
38
|
+
entry: string;
|
|
39
|
+
imports: string[];
|
|
40
|
+
size: ProductionAssetSize;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ProductionBuildManifest {
|
|
44
|
+
version: number;
|
|
45
|
+
buildId: string;
|
|
46
|
+
builtAt: string;
|
|
47
|
+
routes: Record<
|
|
48
|
+
string,
|
|
49
|
+
ProductionRouteAsset
|
|
50
|
+
>;
|
|
51
|
+
assets: ProductionAssetInfo[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface OutputNode {
|
|
55
|
+
absolutePath: string;
|
|
56
|
+
publicPath: string;
|
|
57
|
+
metadata:
|
|
58
|
+
Metafile["outputs"][string];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const PRECOMPRESS_MIN_BYTES =
|
|
62
|
+
1024;
|
|
63
|
+
|
|
64
|
+
export async function buildProductionClient(
|
|
65
|
+
routes: Route[],
|
|
66
|
+
rootDirectory: string
|
|
67
|
+
): Promise<ProductionBuildManifest> {
|
|
68
|
+
const buildRoot =
|
|
69
|
+
path.join(
|
|
70
|
+
rootDirectory,
|
|
71
|
+
".bcp-framework",
|
|
72
|
+
"build"
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const clientDirectory =
|
|
76
|
+
path.join(
|
|
77
|
+
buildRoot,
|
|
78
|
+
"client"
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const entryDirectory =
|
|
82
|
+
path.join(
|
|
83
|
+
buildRoot,
|
|
84
|
+
".entries"
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
fs.rmSync(
|
|
88
|
+
buildRoot,
|
|
89
|
+
{
|
|
90
|
+
recursive: true,
|
|
91
|
+
force: true,
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
fs.mkdirSync(
|
|
96
|
+
entryDirectory,
|
|
97
|
+
{
|
|
98
|
+
recursive: true,
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
fs.mkdirSync(
|
|
103
|
+
clientDirectory,
|
|
104
|
+
{
|
|
105
|
+
recursive: true,
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const entryPoints:
|
|
110
|
+
Record<string, string> = {};
|
|
111
|
+
|
|
112
|
+
const entryToRoute =
|
|
113
|
+
new Map<string, string>();
|
|
114
|
+
|
|
115
|
+
for (
|
|
116
|
+
const route
|
|
117
|
+
of routes
|
|
118
|
+
) {
|
|
119
|
+
const entryName =
|
|
120
|
+
routeToEntryName(
|
|
121
|
+
route.pathname
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const entryFile =
|
|
125
|
+
path.join(
|
|
126
|
+
entryDirectory,
|
|
127
|
+
`${entryName}.mjs`
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
fs.writeFileSync(
|
|
131
|
+
entryFile,
|
|
132
|
+
createProductionClientEntry(
|
|
133
|
+
route,
|
|
134
|
+
entryDirectory
|
|
135
|
+
),
|
|
136
|
+
"utf8"
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
entryPoints[
|
|
140
|
+
entryName
|
|
141
|
+
] =
|
|
142
|
+
entryFile;
|
|
143
|
+
|
|
144
|
+
entryToRoute.set(
|
|
145
|
+
normalizePath(
|
|
146
|
+
entryFile
|
|
147
|
+
),
|
|
148
|
+
route.pathname
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const result =
|
|
153
|
+
await build({
|
|
154
|
+
absWorkingDir:
|
|
155
|
+
rootDirectory,
|
|
156
|
+
entryPoints,
|
|
157
|
+
outdir:
|
|
158
|
+
clientDirectory,
|
|
159
|
+
bundle:
|
|
160
|
+
true,
|
|
161
|
+
splitting:
|
|
162
|
+
true,
|
|
163
|
+
format:
|
|
164
|
+
"esm",
|
|
165
|
+
platform:
|
|
166
|
+
"browser",
|
|
167
|
+
target: [
|
|
168
|
+
"es2022",
|
|
169
|
+
],
|
|
170
|
+
minify:
|
|
171
|
+
process.env
|
|
172
|
+
.BCP_BUILD_MINIFY !==
|
|
173
|
+
"0",
|
|
174
|
+
treeShaking:
|
|
175
|
+
true,
|
|
176
|
+
sourcemap:
|
|
177
|
+
process.env
|
|
178
|
+
.BCP_BUILD_SOURCE_MAPS ===
|
|
179
|
+
"1",
|
|
180
|
+
metafile:
|
|
181
|
+
true,
|
|
182
|
+
jsx:
|
|
183
|
+
"automatic",
|
|
184
|
+
entryNames:
|
|
185
|
+
"routes/[name]-[hash]",
|
|
186
|
+
chunkNames:
|
|
187
|
+
"chunks/[name]-[hash]",
|
|
188
|
+
assetNames:
|
|
189
|
+
"assets/[name]-[hash]",
|
|
190
|
+
define:
|
|
191
|
+
createClientEnvironmentDefines(
|
|
192
|
+
"production"
|
|
193
|
+
),
|
|
194
|
+
legalComments:
|
|
195
|
+
"none",
|
|
196
|
+
logLevel:
|
|
197
|
+
"silent",
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (!result.metafile) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
"BCP Framework: esbuild did not return production metadata."
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const outputNodes =
|
|
207
|
+
createOutputNodeMap(
|
|
208
|
+
result.metafile,
|
|
209
|
+
rootDirectory,
|
|
210
|
+
clientDirectory
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const assetInfoByPath =
|
|
214
|
+
new Map<
|
|
215
|
+
string,
|
|
216
|
+
ProductionAssetInfo
|
|
217
|
+
>();
|
|
218
|
+
|
|
219
|
+
for (
|
|
220
|
+
const node
|
|
221
|
+
of outputNodes.values()
|
|
222
|
+
) {
|
|
223
|
+
const assetInfo =
|
|
224
|
+
precompressOutput(
|
|
225
|
+
node.absolutePath,
|
|
226
|
+
node.publicPath
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
assetInfoByPath.set(
|
|
230
|
+
node.publicPath,
|
|
231
|
+
assetInfo
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const routeManifest:
|
|
236
|
+
Record<
|
|
237
|
+
string,
|
|
238
|
+
ProductionRouteAsset
|
|
239
|
+
> = {};
|
|
240
|
+
|
|
241
|
+
for (
|
|
242
|
+
const node
|
|
243
|
+
of outputNodes.values()
|
|
244
|
+
) {
|
|
245
|
+
if (
|
|
246
|
+
!node.metadata.entryPoint
|
|
247
|
+
) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const absoluteEntry =
|
|
252
|
+
path.isAbsolute(
|
|
253
|
+
node.metadata.entryPoint
|
|
254
|
+
)
|
|
255
|
+
? node.metadata.entryPoint
|
|
256
|
+
: path.resolve(
|
|
257
|
+
rootDirectory,
|
|
258
|
+
node.metadata.entryPoint
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const routePathname =
|
|
262
|
+
entryToRoute.get(
|
|
263
|
+
normalizePath(
|
|
264
|
+
absoluteEntry
|
|
265
|
+
)
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (!routePathname) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const imports =
|
|
273
|
+
collectOutputImports(
|
|
274
|
+
node,
|
|
275
|
+
outputNodes,
|
|
276
|
+
rootDirectory
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const routeAssetPaths = [
|
|
280
|
+
node.publicPath,
|
|
281
|
+
...imports,
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
routeManifest[
|
|
285
|
+
routePathname
|
|
286
|
+
] = {
|
|
287
|
+
entry:
|
|
288
|
+
node.publicPath,
|
|
289
|
+
imports,
|
|
290
|
+
size:
|
|
291
|
+
sumAssetSizes(
|
|
292
|
+
routeAssetPaths,
|
|
293
|
+
assetInfoByPath
|
|
294
|
+
),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
for (
|
|
299
|
+
const route
|
|
300
|
+
of routes
|
|
301
|
+
) {
|
|
302
|
+
if (
|
|
303
|
+
!routeManifest[
|
|
304
|
+
route.pathname
|
|
305
|
+
]
|
|
306
|
+
) {
|
|
307
|
+
throw new Error(
|
|
308
|
+
`BCP Framework: production client output was not found for route "${route.pathname}".`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const version =
|
|
314
|
+
Date.now();
|
|
315
|
+
|
|
316
|
+
const manifest:
|
|
317
|
+
ProductionBuildManifest = {
|
|
318
|
+
version,
|
|
319
|
+
buildId:
|
|
320
|
+
version.toString(36),
|
|
321
|
+
builtAt:
|
|
322
|
+
new Date(
|
|
323
|
+
version
|
|
324
|
+
).toISOString(),
|
|
325
|
+
routes:
|
|
326
|
+
routeManifest,
|
|
327
|
+
assets:
|
|
328
|
+
Array.from(
|
|
329
|
+
assetInfoByPath.values()
|
|
330
|
+
).sort(
|
|
331
|
+
(left, right) =>
|
|
332
|
+
left.path.localeCompare(
|
|
333
|
+
right.path
|
|
334
|
+
)
|
|
335
|
+
),
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
fs.writeFileSync(
|
|
339
|
+
path.join(
|
|
340
|
+
buildRoot,
|
|
341
|
+
"manifest.json"
|
|
342
|
+
),
|
|
343
|
+
JSON.stringify(
|
|
344
|
+
manifest,
|
|
345
|
+
null,
|
|
346
|
+
2
|
|
347
|
+
),
|
|
348
|
+
"utf8"
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
fs.rmSync(
|
|
352
|
+
entryDirectory,
|
|
353
|
+
{
|
|
354
|
+
recursive: true,
|
|
355
|
+
force: true,
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
return manifest;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function createOutputNodeMap(
|
|
363
|
+
metafile: Metafile,
|
|
364
|
+
rootDirectory: string,
|
|
365
|
+
clientDirectory: string
|
|
366
|
+
): Map<string, OutputNode> {
|
|
367
|
+
const nodes =
|
|
368
|
+
new Map<string, OutputNode>();
|
|
369
|
+
|
|
370
|
+
for (
|
|
371
|
+
const [
|
|
372
|
+
outputName,
|
|
373
|
+
metadata,
|
|
374
|
+
]
|
|
375
|
+
of Object.entries(
|
|
376
|
+
metafile.outputs
|
|
377
|
+
)
|
|
378
|
+
) {
|
|
379
|
+
const absolutePath =
|
|
380
|
+
path.isAbsolute(
|
|
381
|
+
outputName
|
|
382
|
+
)
|
|
383
|
+
? outputName
|
|
384
|
+
: path.resolve(
|
|
385
|
+
rootDirectory,
|
|
386
|
+
outputName
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const relativePath =
|
|
390
|
+
path.relative(
|
|
391
|
+
clientDirectory,
|
|
392
|
+
absolutePath
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
if (
|
|
396
|
+
relativePath.startsWith(
|
|
397
|
+
".."
|
|
398
|
+
) ||
|
|
399
|
+
path.isAbsolute(
|
|
400
|
+
relativePath
|
|
401
|
+
)
|
|
402
|
+
) {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const publicPath =
|
|
407
|
+
`/_bcp/client/${relativePath.replace(
|
|
408
|
+
/\\/g,
|
|
409
|
+
"/"
|
|
410
|
+
)}`;
|
|
411
|
+
|
|
412
|
+
nodes.set(
|
|
413
|
+
normalizePath(
|
|
414
|
+
absolutePath
|
|
415
|
+
),
|
|
416
|
+
{
|
|
417
|
+
absolutePath,
|
|
418
|
+
publicPath,
|
|
419
|
+
metadata,
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return nodes;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function collectOutputImports(
|
|
428
|
+
entryNode: OutputNode,
|
|
429
|
+
nodes: Map<string, OutputNode>,
|
|
430
|
+
rootDirectory: string
|
|
431
|
+
): string[] {
|
|
432
|
+
const imports =
|
|
433
|
+
new Set<string>();
|
|
434
|
+
|
|
435
|
+
const visited =
|
|
436
|
+
new Set<string>();
|
|
437
|
+
|
|
438
|
+
function visit(
|
|
439
|
+
node: OutputNode
|
|
440
|
+
): void {
|
|
441
|
+
const key =
|
|
442
|
+
normalizePath(
|
|
443
|
+
node.absolutePath
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
if (
|
|
447
|
+
visited.has(
|
|
448
|
+
key
|
|
449
|
+
)
|
|
450
|
+
) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
visited.add(
|
|
455
|
+
key
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
for (
|
|
459
|
+
const imported
|
|
460
|
+
of node.metadata.imports
|
|
461
|
+
) {
|
|
462
|
+
if (
|
|
463
|
+
imported.external
|
|
464
|
+
) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const target =
|
|
469
|
+
resolveImportedOutput(
|
|
470
|
+
node,
|
|
471
|
+
imported.path,
|
|
472
|
+
nodes,
|
|
473
|
+
rootDirectory
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
if (!target) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (
|
|
481
|
+
target.publicPath !==
|
|
482
|
+
entryNode.publicPath &&
|
|
483
|
+
isModulePreloadAsset(
|
|
484
|
+
target.publicPath
|
|
485
|
+
)
|
|
486
|
+
) {
|
|
487
|
+
imports.add(
|
|
488
|
+
target.publicPath
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
visit(
|
|
493
|
+
target
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
visit(
|
|
499
|
+
entryNode
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
return Array.from(
|
|
503
|
+
imports
|
|
504
|
+
).sort();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function resolveImportedOutput(
|
|
508
|
+
source: OutputNode,
|
|
509
|
+
importPath: string,
|
|
510
|
+
nodes: Map<string, OutputNode>,
|
|
511
|
+
rootDirectory: string
|
|
512
|
+
): OutputNode | null {
|
|
513
|
+
const candidates =
|
|
514
|
+
path.isAbsolute(
|
|
515
|
+
importPath
|
|
516
|
+
)
|
|
517
|
+
? [
|
|
518
|
+
importPath,
|
|
519
|
+
]
|
|
520
|
+
: [
|
|
521
|
+
path.resolve(
|
|
522
|
+
rootDirectory,
|
|
523
|
+
importPath
|
|
524
|
+
),
|
|
525
|
+
path.resolve(
|
|
526
|
+
path.dirname(
|
|
527
|
+
source.absolutePath
|
|
528
|
+
),
|
|
529
|
+
importPath
|
|
530
|
+
),
|
|
531
|
+
];
|
|
532
|
+
|
|
533
|
+
for (
|
|
534
|
+
const candidate
|
|
535
|
+
of candidates
|
|
536
|
+
) {
|
|
537
|
+
const node =
|
|
538
|
+
nodes.get(
|
|
539
|
+
normalizePath(
|
|
540
|
+
candidate
|
|
541
|
+
)
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
if (node) {
|
|
545
|
+
return node;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function precompressOutput(
|
|
553
|
+
absolutePath: string,
|
|
554
|
+
publicPath: string
|
|
555
|
+
): ProductionAssetInfo {
|
|
556
|
+
const content =
|
|
557
|
+
fs.readFileSync(
|
|
558
|
+
absolutePath
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
const raw =
|
|
562
|
+
content.length;
|
|
563
|
+
|
|
564
|
+
let gzip = raw;
|
|
565
|
+
let brotli = raw;
|
|
566
|
+
|
|
567
|
+
if (
|
|
568
|
+
process.env
|
|
569
|
+
.BCP_COMPRESSION !==
|
|
570
|
+
"0" &&
|
|
571
|
+
raw >=
|
|
572
|
+
PRECOMPRESS_MIN_BYTES &&
|
|
573
|
+
isPrecompressibleAsset(
|
|
574
|
+
absolutePath
|
|
575
|
+
)
|
|
576
|
+
) {
|
|
577
|
+
const gzipContent =
|
|
578
|
+
gzipSync(
|
|
579
|
+
content,
|
|
580
|
+
{
|
|
581
|
+
level: 9,
|
|
582
|
+
}
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
if (
|
|
586
|
+
gzipContent.length < raw
|
|
587
|
+
) {
|
|
588
|
+
fs.writeFileSync(
|
|
589
|
+
`${absolutePath}.gz`,
|
|
590
|
+
gzipContent
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
gzip =
|
|
594
|
+
gzipContent.length;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const brotliContent =
|
|
598
|
+
brotliCompressSync(
|
|
599
|
+
content,
|
|
600
|
+
{
|
|
601
|
+
params: {
|
|
602
|
+
[constants.BROTLI_PARAM_QUALITY]:
|
|
603
|
+
8,
|
|
604
|
+
},
|
|
605
|
+
}
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
if (
|
|
609
|
+
brotliContent.length < raw
|
|
610
|
+
) {
|
|
611
|
+
fs.writeFileSync(
|
|
612
|
+
`${absolutePath}.br`,
|
|
613
|
+
brotliContent
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
brotli =
|
|
617
|
+
brotliContent.length;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return {
|
|
622
|
+
path:
|
|
623
|
+
publicPath,
|
|
624
|
+
size: {
|
|
625
|
+
raw,
|
|
626
|
+
gzip,
|
|
627
|
+
brotli,
|
|
628
|
+
},
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function sumAssetSizes(
|
|
633
|
+
assetPaths: string[],
|
|
634
|
+
assets: Map<
|
|
635
|
+
string,
|
|
636
|
+
ProductionAssetInfo
|
|
637
|
+
>
|
|
638
|
+
): ProductionAssetSize {
|
|
639
|
+
const total:
|
|
640
|
+
ProductionAssetSize = {
|
|
641
|
+
raw: 0,
|
|
642
|
+
gzip: 0,
|
|
643
|
+
brotli: 0,
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
for (
|
|
647
|
+
const assetPath
|
|
648
|
+
of assetPaths
|
|
649
|
+
) {
|
|
650
|
+
const asset =
|
|
651
|
+
assets.get(
|
|
652
|
+
assetPath
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
if (!asset) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
total.raw +=
|
|
660
|
+
asset.size.raw;
|
|
661
|
+
total.gzip +=
|
|
662
|
+
asset.size.gzip;
|
|
663
|
+
total.brotli +=
|
|
664
|
+
asset.size.brotli;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return total;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function isModulePreloadAsset(
|
|
671
|
+
publicPath: string
|
|
672
|
+
): boolean {
|
|
673
|
+
return /\.(?:m?js)$/i.test(
|
|
674
|
+
publicPath
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function isPrecompressibleAsset(
|
|
679
|
+
filePath: string
|
|
680
|
+
): boolean {
|
|
681
|
+
return /\.(?:m?js|css|json|html?|svg|txt|xml)$/i.test(
|
|
682
|
+
filePath
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function createProductionClientEntry(
|
|
687
|
+
route: Route,
|
|
688
|
+
entryDirectory: string
|
|
689
|
+
): string {
|
|
690
|
+
const pageImport =
|
|
691
|
+
toImportSpecifier(
|
|
692
|
+
entryDirectory,
|
|
693
|
+
route.filePath
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
const appDirectory =
|
|
697
|
+
resolveRouteAppDirectory(
|
|
698
|
+
route
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
const errorFile =
|
|
702
|
+
findNearestSpecialFile(
|
|
703
|
+
appDirectory,
|
|
704
|
+
route.filePath,
|
|
705
|
+
"error"
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
const notFoundFile =
|
|
709
|
+
findNearestSpecialFile(
|
|
710
|
+
appDirectory,
|
|
711
|
+
route.filePath,
|
|
712
|
+
"not-found"
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
const errorImport =
|
|
716
|
+
errorFile
|
|
717
|
+
? `import ErrorComponent from ${JSON.stringify(
|
|
718
|
+
toImportSpecifier(
|
|
719
|
+
entryDirectory,
|
|
720
|
+
errorFile
|
|
721
|
+
)
|
|
722
|
+
)};`
|
|
723
|
+
: "const ErrorComponent = null;";
|
|
724
|
+
|
|
725
|
+
const notFoundImport =
|
|
726
|
+
notFoundFile
|
|
727
|
+
? `import NotFoundComponent from ${JSON.stringify(
|
|
728
|
+
toImportSpecifier(
|
|
729
|
+
entryDirectory,
|
|
730
|
+
notFoundFile
|
|
731
|
+
)
|
|
732
|
+
)};`
|
|
733
|
+
: "const NotFoundComponent = null;";
|
|
734
|
+
|
|
735
|
+
const layoutImports =
|
|
736
|
+
route.layouts
|
|
737
|
+
.map(
|
|
738
|
+
(
|
|
739
|
+
layoutFile,
|
|
740
|
+
index
|
|
741
|
+
) =>
|
|
742
|
+
`import Layout${index} from ${JSON.stringify(
|
|
743
|
+
toImportSpecifier(
|
|
744
|
+
entryDirectory,
|
|
745
|
+
layoutFile
|
|
746
|
+
)
|
|
747
|
+
)};`
|
|
748
|
+
)
|
|
749
|
+
.join("\n");
|
|
750
|
+
|
|
751
|
+
const layoutNames =
|
|
752
|
+
route.layouts
|
|
753
|
+
.map(
|
|
754
|
+
(
|
|
755
|
+
_,
|
|
756
|
+
index
|
|
757
|
+
) =>
|
|
758
|
+
`Layout${index}`
|
|
759
|
+
)
|
|
760
|
+
.join(", ");
|
|
761
|
+
|
|
762
|
+
return `
|
|
763
|
+
import {
|
|
764
|
+
BcpRouteErrorBoundary
|
|
765
|
+
} from "bcp";
|
|
766
|
+
|
|
767
|
+
import {
|
|
768
|
+
createElement
|
|
769
|
+
} from "react";
|
|
770
|
+
|
|
771
|
+
import {
|
|
772
|
+
hydrateRoot
|
|
773
|
+
} from "react-dom/client";
|
|
774
|
+
|
|
775
|
+
import Page from ${JSON.stringify(pageImport)};
|
|
776
|
+
|
|
777
|
+
${errorImport}
|
|
778
|
+
${notFoundImport}
|
|
779
|
+
${layoutImports}
|
|
780
|
+
|
|
781
|
+
const rootElement =
|
|
782
|
+
document.getElementById(
|
|
783
|
+
"root"
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
if (!rootElement) {
|
|
787
|
+
throw new Error(
|
|
788
|
+
"BCP Framework: root element was not found."
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const dataElement =
|
|
793
|
+
document.getElementById(
|
|
794
|
+
"__BCP_DATA__"
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
if (!dataElement) {
|
|
798
|
+
throw new Error(
|
|
799
|
+
"BCP Framework: framework data was not found."
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const data =
|
|
804
|
+
JSON.parse(
|
|
805
|
+
dataElement.textContent ||
|
|
806
|
+
"{}"
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
const params =
|
|
810
|
+
data.params || {};
|
|
811
|
+
|
|
812
|
+
const layouts = [
|
|
813
|
+
${layoutNames}
|
|
814
|
+
];
|
|
815
|
+
|
|
816
|
+
let tree =
|
|
817
|
+
createElement(
|
|
818
|
+
Page,
|
|
819
|
+
{
|
|
820
|
+
params
|
|
821
|
+
}
|
|
822
|
+
);
|
|
823
|
+
|
|
824
|
+
tree =
|
|
825
|
+
createElement(
|
|
826
|
+
BcpRouteErrorBoundary,
|
|
827
|
+
{
|
|
828
|
+
ErrorComponent,
|
|
829
|
+
NotFoundComponent
|
|
830
|
+
},
|
|
831
|
+
tree
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
for (
|
|
835
|
+
let index =
|
|
836
|
+
layouts.length - 1;
|
|
837
|
+
|
|
838
|
+
index >= 0;
|
|
839
|
+
|
|
840
|
+
index--
|
|
841
|
+
) {
|
|
842
|
+
const Layout =
|
|
843
|
+
layouts[index];
|
|
844
|
+
|
|
845
|
+
tree =
|
|
846
|
+
createElement(
|
|
847
|
+
Layout,
|
|
848
|
+
{
|
|
849
|
+
params
|
|
850
|
+
},
|
|
851
|
+
tree
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (
|
|
856
|
+
globalThis.__BCP_REACT_ROOT__
|
|
857
|
+
) {
|
|
858
|
+
globalThis.__BCP_REACT_ROOT__
|
|
859
|
+
.render(tree);
|
|
860
|
+
} else {
|
|
861
|
+
globalThis.__BCP_REACT_ROOT__ =
|
|
862
|
+
hydrateRoot(
|
|
863
|
+
rootElement,
|
|
864
|
+
tree
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
`.trim();
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function resolveRouteAppDirectory(
|
|
871
|
+
route: Route
|
|
872
|
+
): string {
|
|
873
|
+
let directory =
|
|
874
|
+
path.dirname(
|
|
875
|
+
route.filePath
|
|
876
|
+
);
|
|
877
|
+
|
|
878
|
+
for (
|
|
879
|
+
let index = 0;
|
|
880
|
+
index <
|
|
881
|
+
route.segments.length;
|
|
882
|
+
index++
|
|
883
|
+
) {
|
|
884
|
+
directory =
|
|
885
|
+
path.dirname(
|
|
886
|
+
directory
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
return directory;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function toImportSpecifier(
|
|
894
|
+
fromDirectory: string,
|
|
895
|
+
filePath: string
|
|
896
|
+
): string {
|
|
897
|
+
let relative =
|
|
898
|
+
path.relative(
|
|
899
|
+
fromDirectory,
|
|
900
|
+
filePath
|
|
901
|
+
)
|
|
902
|
+
.replace(
|
|
903
|
+
/\\/g,
|
|
904
|
+
"/"
|
|
905
|
+
);
|
|
906
|
+
|
|
907
|
+
if (
|
|
908
|
+
!relative.startsWith(".")
|
|
909
|
+
) {
|
|
910
|
+
relative =
|
|
911
|
+
`./${relative}`;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
return relative;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function routeToEntryName(
|
|
918
|
+
pathname: string
|
|
919
|
+
): string {
|
|
920
|
+
if (
|
|
921
|
+
pathname === "/"
|
|
922
|
+
) {
|
|
923
|
+
return "index";
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
return pathname
|
|
927
|
+
.slice(1)
|
|
928
|
+
.split("/")
|
|
929
|
+
.map(
|
|
930
|
+
sanitizeSegment
|
|
931
|
+
)
|
|
932
|
+
.join("__");
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function sanitizeSegment(
|
|
936
|
+
segment: string
|
|
937
|
+
): string {
|
|
938
|
+
const dynamicMatch =
|
|
939
|
+
/^\[([A-Za-z_][A-Za-z0-9_]*)\]$/.exec(
|
|
940
|
+
segment
|
|
941
|
+
);
|
|
942
|
+
|
|
943
|
+
if (
|
|
944
|
+
dynamicMatch
|
|
945
|
+
) {
|
|
946
|
+
return `param-${dynamicMatch[1]}`;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
return segment.replace(
|
|
950
|
+
/[^A-Za-z0-9._-]/g,
|
|
951
|
+
"_"
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function normalizePath(
|
|
956
|
+
filePath: string
|
|
957
|
+
): string {
|
|
958
|
+
let normalized =
|
|
959
|
+
path.normalize(
|
|
960
|
+
path.resolve(
|
|
961
|
+
filePath
|
|
962
|
+
)
|
|
963
|
+
);
|
|
964
|
+
|
|
965
|
+
if (
|
|
966
|
+
process.platform ===
|
|
967
|
+
"win32"
|
|
968
|
+
) {
|
|
969
|
+
normalized =
|
|
970
|
+
normalized.toLowerCase();
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
return normalized;
|
|
974
|
+
}
|