@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,317 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import type { Metafile } from "esbuild";
|
|
3
|
+
|
|
4
|
+
export interface ModuleNode {
|
|
5
|
+
id: string;
|
|
6
|
+
bytes: number;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Modules ที่ module นี้ import
|
|
10
|
+
*/
|
|
11
|
+
dependencies: Set<string>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Modules ที่ import module นี้
|
|
15
|
+
*/
|
|
16
|
+
importers: Set<string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ModuleGraphStats {
|
|
20
|
+
modules: number;
|
|
21
|
+
edges: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class ModuleGraph {
|
|
25
|
+
private readonly rootDir: string;
|
|
26
|
+
|
|
27
|
+
private nodes = new Map<string, ModuleNode>();
|
|
28
|
+
|
|
29
|
+
constructor(rootDir: string) {
|
|
30
|
+
this.rootDir = path.resolve(rootDir);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Update graph จาก esbuild metafile
|
|
35
|
+
*/
|
|
36
|
+
update(metafile: Metafile): void {
|
|
37
|
+
const nextNodes = new Map<string, ModuleNode>();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* ----------------------------------------
|
|
41
|
+
* 1. Create all nodes
|
|
42
|
+
* ----------------------------------------
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
for (const [inputPath, input] of Object.entries(metafile.inputs)) {
|
|
46
|
+
const id = this.normalizeInputPath(inputPath);
|
|
47
|
+
|
|
48
|
+
nextNodes.set(id, {
|
|
49
|
+
id,
|
|
50
|
+
bytes: input.bytes,
|
|
51
|
+
dependencies: new Set<string>(),
|
|
52
|
+
importers: new Set<string>(),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* ----------------------------------------
|
|
58
|
+
* 2. Build dependency graph
|
|
59
|
+
* ----------------------------------------
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
for (const [inputPath, input] of Object.entries(metafile.inputs)) {
|
|
63
|
+
const importer = this.normalizeInputPath(inputPath);
|
|
64
|
+
|
|
65
|
+
const importerNode = nextNodes.get(importer);
|
|
66
|
+
|
|
67
|
+
if (!importerNode) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const imported of input.imports) {
|
|
72
|
+
/**
|
|
73
|
+
* External module ไม่อยู่ใน graph ของ application
|
|
74
|
+
*/
|
|
75
|
+
if (imported.external) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const dependency = this.resolveDependency(
|
|
80
|
+
importer,
|
|
81
|
+
imported.path,
|
|
82
|
+
nextNodes,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!dependency) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
importerNode.dependencies.add(dependency);
|
|
90
|
+
|
|
91
|
+
const dependencyNode = nextNodes.get(dependency);
|
|
92
|
+
|
|
93
|
+
if (dependencyNode) {
|
|
94
|
+
dependencyNode.importers.add(importer);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.nodes = nextNodes;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* หา module ที่ได้รับผลกระทบจาก file ที่เปลี่ยน
|
|
104
|
+
*
|
|
105
|
+
* ตัวอย่าง:
|
|
106
|
+
*
|
|
107
|
+
* page.tsx
|
|
108
|
+
* ↑
|
|
109
|
+
* layout.tsx
|
|
110
|
+
* ↑
|
|
111
|
+
* app.tsx
|
|
112
|
+
*
|
|
113
|
+
* ถ้า page.tsx เปลี่ยน
|
|
114
|
+
* affected จะเป็น:
|
|
115
|
+
*
|
|
116
|
+
* page.tsx
|
|
117
|
+
* layout.tsx
|
|
118
|
+
* app.tsx
|
|
119
|
+
*/
|
|
120
|
+
getAffectedModules(filePath: string): string[] {
|
|
121
|
+
const start = this.normalizeAbsolutePath(filePath);
|
|
122
|
+
|
|
123
|
+
if (!this.nodes.has(start)) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const visited = new Set<string>();
|
|
128
|
+
const queue: string[] = [start];
|
|
129
|
+
|
|
130
|
+
while (queue.length > 0) {
|
|
131
|
+
const current = queue.shift();
|
|
132
|
+
|
|
133
|
+
if (!current) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (visited.has(current)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
visited.add(current);
|
|
142
|
+
|
|
143
|
+
const node = this.nodes.get(current);
|
|
144
|
+
|
|
145
|
+
if (!node) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const importer of node.importers) {
|
|
150
|
+
if (!visited.has(importer)) {
|
|
151
|
+
queue.push(importer);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return [...visited];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* ตรวจสอบว่ามี module นี้หรือไม่
|
|
161
|
+
*/
|
|
162
|
+
has(filePath: string): boolean {
|
|
163
|
+
return this.nodes.has(this.normalizeAbsolutePath(filePath));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* ดึง module
|
|
168
|
+
*/
|
|
169
|
+
get(filePath: string): ModuleNode | undefined {
|
|
170
|
+
return this.nodes.get(this.normalizeAbsolutePath(filePath));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* ดึงทุก module
|
|
175
|
+
*/
|
|
176
|
+
getAll(): ModuleNode[] {
|
|
177
|
+
return [...this.nodes.values()];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* จำนวน module / dependency
|
|
182
|
+
*/
|
|
183
|
+
getStats(): ModuleGraphStats {
|
|
184
|
+
let edges = 0;
|
|
185
|
+
|
|
186
|
+
for (const node of this.nodes.values()) {
|
|
187
|
+
edges += node.dependencies.size;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
modules: this.nodes.size,
|
|
192
|
+
edges,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Debug graph
|
|
198
|
+
*/
|
|
199
|
+
print(): void {
|
|
200
|
+
console.log("\n[BCP] Module Graph");
|
|
201
|
+
|
|
202
|
+
for (const node of this.nodes.values()) {
|
|
203
|
+
const relative = path.relative(this.rootDir, node.id);
|
|
204
|
+
|
|
205
|
+
console.log(` ${relative}`);
|
|
206
|
+
|
|
207
|
+
for (const dependency of node.dependencies) {
|
|
208
|
+
const dependencyRelative = path.relative(
|
|
209
|
+
this.rootDir,
|
|
210
|
+
dependency,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
console.log(` └─ ${dependencyRelative}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const stats = this.getStats();
|
|
218
|
+
|
|
219
|
+
console.log(
|
|
220
|
+
`[BCP] ${stats.modules} modules / ${stats.edges} dependencies\n`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Normalize path จาก metafile
|
|
226
|
+
*/
|
|
227
|
+
private normalizeInputPath(inputPath: string): string {
|
|
228
|
+
if (path.isAbsolute(inputPath)) {
|
|
229
|
+
return this.normalizeAbsolutePath(inputPath);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return this.normalizeAbsolutePath(
|
|
233
|
+
path.resolve(this.rootDir, inputPath),
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Normalize absolute path
|
|
239
|
+
*/
|
|
240
|
+
private normalizeAbsolutePath(filePath: string): string {
|
|
241
|
+
return path.normalize(path.resolve(filePath));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Resolve import จาก metafile
|
|
246
|
+
*/
|
|
247
|
+
private resolveDependency(
|
|
248
|
+
importer: string,
|
|
249
|
+
importedPath: string,
|
|
250
|
+
nodes: Map<string, ModuleNode>,
|
|
251
|
+
): string | undefined {
|
|
252
|
+
const normalizedImported = importedPath.replaceAll("/", path.sep);
|
|
253
|
+
|
|
254
|
+
const candidates: string[] = [];
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* absolute
|
|
258
|
+
*/
|
|
259
|
+
if (path.isAbsolute(normalizedImported)) {
|
|
260
|
+
candidates.push(
|
|
261
|
+
this.normalizeAbsolutePath(normalizedImported),
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* relative to importer
|
|
267
|
+
*/
|
|
268
|
+
candidates.push(
|
|
269
|
+
this.normalizeAbsolutePath(
|
|
270
|
+
path.resolve(
|
|
271
|
+
path.dirname(importer),
|
|
272
|
+
normalizedImported,
|
|
273
|
+
),
|
|
274
|
+
),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* relative to project root
|
|
279
|
+
*/
|
|
280
|
+
candidates.push(
|
|
281
|
+
this.normalizeAbsolutePath(
|
|
282
|
+
path.resolve(
|
|
283
|
+
this.rootDir,
|
|
284
|
+
normalizedImported,
|
|
285
|
+
),
|
|
286
|
+
),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
for (const candidate of candidates) {
|
|
290
|
+
if (nodes.has(candidate)) {
|
|
291
|
+
return candidate;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* metafile บางกรณีใช้ path relative
|
|
297
|
+
*/
|
|
298
|
+
const normalizedRelative = path
|
|
299
|
+
.normalize(normalizedImported);
|
|
300
|
+
|
|
301
|
+
for (const nodePath of nodes.keys()) {
|
|
302
|
+
const relative = path
|
|
303
|
+
.relative(this.rootDir, nodePath)
|
|
304
|
+
.split(path.sep)
|
|
305
|
+
.join("/");
|
|
306
|
+
|
|
307
|
+
if (
|
|
308
|
+
relative === normalizedImported ||
|
|
309
|
+
path.normalize(relative) === normalizedRelative
|
|
310
|
+
) {
|
|
311
|
+
return nodePath;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
Route,
|
|
7
|
+
} from "../../router/src/index.js";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
buildClientIslands,
|
|
11
|
+
type ProductionIslandAsset,
|
|
12
|
+
} from "./client-islands.js";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
buildProductionClient as buildBaseProductionClient,
|
|
16
|
+
type ProductionAssetInfo,
|
|
17
|
+
type ProductionBuildManifest,
|
|
18
|
+
} from "./production.js";
|
|
19
|
+
|
|
20
|
+
export type HydrationMode =
|
|
21
|
+
| "full"
|
|
22
|
+
| "none";
|
|
23
|
+
|
|
24
|
+
export interface PartialHydrationBuildManifest
|
|
25
|
+
extends ProductionBuildManifest {
|
|
26
|
+
hydration: {
|
|
27
|
+
serverOnlyRoutes: string[];
|
|
28
|
+
};
|
|
29
|
+
islands: Record<
|
|
30
|
+
string,
|
|
31
|
+
ProductionIslandAsset
|
|
32
|
+
>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function buildProductionClient(
|
|
36
|
+
routes: Route[],
|
|
37
|
+
rootDirectory: string
|
|
38
|
+
): Promise<PartialHydrationBuildManifest> {
|
|
39
|
+
const manifest =
|
|
40
|
+
await buildBaseProductionClient(
|
|
41
|
+
routes,
|
|
42
|
+
rootDirectory
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const islandsEnabled =
|
|
46
|
+
process.env
|
|
47
|
+
.BCP_EXPERIMENTAL_ISLANDS !==
|
|
48
|
+
"0";
|
|
49
|
+
|
|
50
|
+
const partialHydrationEnabled =
|
|
51
|
+
process.env
|
|
52
|
+
.BCP_EXPERIMENTAL_PARTIAL_HYDRATION !==
|
|
53
|
+
"0";
|
|
54
|
+
|
|
55
|
+
const islands =
|
|
56
|
+
islandsEnabled
|
|
57
|
+
? await buildClientIslands(
|
|
58
|
+
manifest,
|
|
59
|
+
rootDirectory
|
|
60
|
+
)
|
|
61
|
+
: {};
|
|
62
|
+
|
|
63
|
+
const serverOnlyRoutes =
|
|
64
|
+
partialHydrationEnabled
|
|
65
|
+
? applyPartialHydration(
|
|
66
|
+
manifest,
|
|
67
|
+
routes,
|
|
68
|
+
rootDirectory
|
|
69
|
+
)
|
|
70
|
+
: [];
|
|
71
|
+
|
|
72
|
+
const result:
|
|
73
|
+
PartialHydrationBuildManifest = {
|
|
74
|
+
...manifest,
|
|
75
|
+
hydration: {
|
|
76
|
+
serverOnlyRoutes,
|
|
77
|
+
},
|
|
78
|
+
islands,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
writeManifest(
|
|
82
|
+
result,
|
|
83
|
+
rootDirectory
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function applyPartialHydration(
|
|
90
|
+
manifest: ProductionBuildManifest,
|
|
91
|
+
routes: Route[],
|
|
92
|
+
rootDirectory: string
|
|
93
|
+
): string[] {
|
|
94
|
+
const buildRoot =
|
|
95
|
+
path.join(
|
|
96
|
+
rootDirectory,
|
|
97
|
+
".bcp-framework",
|
|
98
|
+
"build"
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const clientDirectory =
|
|
102
|
+
path.join(
|
|
103
|
+
buildRoot,
|
|
104
|
+
"client"
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const serverOnlyRoutes:
|
|
108
|
+
string[] = [];
|
|
109
|
+
|
|
110
|
+
for (
|
|
111
|
+
const route
|
|
112
|
+
of routes
|
|
113
|
+
) {
|
|
114
|
+
const hydration =
|
|
115
|
+
readHydrationMode(
|
|
116
|
+
route.filePath
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (
|
|
120
|
+
hydration !== "none"
|
|
121
|
+
) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const current =
|
|
126
|
+
manifest.routes[
|
|
127
|
+
route.pathname
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
if (!current) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`BCP Framework: production client entry missing for route "${route.pathname}".`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
removeUnusedRouteEntry(
|
|
137
|
+
current.entry,
|
|
138
|
+
manifest,
|
|
139
|
+
clientDirectory
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const staticRuntime =
|
|
143
|
+
createServerOnlyRuntime();
|
|
144
|
+
|
|
145
|
+
const hash =
|
|
146
|
+
crypto
|
|
147
|
+
.createHash("sha256")
|
|
148
|
+
.update(
|
|
149
|
+
route.pathname
|
|
150
|
+
)
|
|
151
|
+
.update("\0")
|
|
152
|
+
.update(
|
|
153
|
+
staticRuntime
|
|
154
|
+
)
|
|
155
|
+
.digest("hex")
|
|
156
|
+
.slice(0, 10)
|
|
157
|
+
.toUpperCase();
|
|
158
|
+
|
|
159
|
+
const fileName =
|
|
160
|
+
`${routeToEntryName(
|
|
161
|
+
route.pathname
|
|
162
|
+
)}-server-only-${hash}.js`;
|
|
163
|
+
|
|
164
|
+
const relativePath =
|
|
165
|
+
path.join(
|
|
166
|
+
"routes",
|
|
167
|
+
fileName
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const outputPath =
|
|
171
|
+
path.join(
|
|
172
|
+
clientDirectory,
|
|
173
|
+
relativePath
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
fs.mkdirSync(
|
|
177
|
+
path.dirname(
|
|
178
|
+
outputPath
|
|
179
|
+
),
|
|
180
|
+
{
|
|
181
|
+
recursive: true,
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
fs.writeFileSync(
|
|
186
|
+
outputPath,
|
|
187
|
+
staticRuntime,
|
|
188
|
+
"utf8"
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const publicPath =
|
|
192
|
+
`/_bcp/client/${relativePath.replace(
|
|
193
|
+
/\\/g,
|
|
194
|
+
"/"
|
|
195
|
+
)}`;
|
|
196
|
+
|
|
197
|
+
const size =
|
|
198
|
+
Buffer.byteLength(
|
|
199
|
+
staticRuntime
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
manifest.routes[
|
|
203
|
+
route.pathname
|
|
204
|
+
] = {
|
|
205
|
+
entry:
|
|
206
|
+
publicPath,
|
|
207
|
+
imports: [],
|
|
208
|
+
size: {
|
|
209
|
+
raw:
|
|
210
|
+
size,
|
|
211
|
+
gzip:
|
|
212
|
+
size,
|
|
213
|
+
brotli:
|
|
214
|
+
size,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
manifest.assets.push({
|
|
219
|
+
path:
|
|
220
|
+
publicPath,
|
|
221
|
+
size: {
|
|
222
|
+
raw:
|
|
223
|
+
size,
|
|
224
|
+
gzip:
|
|
225
|
+
size,
|
|
226
|
+
brotli:
|
|
227
|
+
size,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
serverOnlyRoutes.push(
|
|
232
|
+
route.pathname
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
manifest.assets.sort(
|
|
237
|
+
(left, right) =>
|
|
238
|
+
left.path.localeCompare(
|
|
239
|
+
right.path
|
|
240
|
+
)
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return serverOnlyRoutes.sort();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function writeManifest(
|
|
247
|
+
manifest: PartialHydrationBuildManifest,
|
|
248
|
+
rootDirectory: string
|
|
249
|
+
): void {
|
|
250
|
+
const manifestPath =
|
|
251
|
+
path.join(
|
|
252
|
+
rootDirectory,
|
|
253
|
+
".bcp-framework",
|
|
254
|
+
"build",
|
|
255
|
+
"manifest.json"
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
fs.writeFileSync(
|
|
259
|
+
manifestPath,
|
|
260
|
+
JSON.stringify(
|
|
261
|
+
manifest,
|
|
262
|
+
null,
|
|
263
|
+
2
|
|
264
|
+
),
|
|
265
|
+
"utf8"
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function readHydrationMode(
|
|
270
|
+
filePath: string
|
|
271
|
+
): HydrationMode {
|
|
272
|
+
const source =
|
|
273
|
+
fs.readFileSync(
|
|
274
|
+
filePath,
|
|
275
|
+
"utf8"
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const match =
|
|
279
|
+
/\bexport\s+const\s+hydration\s*(?::[^=;]+)?=\s*["']([^"']+)["']/.exec(
|
|
280
|
+
source
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
if (!match) {
|
|
284
|
+
return "full";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const value =
|
|
288
|
+
match[1];
|
|
289
|
+
|
|
290
|
+
if (
|
|
291
|
+
value === "full" ||
|
|
292
|
+
value === "none"
|
|
293
|
+
) {
|
|
294
|
+
return value;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
throw new Error(
|
|
298
|
+
`BCP Framework: invalid hydration mode "${value}" in "${filePath}". Use "full" or "none".`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function removeUnusedRouteEntry(
|
|
303
|
+
publicPath: string,
|
|
304
|
+
manifest: ProductionBuildManifest,
|
|
305
|
+
clientDirectory: string
|
|
306
|
+
): void {
|
|
307
|
+
const prefix =
|
|
308
|
+
"/_bcp/client/";
|
|
309
|
+
|
|
310
|
+
if (
|
|
311
|
+
!publicPath.startsWith(
|
|
312
|
+
prefix
|
|
313
|
+
)
|
|
314
|
+
) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const relativePath =
|
|
319
|
+
publicPath.slice(
|
|
320
|
+
prefix.length
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const outputPath =
|
|
324
|
+
path.resolve(
|
|
325
|
+
clientDirectory,
|
|
326
|
+
relativePath.replaceAll(
|
|
327
|
+
"/",
|
|
328
|
+
path.sep
|
|
329
|
+
)
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const clientRoot =
|
|
333
|
+
path.resolve(
|
|
334
|
+
clientDirectory
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const relative =
|
|
338
|
+
path.relative(
|
|
339
|
+
clientRoot,
|
|
340
|
+
outputPath
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
if (
|
|
344
|
+
relative.startsWith("..") ||
|
|
345
|
+
path.isAbsolute(
|
|
346
|
+
relative
|
|
347
|
+
)
|
|
348
|
+
) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
for (
|
|
353
|
+
const candidate
|
|
354
|
+
of [
|
|
355
|
+
outputPath,
|
|
356
|
+
`${outputPath}.gz`,
|
|
357
|
+
`${outputPath}.br`,
|
|
358
|
+
]
|
|
359
|
+
) {
|
|
360
|
+
fs.rmSync(
|
|
361
|
+
candidate,
|
|
362
|
+
{
|
|
363
|
+
force: true,
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
manifest.assets =
|
|
369
|
+
manifest.assets.filter(
|
|
370
|
+
(asset) =>
|
|
371
|
+
asset.path !==
|
|
372
|
+
publicPath
|
|
373
|
+
) as ProductionAssetInfo[];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function createServerOnlyRuntime(): string {
|
|
377
|
+
return `if(globalThis.__BCP_REACT_ROOT__){window.location.reload();}`;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function routeToEntryName(
|
|
381
|
+
pathname: string
|
|
382
|
+
): string {
|
|
383
|
+
if (
|
|
384
|
+
pathname === "/"
|
|
385
|
+
) {
|
|
386
|
+
return "index";
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return pathname
|
|
390
|
+
.slice(1)
|
|
391
|
+
.split("/")
|
|
392
|
+
.map(
|
|
393
|
+
sanitizeSegment
|
|
394
|
+
)
|
|
395
|
+
.join("__");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function sanitizeSegment(
|
|
399
|
+
segment: string
|
|
400
|
+
): string {
|
|
401
|
+
const dynamicMatch =
|
|
402
|
+
/^\[([A-Za-z_][A-Za-z0-9_]*)\]$/.exec(
|
|
403
|
+
segment
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
if (dynamicMatch) {
|
|
407
|
+
return `param-${dynamicMatch[1]}`;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return segment.replace(
|
|
411
|
+
/[^A-Za-z0-9._-]/g,
|
|
412
|
+
"_"
|
|
413
|
+
);
|
|
414
|
+
}
|