@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,1991 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createRequire,
|
|
6
|
+
} from "node:module";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
build,
|
|
10
|
+
context,
|
|
11
|
+
type BuildContext,
|
|
12
|
+
type BuildResult,
|
|
13
|
+
type Plugin,
|
|
14
|
+
} from "esbuild";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
transformAsync,
|
|
18
|
+
} from "@babel/core";
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
createClientEnvironmentDefines,
|
|
22
|
+
} from "../../env/src/index.js";
|
|
23
|
+
|
|
24
|
+
import type {
|
|
25
|
+
Route,
|
|
26
|
+
} from "../../router/src/index.js";
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* =====================================
|
|
30
|
+
* CommonJS / Babel Helpers
|
|
31
|
+
* =====================================
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const require =
|
|
35
|
+
createRequire(
|
|
36
|
+
import.meta.url
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
function unwrapModule<T>(
|
|
40
|
+
value: any
|
|
41
|
+
): T {
|
|
42
|
+
return (
|
|
43
|
+
value?.default ??
|
|
44
|
+
value
|
|
45
|
+
) as T;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const reactRefreshBabel =
|
|
49
|
+
unwrapModule<any>(
|
|
50
|
+
require(
|
|
51
|
+
"react-refresh/babel"
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const presetReact =
|
|
56
|
+
unwrapModule<any>(
|
|
57
|
+
require(
|
|
58
|
+
"@babel/preset-react"
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const presetTypeScript =
|
|
63
|
+
unwrapModule<any>(
|
|
64
|
+
require(
|
|
65
|
+
"@babel/preset-typescript"
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* =====================================
|
|
71
|
+
* Types
|
|
72
|
+
* =====================================
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
export interface ClientBundle {
|
|
76
|
+
pathname: string;
|
|
77
|
+
|
|
78
|
+
publicPath: string;
|
|
79
|
+
|
|
80
|
+
filePath: string;
|
|
81
|
+
|
|
82
|
+
content: Buffer;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface DevAsset {
|
|
86
|
+
publicPath: string;
|
|
87
|
+
|
|
88
|
+
filePath: string;
|
|
89
|
+
|
|
90
|
+
content: Buffer;
|
|
91
|
+
|
|
92
|
+
contentType: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ClientBuildOptions {
|
|
96
|
+
fastRefresh?: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface RouteBuildState {
|
|
100
|
+
route: Route;
|
|
101
|
+
|
|
102
|
+
context: BuildContext;
|
|
103
|
+
|
|
104
|
+
outputFile: string;
|
|
105
|
+
|
|
106
|
+
bundle: ClientBundle;
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* Dependency files ของ route นี้
|
|
110
|
+
*/
|
|
111
|
+
inputFiles: Set<string>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface RouteBuildSnapshot {
|
|
115
|
+
bundle: ClientBundle;
|
|
116
|
+
|
|
117
|
+
inputFiles: Set<string>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/*
|
|
121
|
+
* =====================================
|
|
122
|
+
* Public Paths
|
|
123
|
+
* =====================================
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
const VENDOR_PATH =
|
|
127
|
+
"/_bcp/vendor";
|
|
128
|
+
|
|
129
|
+
export const DEV_RUNTIME_PATH =
|
|
130
|
+
"/_bcp/dev-runtime.js";
|
|
131
|
+
|
|
132
|
+
/*
|
|
133
|
+
* =====================================
|
|
134
|
+
* Vendor Proxy
|
|
135
|
+
* =====================================
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
function createVendorProxySource(
|
|
139
|
+
packageName: string
|
|
140
|
+
): string {
|
|
141
|
+
const packageModule =
|
|
142
|
+
require(
|
|
143
|
+
packageName
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const exportNames =
|
|
147
|
+
Object.keys(
|
|
148
|
+
packageModule
|
|
149
|
+
)
|
|
150
|
+
.filter(
|
|
151
|
+
(name) =>
|
|
152
|
+
name !==
|
|
153
|
+
"default" &&
|
|
154
|
+
/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(
|
|
155
|
+
name
|
|
156
|
+
)
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const namedExports =
|
|
160
|
+
exportNames
|
|
161
|
+
.map(
|
|
162
|
+
(name) => `
|
|
163
|
+
export const ${name} =
|
|
164
|
+
__bcpModule[${JSON.stringify(name)}];
|
|
165
|
+
`.trim()
|
|
166
|
+
)
|
|
167
|
+
.join("\n\n");
|
|
168
|
+
|
|
169
|
+
return `
|
|
170
|
+
import __bcpModule from ${JSON.stringify(packageName)};
|
|
171
|
+
|
|
172
|
+
${namedExports}
|
|
173
|
+
|
|
174
|
+
export default __bcpModule;
|
|
175
|
+
`.trim();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/*
|
|
179
|
+
* =====================================
|
|
180
|
+
* React Vendor + Refresh Runtime
|
|
181
|
+
* =====================================
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
export async function buildDevAssets(
|
|
185
|
+
rootDirectory: string
|
|
186
|
+
): Promise<DevAsset[]> {
|
|
187
|
+
const frameworkDirectory =
|
|
188
|
+
path.join(
|
|
189
|
+
rootDirectory,
|
|
190
|
+
".bcp-framework"
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const vendorSourceDirectory =
|
|
194
|
+
path.join(
|
|
195
|
+
frameworkDirectory,
|
|
196
|
+
"vendor-src"
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const vendorOutputDirectory =
|
|
200
|
+
path.join(
|
|
201
|
+
frameworkDirectory,
|
|
202
|
+
"vendor"
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const runtimeDirectory =
|
|
206
|
+
path.join(
|
|
207
|
+
frameworkDirectory,
|
|
208
|
+
"runtime"
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
fs.rmSync(
|
|
212
|
+
vendorSourceDirectory,
|
|
213
|
+
{
|
|
214
|
+
recursive: true,
|
|
215
|
+
force: true,
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
fs.rmSync(
|
|
220
|
+
vendorOutputDirectory,
|
|
221
|
+
{
|
|
222
|
+
recursive: true,
|
|
223
|
+
force: true,
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
fs.rmSync(
|
|
228
|
+
runtimeDirectory,
|
|
229
|
+
{
|
|
230
|
+
recursive: true,
|
|
231
|
+
force: true,
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
fs.mkdirSync(
|
|
236
|
+
vendorSourceDirectory,
|
|
237
|
+
{
|
|
238
|
+
recursive: true,
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
fs.mkdirSync(
|
|
243
|
+
vendorOutputDirectory,
|
|
244
|
+
{
|
|
245
|
+
recursive: true,
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
fs.mkdirSync(
|
|
250
|
+
runtimeDirectory,
|
|
251
|
+
{
|
|
252
|
+
recursive: true,
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
/*
|
|
257
|
+
* =================================
|
|
258
|
+
* Vendor Entry Files
|
|
259
|
+
* =================================
|
|
260
|
+
*/
|
|
261
|
+
|
|
262
|
+
const reactEntry =
|
|
263
|
+
path.join(
|
|
264
|
+
vendorSourceDirectory,
|
|
265
|
+
"react.ts"
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const jsxRuntimeEntry =
|
|
269
|
+
path.join(
|
|
270
|
+
vendorSourceDirectory,
|
|
271
|
+
"react-jsx-runtime.ts"
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const jsxDevRuntimeEntry =
|
|
275
|
+
path.join(
|
|
276
|
+
vendorSourceDirectory,
|
|
277
|
+
"react-jsx-dev-runtime.ts"
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const reactDomEntry =
|
|
281
|
+
path.join(
|
|
282
|
+
vendorSourceDirectory,
|
|
283
|
+
"react-dom.ts"
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const reactDomClientEntry =
|
|
287
|
+
path.join(
|
|
288
|
+
vendorSourceDirectory,
|
|
289
|
+
"react-dom-client.ts"
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
fs.writeFileSync(
|
|
293
|
+
reactEntry,
|
|
294
|
+
createVendorProxySource(
|
|
295
|
+
"react"
|
|
296
|
+
)
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
fs.writeFileSync(
|
|
300
|
+
jsxRuntimeEntry,
|
|
301
|
+
createVendorProxySource(
|
|
302
|
+
"react/jsx-runtime"
|
|
303
|
+
)
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
fs.writeFileSync(
|
|
307
|
+
jsxDevRuntimeEntry,
|
|
308
|
+
createVendorProxySource(
|
|
309
|
+
"react/jsx-dev-runtime"
|
|
310
|
+
)
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
fs.writeFileSync(
|
|
314
|
+
reactDomEntry,
|
|
315
|
+
createVendorProxySource(
|
|
316
|
+
"react-dom"
|
|
317
|
+
)
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
fs.writeFileSync(
|
|
321
|
+
reactDomClientEntry,
|
|
322
|
+
createVendorProxySource(
|
|
323
|
+
"react-dom/client"
|
|
324
|
+
)
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
/*
|
|
328
|
+
* Build shared React vendor
|
|
329
|
+
*/
|
|
330
|
+
await build({
|
|
331
|
+
entryPoints: {
|
|
332
|
+
react:
|
|
333
|
+
reactEntry,
|
|
334
|
+
|
|
335
|
+
"react-jsx-runtime":
|
|
336
|
+
jsxRuntimeEntry,
|
|
337
|
+
|
|
338
|
+
"react-jsx-dev-runtime":
|
|
339
|
+
jsxDevRuntimeEntry,
|
|
340
|
+
|
|
341
|
+
"react-dom":
|
|
342
|
+
reactDomEntry,
|
|
343
|
+
|
|
344
|
+
"react-dom-client":
|
|
345
|
+
reactDomClientEntry,
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
outdir:
|
|
349
|
+
vendorOutputDirectory,
|
|
350
|
+
|
|
351
|
+
bundle:
|
|
352
|
+
true,
|
|
353
|
+
|
|
354
|
+
splitting:
|
|
355
|
+
true,
|
|
356
|
+
|
|
357
|
+
format:
|
|
358
|
+
"esm",
|
|
359
|
+
|
|
360
|
+
platform:
|
|
361
|
+
"browser",
|
|
362
|
+
|
|
363
|
+
target: [
|
|
364
|
+
"es2022",
|
|
365
|
+
],
|
|
366
|
+
|
|
367
|
+
entryNames:
|
|
368
|
+
"[name]",
|
|
369
|
+
|
|
370
|
+
chunkNames:
|
|
371
|
+
"chunks/[name]-[hash]",
|
|
372
|
+
|
|
373
|
+
define: {
|
|
374
|
+
"process.env.NODE_ENV":
|
|
375
|
+
'"development"',
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
sourcemap:
|
|
379
|
+
"inline",
|
|
380
|
+
|
|
381
|
+
logLevel:
|
|
382
|
+
"silent",
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
/*
|
|
386
|
+
* =================================
|
|
387
|
+
* HMR Runtime
|
|
388
|
+
* =================================
|
|
389
|
+
*/
|
|
390
|
+
|
|
391
|
+
const runtimeOutput =
|
|
392
|
+
path.join(
|
|
393
|
+
runtimeDirectory,
|
|
394
|
+
"dev-runtime.js"
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const runtimeSource = `
|
|
398
|
+
import RefreshRuntime from "react-refresh/runtime";
|
|
399
|
+
|
|
400
|
+
/*
|
|
401
|
+
* Inject React Refresh
|
|
402
|
+
*/
|
|
403
|
+
if (!globalThis.__BCP_REFRESH_RUNTIME__) {
|
|
404
|
+
RefreshRuntime.injectIntoGlobalHook(
|
|
405
|
+
window
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
globalThis.__BCP_REFRESH_RUNTIME__ =
|
|
409
|
+
RefreshRuntime;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const config =
|
|
413
|
+
globalThis.__BCP_DEV_CONFIG__;
|
|
414
|
+
|
|
415
|
+
if (!config) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
"BCP Framework: dev configuration was not found."
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/*
|
|
422
|
+
* EventSource เปิดแค่ครั้งเดียว
|
|
423
|
+
*/
|
|
424
|
+
if (!globalThis.__BCP_HMR_SOURCE__) {
|
|
425
|
+
const source =
|
|
426
|
+
new EventSource(
|
|
427
|
+
config.eventsPath
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
globalThis.__BCP_HMR_SOURCE__ =
|
|
431
|
+
source;
|
|
432
|
+
|
|
433
|
+
/*
|
|
434
|
+
* =================================
|
|
435
|
+
* Fast Refresh
|
|
436
|
+
* =================================
|
|
437
|
+
*/
|
|
438
|
+
|
|
439
|
+
source.addEventListener(
|
|
440
|
+
"refresh",
|
|
441
|
+
async (event) => {
|
|
442
|
+
const data =
|
|
443
|
+
JSON.parse(
|
|
444
|
+
event.data
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
/*
|
|
448
|
+
* สำคัญ:
|
|
449
|
+
*
|
|
450
|
+
* Server ส่ง affected routes มา
|
|
451
|
+
*
|
|
452
|
+
* ถ้า route ปัจจุบันไม่ได้รับผลกระทบ
|
|
453
|
+
* ไม่ต้อง import bundle ใหม่
|
|
454
|
+
*/
|
|
455
|
+
if (
|
|
456
|
+
Array.isArray(
|
|
457
|
+
data.routes
|
|
458
|
+
) &&
|
|
459
|
+
!data.routes.includes(
|
|
460
|
+
config.route
|
|
461
|
+
)
|
|
462
|
+
) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const separator =
|
|
467
|
+
config.clientScript.includes("?")
|
|
468
|
+
? "&"
|
|
469
|
+
: "?";
|
|
470
|
+
|
|
471
|
+
const moduleUrl =
|
|
472
|
+
config.clientScript +
|
|
473
|
+
separator +
|
|
474
|
+
"v=" +
|
|
475
|
+
encodeURIComponent(
|
|
476
|
+
data.version
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
await import(
|
|
481
|
+
moduleUrl
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
RefreshRuntime
|
|
485
|
+
.performReactRefresh();
|
|
486
|
+
|
|
487
|
+
console.log(
|
|
488
|
+
"[BCP] Fast Refresh applied:",
|
|
489
|
+
config.route
|
|
490
|
+
);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
console.error(
|
|
493
|
+
"[BCP] Fast Refresh failed:",
|
|
494
|
+
error
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
window.location.reload();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
/*
|
|
503
|
+
* =================================
|
|
504
|
+
* Full Reload
|
|
505
|
+
* =================================
|
|
506
|
+
*/
|
|
507
|
+
|
|
508
|
+
source.addEventListener(
|
|
509
|
+
"reload",
|
|
510
|
+
() => {
|
|
511
|
+
console.log(
|
|
512
|
+
"[BCP] Full reload"
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
window.location.reload();
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
`.trim();
|
|
520
|
+
|
|
521
|
+
await build({
|
|
522
|
+
stdin: {
|
|
523
|
+
contents:
|
|
524
|
+
runtimeSource,
|
|
525
|
+
|
|
526
|
+
resolveDir:
|
|
527
|
+
rootDirectory,
|
|
528
|
+
|
|
529
|
+
sourcefile:
|
|
530
|
+
"bcp-dev-runtime.js",
|
|
531
|
+
|
|
532
|
+
loader:
|
|
533
|
+
"js",
|
|
534
|
+
},
|
|
535
|
+
|
|
536
|
+
outfile:
|
|
537
|
+
runtimeOutput,
|
|
538
|
+
|
|
539
|
+
bundle:
|
|
540
|
+
true,
|
|
541
|
+
|
|
542
|
+
format:
|
|
543
|
+
"iife",
|
|
544
|
+
|
|
545
|
+
platform:
|
|
546
|
+
"browser",
|
|
547
|
+
|
|
548
|
+
target: [
|
|
549
|
+
"es2022",
|
|
550
|
+
],
|
|
551
|
+
|
|
552
|
+
define: {
|
|
553
|
+
"process.env.NODE_ENV":
|
|
554
|
+
'"development"',
|
|
555
|
+
},
|
|
556
|
+
|
|
557
|
+
sourcemap:
|
|
558
|
+
"inline",
|
|
559
|
+
|
|
560
|
+
logLevel:
|
|
561
|
+
"silent",
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
/*
|
|
565
|
+
* =================================
|
|
566
|
+
* Assets → RAM
|
|
567
|
+
* =================================
|
|
568
|
+
*/
|
|
569
|
+
|
|
570
|
+
const assets:
|
|
571
|
+
DevAsset[] = [];
|
|
572
|
+
|
|
573
|
+
collectFiles(
|
|
574
|
+
vendorOutputDirectory,
|
|
575
|
+
(filePath) => {
|
|
576
|
+
const relative =
|
|
577
|
+
path.relative(
|
|
578
|
+
vendorOutputDirectory,
|
|
579
|
+
filePath
|
|
580
|
+
)
|
|
581
|
+
.replace(
|
|
582
|
+
/\\/g,
|
|
583
|
+
"/"
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
assets.push({
|
|
587
|
+
publicPath:
|
|
588
|
+
`${VENDOR_PATH}/${relative}`,
|
|
589
|
+
|
|
590
|
+
filePath,
|
|
591
|
+
|
|
592
|
+
content:
|
|
593
|
+
fs.readFileSync(
|
|
594
|
+
filePath
|
|
595
|
+
),
|
|
596
|
+
|
|
597
|
+
contentType:
|
|
598
|
+
"text/javascript; charset=utf-8",
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
assets.push({
|
|
604
|
+
publicPath:
|
|
605
|
+
DEV_RUNTIME_PATH,
|
|
606
|
+
|
|
607
|
+
filePath:
|
|
608
|
+
runtimeOutput,
|
|
609
|
+
|
|
610
|
+
content:
|
|
611
|
+
fs.readFileSync(
|
|
612
|
+
runtimeOutput
|
|
613
|
+
),
|
|
614
|
+
|
|
615
|
+
contentType:
|
|
616
|
+
"text/javascript; charset=utf-8",
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
return assets;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/*
|
|
623
|
+
* =====================================
|
|
624
|
+
* Incremental Client Bundler
|
|
625
|
+
* =====================================
|
|
626
|
+
*/
|
|
627
|
+
|
|
628
|
+
export class DevClientBundler {
|
|
629
|
+
private readonly states =
|
|
630
|
+
new Map<
|
|
631
|
+
string,
|
|
632
|
+
RouteBuildState
|
|
633
|
+
>();
|
|
634
|
+
|
|
635
|
+
/*
|
|
636
|
+
* Dependency graph:
|
|
637
|
+
*
|
|
638
|
+
* filePath
|
|
639
|
+
*
|
|
640
|
+
* ↓
|
|
641
|
+
*
|
|
642
|
+
* Set<Route Pathname>
|
|
643
|
+
*/
|
|
644
|
+
private readonly moduleToRoutes =
|
|
645
|
+
new Map<
|
|
646
|
+
string,
|
|
647
|
+
Set<string>
|
|
648
|
+
>();
|
|
649
|
+
|
|
650
|
+
private constructor(
|
|
651
|
+
private readonly rootDirectory:
|
|
652
|
+
string,
|
|
653
|
+
|
|
654
|
+
private readonly options:
|
|
655
|
+
ClientBuildOptions
|
|
656
|
+
) { }
|
|
657
|
+
|
|
658
|
+
/*
|
|
659
|
+
* =================================
|
|
660
|
+
* Create
|
|
661
|
+
* =================================
|
|
662
|
+
*/
|
|
663
|
+
|
|
664
|
+
static async create(
|
|
665
|
+
routes: Route[],
|
|
666
|
+
rootDirectory: string,
|
|
667
|
+
options:
|
|
668
|
+
ClientBuildOptions = {}
|
|
669
|
+
): Promise<DevClientBundler> {
|
|
670
|
+
const bundler =
|
|
671
|
+
new DevClientBundler(
|
|
672
|
+
rootDirectory,
|
|
673
|
+
options
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
try {
|
|
677
|
+
await bundler.initialize(
|
|
678
|
+
routes
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
return bundler;
|
|
682
|
+
} catch (error) {
|
|
683
|
+
await bundler.dispose();
|
|
684
|
+
|
|
685
|
+
throw error;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/*
|
|
690
|
+
* =================================
|
|
691
|
+
* Initial Build
|
|
692
|
+
* =================================
|
|
693
|
+
*/
|
|
694
|
+
|
|
695
|
+
private async initialize(
|
|
696
|
+
routes: Route[]
|
|
697
|
+
) {
|
|
698
|
+
const outputDirectory =
|
|
699
|
+
path.join(
|
|
700
|
+
this.rootDirectory,
|
|
701
|
+
".bcp-framework",
|
|
702
|
+
"client"
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
fs.rmSync(
|
|
706
|
+
outputDirectory,
|
|
707
|
+
{
|
|
708
|
+
recursive: true,
|
|
709
|
+
force: true,
|
|
710
|
+
}
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
fs.mkdirSync(
|
|
714
|
+
outputDirectory,
|
|
715
|
+
{
|
|
716
|
+
recursive: true,
|
|
717
|
+
}
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
/*
|
|
721
|
+
* สร้าง esbuild Context
|
|
722
|
+
* ต่อ Route
|
|
723
|
+
*/
|
|
724
|
+
for (
|
|
725
|
+
const route
|
|
726
|
+
of routes
|
|
727
|
+
) {
|
|
728
|
+
const state =
|
|
729
|
+
await this.createRouteState(
|
|
730
|
+
route,
|
|
731
|
+
outputDirectory
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
this.states.set(
|
|
735
|
+
route.pathname,
|
|
736
|
+
state
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/*
|
|
741
|
+
* Initial build
|
|
742
|
+
*
|
|
743
|
+
* build ทุก route ครั้งแรก
|
|
744
|
+
*/
|
|
745
|
+
const states =
|
|
746
|
+
Array.from(
|
|
747
|
+
this.states.values()
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
const snapshots =
|
|
751
|
+
await Promise.all(
|
|
752
|
+
states.map(
|
|
753
|
+
(state) =>
|
|
754
|
+
this.buildState(
|
|
755
|
+
state
|
|
756
|
+
)
|
|
757
|
+
)
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
/*
|
|
761
|
+
* ทุก route build ผ่านแล้ว
|
|
762
|
+
* จึงค่อย commit state
|
|
763
|
+
*/
|
|
764
|
+
for (
|
|
765
|
+
let index = 0;
|
|
766
|
+
index <
|
|
767
|
+
states.length;
|
|
768
|
+
index++
|
|
769
|
+
) {
|
|
770
|
+
this.commitSnapshot(
|
|
771
|
+
states[index],
|
|
772
|
+
snapshots[index]
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
this.rebuildModuleGraph();
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/*
|
|
780
|
+
* =================================
|
|
781
|
+
* Create Route Context
|
|
782
|
+
* =================================
|
|
783
|
+
*/
|
|
784
|
+
|
|
785
|
+
private async createRouteState(
|
|
786
|
+
route: Route,
|
|
787
|
+
outputDirectory: string
|
|
788
|
+
): Promise<RouteBuildState> {
|
|
789
|
+
const bundleName =
|
|
790
|
+
routeToBundleName(
|
|
791
|
+
route.pathname
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
const outputFile =
|
|
795
|
+
path.join(
|
|
796
|
+
outputDirectory,
|
|
797
|
+
bundleName
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
const clientEntry =
|
|
801
|
+
createClientEntry(
|
|
802
|
+
route,
|
|
803
|
+
this.rootDirectory
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
const plugins:
|
|
807
|
+
Plugin[] = [];
|
|
808
|
+
|
|
809
|
+
if (
|
|
810
|
+
this.options.fastRefresh
|
|
811
|
+
) {
|
|
812
|
+
plugins.push(
|
|
813
|
+
createReactVendorPlugin()
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
plugins.push(
|
|
817
|
+
createReactRefreshPlugin(
|
|
818
|
+
this.rootDirectory
|
|
819
|
+
)
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/*
|
|
824
|
+
* esbuild context()
|
|
825
|
+
*
|
|
826
|
+
* Context นี้จะถูกเก็บไว้
|
|
827
|
+
* ตลอด Dev Server lifetime
|
|
828
|
+
*/
|
|
829
|
+
const buildContext =
|
|
830
|
+
await context({
|
|
831
|
+
absWorkingDir:
|
|
832
|
+
this.rootDirectory,
|
|
833
|
+
|
|
834
|
+
stdin: {
|
|
835
|
+
contents:
|
|
836
|
+
clientEntry,
|
|
837
|
+
|
|
838
|
+
resolveDir:
|
|
839
|
+
this.rootDirectory,
|
|
840
|
+
|
|
841
|
+
sourcefile:
|
|
842
|
+
`bcp-entry-${sanitizeBundleId(route.pathname)}.js`,
|
|
843
|
+
|
|
844
|
+
loader:
|
|
845
|
+
"js",
|
|
846
|
+
},
|
|
847
|
+
|
|
848
|
+
outfile:
|
|
849
|
+
outputFile,
|
|
850
|
+
|
|
851
|
+
bundle:
|
|
852
|
+
true,
|
|
853
|
+
|
|
854
|
+
/*
|
|
855
|
+
* สำคัญ:
|
|
856
|
+
*
|
|
857
|
+
* ไม่ให้ esbuild เขียนลง disk
|
|
858
|
+
* ทุก rebuild
|
|
859
|
+
*
|
|
860
|
+
* เราจะอ่าน outputFiles
|
|
861
|
+
* เข้า RAM โดยตรง
|
|
862
|
+
*/
|
|
863
|
+
write:
|
|
864
|
+
false,
|
|
865
|
+
|
|
866
|
+
metafile:
|
|
867
|
+
true,
|
|
868
|
+
|
|
869
|
+
platform:
|
|
870
|
+
"browser",
|
|
871
|
+
|
|
872
|
+
format:
|
|
873
|
+
"esm",
|
|
874
|
+
|
|
875
|
+
target: [
|
|
876
|
+
"es2022",
|
|
877
|
+
],
|
|
878
|
+
|
|
879
|
+
plugins,
|
|
880
|
+
|
|
881
|
+
jsx:
|
|
882
|
+
"automatic",
|
|
883
|
+
|
|
884
|
+
define:
|
|
885
|
+
createClientEnvironmentDefines(
|
|
886
|
+
"development"
|
|
887
|
+
),
|
|
888
|
+
|
|
889
|
+
sourcemap:
|
|
890
|
+
"inline",
|
|
891
|
+
|
|
892
|
+
logLevel:
|
|
893
|
+
"silent",
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
const publicPath =
|
|
897
|
+
`/_bcp/client/${bundleName}`;
|
|
898
|
+
|
|
899
|
+
return {
|
|
900
|
+
route,
|
|
901
|
+
|
|
902
|
+
context:
|
|
903
|
+
buildContext,
|
|
904
|
+
|
|
905
|
+
outputFile,
|
|
906
|
+
|
|
907
|
+
/*
|
|
908
|
+
* Placeholder
|
|
909
|
+
* จะถูกแทนหลัง initial rebuild
|
|
910
|
+
*/
|
|
911
|
+
bundle: {
|
|
912
|
+
pathname:
|
|
913
|
+
route.pathname,
|
|
914
|
+
|
|
915
|
+
publicPath,
|
|
916
|
+
|
|
917
|
+
filePath:
|
|
918
|
+
outputFile,
|
|
919
|
+
|
|
920
|
+
content:
|
|
921
|
+
Buffer.alloc(0),
|
|
922
|
+
},
|
|
923
|
+
|
|
924
|
+
inputFiles:
|
|
925
|
+
new Set(),
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/*
|
|
930
|
+
* =================================
|
|
931
|
+
* Incremental Rebuild
|
|
932
|
+
* =================================
|
|
933
|
+
*/
|
|
934
|
+
|
|
935
|
+
async rebuildAffected(
|
|
936
|
+
changedFiles: string[]
|
|
937
|
+
): Promise<string[]> {
|
|
938
|
+
const affected =
|
|
939
|
+
this.findAffectedRoutes(
|
|
940
|
+
changedFiles
|
|
941
|
+
);
|
|
942
|
+
|
|
943
|
+
if (
|
|
944
|
+
affected.length ===
|
|
945
|
+
0
|
|
946
|
+
) {
|
|
947
|
+
return [];
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const states =
|
|
951
|
+
affected
|
|
952
|
+
.map(
|
|
953
|
+
(pathname) =>
|
|
954
|
+
this.states.get(
|
|
955
|
+
pathname
|
|
956
|
+
)
|
|
957
|
+
)
|
|
958
|
+
.filter(
|
|
959
|
+
(
|
|
960
|
+
state
|
|
961
|
+
): state is
|
|
962
|
+
RouteBuildState =>
|
|
963
|
+
Boolean(
|
|
964
|
+
state
|
|
965
|
+
)
|
|
966
|
+
);
|
|
967
|
+
|
|
968
|
+
/*
|
|
969
|
+
* context.rebuild()
|
|
970
|
+
*
|
|
971
|
+
* esbuild จะ reuse cache
|
|
972
|
+
* ของ context เดิม
|
|
973
|
+
*/
|
|
974
|
+
const snapshots =
|
|
975
|
+
await Promise.all(
|
|
976
|
+
states.map(
|
|
977
|
+
(state) =>
|
|
978
|
+
this.buildState(
|
|
979
|
+
state
|
|
980
|
+
)
|
|
981
|
+
)
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
/*
|
|
985
|
+
* Atomic-ish commit:
|
|
986
|
+
*
|
|
987
|
+
* ถ้า Promise.all fail
|
|
988
|
+
* จะยังไม่ commit bundle ใหม่
|
|
989
|
+
*/
|
|
990
|
+
for (
|
|
991
|
+
let index = 0;
|
|
992
|
+
index <
|
|
993
|
+
states.length;
|
|
994
|
+
index++
|
|
995
|
+
) {
|
|
996
|
+
this.commitSnapshot(
|
|
997
|
+
states[index],
|
|
998
|
+
snapshots[index]
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/*
|
|
1003
|
+
* Dependency อาจเปลี่ยนหลัง build
|
|
1004
|
+
*
|
|
1005
|
+
* เช่น:
|
|
1006
|
+
*
|
|
1007
|
+
* Page
|
|
1008
|
+
* เดิม import A
|
|
1009
|
+
*
|
|
1010
|
+
* เปลี่ยนเป็น
|
|
1011
|
+
*
|
|
1012
|
+
* Page
|
|
1013
|
+
* import A
|
|
1014
|
+
* import B
|
|
1015
|
+
*
|
|
1016
|
+
* จึง rebuild graph ใหม่
|
|
1017
|
+
*/
|
|
1018
|
+
this.rebuildModuleGraph();
|
|
1019
|
+
|
|
1020
|
+
return affected;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/*
|
|
1024
|
+
* =================================
|
|
1025
|
+
* Build One Route
|
|
1026
|
+
* =================================
|
|
1027
|
+
*/
|
|
1028
|
+
|
|
1029
|
+
private async buildState(
|
|
1030
|
+
state: RouteBuildState
|
|
1031
|
+
): Promise<RouteBuildSnapshot> {
|
|
1032
|
+
const result =
|
|
1033
|
+
await state.context
|
|
1034
|
+
.rebuild();
|
|
1035
|
+
|
|
1036
|
+
const output =
|
|
1037
|
+
findJavaScriptOutput(
|
|
1038
|
+
result,
|
|
1039
|
+
state.outputFile
|
|
1040
|
+
);
|
|
1041
|
+
|
|
1042
|
+
if (!output) {
|
|
1043
|
+
throw new Error(
|
|
1044
|
+
`BCP Framework: output bundle was not found for route "${state.route.pathname}".`
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
const inputFiles =
|
|
1049
|
+
collectInputFiles(
|
|
1050
|
+
result,
|
|
1051
|
+
this.rootDirectory
|
|
1052
|
+
);
|
|
1053
|
+
|
|
1054
|
+
/*
|
|
1055
|
+
* Page/Layout ต้องอยู่ graph
|
|
1056
|
+
* เสมอ แม้ metafile มี behavior
|
|
1057
|
+
* แตกต่างจาก plugin
|
|
1058
|
+
*/
|
|
1059
|
+
inputFiles.add(
|
|
1060
|
+
normalizeGraphPath(
|
|
1061
|
+
state.route.filePath
|
|
1062
|
+
)
|
|
1063
|
+
);
|
|
1064
|
+
|
|
1065
|
+
for (
|
|
1066
|
+
const layoutFile
|
|
1067
|
+
of state.route.layouts
|
|
1068
|
+
) {
|
|
1069
|
+
inputFiles.add(
|
|
1070
|
+
normalizeGraphPath(
|
|
1071
|
+
layoutFile
|
|
1072
|
+
)
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
return {
|
|
1077
|
+
bundle: {
|
|
1078
|
+
pathname:
|
|
1079
|
+
state.route.pathname,
|
|
1080
|
+
|
|
1081
|
+
publicPath:
|
|
1082
|
+
state.bundle.publicPath,
|
|
1083
|
+
|
|
1084
|
+
filePath:
|
|
1085
|
+
state.outputFile,
|
|
1086
|
+
|
|
1087
|
+
content:
|
|
1088
|
+
Buffer.from(
|
|
1089
|
+
output.contents
|
|
1090
|
+
),
|
|
1091
|
+
},
|
|
1092
|
+
|
|
1093
|
+
inputFiles,
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/*
|
|
1098
|
+
* =================================
|
|
1099
|
+
* Commit Route Build
|
|
1100
|
+
* =================================
|
|
1101
|
+
*/
|
|
1102
|
+
|
|
1103
|
+
private commitSnapshot(
|
|
1104
|
+
state: RouteBuildState,
|
|
1105
|
+
snapshot: RouteBuildSnapshot
|
|
1106
|
+
) {
|
|
1107
|
+
state.bundle =
|
|
1108
|
+
snapshot.bundle;
|
|
1109
|
+
|
|
1110
|
+
state.inputFiles =
|
|
1111
|
+
snapshot.inputFiles;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/*
|
|
1115
|
+
* =================================
|
|
1116
|
+
* Module Graph
|
|
1117
|
+
* =================================
|
|
1118
|
+
*/
|
|
1119
|
+
|
|
1120
|
+
private rebuildModuleGraph() {
|
|
1121
|
+
this.moduleToRoutes
|
|
1122
|
+
.clear();
|
|
1123
|
+
|
|
1124
|
+
for (
|
|
1125
|
+
const state
|
|
1126
|
+
of this.states.values()
|
|
1127
|
+
) {
|
|
1128
|
+
for (
|
|
1129
|
+
const filePath
|
|
1130
|
+
of state.inputFiles
|
|
1131
|
+
) {
|
|
1132
|
+
let routeSet =
|
|
1133
|
+
this.moduleToRoutes
|
|
1134
|
+
.get(
|
|
1135
|
+
filePath
|
|
1136
|
+
);
|
|
1137
|
+
|
|
1138
|
+
if (!routeSet) {
|
|
1139
|
+
routeSet =
|
|
1140
|
+
new Set<string>();
|
|
1141
|
+
|
|
1142
|
+
this.moduleToRoutes.set(
|
|
1143
|
+
filePath,
|
|
1144
|
+
routeSet
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
routeSet.add(
|
|
1149
|
+
state.route.pathname
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/*
|
|
1156
|
+
* =================================
|
|
1157
|
+
* Find Affected Routes
|
|
1158
|
+
* =================================
|
|
1159
|
+
*/
|
|
1160
|
+
|
|
1161
|
+
findAffectedRoutes(
|
|
1162
|
+
changedFiles: string[]
|
|
1163
|
+
): string[] {
|
|
1164
|
+
const affected =
|
|
1165
|
+
new Set<string>();
|
|
1166
|
+
|
|
1167
|
+
for (
|
|
1168
|
+
const filePath
|
|
1169
|
+
of changedFiles
|
|
1170
|
+
) {
|
|
1171
|
+
const normalized =
|
|
1172
|
+
normalizeGraphPath(
|
|
1173
|
+
filePath
|
|
1174
|
+
);
|
|
1175
|
+
|
|
1176
|
+
const routes =
|
|
1177
|
+
this.moduleToRoutes
|
|
1178
|
+
.get(
|
|
1179
|
+
normalized
|
|
1180
|
+
);
|
|
1181
|
+
|
|
1182
|
+
if (!routes) {
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
for (
|
|
1187
|
+
const pathname
|
|
1188
|
+
of routes
|
|
1189
|
+
) {
|
|
1190
|
+
affected.add(
|
|
1191
|
+
pathname
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
return Array.from(
|
|
1197
|
+
affected
|
|
1198
|
+
).sort();
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/*
|
|
1202
|
+
* =================================
|
|
1203
|
+
* Debug Graph
|
|
1204
|
+
* =================================
|
|
1205
|
+
*/
|
|
1206
|
+
|
|
1207
|
+
printAffectedRoutes(
|
|
1208
|
+
filePath: string
|
|
1209
|
+
) {
|
|
1210
|
+
const affected =
|
|
1211
|
+
this.findAffectedRoutes([
|
|
1212
|
+
filePath,
|
|
1213
|
+
]);
|
|
1214
|
+
|
|
1215
|
+
console.log(
|
|
1216
|
+
`[module-graph] ${path.relative(
|
|
1217
|
+
this.rootDirectory,
|
|
1218
|
+
filePath
|
|
1219
|
+
)}`
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
if (
|
|
1223
|
+
affected.length ===
|
|
1224
|
+
0
|
|
1225
|
+
) {
|
|
1226
|
+
console.log(
|
|
1227
|
+
" → no client routes"
|
|
1228
|
+
);
|
|
1229
|
+
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
for (
|
|
1234
|
+
const pathname
|
|
1235
|
+
of affected
|
|
1236
|
+
) {
|
|
1237
|
+
console.log(
|
|
1238
|
+
` → ${pathname}`
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/*
|
|
1244
|
+
* =================================
|
|
1245
|
+
* Bundles
|
|
1246
|
+
* =================================
|
|
1247
|
+
*/
|
|
1248
|
+
|
|
1249
|
+
getBundles():
|
|
1250
|
+
ClientBundle[] {
|
|
1251
|
+
return Array.from(
|
|
1252
|
+
this.states.values()
|
|
1253
|
+
).map(
|
|
1254
|
+
(state) =>
|
|
1255
|
+
state.bundle
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/*
|
|
1260
|
+
* =================================
|
|
1261
|
+
* Dispose
|
|
1262
|
+
* =================================
|
|
1263
|
+
*/
|
|
1264
|
+
|
|
1265
|
+
async dispose() {
|
|
1266
|
+
const contexts =
|
|
1267
|
+
Array.from(
|
|
1268
|
+
this.states.values()
|
|
1269
|
+
).map(
|
|
1270
|
+
(state) =>
|
|
1271
|
+
state.context
|
|
1272
|
+
);
|
|
1273
|
+
|
|
1274
|
+
this.states.clear();
|
|
1275
|
+
|
|
1276
|
+
this.moduleToRoutes
|
|
1277
|
+
.clear();
|
|
1278
|
+
|
|
1279
|
+
await Promise.all(
|
|
1280
|
+
contexts.map(
|
|
1281
|
+
(buildContext) =>
|
|
1282
|
+
buildContext.dispose()
|
|
1283
|
+
)
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/*
|
|
1289
|
+
* =====================================
|
|
1290
|
+
* Client Entry Generator
|
|
1291
|
+
* =====================================
|
|
1292
|
+
*/
|
|
1293
|
+
|
|
1294
|
+
function createClientEntry(
|
|
1295
|
+
route: Route,
|
|
1296
|
+
rootDirectory: string
|
|
1297
|
+
): string {
|
|
1298
|
+
const pageImport =
|
|
1299
|
+
toImportSpecifier(
|
|
1300
|
+
rootDirectory,
|
|
1301
|
+
route.filePath
|
|
1302
|
+
);
|
|
1303
|
+
|
|
1304
|
+
const layoutImports =
|
|
1305
|
+
route.layouts
|
|
1306
|
+
.map(
|
|
1307
|
+
(
|
|
1308
|
+
layoutFile,
|
|
1309
|
+
index
|
|
1310
|
+
) => {
|
|
1311
|
+
const importPath =
|
|
1312
|
+
toImportSpecifier(
|
|
1313
|
+
rootDirectory,
|
|
1314
|
+
layoutFile
|
|
1315
|
+
);
|
|
1316
|
+
|
|
1317
|
+
return `
|
|
1318
|
+
import Layout${index} from ${JSON.stringify(importPath)};
|
|
1319
|
+
`.trim();
|
|
1320
|
+
}
|
|
1321
|
+
)
|
|
1322
|
+
.join("\n");
|
|
1323
|
+
|
|
1324
|
+
const layoutNames =
|
|
1325
|
+
route.layouts
|
|
1326
|
+
.map(
|
|
1327
|
+
(
|
|
1328
|
+
_,
|
|
1329
|
+
index
|
|
1330
|
+
) =>
|
|
1331
|
+
`Layout${index}`
|
|
1332
|
+
)
|
|
1333
|
+
.join(", ");
|
|
1334
|
+
|
|
1335
|
+
return `
|
|
1336
|
+
import {
|
|
1337
|
+
createElement
|
|
1338
|
+
} from "react";
|
|
1339
|
+
|
|
1340
|
+
import {
|
|
1341
|
+
hydrateRoot
|
|
1342
|
+
} from "react-dom/client";
|
|
1343
|
+
|
|
1344
|
+
import Page from ${JSON.stringify(pageImport)};
|
|
1345
|
+
|
|
1346
|
+
${layoutImports}
|
|
1347
|
+
|
|
1348
|
+
const rootElement =
|
|
1349
|
+
document.getElementById(
|
|
1350
|
+
"root"
|
|
1351
|
+
);
|
|
1352
|
+
|
|
1353
|
+
if (!rootElement) {
|
|
1354
|
+
throw new Error(
|
|
1355
|
+
"BCP Framework: root element was not found."
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
const dataElement =
|
|
1360
|
+
document.getElementById(
|
|
1361
|
+
"__BCP_DATA__"
|
|
1362
|
+
);
|
|
1363
|
+
|
|
1364
|
+
if (!dataElement) {
|
|
1365
|
+
throw new Error(
|
|
1366
|
+
"BCP Framework: framework data was not found."
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
const data =
|
|
1371
|
+
JSON.parse(
|
|
1372
|
+
dataElement.textContent ||
|
|
1373
|
+
"{}"
|
|
1374
|
+
);
|
|
1375
|
+
|
|
1376
|
+
const params =
|
|
1377
|
+
data.params || {};
|
|
1378
|
+
|
|
1379
|
+
const layouts = [
|
|
1380
|
+
${layoutNames}
|
|
1381
|
+
];
|
|
1382
|
+
|
|
1383
|
+
let tree =
|
|
1384
|
+
createElement(
|
|
1385
|
+
Page,
|
|
1386
|
+
{
|
|
1387
|
+
params
|
|
1388
|
+
}
|
|
1389
|
+
);
|
|
1390
|
+
|
|
1391
|
+
for (
|
|
1392
|
+
let index =
|
|
1393
|
+
layouts.length - 1;
|
|
1394
|
+
|
|
1395
|
+
index >= 0;
|
|
1396
|
+
|
|
1397
|
+
index--
|
|
1398
|
+
) {
|
|
1399
|
+
const Layout =
|
|
1400
|
+
layouts[index];
|
|
1401
|
+
|
|
1402
|
+
tree =
|
|
1403
|
+
createElement(
|
|
1404
|
+
Layout,
|
|
1405
|
+
{
|
|
1406
|
+
params
|
|
1407
|
+
},
|
|
1408
|
+
tree
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/*
|
|
1413
|
+
* Initial navigation:
|
|
1414
|
+
* hydrate the server-rendered HTML once.
|
|
1415
|
+
*
|
|
1416
|
+
* Client navigation / HMR:
|
|
1417
|
+
* render into the existing hydrated root.
|
|
1418
|
+
* React can then reconcile the tree and keep
|
|
1419
|
+
* state for layouts that remain at the same
|
|
1420
|
+
* position.
|
|
1421
|
+
*/
|
|
1422
|
+
if (
|
|
1423
|
+
globalThis.__BCP_REACT_ROOT__
|
|
1424
|
+
) {
|
|
1425
|
+
globalThis.__BCP_REACT_ROOT__
|
|
1426
|
+
.render(tree);
|
|
1427
|
+
} else {
|
|
1428
|
+
globalThis.__BCP_REACT_ROOT__ =
|
|
1429
|
+
hydrateRoot(
|
|
1430
|
+
rootElement,
|
|
1431
|
+
tree
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
`.trim();
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/*
|
|
1438
|
+
* =====================================
|
|
1439
|
+
* Build Result → Input Files
|
|
1440
|
+
* =====================================
|
|
1441
|
+
*/
|
|
1442
|
+
|
|
1443
|
+
function collectInputFiles(
|
|
1444
|
+
result: BuildResult,
|
|
1445
|
+
rootDirectory: string
|
|
1446
|
+
): Set<string> {
|
|
1447
|
+
const files =
|
|
1448
|
+
new Set<string>();
|
|
1449
|
+
|
|
1450
|
+
const metafile =
|
|
1451
|
+
result.metafile;
|
|
1452
|
+
|
|
1453
|
+
if (!metafile) {
|
|
1454
|
+
return files;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
for (
|
|
1458
|
+
const input
|
|
1459
|
+
of Object.keys(
|
|
1460
|
+
metafile.inputs
|
|
1461
|
+
)
|
|
1462
|
+
) {
|
|
1463
|
+
/*
|
|
1464
|
+
* Virtual entry
|
|
1465
|
+
*
|
|
1466
|
+
* เช่น:
|
|
1467
|
+
*
|
|
1468
|
+
* bcp-entry-index.js
|
|
1469
|
+
*
|
|
1470
|
+
* ไม่ใช่ application file
|
|
1471
|
+
*/
|
|
1472
|
+
if (
|
|
1473
|
+
input.startsWith(
|
|
1474
|
+
"<"
|
|
1475
|
+
) ||
|
|
1476
|
+
input.startsWith(
|
|
1477
|
+
"bcp-entry-"
|
|
1478
|
+
)
|
|
1479
|
+
) {
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
const absolute =
|
|
1484
|
+
path.isAbsolute(
|
|
1485
|
+
input
|
|
1486
|
+
)
|
|
1487
|
+
? input
|
|
1488
|
+
: path.resolve(
|
|
1489
|
+
rootDirectory,
|
|
1490
|
+
input
|
|
1491
|
+
);
|
|
1492
|
+
|
|
1493
|
+
files.add(
|
|
1494
|
+
normalizeGraphPath(
|
|
1495
|
+
absolute
|
|
1496
|
+
)
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
return files;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
/*
|
|
1504
|
+
* =====================================
|
|
1505
|
+
* Output Helper
|
|
1506
|
+
* =====================================
|
|
1507
|
+
*/
|
|
1508
|
+
|
|
1509
|
+
function findJavaScriptOutput(
|
|
1510
|
+
result: BuildResult,
|
|
1511
|
+
outputFile: string
|
|
1512
|
+
) {
|
|
1513
|
+
const outputs =
|
|
1514
|
+
result.outputFiles ??
|
|
1515
|
+
[];
|
|
1516
|
+
|
|
1517
|
+
const normalizedTarget =
|
|
1518
|
+
path.normalize(
|
|
1519
|
+
outputFile
|
|
1520
|
+
);
|
|
1521
|
+
|
|
1522
|
+
const exact =
|
|
1523
|
+
outputs.find(
|
|
1524
|
+
(file) =>
|
|
1525
|
+
path.normalize(
|
|
1526
|
+
file.path
|
|
1527
|
+
) ===
|
|
1528
|
+
normalizedTarget
|
|
1529
|
+
);
|
|
1530
|
+
|
|
1531
|
+
if (exact) {
|
|
1532
|
+
return exact;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
return outputs.find(
|
|
1536
|
+
(file) =>
|
|
1537
|
+
file.path.endsWith(
|
|
1538
|
+
".js"
|
|
1539
|
+
)
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
/*
|
|
1544
|
+
* =====================================
|
|
1545
|
+
* Graph Path
|
|
1546
|
+
* =====================================
|
|
1547
|
+
*/
|
|
1548
|
+
|
|
1549
|
+
function normalizeGraphPath(
|
|
1550
|
+
filePath: string
|
|
1551
|
+
): string {
|
|
1552
|
+
let normalized =
|
|
1553
|
+
path.normalize(
|
|
1554
|
+
path.resolve(
|
|
1555
|
+
filePath
|
|
1556
|
+
)
|
|
1557
|
+
);
|
|
1558
|
+
|
|
1559
|
+
/*
|
|
1560
|
+
* Windows file system
|
|
1561
|
+
* ปกติ case insensitive
|
|
1562
|
+
*
|
|
1563
|
+
* D:\\App\\Page.tsx
|
|
1564
|
+
* d:\\app\\page.tsx
|
|
1565
|
+
*
|
|
1566
|
+
* ให้ถือเป็นไฟล์เดียวกัน
|
|
1567
|
+
*/
|
|
1568
|
+
if (
|
|
1569
|
+
process.platform ===
|
|
1570
|
+
"win32"
|
|
1571
|
+
) {
|
|
1572
|
+
normalized =
|
|
1573
|
+
normalized.toLowerCase();
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
return normalized;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
/*
|
|
1580
|
+
* =====================================
|
|
1581
|
+
* React Vendor Plugin
|
|
1582
|
+
* =====================================
|
|
1583
|
+
*/
|
|
1584
|
+
|
|
1585
|
+
function createReactVendorPlugin():
|
|
1586
|
+
Plugin {
|
|
1587
|
+
const mappings =
|
|
1588
|
+
new Map<
|
|
1589
|
+
string,
|
|
1590
|
+
string
|
|
1591
|
+
>([
|
|
1592
|
+
[
|
|
1593
|
+
"react",
|
|
1594
|
+
`${VENDOR_PATH}/react.js`,
|
|
1595
|
+
],
|
|
1596
|
+
|
|
1597
|
+
[
|
|
1598
|
+
"react/jsx-runtime",
|
|
1599
|
+
`${VENDOR_PATH}/react-jsx-runtime.js`,
|
|
1600
|
+
],
|
|
1601
|
+
|
|
1602
|
+
[
|
|
1603
|
+
"react/jsx-dev-runtime",
|
|
1604
|
+
`${VENDOR_PATH}/react-jsx-dev-runtime.js`,
|
|
1605
|
+
],
|
|
1606
|
+
|
|
1607
|
+
[
|
|
1608
|
+
"react-dom",
|
|
1609
|
+
`${VENDOR_PATH}/react-dom.js`,
|
|
1610
|
+
],
|
|
1611
|
+
|
|
1612
|
+
[
|
|
1613
|
+
"react-dom/client",
|
|
1614
|
+
`${VENDOR_PATH}/react-dom-client.js`,
|
|
1615
|
+
],
|
|
1616
|
+
]);
|
|
1617
|
+
|
|
1618
|
+
return {
|
|
1619
|
+
name:
|
|
1620
|
+
"bcp-react-vendor",
|
|
1621
|
+
|
|
1622
|
+
setup(buildApi) {
|
|
1623
|
+
buildApi.onResolve(
|
|
1624
|
+
{
|
|
1625
|
+
filter:
|
|
1626
|
+
/^(react|react\/jsx-runtime|react\/jsx-dev-runtime|react-dom|react-dom\/client)$/,
|
|
1627
|
+
},
|
|
1628
|
+
|
|
1629
|
+
(args) => {
|
|
1630
|
+
const publicPath =
|
|
1631
|
+
mappings.get(
|
|
1632
|
+
args.path
|
|
1633
|
+
);
|
|
1634
|
+
|
|
1635
|
+
if (!publicPath) {
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
return {
|
|
1640
|
+
path:
|
|
1641
|
+
publicPath,
|
|
1642
|
+
|
|
1643
|
+
external:
|
|
1644
|
+
true,
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
);
|
|
1648
|
+
},
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
/*
|
|
1653
|
+
* =====================================
|
|
1654
|
+
* React Refresh Plugin
|
|
1655
|
+
* =====================================
|
|
1656
|
+
*/
|
|
1657
|
+
|
|
1658
|
+
function createReactRefreshPlugin(
|
|
1659
|
+
rootDirectory: string
|
|
1660
|
+
): Plugin {
|
|
1661
|
+
return {
|
|
1662
|
+
name:
|
|
1663
|
+
"bcp-react-refresh",
|
|
1664
|
+
|
|
1665
|
+
setup(buildApi) {
|
|
1666
|
+
buildApi.onLoad(
|
|
1667
|
+
{
|
|
1668
|
+
filter:
|
|
1669
|
+
/\.[jt]sx?$/,
|
|
1670
|
+
},
|
|
1671
|
+
|
|
1672
|
+
async (args) => {
|
|
1673
|
+
/*
|
|
1674
|
+
* node_modules
|
|
1675
|
+
*/
|
|
1676
|
+
if (
|
|
1677
|
+
args.path.includes(
|
|
1678
|
+
`${path.sep}node_modules${path.sep}`
|
|
1679
|
+
)
|
|
1680
|
+
) {
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
/*
|
|
1685
|
+
* generated framework files
|
|
1686
|
+
*/
|
|
1687
|
+
if (
|
|
1688
|
+
args.path.includes(
|
|
1689
|
+
`${path.sep}.bcp-framework${path.sep}`
|
|
1690
|
+
)
|
|
1691
|
+
) {
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
const relative =
|
|
1696
|
+
path.relative(
|
|
1697
|
+
rootDirectory,
|
|
1698
|
+
args.path
|
|
1699
|
+
);
|
|
1700
|
+
|
|
1701
|
+
/*
|
|
1702
|
+
* ไม่ transform file
|
|
1703
|
+
* นอก application
|
|
1704
|
+
*/
|
|
1705
|
+
if (
|
|
1706
|
+
relative.startsWith(
|
|
1707
|
+
".."
|
|
1708
|
+
)
|
|
1709
|
+
) {
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
const source =
|
|
1714
|
+
await fs.promises
|
|
1715
|
+
.readFile(
|
|
1716
|
+
args.path,
|
|
1717
|
+
"utf8"
|
|
1718
|
+
);
|
|
1719
|
+
|
|
1720
|
+
const extension =
|
|
1721
|
+
path.extname(
|
|
1722
|
+
args.path
|
|
1723
|
+
);
|
|
1724
|
+
|
|
1725
|
+
const isTypeScript =
|
|
1726
|
+
extension === ".ts" ||
|
|
1727
|
+
extension === ".tsx";
|
|
1728
|
+
|
|
1729
|
+
const presets:
|
|
1730
|
+
any[] = [];
|
|
1731
|
+
|
|
1732
|
+
/*
|
|
1733
|
+
* Babel 8
|
|
1734
|
+
*
|
|
1735
|
+
* ไม่ใช้:
|
|
1736
|
+
*
|
|
1737
|
+
* allowDeclareFields
|
|
1738
|
+
* allExtensions
|
|
1739
|
+
* isTSX
|
|
1740
|
+
*/
|
|
1741
|
+
if (
|
|
1742
|
+
isTypeScript
|
|
1743
|
+
) {
|
|
1744
|
+
presets.push([
|
|
1745
|
+
presetTypeScript,
|
|
1746
|
+
]);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
presets.push([
|
|
1750
|
+
presetReact,
|
|
1751
|
+
{
|
|
1752
|
+
runtime:
|
|
1753
|
+
"automatic",
|
|
1754
|
+
|
|
1755
|
+
development:
|
|
1756
|
+
true,
|
|
1757
|
+
},
|
|
1758
|
+
]);
|
|
1759
|
+
|
|
1760
|
+
const result =
|
|
1761
|
+
await transformAsync(
|
|
1762
|
+
source,
|
|
1763
|
+
{
|
|
1764
|
+
filename:
|
|
1765
|
+
args.path,
|
|
1766
|
+
|
|
1767
|
+
babelrc:
|
|
1768
|
+
false,
|
|
1769
|
+
|
|
1770
|
+
configFile:
|
|
1771
|
+
false,
|
|
1772
|
+
|
|
1773
|
+
sourceType:
|
|
1774
|
+
"module",
|
|
1775
|
+
|
|
1776
|
+
presets,
|
|
1777
|
+
|
|
1778
|
+
plugins: [
|
|
1779
|
+
reactRefreshBabel,
|
|
1780
|
+
],
|
|
1781
|
+
}
|
|
1782
|
+
);
|
|
1783
|
+
|
|
1784
|
+
if (
|
|
1785
|
+
!result?.code
|
|
1786
|
+
) {
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
const moduleId =
|
|
1791
|
+
path.relative(
|
|
1792
|
+
rootDirectory,
|
|
1793
|
+
args.path
|
|
1794
|
+
)
|
|
1795
|
+
.replace(
|
|
1796
|
+
/\\/g,
|
|
1797
|
+
"/"
|
|
1798
|
+
);
|
|
1799
|
+
|
|
1800
|
+
const transformed =
|
|
1801
|
+
`
|
|
1802
|
+
const __bcpRefreshRuntime =
|
|
1803
|
+
globalThis.__BCP_REFRESH_RUNTIME__;
|
|
1804
|
+
|
|
1805
|
+
if (!__bcpRefreshRuntime) {
|
|
1806
|
+
throw new Error(
|
|
1807
|
+
"BCP Framework: React Refresh runtime is missing."
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
const $RefreshReg$ =
|
|
1812
|
+
(type, id) => {
|
|
1813
|
+
__bcpRefreshRuntime.register(
|
|
1814
|
+
type,
|
|
1815
|
+
${JSON.stringify(moduleId)} + " " + id
|
|
1816
|
+
);
|
|
1817
|
+
};
|
|
1818
|
+
|
|
1819
|
+
const $RefreshSig$ =
|
|
1820
|
+
__bcpRefreshRuntime
|
|
1821
|
+
.createSignatureFunctionForTransform;
|
|
1822
|
+
|
|
1823
|
+
${result.code}
|
|
1824
|
+
`.trim();
|
|
1825
|
+
|
|
1826
|
+
return {
|
|
1827
|
+
contents:
|
|
1828
|
+
transformed,
|
|
1829
|
+
|
|
1830
|
+
loader:
|
|
1831
|
+
"js",
|
|
1832
|
+
|
|
1833
|
+
resolveDir:
|
|
1834
|
+
path.dirname(
|
|
1835
|
+
args.path
|
|
1836
|
+
),
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
);
|
|
1840
|
+
},
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
/*
|
|
1845
|
+
* =====================================
|
|
1846
|
+
* File Utilities
|
|
1847
|
+
* =====================================
|
|
1848
|
+
*/
|
|
1849
|
+
|
|
1850
|
+
function collectFiles(
|
|
1851
|
+
directory: string,
|
|
1852
|
+
callback:
|
|
1853
|
+
(
|
|
1854
|
+
filePath: string
|
|
1855
|
+
) => void
|
|
1856
|
+
) {
|
|
1857
|
+
const entries =
|
|
1858
|
+
fs.readdirSync(
|
|
1859
|
+
directory,
|
|
1860
|
+
{
|
|
1861
|
+
withFileTypes: true,
|
|
1862
|
+
}
|
|
1863
|
+
);
|
|
1864
|
+
|
|
1865
|
+
for (
|
|
1866
|
+
const entry
|
|
1867
|
+
of entries
|
|
1868
|
+
) {
|
|
1869
|
+
const filePath =
|
|
1870
|
+
path.join(
|
|
1871
|
+
directory,
|
|
1872
|
+
entry.name
|
|
1873
|
+
);
|
|
1874
|
+
|
|
1875
|
+
if (
|
|
1876
|
+
entry.isDirectory()
|
|
1877
|
+
) {
|
|
1878
|
+
collectFiles(
|
|
1879
|
+
filePath,
|
|
1880
|
+
callback
|
|
1881
|
+
);
|
|
1882
|
+
|
|
1883
|
+
continue;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
callback(
|
|
1887
|
+
filePath
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
/*
|
|
1893
|
+
* =====================================
|
|
1894
|
+
* Import Specifier
|
|
1895
|
+
* =====================================
|
|
1896
|
+
*/
|
|
1897
|
+
|
|
1898
|
+
function toImportSpecifier(
|
|
1899
|
+
rootDirectory: string,
|
|
1900
|
+
filePath: string
|
|
1901
|
+
): string {
|
|
1902
|
+
let result =
|
|
1903
|
+
path.relative(
|
|
1904
|
+
rootDirectory,
|
|
1905
|
+
filePath
|
|
1906
|
+
);
|
|
1907
|
+
|
|
1908
|
+
result =
|
|
1909
|
+
result.replace(
|
|
1910
|
+
/\\/g,
|
|
1911
|
+
"/"
|
|
1912
|
+
);
|
|
1913
|
+
|
|
1914
|
+
if (
|
|
1915
|
+
!result.startsWith(
|
|
1916
|
+
"."
|
|
1917
|
+
)
|
|
1918
|
+
) {
|
|
1919
|
+
result =
|
|
1920
|
+
`./${result}`;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
return result;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
/*
|
|
1927
|
+
* =====================================
|
|
1928
|
+
* Bundle Names
|
|
1929
|
+
* =====================================
|
|
1930
|
+
*/
|
|
1931
|
+
|
|
1932
|
+
function routeToBundleName(
|
|
1933
|
+
pathname: string
|
|
1934
|
+
): string {
|
|
1935
|
+
if (
|
|
1936
|
+
pathname === "/"
|
|
1937
|
+
) {
|
|
1938
|
+
return "index.js";
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
const name =
|
|
1942
|
+
pathname
|
|
1943
|
+
.slice(1)
|
|
1944
|
+
.split("/")
|
|
1945
|
+
.map(
|
|
1946
|
+
sanitizeSegment
|
|
1947
|
+
)
|
|
1948
|
+
.join("__");
|
|
1949
|
+
|
|
1950
|
+
return `${name}.js`;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
function sanitizeBundleId(
|
|
1954
|
+
pathname: string
|
|
1955
|
+
): string {
|
|
1956
|
+
if (
|
|
1957
|
+
pathname === "/"
|
|
1958
|
+
) {
|
|
1959
|
+
return "root";
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
return pathname
|
|
1963
|
+
.slice(1)
|
|
1964
|
+
.split("/")
|
|
1965
|
+
.map(
|
|
1966
|
+
sanitizeSegment
|
|
1967
|
+
)
|
|
1968
|
+
.join("-");
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
function sanitizeSegment(
|
|
1972
|
+
segment: string
|
|
1973
|
+
): string {
|
|
1974
|
+
const dynamicMatch =
|
|
1975
|
+
/^\[([A-Za-z_][A-Za-z0-9_]*)\]$/.exec(
|
|
1976
|
+
segment
|
|
1977
|
+
);
|
|
1978
|
+
|
|
1979
|
+
if (
|
|
1980
|
+
dynamicMatch
|
|
1981
|
+
) {
|
|
1982
|
+
return (
|
|
1983
|
+
`param-${dynamicMatch[1]}`
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
return segment.replace(
|
|
1988
|
+
/[^A-Za-z0-9._-]/g,
|
|
1989
|
+
"_"
|
|
1990
|
+
);
|
|
1991
|
+
}
|