@lolyjs/core 0.2.0-alpha.14 → 0.2.0-alpha.16
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/README.md +1016 -755
- package/dist/{bootstrap-DgvWWDim.d.mts → bootstrap-BfGTMUkj.d.mts} +12 -0
- package/dist/{bootstrap-DgvWWDim.d.ts → bootstrap-BfGTMUkj.d.ts} +12 -0
- package/dist/cli.cjs +4389 -2972
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4385 -2968
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +2225 -560
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +9 -75
- package/dist/index.d.ts +9 -75
- package/dist/index.js +2226 -561
- package/dist/index.js.map +1 -1
- package/dist/index.types-BPX6IVAC.d.mts +198 -0
- package/dist/index.types-BPX6IVAC.d.ts +198 -0
- package/dist/react/cache.cjs.map +1 -1
- package/dist/react/cache.d.mts +22 -2
- package/dist/react/cache.d.ts +22 -2
- package/dist/react/cache.js.map +1 -1
- package/dist/react/components.cjs.map +1 -1
- package/dist/react/components.js.map +1 -1
- package/dist/react/hooks.cjs.map +1 -1
- package/dist/react/hooks.js.map +1 -1
- package/dist/react/sockets.cjs.map +1 -1
- package/dist/react/sockets.js.map +1 -1
- package/dist/runtime.cjs +359 -95
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.mts +2 -2
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +359 -95
- package/dist/runtime.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -74,7 +74,7 @@ var init_globals = __esm({
|
|
|
74
74
|
|
|
75
75
|
// src/server.ts
|
|
76
76
|
import fs16 from "fs";
|
|
77
|
-
import
|
|
77
|
+
import path23 from "path";
|
|
78
78
|
|
|
79
79
|
// modules/server/utils/server-dir.ts
|
|
80
80
|
init_globals();
|
|
@@ -204,7 +204,7 @@ function loadLayoutsForDir(pageDir, appDir) {
|
|
|
204
204
|
};
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// modules/router/
|
|
207
|
+
// modules/router/server-hook.ts
|
|
208
208
|
import fs3 from "fs";
|
|
209
209
|
import path3 from "path";
|
|
210
210
|
var NAMING = {
|
|
@@ -216,14 +216,16 @@ var NAMING = {
|
|
|
216
216
|
// Files
|
|
217
217
|
SERVER_HOOK: "server.hook"
|
|
218
218
|
};
|
|
219
|
-
function
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
const
|
|
219
|
+
function loadServerHookForDir(currentDir) {
|
|
220
|
+
const pageServerHookTs = path3.join(currentDir, `page.server.hook.ts`);
|
|
221
|
+
const pageServerHookJs = path3.join(currentDir, `page.server.hook.js`);
|
|
222
|
+
const serverHookTs = path3.join(currentDir, `${NAMING.SERVER_HOOK}.ts`);
|
|
223
|
+
const serverHookJs = path3.join(currentDir, `${NAMING.SERVER_HOOK}.js`);
|
|
224
|
+
const file = fs3.existsSync(pageServerHookTs) ? pageServerHookTs : fs3.existsSync(pageServerHookJs) ? pageServerHookJs : fs3.existsSync(serverHookTs) ? serverHookTs : fs3.existsSync(serverHookJs) ? serverHookJs : null;
|
|
223
225
|
if (!file) {
|
|
224
226
|
return {
|
|
225
227
|
middlewares: [],
|
|
226
|
-
|
|
228
|
+
serverHook: null,
|
|
227
229
|
dynamic: "auto",
|
|
228
230
|
generateStaticParams: null
|
|
229
231
|
};
|
|
@@ -239,31 +241,119 @@ function loadLoaderForDir(currentDir) {
|
|
|
239
241
|
mod = __require(file);
|
|
240
242
|
} catch (error) {
|
|
241
243
|
console.error(
|
|
242
|
-
`[framework][
|
|
244
|
+
`[framework][server-hook] Error loading server hook from ${file}:`,
|
|
243
245
|
error
|
|
244
246
|
);
|
|
245
247
|
return {
|
|
246
248
|
middlewares: [],
|
|
247
|
-
|
|
249
|
+
serverHook: null,
|
|
248
250
|
dynamic: "auto",
|
|
249
251
|
generateStaticParams: null
|
|
250
252
|
};
|
|
251
253
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
254
|
+
let middlewares = [];
|
|
255
|
+
const rawMiddlewares = mod?.[NAMING.BEFORE_MIDDLEWARES];
|
|
256
|
+
if (rawMiddlewares !== void 0) {
|
|
257
|
+
if (!Array.isArray(rawMiddlewares)) {
|
|
258
|
+
console.warn(
|
|
259
|
+
`[framework][server-hook] ${NAMING.BEFORE_MIDDLEWARES} must be an array in ${file}, ignoring invalid value`
|
|
260
|
+
);
|
|
261
|
+
} else {
|
|
262
|
+
for (let i = 0; i < rawMiddlewares.length; i++) {
|
|
263
|
+
const mw = rawMiddlewares[i];
|
|
264
|
+
if (typeof mw !== "function") {
|
|
265
|
+
console.warn(
|
|
266
|
+
`[framework][server-hook] Middleware at index ${i} in ${NAMING.BEFORE_MIDDLEWARES} is not a function in ${file}, skipping`
|
|
267
|
+
);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
middlewares.push(mw);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const serverHook = typeof mod?.[NAMING.GET_SERVER_DATA_FN] === "function" ? mod[NAMING.GET_SERVER_DATA_FN] : null;
|
|
256
275
|
const dynamic = mod?.[NAMING.RENDER_TYPE_CONST] === "force-static" || mod?.[NAMING.RENDER_TYPE_CONST] === "force-dynamic" ? mod.dynamic : "auto";
|
|
257
276
|
const generateStaticParams = typeof mod?.[NAMING.GENERATE_SSG_PARAMS] === "function" ? mod[NAMING.GENERATE_SSG_PARAMS] : null;
|
|
258
277
|
return {
|
|
259
278
|
middlewares,
|
|
260
|
-
|
|
279
|
+
serverHook,
|
|
261
280
|
dynamic,
|
|
262
281
|
generateStaticParams
|
|
263
282
|
};
|
|
264
283
|
}
|
|
284
|
+
function loadLayoutServerHook(layoutFile) {
|
|
285
|
+
const layoutDir = path3.dirname(layoutFile);
|
|
286
|
+
const layoutBasename = path3.basename(layoutFile, path3.extname(layoutFile));
|
|
287
|
+
const serverHookTs = path3.join(layoutDir, `${layoutBasename}.server.hook.ts`);
|
|
288
|
+
const serverHookJs = path3.join(layoutDir, `${layoutBasename}.server.hook.js`);
|
|
289
|
+
const file = fs3.existsSync(serverHookTs) ? serverHookTs : fs3.existsSync(serverHookJs) ? serverHookJs : null;
|
|
290
|
+
if (!file) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
if (file.endsWith(".ts") || file.endsWith(".tsx")) {
|
|
294
|
+
try {
|
|
295
|
+
__require("tsx/cjs");
|
|
296
|
+
} catch (e) {
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const mod = __require(file);
|
|
301
|
+
const serverHook = typeof mod?.getServerSideProps === "function" ? mod.getServerSideProps : null;
|
|
302
|
+
return serverHook;
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.error(
|
|
305
|
+
`[framework][server-hook] Error loading layout server hook from ${file}:`,
|
|
306
|
+
error
|
|
307
|
+
);
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
265
311
|
|
|
266
312
|
// modules/router/loader-pages.ts
|
|
313
|
+
function validateRoutes(routes, appDir) {
|
|
314
|
+
const routePatterns = /* @__PURE__ */ new Map();
|
|
315
|
+
const errors = [];
|
|
316
|
+
const warnings = [];
|
|
317
|
+
for (const route of routes) {
|
|
318
|
+
const existing = routePatterns.get(route.pattern) || [];
|
|
319
|
+
existing.push(route);
|
|
320
|
+
routePatterns.set(route.pattern, existing);
|
|
321
|
+
}
|
|
322
|
+
for (const [pattern, duplicateRoutes] of routePatterns.entries()) {
|
|
323
|
+
if (duplicateRoutes.length > 1) {
|
|
324
|
+
const files = duplicateRoutes.map(
|
|
325
|
+
(r) => r.pageFile ? path4.relative(appDir, r.pageFile) : "unknown"
|
|
326
|
+
).join(", ");
|
|
327
|
+
errors.push(
|
|
328
|
+
`Duplicate route pattern "${pattern}" found in multiple files:
|
|
329
|
+
${files}
|
|
330
|
+
\u{1F4A1} Suggestion: Ensure each route has a unique path pattern`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
for (const route of routes) {
|
|
335
|
+
if (!route.pageFile || !fs4.existsSync(route.pageFile)) {
|
|
336
|
+
warnings.push(
|
|
337
|
+
`Route pattern "${route.pattern}" references a missing page file`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (errors.length > 0) {
|
|
342
|
+
const errorMessage = [
|
|
343
|
+
"\u274C Route validation failed:",
|
|
344
|
+
"",
|
|
345
|
+
...errors,
|
|
346
|
+
"",
|
|
347
|
+
"\u{1F4A1} Please fix the errors above before starting the server."
|
|
348
|
+
].join("\n");
|
|
349
|
+
throw new Error(errorMessage);
|
|
350
|
+
}
|
|
351
|
+
if (warnings.length > 0 && process.env.NODE_ENV === "development") {
|
|
352
|
+
console.warn("\n\u26A0\uFE0F Route warnings:");
|
|
353
|
+
warnings.forEach((warning) => console.warn(` \u2022 ${warning}`));
|
|
354
|
+
console.warn("");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
267
357
|
function loadRoutes(appDir) {
|
|
268
358
|
if (!fs4.existsSync(appDir)) {
|
|
269
359
|
return [];
|
|
@@ -293,7 +383,12 @@ function loadRoutes(appDir) {
|
|
|
293
383
|
currentDir,
|
|
294
384
|
appDir
|
|
295
385
|
);
|
|
296
|
-
const
|
|
386
|
+
const layoutServerHooks = [];
|
|
387
|
+
for (const layoutFile of layoutFiles) {
|
|
388
|
+
const layoutServerHook = loadLayoutServerHook(layoutFile);
|
|
389
|
+
layoutServerHooks.push(layoutServerHook);
|
|
390
|
+
}
|
|
391
|
+
const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(currentDir);
|
|
297
392
|
routes.push({
|
|
298
393
|
pattern: routePath,
|
|
299
394
|
regex,
|
|
@@ -303,13 +398,17 @@ function loadRoutes(appDir) {
|
|
|
303
398
|
pageFile: fullPath,
|
|
304
399
|
layoutFiles,
|
|
305
400
|
middlewares,
|
|
306
|
-
loader,
|
|
401
|
+
loader: serverHook,
|
|
402
|
+
// Keep 'loader' field name for backward compatibility
|
|
403
|
+
layoutServerHooks,
|
|
404
|
+
// Server hooks for each layout (same order as layouts)
|
|
307
405
|
dynamic,
|
|
308
406
|
generateStaticParams
|
|
309
407
|
});
|
|
310
408
|
}
|
|
311
409
|
}
|
|
312
410
|
walk(appDir);
|
|
411
|
+
validateRoutes(routes, appDir);
|
|
313
412
|
return routes;
|
|
314
413
|
}
|
|
315
414
|
|
|
@@ -673,9 +772,12 @@ function writeClientBoostrapManifest(projectRoot) {
|
|
|
673
772
|
lines.push("");
|
|
674
773
|
lines.push(`import { bootstrapClient } from "@lolyjs/core/runtime"`);
|
|
675
774
|
lines.push("");
|
|
676
|
-
lines.push(
|
|
677
|
-
|
|
678
|
-
);
|
|
775
|
+
lines.push(`try {`);
|
|
776
|
+
lines.push(` bootstrapClient(routes as ClientRouteLoaded[], notFoundRoute, errorRoute);`);
|
|
777
|
+
lines.push(`} catch (error) {`);
|
|
778
|
+
lines.push(` console.error("[bootstrap] Fatal error during bootstrap:", error);`);
|
|
779
|
+
lines.push(` throw error;`);
|
|
780
|
+
lines.push(`}`);
|
|
679
781
|
fs7.writeFileSync(manifestPath, lines.join("\n"), "utf-8");
|
|
680
782
|
}
|
|
681
783
|
function writeRoutesManifest({
|
|
@@ -822,7 +924,12 @@ function loadRoutesFromManifest(projectRoot) {
|
|
|
822
924
|
(f) => path8.join(projectRoot, f)
|
|
823
925
|
);
|
|
824
926
|
const pageDir = path8.dirname(pageFile);
|
|
825
|
-
const
|
|
927
|
+
const layoutServerHooks = [];
|
|
928
|
+
for (const layoutFile of layoutFiles) {
|
|
929
|
+
const layoutServerHook = loadLayoutServerHook(layoutFile);
|
|
930
|
+
layoutServerHooks.push(layoutServerHook);
|
|
931
|
+
}
|
|
932
|
+
const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(pageDir);
|
|
826
933
|
pageRoutes.push({
|
|
827
934
|
pattern: entry.pattern,
|
|
828
935
|
regex,
|
|
@@ -832,7 +939,10 @@ function loadRoutesFromManifest(projectRoot) {
|
|
|
832
939
|
pageFile,
|
|
833
940
|
layoutFiles,
|
|
834
941
|
middlewares,
|
|
835
|
-
loader,
|
|
942
|
+
loader: serverHook,
|
|
943
|
+
// Keep 'loader' field name for backward compatibility
|
|
944
|
+
layoutServerHooks,
|
|
945
|
+
// Server hooks for each layout (same order as layouts)
|
|
836
946
|
dynamic: entry.dynamic ?? dynamic,
|
|
837
947
|
generateStaticParams
|
|
838
948
|
});
|
|
@@ -1005,24 +1115,210 @@ function loadWssRoutes(appDir) {
|
|
|
1005
1115
|
}
|
|
1006
1116
|
|
|
1007
1117
|
// modules/router/route-loader.ts
|
|
1118
|
+
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
1119
|
+
"node_modules",
|
|
1120
|
+
".git",
|
|
1121
|
+
".loly",
|
|
1122
|
+
"dist",
|
|
1123
|
+
"build",
|
|
1124
|
+
".next",
|
|
1125
|
+
".cache",
|
|
1126
|
+
"coverage",
|
|
1127
|
+
".vscode",
|
|
1128
|
+
".idea",
|
|
1129
|
+
".turbo",
|
|
1130
|
+
".swc"
|
|
1131
|
+
]);
|
|
1132
|
+
function getRelevantFiles(appDir, projectRoot) {
|
|
1133
|
+
const files = [];
|
|
1134
|
+
const projectRootNormalized = path10.resolve(projectRoot);
|
|
1135
|
+
function walk(currentDir) {
|
|
1136
|
+
if (!fs10.existsSync(currentDir)) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const entries = fs10.readdirSync(currentDir, { withFileTypes: true });
|
|
1140
|
+
for (const entry of entries) {
|
|
1141
|
+
const fullPath = path10.join(currentDir, entry.name);
|
|
1142
|
+
if (entry.isDirectory()) {
|
|
1143
|
+
if (SKIP_DIRECTORIES.has(entry.name)) {
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
if (entry.name.startsWith(".")) {
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
walk(fullPath);
|
|
1150
|
+
} else {
|
|
1151
|
+
const ext = path10.extname(entry.name);
|
|
1152
|
+
if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
|
|
1153
|
+
files.push(fullPath);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
walk(projectRootNormalized);
|
|
1159
|
+
return files;
|
|
1160
|
+
}
|
|
1161
|
+
function hasFilesChanged(appDir, projectRoot, cachedStats) {
|
|
1162
|
+
const currentFiles = getRelevantFiles(appDir, projectRoot);
|
|
1163
|
+
const currentFilesSet = new Set(currentFiles);
|
|
1164
|
+
for (const [filePath] of cachedStats.entries()) {
|
|
1165
|
+
if (!currentFilesSet.has(filePath)) {
|
|
1166
|
+
return true;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
for (const filePath of currentFiles) {
|
|
1170
|
+
if (!fs10.existsSync(filePath)) {
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
const stats = fs10.statSync(filePath);
|
|
1174
|
+
const cachedStat = cachedStats.get(filePath);
|
|
1175
|
+
if (!cachedStat) {
|
|
1176
|
+
return true;
|
|
1177
|
+
}
|
|
1178
|
+
if (stats.mtimeMs !== cachedStat.mtime || stats.size !== cachedStat.size) {
|
|
1179
|
+
return true;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return false;
|
|
1183
|
+
}
|
|
1184
|
+
function buildFileStats(files) {
|
|
1185
|
+
const statsMap = /* @__PURE__ */ new Map();
|
|
1186
|
+
for (const filePath of files) {
|
|
1187
|
+
if (fs10.existsSync(filePath)) {
|
|
1188
|
+
const stats = fs10.statSync(filePath);
|
|
1189
|
+
statsMap.set(filePath, {
|
|
1190
|
+
mtime: stats.mtimeMs,
|
|
1191
|
+
size: stats.size
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return statsMap;
|
|
1196
|
+
}
|
|
1008
1197
|
var FilesystemRouteLoader = class {
|
|
1009
|
-
|
|
1198
|
+
// Maximum cache age in ms (1 second fallback)
|
|
1199
|
+
constructor(appDir, projectRoot = appDir) {
|
|
1010
1200
|
this.appDir = appDir;
|
|
1201
|
+
this.projectRoot = projectRoot;
|
|
1202
|
+
this.cache = null;
|
|
1203
|
+
this.cacheMaxAge = 1e3;
|
|
1204
|
+
if (this.projectRoot === this.appDir) {
|
|
1205
|
+
let current = path10.resolve(this.appDir);
|
|
1206
|
+
while (current !== path10.dirname(current)) {
|
|
1207
|
+
if (fs10.existsSync(path10.join(current, "package.json"))) {
|
|
1208
|
+
this.projectRoot = current;
|
|
1209
|
+
break;
|
|
1210
|
+
}
|
|
1211
|
+
current = path10.dirname(current);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Invalidates the cache, forcing a reload on next access.
|
|
1217
|
+
*/
|
|
1218
|
+
invalidateCache() {
|
|
1219
|
+
this.cache = null;
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Checks if cache is still valid, invalidates if files changed.
|
|
1223
|
+
*/
|
|
1224
|
+
ensureCacheValid() {
|
|
1225
|
+
if (!this.cache) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const now = Date.now();
|
|
1229
|
+
if (now - this.cache.timestamp > this.cacheMaxAge) {
|
|
1230
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats)) {
|
|
1231
|
+
this.cache = null;
|
|
1232
|
+
} else {
|
|
1233
|
+
this.cache.timestamp = now;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1011
1236
|
}
|
|
1012
1237
|
loadRoutes() {
|
|
1013
|
-
|
|
1238
|
+
this.ensureCacheValid();
|
|
1239
|
+
if (!this.cache || hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats)) {
|
|
1240
|
+
const routes = loadRoutes(this.appDir);
|
|
1241
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1242
|
+
const fileStats = buildFileStats(files);
|
|
1243
|
+
this.cache = {
|
|
1244
|
+
routes,
|
|
1245
|
+
apiRoutes: this.cache?.apiRoutes || [],
|
|
1246
|
+
wssRoutes: this.cache?.wssRoutes || [],
|
|
1247
|
+
notFoundRoute: this.cache?.notFoundRoute ?? null,
|
|
1248
|
+
errorRoute: this.cache?.errorRoute ?? null,
|
|
1249
|
+
fileStats,
|
|
1250
|
+
timestamp: Date.now()
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
return this.cache.routes;
|
|
1014
1254
|
}
|
|
1015
1255
|
loadApiRoutes() {
|
|
1016
|
-
|
|
1256
|
+
this.ensureCacheValid();
|
|
1257
|
+
if (!this.cache) {
|
|
1258
|
+
this.loadRoutes();
|
|
1259
|
+
}
|
|
1260
|
+
if (!this.cache) {
|
|
1261
|
+
throw new Error("Failed to initialize route cache");
|
|
1262
|
+
}
|
|
1263
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats) || this.cache.apiRoutes.length === 0) {
|
|
1264
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1265
|
+
const fileStats = buildFileStats(files);
|
|
1266
|
+
this.cache.apiRoutes = loadApiRoutes(this.appDir);
|
|
1267
|
+
this.cache.fileStats = fileStats;
|
|
1268
|
+
this.cache.timestamp = Date.now();
|
|
1269
|
+
}
|
|
1270
|
+
return this.cache.apiRoutes;
|
|
1017
1271
|
}
|
|
1018
1272
|
loadWssRoutes() {
|
|
1019
|
-
|
|
1273
|
+
this.ensureCacheValid();
|
|
1274
|
+
if (!this.cache) {
|
|
1275
|
+
this.loadRoutes();
|
|
1276
|
+
}
|
|
1277
|
+
if (!this.cache) {
|
|
1278
|
+
throw new Error("Failed to initialize route cache");
|
|
1279
|
+
}
|
|
1280
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats) || this.cache.wssRoutes.length === 0) {
|
|
1281
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1282
|
+
const fileStats = buildFileStats(files);
|
|
1283
|
+
this.cache.wssRoutes = loadWssRoutes(this.appDir);
|
|
1284
|
+
this.cache.fileStats = fileStats;
|
|
1285
|
+
this.cache.timestamp = Date.now();
|
|
1286
|
+
}
|
|
1287
|
+
return this.cache.wssRoutes;
|
|
1020
1288
|
}
|
|
1021
1289
|
loadNotFoundRoute() {
|
|
1022
|
-
|
|
1290
|
+
this.ensureCacheValid();
|
|
1291
|
+
if (!this.cache) {
|
|
1292
|
+
this.loadRoutes();
|
|
1293
|
+
}
|
|
1294
|
+
if (!this.cache) {
|
|
1295
|
+
throw new Error("Failed to initialize route cache");
|
|
1296
|
+
}
|
|
1297
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats) || this.cache.notFoundRoute === void 0) {
|
|
1298
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1299
|
+
const fileStats = buildFileStats(files);
|
|
1300
|
+
this.cache.notFoundRoute = loadNotFoundRouteFromFilesystem(this.appDir);
|
|
1301
|
+
this.cache.fileStats = fileStats;
|
|
1302
|
+
this.cache.timestamp = Date.now();
|
|
1303
|
+
}
|
|
1304
|
+
return this.cache.notFoundRoute;
|
|
1023
1305
|
}
|
|
1024
1306
|
loadErrorRoute() {
|
|
1025
|
-
|
|
1307
|
+
this.ensureCacheValid();
|
|
1308
|
+
if (!this.cache) {
|
|
1309
|
+
this.loadRoutes();
|
|
1310
|
+
}
|
|
1311
|
+
if (!this.cache) {
|
|
1312
|
+
throw new Error("Failed to initialize route cache");
|
|
1313
|
+
}
|
|
1314
|
+
if (hasFilesChanged(this.appDir, this.projectRoot, this.cache.fileStats) || this.cache.errorRoute === void 0) {
|
|
1315
|
+
const files = getRelevantFiles(this.appDir, this.projectRoot);
|
|
1316
|
+
const fileStats = buildFileStats(files);
|
|
1317
|
+
this.cache.errorRoute = loadErrorRouteFromFilesystem(this.appDir);
|
|
1318
|
+
this.cache.fileStats = fileStats;
|
|
1319
|
+
this.cache.timestamp = Date.now();
|
|
1320
|
+
}
|
|
1321
|
+
return this.cache.errorRoute;
|
|
1026
1322
|
}
|
|
1027
1323
|
loadRouteChunks() {
|
|
1028
1324
|
return {};
|
|
@@ -1031,27 +1327,67 @@ var FilesystemRouteLoader = class {
|
|
|
1031
1327
|
var ManifestRouteLoader = class {
|
|
1032
1328
|
constructor(projectRoot) {
|
|
1033
1329
|
this.projectRoot = projectRoot;
|
|
1330
|
+
this.cache = {};
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Gets the manifest, using cache if available.
|
|
1334
|
+
* The manifest is read once and cached for the lifetime of the loader.
|
|
1335
|
+
*/
|
|
1336
|
+
getManifest() {
|
|
1337
|
+
if (this.cache.manifest !== void 0) {
|
|
1338
|
+
return this.cache.manifest;
|
|
1339
|
+
}
|
|
1340
|
+
const manifest = readManifest(this.projectRoot);
|
|
1341
|
+
this.cache.manifest = manifest;
|
|
1342
|
+
return manifest;
|
|
1034
1343
|
}
|
|
1035
1344
|
loadRoutes() {
|
|
1345
|
+
if (this.cache.routes) {
|
|
1346
|
+
return this.cache.routes;
|
|
1347
|
+
}
|
|
1036
1348
|
const { routes } = loadRoutesFromManifest(this.projectRoot);
|
|
1349
|
+
this.cache.routes = routes;
|
|
1037
1350
|
return routes;
|
|
1038
1351
|
}
|
|
1039
1352
|
loadApiRoutes() {
|
|
1353
|
+
if (this.cache.apiRoutes) {
|
|
1354
|
+
return this.cache.apiRoutes;
|
|
1355
|
+
}
|
|
1040
1356
|
const { apiRoutes } = loadRoutesFromManifest(this.projectRoot);
|
|
1357
|
+
this.cache.apiRoutes = apiRoutes;
|
|
1041
1358
|
return apiRoutes;
|
|
1042
1359
|
}
|
|
1043
1360
|
loadWssRoutes() {
|
|
1361
|
+
if (this.cache.wssRoutes) {
|
|
1362
|
+
return this.cache.wssRoutes;
|
|
1363
|
+
}
|
|
1044
1364
|
const { wssRoutes } = loadRoutesFromManifest(this.projectRoot);
|
|
1365
|
+
this.cache.wssRoutes = wssRoutes;
|
|
1045
1366
|
return wssRoutes;
|
|
1046
1367
|
}
|
|
1047
1368
|
loadNotFoundRoute() {
|
|
1048
|
-
|
|
1369
|
+
if (this.cache.notFoundRoute !== void 0) {
|
|
1370
|
+
return this.cache.notFoundRoute;
|
|
1371
|
+
}
|
|
1372
|
+
const route = loadNotFoundFromManifest(this.projectRoot);
|
|
1373
|
+
this.cache.notFoundRoute = route;
|
|
1374
|
+
return route;
|
|
1049
1375
|
}
|
|
1050
1376
|
loadErrorRoute() {
|
|
1051
|
-
|
|
1377
|
+
if (this.cache.errorRoute !== void 0) {
|
|
1378
|
+
return this.cache.errorRoute;
|
|
1379
|
+
}
|
|
1380
|
+
const route = loadErrorFromManifest(this.projectRoot);
|
|
1381
|
+
this.cache.errorRoute = route;
|
|
1382
|
+
return route;
|
|
1052
1383
|
}
|
|
1053
1384
|
loadRouteChunks() {
|
|
1054
|
-
|
|
1385
|
+
if (this.cache.routeChunks) {
|
|
1386
|
+
return this.cache.routeChunks;
|
|
1387
|
+
}
|
|
1388
|
+
const chunks = loadChunksFromManifest(this.projectRoot);
|
|
1389
|
+
this.cache.routeChunks = chunks;
|
|
1390
|
+
return chunks;
|
|
1055
1391
|
}
|
|
1056
1392
|
};
|
|
1057
1393
|
function loadNotFoundRouteFromFilesystem(appDir) {
|
|
@@ -1086,7 +1422,12 @@ function loadNotFoundRouteFromFilesystem(appDir) {
|
|
|
1086
1422
|
notFoundDir,
|
|
1087
1423
|
appDir
|
|
1088
1424
|
);
|
|
1089
|
-
const
|
|
1425
|
+
const layoutServerHooks = [];
|
|
1426
|
+
for (const layoutFile of layoutFiles) {
|
|
1427
|
+
const layoutServerHook = loadLayoutServerHook(layoutFile);
|
|
1428
|
+
layoutServerHooks.push(layoutServerHook);
|
|
1429
|
+
}
|
|
1430
|
+
const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(notFoundDir);
|
|
1090
1431
|
return {
|
|
1091
1432
|
pattern: NOT_FOUND_PATTERN,
|
|
1092
1433
|
regex: new RegExp(`^${NOT_FOUND_PATTERN}/?$`),
|
|
@@ -1096,7 +1437,10 @@ function loadNotFoundRouteFromFilesystem(appDir) {
|
|
|
1096
1437
|
pageFile: notFoundFile,
|
|
1097
1438
|
layoutFiles,
|
|
1098
1439
|
middlewares,
|
|
1099
|
-
loader,
|
|
1440
|
+
loader: serverHook,
|
|
1441
|
+
// Keep 'loader' field name for backward compatibility
|
|
1442
|
+
layoutServerHooks,
|
|
1443
|
+
// Server hooks for each layout (same order as layouts)
|
|
1100
1444
|
dynamic,
|
|
1101
1445
|
generateStaticParams
|
|
1102
1446
|
};
|
|
@@ -1127,7 +1471,12 @@ function loadErrorRouteFromFilesystem(appDir) {
|
|
|
1127
1471
|
appDir,
|
|
1128
1472
|
appDir
|
|
1129
1473
|
);
|
|
1130
|
-
const
|
|
1474
|
+
const layoutServerHooks = [];
|
|
1475
|
+
for (const layoutFile of layoutFiles) {
|
|
1476
|
+
const layoutServerHook = loadLayoutServerHook(layoutFile);
|
|
1477
|
+
layoutServerHooks.push(layoutServerHook);
|
|
1478
|
+
}
|
|
1479
|
+
const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(appDir);
|
|
1131
1480
|
return {
|
|
1132
1481
|
pattern: ERROR_PATTERN,
|
|
1133
1482
|
regex: new RegExp(`^${ERROR_PATTERN}/?$`),
|
|
@@ -1137,7 +1486,10 @@ function loadErrorRouteFromFilesystem(appDir) {
|
|
|
1137
1486
|
pageFile: errorFile,
|
|
1138
1487
|
layoutFiles,
|
|
1139
1488
|
middlewares,
|
|
1140
|
-
loader,
|
|
1489
|
+
loader: serverHook,
|
|
1490
|
+
// Keep 'loader' field name for backward compatibility
|
|
1491
|
+
layoutServerHooks,
|
|
1492
|
+
// Server hooks for each layout (same order as layouts)
|
|
1141
1493
|
dynamic,
|
|
1142
1494
|
generateStaticParams
|
|
1143
1495
|
};
|
|
@@ -1168,7 +1520,8 @@ function loadAliasesFromTsconfig(projectRoot) {
|
|
|
1168
1520
|
try {
|
|
1169
1521
|
tsconfig = JSON.parse(fs11.readFileSync(tsconfigPath, "utf-8"));
|
|
1170
1522
|
} catch (err) {
|
|
1171
|
-
console.warn("[framework] Could not read tsconfig.json:", err);
|
|
1523
|
+
console.warn("\u26A0\uFE0F [framework] Could not read tsconfig.json:", err instanceof Error ? err.message : String(err));
|
|
1524
|
+
console.warn("\u{1F4A1} Using default path aliases. For custom aliases, ensure tsconfig.json is valid.");
|
|
1172
1525
|
aliases["@app"] = path11.resolve(projectRoot, "app");
|
|
1173
1526
|
return aliases;
|
|
1174
1527
|
}
|
|
@@ -1222,7 +1575,7 @@ function copyStaticAssets(projectRoot, outDir) {
|
|
|
1222
1575
|
}
|
|
1223
1576
|
}
|
|
1224
1577
|
}
|
|
1225
|
-
function generateAssetManifest(outDir) {
|
|
1578
|
+
function generateAssetManifest(outDir, stats) {
|
|
1226
1579
|
const manifest = {
|
|
1227
1580
|
client: {
|
|
1228
1581
|
js: "client.js",
|
|
@@ -1234,27 +1587,128 @@ function generateAssetManifest(outDir) {
|
|
|
1234
1587
|
return manifest;
|
|
1235
1588
|
}
|
|
1236
1589
|
const files = fs11.readdirSync(outDir);
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1590
|
+
if (stats) {
|
|
1591
|
+
try {
|
|
1592
|
+
const statsJson = stats.toJson({
|
|
1593
|
+
all: false,
|
|
1594
|
+
entrypoints: true,
|
|
1595
|
+
assets: true,
|
|
1596
|
+
chunks: true,
|
|
1597
|
+
chunkRelations: true
|
|
1598
|
+
// Include chunk dependencies
|
|
1599
|
+
});
|
|
1600
|
+
const clientEntrypoint = statsJson.entrypoints?.client;
|
|
1601
|
+
if (clientEntrypoint?.assets) {
|
|
1602
|
+
const clientJsFiles = clientEntrypoint.assets.map((asset) => typeof asset === "string" ? asset : asset.name).filter((name) => name.endsWith(".js"));
|
|
1603
|
+
if (statsJson.chunks && clientEntrypoint.chunks) {
|
|
1604
|
+
const entrypointChunkIds = new Set(
|
|
1605
|
+
Array.isArray(clientEntrypoint.chunks) ? clientEntrypoint.chunks : [clientEntrypoint.chunks]
|
|
1606
|
+
);
|
|
1607
|
+
const dependencyChunks = [];
|
|
1608
|
+
for (const chunk of statsJson.chunks) {
|
|
1609
|
+
if (chunk.id && entrypointChunkIds.has(chunk.id)) {
|
|
1610
|
+
if (chunk.files) {
|
|
1611
|
+
const jsFiles = chunk.files.filter((f) => f.endsWith(".js")).filter((f) => !clientJsFiles.includes(f));
|
|
1612
|
+
dependencyChunks.push(...jsFiles);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
const visitedChunkIds = new Set(entrypointChunkIds);
|
|
1617
|
+
const chunksToCheck = Array.from(entrypointChunkIds);
|
|
1618
|
+
while (chunksToCheck.length > 0) {
|
|
1619
|
+
const chunkId = chunksToCheck.shift();
|
|
1620
|
+
if (!chunkId) continue;
|
|
1621
|
+
const chunk = statsJson.chunks?.find((c) => c.id === chunkId);
|
|
1622
|
+
if (chunk?.children) {
|
|
1623
|
+
const children = Array.isArray(chunk.children) ? chunk.children : [chunk.children];
|
|
1624
|
+
for (const childId of children) {
|
|
1625
|
+
if (!visitedChunkIds.has(childId)) {
|
|
1626
|
+
visitedChunkIds.add(childId);
|
|
1627
|
+
chunksToCheck.push(childId);
|
|
1628
|
+
const childChunk = statsJson.chunks?.find((c) => c.id === childId);
|
|
1629
|
+
if (childChunk?.files) {
|
|
1630
|
+
const jsFiles = childChunk.files.filter((f) => f.endsWith(".js")).filter((f) => !clientJsFiles.includes(f) && !dependencyChunks.includes(f));
|
|
1631
|
+
dependencyChunks.push(...jsFiles);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
if (dependencyChunks.length > 0) {
|
|
1638
|
+
clientJsFiles.splice(-1, 0, ...dependencyChunks);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (clientJsFiles.length > 0) {
|
|
1642
|
+
manifest.entrypoints = {
|
|
1643
|
+
client: clientJsFiles
|
|
1644
|
+
};
|
|
1645
|
+
manifest.client.js = clientJsFiles[clientJsFiles.length - 1];
|
|
1646
|
+
const clientCssFiles = clientEntrypoint.assets.map((asset) => typeof asset === "string" ? asset : asset.name).filter((name) => name.endsWith(".css"));
|
|
1647
|
+
if (clientCssFiles.length > 0) {
|
|
1648
|
+
manifest.client.css = clientCssFiles[0];
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
} catch (err) {
|
|
1653
|
+
console.warn("[framework] Failed to extract entrypoints from stats, falling back to file scanning:", err);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
if (!manifest.client.js) {
|
|
1657
|
+
const clientJsMatch = files.find((f) => /^client\.[\w-]+\.js$/.test(f) || f === "client.js");
|
|
1658
|
+
if (clientJsMatch) {
|
|
1659
|
+
manifest.client.js = clientJsMatch;
|
|
1660
|
+
}
|
|
1240
1661
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1662
|
+
if (!manifest.client.css) {
|
|
1663
|
+
const clientCssMatch = files.find((f) => /^client\.[\w-]+\.css$/.test(f) || f === "client.css");
|
|
1664
|
+
if (clientCssMatch) {
|
|
1665
|
+
manifest.client.css = clientCssMatch;
|
|
1666
|
+
}
|
|
1244
1667
|
}
|
|
1668
|
+
const sharedChunksToAdd = [];
|
|
1245
1669
|
for (const file of files) {
|
|
1246
1670
|
if (!file.endsWith(".js")) continue;
|
|
1247
1671
|
if (file === manifest.client.js) continue;
|
|
1672
|
+
if (manifest.entrypoints?.client?.includes(file)) continue;
|
|
1248
1673
|
const routeMatch = file.match(/^(route-[^.]+)(\.[\w-]+)?\.js$/);
|
|
1249
1674
|
if (routeMatch) {
|
|
1250
1675
|
const chunkName = routeMatch[1];
|
|
1251
1676
|
manifest.chunks[chunkName] = file;
|
|
1252
1677
|
continue;
|
|
1253
1678
|
}
|
|
1679
|
+
const vendorMatch = file.match(/^(vendor)(\.[\w-]+)?\.js$/);
|
|
1680
|
+
if (vendorMatch) {
|
|
1681
|
+
const chunkName = vendorMatch[1];
|
|
1682
|
+
manifest.chunks[chunkName] = file;
|
|
1683
|
+
sharedChunksToAdd.push(file);
|
|
1684
|
+
continue;
|
|
1685
|
+
}
|
|
1686
|
+
const vendorCommonsMatch = file.match(/^(vendor-commons)(\.[\w-]+)?\.js$/);
|
|
1687
|
+
if (vendorCommonsMatch) {
|
|
1688
|
+
const chunkName = vendorCommonsMatch[1];
|
|
1689
|
+
manifest.chunks[chunkName] = file;
|
|
1690
|
+
sharedChunksToAdd.push(file);
|
|
1691
|
+
continue;
|
|
1692
|
+
}
|
|
1254
1693
|
const numericMatch = file.match(/^(\d+)(\.[\w-]+)?\.js$/);
|
|
1255
1694
|
if (numericMatch) {
|
|
1256
1695
|
const chunkName = numericMatch[1];
|
|
1257
1696
|
manifest.chunks[chunkName] = file;
|
|
1697
|
+
sharedChunksToAdd.push(file);
|
|
1698
|
+
continue;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
if (sharedChunksToAdd.length > 0 && manifest.entrypoints?.client) {
|
|
1702
|
+
const entrypoints = manifest.entrypoints.client;
|
|
1703
|
+
const mainEntry = entrypoints[entrypoints.length - 1];
|
|
1704
|
+
const uniqueShared = sharedChunksToAdd.filter((f) => !entrypoints.includes(f));
|
|
1705
|
+
entrypoints.splice(-1, 0, ...uniqueShared);
|
|
1706
|
+
if (entrypoints[entrypoints.length - 1] !== mainEntry) {
|
|
1707
|
+
const mainIndex = entrypoints.indexOf(mainEntry);
|
|
1708
|
+
if (mainIndex >= 0) {
|
|
1709
|
+
entrypoints.splice(mainIndex, 1);
|
|
1710
|
+
}
|
|
1711
|
+
entrypoints.push(mainEntry);
|
|
1258
1712
|
}
|
|
1259
1713
|
}
|
|
1260
1714
|
return manifest;
|
|
@@ -1294,9 +1748,7 @@ function createClientConfig(projectRoot, mode) {
|
|
|
1294
1748
|
const outDir = path12.join(buildDir, "client");
|
|
1295
1749
|
const envPath2 = path12.join(projectRoot, ".env");
|
|
1296
1750
|
if (fs12.existsSync(envPath2)) {
|
|
1297
|
-
dotenv.config({
|
|
1298
|
-
path: envPath2
|
|
1299
|
-
});
|
|
1751
|
+
dotenv.config({ path: envPath2 });
|
|
1300
1752
|
}
|
|
1301
1753
|
const publicEnv = {};
|
|
1302
1754
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -1355,14 +1807,57 @@ function createClientConfig(projectRoot, mode) {
|
|
|
1355
1807
|
},
|
|
1356
1808
|
plugins: [
|
|
1357
1809
|
new rspack.DefinePlugin({
|
|
1358
|
-
//
|
|
1359
|
-
|
|
1810
|
+
// Use mode directly to ensure development mode is correctly set
|
|
1811
|
+
// This replaces process.env.NODE_ENV in the client bundle with the literal string value
|
|
1812
|
+
"process.env.NODE_ENV": JSON.stringify(mode),
|
|
1360
1813
|
...publicEnv
|
|
1361
1814
|
}),
|
|
1362
1815
|
new rspack.CssExtractRspackPlugin({
|
|
1363
1816
|
filename: mode === "production" ? "client.[contenthash].css" : "client.css"
|
|
1364
1817
|
})
|
|
1365
1818
|
],
|
|
1819
|
+
optimization: mode === "production" ? {
|
|
1820
|
+
usedExports: true,
|
|
1821
|
+
sideEffects: false,
|
|
1822
|
+
// More aggressive tree shaking - assume no side effects
|
|
1823
|
+
providedExports: true,
|
|
1824
|
+
concatenateModules: true,
|
|
1825
|
+
// Better for tree shaking
|
|
1826
|
+
minimize: true,
|
|
1827
|
+
removeEmptyChunks: true,
|
|
1828
|
+
mergeDuplicateChunks: true,
|
|
1829
|
+
// Improved code splitting: separate vendor chunks for better caching
|
|
1830
|
+
splitChunks: {
|
|
1831
|
+
chunks: "all",
|
|
1832
|
+
cacheGroups: {
|
|
1833
|
+
// Separate React/React-DOM into dedicated vendor chunk
|
|
1834
|
+
// This improves caching: React rarely changes, so users don't need to re-download it
|
|
1835
|
+
vendor: {
|
|
1836
|
+
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
|
|
1837
|
+
name: "vendor",
|
|
1838
|
+
priority: 30,
|
|
1839
|
+
enforce: true,
|
|
1840
|
+
// Force separation even if only used once
|
|
1841
|
+
reuseExistingChunk: true
|
|
1842
|
+
},
|
|
1843
|
+
// Other node_modules dependencies in a separate chunk
|
|
1844
|
+
vendorCommons: {
|
|
1845
|
+
test: /[\\/]node_modules[\\/](?!(react|react-dom|scheduler)[\\/])/,
|
|
1846
|
+
name: "vendor-commons",
|
|
1847
|
+
priority: 10,
|
|
1848
|
+
minChunks: 2,
|
|
1849
|
+
// Only create if used in 2+ chunks
|
|
1850
|
+
reuseExistingChunk: true
|
|
1851
|
+
},
|
|
1852
|
+
// Default: shared application code (not in node_modules)
|
|
1853
|
+
default: {
|
|
1854
|
+
minChunks: 2,
|
|
1855
|
+
priority: 5,
|
|
1856
|
+
reuseExistingChunk: true
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
} : void 0,
|
|
1366
1861
|
infrastructureLogging: {
|
|
1367
1862
|
level: "error"
|
|
1368
1863
|
},
|
|
@@ -1391,7 +1886,13 @@ function startClientBundler(projectRoot, mode = "development") {
|
|
|
1391
1886
|
});
|
|
1392
1887
|
compiler.watch({}, (err, stats) => {
|
|
1393
1888
|
if (err) {
|
|
1394
|
-
console.error("[framework][client] Rspack error:"
|
|
1889
|
+
console.error("\n\u274C [framework][client] Rspack compilation error:");
|
|
1890
|
+
console.error(err);
|
|
1891
|
+
console.error("\n\u{1F4A1} Suggestions:");
|
|
1892
|
+
console.error(" \u2022 Check for syntax errors in your code");
|
|
1893
|
+
console.error(" \u2022 Verify all imports are correct");
|
|
1894
|
+
console.error(" \u2022 Ensure all dependencies are installed");
|
|
1895
|
+
console.error(" \u2022 Try deleting .loly folder and rebuilding\n");
|
|
1395
1896
|
isBuilding = false;
|
|
1396
1897
|
lastBuildTime = Date.now();
|
|
1397
1898
|
if (buildResolve) {
|
|
@@ -1402,17 +1903,20 @@ function startClientBundler(projectRoot, mode = "development") {
|
|
|
1402
1903
|
return;
|
|
1403
1904
|
}
|
|
1404
1905
|
if (!stats) {
|
|
1906
|
+
console.warn("\u26A0\uFE0F [framework][client] Build completed but no stats available");
|
|
1405
1907
|
isBuilding = false;
|
|
1406
1908
|
lastBuildTime = Date.now();
|
|
1407
1909
|
return;
|
|
1408
1910
|
}
|
|
1409
1911
|
if (stats.hasErrors()) {
|
|
1410
|
-
console.error(
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
);
|
|
1912
|
+
console.error("\n\u274C [framework][client] Build failed with errors:\n");
|
|
1913
|
+
console.error(stats.toString("errors-only"));
|
|
1914
|
+
console.error("\n\u{1F4A1} Common fixes:");
|
|
1915
|
+
console.error(" \u2022 Fix syntax errors shown above");
|
|
1916
|
+
console.error(" \u2022 Check for missing imports or dependencies");
|
|
1917
|
+
console.error(" \u2022 Verify TypeScript types are correct\n");
|
|
1414
1918
|
} else {
|
|
1415
|
-
console.log("[framework][client]
|
|
1919
|
+
console.log("\u2705 [framework][client] Client bundle rebuilt successfully");
|
|
1416
1920
|
}
|
|
1417
1921
|
isBuilding = false;
|
|
1418
1922
|
lastBuildTime = Date.now();
|
|
@@ -1447,23 +1951,33 @@ function buildClientBundle(projectRoot) {
|
|
|
1447
1951
|
compiler.close(() => {
|
|
1448
1952
|
});
|
|
1449
1953
|
if (err) {
|
|
1450
|
-
console.error("[framework][client]
|
|
1954
|
+
console.error("\n\u274C [framework][client] Production build error:");
|
|
1955
|
+
console.error(err);
|
|
1956
|
+
console.error("\n\u{1F4A1} Suggestions:");
|
|
1957
|
+
console.error(" \u2022 Check for syntax errors in your code");
|
|
1958
|
+
console.error(" \u2022 Verify all imports are correct");
|
|
1959
|
+
console.error(" \u2022 Ensure all dependencies are installed");
|
|
1960
|
+
console.error(" \u2022 Review the error details above\n");
|
|
1451
1961
|
return reject(err);
|
|
1452
1962
|
}
|
|
1453
1963
|
if (!stats) {
|
|
1454
|
-
const error = new Error("No stats from Rspack");
|
|
1455
|
-
console.error("[framework][client] Build error:", error);
|
|
1964
|
+
const error = new Error("No stats from Rspack - build may have failed silently");
|
|
1965
|
+
console.error("\n\u274C [framework][client] Build error:", error.message);
|
|
1966
|
+
console.error("\u{1F4A1} Try rebuilding or check Rspack configuration\n");
|
|
1456
1967
|
return reject(error);
|
|
1457
1968
|
}
|
|
1458
1969
|
if (stats.hasErrors()) {
|
|
1459
|
-
console.error(
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
);
|
|
1463
|
-
|
|
1970
|
+
console.error("\n\u274C [framework][client] Production build failed:\n");
|
|
1971
|
+
console.error(stats.toString("errors-only"));
|
|
1972
|
+
console.error("\n\u{1F4A1} Common fixes:");
|
|
1973
|
+
console.error(" \u2022 Fix syntax errors shown above");
|
|
1974
|
+
console.error(" \u2022 Check for missing imports or dependencies");
|
|
1975
|
+
console.error(" \u2022 Verify TypeScript types are correct");
|
|
1976
|
+
console.error(" \u2022 Review build configuration\n");
|
|
1977
|
+
return reject(new Error("Client build failed - see errors above"));
|
|
1464
1978
|
}
|
|
1465
1979
|
copyStaticAssets(projectRoot, outDir);
|
|
1466
|
-
const assetManifest = generateAssetManifest(outDir);
|
|
1980
|
+
const assetManifest = generateAssetManifest(outDir, stats);
|
|
1467
1981
|
const manifestPath = path13.join(projectRoot, BUILD_FOLDER_NAME, "asset-manifest.json");
|
|
1468
1982
|
fs13.writeFileSync(manifestPath, JSON.stringify(assetManifest, null, 2), "utf-8");
|
|
1469
1983
|
resolve3({ outDir });
|
|
@@ -1547,7 +2061,7 @@ var ReaddirpStream = class extends Readable {
|
|
|
1547
2061
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
1548
2062
|
const statMethod = opts.lstat ? lstat : stat;
|
|
1549
2063
|
if (wantBigintFsStats) {
|
|
1550
|
-
this._stat = (
|
|
2064
|
+
this._stat = (path28) => statMethod(path28, { bigint: true });
|
|
1551
2065
|
} else {
|
|
1552
2066
|
this._stat = statMethod;
|
|
1553
2067
|
}
|
|
@@ -1572,8 +2086,8 @@ var ReaddirpStream = class extends Readable {
|
|
|
1572
2086
|
const par = this.parent;
|
|
1573
2087
|
const fil = par && par.files;
|
|
1574
2088
|
if (fil && fil.length > 0) {
|
|
1575
|
-
const { path:
|
|
1576
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
2089
|
+
const { path: path28, depth } = par;
|
|
2090
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path28));
|
|
1577
2091
|
const awaited = await Promise.all(slice);
|
|
1578
2092
|
for (const entry of awaited) {
|
|
1579
2093
|
if (!entry)
|
|
@@ -1613,20 +2127,20 @@ var ReaddirpStream = class extends Readable {
|
|
|
1613
2127
|
this.reading = false;
|
|
1614
2128
|
}
|
|
1615
2129
|
}
|
|
1616
|
-
async _exploreDir(
|
|
2130
|
+
async _exploreDir(path28, depth) {
|
|
1617
2131
|
let files;
|
|
1618
2132
|
try {
|
|
1619
|
-
files = await readdir(
|
|
2133
|
+
files = await readdir(path28, this._rdOptions);
|
|
1620
2134
|
} catch (error) {
|
|
1621
2135
|
this._onError(error);
|
|
1622
2136
|
}
|
|
1623
|
-
return { files, depth, path:
|
|
2137
|
+
return { files, depth, path: path28 };
|
|
1624
2138
|
}
|
|
1625
|
-
async _formatEntry(dirent,
|
|
2139
|
+
async _formatEntry(dirent, path28) {
|
|
1626
2140
|
let entry;
|
|
1627
2141
|
const basename3 = this._isDirent ? dirent.name : dirent;
|
|
1628
2142
|
try {
|
|
1629
|
-
const fullPath = presolve(pjoin(
|
|
2143
|
+
const fullPath = presolve(pjoin(path28, basename3));
|
|
1630
2144
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
|
|
1631
2145
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
1632
2146
|
} catch (err) {
|
|
@@ -2026,16 +2540,16 @@ var delFromSet = (main, prop, item) => {
|
|
|
2026
2540
|
};
|
|
2027
2541
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
2028
2542
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
2029
|
-
function createFsWatchInstance(
|
|
2543
|
+
function createFsWatchInstance(path28, options, listener, errHandler, emitRaw) {
|
|
2030
2544
|
const handleEvent = (rawEvent, evPath) => {
|
|
2031
|
-
listener(
|
|
2032
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
2033
|
-
if (evPath &&
|
|
2034
|
-
fsWatchBroadcast(sysPath.resolve(
|
|
2545
|
+
listener(path28);
|
|
2546
|
+
emitRaw(rawEvent, evPath, { watchedPath: path28 });
|
|
2547
|
+
if (evPath && path28 !== evPath) {
|
|
2548
|
+
fsWatchBroadcast(sysPath.resolve(path28, evPath), KEY_LISTENERS, sysPath.join(path28, evPath));
|
|
2035
2549
|
}
|
|
2036
2550
|
};
|
|
2037
2551
|
try {
|
|
2038
|
-
return fs_watch(
|
|
2552
|
+
return fs_watch(path28, {
|
|
2039
2553
|
persistent: options.persistent
|
|
2040
2554
|
}, handleEvent);
|
|
2041
2555
|
} catch (error) {
|
|
@@ -2051,12 +2565,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
2051
2565
|
listener(val1, val2, val3);
|
|
2052
2566
|
});
|
|
2053
2567
|
};
|
|
2054
|
-
var setFsWatchListener = (
|
|
2568
|
+
var setFsWatchListener = (path28, fullPath, options, handlers) => {
|
|
2055
2569
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
2056
2570
|
let cont = FsWatchInstances.get(fullPath);
|
|
2057
2571
|
let watcher;
|
|
2058
2572
|
if (!options.persistent) {
|
|
2059
|
-
watcher = createFsWatchInstance(
|
|
2573
|
+
watcher = createFsWatchInstance(path28, options, listener, errHandler, rawEmitter);
|
|
2060
2574
|
if (!watcher)
|
|
2061
2575
|
return;
|
|
2062
2576
|
return watcher.close.bind(watcher);
|
|
@@ -2067,7 +2581,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
2067
2581
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
2068
2582
|
} else {
|
|
2069
2583
|
watcher = createFsWatchInstance(
|
|
2070
|
-
|
|
2584
|
+
path28,
|
|
2071
2585
|
options,
|
|
2072
2586
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
2073
2587
|
errHandler,
|
|
@@ -2082,7 +2596,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
2082
2596
|
cont.watcherUnusable = true;
|
|
2083
2597
|
if (isWindows && error.code === "EPERM") {
|
|
2084
2598
|
try {
|
|
2085
|
-
const fd = await open(
|
|
2599
|
+
const fd = await open(path28, "r");
|
|
2086
2600
|
await fd.close();
|
|
2087
2601
|
broadcastErr(error);
|
|
2088
2602
|
} catch (err) {
|
|
@@ -2113,7 +2627,7 @@ var setFsWatchListener = (path25, fullPath, options, handlers) => {
|
|
|
2113
2627
|
};
|
|
2114
2628
|
};
|
|
2115
2629
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
2116
|
-
var setFsWatchFileListener = (
|
|
2630
|
+
var setFsWatchFileListener = (path28, fullPath, options, handlers) => {
|
|
2117
2631
|
const { listener, rawEmitter } = handlers;
|
|
2118
2632
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
2119
2633
|
const copts = cont && cont.options;
|
|
@@ -2135,7 +2649,7 @@ var setFsWatchFileListener = (path25, fullPath, options, handlers) => {
|
|
|
2135
2649
|
});
|
|
2136
2650
|
const currmtime = curr.mtimeMs;
|
|
2137
2651
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
2138
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
2652
|
+
foreach(cont.listeners, (listener2) => listener2(path28, curr));
|
|
2139
2653
|
}
|
|
2140
2654
|
})
|
|
2141
2655
|
};
|
|
@@ -2163,13 +2677,13 @@ var NodeFsHandler = class {
|
|
|
2163
2677
|
* @param listener on fs change
|
|
2164
2678
|
* @returns closer for the watcher instance
|
|
2165
2679
|
*/
|
|
2166
|
-
_watchWithNodeFs(
|
|
2680
|
+
_watchWithNodeFs(path28, listener) {
|
|
2167
2681
|
const opts = this.fsw.options;
|
|
2168
|
-
const directory = sysPath.dirname(
|
|
2169
|
-
const basename3 = sysPath.basename(
|
|
2682
|
+
const directory = sysPath.dirname(path28);
|
|
2683
|
+
const basename3 = sysPath.basename(path28);
|
|
2170
2684
|
const parent = this.fsw._getWatchedDir(directory);
|
|
2171
2685
|
parent.add(basename3);
|
|
2172
|
-
const absolutePath = sysPath.resolve(
|
|
2686
|
+
const absolutePath = sysPath.resolve(path28);
|
|
2173
2687
|
const options = {
|
|
2174
2688
|
persistent: opts.persistent
|
|
2175
2689
|
};
|
|
@@ -2179,12 +2693,12 @@ var NodeFsHandler = class {
|
|
|
2179
2693
|
if (opts.usePolling) {
|
|
2180
2694
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
2181
2695
|
options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
|
|
2182
|
-
closer = setFsWatchFileListener(
|
|
2696
|
+
closer = setFsWatchFileListener(path28, absolutePath, options, {
|
|
2183
2697
|
listener,
|
|
2184
2698
|
rawEmitter: this.fsw._emitRaw
|
|
2185
2699
|
});
|
|
2186
2700
|
} else {
|
|
2187
|
-
closer = setFsWatchListener(
|
|
2701
|
+
closer = setFsWatchListener(path28, absolutePath, options, {
|
|
2188
2702
|
listener,
|
|
2189
2703
|
errHandler: this._boundHandleError,
|
|
2190
2704
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -2206,7 +2720,7 @@ var NodeFsHandler = class {
|
|
|
2206
2720
|
let prevStats = stats;
|
|
2207
2721
|
if (parent.has(basename3))
|
|
2208
2722
|
return;
|
|
2209
|
-
const listener = async (
|
|
2723
|
+
const listener = async (path28, newStats) => {
|
|
2210
2724
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
2211
2725
|
return;
|
|
2212
2726
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -2220,11 +2734,11 @@ var NodeFsHandler = class {
|
|
|
2220
2734
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
2221
2735
|
}
|
|
2222
2736
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
2223
|
-
this.fsw._closeFile(
|
|
2737
|
+
this.fsw._closeFile(path28);
|
|
2224
2738
|
prevStats = newStats2;
|
|
2225
2739
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
2226
2740
|
if (closer2)
|
|
2227
|
-
this.fsw._addPathCloser(
|
|
2741
|
+
this.fsw._addPathCloser(path28, closer2);
|
|
2228
2742
|
} else {
|
|
2229
2743
|
prevStats = newStats2;
|
|
2230
2744
|
}
|
|
@@ -2256,7 +2770,7 @@ var NodeFsHandler = class {
|
|
|
2256
2770
|
* @param item basename of this item
|
|
2257
2771
|
* @returns true if no more processing is needed for this entry.
|
|
2258
2772
|
*/
|
|
2259
|
-
async _handleSymlink(entry, directory,
|
|
2773
|
+
async _handleSymlink(entry, directory, path28, item) {
|
|
2260
2774
|
if (this.fsw.closed) {
|
|
2261
2775
|
return;
|
|
2262
2776
|
}
|
|
@@ -2266,7 +2780,7 @@ var NodeFsHandler = class {
|
|
|
2266
2780
|
this.fsw._incrReadyCount();
|
|
2267
2781
|
let linkPath;
|
|
2268
2782
|
try {
|
|
2269
|
-
linkPath = await fsrealpath(
|
|
2783
|
+
linkPath = await fsrealpath(path28);
|
|
2270
2784
|
} catch (e) {
|
|
2271
2785
|
this.fsw._emitReady();
|
|
2272
2786
|
return true;
|
|
@@ -2276,12 +2790,12 @@ var NodeFsHandler = class {
|
|
|
2276
2790
|
if (dir.has(item)) {
|
|
2277
2791
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
2278
2792
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
2279
|
-
this.fsw._emit(EV.CHANGE,
|
|
2793
|
+
this.fsw._emit(EV.CHANGE, path28, entry.stats);
|
|
2280
2794
|
}
|
|
2281
2795
|
} else {
|
|
2282
2796
|
dir.add(item);
|
|
2283
2797
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
2284
|
-
this.fsw._emit(EV.ADD,
|
|
2798
|
+
this.fsw._emit(EV.ADD, path28, entry.stats);
|
|
2285
2799
|
}
|
|
2286
2800
|
this.fsw._emitReady();
|
|
2287
2801
|
return true;
|
|
@@ -2310,9 +2824,9 @@ var NodeFsHandler = class {
|
|
|
2310
2824
|
return;
|
|
2311
2825
|
}
|
|
2312
2826
|
const item = entry.path;
|
|
2313
|
-
let
|
|
2827
|
+
let path28 = sysPath.join(directory, item);
|
|
2314
2828
|
current.add(item);
|
|
2315
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
2829
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path28, item)) {
|
|
2316
2830
|
return;
|
|
2317
2831
|
}
|
|
2318
2832
|
if (this.fsw.closed) {
|
|
@@ -2321,8 +2835,8 @@ var NodeFsHandler = class {
|
|
|
2321
2835
|
}
|
|
2322
2836
|
if (item === target || !target && !previous.has(item)) {
|
|
2323
2837
|
this.fsw._incrReadyCount();
|
|
2324
|
-
|
|
2325
|
-
this._addToNodeFs(
|
|
2838
|
+
path28 = sysPath.join(dir, sysPath.relative(dir, path28));
|
|
2839
|
+
this._addToNodeFs(path28, initialAdd, wh, depth + 1);
|
|
2326
2840
|
}
|
|
2327
2841
|
}).on(EV.ERROR, this._boundHandleError);
|
|
2328
2842
|
return new Promise((resolve3, reject) => {
|
|
@@ -2391,13 +2905,13 @@ var NodeFsHandler = class {
|
|
|
2391
2905
|
* @param depth Child path actually targeted for watch
|
|
2392
2906
|
* @param target Child path actually targeted for watch
|
|
2393
2907
|
*/
|
|
2394
|
-
async _addToNodeFs(
|
|
2908
|
+
async _addToNodeFs(path28, initialAdd, priorWh, depth, target) {
|
|
2395
2909
|
const ready = this.fsw._emitReady;
|
|
2396
|
-
if (this.fsw._isIgnored(
|
|
2910
|
+
if (this.fsw._isIgnored(path28) || this.fsw.closed) {
|
|
2397
2911
|
ready();
|
|
2398
2912
|
return false;
|
|
2399
2913
|
}
|
|
2400
|
-
const wh = this.fsw._getWatchHelpers(
|
|
2914
|
+
const wh = this.fsw._getWatchHelpers(path28);
|
|
2401
2915
|
if (priorWh) {
|
|
2402
2916
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
2403
2917
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -2413,8 +2927,8 @@ var NodeFsHandler = class {
|
|
|
2413
2927
|
const follow = this.fsw.options.followSymlinks;
|
|
2414
2928
|
let closer;
|
|
2415
2929
|
if (stats.isDirectory()) {
|
|
2416
|
-
const absPath = sysPath.resolve(
|
|
2417
|
-
const targetPath = follow ? await fsrealpath(
|
|
2930
|
+
const absPath = sysPath.resolve(path28);
|
|
2931
|
+
const targetPath = follow ? await fsrealpath(path28) : path28;
|
|
2418
2932
|
if (this.fsw.closed)
|
|
2419
2933
|
return;
|
|
2420
2934
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -2424,29 +2938,29 @@ var NodeFsHandler = class {
|
|
|
2424
2938
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
2425
2939
|
}
|
|
2426
2940
|
} else if (stats.isSymbolicLink()) {
|
|
2427
|
-
const targetPath = follow ? await fsrealpath(
|
|
2941
|
+
const targetPath = follow ? await fsrealpath(path28) : path28;
|
|
2428
2942
|
if (this.fsw.closed)
|
|
2429
2943
|
return;
|
|
2430
2944
|
const parent = sysPath.dirname(wh.watchPath);
|
|
2431
2945
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
2432
2946
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
2433
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
2947
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path28, wh, targetPath);
|
|
2434
2948
|
if (this.fsw.closed)
|
|
2435
2949
|
return;
|
|
2436
2950
|
if (targetPath !== void 0) {
|
|
2437
|
-
this.fsw._symlinkPaths.set(sysPath.resolve(
|
|
2951
|
+
this.fsw._symlinkPaths.set(sysPath.resolve(path28), targetPath);
|
|
2438
2952
|
}
|
|
2439
2953
|
} else {
|
|
2440
2954
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
2441
2955
|
}
|
|
2442
2956
|
ready();
|
|
2443
2957
|
if (closer)
|
|
2444
|
-
this.fsw._addPathCloser(
|
|
2958
|
+
this.fsw._addPathCloser(path28, closer);
|
|
2445
2959
|
return false;
|
|
2446
2960
|
} catch (error) {
|
|
2447
2961
|
if (this.fsw._handleError(error)) {
|
|
2448
2962
|
ready();
|
|
2449
|
-
return
|
|
2963
|
+
return path28;
|
|
2450
2964
|
}
|
|
2451
2965
|
}
|
|
2452
2966
|
}
|
|
@@ -2489,26 +3003,26 @@ function createPattern(matcher) {
|
|
|
2489
3003
|
}
|
|
2490
3004
|
return () => false;
|
|
2491
3005
|
}
|
|
2492
|
-
function normalizePath(
|
|
2493
|
-
if (typeof
|
|
3006
|
+
function normalizePath(path28) {
|
|
3007
|
+
if (typeof path28 !== "string")
|
|
2494
3008
|
throw new Error("string expected");
|
|
2495
|
-
|
|
2496
|
-
|
|
3009
|
+
path28 = sysPath2.normalize(path28);
|
|
3010
|
+
path28 = path28.replace(/\\/g, "/");
|
|
2497
3011
|
let prepend = false;
|
|
2498
|
-
if (
|
|
3012
|
+
if (path28.startsWith("//"))
|
|
2499
3013
|
prepend = true;
|
|
2500
3014
|
const DOUBLE_SLASH_RE2 = /\/\//;
|
|
2501
|
-
while (
|
|
2502
|
-
|
|
3015
|
+
while (path28.match(DOUBLE_SLASH_RE2))
|
|
3016
|
+
path28 = path28.replace(DOUBLE_SLASH_RE2, "/");
|
|
2503
3017
|
if (prepend)
|
|
2504
|
-
|
|
2505
|
-
return
|
|
3018
|
+
path28 = "/" + path28;
|
|
3019
|
+
return path28;
|
|
2506
3020
|
}
|
|
2507
3021
|
function matchPatterns(patterns, testString, stats) {
|
|
2508
|
-
const
|
|
3022
|
+
const path28 = normalizePath(testString);
|
|
2509
3023
|
for (let index = 0; index < patterns.length; index++) {
|
|
2510
3024
|
const pattern = patterns[index];
|
|
2511
|
-
if (pattern(
|
|
3025
|
+
if (pattern(path28, stats)) {
|
|
2512
3026
|
return true;
|
|
2513
3027
|
}
|
|
2514
3028
|
}
|
|
@@ -2548,19 +3062,19 @@ var toUnix = (string) => {
|
|
|
2548
3062
|
}
|
|
2549
3063
|
return str;
|
|
2550
3064
|
};
|
|
2551
|
-
var normalizePathToUnix = (
|
|
2552
|
-
var normalizeIgnored = (cwd = "") => (
|
|
2553
|
-
if (typeof
|
|
2554
|
-
return normalizePathToUnix(sysPath2.isAbsolute(
|
|
3065
|
+
var normalizePathToUnix = (path28) => toUnix(sysPath2.normalize(toUnix(path28)));
|
|
3066
|
+
var normalizeIgnored = (cwd = "") => (path28) => {
|
|
3067
|
+
if (typeof path28 === "string") {
|
|
3068
|
+
return normalizePathToUnix(sysPath2.isAbsolute(path28) ? path28 : sysPath2.join(cwd, path28));
|
|
2555
3069
|
} else {
|
|
2556
|
-
return
|
|
3070
|
+
return path28;
|
|
2557
3071
|
}
|
|
2558
3072
|
};
|
|
2559
|
-
var getAbsolutePath = (
|
|
2560
|
-
if (sysPath2.isAbsolute(
|
|
2561
|
-
return
|
|
3073
|
+
var getAbsolutePath = (path28, cwd) => {
|
|
3074
|
+
if (sysPath2.isAbsolute(path28)) {
|
|
3075
|
+
return path28;
|
|
2562
3076
|
}
|
|
2563
|
-
return sysPath2.join(cwd,
|
|
3077
|
+
return sysPath2.join(cwd, path28);
|
|
2564
3078
|
};
|
|
2565
3079
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
2566
3080
|
var DirEntry = class {
|
|
@@ -2615,10 +3129,10 @@ var DirEntry = class {
|
|
|
2615
3129
|
var STAT_METHOD_F = "stat";
|
|
2616
3130
|
var STAT_METHOD_L = "lstat";
|
|
2617
3131
|
var WatchHelper = class {
|
|
2618
|
-
constructor(
|
|
3132
|
+
constructor(path28, follow, fsw) {
|
|
2619
3133
|
this.fsw = fsw;
|
|
2620
|
-
const watchPath =
|
|
2621
|
-
this.path =
|
|
3134
|
+
const watchPath = path28;
|
|
3135
|
+
this.path = path28 = path28.replace(REPLACER_RE, "");
|
|
2622
3136
|
this.watchPath = watchPath;
|
|
2623
3137
|
this.fullWatchPath = sysPath2.resolve(watchPath);
|
|
2624
3138
|
this.dirParts = [];
|
|
@@ -2740,20 +3254,20 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2740
3254
|
this._closePromise = void 0;
|
|
2741
3255
|
let paths = unifyPaths(paths_);
|
|
2742
3256
|
if (cwd) {
|
|
2743
|
-
paths = paths.map((
|
|
2744
|
-
const absPath = getAbsolutePath(
|
|
3257
|
+
paths = paths.map((path28) => {
|
|
3258
|
+
const absPath = getAbsolutePath(path28, cwd);
|
|
2745
3259
|
return absPath;
|
|
2746
3260
|
});
|
|
2747
3261
|
}
|
|
2748
|
-
paths.forEach((
|
|
2749
|
-
this._removeIgnoredPath(
|
|
3262
|
+
paths.forEach((path28) => {
|
|
3263
|
+
this._removeIgnoredPath(path28);
|
|
2750
3264
|
});
|
|
2751
3265
|
this._userIgnored = void 0;
|
|
2752
3266
|
if (!this._readyCount)
|
|
2753
3267
|
this._readyCount = 0;
|
|
2754
3268
|
this._readyCount += paths.length;
|
|
2755
|
-
Promise.all(paths.map(async (
|
|
2756
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
3269
|
+
Promise.all(paths.map(async (path28) => {
|
|
3270
|
+
const res = await this._nodeFsHandler._addToNodeFs(path28, !_internal, void 0, 0, _origAdd);
|
|
2757
3271
|
if (res)
|
|
2758
3272
|
this._emitReady();
|
|
2759
3273
|
return res;
|
|
@@ -2775,17 +3289,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2775
3289
|
return this;
|
|
2776
3290
|
const paths = unifyPaths(paths_);
|
|
2777
3291
|
const { cwd } = this.options;
|
|
2778
|
-
paths.forEach((
|
|
2779
|
-
if (!sysPath2.isAbsolute(
|
|
3292
|
+
paths.forEach((path28) => {
|
|
3293
|
+
if (!sysPath2.isAbsolute(path28) && !this._closers.has(path28)) {
|
|
2780
3294
|
if (cwd)
|
|
2781
|
-
|
|
2782
|
-
|
|
3295
|
+
path28 = sysPath2.join(cwd, path28);
|
|
3296
|
+
path28 = sysPath2.resolve(path28);
|
|
2783
3297
|
}
|
|
2784
|
-
this._closePath(
|
|
2785
|
-
this._addIgnoredPath(
|
|
2786
|
-
if (this._watched.has(
|
|
3298
|
+
this._closePath(path28);
|
|
3299
|
+
this._addIgnoredPath(path28);
|
|
3300
|
+
if (this._watched.has(path28)) {
|
|
2787
3301
|
this._addIgnoredPath({
|
|
2788
|
-
path:
|
|
3302
|
+
path: path28,
|
|
2789
3303
|
recursive: true
|
|
2790
3304
|
});
|
|
2791
3305
|
}
|
|
@@ -2849,38 +3363,38 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2849
3363
|
* @param stats arguments to be passed with event
|
|
2850
3364
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
2851
3365
|
*/
|
|
2852
|
-
async _emit(event,
|
|
3366
|
+
async _emit(event, path28, stats) {
|
|
2853
3367
|
if (this.closed)
|
|
2854
3368
|
return;
|
|
2855
3369
|
const opts = this.options;
|
|
2856
3370
|
if (isWindows)
|
|
2857
|
-
|
|
3371
|
+
path28 = sysPath2.normalize(path28);
|
|
2858
3372
|
if (opts.cwd)
|
|
2859
|
-
|
|
2860
|
-
const args = [
|
|
3373
|
+
path28 = sysPath2.relative(opts.cwd, path28);
|
|
3374
|
+
const args = [path28];
|
|
2861
3375
|
if (stats != null)
|
|
2862
3376
|
args.push(stats);
|
|
2863
3377
|
const awf = opts.awaitWriteFinish;
|
|
2864
3378
|
let pw;
|
|
2865
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
3379
|
+
if (awf && (pw = this._pendingWrites.get(path28))) {
|
|
2866
3380
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
2867
3381
|
return this;
|
|
2868
3382
|
}
|
|
2869
3383
|
if (opts.atomic) {
|
|
2870
3384
|
if (event === EVENTS.UNLINK) {
|
|
2871
|
-
this._pendingUnlinks.set(
|
|
3385
|
+
this._pendingUnlinks.set(path28, [event, ...args]);
|
|
2872
3386
|
setTimeout(() => {
|
|
2873
|
-
this._pendingUnlinks.forEach((entry,
|
|
3387
|
+
this._pendingUnlinks.forEach((entry, path29) => {
|
|
2874
3388
|
this.emit(...entry);
|
|
2875
3389
|
this.emit(EVENTS.ALL, ...entry);
|
|
2876
|
-
this._pendingUnlinks.delete(
|
|
3390
|
+
this._pendingUnlinks.delete(path29);
|
|
2877
3391
|
});
|
|
2878
3392
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
2879
3393
|
return this;
|
|
2880
3394
|
}
|
|
2881
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
3395
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path28)) {
|
|
2882
3396
|
event = EVENTS.CHANGE;
|
|
2883
|
-
this._pendingUnlinks.delete(
|
|
3397
|
+
this._pendingUnlinks.delete(path28);
|
|
2884
3398
|
}
|
|
2885
3399
|
}
|
|
2886
3400
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -2898,16 +3412,16 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2898
3412
|
this.emitWithAll(event, args);
|
|
2899
3413
|
}
|
|
2900
3414
|
};
|
|
2901
|
-
this._awaitWriteFinish(
|
|
3415
|
+
this._awaitWriteFinish(path28, awf.stabilityThreshold, event, awfEmit);
|
|
2902
3416
|
return this;
|
|
2903
3417
|
}
|
|
2904
3418
|
if (event === EVENTS.CHANGE) {
|
|
2905
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
3419
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path28, 50);
|
|
2906
3420
|
if (isThrottled)
|
|
2907
3421
|
return this;
|
|
2908
3422
|
}
|
|
2909
3423
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
2910
|
-
const fullPath = opts.cwd ? sysPath2.join(opts.cwd,
|
|
3424
|
+
const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path28) : path28;
|
|
2911
3425
|
let stats2;
|
|
2912
3426
|
try {
|
|
2913
3427
|
stats2 = await stat3(fullPath);
|
|
@@ -2938,23 +3452,23 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2938
3452
|
* @param timeout duration of time to suppress duplicate actions
|
|
2939
3453
|
* @returns tracking object or false if action should be suppressed
|
|
2940
3454
|
*/
|
|
2941
|
-
_throttle(actionType,
|
|
3455
|
+
_throttle(actionType, path28, timeout) {
|
|
2942
3456
|
if (!this._throttled.has(actionType)) {
|
|
2943
3457
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
2944
3458
|
}
|
|
2945
3459
|
const action = this._throttled.get(actionType);
|
|
2946
3460
|
if (!action)
|
|
2947
3461
|
throw new Error("invalid throttle");
|
|
2948
|
-
const actionPath = action.get(
|
|
3462
|
+
const actionPath = action.get(path28);
|
|
2949
3463
|
if (actionPath) {
|
|
2950
3464
|
actionPath.count++;
|
|
2951
3465
|
return false;
|
|
2952
3466
|
}
|
|
2953
3467
|
let timeoutObject;
|
|
2954
3468
|
const clear = () => {
|
|
2955
|
-
const item = action.get(
|
|
3469
|
+
const item = action.get(path28);
|
|
2956
3470
|
const count = item ? item.count : 0;
|
|
2957
|
-
action.delete(
|
|
3471
|
+
action.delete(path28);
|
|
2958
3472
|
clearTimeout(timeoutObject);
|
|
2959
3473
|
if (item)
|
|
2960
3474
|
clearTimeout(item.timeoutObject);
|
|
@@ -2962,7 +3476,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2962
3476
|
};
|
|
2963
3477
|
timeoutObject = setTimeout(clear, timeout);
|
|
2964
3478
|
const thr = { timeoutObject, clear, count: 0 };
|
|
2965
|
-
action.set(
|
|
3479
|
+
action.set(path28, thr);
|
|
2966
3480
|
return thr;
|
|
2967
3481
|
}
|
|
2968
3482
|
_incrReadyCount() {
|
|
@@ -2976,44 +3490,44 @@ var FSWatcher = class extends EventEmitter {
|
|
|
2976
3490
|
* @param event
|
|
2977
3491
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
2978
3492
|
*/
|
|
2979
|
-
_awaitWriteFinish(
|
|
3493
|
+
_awaitWriteFinish(path28, threshold, event, awfEmit) {
|
|
2980
3494
|
const awf = this.options.awaitWriteFinish;
|
|
2981
3495
|
if (typeof awf !== "object")
|
|
2982
3496
|
return;
|
|
2983
3497
|
const pollInterval = awf.pollInterval;
|
|
2984
3498
|
let timeoutHandler;
|
|
2985
|
-
let fullPath =
|
|
2986
|
-
if (this.options.cwd && !sysPath2.isAbsolute(
|
|
2987
|
-
fullPath = sysPath2.join(this.options.cwd,
|
|
3499
|
+
let fullPath = path28;
|
|
3500
|
+
if (this.options.cwd && !sysPath2.isAbsolute(path28)) {
|
|
3501
|
+
fullPath = sysPath2.join(this.options.cwd, path28);
|
|
2988
3502
|
}
|
|
2989
3503
|
const now = /* @__PURE__ */ new Date();
|
|
2990
3504
|
const writes = this._pendingWrites;
|
|
2991
3505
|
function awaitWriteFinishFn(prevStat) {
|
|
2992
3506
|
statcb(fullPath, (err, curStat) => {
|
|
2993
|
-
if (err || !writes.has(
|
|
3507
|
+
if (err || !writes.has(path28)) {
|
|
2994
3508
|
if (err && err.code !== "ENOENT")
|
|
2995
3509
|
awfEmit(err);
|
|
2996
3510
|
return;
|
|
2997
3511
|
}
|
|
2998
3512
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
2999
3513
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
3000
|
-
writes.get(
|
|
3514
|
+
writes.get(path28).lastChange = now2;
|
|
3001
3515
|
}
|
|
3002
|
-
const pw = writes.get(
|
|
3516
|
+
const pw = writes.get(path28);
|
|
3003
3517
|
const df = now2 - pw.lastChange;
|
|
3004
3518
|
if (df >= threshold) {
|
|
3005
|
-
writes.delete(
|
|
3519
|
+
writes.delete(path28);
|
|
3006
3520
|
awfEmit(void 0, curStat);
|
|
3007
3521
|
} else {
|
|
3008
3522
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
3009
3523
|
}
|
|
3010
3524
|
});
|
|
3011
3525
|
}
|
|
3012
|
-
if (!writes.has(
|
|
3013
|
-
writes.set(
|
|
3526
|
+
if (!writes.has(path28)) {
|
|
3527
|
+
writes.set(path28, {
|
|
3014
3528
|
lastChange: now,
|
|
3015
3529
|
cancelWait: () => {
|
|
3016
|
-
writes.delete(
|
|
3530
|
+
writes.delete(path28);
|
|
3017
3531
|
clearTimeout(timeoutHandler);
|
|
3018
3532
|
return event;
|
|
3019
3533
|
}
|
|
@@ -3024,8 +3538,8 @@ var FSWatcher = class extends EventEmitter {
|
|
|
3024
3538
|
/**
|
|
3025
3539
|
* Determines whether user has asked to ignore this path.
|
|
3026
3540
|
*/
|
|
3027
|
-
_isIgnored(
|
|
3028
|
-
if (this.options.atomic && DOT_RE.test(
|
|
3541
|
+
_isIgnored(path28, stats) {
|
|
3542
|
+
if (this.options.atomic && DOT_RE.test(path28))
|
|
3029
3543
|
return true;
|
|
3030
3544
|
if (!this._userIgnored) {
|
|
3031
3545
|
const { cwd } = this.options;
|
|
@@ -3035,17 +3549,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
3035
3549
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
3036
3550
|
this._userIgnored = anymatch(list, void 0);
|
|
3037
3551
|
}
|
|
3038
|
-
return this._userIgnored(
|
|
3552
|
+
return this._userIgnored(path28, stats);
|
|
3039
3553
|
}
|
|
3040
|
-
_isntIgnored(
|
|
3041
|
-
return !this._isIgnored(
|
|
3554
|
+
_isntIgnored(path28, stat4) {
|
|
3555
|
+
return !this._isIgnored(path28, stat4);
|
|
3042
3556
|
}
|
|
3043
3557
|
/**
|
|
3044
3558
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
3045
3559
|
* @param path file or directory pattern being watched
|
|
3046
3560
|
*/
|
|
3047
|
-
_getWatchHelpers(
|
|
3048
|
-
return new WatchHelper(
|
|
3561
|
+
_getWatchHelpers(path28) {
|
|
3562
|
+
return new WatchHelper(path28, this.options.followSymlinks, this);
|
|
3049
3563
|
}
|
|
3050
3564
|
// Directory helpers
|
|
3051
3565
|
// -----------------
|
|
@@ -3077,63 +3591,63 @@ var FSWatcher = class extends EventEmitter {
|
|
|
3077
3591
|
* @param item base path of item/directory
|
|
3078
3592
|
*/
|
|
3079
3593
|
_remove(directory, item, isDirectory) {
|
|
3080
|
-
const
|
|
3081
|
-
const fullPath = sysPath2.resolve(
|
|
3082
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
3083
|
-
if (!this._throttle("remove",
|
|
3594
|
+
const path28 = sysPath2.join(directory, item);
|
|
3595
|
+
const fullPath = sysPath2.resolve(path28);
|
|
3596
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path28) || this._watched.has(fullPath);
|
|
3597
|
+
if (!this._throttle("remove", path28, 100))
|
|
3084
3598
|
return;
|
|
3085
3599
|
if (!isDirectory && this._watched.size === 1) {
|
|
3086
3600
|
this.add(directory, item, true);
|
|
3087
3601
|
}
|
|
3088
|
-
const wp = this._getWatchedDir(
|
|
3602
|
+
const wp = this._getWatchedDir(path28);
|
|
3089
3603
|
const nestedDirectoryChildren = wp.getChildren();
|
|
3090
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
3604
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path28, nested));
|
|
3091
3605
|
const parent = this._getWatchedDir(directory);
|
|
3092
3606
|
const wasTracked = parent.has(item);
|
|
3093
3607
|
parent.remove(item);
|
|
3094
3608
|
if (this._symlinkPaths.has(fullPath)) {
|
|
3095
3609
|
this._symlinkPaths.delete(fullPath);
|
|
3096
3610
|
}
|
|
3097
|
-
let relPath =
|
|
3611
|
+
let relPath = path28;
|
|
3098
3612
|
if (this.options.cwd)
|
|
3099
|
-
relPath = sysPath2.relative(this.options.cwd,
|
|
3613
|
+
relPath = sysPath2.relative(this.options.cwd, path28);
|
|
3100
3614
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
3101
3615
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
3102
3616
|
if (event === EVENTS.ADD)
|
|
3103
3617
|
return;
|
|
3104
3618
|
}
|
|
3105
|
-
this._watched.delete(
|
|
3619
|
+
this._watched.delete(path28);
|
|
3106
3620
|
this._watched.delete(fullPath);
|
|
3107
3621
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
3108
|
-
if (wasTracked && !this._isIgnored(
|
|
3109
|
-
this._emit(eventName,
|
|
3110
|
-
this._closePath(
|
|
3622
|
+
if (wasTracked && !this._isIgnored(path28))
|
|
3623
|
+
this._emit(eventName, path28);
|
|
3624
|
+
this._closePath(path28);
|
|
3111
3625
|
}
|
|
3112
3626
|
/**
|
|
3113
3627
|
* Closes all watchers for a path
|
|
3114
3628
|
*/
|
|
3115
|
-
_closePath(
|
|
3116
|
-
this._closeFile(
|
|
3117
|
-
const dir = sysPath2.dirname(
|
|
3118
|
-
this._getWatchedDir(dir).remove(sysPath2.basename(
|
|
3629
|
+
_closePath(path28) {
|
|
3630
|
+
this._closeFile(path28);
|
|
3631
|
+
const dir = sysPath2.dirname(path28);
|
|
3632
|
+
this._getWatchedDir(dir).remove(sysPath2.basename(path28));
|
|
3119
3633
|
}
|
|
3120
3634
|
/**
|
|
3121
3635
|
* Closes only file-specific watchers
|
|
3122
3636
|
*/
|
|
3123
|
-
_closeFile(
|
|
3124
|
-
const closers = this._closers.get(
|
|
3637
|
+
_closeFile(path28) {
|
|
3638
|
+
const closers = this._closers.get(path28);
|
|
3125
3639
|
if (!closers)
|
|
3126
3640
|
return;
|
|
3127
3641
|
closers.forEach((closer) => closer());
|
|
3128
|
-
this._closers.delete(
|
|
3642
|
+
this._closers.delete(path28);
|
|
3129
3643
|
}
|
|
3130
|
-
_addPathCloser(
|
|
3644
|
+
_addPathCloser(path28, closer) {
|
|
3131
3645
|
if (!closer)
|
|
3132
3646
|
return;
|
|
3133
|
-
let list = this._closers.get(
|
|
3647
|
+
let list = this._closers.get(path28);
|
|
3134
3648
|
if (!list) {
|
|
3135
3649
|
list = [];
|
|
3136
|
-
this._closers.set(
|
|
3650
|
+
this._closers.set(path28, list);
|
|
3137
3651
|
}
|
|
3138
3652
|
list.push(closer);
|
|
3139
3653
|
}
|
|
@@ -3168,54 +3682,132 @@ import path14 from "path";
|
|
|
3168
3682
|
function setupHotReload({
|
|
3169
3683
|
app,
|
|
3170
3684
|
appDir,
|
|
3685
|
+
projectRoot,
|
|
3171
3686
|
route = "/__fw/hot",
|
|
3172
3687
|
waitForBuild,
|
|
3173
3688
|
onFileChange
|
|
3174
3689
|
}) {
|
|
3175
3690
|
const clients = /* @__PURE__ */ new Set();
|
|
3176
3691
|
app.get(route, (req, res) => {
|
|
3177
|
-
res.setHeader("Content-Type", "text/event-stream");
|
|
3178
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
3692
|
+
res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
|
|
3693
|
+
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
3179
3694
|
res.setHeader("Connection", "keep-alive");
|
|
3180
|
-
res.
|
|
3181
|
-
res.
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3695
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
3696
|
+
if (res.flushHeaders) {
|
|
3697
|
+
res.flushHeaders();
|
|
3698
|
+
} else {
|
|
3699
|
+
res.writeHead(200, {
|
|
3700
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
3701
|
+
"Cache-Control": "no-cache, no-transform",
|
|
3702
|
+
"Connection": "keep-alive",
|
|
3703
|
+
"X-Accel-Buffering": "no"
|
|
3704
|
+
});
|
|
3705
|
+
}
|
|
3185
3706
|
clients.add(res);
|
|
3707
|
+
const pingMessage = "event: ping\ndata: connected\n\n";
|
|
3708
|
+
try {
|
|
3709
|
+
res.write(pingMessage, "utf-8");
|
|
3710
|
+
} catch (error) {
|
|
3711
|
+
console.error(`[hot-reload-server] \u274C Error sending ping:`, error);
|
|
3712
|
+
clients.delete(res);
|
|
3713
|
+
}
|
|
3186
3714
|
req.on("close", () => {
|
|
3187
3715
|
clients.delete(res);
|
|
3188
3716
|
});
|
|
3717
|
+
req.on("aborted", () => {
|
|
3718
|
+
clients.delete(res);
|
|
3719
|
+
});
|
|
3189
3720
|
});
|
|
3190
|
-
|
|
3721
|
+
console.log(`[hot-reload-server] \u2705 SSE endpoint registered at ${route}`);
|
|
3722
|
+
const resolvedProjectRoot = projectRoot ? path14.resolve(projectRoot) : path14.dirname(path14.resolve(appDir));
|
|
3723
|
+
const watcher = esm_default.watch(resolvedProjectRoot, {
|
|
3191
3724
|
ignoreInitial: true,
|
|
3192
|
-
ignored: [
|
|
3725
|
+
ignored: [
|
|
3726
|
+
"**/node_modules/**",
|
|
3727
|
+
`**/${BUILD_FOLDER_NAME}/**`,
|
|
3728
|
+
"**/.loly/**",
|
|
3729
|
+
// Ignore build output directory completely
|
|
3730
|
+
"**/.git/**",
|
|
3731
|
+
"**/dist/**",
|
|
3732
|
+
"**/build/**",
|
|
3733
|
+
"**/.next/**",
|
|
3734
|
+
"**/.cache/**",
|
|
3735
|
+
"**/.turbo/**",
|
|
3736
|
+
"**/.swc/**",
|
|
3737
|
+
"**/coverage/**",
|
|
3738
|
+
// Ignore generated files that trigger unnecessary reloads
|
|
3739
|
+
"**/*.map",
|
|
3740
|
+
// Source maps
|
|
3741
|
+
"**/*.log",
|
|
3742
|
+
// Log files
|
|
3743
|
+
"**/.DS_Store",
|
|
3744
|
+
// macOS
|
|
3745
|
+
"**/Thumbs.db"
|
|
3746
|
+
// Windows
|
|
3747
|
+
],
|
|
3748
|
+
persistent: true,
|
|
3749
|
+
ignorePermissionErrors: true,
|
|
3750
|
+
// Only watch relevant source files (TypeScript, JavaScript, CSS)
|
|
3751
|
+
// Filter out JSON files in build directories
|
|
3752
|
+
awaitWriteFinish: {
|
|
3753
|
+
stabilityThreshold: 150,
|
|
3754
|
+
// Wait 150ms after file change to trigger event (debounce)
|
|
3755
|
+
pollInterval: 50
|
|
3756
|
+
// Check every 50ms
|
|
3757
|
+
}
|
|
3193
3758
|
});
|
|
3759
|
+
let broadcastTimeout = null;
|
|
3760
|
+
const BROADCAST_DEBOUNCE_MS = 300;
|
|
3194
3761
|
async function broadcastReload(reason, filePath) {
|
|
3762
|
+
const normalizedPath = path14.normalize(filePath);
|
|
3763
|
+
if (normalizedPath.includes(BUILD_FOLDER_NAME) || normalizedPath.includes(".loly") || normalizedPath.endsWith(".map") || normalizedPath.endsWith(".log") || normalizedPath.includes("route-chunks.json") || normalizedPath.includes("routes-client.ts") || normalizedPath.includes("/client/route-")) {
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3195
3766
|
const rel = path14.relative(appDir, filePath);
|
|
3196
3767
|
console.log(`[hot-reload] ${reason}: ${rel}`);
|
|
3197
|
-
if (
|
|
3198
|
-
|
|
3199
|
-
await onFileChange(filePath);
|
|
3200
|
-
} catch (error) {
|
|
3201
|
-
console.warn("[hot-reload] Error in onFileChange callback:", error);
|
|
3202
|
-
}
|
|
3768
|
+
if (broadcastTimeout) {
|
|
3769
|
+
clearTimeout(broadcastTimeout);
|
|
3203
3770
|
}
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3771
|
+
broadcastTimeout = setTimeout(async () => {
|
|
3772
|
+
if (onFileChange) {
|
|
3773
|
+
try {
|
|
3774
|
+
await onFileChange(filePath);
|
|
3775
|
+
} catch (error) {
|
|
3776
|
+
console.warn("[hot-reload] Error in onFileChange callback:", error);
|
|
3777
|
+
}
|
|
3211
3778
|
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3779
|
+
if (waitForBuild) {
|
|
3780
|
+
try {
|
|
3781
|
+
console.log("[hot-reload] Waiting for client bundle to finish...");
|
|
3782
|
+
await waitForBuild();
|
|
3783
|
+
console.log("[hot-reload] Client bundle ready, sending reload event");
|
|
3784
|
+
} catch (error) {
|
|
3785
|
+
console.warn("[hot-reload] Error waiting for build:", error);
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
const message = `event: message
|
|
3215
3789
|
data: reload:${rel}
|
|
3216
3790
|
|
|
3217
|
-
|
|
3218
|
-
|
|
3791
|
+
`;
|
|
3792
|
+
console.log(`[hot-reload-server] \u{1F4E4} Broadcasting reload event to ${clients.size} client(s)`);
|
|
3793
|
+
let sentCount = 0;
|
|
3794
|
+
for (const res of clients) {
|
|
3795
|
+
try {
|
|
3796
|
+
if (res.writableEnded || res.destroyed) {
|
|
3797
|
+
clients.delete(res);
|
|
3798
|
+
continue;
|
|
3799
|
+
}
|
|
3800
|
+
res.write(message, "utf-8");
|
|
3801
|
+
sentCount++;
|
|
3802
|
+
} catch (error) {
|
|
3803
|
+
console.error(`[hot-reload-server] \u2717 Error sending to client:`, error);
|
|
3804
|
+
clients.delete(res);
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
if (sentCount > 0) {
|
|
3808
|
+
console.log(`[hot-reload-server] \u2705 Reload event sent to ${sentCount} client(s)`);
|
|
3809
|
+
}
|
|
3810
|
+
}, BROADCAST_DEBOUNCE_MS);
|
|
3219
3811
|
}
|
|
3220
3812
|
watcher.on("add", (filePath) => broadcastReload("add", filePath)).on("change", (filePath) => broadcastReload("change", filePath)).on("unlink", (filePath) => broadcastReload("unlink", filePath));
|
|
3221
3813
|
}
|
|
@@ -3291,6 +3883,121 @@ function deepMerge(target, source) {
|
|
|
3291
3883
|
}
|
|
3292
3884
|
return result;
|
|
3293
3885
|
}
|
|
3886
|
+
var ConfigValidationError = class extends Error {
|
|
3887
|
+
constructor(message, errors = []) {
|
|
3888
|
+
super(message);
|
|
3889
|
+
this.errors = errors;
|
|
3890
|
+
this.name = "ConfigValidationError";
|
|
3891
|
+
}
|
|
3892
|
+
};
|
|
3893
|
+
function validateConfig(config, projectRoot) {
|
|
3894
|
+
const errors = [];
|
|
3895
|
+
if (!config.directories.app || typeof config.directories.app !== "string") {
|
|
3896
|
+
errors.push("config.directories.app must be a non-empty string");
|
|
3897
|
+
} else {
|
|
3898
|
+
const appDir = path16.join(projectRoot, config.directories.app);
|
|
3899
|
+
if (!fs14.existsSync(appDir) && process.env.NODE_ENV !== "test") {
|
|
3900
|
+
errors.push(
|
|
3901
|
+
`App directory not found: ${config.directories.app}
|
|
3902
|
+
Expected at: ${appDir}
|
|
3903
|
+
\u{1F4A1} Suggestion: Create the directory or update config.directories.app`
|
|
3904
|
+
);
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
if (!config.directories.build || typeof config.directories.build !== "string") {
|
|
3908
|
+
errors.push("config.directories.build must be a non-empty string");
|
|
3909
|
+
}
|
|
3910
|
+
if (!config.directories.static || typeof config.directories.static !== "string") {
|
|
3911
|
+
errors.push("config.directories.static must be a non-empty string");
|
|
3912
|
+
}
|
|
3913
|
+
const conventionKeys = ["page", "layout", "notFound", "error", "api"];
|
|
3914
|
+
for (const key of conventionKeys) {
|
|
3915
|
+
if (!config.conventions[key] || typeof config.conventions[key] !== "string") {
|
|
3916
|
+
errors.push(`config.conventions.${key} must be a non-empty string`);
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
if (!["always", "never", "ignore"].includes(config.routing.trailingSlash)) {
|
|
3920
|
+
errors.push(
|
|
3921
|
+
`config.routing.trailingSlash must be 'always', 'never', or 'ignore'
|
|
3922
|
+
Received: ${JSON.stringify(config.routing.trailingSlash)}
|
|
3923
|
+
\u{1F4A1} Suggestion: Use one of the valid values: 'always' | 'never' | 'ignore'`
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
if (typeof config.routing.caseSensitive !== "boolean") {
|
|
3927
|
+
errors.push("config.routing.caseSensitive must be a boolean");
|
|
3928
|
+
}
|
|
3929
|
+
if (typeof config.routing.basePath !== "string") {
|
|
3930
|
+
errors.push("config.routing.basePath must be a string");
|
|
3931
|
+
} else if (config.routing.basePath && !config.routing.basePath.startsWith("/")) {
|
|
3932
|
+
errors.push(
|
|
3933
|
+
`config.routing.basePath must start with '/' (if not empty)
|
|
3934
|
+
Received: ${JSON.stringify(config.routing.basePath)}
|
|
3935
|
+
\u{1F4A1} Suggestion: Use an empty string '' or a path starting with '/', e.g., '/api'`
|
|
3936
|
+
);
|
|
3937
|
+
}
|
|
3938
|
+
const validClientBundlers = ["rspack", "webpack", "vite"];
|
|
3939
|
+
if (!validClientBundlers.includes(config.build.clientBundler)) {
|
|
3940
|
+
errors.push(
|
|
3941
|
+
`config.build.clientBundler must be one of: ${validClientBundlers.join(", ")}
|
|
3942
|
+
Received: ${JSON.stringify(config.build.clientBundler)}`
|
|
3943
|
+
);
|
|
3944
|
+
}
|
|
3945
|
+
const validServerBundlers = ["esbuild", "tsup", "swc"];
|
|
3946
|
+
if (!validServerBundlers.includes(config.build.serverBundler)) {
|
|
3947
|
+
errors.push(
|
|
3948
|
+
`config.build.serverBundler must be one of: ${validServerBundlers.join(", ")}
|
|
3949
|
+
Received: ${JSON.stringify(config.build.serverBundler)}`
|
|
3950
|
+
);
|
|
3951
|
+
}
|
|
3952
|
+
if (!["cjs", "esm"].includes(config.build.outputFormat)) {
|
|
3953
|
+
errors.push(
|
|
3954
|
+
`config.build.outputFormat must be 'cjs' or 'esm'
|
|
3955
|
+
Received: ${JSON.stringify(config.build.outputFormat)}`
|
|
3956
|
+
);
|
|
3957
|
+
}
|
|
3958
|
+
const validAdapters = ["express", "fastify", "koa"];
|
|
3959
|
+
if (!validAdapters.includes(config.server.adapter)) {
|
|
3960
|
+
errors.push(
|
|
3961
|
+
`config.server.adapter must be one of: ${validAdapters.join(", ")}
|
|
3962
|
+
Received: ${JSON.stringify(config.server.adapter)}`
|
|
3963
|
+
);
|
|
3964
|
+
}
|
|
3965
|
+
if (typeof config.server.port !== "number" || config.server.port < 1 || config.server.port > 65535) {
|
|
3966
|
+
errors.push(
|
|
3967
|
+
`config.server.port must be a number between 1 and 65535
|
|
3968
|
+
Received: ${JSON.stringify(config.server.port)}`
|
|
3969
|
+
);
|
|
3970
|
+
}
|
|
3971
|
+
if (!config.server.host || typeof config.server.host !== "string") {
|
|
3972
|
+
errors.push("config.server.host must be a non-empty string");
|
|
3973
|
+
}
|
|
3974
|
+
const validFrameworks = ["react", "preact", "vue", "svelte"];
|
|
3975
|
+
if (!validFrameworks.includes(config.rendering.framework)) {
|
|
3976
|
+
errors.push(
|
|
3977
|
+
`config.rendering.framework must be one of: ${validFrameworks.join(", ")}
|
|
3978
|
+
Received: ${JSON.stringify(config.rendering.framework)}`
|
|
3979
|
+
);
|
|
3980
|
+
}
|
|
3981
|
+
if (typeof config.rendering.streaming !== "boolean") {
|
|
3982
|
+
errors.push("config.rendering.streaming must be a boolean");
|
|
3983
|
+
}
|
|
3984
|
+
if (typeof config.rendering.ssr !== "boolean") {
|
|
3985
|
+
errors.push("config.rendering.ssr must be a boolean");
|
|
3986
|
+
}
|
|
3987
|
+
if (typeof config.rendering.ssg !== "boolean") {
|
|
3988
|
+
errors.push("config.rendering.ssg must be a boolean");
|
|
3989
|
+
}
|
|
3990
|
+
if (errors.length > 0) {
|
|
3991
|
+
const errorMessage = [
|
|
3992
|
+
"\u274C Configuration validation failed:",
|
|
3993
|
+
"",
|
|
3994
|
+
...errors.map((err, i) => `${i + 1}. ${err}`),
|
|
3995
|
+
"",
|
|
3996
|
+
"\u{1F4A1} Please check your loly.config.ts file and fix the errors above."
|
|
3997
|
+
].join("\n");
|
|
3998
|
+
throw new ConfigValidationError(errorMessage, errors);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
3294
4001
|
function loadConfig(projectRoot) {
|
|
3295
4002
|
const configFiles = [
|
|
3296
4003
|
path16.join(projectRoot, "loly.config.ts"),
|
|
@@ -3298,6 +4005,7 @@ function loadConfig(projectRoot) {
|
|
|
3298
4005
|
path16.join(projectRoot, "loly.config.json")
|
|
3299
4006
|
];
|
|
3300
4007
|
let userConfig = {};
|
|
4008
|
+
let loadedConfigFile = null;
|
|
3301
4009
|
for (const configFile of configFiles) {
|
|
3302
4010
|
if (fs14.existsSync(configFile)) {
|
|
3303
4011
|
try {
|
|
@@ -3311,16 +4019,31 @@ function loadConfig(projectRoot) {
|
|
|
3311
4019
|
const mod = __require(configFile);
|
|
3312
4020
|
userConfig = typeof mod.default === "function" ? mod.default(process.env.NODE_ENV) : mod.default || mod.config || mod;
|
|
3313
4021
|
}
|
|
4022
|
+
loadedConfigFile = path16.relative(projectRoot, configFile);
|
|
3314
4023
|
break;
|
|
3315
4024
|
} catch (error) {
|
|
3316
|
-
|
|
4025
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4026
|
+
throw new ConfigValidationError(
|
|
4027
|
+
`Failed to load configuration from ${path16.relative(projectRoot, configFile)}:
|
|
4028
|
+
${errorMessage}
|
|
4029
|
+
\u{1F4A1} Suggestion: Check that your config file exports a valid configuration object`
|
|
4030
|
+
);
|
|
3317
4031
|
}
|
|
3318
4032
|
}
|
|
3319
4033
|
}
|
|
3320
4034
|
const config = deepMerge(DEFAULT_CONFIG, userConfig);
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
4035
|
+
try {
|
|
4036
|
+
validateConfig(config, projectRoot);
|
|
4037
|
+
} catch (error) {
|
|
4038
|
+
if (error instanceof ConfigValidationError) {
|
|
4039
|
+
if (loadedConfigFile) {
|
|
4040
|
+
error.message = `[Configuration Error in ${loadedConfigFile}]
|
|
4041
|
+
|
|
4042
|
+
${error.message}`;
|
|
4043
|
+
}
|
|
4044
|
+
throw error;
|
|
4045
|
+
}
|
|
4046
|
+
throw error;
|
|
3324
4047
|
}
|
|
3325
4048
|
return config;
|
|
3326
4049
|
}
|
|
@@ -3337,14 +4060,14 @@ function getStaticDir(projectRoot, config) {
|
|
|
3337
4060
|
// modules/server/setup.ts
|
|
3338
4061
|
function setupServer(app, options) {
|
|
3339
4062
|
const { projectRoot, appDir, isDev, config } = options;
|
|
3340
|
-
const routeLoader = isDev ? new FilesystemRouteLoader(appDir) : new ManifestRouteLoader(projectRoot);
|
|
4063
|
+
const routeLoader = isDev ? new FilesystemRouteLoader(appDir, projectRoot) : new ManifestRouteLoader(projectRoot);
|
|
3341
4064
|
if (isDev) {
|
|
3342
4065
|
let getRoutes2 = function() {
|
|
3343
4066
|
clearAppRequireCache(appDir);
|
|
3344
|
-
|
|
4067
|
+
sharedLoader.invalidateCache();
|
|
3345
4068
|
return {
|
|
3346
|
-
routes:
|
|
3347
|
-
apiRoutes:
|
|
4069
|
+
routes: sharedLoader.loadRoutes(),
|
|
4070
|
+
apiRoutes: sharedLoader.loadApiRoutes()
|
|
3348
4071
|
};
|
|
3349
4072
|
};
|
|
3350
4073
|
var getRoutes = getRoutes2;
|
|
@@ -3358,19 +4081,20 @@ function setupServer(app, options) {
|
|
|
3358
4081
|
console.log(`[hot-reload] Cleared require cache for: ${rel}`);
|
|
3359
4082
|
}
|
|
3360
4083
|
if (isPageFile) {
|
|
3361
|
-
const loader = new FilesystemRouteLoader(appDir);
|
|
4084
|
+
const loader = new FilesystemRouteLoader(appDir, projectRoot);
|
|
3362
4085
|
const newRoutes = loader.loadRoutes();
|
|
3363
4086
|
writeClientRoutesManifest(newRoutes, projectRoot);
|
|
3364
4087
|
console.log("[hot-reload] Client routes manifest reloaded");
|
|
3365
4088
|
}
|
|
3366
4089
|
};
|
|
3367
|
-
setupHotReload({ app, appDir, waitForBuild, onFileChange });
|
|
4090
|
+
setupHotReload({ app, appDir, projectRoot, waitForBuild, onFileChange });
|
|
3368
4091
|
app.use("/static", express.static(outDir));
|
|
3369
4092
|
const routes = routeLoader.loadRoutes();
|
|
3370
4093
|
const wssRoutes = routeLoader.loadWssRoutes();
|
|
3371
4094
|
const notFoundPage = routeLoader.loadNotFoundRoute();
|
|
3372
4095
|
const errorPage = routeLoader.loadErrorRoute();
|
|
3373
4096
|
writeClientRoutesManifest(routes, projectRoot);
|
|
4097
|
+
const sharedLoader = new FilesystemRouteLoader(appDir, projectRoot);
|
|
3374
4098
|
return {
|
|
3375
4099
|
routes,
|
|
3376
4100
|
wssRoutes,
|
|
@@ -3458,90 +4182,10 @@ function sanitizeQuery(query) {
|
|
|
3458
4182
|
|
|
3459
4183
|
// modules/server/middleware/rate-limit.ts
|
|
3460
4184
|
import rateLimit from "express-rate-limit";
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
max = 100,
|
|
3466
|
-
// limit each IP to 100 requests per windowMs
|
|
3467
|
-
message = "Too many requests from this IP, please try again later.",
|
|
3468
|
-
standardHeaders = true,
|
|
3469
|
-
legacyHeaders = false,
|
|
3470
|
-
skipSuccessfulRequests = false,
|
|
3471
|
-
skipFailedRequests = false
|
|
3472
|
-
} = config;
|
|
3473
|
-
return rateLimit({
|
|
3474
|
-
windowMs,
|
|
3475
|
-
max,
|
|
3476
|
-
message: {
|
|
3477
|
-
error: message
|
|
3478
|
-
},
|
|
3479
|
-
standardHeaders,
|
|
3480
|
-
legacyHeaders,
|
|
3481
|
-
skipSuccessfulRequests,
|
|
3482
|
-
skipFailedRequests
|
|
3483
|
-
});
|
|
3484
|
-
}
|
|
3485
|
-
var defaultRateLimiter = createRateLimiter({
|
|
3486
|
-
windowMs: 15 * 60 * 1e3,
|
|
3487
|
-
// 15 minutes
|
|
3488
|
-
max: 100,
|
|
3489
|
-
message: "Too many requests from this IP, please try again later."
|
|
3490
|
-
});
|
|
3491
|
-
var strictRateLimiter = createRateLimiter({
|
|
3492
|
-
windowMs: 15 * 60 * 1e3,
|
|
3493
|
-
// 15 minutes
|
|
3494
|
-
max: 5,
|
|
3495
|
-
message: "Too many authentication attempts, please try again later."
|
|
3496
|
-
});
|
|
3497
|
-
var lenientRateLimiter = createRateLimiter({
|
|
3498
|
-
windowMs: 15 * 60 * 1e3,
|
|
3499
|
-
// 15 minutes
|
|
3500
|
-
max: 200,
|
|
3501
|
-
message: "Too many requests from this IP, please try again later."
|
|
3502
|
-
});
|
|
3503
|
-
|
|
3504
|
-
// modules/server/middleware/auto-rate-limit.ts
|
|
3505
|
-
function matchesStrictPattern(path25, patterns) {
|
|
3506
|
-
for (const pattern of patterns) {
|
|
3507
|
-
const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\//g, "\\/");
|
|
3508
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
3509
|
-
if (regex.test(path25)) {
|
|
3510
|
-
return true;
|
|
3511
|
-
}
|
|
3512
|
-
}
|
|
3513
|
-
return false;
|
|
3514
|
-
}
|
|
3515
|
-
function isRateLimiter(mw) {
|
|
3516
|
-
if (!mw) return false;
|
|
3517
|
-
if (mw === strictRateLimiter || mw === defaultRateLimiter || mw === lenientRateLimiter) {
|
|
3518
|
-
return true;
|
|
3519
|
-
}
|
|
3520
|
-
if (typeof mw === "function" && mw.name && mw.name.includes("rateLimit")) {
|
|
3521
|
-
return true;
|
|
3522
|
-
}
|
|
3523
|
-
if (mw && typeof mw === "function" && mw.skip || mw.resetKey) {
|
|
3524
|
-
return true;
|
|
3525
|
-
}
|
|
3526
|
-
return false;
|
|
3527
|
-
}
|
|
3528
|
-
function getAutoRateLimiter(route, strictPatterns = []) {
|
|
3529
|
-
const hasRateLimiter = route.middlewares?.some(isRateLimiter) || Object.values(route.methodMiddlewares || {}).some(
|
|
3530
|
-
(mws) => mws?.some(isRateLimiter)
|
|
3531
|
-
);
|
|
3532
|
-
if (hasRateLimiter) {
|
|
3533
|
-
return null;
|
|
3534
|
-
}
|
|
3535
|
-
if (strictPatterns.length > 0 && matchesStrictPattern(route.pattern, strictPatterns)) {
|
|
3536
|
-
console.log(`[rate-limit] Applying strict rate limiter to route: ${route.pattern}`);
|
|
3537
|
-
return strictRateLimiter;
|
|
3538
|
-
}
|
|
3539
|
-
return null;
|
|
3540
|
-
}
|
|
3541
|
-
|
|
3542
|
-
// modules/logger/index.ts
|
|
3543
|
-
import pino from "pino";
|
|
3544
|
-
function createLogger(options = {}) {
|
|
4185
|
+
|
|
4186
|
+
// modules/logger/index.ts
|
|
4187
|
+
import pino from "pino";
|
|
4188
|
+
function createLogger(options = {}) {
|
|
3545
4189
|
const {
|
|
3546
4190
|
level = process.env.LOG_LEVEL || (process.env.NODE_ENV === "development" ? "debug" : "info"),
|
|
3547
4191
|
enabled = process.env.LOG_ENABLED !== "false",
|
|
@@ -3597,8 +4241,8 @@ function resetLogger() {
|
|
|
3597
4241
|
loggerInstance = null;
|
|
3598
4242
|
}
|
|
3599
4243
|
var Logger = class _Logger {
|
|
3600
|
-
constructor(
|
|
3601
|
-
this.pino =
|
|
4244
|
+
constructor(logger5, context = {}) {
|
|
4245
|
+
this.pino = logger5 || getLogger();
|
|
3602
4246
|
this.context = context;
|
|
3603
4247
|
}
|
|
3604
4248
|
/**
|
|
@@ -3675,12 +4319,12 @@ var DEFAULT_IGNORED_PATHS = [
|
|
|
3675
4319
|
/^\/sockjs-node/
|
|
3676
4320
|
// Hot reload websocket
|
|
3677
4321
|
];
|
|
3678
|
-
function shouldIgnorePath(
|
|
4322
|
+
function shouldIgnorePath(path28, ignoredPaths) {
|
|
3679
4323
|
return ignoredPaths.some((pattern) => {
|
|
3680
4324
|
if (typeof pattern === "string") {
|
|
3681
|
-
return
|
|
4325
|
+
return path28 === pattern || path28.startsWith(pattern);
|
|
3682
4326
|
}
|
|
3683
|
-
return pattern.test(
|
|
4327
|
+
return pattern.test(path28);
|
|
3684
4328
|
});
|
|
3685
4329
|
}
|
|
3686
4330
|
function requestLoggerMiddleware(options = {}) {
|
|
@@ -3731,6 +4375,151 @@ function getRequestLogger(req) {
|
|
|
3731
4375
|
return req.logger || logger.child({ requestId: "unknown" });
|
|
3732
4376
|
}
|
|
3733
4377
|
|
|
4378
|
+
// modules/server/middleware/rate-limit.ts
|
|
4379
|
+
var logger2 = createModuleLogger("rate-limit");
|
|
4380
|
+
function validateRateLimitConfig(config) {
|
|
4381
|
+
if (config.windowMs !== void 0 && (config.windowMs < 1e3 || !Number.isInteger(config.windowMs))) {
|
|
4382
|
+
throw new Error(
|
|
4383
|
+
`Invalid rateLimit.windowMs: ${config.windowMs}. Must be an integer >= 1000 (milliseconds)`
|
|
4384
|
+
);
|
|
4385
|
+
}
|
|
4386
|
+
if (config.max !== void 0 && (config.max < 1 || !Number.isInteger(config.max))) {
|
|
4387
|
+
throw new Error(
|
|
4388
|
+
`Invalid rateLimit.max: ${config.max}. Must be an integer >= 1`
|
|
4389
|
+
);
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
function createRateLimiter(config = {}) {
|
|
4393
|
+
validateRateLimitConfig(config);
|
|
4394
|
+
const {
|
|
4395
|
+
windowMs = 15 * 60 * 1e3,
|
|
4396
|
+
// 15 minutes
|
|
4397
|
+
max = 100,
|
|
4398
|
+
// limit each IP to 100 requests per windowMs
|
|
4399
|
+
message = "Too many requests from this IP, please try again later.",
|
|
4400
|
+
standardHeaders = true,
|
|
4401
|
+
legacyHeaders = false,
|
|
4402
|
+
skipSuccessfulRequests = false,
|
|
4403
|
+
skipFailedRequests = false,
|
|
4404
|
+
keyGenerator,
|
|
4405
|
+
skip
|
|
4406
|
+
} = config;
|
|
4407
|
+
const limiter = rateLimit({
|
|
4408
|
+
windowMs,
|
|
4409
|
+
max,
|
|
4410
|
+
message: {
|
|
4411
|
+
error: message,
|
|
4412
|
+
retryAfter: Math.ceil(windowMs / 1e3)
|
|
4413
|
+
// seconds until retry
|
|
4414
|
+
},
|
|
4415
|
+
standardHeaders,
|
|
4416
|
+
legacyHeaders,
|
|
4417
|
+
skipSuccessfulRequests,
|
|
4418
|
+
skipFailedRequests,
|
|
4419
|
+
keyGenerator,
|
|
4420
|
+
skip
|
|
4421
|
+
});
|
|
4422
|
+
const wrappedLimiter = (req, res, next) => {
|
|
4423
|
+
limiter(req, res, (err) => {
|
|
4424
|
+
if (err && res.statusCode === 429) {
|
|
4425
|
+
const ip = req.ip || req.connection?.remoteAddress || "unknown";
|
|
4426
|
+
logger2.warn("Rate limit exceeded", {
|
|
4427
|
+
ip,
|
|
4428
|
+
path: req.path,
|
|
4429
|
+
method: req.method,
|
|
4430
|
+
limit: max,
|
|
4431
|
+
windowMs,
|
|
4432
|
+
retryAfter: Math.ceil(windowMs / 1e3)
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
if (err) {
|
|
4436
|
+
return next(err);
|
|
4437
|
+
}
|
|
4438
|
+
next();
|
|
4439
|
+
});
|
|
4440
|
+
};
|
|
4441
|
+
Object.setPrototypeOf(wrappedLimiter, limiter);
|
|
4442
|
+
Object.assign(wrappedLimiter, limiter);
|
|
4443
|
+
return wrappedLimiter;
|
|
4444
|
+
}
|
|
4445
|
+
var defaultRateLimiter = createRateLimiter({
|
|
4446
|
+
windowMs: 15 * 60 * 1e3,
|
|
4447
|
+
// 15 minutes
|
|
4448
|
+
max: 100,
|
|
4449
|
+
message: "Too many requests from this IP, please try again later."
|
|
4450
|
+
});
|
|
4451
|
+
var strictRateLimiter = createRateLimiter({
|
|
4452
|
+
windowMs: 15 * 60 * 1e3,
|
|
4453
|
+
// 15 minutes
|
|
4454
|
+
max: 5,
|
|
4455
|
+
message: "Too many authentication attempts, please try again later."
|
|
4456
|
+
});
|
|
4457
|
+
var lenientRateLimiter = createRateLimiter({
|
|
4458
|
+
windowMs: 15 * 60 * 1e3,
|
|
4459
|
+
// 15 minutes
|
|
4460
|
+
max: 200,
|
|
4461
|
+
message: "Too many requests from this IP, please try again later."
|
|
4462
|
+
});
|
|
4463
|
+
function createRateLimiterFromConfig(config, useApiMax = false) {
|
|
4464
|
+
if (!config) return null;
|
|
4465
|
+
const max = useApiMax ? config.apiMax ?? config.max ?? 100 : config.max ?? 100;
|
|
4466
|
+
const windowMs = config.windowMs ?? 15 * 60 * 1e3;
|
|
4467
|
+
return createRateLimiter({
|
|
4468
|
+
windowMs,
|
|
4469
|
+
max,
|
|
4470
|
+
message: `Too many requests from this IP, please try again after ${Math.ceil(windowMs / 1e3)} seconds.`
|
|
4471
|
+
});
|
|
4472
|
+
}
|
|
4473
|
+
function createStrictRateLimiterFromConfig(config) {
|
|
4474
|
+
if (!config || config.strictMax === void 0) {
|
|
4475
|
+
return strictRateLimiter;
|
|
4476
|
+
}
|
|
4477
|
+
const windowMs = config.windowMs ?? 15 * 60 * 1e3;
|
|
4478
|
+
return createRateLimiter({
|
|
4479
|
+
windowMs,
|
|
4480
|
+
max: config.strictMax,
|
|
4481
|
+
message: `Too many authentication attempts, please try again after ${Math.ceil(windowMs / 1e3)} seconds.`
|
|
4482
|
+
});
|
|
4483
|
+
}
|
|
4484
|
+
|
|
4485
|
+
// modules/server/middleware/auto-rate-limit.ts
|
|
4486
|
+
function matchesStrictPattern(path28, patterns) {
|
|
4487
|
+
for (const pattern of patterns) {
|
|
4488
|
+
const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\//g, "\\/");
|
|
4489
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
4490
|
+
if (regex.test(path28)) {
|
|
4491
|
+
return true;
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
return false;
|
|
4495
|
+
}
|
|
4496
|
+
function isRateLimiter(mw) {
|
|
4497
|
+
if (!mw) return false;
|
|
4498
|
+
if (mw === strictRateLimiter || mw === defaultRateLimiter || mw === lenientRateLimiter) {
|
|
4499
|
+
return true;
|
|
4500
|
+
}
|
|
4501
|
+
if (typeof mw === "function" && mw.name && mw.name.includes("rateLimit")) {
|
|
4502
|
+
return true;
|
|
4503
|
+
}
|
|
4504
|
+
if (mw && typeof mw === "function" && mw.skip || mw.resetKey) {
|
|
4505
|
+
return true;
|
|
4506
|
+
}
|
|
4507
|
+
return false;
|
|
4508
|
+
}
|
|
4509
|
+
function getAutoRateLimiter(route, strictPatterns = [], rateLimitConfig) {
|
|
4510
|
+
const hasRateLimiter = route.middlewares?.some(isRateLimiter) || Object.values(route.methodMiddlewares || {}).some(
|
|
4511
|
+
(mws) => mws?.some(isRateLimiter)
|
|
4512
|
+
);
|
|
4513
|
+
if (hasRateLimiter) {
|
|
4514
|
+
return null;
|
|
4515
|
+
}
|
|
4516
|
+
if (strictPatterns.length > 0 && matchesStrictPattern(route.pattern, strictPatterns)) {
|
|
4517
|
+
const limiter = rateLimitConfig ? createStrictRateLimiterFromConfig(rateLimitConfig) : strictRateLimiter;
|
|
4518
|
+
return limiter;
|
|
4519
|
+
}
|
|
4520
|
+
return null;
|
|
4521
|
+
}
|
|
4522
|
+
|
|
3734
4523
|
// modules/server/handlers/api.ts
|
|
3735
4524
|
async function handleApiRequest(options) {
|
|
3736
4525
|
const { apiRoutes, urlPath, req, res, env = "dev" } = options;
|
|
@@ -3762,7 +4551,8 @@ async function handleApiRequest(options) {
|
|
|
3762
4551
|
try {
|
|
3763
4552
|
const autoRateLimiter = getAutoRateLimiter(
|
|
3764
4553
|
route,
|
|
3765
|
-
options.strictRateLimitPatterns
|
|
4554
|
+
options.strictRateLimitPatterns,
|
|
4555
|
+
options.rateLimitConfig
|
|
3766
4556
|
);
|
|
3767
4557
|
const reqLogger = getRequestLogger(req);
|
|
3768
4558
|
if (autoRateLimiter) {
|
|
@@ -3774,26 +4564,46 @@ async function handleApiRequest(options) {
|
|
|
3774
4564
|
const globalMws = route.middlewares ?? [];
|
|
3775
4565
|
const perMethodMws = route.methodMiddlewares?.[method] ?? [];
|
|
3776
4566
|
const chain = autoRateLimiter ? [autoRateLimiter, ...globalMws, ...perMethodMws] : [...globalMws, ...perMethodMws];
|
|
3777
|
-
for (
|
|
3778
|
-
const
|
|
3779
|
-
if (
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
4567
|
+
for (let i = 0; i < chain.length; i++) {
|
|
4568
|
+
const mw = chain[i];
|
|
4569
|
+
if (typeof mw !== "function") {
|
|
4570
|
+
reqLogger.warn("Invalid middleware in chain", {
|
|
4571
|
+
route: route.pattern,
|
|
4572
|
+
method,
|
|
4573
|
+
middlewareIndex: i,
|
|
4574
|
+
middlewareType: typeof mw
|
|
4575
|
+
});
|
|
4576
|
+
continue;
|
|
4577
|
+
}
|
|
4578
|
+
try {
|
|
4579
|
+
const isExpressRateLimit = mw.skip || mw.resetKey || mw.name?.includes("rateLimit");
|
|
4580
|
+
if (isExpressRateLimit) {
|
|
4581
|
+
await new Promise((resolve3, reject) => {
|
|
4582
|
+
const next = (err) => {
|
|
4583
|
+
if (err) reject(err);
|
|
4584
|
+
else resolve3();
|
|
4585
|
+
};
|
|
4586
|
+
try {
|
|
4587
|
+
const result = mw(req, res, next);
|
|
4588
|
+
if (result && typeof result.then === "function") {
|
|
4589
|
+
result.then(() => resolve3()).catch(reject);
|
|
4590
|
+
}
|
|
4591
|
+
} catch (err) {
|
|
4592
|
+
reject(err);
|
|
3789
4593
|
}
|
|
3790
|
-
}
|
|
3791
|
-
|
|
3792
|
-
|
|
4594
|
+
});
|
|
4595
|
+
} else {
|
|
4596
|
+
await Promise.resolve(mw(ctx, async () => {
|
|
4597
|
+
}));
|
|
4598
|
+
}
|
|
4599
|
+
} catch (error) {
|
|
4600
|
+
reqLogger.error("API middleware failed", error instanceof Error ? error : new Error(String(error)), {
|
|
4601
|
+
route: route.pattern,
|
|
4602
|
+
method,
|
|
4603
|
+
middlewareIndex: i,
|
|
4604
|
+
middlewareName: mw.name || "anonymous"
|
|
3793
4605
|
});
|
|
3794
|
-
|
|
3795
|
-
await Promise.resolve(mw(ctx, async () => {
|
|
3796
|
-
}));
|
|
4606
|
+
throw error;
|
|
3797
4607
|
}
|
|
3798
4608
|
if (res.headersSent) {
|
|
3799
4609
|
return;
|
|
@@ -3844,34 +4654,260 @@ function createDocumentTree(options) {
|
|
|
3844
4654
|
titleFallback,
|
|
3845
4655
|
descriptionFallback,
|
|
3846
4656
|
chunkHref,
|
|
4657
|
+
entrypointFiles = [],
|
|
3847
4658
|
theme,
|
|
3848
4659
|
clientJsPath = "/static/client.js",
|
|
3849
4660
|
clientCssPath = "/static/client.css",
|
|
3850
|
-
nonce
|
|
4661
|
+
nonce,
|
|
4662
|
+
includeInlineScripts = true
|
|
4663
|
+
// Default true - scripts inline in body for both SSR and SSG
|
|
3851
4664
|
} = options;
|
|
3852
|
-
const metaObj = meta ??
|
|
3853
|
-
const title = metaObj
|
|
3854
|
-
const lang = metaObj
|
|
3855
|
-
const description = metaObj
|
|
4665
|
+
const metaObj = meta ?? null;
|
|
4666
|
+
const title = metaObj?.title ?? titleFallback ?? "My Framework Dev";
|
|
4667
|
+
const lang = metaObj?.lang ?? "en";
|
|
4668
|
+
const description = metaObj?.description ?? descriptionFallback ?? "Demo Loly framework";
|
|
3856
4669
|
const extraMetaTags = [];
|
|
4670
|
+
const linkTags = [];
|
|
3857
4671
|
if (description) {
|
|
3858
4672
|
extraMetaTags.push(
|
|
3859
4673
|
React.createElement("meta", {
|
|
4674
|
+
key: "meta-description",
|
|
3860
4675
|
name: "description",
|
|
3861
4676
|
content: description
|
|
3862
4677
|
})
|
|
3863
4678
|
);
|
|
3864
4679
|
}
|
|
3865
|
-
if (
|
|
3866
|
-
|
|
4680
|
+
if (metaObj?.robots) {
|
|
4681
|
+
extraMetaTags.push(
|
|
4682
|
+
React.createElement("meta", {
|
|
4683
|
+
key: "meta-robots",
|
|
4684
|
+
name: "robots",
|
|
4685
|
+
content: metaObj.robots
|
|
4686
|
+
})
|
|
4687
|
+
);
|
|
4688
|
+
}
|
|
4689
|
+
if (metaObj?.themeColor) {
|
|
4690
|
+
extraMetaTags.push(
|
|
4691
|
+
React.createElement("meta", {
|
|
4692
|
+
key: "meta-theme-color",
|
|
4693
|
+
name: "theme-color",
|
|
4694
|
+
content: metaObj.themeColor
|
|
4695
|
+
})
|
|
4696
|
+
);
|
|
4697
|
+
}
|
|
4698
|
+
if (metaObj?.viewport) {
|
|
4699
|
+
extraMetaTags.push(
|
|
4700
|
+
React.createElement("meta", {
|
|
4701
|
+
key: "meta-viewport",
|
|
4702
|
+
name: "viewport",
|
|
4703
|
+
content: metaObj.viewport
|
|
4704
|
+
})
|
|
4705
|
+
);
|
|
4706
|
+
}
|
|
4707
|
+
if (metaObj?.canonical) {
|
|
4708
|
+
linkTags.push(
|
|
4709
|
+
React.createElement("link", {
|
|
4710
|
+
key: "link-canonical",
|
|
4711
|
+
rel: "canonical",
|
|
4712
|
+
href: metaObj.canonical
|
|
4713
|
+
})
|
|
4714
|
+
);
|
|
4715
|
+
}
|
|
4716
|
+
if (metaObj?.openGraph) {
|
|
4717
|
+
const og = metaObj.openGraph;
|
|
4718
|
+
if (og.title) {
|
|
4719
|
+
extraMetaTags.push(
|
|
4720
|
+
React.createElement("meta", {
|
|
4721
|
+
key: "og-title",
|
|
4722
|
+
property: "og:title",
|
|
4723
|
+
content: og.title
|
|
4724
|
+
})
|
|
4725
|
+
);
|
|
4726
|
+
}
|
|
4727
|
+
if (og.description) {
|
|
4728
|
+
extraMetaTags.push(
|
|
4729
|
+
React.createElement("meta", {
|
|
4730
|
+
key: "og-description",
|
|
4731
|
+
property: "og:description",
|
|
4732
|
+
content: og.description
|
|
4733
|
+
})
|
|
4734
|
+
);
|
|
4735
|
+
}
|
|
4736
|
+
if (og.type) {
|
|
4737
|
+
extraMetaTags.push(
|
|
4738
|
+
React.createElement("meta", {
|
|
4739
|
+
key: "og-type",
|
|
4740
|
+
property: "og:type",
|
|
4741
|
+
content: og.type
|
|
4742
|
+
})
|
|
4743
|
+
);
|
|
4744
|
+
}
|
|
4745
|
+
if (og.url) {
|
|
4746
|
+
extraMetaTags.push(
|
|
4747
|
+
React.createElement("meta", {
|
|
4748
|
+
key: "og-url",
|
|
4749
|
+
property: "og:url",
|
|
4750
|
+
content: og.url
|
|
4751
|
+
})
|
|
4752
|
+
);
|
|
4753
|
+
}
|
|
4754
|
+
if (og.image) {
|
|
4755
|
+
if (typeof og.image === "string") {
|
|
4756
|
+
extraMetaTags.push(
|
|
4757
|
+
React.createElement("meta", {
|
|
4758
|
+
key: "og-image",
|
|
4759
|
+
property: "og:image",
|
|
4760
|
+
content: og.image
|
|
4761
|
+
})
|
|
4762
|
+
);
|
|
4763
|
+
} else {
|
|
4764
|
+
extraMetaTags.push(
|
|
4765
|
+
React.createElement("meta", {
|
|
4766
|
+
key: "og-image",
|
|
4767
|
+
property: "og:image",
|
|
4768
|
+
content: og.image.url
|
|
4769
|
+
})
|
|
4770
|
+
);
|
|
4771
|
+
if (og.image.width) {
|
|
4772
|
+
extraMetaTags.push(
|
|
4773
|
+
React.createElement("meta", {
|
|
4774
|
+
key: "og-image-width",
|
|
4775
|
+
property: "og:image:width",
|
|
4776
|
+
content: String(og.image.width)
|
|
4777
|
+
})
|
|
4778
|
+
);
|
|
4779
|
+
}
|
|
4780
|
+
if (og.image.height) {
|
|
4781
|
+
extraMetaTags.push(
|
|
4782
|
+
React.createElement("meta", {
|
|
4783
|
+
key: "og-image-height",
|
|
4784
|
+
property: "og:image:height",
|
|
4785
|
+
content: String(og.image.height)
|
|
4786
|
+
})
|
|
4787
|
+
);
|
|
4788
|
+
}
|
|
4789
|
+
if (og.image.alt) {
|
|
4790
|
+
extraMetaTags.push(
|
|
4791
|
+
React.createElement("meta", {
|
|
4792
|
+
key: "og-image-alt",
|
|
4793
|
+
property: "og:image:alt",
|
|
4794
|
+
content: og.image.alt
|
|
4795
|
+
})
|
|
4796
|
+
);
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
if (og.siteName) {
|
|
4801
|
+
extraMetaTags.push(
|
|
4802
|
+
React.createElement("meta", {
|
|
4803
|
+
key: "og-site-name",
|
|
4804
|
+
property: "og:site_name",
|
|
4805
|
+
content: og.siteName
|
|
4806
|
+
})
|
|
4807
|
+
);
|
|
4808
|
+
}
|
|
4809
|
+
if (og.locale) {
|
|
4810
|
+
extraMetaTags.push(
|
|
4811
|
+
React.createElement("meta", {
|
|
4812
|
+
key: "og-locale",
|
|
4813
|
+
property: "og:locale",
|
|
4814
|
+
content: og.locale
|
|
4815
|
+
})
|
|
4816
|
+
);
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
if (metaObj?.twitter) {
|
|
4820
|
+
const twitter = metaObj.twitter;
|
|
4821
|
+
if (twitter.card) {
|
|
4822
|
+
extraMetaTags.push(
|
|
4823
|
+
React.createElement("meta", {
|
|
4824
|
+
key: "twitter-card",
|
|
4825
|
+
name: "twitter:card",
|
|
4826
|
+
content: twitter.card
|
|
4827
|
+
})
|
|
4828
|
+
);
|
|
4829
|
+
}
|
|
4830
|
+
if (twitter.title) {
|
|
4831
|
+
extraMetaTags.push(
|
|
4832
|
+
React.createElement("meta", {
|
|
4833
|
+
key: "twitter-title",
|
|
4834
|
+
name: "twitter:title",
|
|
4835
|
+
content: twitter.title
|
|
4836
|
+
})
|
|
4837
|
+
);
|
|
4838
|
+
}
|
|
4839
|
+
if (twitter.description) {
|
|
4840
|
+
extraMetaTags.push(
|
|
4841
|
+
React.createElement("meta", {
|
|
4842
|
+
key: "twitter-description",
|
|
4843
|
+
name: "twitter:description",
|
|
4844
|
+
content: twitter.description
|
|
4845
|
+
})
|
|
4846
|
+
);
|
|
4847
|
+
}
|
|
4848
|
+
if (twitter.image) {
|
|
4849
|
+
extraMetaTags.push(
|
|
4850
|
+
React.createElement("meta", {
|
|
4851
|
+
key: "twitter-image",
|
|
4852
|
+
name: "twitter:image",
|
|
4853
|
+
content: twitter.image
|
|
4854
|
+
})
|
|
4855
|
+
);
|
|
4856
|
+
}
|
|
4857
|
+
if (twitter.imageAlt) {
|
|
3867
4858
|
extraMetaTags.push(
|
|
3868
4859
|
React.createElement("meta", {
|
|
4860
|
+
key: "twitter-image-alt",
|
|
4861
|
+
name: "twitter:image:alt",
|
|
4862
|
+
content: twitter.imageAlt
|
|
4863
|
+
})
|
|
4864
|
+
);
|
|
4865
|
+
}
|
|
4866
|
+
if (twitter.site) {
|
|
4867
|
+
extraMetaTags.push(
|
|
4868
|
+
React.createElement("meta", {
|
|
4869
|
+
key: "twitter-site",
|
|
4870
|
+
name: "twitter:site",
|
|
4871
|
+
content: twitter.site
|
|
4872
|
+
})
|
|
4873
|
+
);
|
|
4874
|
+
}
|
|
4875
|
+
if (twitter.creator) {
|
|
4876
|
+
extraMetaTags.push(
|
|
4877
|
+
React.createElement("meta", {
|
|
4878
|
+
key: "twitter-creator",
|
|
4879
|
+
name: "twitter:creator",
|
|
4880
|
+
content: twitter.creator
|
|
4881
|
+
})
|
|
4882
|
+
);
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
if (metaObj?.metaTags && Array.isArray(metaObj.metaTags)) {
|
|
4886
|
+
metaObj.metaTags.forEach((tag, index) => {
|
|
4887
|
+
extraMetaTags.push(
|
|
4888
|
+
React.createElement("meta", {
|
|
4889
|
+
key: `meta-custom-${index}`,
|
|
3869
4890
|
name: tag.name,
|
|
3870
4891
|
property: tag.property,
|
|
4892
|
+
httpEquiv: tag.httpEquiv,
|
|
3871
4893
|
content: tag.content
|
|
3872
4894
|
})
|
|
3873
4895
|
);
|
|
3874
|
-
}
|
|
4896
|
+
});
|
|
4897
|
+
}
|
|
4898
|
+
if (metaObj?.links && Array.isArray(metaObj.links)) {
|
|
4899
|
+
metaObj.links.forEach((link, index) => {
|
|
4900
|
+
linkTags.push(
|
|
4901
|
+
React.createElement("link", {
|
|
4902
|
+
key: `link-custom-${index}`,
|
|
4903
|
+
rel: link.rel,
|
|
4904
|
+
href: link.href,
|
|
4905
|
+
as: link.as,
|
|
4906
|
+
crossOrigin: link.crossorigin,
|
|
4907
|
+
type: link.type
|
|
4908
|
+
})
|
|
4909
|
+
);
|
|
4910
|
+
});
|
|
3875
4911
|
}
|
|
3876
4912
|
const serialized = JSON.stringify({
|
|
3877
4913
|
...initialData,
|
|
@@ -3880,6 +4916,27 @@ function createDocumentTree(options) {
|
|
|
3880
4916
|
const routerSerialized = JSON.stringify({
|
|
3881
4917
|
...routerData
|
|
3882
4918
|
});
|
|
4919
|
+
const bodyChildren = [
|
|
4920
|
+
React.createElement("div", { id: APP_CONTAINER_ID }, appTree)
|
|
4921
|
+
];
|
|
4922
|
+
if (includeInlineScripts) {
|
|
4923
|
+
bodyChildren.push(
|
|
4924
|
+
React.createElement("script", {
|
|
4925
|
+
key: "initial-data",
|
|
4926
|
+
nonce,
|
|
4927
|
+
dangerouslySetInnerHTML: {
|
|
4928
|
+
__html: `window.${WINDOW_DATA_KEY} = ${serialized};`
|
|
4929
|
+
}
|
|
4930
|
+
}),
|
|
4931
|
+
React.createElement("script", {
|
|
4932
|
+
key: "router-data",
|
|
4933
|
+
nonce,
|
|
4934
|
+
dangerouslySetInnerHTML: {
|
|
4935
|
+
__html: `window.${ROUTER_DATA_KEY} = ${routerSerialized};`
|
|
4936
|
+
}
|
|
4937
|
+
})
|
|
4938
|
+
);
|
|
4939
|
+
}
|
|
3883
4940
|
const documentTree = React.createElement(
|
|
3884
4941
|
"html",
|
|
3885
4942
|
{ lang },
|
|
@@ -3888,13 +4945,25 @@ function createDocumentTree(options) {
|
|
|
3888
4945
|
null,
|
|
3889
4946
|
React.createElement("meta", { charSet: "utf-8" }),
|
|
3890
4947
|
React.createElement("title", null, title),
|
|
4948
|
+
// Viewport: use custom if provided, otherwise default
|
|
3891
4949
|
React.createElement("meta", {
|
|
3892
4950
|
name: "viewport",
|
|
3893
|
-
content: "width=device-width, initial-scale=1"
|
|
4951
|
+
content: metaObj?.viewport ?? "width=device-width, initial-scale=1"
|
|
3894
4952
|
}),
|
|
3895
4953
|
...extraMetaTags,
|
|
4954
|
+
...linkTags,
|
|
4955
|
+
...entrypointFiles.length > 0 ? entrypointFiles.slice(0, -1).map(
|
|
4956
|
+
(file) => React.createElement("link", {
|
|
4957
|
+
key: `preload-${file}`,
|
|
4958
|
+
rel: "preload",
|
|
4959
|
+
href: file,
|
|
4960
|
+
as: "script"
|
|
4961
|
+
})
|
|
4962
|
+
) : [],
|
|
4963
|
+
// Preload route-specific chunk if available
|
|
3896
4964
|
chunkHref && React.createElement("link", {
|
|
3897
|
-
|
|
4965
|
+
key: `preload-${chunkHref}`,
|
|
4966
|
+
rel: "preload",
|
|
3898
4967
|
href: chunkHref,
|
|
3899
4968
|
as: "script"
|
|
3900
4969
|
}),
|
|
@@ -3906,34 +4975,35 @@ function createDocumentTree(options) {
|
|
|
3906
4975
|
React.createElement("link", {
|
|
3907
4976
|
rel: "stylesheet",
|
|
3908
4977
|
href: clientCssPath
|
|
3909
|
-
}),
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
4978
|
+
}),
|
|
4979
|
+
...entrypointFiles.length > 0 ? entrypointFiles.map(
|
|
4980
|
+
(file) => React.createElement("script", {
|
|
4981
|
+
key: file,
|
|
4982
|
+
src: file,
|
|
4983
|
+
defer: true,
|
|
4984
|
+
nonce
|
|
4985
|
+
// CSP nonce for external scripts
|
|
4986
|
+
})
|
|
4987
|
+
) : [
|
|
4988
|
+
React.createElement("script", {
|
|
4989
|
+
key: "client",
|
|
4990
|
+
src: clientJsPath,
|
|
4991
|
+
defer: true,
|
|
4992
|
+
nonce
|
|
4993
|
+
// CSP nonce for external scripts
|
|
4994
|
+
})
|
|
4995
|
+
]
|
|
3914
4996
|
),
|
|
3915
4997
|
React.createElement(
|
|
3916
4998
|
"body",
|
|
3917
4999
|
{
|
|
3918
5000
|
style: { margin: 0 },
|
|
3919
|
-
className: [initialData.className, theme].filter(Boolean).join(" "),
|
|
5001
|
+
className: [initialData.className || "", theme].filter(Boolean).join(" "),
|
|
3920
5002
|
suppressHydrationWarning: true
|
|
3921
5003
|
// Allow theme class to differ between server and client initially
|
|
3922
5004
|
},
|
|
3923
|
-
|
|
3924
|
-
)
|
|
3925
|
-
React.createElement("script", {
|
|
3926
|
-
nonce,
|
|
3927
|
-
dangerouslySetInnerHTML: {
|
|
3928
|
-
__html: `window.${WINDOW_DATA_KEY} = ${serialized};`
|
|
3929
|
-
}
|
|
3930
|
-
}),
|
|
3931
|
-
React.createElement("script", {
|
|
3932
|
-
nonce,
|
|
3933
|
-
dangerouslySetInnerHTML: {
|
|
3934
|
-
__html: `window.${ROUTER_DATA_KEY} = ${routerSerialized};`
|
|
3935
|
-
}
|
|
3936
|
-
})
|
|
5005
|
+
...bodyChildren
|
|
5006
|
+
)
|
|
3937
5007
|
);
|
|
3938
5008
|
return documentTree;
|
|
3939
5009
|
}
|
|
@@ -3949,7 +5019,7 @@ function buildInitialData(urlPath, params, loaderResult) {
|
|
|
3949
5019
|
params,
|
|
3950
5020
|
props,
|
|
3951
5021
|
metadata: loaderResult.metadata ?? null,
|
|
3952
|
-
className: loaderResult.className
|
|
5022
|
+
className: loaderResult.className,
|
|
3953
5023
|
error: false,
|
|
3954
5024
|
notFound: false
|
|
3955
5025
|
};
|
|
@@ -3965,24 +5035,83 @@ var buildRouterData = (req) => {
|
|
|
3965
5035
|
};
|
|
3966
5036
|
|
|
3967
5037
|
// modules/server/handlers/middleware.ts
|
|
5038
|
+
import path18 from "path";
|
|
3968
5039
|
async function runRouteMiddlewares(route, ctx) {
|
|
3969
|
-
for (
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
5040
|
+
for (let i = 0; i < route.middlewares.length; i++) {
|
|
5041
|
+
const mw = route.middlewares[i];
|
|
5042
|
+
try {
|
|
5043
|
+
await Promise.resolve(
|
|
5044
|
+
mw(ctx, async () => {
|
|
5045
|
+
})
|
|
5046
|
+
);
|
|
5047
|
+
} catch (error) {
|
|
5048
|
+
const reqLogger = getRequestLogger(ctx.req);
|
|
5049
|
+
const relativePath = route.pageFile ? path18.relative(process.cwd(), route.pageFile) : route.pattern;
|
|
5050
|
+
reqLogger.error("Route middleware failed", error instanceof Error ? error : new Error(String(error)), {
|
|
5051
|
+
route: route.pattern,
|
|
5052
|
+
middlewareIndex: i,
|
|
5053
|
+
pageFile: relativePath
|
|
5054
|
+
});
|
|
5055
|
+
throw error;
|
|
5056
|
+
}
|
|
3974
5057
|
if (ctx.res.headersSent) {
|
|
3975
5058
|
return;
|
|
3976
5059
|
}
|
|
3977
5060
|
}
|
|
3978
5061
|
}
|
|
3979
5062
|
|
|
3980
|
-
// modules/server/handlers/
|
|
3981
|
-
|
|
5063
|
+
// modules/server/handlers/server-hook.ts
|
|
5064
|
+
import path19 from "path";
|
|
5065
|
+
function createServerHookErrorMessage(error, hookType, routePattern, filePath) {
|
|
5066
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5067
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
5068
|
+
let message = `[${hookType.toUpperCase()} SERVER HOOK ERROR]
|
|
5069
|
+
`;
|
|
5070
|
+
message += `Route: ${routePattern}
|
|
5071
|
+
`;
|
|
5072
|
+
if (filePath) {
|
|
5073
|
+
const relativePath = path19.relative(process.cwd(), filePath);
|
|
5074
|
+
message += `File: ${relativePath}
|
|
5075
|
+
`;
|
|
5076
|
+
}
|
|
5077
|
+
message += `Error: ${errorMessage}
|
|
5078
|
+
`;
|
|
5079
|
+
if (errorMessage.includes("Cannot find module")) {
|
|
5080
|
+
message += `
|
|
5081
|
+
\u{1F4A1} Suggestion: Check that all imports in your ${hookType}.server.hook.ts are correct.
|
|
5082
|
+
`;
|
|
5083
|
+
} else if (errorMessage.includes("is not defined") || errorMessage.includes("Cannot read property")) {
|
|
5084
|
+
message += `
|
|
5085
|
+
\u{1F4A1} Suggestion: Verify that all variables and properties exist in your ${hookType}.server.hook.ts.
|
|
5086
|
+
`;
|
|
5087
|
+
} else if (errorMessage.includes("async") || errorMessage.includes("await")) {
|
|
5088
|
+
message += `
|
|
5089
|
+
\u{1F4A1} Suggestion: Make sure getServerSideProps is an async function and all promises are awaited.
|
|
5090
|
+
`;
|
|
5091
|
+
}
|
|
5092
|
+
if (errorStack) {
|
|
5093
|
+
message += `
|
|
5094
|
+
Stack trace:
|
|
5095
|
+
${errorStack}`;
|
|
5096
|
+
}
|
|
5097
|
+
return message;
|
|
5098
|
+
}
|
|
5099
|
+
async function runRouteServerHook(route, ctx) {
|
|
3982
5100
|
if (!route.loader) {
|
|
3983
5101
|
return { props: {} };
|
|
3984
5102
|
}
|
|
3985
|
-
|
|
5103
|
+
try {
|
|
5104
|
+
return await route.loader(ctx);
|
|
5105
|
+
} catch (error) {
|
|
5106
|
+
const detailedError = new Error(
|
|
5107
|
+
createServerHookErrorMessage(error, "page", route.pattern, route.pageFile)
|
|
5108
|
+
);
|
|
5109
|
+
if (error instanceof Error && error.stack) {
|
|
5110
|
+
detailedError.stack = error.stack;
|
|
5111
|
+
}
|
|
5112
|
+
detailedError.originalError = error;
|
|
5113
|
+
throw detailedError;
|
|
5114
|
+
}
|
|
3986
5115
|
}
|
|
3987
5116
|
|
|
3988
5117
|
// modules/server/handlers/response.ts
|
|
@@ -4023,26 +5152,26 @@ function handleNotFound(res, urlPath) {
|
|
|
4023
5152
|
|
|
4024
5153
|
// modules/server/handlers/ssg.ts
|
|
4025
5154
|
import fs15 from "fs";
|
|
4026
|
-
import
|
|
4027
|
-
var
|
|
5155
|
+
import path20 from "path";
|
|
5156
|
+
var logger3 = createModuleLogger("ssg");
|
|
4028
5157
|
function getSsgDirForPath(baseDir, urlPath) {
|
|
4029
5158
|
const clean = urlPath === "/" ? "" : urlPath.replace(/^\/+/, "");
|
|
4030
|
-
return
|
|
5159
|
+
return path20.join(baseDir, clean);
|
|
4031
5160
|
}
|
|
4032
5161
|
function getSsgHtmlPath(baseDir, urlPath) {
|
|
4033
5162
|
const dir = getSsgDirForPath(baseDir, urlPath);
|
|
4034
|
-
return
|
|
5163
|
+
return path20.join(dir, "index.html");
|
|
4035
5164
|
}
|
|
4036
5165
|
function getSsgDataPath(baseDir, urlPath) {
|
|
4037
5166
|
const dir = getSsgDirForPath(baseDir, urlPath);
|
|
4038
|
-
return
|
|
5167
|
+
return path20.join(dir, "data.json");
|
|
4039
5168
|
}
|
|
4040
5169
|
function tryServeSsgHtml(res, ssgOutDir, urlPath) {
|
|
4041
5170
|
const ssgHtmlPath = getSsgHtmlPath(ssgOutDir, urlPath);
|
|
4042
5171
|
if (!fs15.existsSync(ssgHtmlPath)) {
|
|
4043
5172
|
return false;
|
|
4044
5173
|
}
|
|
4045
|
-
|
|
5174
|
+
logger3.info("Serving SSG HTML", { urlPath, ssgHtmlPath });
|
|
4046
5175
|
res.setHeader(
|
|
4047
5176
|
"Content-Security-Policy",
|
|
4048
5177
|
"default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https:; font-src 'self' data: https://fonts.gstatic.com; object-src 'none'; media-src 'self' https:; frame-src 'none';"
|
|
@@ -4064,13 +5193,43 @@ function tryServeSsgData(res, ssgOutDir, urlPath) {
|
|
|
4064
5193
|
res.status(200).end(raw);
|
|
4065
5194
|
return true;
|
|
4066
5195
|
} catch (err) {
|
|
4067
|
-
|
|
5196
|
+
logger3.error("Error reading SSG data", err, { urlPath, ssgDataPath });
|
|
4068
5197
|
return false;
|
|
4069
5198
|
}
|
|
4070
5199
|
}
|
|
4071
5200
|
|
|
4072
5201
|
// modules/server/handlers/pages.ts
|
|
4073
5202
|
init_globals();
|
|
5203
|
+
import path21 from "path";
|
|
5204
|
+
function mergeMetadata(base, override) {
|
|
5205
|
+
if (!base && !override) return null;
|
|
5206
|
+
if (!base) return override;
|
|
5207
|
+
if (!override) return base;
|
|
5208
|
+
return {
|
|
5209
|
+
// Simple fields: override wins
|
|
5210
|
+
title: override.title ?? base.title,
|
|
5211
|
+
description: override.description ?? base.description,
|
|
5212
|
+
lang: override.lang ?? base.lang,
|
|
5213
|
+
canonical: override.canonical ?? base.canonical,
|
|
5214
|
+
robots: override.robots ?? base.robots,
|
|
5215
|
+
themeColor: override.themeColor ?? base.themeColor,
|
|
5216
|
+
viewport: override.viewport ?? base.viewport,
|
|
5217
|
+
// Nested objects: shallow merge (override wins for each field)
|
|
5218
|
+
openGraph: override.openGraph ? {
|
|
5219
|
+
...base.openGraph,
|
|
5220
|
+
...override.openGraph,
|
|
5221
|
+
// For image, if override has image, use it entirely (don't merge)
|
|
5222
|
+
image: override.openGraph.image ?? base.openGraph?.image
|
|
5223
|
+
} : base.openGraph,
|
|
5224
|
+
twitter: override.twitter ? {
|
|
5225
|
+
...base.twitter,
|
|
5226
|
+
...override.twitter
|
|
5227
|
+
} : base.twitter,
|
|
5228
|
+
// Arrays: override replaces base entirely (not merged)
|
|
5229
|
+
metaTags: override.metaTags ?? base.metaTags,
|
|
5230
|
+
links: override.links ?? base.links
|
|
5231
|
+
};
|
|
5232
|
+
}
|
|
4074
5233
|
function isDataRequest(req) {
|
|
4075
5234
|
return req.query && req.query.__fw_data === "1" || req.headers["x-fw-data"] === "1";
|
|
4076
5235
|
}
|
|
@@ -4135,22 +5294,59 @@ async function handlePageRequestInternal(options) {
|
|
|
4135
5294
|
pathname: urlPath,
|
|
4136
5295
|
locals: {}
|
|
4137
5296
|
};
|
|
4138
|
-
|
|
5297
|
+
const layoutProps2 = {};
|
|
5298
|
+
if (notFoundPage.layoutServerHooks && notFoundPage.layoutServerHooks.length > 0) {
|
|
5299
|
+
for (let i = 0; i < notFoundPage.layoutServerHooks.length; i++) {
|
|
5300
|
+
const layoutServerHook = notFoundPage.layoutServerHooks[i];
|
|
5301
|
+
if (layoutServerHook) {
|
|
5302
|
+
try {
|
|
5303
|
+
const layoutResult = await layoutServerHook(ctx2);
|
|
5304
|
+
if (layoutResult.props) {
|
|
5305
|
+
Object.assign(layoutProps2, layoutResult.props);
|
|
5306
|
+
}
|
|
5307
|
+
} catch (error) {
|
|
5308
|
+
const reqLogger2 = getRequestLogger(req);
|
|
5309
|
+
const layoutFile = notFoundPage.layoutFiles[i];
|
|
5310
|
+
const relativeLayoutPath = layoutFile ? path21.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
|
|
5311
|
+
reqLogger2.warn("Layout server hook failed for not-found page", {
|
|
5312
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5313
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
5314
|
+
layoutFile: relativeLayoutPath,
|
|
5315
|
+
layoutIndex: i
|
|
5316
|
+
});
|
|
5317
|
+
}
|
|
5318
|
+
}
|
|
5319
|
+
}
|
|
5320
|
+
}
|
|
5321
|
+
let loaderResult2 = await runRouteServerHook(notFoundPage, ctx2);
|
|
4139
5322
|
if (!loaderResult2.theme) {
|
|
4140
5323
|
loaderResult2.theme = theme;
|
|
4141
5324
|
}
|
|
4142
|
-
const
|
|
5325
|
+
const combinedProps2 = {
|
|
5326
|
+
...layoutProps2,
|
|
5327
|
+
...loaderResult2.props || {}
|
|
5328
|
+
};
|
|
5329
|
+
const combinedLoaderResult2 = {
|
|
5330
|
+
...loaderResult2,
|
|
5331
|
+
props: combinedProps2
|
|
5332
|
+
};
|
|
5333
|
+
const initialData2 = buildInitialData(urlPath, {}, combinedLoaderResult2);
|
|
4143
5334
|
const appTree2 = buildAppTree(notFoundPage, {}, initialData2.props);
|
|
4144
5335
|
initialData2.notFound = true;
|
|
4145
5336
|
const nonce2 = res.locals.nonce || void 0;
|
|
5337
|
+
const entrypointFiles2 = [];
|
|
5338
|
+
if (assetManifest?.entrypoints?.client) {
|
|
5339
|
+
entrypointFiles2.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
5340
|
+
}
|
|
4146
5341
|
const documentTree2 = createDocumentTree({
|
|
4147
5342
|
appTree: appTree2,
|
|
4148
5343
|
initialData: initialData2,
|
|
4149
5344
|
routerData,
|
|
4150
|
-
meta:
|
|
5345
|
+
meta: combinedLoaderResult2.metadata ?? null,
|
|
4151
5346
|
titleFallback: "Not found",
|
|
4152
5347
|
descriptionFallback: "Loly demo",
|
|
4153
5348
|
chunkHref: null,
|
|
5349
|
+
entrypointFiles: entrypointFiles2,
|
|
4154
5350
|
theme,
|
|
4155
5351
|
clientJsPath,
|
|
4156
5352
|
clientCssPath,
|
|
@@ -4166,8 +5362,8 @@ async function handlePageRequestInternal(options) {
|
|
|
4166
5362
|
},
|
|
4167
5363
|
onShellError(err) {
|
|
4168
5364
|
didError2 = true;
|
|
4169
|
-
const
|
|
4170
|
-
|
|
5365
|
+
const reqLogger2 = getRequestLogger(req);
|
|
5366
|
+
reqLogger2.error("SSR shell error", err, { route: "not-found" });
|
|
4171
5367
|
if (!res.headersSent && errorPage) {
|
|
4172
5368
|
renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
|
|
4173
5369
|
} else if (!res.headersSent) {
|
|
@@ -4179,8 +5375,8 @@ async function handlePageRequestInternal(options) {
|
|
|
4179
5375
|
},
|
|
4180
5376
|
onError(err) {
|
|
4181
5377
|
didError2 = true;
|
|
4182
|
-
const
|
|
4183
|
-
|
|
5378
|
+
const reqLogger2 = getRequestLogger(req);
|
|
5379
|
+
reqLogger2.error("SSR error", err, { route: "not-found" });
|
|
4184
5380
|
}
|
|
4185
5381
|
});
|
|
4186
5382
|
req.on("close", () => abort2());
|
|
@@ -4202,17 +5398,62 @@ async function handlePageRequestInternal(options) {
|
|
|
4202
5398
|
if (res.headersSent) {
|
|
4203
5399
|
return;
|
|
4204
5400
|
}
|
|
5401
|
+
const layoutProps = {};
|
|
5402
|
+
const layoutMetadata = [];
|
|
5403
|
+
const reqLogger = getRequestLogger(req);
|
|
5404
|
+
if (route.layoutServerHooks && route.layoutServerHooks.length > 0) {
|
|
5405
|
+
for (let i = 0; i < route.layoutServerHooks.length; i++) {
|
|
5406
|
+
const layoutServerHook = route.layoutServerHooks[i];
|
|
5407
|
+
if (layoutServerHook) {
|
|
5408
|
+
try {
|
|
5409
|
+
const layoutResult = await layoutServerHook(ctx);
|
|
5410
|
+
if (layoutResult.props) {
|
|
5411
|
+
Object.assign(layoutProps, layoutResult.props);
|
|
5412
|
+
}
|
|
5413
|
+
if (layoutResult.metadata) {
|
|
5414
|
+
layoutMetadata.push(layoutResult.metadata);
|
|
5415
|
+
}
|
|
5416
|
+
} catch (error) {
|
|
5417
|
+
const layoutFile = route.layoutFiles[i];
|
|
5418
|
+
const relativeLayoutPath = layoutFile ? path21.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
|
|
5419
|
+
reqLogger.warn("Layout server hook failed", {
|
|
5420
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5421
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
5422
|
+
layoutFile: relativeLayoutPath,
|
|
5423
|
+
route: route.pattern,
|
|
5424
|
+
layoutIndex: i,
|
|
5425
|
+
suggestion: "Check your layout.server.hook.ts file for errors"
|
|
5426
|
+
});
|
|
5427
|
+
}
|
|
5428
|
+
}
|
|
5429
|
+
}
|
|
5430
|
+
}
|
|
4205
5431
|
let loaderResult;
|
|
4206
5432
|
try {
|
|
4207
|
-
loaderResult = await
|
|
5433
|
+
loaderResult = await runRouteServerHook(route, ctx);
|
|
4208
5434
|
if (!loaderResult.theme) {
|
|
4209
5435
|
loaderResult.theme = theme;
|
|
4210
5436
|
}
|
|
4211
5437
|
} catch (error) {
|
|
5438
|
+
const relativePagePath = route.pageFile ? path21.relative(projectRoot || process.cwd(), route.pageFile) : "unknown";
|
|
5439
|
+
reqLogger.error("Page server hook failed", {
|
|
5440
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5441
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
5442
|
+
pageFile: relativePagePath,
|
|
5443
|
+
route: route.pattern,
|
|
5444
|
+
pathname: urlPath,
|
|
5445
|
+
suggestion: "Check your page.server.hook.ts (or server.hook.ts) file for errors"
|
|
5446
|
+
});
|
|
4212
5447
|
if (isDataReq) {
|
|
4213
5448
|
res.statusCode = 500;
|
|
4214
5449
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
4215
|
-
|
|
5450
|
+
const errorResponse = {
|
|
5451
|
+
error: true,
|
|
5452
|
+
message: error instanceof Error ? error.message : String(error),
|
|
5453
|
+
route: route.pattern,
|
|
5454
|
+
pageFile: relativePagePath
|
|
5455
|
+
};
|
|
5456
|
+
res.end(JSON.stringify(errorResponse, null, 2));
|
|
4216
5457
|
return;
|
|
4217
5458
|
} else {
|
|
4218
5459
|
if (errorPage) {
|
|
@@ -4223,8 +5464,28 @@ async function handlePageRequestInternal(options) {
|
|
|
4223
5464
|
}
|
|
4224
5465
|
}
|
|
4225
5466
|
}
|
|
5467
|
+
const combinedProps = {
|
|
5468
|
+
...layoutProps,
|
|
5469
|
+
// Props from layouts (stable)
|
|
5470
|
+
...loaderResult.props || {}
|
|
5471
|
+
// Props from page (overrides layout)
|
|
5472
|
+
};
|
|
5473
|
+
let combinedMetadata = null;
|
|
5474
|
+
for (const layoutMeta of layoutMetadata) {
|
|
5475
|
+
if (layoutMeta) {
|
|
5476
|
+
combinedMetadata = mergeMetadata(combinedMetadata, layoutMeta);
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
if (loaderResult.metadata) {
|
|
5480
|
+
combinedMetadata = mergeMetadata(combinedMetadata, loaderResult.metadata);
|
|
5481
|
+
}
|
|
5482
|
+
const combinedLoaderResult = {
|
|
5483
|
+
...loaderResult,
|
|
5484
|
+
props: combinedProps,
|
|
5485
|
+
metadata: combinedMetadata
|
|
5486
|
+
};
|
|
4226
5487
|
if (isDataReq) {
|
|
4227
|
-
handleDataResponse(res,
|
|
5488
|
+
handleDataResponse(res, combinedLoaderResult, theme);
|
|
4228
5489
|
return;
|
|
4229
5490
|
}
|
|
4230
5491
|
if (loaderResult.redirect) {
|
|
@@ -4239,7 +5500,7 @@ async function handlePageRequestInternal(options) {
|
|
|
4239
5500
|
}
|
|
4240
5501
|
return;
|
|
4241
5502
|
}
|
|
4242
|
-
const initialData = buildInitialData(urlPath, params,
|
|
5503
|
+
const initialData = buildInitialData(urlPath, params, combinedLoaderResult);
|
|
4243
5504
|
const appTree = buildAppTree(route, params, initialData.props);
|
|
4244
5505
|
const chunkName = routeChunks[route.pattern];
|
|
4245
5506
|
let chunkHref = null;
|
|
@@ -4251,14 +5512,19 @@ async function handlePageRequestInternal(options) {
|
|
|
4251
5512
|
}
|
|
4252
5513
|
}
|
|
4253
5514
|
const nonce = res.locals.nonce || void 0;
|
|
5515
|
+
const entrypointFiles = [];
|
|
5516
|
+
if (assetManifest?.entrypoints?.client) {
|
|
5517
|
+
entrypointFiles.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
5518
|
+
}
|
|
4254
5519
|
const documentTree = createDocumentTree({
|
|
4255
5520
|
appTree,
|
|
4256
5521
|
initialData,
|
|
4257
5522
|
routerData,
|
|
4258
|
-
meta:
|
|
5523
|
+
meta: combinedLoaderResult.metadata,
|
|
4259
5524
|
titleFallback: "Loly framework",
|
|
4260
5525
|
descriptionFallback: "Loly demo",
|
|
4261
5526
|
chunkHref,
|
|
5527
|
+
entrypointFiles,
|
|
4262
5528
|
theme,
|
|
4263
5529
|
clientJsPath,
|
|
4264
5530
|
clientCssPath,
|
|
@@ -4276,8 +5542,17 @@ async function handlePageRequestInternal(options) {
|
|
|
4276
5542
|
},
|
|
4277
5543
|
onShellError(err) {
|
|
4278
5544
|
didError = true;
|
|
4279
|
-
const
|
|
4280
|
-
|
|
5545
|
+
const reqLogger2 = getRequestLogger(req);
|
|
5546
|
+
const routePattern = matched?.route?.pattern || "unknown";
|
|
5547
|
+
reqLogger2.error("SSR shell error", err, { route: routePattern });
|
|
5548
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
5549
|
+
console.error(`
|
|
5550
|
+
\u274C [framework][ssr] Shell error for route "${routePattern}":`);
|
|
5551
|
+
console.error(` ${errorMessage}`);
|
|
5552
|
+
if (err instanceof Error && err.stack) {
|
|
5553
|
+
console.error(` Stack: ${err.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
5554
|
+
}
|
|
5555
|
+
console.error("\u{1F4A1} This usually indicates a React rendering error\n");
|
|
4281
5556
|
if (!res.headersSent && errorPage) {
|
|
4282
5557
|
renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
|
|
4283
5558
|
} else if (!res.headersSent) {
|
|
@@ -4289,8 +5564,12 @@ async function handlePageRequestInternal(options) {
|
|
|
4289
5564
|
},
|
|
4290
5565
|
onError(err) {
|
|
4291
5566
|
didError = true;
|
|
4292
|
-
const
|
|
4293
|
-
|
|
5567
|
+
const reqLogger2 = getRequestLogger(req);
|
|
5568
|
+
const routePattern = matched?.route?.pattern || "unknown";
|
|
5569
|
+
reqLogger2.error("SSR error", err, { route: routePattern });
|
|
5570
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
5571
|
+
console.error(`\u26A0\uFE0F [framework][ssr] Error during streaming for route "${routePattern}":`);
|
|
5572
|
+
console.error(` ${errorMessage}`);
|
|
4294
5573
|
}
|
|
4295
5574
|
});
|
|
4296
5575
|
req.on("close", () => {
|
|
@@ -4307,11 +5586,43 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4307
5586
|
pathname: req.path,
|
|
4308
5587
|
locals: { error }
|
|
4309
5588
|
};
|
|
4310
|
-
|
|
5589
|
+
const layoutProps = {};
|
|
5590
|
+
const reqLogger = getRequestLogger(req);
|
|
5591
|
+
if (errorPage.layoutServerHooks && errorPage.layoutServerHooks.length > 0) {
|
|
5592
|
+
for (let i = 0; i < errorPage.layoutServerHooks.length; i++) {
|
|
5593
|
+
const layoutServerHook = errorPage.layoutServerHooks[i];
|
|
5594
|
+
if (layoutServerHook) {
|
|
5595
|
+
try {
|
|
5596
|
+
const layoutResult = await layoutServerHook(ctx);
|
|
5597
|
+
if (layoutResult.props) {
|
|
5598
|
+
Object.assign(layoutProps, layoutResult.props);
|
|
5599
|
+
}
|
|
5600
|
+
} catch (err) {
|
|
5601
|
+
const layoutFile = errorPage.layoutFiles[i];
|
|
5602
|
+
const relativeLayoutPath = layoutFile ? path21.relative(projectRoot || process.cwd(), layoutFile) : "unknown";
|
|
5603
|
+
reqLogger.warn("Layout server hook failed for error page", {
|
|
5604
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5605
|
+
stack: err instanceof Error ? err.stack : void 0,
|
|
5606
|
+
layoutFile: relativeLayoutPath,
|
|
5607
|
+
layoutIndex: i
|
|
5608
|
+
});
|
|
5609
|
+
}
|
|
5610
|
+
}
|
|
5611
|
+
}
|
|
5612
|
+
}
|
|
5613
|
+
let loaderResult = await runRouteServerHook(errorPage, ctx);
|
|
4311
5614
|
if (!loaderResult.theme && theme) {
|
|
4312
5615
|
loaderResult.theme = theme;
|
|
4313
5616
|
}
|
|
4314
|
-
const
|
|
5617
|
+
const combinedProps = {
|
|
5618
|
+
...layoutProps,
|
|
5619
|
+
...loaderResult.props || {}
|
|
5620
|
+
};
|
|
5621
|
+
const combinedLoaderResult = {
|
|
5622
|
+
...loaderResult,
|
|
5623
|
+
props: combinedProps
|
|
5624
|
+
};
|
|
5625
|
+
const initialData = buildInitialData(req.path, { error: String(error) }, combinedLoaderResult);
|
|
4315
5626
|
const routerData = buildRouterData(req);
|
|
4316
5627
|
initialData.error = true;
|
|
4317
5628
|
if (isDataReq) {
|
|
@@ -4321,8 +5632,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4321
5632
|
error: true,
|
|
4322
5633
|
message: String(error),
|
|
4323
5634
|
props: initialData.props,
|
|
4324
|
-
metadata:
|
|
4325
|
-
theme:
|
|
5635
|
+
metadata: combinedLoaderResult.metadata ?? null,
|
|
5636
|
+
theme: combinedLoaderResult.theme ?? theme ?? null
|
|
4326
5637
|
}));
|
|
4327
5638
|
return;
|
|
4328
5639
|
}
|
|
@@ -4340,11 +5651,15 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4340
5651
|
}
|
|
4341
5652
|
}
|
|
4342
5653
|
const nonce = res.locals.nonce || void 0;
|
|
5654
|
+
const entrypointFiles = [];
|
|
5655
|
+
if (assetManifest?.entrypoints?.client) {
|
|
5656
|
+
entrypointFiles.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
5657
|
+
}
|
|
4343
5658
|
const documentTree = createDocumentTree({
|
|
4344
5659
|
appTree,
|
|
4345
5660
|
initialData,
|
|
4346
5661
|
routerData,
|
|
4347
|
-
meta:
|
|
5662
|
+
meta: combinedLoaderResult.metadata ?? null,
|
|
4348
5663
|
titleFallback: "Error",
|
|
4349
5664
|
descriptionFallback: "An error occurred",
|
|
4350
5665
|
chunkHref,
|
|
@@ -4365,8 +5680,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4365
5680
|
},
|
|
4366
5681
|
onShellError(err) {
|
|
4367
5682
|
didError = true;
|
|
4368
|
-
const
|
|
4369
|
-
|
|
5683
|
+
const reqLogger2 = getRequestLogger(req);
|
|
5684
|
+
reqLogger2.error("Error rendering error page", err, { type: "shellError" });
|
|
4370
5685
|
if (!res.headersSent) {
|
|
4371
5686
|
res.statusCode = 500;
|
|
4372
5687
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
@@ -4376,8 +5691,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
|
|
|
4376
5691
|
},
|
|
4377
5692
|
onError(err) {
|
|
4378
5693
|
didError = true;
|
|
4379
|
-
const
|
|
4380
|
-
|
|
5694
|
+
const reqLogger2 = getRequestLogger(req);
|
|
5695
|
+
reqLogger2.error("Error in error page", err);
|
|
4381
5696
|
}
|
|
4382
5697
|
});
|
|
4383
5698
|
req.on("close", () => {
|
|
@@ -4465,7 +5780,7 @@ async function getServerConfig(projectRoot) {
|
|
|
4465
5780
|
}
|
|
4466
5781
|
|
|
4467
5782
|
// modules/server/routes.ts
|
|
4468
|
-
import
|
|
5783
|
+
import path22 from "path";
|
|
4469
5784
|
function setupRoutes(options) {
|
|
4470
5785
|
const {
|
|
4471
5786
|
app,
|
|
@@ -4480,21 +5795,23 @@ function setupRoutes(options) {
|
|
|
4480
5795
|
config
|
|
4481
5796
|
} = options;
|
|
4482
5797
|
const routeChunks = routeLoader.loadRouteChunks();
|
|
4483
|
-
const ssgOutDir =
|
|
4484
|
-
config ? getBuildDir(projectRoot, config) :
|
|
5798
|
+
const ssgOutDir = path22.join(
|
|
5799
|
+
config ? getBuildDir(projectRoot, config) : path22.join(projectRoot, BUILD_FOLDER_NAME),
|
|
4485
5800
|
"ssg"
|
|
4486
5801
|
);
|
|
4487
5802
|
app.all("/api/*", async (req, res) => {
|
|
4488
5803
|
const apiRoutes = isDev && getRoutes ? getRoutes().apiRoutes : initialApiRoutes;
|
|
4489
5804
|
const serverConfig = await getServerConfig(projectRoot);
|
|
4490
5805
|
const strictPatterns = serverConfig.rateLimit?.strictPatterns || [];
|
|
5806
|
+
const rateLimitConfig = serverConfig.rateLimit;
|
|
4491
5807
|
await handleApiRequest({
|
|
4492
5808
|
apiRoutes,
|
|
4493
5809
|
urlPath: req.path,
|
|
4494
5810
|
req,
|
|
4495
5811
|
res,
|
|
4496
5812
|
env: isDev ? "dev" : "prod",
|
|
4497
|
-
strictRateLimitPatterns: strictPatterns
|
|
5813
|
+
strictRateLimitPatterns: strictPatterns,
|
|
5814
|
+
rateLimitConfig
|
|
4498
5815
|
});
|
|
4499
5816
|
});
|
|
4500
5817
|
app.get("*", async (req, res) => {
|
|
@@ -4747,12 +6064,29 @@ var setupApplication = async ({
|
|
|
4747
6064
|
corsOptions.origin = process.env.NODE_ENV === "development";
|
|
4748
6065
|
}
|
|
4749
6066
|
app.use(cors(corsOptions));
|
|
4750
|
-
if (rateLimit2
|
|
4751
|
-
const
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
6067
|
+
if (rateLimit2) {
|
|
6068
|
+
const shouldApply = process.env.NODE_ENV !== "development" || process.env.ENABLE_RATE_LIMIT === "true";
|
|
6069
|
+
if (shouldApply) {
|
|
6070
|
+
try {
|
|
6071
|
+
const generalLimiter = createRateLimiterFromConfig(rateLimit2, false);
|
|
6072
|
+
if (generalLimiter) {
|
|
6073
|
+
app.use(generalLimiter);
|
|
6074
|
+
const logger5 = createModuleLogger("server");
|
|
6075
|
+
logger5.info("Rate limiting enabled", {
|
|
6076
|
+
windowMs: rateLimit2.windowMs ?? 15 * 60 * 1e3,
|
|
6077
|
+
max: rateLimit2.max ?? 100,
|
|
6078
|
+
apiMax: rateLimit2.apiMax,
|
|
6079
|
+
strictMax: rateLimit2.strictMax,
|
|
6080
|
+
strictPatterns: rateLimit2.strictPatterns?.length ?? 0
|
|
6081
|
+
});
|
|
6082
|
+
}
|
|
6083
|
+
} catch (error) {
|
|
6084
|
+
const logger5 = createModuleLogger("server");
|
|
6085
|
+
logger5.error("Failed to setup rate limiting", {
|
|
6086
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6087
|
+
});
|
|
6088
|
+
}
|
|
6089
|
+
}
|
|
4756
6090
|
}
|
|
4757
6091
|
app.use(cookieParser());
|
|
4758
6092
|
app.use(express2.json({ limit: bodyLimit }));
|
|
@@ -4767,22 +6101,31 @@ var setupApplication = async ({
|
|
|
4767
6101
|
|
|
4768
6102
|
// src/server.ts
|
|
4769
6103
|
import dotenv2 from "dotenv";
|
|
4770
|
-
var envPath =
|
|
6104
|
+
var envPath = path23.join(process.cwd(), ".env");
|
|
4771
6105
|
if (fs16.existsSync(envPath)) {
|
|
4772
6106
|
dotenv2.config({ path: envPath });
|
|
4773
6107
|
} else {
|
|
4774
6108
|
dotenv2.config();
|
|
4775
6109
|
}
|
|
4776
|
-
var
|
|
6110
|
+
var logger4 = createModuleLogger("server");
|
|
4777
6111
|
async function startServer(options = {}) {
|
|
4778
6112
|
const isDev = options.isDev ?? process.env.NODE_ENV === "development";
|
|
4779
6113
|
const projectRoot = options.rootDir ?? process.cwd();
|
|
4780
|
-
|
|
6114
|
+
let config;
|
|
6115
|
+
try {
|
|
6116
|
+
config = options.config ?? loadConfig(projectRoot);
|
|
6117
|
+
} catch (error) {
|
|
6118
|
+
if (error instanceof ConfigValidationError) {
|
|
6119
|
+
console.error("\n" + error.message + "\n");
|
|
6120
|
+
process.exit(1);
|
|
6121
|
+
}
|
|
6122
|
+
throw error;
|
|
6123
|
+
}
|
|
4781
6124
|
const port = options.port ?? (process.env.PORT ? parseInt(process.env.PORT, 10) : void 0) ?? config.server.port;
|
|
4782
6125
|
const host = process.env.HOST ?? (!isDev ? "0.0.0.0" : void 0) ?? config.server.host;
|
|
4783
|
-
const appDir = options.appDir ?? (isDev ? getAppDir(projectRoot, config) :
|
|
6126
|
+
const appDir = options.appDir ?? (isDev ? getAppDir(projectRoot, config) : path23.join(getBuildDir(projectRoot, config), "server"));
|
|
4784
6127
|
if (!isDev && !fs16.existsSync(appDir)) {
|
|
4785
|
-
|
|
6128
|
+
logger4.error("Compiled directory not found", void 0, {
|
|
4786
6129
|
buildDir: config.directories.build,
|
|
4787
6130
|
appDir,
|
|
4788
6131
|
environment: "production"
|
|
@@ -4799,7 +6142,7 @@ async function startServer(options = {}) {
|
|
|
4799
6142
|
isDev,
|
|
4800
6143
|
config
|
|
4801
6144
|
});
|
|
4802
|
-
const routeLoader = isDev ? new FilesystemRouteLoader(appDir) : new ManifestRouteLoader(projectRoot);
|
|
6145
|
+
const routeLoader = isDev ? new FilesystemRouteLoader(appDir, projectRoot) : new ManifestRouteLoader(projectRoot);
|
|
4803
6146
|
setupWssEvents({
|
|
4804
6147
|
httpServer,
|
|
4805
6148
|
wssRoutes
|
|
@@ -4851,10 +6194,10 @@ async function startProdServer(options = {}) {
|
|
|
4851
6194
|
}
|
|
4852
6195
|
|
|
4853
6196
|
// modules/build/ssg/builder.ts
|
|
4854
|
-
import
|
|
6197
|
+
import path26 from "path";
|
|
4855
6198
|
|
|
4856
6199
|
// modules/build/ssg/path.ts
|
|
4857
|
-
import
|
|
6200
|
+
import path24 from "path";
|
|
4858
6201
|
function buildPathFromPattern(pattern, params) {
|
|
4859
6202
|
const segments = pattern.split("/").filter(Boolean);
|
|
4860
6203
|
const parts = [];
|
|
@@ -4883,12 +6226,12 @@ function buildPathFromPattern(pattern, params) {
|
|
|
4883
6226
|
}
|
|
4884
6227
|
function pathToOutDir(baseDir, urlPath) {
|
|
4885
6228
|
const clean = urlPath === "/" ? "" : urlPath.replace(/^\/+/, "");
|
|
4886
|
-
return
|
|
6229
|
+
return path24.join(baseDir, clean);
|
|
4887
6230
|
}
|
|
4888
6231
|
|
|
4889
6232
|
// modules/build/ssg/renderer.ts
|
|
4890
6233
|
import fs17 from "fs";
|
|
4891
|
-
import
|
|
6234
|
+
import path25 from "path";
|
|
4892
6235
|
import { renderToString } from "react-dom/server";
|
|
4893
6236
|
init_globals();
|
|
4894
6237
|
async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params) {
|
|
@@ -4905,6 +6248,10 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
4905
6248
|
chunkHref = `${STATIC_PATH}/${chunkName}.js`;
|
|
4906
6249
|
}
|
|
4907
6250
|
}
|
|
6251
|
+
const entrypointFiles = [];
|
|
6252
|
+
if (assetManifest?.entrypoints?.client) {
|
|
6253
|
+
entrypointFiles.push(...assetManifest.entrypoints.client.map((file) => `${STATIC_PATH}/${file}`));
|
|
6254
|
+
}
|
|
4908
6255
|
const req = {
|
|
4909
6256
|
method: "GET",
|
|
4910
6257
|
headers: {},
|
|
@@ -4934,32 +6281,79 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
4934
6281
|
})
|
|
4935
6282
|
);
|
|
4936
6283
|
}
|
|
6284
|
+
const layoutProps = {};
|
|
6285
|
+
const layoutMetadata = [];
|
|
6286
|
+
if (route.layoutServerHooks && route.layoutServerHooks.length > 0) {
|
|
6287
|
+
for (let i = 0; i < route.layoutServerHooks.length; i++) {
|
|
6288
|
+
const layoutServerHook = route.layoutServerHooks[i];
|
|
6289
|
+
if (layoutServerHook) {
|
|
6290
|
+
try {
|
|
6291
|
+
const layoutResult = await layoutServerHook(ctx);
|
|
6292
|
+
if (layoutResult.props) {
|
|
6293
|
+
Object.assign(layoutProps, layoutResult.props);
|
|
6294
|
+
}
|
|
6295
|
+
if (layoutResult.metadata) {
|
|
6296
|
+
layoutMetadata.push(layoutResult.metadata);
|
|
6297
|
+
}
|
|
6298
|
+
} catch (error) {
|
|
6299
|
+
console.warn(
|
|
6300
|
+
`\u26A0\uFE0F [framework][ssg] Layout server hook ${i} failed for route ${route.pattern}:`,
|
|
6301
|
+
error instanceof Error ? error.message : String(error)
|
|
6302
|
+
);
|
|
6303
|
+
if (error instanceof Error && error.stack) {
|
|
6304
|
+
console.warn(` Stack: ${error.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
6305
|
+
}
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
4937
6310
|
let loaderResult = { props: {} };
|
|
4938
6311
|
if (route.loader) {
|
|
4939
6312
|
loaderResult = await route.loader(ctx);
|
|
4940
6313
|
}
|
|
4941
|
-
|
|
6314
|
+
const combinedProps = {
|
|
6315
|
+
...layoutProps,
|
|
6316
|
+
...loaderResult.props || {}
|
|
6317
|
+
};
|
|
6318
|
+
let combinedMetadata = null;
|
|
6319
|
+
for (const layoutMeta of layoutMetadata) {
|
|
6320
|
+
if (layoutMeta) {
|
|
6321
|
+
combinedMetadata = mergeMetadata(combinedMetadata, layoutMeta);
|
|
6322
|
+
}
|
|
6323
|
+
}
|
|
6324
|
+
if (loaderResult.metadata) {
|
|
6325
|
+
combinedMetadata = mergeMetadata(combinedMetadata, loaderResult.metadata);
|
|
6326
|
+
}
|
|
6327
|
+
const combinedLoaderResult = {
|
|
6328
|
+
...loaderResult,
|
|
6329
|
+
props: combinedProps,
|
|
6330
|
+
metadata: combinedMetadata
|
|
6331
|
+
};
|
|
6332
|
+
if (combinedLoaderResult.redirect || combinedLoaderResult.notFound) {
|
|
4942
6333
|
return;
|
|
4943
6334
|
}
|
|
4944
|
-
const initialData = buildInitialData(urlPath, params,
|
|
6335
|
+
const initialData = buildInitialData(urlPath, params, combinedLoaderResult);
|
|
4945
6336
|
const routerData = buildRouterData(req);
|
|
4946
6337
|
const appTree = buildAppTree(route, params, initialData.props);
|
|
4947
6338
|
const documentTree = createDocumentTree({
|
|
4948
6339
|
appTree,
|
|
4949
6340
|
initialData,
|
|
4950
6341
|
routerData,
|
|
4951
|
-
meta:
|
|
6342
|
+
meta: combinedLoaderResult.metadata,
|
|
4952
6343
|
titleFallback: "My Framework Dev",
|
|
4953
6344
|
descriptionFallback: "Static page generated by @lolyjs/core.",
|
|
4954
6345
|
chunkHref,
|
|
6346
|
+
entrypointFiles,
|
|
4955
6347
|
clientJsPath,
|
|
4956
|
-
clientCssPath
|
|
6348
|
+
clientCssPath,
|
|
6349
|
+
includeInlineScripts: true
|
|
6350
|
+
// SSG needs inline scripts (renderToString doesn't support bootstrapScripts)
|
|
4957
6351
|
});
|
|
4958
6352
|
const html = "<!DOCTYPE html>" + renderToString(documentTree);
|
|
4959
6353
|
const dir = pathToOutDir(ssgOutDir, urlPath);
|
|
4960
6354
|
ensureDir(dir);
|
|
4961
|
-
const htmlFile =
|
|
4962
|
-
const dataFile =
|
|
6355
|
+
const htmlFile = path25.join(dir, "index.html");
|
|
6356
|
+
const dataFile = path25.join(dir, "data.json");
|
|
4963
6357
|
fs17.writeFileSync(htmlFile, html, "utf-8");
|
|
4964
6358
|
fs17.writeFileSync(dataFile, JSON.stringify(initialData, null, 2), "utf-8");
|
|
4965
6359
|
}
|
|
@@ -4967,7 +6361,7 @@ async function renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params)
|
|
|
4967
6361
|
// modules/build/ssg/builder.ts
|
|
4968
6362
|
init_globals();
|
|
4969
6363
|
async function buildStaticPages(projectRoot, routes) {
|
|
4970
|
-
const ssgOutDir =
|
|
6364
|
+
const ssgOutDir = path26.join(projectRoot, BUILD_FOLDER_NAME, "ssg");
|
|
4971
6365
|
ensureDir(ssgOutDir);
|
|
4972
6366
|
for (const route of routes) {
|
|
4973
6367
|
if (route.dynamic !== "force-static") continue;
|
|
@@ -4977,12 +6371,15 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
4977
6371
|
} else {
|
|
4978
6372
|
if (!route.generateStaticParams) {
|
|
4979
6373
|
console.warn(
|
|
4980
|
-
|
|
6374
|
+
`\u26A0\uFE0F [framework][ssg] Route "${route.pattern}" is marked as force-static but has no generateStaticParams function`
|
|
4981
6375
|
);
|
|
6376
|
+
console.warn(` \u{1F4A1} Add a generateStaticParams export to enable static generation for this route`);
|
|
6377
|
+
console.warn(` Skipping this route...
|
|
6378
|
+
`);
|
|
4982
6379
|
continue;
|
|
4983
6380
|
}
|
|
4984
6381
|
try {
|
|
4985
|
-
console.log(
|
|
6382
|
+
console.log(`\u{1F4E6} [framework][ssg] Generating static params for route: ${route.pattern}`);
|
|
4986
6383
|
let timeoutId = null;
|
|
4987
6384
|
const timeoutPromise = new Promise((_, reject) => {
|
|
4988
6385
|
timeoutId = setTimeout(() => {
|
|
@@ -4997,12 +6394,16 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
4997
6394
|
clearTimeout(timeoutId);
|
|
4998
6395
|
}
|
|
4999
6396
|
allParams = sp;
|
|
5000
|
-
console.log(`
|
|
6397
|
+
console.log(` \u2705 Generated ${sp.length} static params for route: ${route.pattern}`);
|
|
5001
6398
|
} catch (error) {
|
|
5002
|
-
console.error(
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
)
|
|
6399
|
+
console.error(`
|
|
6400
|
+
\u274C [framework][ssg] Error generating static params for route "${route.pattern}":`);
|
|
6401
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
6402
|
+
if (error instanceof Error && error.stack) {
|
|
6403
|
+
console.error(` Stack: ${error.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
6404
|
+
}
|
|
6405
|
+
console.error(`\u{1F4A1} Check your generateStaticParams function for this route
|
|
6406
|
+
`);
|
|
5006
6407
|
throw error;
|
|
5007
6408
|
}
|
|
5008
6409
|
}
|
|
@@ -5011,11 +6412,11 @@ async function buildStaticPages(projectRoot, routes) {
|
|
|
5011
6412
|
await renderStaticRoute(projectRoot, ssgOutDir, route, urlPath, params);
|
|
5012
6413
|
}
|
|
5013
6414
|
}
|
|
5014
|
-
console.log(
|
|
6415
|
+
console.log(`\u2705 [framework][ssg] Finished building all static pages`);
|
|
5015
6416
|
}
|
|
5016
6417
|
|
|
5017
6418
|
// modules/build/bundler/server.ts
|
|
5018
|
-
import
|
|
6419
|
+
import path27 from "path";
|
|
5019
6420
|
import fs18 from "fs";
|
|
5020
6421
|
import esbuild from "esbuild";
|
|
5021
6422
|
init_globals();
|
|
@@ -5025,7 +6426,7 @@ function collectAppSources(appDir) {
|
|
|
5025
6426
|
function walk(dir) {
|
|
5026
6427
|
const items = fs18.readdirSync(dir, { withFileTypes: true });
|
|
5027
6428
|
for (const item of items) {
|
|
5028
|
-
const full =
|
|
6429
|
+
const full = path27.join(dir, item.name);
|
|
5029
6430
|
if (item.isDirectory()) {
|
|
5030
6431
|
walk(full);
|
|
5031
6432
|
continue;
|
|
@@ -5042,7 +6443,7 @@ function collectAppSources(appDir) {
|
|
|
5042
6443
|
return entries;
|
|
5043
6444
|
}
|
|
5044
6445
|
async function buildServerApp(projectRoot, appDir) {
|
|
5045
|
-
const outDir =
|
|
6446
|
+
const outDir = path27.join(projectRoot, BUILD_FOLDER_NAME, "server");
|
|
5046
6447
|
const entryPoints = collectAppSources(appDir);
|
|
5047
6448
|
ensureDir(outDir);
|
|
5048
6449
|
if (entryPoints.length === 0) {
|
|
@@ -5060,12 +6461,12 @@ async function buildServerApp(projectRoot, appDir) {
|
|
|
5060
6461
|
bundle: true,
|
|
5061
6462
|
splitting: false,
|
|
5062
6463
|
logLevel: "info",
|
|
5063
|
-
tsconfig:
|
|
6464
|
+
tsconfig: path27.join(projectRoot, "tsconfig.json"),
|
|
5064
6465
|
packages: "external"
|
|
5065
6466
|
});
|
|
5066
6467
|
for (const fileName of SERVER_FILES) {
|
|
5067
|
-
const initTS =
|
|
5068
|
-
const initJS =
|
|
6468
|
+
const initTS = path27.join(projectRoot, `${fileName}.ts`);
|
|
6469
|
+
const initJS = path27.join(outDir, `${fileName}.js`);
|
|
5069
6470
|
if (fs18.existsSync(initTS)) {
|
|
5070
6471
|
await esbuild.build({
|
|
5071
6472
|
entryPoints: [initTS],
|
|
@@ -5077,7 +6478,7 @@ async function buildServerApp(projectRoot, appDir) {
|
|
|
5077
6478
|
sourcemap: true,
|
|
5078
6479
|
bundle: false,
|
|
5079
6480
|
logLevel: "info",
|
|
5080
|
-
tsconfig:
|
|
6481
|
+
tsconfig: path27.join(projectRoot, "tsconfig.json")
|
|
5081
6482
|
});
|
|
5082
6483
|
}
|
|
5083
6484
|
}
|
|
@@ -5220,22 +6621,158 @@ function matchRouteClient(pathWithSearch, routes) {
|
|
|
5220
6621
|
}
|
|
5221
6622
|
|
|
5222
6623
|
// modules/runtime/client/metadata.ts
|
|
6624
|
+
function getOrCreateMeta(selector, attributes) {
|
|
6625
|
+
let meta = document.querySelector(selector);
|
|
6626
|
+
if (!meta) {
|
|
6627
|
+
meta = document.createElement("meta");
|
|
6628
|
+
if (attributes.name) meta.name = attributes.name;
|
|
6629
|
+
if (attributes.property) meta.setAttribute("property", attributes.property);
|
|
6630
|
+
if (attributes.httpEquiv) meta.httpEquiv = attributes.httpEquiv;
|
|
6631
|
+
document.head.appendChild(meta);
|
|
6632
|
+
}
|
|
6633
|
+
return meta;
|
|
6634
|
+
}
|
|
6635
|
+
function getOrCreateLink(rel, href) {
|
|
6636
|
+
const selector = `link[rel="${rel}"]`;
|
|
6637
|
+
let link = document.querySelector(selector);
|
|
6638
|
+
if (!link) {
|
|
6639
|
+
link = document.createElement("link");
|
|
6640
|
+
link.rel = rel;
|
|
6641
|
+
link.href = href;
|
|
6642
|
+
document.head.appendChild(link);
|
|
6643
|
+
} else {
|
|
6644
|
+
link.href = href;
|
|
6645
|
+
}
|
|
6646
|
+
return link;
|
|
6647
|
+
}
|
|
5223
6648
|
function applyMetadata(md) {
|
|
5224
6649
|
if (!md) return;
|
|
5225
6650
|
if (md.title) {
|
|
5226
6651
|
document.title = md.title;
|
|
5227
6652
|
}
|
|
5228
6653
|
if (md.description) {
|
|
5229
|
-
|
|
5230
|
-
'meta[name="description"]'
|
|
5231
|
-
);
|
|
5232
|
-
if (!meta) {
|
|
5233
|
-
meta = document.createElement("meta");
|
|
5234
|
-
meta.name = "description";
|
|
5235
|
-
document.head.appendChild(meta);
|
|
5236
|
-
}
|
|
6654
|
+
const meta = getOrCreateMeta('meta[name="description"]', { name: "description" });
|
|
5237
6655
|
meta.content = md.description;
|
|
5238
6656
|
}
|
|
6657
|
+
if (md.robots) {
|
|
6658
|
+
const meta = getOrCreateMeta('meta[name="robots"]', { name: "robots" });
|
|
6659
|
+
meta.content = md.robots;
|
|
6660
|
+
}
|
|
6661
|
+
if (md.themeColor) {
|
|
6662
|
+
const meta = getOrCreateMeta('meta[name="theme-color"]', { name: "theme-color" });
|
|
6663
|
+
meta.content = md.themeColor;
|
|
6664
|
+
}
|
|
6665
|
+
if (md.viewport) {
|
|
6666
|
+
const meta = getOrCreateMeta('meta[name="viewport"]', { name: "viewport" });
|
|
6667
|
+
meta.content = md.viewport;
|
|
6668
|
+
}
|
|
6669
|
+
if (md.canonical) {
|
|
6670
|
+
getOrCreateLink("canonical", md.canonical);
|
|
6671
|
+
}
|
|
6672
|
+
if (md.openGraph) {
|
|
6673
|
+
const og = md.openGraph;
|
|
6674
|
+
if (og.title) {
|
|
6675
|
+
const meta = getOrCreateMeta('meta[property="og:title"]', { property: "og:title" });
|
|
6676
|
+
meta.content = og.title;
|
|
6677
|
+
}
|
|
6678
|
+
if (og.description) {
|
|
6679
|
+
const meta = getOrCreateMeta('meta[property="og:description"]', { property: "og:description" });
|
|
6680
|
+
meta.content = og.description;
|
|
6681
|
+
}
|
|
6682
|
+
if (og.type) {
|
|
6683
|
+
const meta = getOrCreateMeta('meta[property="og:type"]', { property: "og:type" });
|
|
6684
|
+
meta.content = og.type;
|
|
6685
|
+
}
|
|
6686
|
+
if (og.url) {
|
|
6687
|
+
const meta = getOrCreateMeta('meta[property="og:url"]', { property: "og:url" });
|
|
6688
|
+
meta.content = og.url;
|
|
6689
|
+
}
|
|
6690
|
+
if (og.image) {
|
|
6691
|
+
if (typeof og.image === "string") {
|
|
6692
|
+
const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
|
|
6693
|
+
meta.content = og.image;
|
|
6694
|
+
} else {
|
|
6695
|
+
const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
|
|
6696
|
+
meta.content = og.image.url;
|
|
6697
|
+
if (og.image.width) {
|
|
6698
|
+
const metaWidth = getOrCreateMeta('meta[property="og:image:width"]', { property: "og:image:width" });
|
|
6699
|
+
metaWidth.content = String(og.image.width);
|
|
6700
|
+
}
|
|
6701
|
+
if (og.image.height) {
|
|
6702
|
+
const metaHeight = getOrCreateMeta('meta[property="og:image:height"]', { property: "og:image:height" });
|
|
6703
|
+
metaHeight.content = String(og.image.height);
|
|
6704
|
+
}
|
|
6705
|
+
if (og.image.alt) {
|
|
6706
|
+
const metaAlt = getOrCreateMeta('meta[property="og:image:alt"]', { property: "og:image:alt" });
|
|
6707
|
+
metaAlt.content = og.image.alt;
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
}
|
|
6711
|
+
if (og.siteName) {
|
|
6712
|
+
const meta = getOrCreateMeta('meta[property="og:site_name"]', { property: "og:site_name" });
|
|
6713
|
+
meta.content = og.siteName;
|
|
6714
|
+
}
|
|
6715
|
+
if (og.locale) {
|
|
6716
|
+
const meta = getOrCreateMeta('meta[property="og:locale"]', { property: "og:locale" });
|
|
6717
|
+
meta.content = og.locale;
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
if (md.twitter) {
|
|
6721
|
+
const twitter = md.twitter;
|
|
6722
|
+
if (twitter.card) {
|
|
6723
|
+
const meta = getOrCreateMeta('meta[name="twitter:card"]', { name: "twitter:card" });
|
|
6724
|
+
meta.content = twitter.card;
|
|
6725
|
+
}
|
|
6726
|
+
if (twitter.title) {
|
|
6727
|
+
const meta = getOrCreateMeta('meta[name="twitter:title"]', { name: "twitter:title" });
|
|
6728
|
+
meta.content = twitter.title;
|
|
6729
|
+
}
|
|
6730
|
+
if (twitter.description) {
|
|
6731
|
+
const meta = getOrCreateMeta('meta[name="twitter:description"]', { name: "twitter:description" });
|
|
6732
|
+
meta.content = twitter.description;
|
|
6733
|
+
}
|
|
6734
|
+
if (twitter.image) {
|
|
6735
|
+
const meta = getOrCreateMeta('meta[name="twitter:image"]', { name: "twitter:image" });
|
|
6736
|
+
meta.content = twitter.image;
|
|
6737
|
+
}
|
|
6738
|
+
if (twitter.imageAlt) {
|
|
6739
|
+
const meta = getOrCreateMeta('meta[name="twitter:image:alt"]', { name: "twitter:image:alt" });
|
|
6740
|
+
meta.content = twitter.imageAlt;
|
|
6741
|
+
}
|
|
6742
|
+
if (twitter.site) {
|
|
6743
|
+
const meta = getOrCreateMeta('meta[name="twitter:site"]', { name: "twitter:site" });
|
|
6744
|
+
meta.content = twitter.site;
|
|
6745
|
+
}
|
|
6746
|
+
if (twitter.creator) {
|
|
6747
|
+
const meta = getOrCreateMeta('meta[name="twitter:creator"]', { name: "twitter:creator" });
|
|
6748
|
+
meta.content = twitter.creator;
|
|
6749
|
+
}
|
|
6750
|
+
}
|
|
6751
|
+
if (md.metaTags && Array.isArray(md.metaTags)) {
|
|
6752
|
+
md.metaTags.forEach((tag) => {
|
|
6753
|
+
let selector = "";
|
|
6754
|
+
if (tag.name) {
|
|
6755
|
+
selector = `meta[name="${tag.name}"]`;
|
|
6756
|
+
} else if (tag.property) {
|
|
6757
|
+
selector = `meta[property="${tag.property}"]`;
|
|
6758
|
+
} else if (tag.httpEquiv) {
|
|
6759
|
+
selector = `meta[http-equiv="${tag.httpEquiv}"]`;
|
|
6760
|
+
}
|
|
6761
|
+
if (selector) {
|
|
6762
|
+
const meta = getOrCreateMeta(selector, {
|
|
6763
|
+
name: tag.name,
|
|
6764
|
+
property: tag.property,
|
|
6765
|
+
httpEquiv: tag.httpEquiv
|
|
6766
|
+
});
|
|
6767
|
+
meta.content = tag.content;
|
|
6768
|
+
}
|
|
6769
|
+
});
|
|
6770
|
+
}
|
|
6771
|
+
if (md.links && Array.isArray(md.links)) {
|
|
6772
|
+
md.links.forEach((link) => {
|
|
6773
|
+
getOrCreateLink(link.rel, link.href);
|
|
6774
|
+
});
|
|
6775
|
+
}
|
|
5239
6776
|
}
|
|
5240
6777
|
|
|
5241
6778
|
// modules/runtime/client/AppShell.tsx
|
|
@@ -5452,10 +6989,15 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
|
|
|
5452
6989
|
});
|
|
5453
6990
|
return true;
|
|
5454
6991
|
} catch (loadError) {
|
|
5455
|
-
console.error(
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
6992
|
+
console.error("\n\u274C [client] Error loading error route components:");
|
|
6993
|
+
console.error(loadError);
|
|
6994
|
+
if (loadError instanceof Error) {
|
|
6995
|
+
console.error(` Message: ${loadError.message}`);
|
|
6996
|
+
if (loadError.stack) {
|
|
6997
|
+
console.error(` Stack: ${loadError.stack.split("\n").slice(0, 3).join("\n ")}`);
|
|
6998
|
+
}
|
|
6999
|
+
}
|
|
7000
|
+
console.error("\u{1F4A1} Falling back to full page reload\n");
|
|
5459
7001
|
window.location.href = nextUrl;
|
|
5460
7002
|
return false;
|
|
5461
7003
|
}
|
|
@@ -5560,7 +7102,11 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
|
|
|
5560
7102
|
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
5561
7103
|
};
|
|
5562
7104
|
setRouterData(routerData);
|
|
5563
|
-
const
|
|
7105
|
+
const prefetched = prefetchedRoutes.get(matched.route);
|
|
7106
|
+
const components = prefetched ? await prefetched : await matched.route.load();
|
|
7107
|
+
if (!prefetched) {
|
|
7108
|
+
prefetchedRoutes.set(matched.route, Promise.resolve(components));
|
|
7109
|
+
}
|
|
5564
7110
|
window.scrollTo({
|
|
5565
7111
|
top: 0,
|
|
5566
7112
|
behavior: "smooth"
|
|
@@ -5599,7 +7145,7 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
5599
7145
|
}
|
|
5600
7146
|
}
|
|
5601
7147
|
if (!ok) {
|
|
5602
|
-
if (json
|
|
7148
|
+
if (json?.redirect) {
|
|
5603
7149
|
window.location.href = json.redirect.destination;
|
|
5604
7150
|
return;
|
|
5605
7151
|
}
|
|
@@ -5620,6 +7166,47 @@ async function navigate(nextUrl, handlers, options) {
|
|
|
5620
7166
|
window.location.href = nextUrl;
|
|
5621
7167
|
}
|
|
5622
7168
|
}
|
|
7169
|
+
var prefetchedRoutes = /* @__PURE__ */ new WeakMap();
|
|
7170
|
+
function prefetchRoute(url, routes, notFoundRoute) {
|
|
7171
|
+
const [pathname] = url.split("?");
|
|
7172
|
+
const matched = matchRouteClient(pathname, routes);
|
|
7173
|
+
if (!matched) {
|
|
7174
|
+
if (notFoundRoute) {
|
|
7175
|
+
const existing2 = prefetchedRoutes.get(notFoundRoute);
|
|
7176
|
+
if (!existing2) {
|
|
7177
|
+
const promise = notFoundRoute.load();
|
|
7178
|
+
prefetchedRoutes.set(notFoundRoute, promise);
|
|
7179
|
+
}
|
|
7180
|
+
}
|
|
7181
|
+
return;
|
|
7182
|
+
}
|
|
7183
|
+
const existing = prefetchedRoutes.get(matched.route);
|
|
7184
|
+
if (!existing) {
|
|
7185
|
+
const promise = matched.route.load();
|
|
7186
|
+
prefetchedRoutes.set(matched.route, promise);
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
function createHoverHandler(routes, notFoundRoute) {
|
|
7190
|
+
return function handleHover(ev) {
|
|
7191
|
+
try {
|
|
7192
|
+
const target = ev.target;
|
|
7193
|
+
if (!target) return;
|
|
7194
|
+
const anchor = target.closest("a[href]");
|
|
7195
|
+
if (!anchor) return;
|
|
7196
|
+
const href = anchor.getAttribute("href");
|
|
7197
|
+
if (!href) return;
|
|
7198
|
+
if (href.startsWith("#")) return;
|
|
7199
|
+
const url = new URL(href, window.location.href);
|
|
7200
|
+
if (url.origin !== window.location.origin) return;
|
|
7201
|
+
if (anchor.target && anchor.target !== "_self") return;
|
|
7202
|
+
const nextUrl = url.pathname + url.search;
|
|
7203
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
7204
|
+
if (nextUrl === currentUrl) return;
|
|
7205
|
+
prefetchRoute(nextUrl, routes, notFoundRoute);
|
|
7206
|
+
} catch (error) {
|
|
7207
|
+
}
|
|
7208
|
+
};
|
|
7209
|
+
}
|
|
5623
7210
|
function createClickHandler(navigate2) {
|
|
5624
7211
|
return function handleClick(ev) {
|
|
5625
7212
|
try {
|
|
@@ -5730,17 +7317,20 @@ function AppShell({
|
|
|
5730
7317
|
}
|
|
5731
7318
|
const handleClick = createClickHandler(handleNavigateInternal);
|
|
5732
7319
|
const handlePopState = createPopStateHandler(handleNavigateInternal);
|
|
7320
|
+
const handleHover = createHoverHandler(routes, notFoundRoute);
|
|
5733
7321
|
window.addEventListener("click", handleClick, false);
|
|
5734
7322
|
window.addEventListener("popstate", handlePopState, false);
|
|
7323
|
+
window.addEventListener("mouseover", handleHover, false);
|
|
5735
7324
|
return () => {
|
|
5736
7325
|
isMounted = false;
|
|
5737
7326
|
window.removeEventListener("click", handleClick, false);
|
|
5738
7327
|
window.removeEventListener("popstate", handlePopState, false);
|
|
7328
|
+
window.removeEventListener("mouseover", handleHover, false);
|
|
5739
7329
|
};
|
|
5740
|
-
}, []);
|
|
7330
|
+
}, [routes, notFoundRoute]);
|
|
5741
7331
|
useEffect(() => {
|
|
5742
7332
|
const handleDataRefresh = () => {
|
|
5743
|
-
const freshData = window
|
|
7333
|
+
const freshData = window[WINDOW_DATA_KEY2];
|
|
5744
7334
|
if (!freshData) return;
|
|
5745
7335
|
const currentPathname = window.location.pathname;
|
|
5746
7336
|
const freshPathname = freshData.pathname;
|
|
@@ -5767,6 +7357,91 @@ function AppShell({
|
|
|
5767
7357
|
return /* @__PURE__ */ jsx2(RouterContext.Provider, { value: { navigate: handleNavigate }, children: /* @__PURE__ */ jsx2(RouterView, { state }, routeKey) });
|
|
5768
7358
|
}
|
|
5769
7359
|
|
|
7360
|
+
// modules/runtime/client/hot-reload.ts
|
|
7361
|
+
function setupHotReload2() {
|
|
7362
|
+
const nodeEnv = process.env.NODE_ENV || "production";
|
|
7363
|
+
const isDev = nodeEnv === "development";
|
|
7364
|
+
console.log(`[hot-reload] NODE_ENV: ${nodeEnv}, isDev: ${isDev}`);
|
|
7365
|
+
if (!isDev) {
|
|
7366
|
+
console.log("[hot-reload] Skipping hot reload setup (not in development mode)");
|
|
7367
|
+
return;
|
|
7368
|
+
}
|
|
7369
|
+
console.log("[hot-reload] Setting up hot reload client...");
|
|
7370
|
+
let eventSource = null;
|
|
7371
|
+
let reloadTimeout = null;
|
|
7372
|
+
let reconnectTimeout = null;
|
|
7373
|
+
let reconnectAttempts = 0;
|
|
7374
|
+
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
7375
|
+
const RECONNECT_DELAY = 1e3;
|
|
7376
|
+
const RELOAD_DELAY = 100;
|
|
7377
|
+
function connect() {
|
|
7378
|
+
try {
|
|
7379
|
+
if (eventSource) {
|
|
7380
|
+
console.log("[hot-reload] Closing existing EventSource connection");
|
|
7381
|
+
eventSource.close();
|
|
7382
|
+
}
|
|
7383
|
+
const endpoint = "/__fw/hot";
|
|
7384
|
+
eventSource = new EventSource(endpoint);
|
|
7385
|
+
eventSource.addEventListener("ping", (event) => {
|
|
7386
|
+
if ("data" in event) {
|
|
7387
|
+
console.log("[hot-reload] \u2705 Connected to hot reload server");
|
|
7388
|
+
}
|
|
7389
|
+
reconnectAttempts = 0;
|
|
7390
|
+
});
|
|
7391
|
+
eventSource.addEventListener("message", (event) => {
|
|
7392
|
+
const data = event.data;
|
|
7393
|
+
if (data && typeof data === "string" && data.startsWith("reload:")) {
|
|
7394
|
+
const filePath = data.slice(7);
|
|
7395
|
+
console.log(`[hot-reload] \u{1F4DD} File changed: ${filePath}, reloading...`);
|
|
7396
|
+
if (reloadTimeout) {
|
|
7397
|
+
clearTimeout(reloadTimeout);
|
|
7398
|
+
}
|
|
7399
|
+
reloadTimeout = setTimeout(() => {
|
|
7400
|
+
try {
|
|
7401
|
+
window.location.reload();
|
|
7402
|
+
} catch (error) {
|
|
7403
|
+
console.error("[hot-reload] \u274C Error reloading page:", error);
|
|
7404
|
+
setTimeout(() => window.location.reload(), 100);
|
|
7405
|
+
}
|
|
7406
|
+
}, RELOAD_DELAY);
|
|
7407
|
+
}
|
|
7408
|
+
});
|
|
7409
|
+
eventSource.onopen = () => {
|
|
7410
|
+
reconnectAttempts = 0;
|
|
7411
|
+
};
|
|
7412
|
+
eventSource.onerror = (error) => {
|
|
7413
|
+
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
7414
|
+
const state = states[eventSource?.readyState ?? 0] || "UNKNOWN";
|
|
7415
|
+
if (eventSource?.readyState === EventSource.CONNECTING) {
|
|
7416
|
+
console.log("[hot-reload] \u23F3 Still connecting...");
|
|
7417
|
+
return;
|
|
7418
|
+
} else if (eventSource?.readyState === EventSource.OPEN) {
|
|
7419
|
+
console.warn("[hot-reload] \u26A0\uFE0F Connection error (but connection is open):", error);
|
|
7420
|
+
} else {
|
|
7421
|
+
console.warn(`[hot-reload] \u274C Connection closed (readyState: ${state})`);
|
|
7422
|
+
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
|
7423
|
+
reconnectAttempts++;
|
|
7424
|
+
const delay = RECONNECT_DELAY * reconnectAttempts;
|
|
7425
|
+
if (reconnectTimeout) {
|
|
7426
|
+
clearTimeout(reconnectTimeout);
|
|
7427
|
+
}
|
|
7428
|
+
reconnectTimeout = setTimeout(() => {
|
|
7429
|
+
console.log(`[hot-reload] \u{1F504} Reconnecting... (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
7430
|
+
connect();
|
|
7431
|
+
}, delay);
|
|
7432
|
+
} else {
|
|
7433
|
+
console.error("[hot-reload] \u274C Max reconnect attempts reached. Please refresh the page manually.");
|
|
7434
|
+
}
|
|
7435
|
+
}
|
|
7436
|
+
};
|
|
7437
|
+
} catch (error) {
|
|
7438
|
+
console.error("[hot-reload] \u274C Failed to create EventSource:", error);
|
|
7439
|
+
console.error("[hot-reload] EventSource may not be supported in this browser.");
|
|
7440
|
+
}
|
|
7441
|
+
}
|
|
7442
|
+
connect();
|
|
7443
|
+
}
|
|
7444
|
+
|
|
5770
7445
|
// modules/runtime/client/bootstrap.tsx
|
|
5771
7446
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
5772
7447
|
async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
@@ -5808,101 +7483,91 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
|
|
|
5808
7483
|
props: initialData?.props ?? {}
|
|
5809
7484
|
};
|
|
5810
7485
|
}
|
|
5811
|
-
function
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
7486
|
+
function initializeRouterData(initialUrl, initialData) {
|
|
7487
|
+
let routerData = getRouterData();
|
|
7488
|
+
if (!routerData) {
|
|
7489
|
+
const url = new URL(initialUrl, window.location.origin);
|
|
7490
|
+
routerData = {
|
|
7491
|
+
pathname: url.pathname,
|
|
7492
|
+
params: initialData?.params || {},
|
|
7493
|
+
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
7494
|
+
};
|
|
7495
|
+
setRouterData(routerData);
|
|
5816
7496
|
}
|
|
7497
|
+
}
|
|
7498
|
+
async function hydrateInitialRoute(container, initialUrl, initialData, routes, notFoundRoute, errorRoute) {
|
|
5817
7499
|
try {
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
console.log("[hot-reload] Reloading page...");
|
|
5831
|
-
window.location.reload();
|
|
5832
|
-
}, 500);
|
|
5833
|
-
}
|
|
5834
|
-
});
|
|
5835
|
-
eventSource.addEventListener("ping", () => {
|
|
5836
|
-
console.log("[hot-reload] \u2713 Connected to hot reload server");
|
|
5837
|
-
});
|
|
5838
|
-
eventSource.onopen = () => {
|
|
5839
|
-
console.log("[hot-reload] \u2713 SSE connection opened");
|
|
5840
|
-
};
|
|
5841
|
-
eventSource.onerror = (error) => {
|
|
5842
|
-
const states = ["CONNECTING", "OPEN", "CLOSED"];
|
|
5843
|
-
const state = states[eventSource.readyState] || "UNKNOWN";
|
|
5844
|
-
if (eventSource.readyState === EventSource.CONNECTING) {
|
|
5845
|
-
console.log("[hot-reload] Connecting...");
|
|
5846
|
-
} else if (eventSource.readyState === EventSource.OPEN) {
|
|
5847
|
-
console.warn("[hot-reload] Connection error (but connection is open):", error);
|
|
5848
|
-
} else {
|
|
5849
|
-
console.log("[hot-reload] Connection closed (readyState:", state, ")");
|
|
7500
|
+
const initialState = await loadInitialRoute(
|
|
7501
|
+
initialUrl,
|
|
7502
|
+
initialData,
|
|
7503
|
+
routes,
|
|
7504
|
+
notFoundRoute,
|
|
7505
|
+
errorRoute
|
|
7506
|
+
);
|
|
7507
|
+
if (initialData?.metadata) {
|
|
7508
|
+
try {
|
|
7509
|
+
applyMetadata(initialData.metadata);
|
|
7510
|
+
} catch (metadataError) {
|
|
7511
|
+
console.warn("[client] Error applying metadata:", metadataError);
|
|
5850
7512
|
}
|
|
5851
|
-
}
|
|
7513
|
+
}
|
|
7514
|
+
hydrateRoot(
|
|
7515
|
+
container,
|
|
7516
|
+
/* @__PURE__ */ jsx3(
|
|
7517
|
+
AppShell,
|
|
7518
|
+
{
|
|
7519
|
+
initialState,
|
|
7520
|
+
routes,
|
|
7521
|
+
notFoundRoute,
|
|
7522
|
+
errorRoute
|
|
7523
|
+
}
|
|
7524
|
+
)
|
|
7525
|
+
);
|
|
5852
7526
|
} catch (error) {
|
|
5853
|
-
console.
|
|
7527
|
+
console.error(
|
|
7528
|
+
"[client] Error loading initial route components for",
|
|
7529
|
+
initialUrl,
|
|
7530
|
+
error
|
|
7531
|
+
);
|
|
7532
|
+
throw error;
|
|
5854
7533
|
}
|
|
5855
7534
|
}
|
|
5856
7535
|
function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
|
|
5857
|
-
console.log("[client] Bootstrap starting, setting up hot reload...");
|
|
5858
7536
|
setupHotReload2();
|
|
5859
|
-
(async
|
|
5860
|
-
const container = document.getElementById(APP_CONTAINER_ID2);
|
|
5861
|
-
const initialData = getWindowData();
|
|
5862
|
-
if (!container) {
|
|
5863
|
-
console.error(`Container #${APP_CONTAINER_ID2} not found for hydration`);
|
|
5864
|
-
return;
|
|
5865
|
-
}
|
|
5866
|
-
const initialUrl = window.location.pathname + window.location.search;
|
|
5867
|
-
let routerData = getRouterData();
|
|
5868
|
-
if (!routerData) {
|
|
5869
|
-
const url = new URL(initialUrl, window.location.origin);
|
|
5870
|
-
routerData = {
|
|
5871
|
-
pathname: url.pathname,
|
|
5872
|
-
params: initialData?.params || {},
|
|
5873
|
-
searchParams: Object.fromEntries(url.searchParams.entries())
|
|
5874
|
-
};
|
|
5875
|
-
setRouterData(routerData);
|
|
5876
|
-
}
|
|
7537
|
+
(async () => {
|
|
5877
7538
|
try {
|
|
5878
|
-
const
|
|
7539
|
+
const container = document.getElementById(APP_CONTAINER_ID2);
|
|
7540
|
+
if (!container) {
|
|
7541
|
+
console.error(`
|
|
7542
|
+
\u274C [client] Hydration failed: Container #${APP_CONTAINER_ID2} not found`);
|
|
7543
|
+
console.error("\u{1F4A1} This usually means:");
|
|
7544
|
+
console.error(" \u2022 The HTML structure doesn't match what React expects");
|
|
7545
|
+
console.error(" \u2022 The container was removed before hydration");
|
|
7546
|
+
console.error(" \u2022 There's a mismatch between SSR and client HTML\n");
|
|
7547
|
+
return;
|
|
7548
|
+
}
|
|
7549
|
+
const initialData = getWindowData();
|
|
7550
|
+
const initialUrl = window.location.pathname + window.location.search;
|
|
7551
|
+
initializeRouterData(initialUrl, initialData);
|
|
7552
|
+
await hydrateInitialRoute(
|
|
7553
|
+
container,
|
|
5879
7554
|
initialUrl,
|
|
5880
7555
|
initialData,
|
|
5881
7556
|
routes,
|
|
5882
7557
|
notFoundRoute,
|
|
5883
7558
|
errorRoute
|
|
5884
7559
|
);
|
|
5885
|
-
if (initialData?.metadata) {
|
|
5886
|
-
applyMetadata(initialData.metadata);
|
|
5887
|
-
}
|
|
5888
|
-
hydrateRoot(
|
|
5889
|
-
container,
|
|
5890
|
-
/* @__PURE__ */ jsx3(
|
|
5891
|
-
AppShell,
|
|
5892
|
-
{
|
|
5893
|
-
initialState,
|
|
5894
|
-
routes,
|
|
5895
|
-
notFoundRoute,
|
|
5896
|
-
errorRoute
|
|
5897
|
-
}
|
|
5898
|
-
)
|
|
5899
|
-
);
|
|
5900
7560
|
} catch (error) {
|
|
5901
|
-
console.error(
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
error
|
|
5905
|
-
|
|
7561
|
+
console.error("\n\u274C [client] Fatal error during bootstrap:");
|
|
7562
|
+
console.error(error);
|
|
7563
|
+
if (error instanceof Error) {
|
|
7564
|
+
console.error("\nError details:");
|
|
7565
|
+
console.error(` Message: ${error.message}`);
|
|
7566
|
+
if (error.stack) {
|
|
7567
|
+
console.error(` Stack: ${error.stack}`);
|
|
7568
|
+
}
|
|
7569
|
+
}
|
|
7570
|
+
console.error("\n\u{1F4A1} Attempting page reload to recover...\n");
|
|
5906
7571
|
window.location.reload();
|
|
5907
7572
|
}
|
|
5908
7573
|
})();
|
|
@@ -5930,11 +7595,11 @@ var ValidationError = class extends Error {
|
|
|
5930
7595
|
format() {
|
|
5931
7596
|
const formatted = {};
|
|
5932
7597
|
for (const error of this.errors) {
|
|
5933
|
-
const
|
|
5934
|
-
if (!formatted[
|
|
5935
|
-
formatted[
|
|
7598
|
+
const path28 = error.path.join(".");
|
|
7599
|
+
if (!formatted[path28]) {
|
|
7600
|
+
formatted[path28] = [];
|
|
5936
7601
|
}
|
|
5937
|
-
formatted[
|
|
7602
|
+
formatted[path28].push(error.message);
|
|
5938
7603
|
}
|
|
5939
7604
|
return formatted;
|
|
5940
7605
|
}
|