@webtypen/webframez-react 0.0.35 → 0.0.36
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/bin/webframez-react.mjs +358 -1
- package/defaults/default-client.js +3 -0
- package/defaults/webpack.client.cjs +177 -10
- package/dist/build-plugin.cjs +111 -0
- package/dist/build-plugin.d.ts +14 -0
- package/dist/build-plugin.js +77 -0
- package/dist/client.cjs +1 -1
- package/dist/client.js +1 -1
- package/dist/http.cjs +197 -35
- package/dist/http.js +202 -35
- package/dist/index.cjs +325 -51
- package/dist/index.js +327 -51
- package/dist/types.d.ts +0 -1
- package/dist/webframez-core.cjs +325 -51
- package/dist/webframez-core.d.ts +27 -3
- package/dist/webframez-core.js +329 -51
- package/package.json +10 -1
package/bin/webframez-react.mjs
CHANGED
|
@@ -4,16 +4,18 @@ import fs from "node:fs";
|
|
|
4
4
|
import fsp from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
7
8
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
9
|
|
|
9
10
|
const binFilePath = fileURLToPath(import.meta.url);
|
|
10
11
|
const packageRoot = path.resolve(path.dirname(binFilePath), "..");
|
|
11
12
|
const projectRoot = process.cwd();
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
12
14
|
|
|
13
15
|
const command = process.argv[2];
|
|
14
16
|
const passthroughStart = process.argv[3] === "--" ? 4 : 3;
|
|
15
17
|
const passthroughArgs = process.argv.slice(passthroughStart);
|
|
16
|
-
const customArgPrefixes = ["--client-entry", "--server-entry"];
|
|
18
|
+
const customArgPrefixes = ["--client-entry", "--server-entry", "--routes", "--out-dir", "--runtime-out-dir"];
|
|
17
19
|
|
|
18
20
|
function printHelp() {
|
|
19
21
|
console.log(
|
|
@@ -25,6 +27,8 @@ function printHelp() {
|
|
|
25
27
|
" webframez-react watch:server",
|
|
26
28
|
" webframez-react build:client",
|
|
27
29
|
" webframez-react watch:client",
|
|
30
|
+
" webframez-react build:routes --routes=dist/app/routes.js --out-dir=dist",
|
|
31
|
+
" webframez-react watch:routes --routes=dist/app/routes.js --out-dir=dist",
|
|
28
32
|
" webframez-react build:server:webpack",
|
|
29
33
|
" webframez-react watch:server:webpack",
|
|
30
34
|
" webframez-react exec -- <command> [args...]",
|
|
@@ -44,6 +48,9 @@ function printHelp() {
|
|
|
44
48
|
"Optional CLI overrides:",
|
|
45
49
|
" --client-entry=src/client.tsx",
|
|
46
50
|
" --server-entry=src/server.ts",
|
|
51
|
+
" --routes=dist/app/routes.js",
|
|
52
|
+
" --out-dir=dist",
|
|
53
|
+
" --runtime-out-dir=dist",
|
|
47
54
|
].join("\n"),
|
|
48
55
|
);
|
|
49
56
|
}
|
|
@@ -70,6 +77,14 @@ function readCustomArg(name) {
|
|
|
70
77
|
return null;
|
|
71
78
|
}
|
|
72
79
|
|
|
80
|
+
function requireCustomArg(name, description) {
|
|
81
|
+
const value = readCustomArg(name);
|
|
82
|
+
if (!value) {
|
|
83
|
+
throw new Error(`Missing ${description || name}.`);
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
73
88
|
function stripCustomArgs(args) {
|
|
74
89
|
const filtered = [];
|
|
75
90
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -121,6 +136,55 @@ function buildReactServerNodeOptions() {
|
|
|
121
136
|
return `${existing}--conditions react-server -r @webtypen/webframez-react/register`.trim();
|
|
122
137
|
}
|
|
123
138
|
|
|
139
|
+
function hasReactServerCondition() {
|
|
140
|
+
if (process.execArgv.includes("--conditions=react-server")) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (let index = 0; index < process.execArgv.length; index += 1) {
|
|
145
|
+
if (process.execArgv[index] === "--conditions" && process.execArgv[index + 1] === "react-server") {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return /\b--conditions(?:=|\s+)react-server\b/.test(process.env.NODE_OPTIONS || "");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function reexecWithReactServerConditionIfNeeded() {
|
|
154
|
+
if (
|
|
155
|
+
(command !== "build:routes" && command !== "watch:routes") ||
|
|
156
|
+
hasReactServerCondition() ||
|
|
157
|
+
process.env.WEBFRAMEZ_REACT_CLI_REEXEC === "1"
|
|
158
|
+
) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const child = spawn(process.execPath, [
|
|
163
|
+
"--conditions",
|
|
164
|
+
"react-server",
|
|
165
|
+
"--require",
|
|
166
|
+
path.resolve(packageRoot, "register.cjs"),
|
|
167
|
+
binFilePath,
|
|
168
|
+
...process.argv.slice(2),
|
|
169
|
+
], {
|
|
170
|
+
cwd: projectRoot,
|
|
171
|
+
stdio: "inherit",
|
|
172
|
+
shell: false,
|
|
173
|
+
env: {
|
|
174
|
+
...process.env,
|
|
175
|
+
WEBFRAMEZ_REACT_CLI_REEXEC: "1",
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const code = await new Promise((resolve, reject) => {
|
|
180
|
+
child.on("error", reject);
|
|
181
|
+
child.on("close", (exitCode) => resolve(exitCode || 0));
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
process.exit(code);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
124
188
|
async function loadProjectConfig() {
|
|
125
189
|
const configFiles = [
|
|
126
190
|
"webframez-react.config.mjs",
|
|
@@ -216,12 +280,270 @@ function run(binaryName, args, envAdditions = {}) {
|
|
|
216
280
|
});
|
|
217
281
|
}
|
|
218
282
|
|
|
283
|
+
function start(binaryName, args, envAdditions = {}) {
|
|
284
|
+
const binary = resolveBinary(binaryName);
|
|
285
|
+
|
|
286
|
+
return spawn(binary, args, {
|
|
287
|
+
cwd: projectRoot,
|
|
288
|
+
stdio: "inherit",
|
|
289
|
+
shell: false,
|
|
290
|
+
env: {
|
|
291
|
+
...process.env,
|
|
292
|
+
...envAdditions,
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function fileExists(filePath) {
|
|
298
|
+
return fs.existsSync(filePath);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function mapTargetPath(value, fromRoot, toRoot) {
|
|
302
|
+
if (!value) {
|
|
303
|
+
return value;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const relative = path.relative(fromRoot, value);
|
|
307
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return path.resolve(toRoot, relative);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function loadRouteBuildTargets(routesPath, outDir, runtimeOutDir) {
|
|
315
|
+
const coreModulePath = path.resolve(packageRoot, "dist", "webframez-core.cjs");
|
|
316
|
+
const core = require(coreModulePath);
|
|
317
|
+
const Module = require("node:module");
|
|
318
|
+
const originalLoad = Module._load;
|
|
319
|
+
const previousOutDir = process.env.WEBFRAMEZ_REACT_OUT_DIR;
|
|
320
|
+
const previousCaptureRoutes = process.env.WEBFRAMEZ_REACT_CAPTURE_ROUTES;
|
|
321
|
+
const fakeRoute = {
|
|
322
|
+
extend(name, factory) {
|
|
323
|
+
this[name] = factory(this);
|
|
324
|
+
return this;
|
|
325
|
+
},
|
|
326
|
+
group(_options, callback) {
|
|
327
|
+
if (typeof callback === "function") {
|
|
328
|
+
callback();
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
get() {},
|
|
332
|
+
post() {},
|
|
333
|
+
put() {},
|
|
334
|
+
delete() {},
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
process.env.WEBFRAMEZ_REACT_OUT_DIR = runtimeOutDir;
|
|
338
|
+
process.env.WEBFRAMEZ_REACT_CAPTURE_ROUTES = "1";
|
|
339
|
+
core.clearRegisteredReactBuildTargets();
|
|
340
|
+
Module._load = function webframezReactCaptureModuleLoad(request, parent, isMain) {
|
|
341
|
+
if (request === "@webtypen/webframez-react") {
|
|
342
|
+
return require(path.resolve(packageRoot, "dist", "index.cjs"));
|
|
343
|
+
}
|
|
344
|
+
if (request === "@webtypen/webframez-react/http") {
|
|
345
|
+
return require(path.resolve(packageRoot, "dist", "http.cjs"));
|
|
346
|
+
}
|
|
347
|
+
if (request === "@webtypen/webframez-react/webframez-core") {
|
|
348
|
+
return require(path.resolve(packageRoot, "dist", "webframez-core.cjs"));
|
|
349
|
+
}
|
|
350
|
+
if (request === "@webtypen/webframez-react/navigation") {
|
|
351
|
+
return require(path.resolve(packageRoot, "dist", "navigation.cjs"));
|
|
352
|
+
}
|
|
353
|
+
if (request === "@webtypen/webframez-react/route-slot") {
|
|
354
|
+
return require(path.resolve(packageRoot, "dist", "route-slot.cjs"));
|
|
355
|
+
}
|
|
356
|
+
if (request === "@webtypen/webframez-react/client") {
|
|
357
|
+
return require(path.resolve(packageRoot, "dist", "client.cjs"));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (
|
|
361
|
+
request === "@webtypen/webframez-core" ||
|
|
362
|
+
request === "webframez-core" ||
|
|
363
|
+
request === "@webtypen/webframez-core/dist/Router/Route" ||
|
|
364
|
+
request === "webframez-core/dist/Router/Route"
|
|
365
|
+
) {
|
|
366
|
+
return {
|
|
367
|
+
Route: fakeRoute,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return originalLoad.call(this, request, parent, isMain);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const resolvedRoutesPath = path.resolve(projectRoot, routesPath);
|
|
376
|
+
await import(pathToFileURL(resolvedRoutesPath).href);
|
|
377
|
+
return core.getRegisteredReactBuildTargets().map((target) => ({
|
|
378
|
+
...target,
|
|
379
|
+
runtimeDistRootDir: target.distRootDir,
|
|
380
|
+
runtimePagesDir: target.pagesDir,
|
|
381
|
+
runtimeManifestPath: target.manifestPath,
|
|
382
|
+
distRootDir: mapTargetPath(target.distRootDir, runtimeOutDir, outDir),
|
|
383
|
+
pagesDir: mapTargetPath(target.pagesDir, runtimeOutDir, outDir),
|
|
384
|
+
manifestPath: mapTargetPath(target.manifestPath, runtimeOutDir, outDir),
|
|
385
|
+
}));
|
|
386
|
+
} finally {
|
|
387
|
+
Module._load = originalLoad;
|
|
388
|
+
if (previousOutDir === undefined) {
|
|
389
|
+
delete process.env.WEBFRAMEZ_REACT_OUT_DIR;
|
|
390
|
+
} else {
|
|
391
|
+
process.env.WEBFRAMEZ_REACT_OUT_DIR = previousOutDir;
|
|
392
|
+
}
|
|
393
|
+
if (previousCaptureRoutes === undefined) {
|
|
394
|
+
delete process.env.WEBFRAMEZ_REACT_CAPTURE_ROUTES;
|
|
395
|
+
} else {
|
|
396
|
+
process.env.WEBFRAMEZ_REACT_CAPTURE_ROUTES = previousCaptureRoutes;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async function waitForFile(filePath) {
|
|
402
|
+
const startedAt = Date.now();
|
|
403
|
+
|
|
404
|
+
while (!fileExists(filePath)) {
|
|
405
|
+
if (Date.now() - startedAt > 30000) {
|
|
406
|
+
throw new Error(`Timed out waiting for ${filePath}.`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
console.log(`[webframez-react] waiting for routes file: ${filePath}`);
|
|
410
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function buildRouteStyles(target) {
|
|
415
|
+
if (!target.styleSrcPath || !fileExists(target.styleSrcPath)) {
|
|
416
|
+
return 0;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const outputDir = path.join(target.distRootDir, "scss");
|
|
420
|
+
await fsp.mkdir(outputDir, { recursive: true });
|
|
421
|
+
console.log(`[webframez-react] sass: ${target.styleSrcPath} -> ${outputDir}`);
|
|
422
|
+
|
|
423
|
+
return run("sass", ["--no-source-map", `${target.styleSrcPath}:${outputDir}`]);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function buildRouteClient(target, passthroughArgsClean) {
|
|
427
|
+
const config = {
|
|
428
|
+
path: path.resolve(packageRoot, "defaults", "webpack.client.cjs"),
|
|
429
|
+
source: "package",
|
|
430
|
+
};
|
|
431
|
+
const clientEntry = target.clientEntryPath || path.resolve(packageRoot, "defaults", "default-client.js");
|
|
432
|
+
|
|
433
|
+
console.log(`[webframez-react] route: ${target.path}`);
|
|
434
|
+
console.log(`[webframez-react] webpack config (${config.source}): ${config.path}`);
|
|
435
|
+
console.log(`[webframez-react] pages: ${target.pagesDir}`);
|
|
436
|
+
console.log(`[webframez-react] dist: ${target.distRootDir}`);
|
|
437
|
+
console.log(`[webframez-react] client entry: ${clientEntry}`);
|
|
438
|
+
|
|
439
|
+
return run(
|
|
440
|
+
"webpack",
|
|
441
|
+
["--config", config.path, ...passthroughArgsClean],
|
|
442
|
+
{
|
|
443
|
+
WEBFRAMEZ_REACT_CLIENT_ENTRY: clientEntry,
|
|
444
|
+
WEBFRAMEZ_REACT_DIST_ROOT_DIR: target.distRootDir,
|
|
445
|
+
WEBFRAMEZ_REACT_PAGES_DIR: target.pagesDir,
|
|
446
|
+
WEBFRAMEZ_REACT_RUNTIME_DIST_ROOT_DIR: target.runtimeDistRootDir || target.distRootDir,
|
|
447
|
+
WEBFRAMEZ_REACT_RUNTIME_PAGES_DIR: target.runtimePagesDir || target.pagesDir,
|
|
448
|
+
},
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function watchRouteStyles(target) {
|
|
453
|
+
if (!target.styleSrcPath || !fileExists(target.styleSrcPath)) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const outputDir = path.join(target.distRootDir, "scss");
|
|
458
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
459
|
+
console.log(`[webframez-react] sass watch: ${target.styleSrcPath} -> ${outputDir}`);
|
|
460
|
+
|
|
461
|
+
return start("sass", ["--watch", "--poll", "--no-source-map", `${target.styleSrcPath}:${outputDir}`]);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function watchRouteClient(target, passthroughArgsClean) {
|
|
465
|
+
const config = {
|
|
466
|
+
path: path.resolve(packageRoot, "defaults", "webpack.client.cjs"),
|
|
467
|
+
source: "package",
|
|
468
|
+
};
|
|
469
|
+
const clientEntry = target.clientEntryPath || path.resolve(packageRoot, "defaults", "default-client.js");
|
|
470
|
+
const args = ["--config", config.path, ...passthroughArgsClean];
|
|
471
|
+
|
|
472
|
+
if (!args.includes("--watch")) {
|
|
473
|
+
args.push("--watch");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
console.log(`[webframez-react] route watch: ${target.path}`);
|
|
477
|
+
console.log(`[webframez-react] webpack config (${config.source}): ${config.path}`);
|
|
478
|
+
console.log(`[webframez-react] pages: ${target.pagesDir}`);
|
|
479
|
+
console.log(`[webframez-react] dist: ${target.distRootDir}`);
|
|
480
|
+
console.log(`[webframez-react] client entry: ${clientEntry}`);
|
|
481
|
+
|
|
482
|
+
return start("webpack", args, {
|
|
483
|
+
WEBFRAMEZ_REACT_CLIENT_ENTRY: clientEntry,
|
|
484
|
+
WEBFRAMEZ_REACT_DIST_ROOT_DIR: target.distRootDir,
|
|
485
|
+
WEBFRAMEZ_REACT_PAGES_DIR: target.pagesDir,
|
|
486
|
+
WEBFRAMEZ_REACT_RUNTIME_DIST_ROOT_DIR: target.runtimeDistRootDir || target.distRootDir,
|
|
487
|
+
WEBFRAMEZ_REACT_RUNTIME_PAGES_DIR: target.runtimePagesDir || target.pagesDir,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async function watchRouteTargets(targets, passthroughArgsClean) {
|
|
492
|
+
const children = [];
|
|
493
|
+
|
|
494
|
+
for (const target of targets) {
|
|
495
|
+
const styleChild = watchRouteStyles(target);
|
|
496
|
+
if (styleChild) {
|
|
497
|
+
children.push(styleChild);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
children.push(watchRouteClient(target, passthroughArgsClean));
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (children.length === 0) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
await new Promise((resolve, reject) => {
|
|
508
|
+
for (const child of children) {
|
|
509
|
+
child.on("error", reject);
|
|
510
|
+
child.on("close", (code) => {
|
|
511
|
+
if (code && code !== 0) {
|
|
512
|
+
reject(new Error(`[webframez-react] watch child exited with code ${code}.`));
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
resolve();
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async function validateRouteBuild(target) {
|
|
523
|
+
const requiredFiles = [
|
|
524
|
+
path.join(target.distRootDir, "client.js"),
|
|
525
|
+
path.join(target.distRootDir, "react-client-manifest.json"),
|
|
526
|
+
path.join(target.distRootDir, "react-ssr-manifest.json"),
|
|
527
|
+
];
|
|
528
|
+
|
|
529
|
+
const missing = requiredFiles.filter((filePath) => !fileExists(filePath));
|
|
530
|
+
if (missing.length > 0) {
|
|
531
|
+
throw new Error(
|
|
532
|
+
`Route ${target.path} build is incomplete. Missing: ${missing.join(", ")}`,
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
219
537
|
async function main() {
|
|
220
538
|
if (!command || command === "--help" || command === "-h") {
|
|
221
539
|
printHelp();
|
|
222
540
|
return;
|
|
223
541
|
}
|
|
224
542
|
|
|
543
|
+
if (await reexecWithReactServerConditionIfNeeded()) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
225
547
|
const projectConfig = await loadProjectConfig();
|
|
226
548
|
const customClientEntry = readCustomArg("--client-entry");
|
|
227
549
|
const customServerEntry = readCustomArg("--server-entry");
|
|
@@ -280,6 +602,41 @@ async function main() {
|
|
|
280
602
|
return;
|
|
281
603
|
}
|
|
282
604
|
|
|
605
|
+
if (command === "build:routes" || command === "watch:routes") {
|
|
606
|
+
const routesPath = requireCustomArg("--routes", "--routes=<compiled routes file>");
|
|
607
|
+
const outDir = path.resolve(projectRoot, readCustomArg("--out-dir") || "dist");
|
|
608
|
+
const runtimeOutDir = path.resolve(projectRoot, readCustomArg("--runtime-out-dir") || outDir);
|
|
609
|
+
await waitForFile(path.resolve(projectRoot, routesPath));
|
|
610
|
+
const targets = await loadRouteBuildTargets(routesPath, outDir, runtimeOutDir);
|
|
611
|
+
|
|
612
|
+
if (targets.length === 0) {
|
|
613
|
+
throw new Error(`[webframez-react] No React routes were registered by ${routesPath}.`);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (command === "watch:routes") {
|
|
617
|
+
await watchRouteTargets(targets, passthroughArgsClean);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
for (const target of targets) {
|
|
622
|
+
const sassCode = await buildRouteStyles(target);
|
|
623
|
+
if (sassCode !== 0) {
|
|
624
|
+
process.exit(sassCode);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const webpackCode = await buildRouteClient(target, passthroughArgsClean);
|
|
629
|
+
if (webpackCode !== 0) {
|
|
630
|
+
process.exit(webpackCode);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
await validateRouteBuild(target);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
283
640
|
if (command === "build:client" || command === "watch:client") {
|
|
284
641
|
const config = resolveConfig("webpack.client.cjs", "webpack.client.cjs");
|
|
285
642
|
console.log(`[webframez-react] webpack config (${config.source}): ${config.path}`);
|
|
@@ -1,30 +1,201 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
1
2
|
const path = require("path");
|
|
2
3
|
const ReactFlightWebpackPlugin = require("react-server-dom-webpack/plugin");
|
|
4
|
+
const { fileURLToPath, pathToFileURL } = require("url");
|
|
3
5
|
|
|
4
6
|
const projectRoot = process.cwd();
|
|
5
7
|
const frameworkDistDir = path.resolve(__dirname, "..", "dist");
|
|
6
8
|
const clientEntry = process.env.WEBFRAMEZ_REACT_CLIENT_ENTRY
|
|
7
9
|
? path.resolve(projectRoot, process.env.WEBFRAMEZ_REACT_CLIENT_ENTRY)
|
|
8
|
-
: path.resolve(
|
|
10
|
+
: path.resolve(__dirname, "default-client.js");
|
|
11
|
+
const pagesDir = process.env.WEBFRAMEZ_REACT_PAGES_DIR
|
|
12
|
+
? path.resolve(projectRoot, process.env.WEBFRAMEZ_REACT_PAGES_DIR)
|
|
13
|
+
: path.resolve(projectRoot, "dist/pages");
|
|
14
|
+
const distDir = process.env.WEBFRAMEZ_REACT_DIST_ROOT_DIR
|
|
15
|
+
? path.resolve(projectRoot, process.env.WEBFRAMEZ_REACT_DIST_ROOT_DIR)
|
|
16
|
+
: path.resolve(projectRoot, "dist");
|
|
17
|
+
const runtimePagesDir = process.env.WEBFRAMEZ_REACT_RUNTIME_PAGES_DIR
|
|
18
|
+
? path.resolve(projectRoot, process.env.WEBFRAMEZ_REACT_RUNTIME_PAGES_DIR)
|
|
19
|
+
: pagesDir;
|
|
9
20
|
const isWatchMode = process.argv.includes("--watch");
|
|
10
21
|
const mode =
|
|
11
22
|
process.env.NODE_ENV === "production" || (typeof process.env.NODE_ENV === "undefined" && !isWatchMode)
|
|
12
23
|
? "production"
|
|
13
24
|
: "development";
|
|
25
|
+
const frameworkCacheFiles = [
|
|
26
|
+
path.join(frameworkDistDir, "client.js"),
|
|
27
|
+
path.join(frameworkDistDir, "route-slot.js"),
|
|
28
|
+
path.join(frameworkDistDir, "navigation.js"),
|
|
29
|
+
].filter((filePath) => fs.existsSync(filePath));
|
|
30
|
+
const frameworkCacheVersion = frameworkCacheFiles
|
|
31
|
+
.map((filePath) => `${path.basename(filePath)}:${fs.statSync(filePath).mtimeMs}:${fs.statSync(filePath).size}`)
|
|
32
|
+
.join("|");
|
|
33
|
+
|
|
34
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
35
|
+
fs.mkdirSync(pagesDir, { recursive: true });
|
|
36
|
+
|
|
37
|
+
class ClientManifestExportAliasesPlugin {
|
|
38
|
+
apply(compiler) {
|
|
39
|
+
compiler.hooks.done.tap("ClientManifestExportAliasesPlugin", () => {
|
|
40
|
+
const manifestPath = path.join(distDir, "react-client-manifest.json");
|
|
41
|
+
if (!fs.existsSync(manifestPath)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
46
|
+
let changed = false;
|
|
47
|
+
|
|
48
|
+
for (const [key, value] of Object.entries(manifest)) {
|
|
49
|
+
if (!key.startsWith("file://")) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let absolutePath = null;
|
|
54
|
+
try {
|
|
55
|
+
absolutePath = fileURLToPath(key);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
absolutePath = null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!absolutePath || !fs.existsSync(absolutePath)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const relativeToProject = path.relative(projectRoot, absolutePath).split(path.sep).join("/");
|
|
65
|
+
const relativeAlias = relativeToProject.startsWith(".") ? relativeToProject : `./${relativeToProject}`;
|
|
66
|
+
const aliases = [
|
|
67
|
+
{ key, force: false },
|
|
68
|
+
{ key: absolutePath, force: false },
|
|
69
|
+
{ key: relativeAlias, force: false },
|
|
70
|
+
{ key: relativeToProject, force: false },
|
|
71
|
+
];
|
|
72
|
+
const relativeToPagesDir = path.relative(pagesDir, absolutePath);
|
|
73
|
+
|
|
74
|
+
if (!relativeToPagesDir.startsWith("..") && !path.isAbsolute(relativeToPagesDir)) {
|
|
75
|
+
const runtimeAbsolutePath = path.resolve(runtimePagesDir, relativeToPagesDir);
|
|
76
|
+
const runtimeRelativeToProject = path.relative(projectRoot, runtimeAbsolutePath).split(path.sep).join("/");
|
|
77
|
+
const runtimeRelativeAlias = runtimeRelativeToProject.startsWith(".")
|
|
78
|
+
? runtimeRelativeToProject
|
|
79
|
+
: `./${runtimeRelativeToProject}`;
|
|
80
|
+
aliases.push(
|
|
81
|
+
{ key: pathToFileURL(runtimeAbsolutePath).href, force: false },
|
|
82
|
+
{ key: runtimeAbsolutePath, force: false },
|
|
83
|
+
{ key: runtimeRelativeAlias, force: false },
|
|
84
|
+
{ key: runtimeRelativeToProject, force: false },
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const relativeToFrameworkDist = path.relative(frameworkDistDir, absolutePath);
|
|
89
|
+
if (
|
|
90
|
+
!relativeToFrameworkDist.startsWith("..") &&
|
|
91
|
+
!path.isAbsolute(relativeToFrameworkDist) &&
|
|
92
|
+
/\.(js)$/.test(relativeToFrameworkDist) &&
|
|
93
|
+
/^(navigation|route-slot)\.js$/.test(relativeToFrameworkDist)
|
|
94
|
+
) {
|
|
95
|
+
const cjsAbsolutePath = absolutePath.replace(/\.js$/, ".cjs");
|
|
96
|
+
if (fs.existsSync(cjsAbsolutePath)) {
|
|
97
|
+
const cjsRelativeToProject = path.relative(projectRoot, cjsAbsolutePath).split(path.sep).join("/");
|
|
98
|
+
const cjsRelativeAlias = cjsRelativeToProject.startsWith(".")
|
|
99
|
+
? cjsRelativeToProject
|
|
100
|
+
: `./${cjsRelativeToProject}`;
|
|
101
|
+
aliases.push(
|
|
102
|
+
{ key: pathToFileURL(cjsAbsolutePath).href, force: true },
|
|
103
|
+
{ key: cjsAbsolutePath, force: true },
|
|
104
|
+
{ key: cjsRelativeAlias, force: true },
|
|
105
|
+
{ key: cjsRelativeToProject, force: true },
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const alias of aliases) {
|
|
111
|
+
if (alias.force || !(alias.key in manifest)) {
|
|
112
|
+
manifest[alias.key] = value;
|
|
113
|
+
changed = true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const source = fs.readFileSync(absolutePath, "utf-8");
|
|
118
|
+
const exportNames = new Set();
|
|
119
|
+
const exportPattern = /exports\.([A-Za-z0-9_$]+)\s*=/g;
|
|
120
|
+
let match = null;
|
|
121
|
+
while ((match = exportPattern.exec(source)) !== null) {
|
|
122
|
+
if (match[1] && match[1] !== "__esModule") {
|
|
123
|
+
exportNames.add(match[1]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const exportName of exportNames) {
|
|
128
|
+
const exportValue =
|
|
129
|
+
value && typeof value === "object"
|
|
130
|
+
? {
|
|
131
|
+
...value,
|
|
132
|
+
name: exportName,
|
|
133
|
+
}
|
|
134
|
+
: value;
|
|
135
|
+
for (const alias of aliases) {
|
|
136
|
+
const aliasKey = `${alias.key}#${exportName}`;
|
|
137
|
+
if (alias.force || !(aliasKey in manifest)) {
|
|
138
|
+
manifest[aliasKey] = exportValue;
|
|
139
|
+
changed = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (exportName === "default") {
|
|
144
|
+
for (const alias of aliases) {
|
|
145
|
+
const aliasKey = `${alias.key}#`;
|
|
146
|
+
if (alias.force || !(aliasKey in manifest)) {
|
|
147
|
+
manifest[aliasKey] = exportValue;
|
|
148
|
+
changed = true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (changed) {
|
|
156
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
14
161
|
|
|
15
162
|
module.exports = {
|
|
16
163
|
mode,
|
|
164
|
+
cache: {
|
|
165
|
+
type: "filesystem",
|
|
166
|
+
version: frameworkCacheVersion,
|
|
167
|
+
buildDependencies: {
|
|
168
|
+
config: [__filename, ...frameworkCacheFiles],
|
|
169
|
+
},
|
|
170
|
+
},
|
|
17
171
|
entry: {
|
|
18
172
|
client: clientEntry,
|
|
19
173
|
},
|
|
20
174
|
output: {
|
|
21
|
-
path:
|
|
22
|
-
filename: "
|
|
175
|
+
path: distDir,
|
|
176
|
+
filename: "client.js",
|
|
23
177
|
chunkFilename: "chunks/[name]-[contenthash].js",
|
|
24
178
|
publicPath: "auto",
|
|
25
179
|
},
|
|
26
180
|
resolve: {
|
|
27
181
|
extensions: [".tsx", ".ts", ".js"],
|
|
182
|
+
modules: [path.resolve(projectRoot, "node_modules"), "node_modules"],
|
|
183
|
+
alias: {
|
|
184
|
+
react: path.resolve(projectRoot, "node_modules", "react"),
|
|
185
|
+
"react-dom": path.resolve(projectRoot, "node_modules", "react-dom"),
|
|
186
|
+
"react/jsx-runtime": path.resolve(projectRoot, "node_modules", "react", "jsx-runtime.js"),
|
|
187
|
+
"react/jsx-dev-runtime": path.resolve(projectRoot, "node_modules", "react", "jsx-dev-runtime.js"),
|
|
188
|
+
scheduler: path.resolve(projectRoot, "node_modules", "scheduler"),
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
watchOptions: {
|
|
192
|
+
aggregateTimeout: 150,
|
|
193
|
+
poll: Number(process.env.WEBFRAMEZ_REACT_WATCH_POLL || 1000),
|
|
194
|
+
ignored: [
|
|
195
|
+
path.resolve(projectRoot, "node_modules", "**"),
|
|
196
|
+
path.resolve(distDir, "**"),
|
|
197
|
+
path.resolve(projectRoot, "build", "node_modules", "**"),
|
|
198
|
+
],
|
|
28
199
|
},
|
|
29
200
|
module: {
|
|
30
201
|
rules: [
|
|
@@ -51,21 +222,17 @@ module.exports = {
|
|
|
51
222
|
isServer: false,
|
|
52
223
|
clientReferences: [
|
|
53
224
|
{
|
|
54
|
-
directory:
|
|
55
|
-
recursive: true,
|
|
56
|
-
include: /\.js$/,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
directory: path.resolve(projectRoot, "dist/src/components"),
|
|
225
|
+
directory: pagesDir,
|
|
60
226
|
recursive: true,
|
|
61
227
|
include: /\.js$/,
|
|
62
228
|
},
|
|
63
229
|
{
|
|
64
230
|
directory: frameworkDistDir,
|
|
65
231
|
recursive: false,
|
|
66
|
-
include: /navigation\.
|
|
232
|
+
include: /(navigation|route-slot)\.js$/,
|
|
67
233
|
},
|
|
68
234
|
],
|
|
69
235
|
}),
|
|
236
|
+
new ClientManifestExportAliasesPlugin(),
|
|
70
237
|
],
|
|
71
238
|
};
|