@lolyjs/core 0.2.0-alpha.14 → 0.2.0-alpha.15

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/dist/index.cjs CHANGED
@@ -246,7 +246,7 @@ function loadLayoutsForDir(pageDir, appDir) {
246
246
  };
247
247
  }
248
248
 
249
- // modules/router/loader.ts
249
+ // modules/router/server-hook.ts
250
250
  var import_fs3 = __toESM(require("fs"));
251
251
  var import_path3 = __toESM(require("path"));
252
252
  var NAMING = {
@@ -258,14 +258,16 @@ var NAMING = {
258
258
  // Files
259
259
  SERVER_HOOK: "server.hook"
260
260
  };
261
- function loadLoaderForDir(currentDir) {
262
- const loaderTs = import_path3.default.join(currentDir, `${NAMING.SERVER_HOOK}.ts`);
263
- const loaderJs = import_path3.default.join(currentDir, `${NAMING.SERVER_HOOK}.js`);
264
- const file = import_fs3.default.existsSync(loaderTs) ? loaderTs : import_fs3.default.existsSync(loaderJs) ? loaderJs : null;
261
+ function loadServerHookForDir(currentDir) {
262
+ const pageServerHookTs = import_path3.default.join(currentDir, `page.server.hook.ts`);
263
+ const pageServerHookJs = import_path3.default.join(currentDir, `page.server.hook.js`);
264
+ const serverHookTs = import_path3.default.join(currentDir, `${NAMING.SERVER_HOOK}.ts`);
265
+ const serverHookJs = import_path3.default.join(currentDir, `${NAMING.SERVER_HOOK}.js`);
266
+ const file = import_fs3.default.existsSync(pageServerHookTs) ? pageServerHookTs : import_fs3.default.existsSync(pageServerHookJs) ? pageServerHookJs : import_fs3.default.existsSync(serverHookTs) ? serverHookTs : import_fs3.default.existsSync(serverHookJs) ? serverHookJs : null;
265
267
  if (!file) {
266
268
  return {
267
269
  middlewares: [],
268
- loader: null,
270
+ serverHook: null,
269
271
  dynamic: "auto",
270
272
  generateStaticParams: null
271
273
  };
@@ -281,12 +283,12 @@ function loadLoaderForDir(currentDir) {
281
283
  mod = require(file);
282
284
  } catch (error) {
283
285
  console.error(
284
- `[framework][loader] Error loading server hook from ${file}:`,
286
+ `[framework][server-hook] Error loading server hook from ${file}:`,
285
287
  error
286
288
  );
287
289
  return {
288
290
  middlewares: [],
289
- loader: null,
291
+ serverHook: null,
290
292
  dynamic: "auto",
291
293
  generateStaticParams: null
292
294
  };
@@ -294,16 +296,43 @@ function loadLoaderForDir(currentDir) {
294
296
  const middlewares = Array.isArray(
295
297
  mod?.[NAMING.BEFORE_MIDDLEWARES]
296
298
  ) ? mod[NAMING.BEFORE_MIDDLEWARES] : [];
297
- const loader = typeof mod?.[NAMING.GET_SERVER_DATA_FN] === "function" ? mod[NAMING.GET_SERVER_DATA_FN] : null;
299
+ const serverHook = typeof mod?.[NAMING.GET_SERVER_DATA_FN] === "function" ? mod[NAMING.GET_SERVER_DATA_FN] : null;
298
300
  const dynamic = mod?.[NAMING.RENDER_TYPE_CONST] === "force-static" || mod?.[NAMING.RENDER_TYPE_CONST] === "force-dynamic" ? mod.dynamic : "auto";
299
301
  const generateStaticParams = typeof mod?.[NAMING.GENERATE_SSG_PARAMS] === "function" ? mod[NAMING.GENERATE_SSG_PARAMS] : null;
300
302
  return {
301
303
  middlewares,
302
- loader,
304
+ serverHook,
303
305
  dynamic,
304
306
  generateStaticParams
305
307
  };
306
308
  }
309
+ function loadLayoutServerHook(layoutFile) {
310
+ const layoutDir = import_path3.default.dirname(layoutFile);
311
+ const layoutBasename = import_path3.default.basename(layoutFile, import_path3.default.extname(layoutFile));
312
+ const serverHookTs = import_path3.default.join(layoutDir, `${layoutBasename}.server.hook.ts`);
313
+ const serverHookJs = import_path3.default.join(layoutDir, `${layoutBasename}.server.hook.js`);
314
+ const file = import_fs3.default.existsSync(serverHookTs) ? serverHookTs : import_fs3.default.existsSync(serverHookJs) ? serverHookJs : null;
315
+ if (!file) {
316
+ return null;
317
+ }
318
+ if (file.endsWith(".ts") || file.endsWith(".tsx")) {
319
+ try {
320
+ require("tsx/cjs");
321
+ } catch (e) {
322
+ }
323
+ }
324
+ try {
325
+ const mod = require(file);
326
+ const serverHook = typeof mod?.getServerSideProps === "function" ? mod.getServerSideProps : null;
327
+ return serverHook;
328
+ } catch (error) {
329
+ console.error(
330
+ `[framework][server-hook] Error loading layout server hook from ${file}:`,
331
+ error
332
+ );
333
+ return null;
334
+ }
335
+ }
307
336
 
308
337
  // modules/router/loader-pages.ts
309
338
  function loadRoutes(appDir) {
@@ -335,7 +364,12 @@ function loadRoutes(appDir) {
335
364
  currentDir,
336
365
  appDir
337
366
  );
338
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(currentDir);
367
+ const layoutServerHooks = [];
368
+ for (const layoutFile of layoutFiles) {
369
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
370
+ layoutServerHooks.push(layoutServerHook);
371
+ }
372
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(currentDir);
339
373
  routes.push({
340
374
  pattern: routePath,
341
375
  regex,
@@ -345,7 +379,10 @@ function loadRoutes(appDir) {
345
379
  pageFile: fullPath,
346
380
  layoutFiles,
347
381
  middlewares,
348
- loader,
382
+ loader: serverHook,
383
+ // Keep 'loader' field name for backward compatibility
384
+ layoutServerHooks,
385
+ // Server hooks for each layout (same order as layouts)
349
386
  dynamic,
350
387
  generateStaticParams
351
388
  });
@@ -864,7 +901,12 @@ function loadRoutesFromManifest(projectRoot) {
864
901
  (f) => import_path10.default.join(projectRoot, f)
865
902
  );
866
903
  const pageDir = import_path10.default.dirname(pageFile);
867
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(pageDir);
904
+ const layoutServerHooks = [];
905
+ for (const layoutFile of layoutFiles) {
906
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
907
+ layoutServerHooks.push(layoutServerHook);
908
+ }
909
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(pageDir);
868
910
  pageRoutes.push({
869
911
  pattern: entry.pattern,
870
912
  regex,
@@ -874,7 +916,10 @@ function loadRoutesFromManifest(projectRoot) {
874
916
  pageFile,
875
917
  layoutFiles,
876
918
  middlewares,
877
- loader,
919
+ loader: serverHook,
920
+ // Keep 'loader' field name for backward compatibility
921
+ layoutServerHooks,
922
+ // Server hooks for each layout (same order as layouts)
878
923
  dynamic: entry.dynamic ?? dynamic,
879
924
  generateStaticParams
880
925
  });
@@ -1128,7 +1173,12 @@ function loadNotFoundRouteFromFilesystem(appDir) {
1128
1173
  notFoundDir,
1129
1174
  appDir
1130
1175
  );
1131
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(notFoundDir);
1176
+ const layoutServerHooks = [];
1177
+ for (const layoutFile of layoutFiles) {
1178
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
1179
+ layoutServerHooks.push(layoutServerHook);
1180
+ }
1181
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(notFoundDir);
1132
1182
  return {
1133
1183
  pattern: NOT_FOUND_PATTERN,
1134
1184
  regex: new RegExp(`^${NOT_FOUND_PATTERN}/?$`),
@@ -1138,7 +1188,10 @@ function loadNotFoundRouteFromFilesystem(appDir) {
1138
1188
  pageFile: notFoundFile,
1139
1189
  layoutFiles,
1140
1190
  middlewares,
1141
- loader,
1191
+ loader: serverHook,
1192
+ // Keep 'loader' field name for backward compatibility
1193
+ layoutServerHooks,
1194
+ // Server hooks for each layout (same order as layouts)
1142
1195
  dynamic,
1143
1196
  generateStaticParams
1144
1197
  };
@@ -1169,7 +1222,12 @@ function loadErrorRouteFromFilesystem(appDir) {
1169
1222
  appDir,
1170
1223
  appDir
1171
1224
  );
1172
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(appDir);
1225
+ const layoutServerHooks = [];
1226
+ for (const layoutFile of layoutFiles) {
1227
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
1228
+ layoutServerHooks.push(layoutServerHook);
1229
+ }
1230
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(appDir);
1173
1231
  return {
1174
1232
  pattern: ERROR_PATTERN,
1175
1233
  regex: new RegExp(`^${ERROR_PATTERN}/?$`),
@@ -1179,7 +1237,10 @@ function loadErrorRouteFromFilesystem(appDir) {
1179
1237
  pageFile: errorFile,
1180
1238
  layoutFiles,
1181
1239
  middlewares,
1182
- loader,
1240
+ loader: serverHook,
1241
+ // Keep 'loader' field name for backward compatibility
1242
+ layoutServerHooks,
1243
+ // Server hooks for each layout (same order as layouts)
1183
1244
  dynamic,
1184
1245
  generateStaticParams
1185
1246
  };
@@ -4019,8 +4080,8 @@ async function runRouteMiddlewares(route, ctx) {
4019
4080
  }
4020
4081
  }
4021
4082
 
4022
- // modules/server/handlers/loader.ts
4023
- async function runRouteLoader(route, ctx) {
4083
+ // modules/server/handlers/server-hook.ts
4084
+ async function runRouteServerHook(route, ctx) {
4024
4085
  if (!route.loader) {
4025
4086
  return { props: {} };
4026
4087
  }
@@ -4177,11 +4238,39 @@ async function handlePageRequestInternal(options) {
4177
4238
  pathname: urlPath,
4178
4239
  locals: {}
4179
4240
  };
4180
- let loaderResult2 = await runRouteLoader(notFoundPage, ctx2);
4241
+ const layoutProps2 = {};
4242
+ if (notFoundPage.layoutServerHooks && notFoundPage.layoutServerHooks.length > 0) {
4243
+ for (let i = 0; i < notFoundPage.layoutServerHooks.length; i++) {
4244
+ const layoutServerHook = notFoundPage.layoutServerHooks[i];
4245
+ if (layoutServerHook) {
4246
+ try {
4247
+ const layoutResult = await layoutServerHook(ctx2);
4248
+ if (layoutResult.props) {
4249
+ Object.assign(layoutProps2, layoutResult.props);
4250
+ }
4251
+ } catch (error) {
4252
+ const reqLogger2 = getRequestLogger(req);
4253
+ reqLogger2.warn(`Layout server hook ${i} failed for not-found`, {
4254
+ error,
4255
+ layoutFile: notFoundPage.layoutFiles[i]
4256
+ });
4257
+ }
4258
+ }
4259
+ }
4260
+ }
4261
+ let loaderResult2 = await runRouteServerHook(notFoundPage, ctx2);
4181
4262
  if (!loaderResult2.theme) {
4182
4263
  loaderResult2.theme = theme;
4183
4264
  }
4184
- const initialData2 = buildInitialData(urlPath, {}, loaderResult2);
4265
+ const combinedProps2 = {
4266
+ ...layoutProps2,
4267
+ ...loaderResult2.props || {}
4268
+ };
4269
+ const combinedLoaderResult2 = {
4270
+ ...loaderResult2,
4271
+ props: combinedProps2
4272
+ };
4273
+ const initialData2 = buildInitialData(urlPath, {}, combinedLoaderResult2);
4185
4274
  const appTree2 = buildAppTree(notFoundPage, {}, initialData2.props);
4186
4275
  initialData2.notFound = true;
4187
4276
  const nonce2 = res.locals.nonce || void 0;
@@ -4189,7 +4278,7 @@ async function handlePageRequestInternal(options) {
4189
4278
  appTree: appTree2,
4190
4279
  initialData: initialData2,
4191
4280
  routerData,
4192
- meta: loaderResult2.metadata ?? null,
4281
+ meta: combinedLoaderResult2.metadata ?? null,
4193
4282
  titleFallback: "Not found",
4194
4283
  descriptionFallback: "Loly demo",
4195
4284
  chunkHref: null,
@@ -4208,8 +4297,8 @@ async function handlePageRequestInternal(options) {
4208
4297
  },
4209
4298
  onShellError(err) {
4210
4299
  didError2 = true;
4211
- const reqLogger = getRequestLogger(req);
4212
- reqLogger.error("SSR shell error", err, { route: "not-found" });
4300
+ const reqLogger2 = getRequestLogger(req);
4301
+ reqLogger2.error("SSR shell error", err, { route: "not-found" });
4213
4302
  if (!res.headersSent && errorPage) {
4214
4303
  renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
4215
4304
  } else if (!res.headersSent) {
@@ -4221,8 +4310,8 @@ async function handlePageRequestInternal(options) {
4221
4310
  },
4222
4311
  onError(err) {
4223
4312
  didError2 = true;
4224
- const reqLogger = getRequestLogger(req);
4225
- reqLogger.error("SSR error", err, { route: "not-found" });
4313
+ const reqLogger2 = getRequestLogger(req);
4314
+ reqLogger2.error("SSR error", err, { route: "not-found" });
4226
4315
  }
4227
4316
  });
4228
4317
  req.on("close", () => abort2());
@@ -4244,9 +4333,30 @@ async function handlePageRequestInternal(options) {
4244
4333
  if (res.headersSent) {
4245
4334
  return;
4246
4335
  }
4336
+ const layoutProps = {};
4337
+ const reqLogger = getRequestLogger(req);
4338
+ if (route.layoutServerHooks && route.layoutServerHooks.length > 0) {
4339
+ for (let i = 0; i < route.layoutServerHooks.length; i++) {
4340
+ const layoutServerHook = route.layoutServerHooks[i];
4341
+ if (layoutServerHook) {
4342
+ try {
4343
+ const layoutResult = await layoutServerHook(ctx);
4344
+ if (layoutResult.props) {
4345
+ Object.assign(layoutProps, layoutResult.props);
4346
+ }
4347
+ } catch (error) {
4348
+ reqLogger.warn(`Layout server hook ${i} failed`, {
4349
+ error,
4350
+ layoutFile: route.layoutFiles[i],
4351
+ route: route.pattern
4352
+ });
4353
+ }
4354
+ }
4355
+ }
4356
+ }
4247
4357
  let loaderResult;
4248
4358
  try {
4249
- loaderResult = await runRouteLoader(route, ctx);
4359
+ loaderResult = await runRouteServerHook(route, ctx);
4250
4360
  if (!loaderResult.theme) {
4251
4361
  loaderResult.theme = theme;
4252
4362
  }
@@ -4265,8 +4375,18 @@ async function handlePageRequestInternal(options) {
4265
4375
  }
4266
4376
  }
4267
4377
  }
4378
+ const combinedProps = {
4379
+ ...layoutProps,
4380
+ // Props from layouts (stable)
4381
+ ...loaderResult.props || {}
4382
+ // Props from page (overrides layout)
4383
+ };
4384
+ const combinedLoaderResult = {
4385
+ ...loaderResult,
4386
+ props: combinedProps
4387
+ };
4268
4388
  if (isDataReq) {
4269
- handleDataResponse(res, loaderResult, theme);
4389
+ handleDataResponse(res, combinedLoaderResult, theme);
4270
4390
  return;
4271
4391
  }
4272
4392
  if (loaderResult.redirect) {
@@ -4281,7 +4401,7 @@ async function handlePageRequestInternal(options) {
4281
4401
  }
4282
4402
  return;
4283
4403
  }
4284
- const initialData = buildInitialData(urlPath, params, loaderResult);
4404
+ const initialData = buildInitialData(urlPath, params, combinedLoaderResult);
4285
4405
  const appTree = buildAppTree(route, params, initialData.props);
4286
4406
  const chunkName = routeChunks[route.pattern];
4287
4407
  let chunkHref = null;
@@ -4297,7 +4417,7 @@ async function handlePageRequestInternal(options) {
4297
4417
  appTree,
4298
4418
  initialData,
4299
4419
  routerData,
4300
- meta: loaderResult.metadata,
4420
+ meta: combinedLoaderResult.metadata,
4301
4421
  titleFallback: "Loly framework",
4302
4422
  descriptionFallback: "Loly demo",
4303
4423
  chunkHref,
@@ -4318,8 +4438,8 @@ async function handlePageRequestInternal(options) {
4318
4438
  },
4319
4439
  onShellError(err) {
4320
4440
  didError = true;
4321
- const reqLogger = getRequestLogger(req);
4322
- reqLogger.error("SSR shell error", err, { route: matched?.route?.pattern || "unknown" });
4441
+ const reqLogger2 = getRequestLogger(req);
4442
+ reqLogger2.error("SSR shell error", err, { route: matched?.route?.pattern || "unknown" });
4323
4443
  if (!res.headersSent && errorPage) {
4324
4444
  renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
4325
4445
  } else if (!res.headersSent) {
@@ -4331,8 +4451,8 @@ async function handlePageRequestInternal(options) {
4331
4451
  },
4332
4452
  onError(err) {
4333
4453
  didError = true;
4334
- const reqLogger = getRequestLogger(req);
4335
- reqLogger.error("SSR error", err, { route: matched?.route?.pattern || "unknown" });
4454
+ const reqLogger2 = getRequestLogger(req);
4455
+ reqLogger2.error("SSR error", err, { route: matched?.route?.pattern || "unknown" });
4336
4456
  }
4337
4457
  });
4338
4458
  req.on("close", () => {
@@ -4349,11 +4469,39 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4349
4469
  pathname: req.path,
4350
4470
  locals: { error }
4351
4471
  };
4352
- let loaderResult = await runRouteLoader(errorPage, ctx);
4472
+ const layoutProps = {};
4473
+ const reqLogger = getRequestLogger(req);
4474
+ if (errorPage.layoutServerHooks && errorPage.layoutServerHooks.length > 0) {
4475
+ for (let i = 0; i < errorPage.layoutServerHooks.length; i++) {
4476
+ const layoutServerHook = errorPage.layoutServerHooks[i];
4477
+ if (layoutServerHook) {
4478
+ try {
4479
+ const layoutResult = await layoutServerHook(ctx);
4480
+ if (layoutResult.props) {
4481
+ Object.assign(layoutProps, layoutResult.props);
4482
+ }
4483
+ } catch (err) {
4484
+ reqLogger.warn(`Layout server hook ${i} failed for error page`, {
4485
+ error: err,
4486
+ layoutFile: errorPage.layoutFiles[i]
4487
+ });
4488
+ }
4489
+ }
4490
+ }
4491
+ }
4492
+ let loaderResult = await runRouteServerHook(errorPage, ctx);
4353
4493
  if (!loaderResult.theme && theme) {
4354
4494
  loaderResult.theme = theme;
4355
4495
  }
4356
- const initialData = buildInitialData(req.path, { error: String(error) }, loaderResult);
4496
+ const combinedProps = {
4497
+ ...layoutProps,
4498
+ ...loaderResult.props || {}
4499
+ };
4500
+ const combinedLoaderResult = {
4501
+ ...loaderResult,
4502
+ props: combinedProps
4503
+ };
4504
+ const initialData = buildInitialData(req.path, { error: String(error) }, combinedLoaderResult);
4357
4505
  const routerData = buildRouterData(req);
4358
4506
  initialData.error = true;
4359
4507
  if (isDataReq) {
@@ -4363,8 +4511,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4363
4511
  error: true,
4364
4512
  message: String(error),
4365
4513
  props: initialData.props,
4366
- metadata: loaderResult.metadata ?? null,
4367
- theme: loaderResult.theme ?? theme ?? null
4514
+ metadata: combinedLoaderResult.metadata ?? null,
4515
+ theme: combinedLoaderResult.theme ?? theme ?? null
4368
4516
  }));
4369
4517
  return;
4370
4518
  }
@@ -4386,7 +4534,7 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4386
4534
  appTree,
4387
4535
  initialData,
4388
4536
  routerData,
4389
- meta: loaderResult.metadata ?? null,
4537
+ meta: combinedLoaderResult.metadata ?? null,
4390
4538
  titleFallback: "Error",
4391
4539
  descriptionFallback: "An error occurred",
4392
4540
  chunkHref,
@@ -4407,8 +4555,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4407
4555
  },
4408
4556
  onShellError(err) {
4409
4557
  didError = true;
4410
- const reqLogger = getRequestLogger(req);
4411
- reqLogger.error("Error rendering error page", err, { type: "shellError" });
4558
+ const reqLogger2 = getRequestLogger(req);
4559
+ reqLogger2.error("Error rendering error page", err, { type: "shellError" });
4412
4560
  if (!res.headersSent) {
4413
4561
  res.statusCode = 500;
4414
4562
  res.setHeader("Content-Type", "text/html; charset=utf-8");
@@ -4418,8 +4566,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4418
4566
  },
4419
4567
  onError(err) {
4420
4568
  didError = true;
4421
- const reqLogger = getRequestLogger(req);
4422
- reqLogger.error("Error in error page", err);
4569
+ const reqLogger2 = getRequestLogger(req);
4570
+ reqLogger2.error("Error in error page", err);
4423
4571
  }
4424
4572
  });
4425
4573
  req.on("close", () => {