@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.d.mts CHANGED
@@ -168,6 +168,10 @@ interface LoaderResult {
168
168
  className?: string;
169
169
  theme?: string;
170
170
  }
171
+ /**
172
+ * Server loader function type (getServerSideProps).
173
+ * This function is exported from server.hook.ts files.
174
+ */
171
175
  type ServerLoader = (ctx: ServerContext) => Promise<LoaderResult>;
172
176
  interface PageMetadata {
173
177
  title?: string;
package/dist/index.d.ts CHANGED
@@ -168,6 +168,10 @@ interface LoaderResult {
168
168
  className?: string;
169
169
  theme?: string;
170
170
  }
171
+ /**
172
+ * Server loader function type (getServerSideProps).
173
+ * This function is exported from server.hook.ts files.
174
+ */
171
175
  type ServerLoader = (ctx: ServerContext) => Promise<LoaderResult>;
172
176
  interface PageMetadata {
173
177
  title?: string;
package/dist/index.js CHANGED
@@ -204,7 +204,7 @@ function loadLayoutsForDir(pageDir, appDir) {
204
204
  };
205
205
  }
206
206
 
207
- // modules/router/loader.ts
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 loadLoaderForDir(currentDir) {
220
- const loaderTs = path3.join(currentDir, `${NAMING.SERVER_HOOK}.ts`);
221
- const loaderJs = path3.join(currentDir, `${NAMING.SERVER_HOOK}.js`);
222
- const file = fs3.existsSync(loaderTs) ? loaderTs : fs3.existsSync(loaderJs) ? loaderJs : null;
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
- loader: null,
228
+ serverHook: null,
227
229
  dynamic: "auto",
228
230
  generateStaticParams: null
229
231
  };
@@ -239,12 +241,12 @@ function loadLoaderForDir(currentDir) {
239
241
  mod = __require(file);
240
242
  } catch (error) {
241
243
  console.error(
242
- `[framework][loader] Error loading server hook from ${file}:`,
244
+ `[framework][server-hook] Error loading server hook from ${file}:`,
243
245
  error
244
246
  );
245
247
  return {
246
248
  middlewares: [],
247
- loader: null,
249
+ serverHook: null,
248
250
  dynamic: "auto",
249
251
  generateStaticParams: null
250
252
  };
@@ -252,16 +254,43 @@ function loadLoaderForDir(currentDir) {
252
254
  const middlewares = Array.isArray(
253
255
  mod?.[NAMING.BEFORE_MIDDLEWARES]
254
256
  ) ? mod[NAMING.BEFORE_MIDDLEWARES] : [];
255
- const loader = typeof mod?.[NAMING.GET_SERVER_DATA_FN] === "function" ? mod[NAMING.GET_SERVER_DATA_FN] : null;
257
+ const serverHook = typeof mod?.[NAMING.GET_SERVER_DATA_FN] === "function" ? mod[NAMING.GET_SERVER_DATA_FN] : null;
256
258
  const dynamic = mod?.[NAMING.RENDER_TYPE_CONST] === "force-static" || mod?.[NAMING.RENDER_TYPE_CONST] === "force-dynamic" ? mod.dynamic : "auto";
257
259
  const generateStaticParams = typeof mod?.[NAMING.GENERATE_SSG_PARAMS] === "function" ? mod[NAMING.GENERATE_SSG_PARAMS] : null;
258
260
  return {
259
261
  middlewares,
260
- loader,
262
+ serverHook,
261
263
  dynamic,
262
264
  generateStaticParams
263
265
  };
264
266
  }
267
+ function loadLayoutServerHook(layoutFile) {
268
+ const layoutDir = path3.dirname(layoutFile);
269
+ const layoutBasename = path3.basename(layoutFile, path3.extname(layoutFile));
270
+ const serverHookTs = path3.join(layoutDir, `${layoutBasename}.server.hook.ts`);
271
+ const serverHookJs = path3.join(layoutDir, `${layoutBasename}.server.hook.js`);
272
+ const file = fs3.existsSync(serverHookTs) ? serverHookTs : fs3.existsSync(serverHookJs) ? serverHookJs : null;
273
+ if (!file) {
274
+ return null;
275
+ }
276
+ if (file.endsWith(".ts") || file.endsWith(".tsx")) {
277
+ try {
278
+ __require("tsx/cjs");
279
+ } catch (e) {
280
+ }
281
+ }
282
+ try {
283
+ const mod = __require(file);
284
+ const serverHook = typeof mod?.getServerSideProps === "function" ? mod.getServerSideProps : null;
285
+ return serverHook;
286
+ } catch (error) {
287
+ console.error(
288
+ `[framework][server-hook] Error loading layout server hook from ${file}:`,
289
+ error
290
+ );
291
+ return null;
292
+ }
293
+ }
265
294
 
266
295
  // modules/router/loader-pages.ts
267
296
  function loadRoutes(appDir) {
@@ -293,7 +322,12 @@ function loadRoutes(appDir) {
293
322
  currentDir,
294
323
  appDir
295
324
  );
296
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(currentDir);
325
+ const layoutServerHooks = [];
326
+ for (const layoutFile of layoutFiles) {
327
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
328
+ layoutServerHooks.push(layoutServerHook);
329
+ }
330
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(currentDir);
297
331
  routes.push({
298
332
  pattern: routePath,
299
333
  regex,
@@ -303,7 +337,10 @@ function loadRoutes(appDir) {
303
337
  pageFile: fullPath,
304
338
  layoutFiles,
305
339
  middlewares,
306
- loader,
340
+ loader: serverHook,
341
+ // Keep 'loader' field name for backward compatibility
342
+ layoutServerHooks,
343
+ // Server hooks for each layout (same order as layouts)
307
344
  dynamic,
308
345
  generateStaticParams
309
346
  });
@@ -822,7 +859,12 @@ function loadRoutesFromManifest(projectRoot) {
822
859
  (f) => path8.join(projectRoot, f)
823
860
  );
824
861
  const pageDir = path8.dirname(pageFile);
825
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(pageDir);
862
+ const layoutServerHooks = [];
863
+ for (const layoutFile of layoutFiles) {
864
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
865
+ layoutServerHooks.push(layoutServerHook);
866
+ }
867
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(pageDir);
826
868
  pageRoutes.push({
827
869
  pattern: entry.pattern,
828
870
  regex,
@@ -832,7 +874,10 @@ function loadRoutesFromManifest(projectRoot) {
832
874
  pageFile,
833
875
  layoutFiles,
834
876
  middlewares,
835
- loader,
877
+ loader: serverHook,
878
+ // Keep 'loader' field name for backward compatibility
879
+ layoutServerHooks,
880
+ // Server hooks for each layout (same order as layouts)
836
881
  dynamic: entry.dynamic ?? dynamic,
837
882
  generateStaticParams
838
883
  });
@@ -1086,7 +1131,12 @@ function loadNotFoundRouteFromFilesystem(appDir) {
1086
1131
  notFoundDir,
1087
1132
  appDir
1088
1133
  );
1089
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(notFoundDir);
1134
+ const layoutServerHooks = [];
1135
+ for (const layoutFile of layoutFiles) {
1136
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
1137
+ layoutServerHooks.push(layoutServerHook);
1138
+ }
1139
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(notFoundDir);
1090
1140
  return {
1091
1141
  pattern: NOT_FOUND_PATTERN,
1092
1142
  regex: new RegExp(`^${NOT_FOUND_PATTERN}/?$`),
@@ -1096,7 +1146,10 @@ function loadNotFoundRouteFromFilesystem(appDir) {
1096
1146
  pageFile: notFoundFile,
1097
1147
  layoutFiles,
1098
1148
  middlewares,
1099
- loader,
1149
+ loader: serverHook,
1150
+ // Keep 'loader' field name for backward compatibility
1151
+ layoutServerHooks,
1152
+ // Server hooks for each layout (same order as layouts)
1100
1153
  dynamic,
1101
1154
  generateStaticParams
1102
1155
  };
@@ -1127,7 +1180,12 @@ function loadErrorRouteFromFilesystem(appDir) {
1127
1180
  appDir,
1128
1181
  appDir
1129
1182
  );
1130
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(appDir);
1183
+ const layoutServerHooks = [];
1184
+ for (const layoutFile of layoutFiles) {
1185
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
1186
+ layoutServerHooks.push(layoutServerHook);
1187
+ }
1188
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(appDir);
1131
1189
  return {
1132
1190
  pattern: ERROR_PATTERN,
1133
1191
  regex: new RegExp(`^${ERROR_PATTERN}/?$`),
@@ -1137,7 +1195,10 @@ function loadErrorRouteFromFilesystem(appDir) {
1137
1195
  pageFile: errorFile,
1138
1196
  layoutFiles,
1139
1197
  middlewares,
1140
- loader,
1198
+ loader: serverHook,
1199
+ // Keep 'loader' field name for backward compatibility
1200
+ layoutServerHooks,
1201
+ // Server hooks for each layout (same order as layouts)
1141
1202
  dynamic,
1142
1203
  generateStaticParams
1143
1204
  };
@@ -3977,8 +4038,8 @@ async function runRouteMiddlewares(route, ctx) {
3977
4038
  }
3978
4039
  }
3979
4040
 
3980
- // modules/server/handlers/loader.ts
3981
- async function runRouteLoader(route, ctx) {
4041
+ // modules/server/handlers/server-hook.ts
4042
+ async function runRouteServerHook(route, ctx) {
3982
4043
  if (!route.loader) {
3983
4044
  return { props: {} };
3984
4045
  }
@@ -4135,11 +4196,39 @@ async function handlePageRequestInternal(options) {
4135
4196
  pathname: urlPath,
4136
4197
  locals: {}
4137
4198
  };
4138
- let loaderResult2 = await runRouteLoader(notFoundPage, ctx2);
4199
+ const layoutProps2 = {};
4200
+ if (notFoundPage.layoutServerHooks && notFoundPage.layoutServerHooks.length > 0) {
4201
+ for (let i = 0; i < notFoundPage.layoutServerHooks.length; i++) {
4202
+ const layoutServerHook = notFoundPage.layoutServerHooks[i];
4203
+ if (layoutServerHook) {
4204
+ try {
4205
+ const layoutResult = await layoutServerHook(ctx2);
4206
+ if (layoutResult.props) {
4207
+ Object.assign(layoutProps2, layoutResult.props);
4208
+ }
4209
+ } catch (error) {
4210
+ const reqLogger2 = getRequestLogger(req);
4211
+ reqLogger2.warn(`Layout server hook ${i} failed for not-found`, {
4212
+ error,
4213
+ layoutFile: notFoundPage.layoutFiles[i]
4214
+ });
4215
+ }
4216
+ }
4217
+ }
4218
+ }
4219
+ let loaderResult2 = await runRouteServerHook(notFoundPage, ctx2);
4139
4220
  if (!loaderResult2.theme) {
4140
4221
  loaderResult2.theme = theme;
4141
4222
  }
4142
- const initialData2 = buildInitialData(urlPath, {}, loaderResult2);
4223
+ const combinedProps2 = {
4224
+ ...layoutProps2,
4225
+ ...loaderResult2.props || {}
4226
+ };
4227
+ const combinedLoaderResult2 = {
4228
+ ...loaderResult2,
4229
+ props: combinedProps2
4230
+ };
4231
+ const initialData2 = buildInitialData(urlPath, {}, combinedLoaderResult2);
4143
4232
  const appTree2 = buildAppTree(notFoundPage, {}, initialData2.props);
4144
4233
  initialData2.notFound = true;
4145
4234
  const nonce2 = res.locals.nonce || void 0;
@@ -4147,7 +4236,7 @@ async function handlePageRequestInternal(options) {
4147
4236
  appTree: appTree2,
4148
4237
  initialData: initialData2,
4149
4238
  routerData,
4150
- meta: loaderResult2.metadata ?? null,
4239
+ meta: combinedLoaderResult2.metadata ?? null,
4151
4240
  titleFallback: "Not found",
4152
4241
  descriptionFallback: "Loly demo",
4153
4242
  chunkHref: null,
@@ -4166,8 +4255,8 @@ async function handlePageRequestInternal(options) {
4166
4255
  },
4167
4256
  onShellError(err) {
4168
4257
  didError2 = true;
4169
- const reqLogger = getRequestLogger(req);
4170
- reqLogger.error("SSR shell error", err, { route: "not-found" });
4258
+ const reqLogger2 = getRequestLogger(req);
4259
+ reqLogger2.error("SSR shell error", err, { route: "not-found" });
4171
4260
  if (!res.headersSent && errorPage) {
4172
4261
  renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
4173
4262
  } else if (!res.headersSent) {
@@ -4179,8 +4268,8 @@ async function handlePageRequestInternal(options) {
4179
4268
  },
4180
4269
  onError(err) {
4181
4270
  didError2 = true;
4182
- const reqLogger = getRequestLogger(req);
4183
- reqLogger.error("SSR error", err, { route: "not-found" });
4271
+ const reqLogger2 = getRequestLogger(req);
4272
+ reqLogger2.error("SSR error", err, { route: "not-found" });
4184
4273
  }
4185
4274
  });
4186
4275
  req.on("close", () => abort2());
@@ -4202,9 +4291,30 @@ async function handlePageRequestInternal(options) {
4202
4291
  if (res.headersSent) {
4203
4292
  return;
4204
4293
  }
4294
+ const layoutProps = {};
4295
+ const reqLogger = getRequestLogger(req);
4296
+ if (route.layoutServerHooks && route.layoutServerHooks.length > 0) {
4297
+ for (let i = 0; i < route.layoutServerHooks.length; i++) {
4298
+ const layoutServerHook = route.layoutServerHooks[i];
4299
+ if (layoutServerHook) {
4300
+ try {
4301
+ const layoutResult = await layoutServerHook(ctx);
4302
+ if (layoutResult.props) {
4303
+ Object.assign(layoutProps, layoutResult.props);
4304
+ }
4305
+ } catch (error) {
4306
+ reqLogger.warn(`Layout server hook ${i} failed`, {
4307
+ error,
4308
+ layoutFile: route.layoutFiles[i],
4309
+ route: route.pattern
4310
+ });
4311
+ }
4312
+ }
4313
+ }
4314
+ }
4205
4315
  let loaderResult;
4206
4316
  try {
4207
- loaderResult = await runRouteLoader(route, ctx);
4317
+ loaderResult = await runRouteServerHook(route, ctx);
4208
4318
  if (!loaderResult.theme) {
4209
4319
  loaderResult.theme = theme;
4210
4320
  }
@@ -4223,8 +4333,18 @@ async function handlePageRequestInternal(options) {
4223
4333
  }
4224
4334
  }
4225
4335
  }
4336
+ const combinedProps = {
4337
+ ...layoutProps,
4338
+ // Props from layouts (stable)
4339
+ ...loaderResult.props || {}
4340
+ // Props from page (overrides layout)
4341
+ };
4342
+ const combinedLoaderResult = {
4343
+ ...loaderResult,
4344
+ props: combinedProps
4345
+ };
4226
4346
  if (isDataReq) {
4227
- handleDataResponse(res, loaderResult, theme);
4347
+ handleDataResponse(res, combinedLoaderResult, theme);
4228
4348
  return;
4229
4349
  }
4230
4350
  if (loaderResult.redirect) {
@@ -4239,7 +4359,7 @@ async function handlePageRequestInternal(options) {
4239
4359
  }
4240
4360
  return;
4241
4361
  }
4242
- const initialData = buildInitialData(urlPath, params, loaderResult);
4362
+ const initialData = buildInitialData(urlPath, params, combinedLoaderResult);
4243
4363
  const appTree = buildAppTree(route, params, initialData.props);
4244
4364
  const chunkName = routeChunks[route.pattern];
4245
4365
  let chunkHref = null;
@@ -4255,7 +4375,7 @@ async function handlePageRequestInternal(options) {
4255
4375
  appTree,
4256
4376
  initialData,
4257
4377
  routerData,
4258
- meta: loaderResult.metadata,
4378
+ meta: combinedLoaderResult.metadata,
4259
4379
  titleFallback: "Loly framework",
4260
4380
  descriptionFallback: "Loly demo",
4261
4381
  chunkHref,
@@ -4276,8 +4396,8 @@ async function handlePageRequestInternal(options) {
4276
4396
  },
4277
4397
  onShellError(err) {
4278
4398
  didError = true;
4279
- const reqLogger = getRequestLogger(req);
4280
- reqLogger.error("SSR shell error", err, { route: matched?.route?.pattern || "unknown" });
4399
+ const reqLogger2 = getRequestLogger(req);
4400
+ reqLogger2.error("SSR shell error", err, { route: matched?.route?.pattern || "unknown" });
4281
4401
  if (!res.headersSent && errorPage) {
4282
4402
  renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
4283
4403
  } else if (!res.headersSent) {
@@ -4289,8 +4409,8 @@ async function handlePageRequestInternal(options) {
4289
4409
  },
4290
4410
  onError(err) {
4291
4411
  didError = true;
4292
- const reqLogger = getRequestLogger(req);
4293
- reqLogger.error("SSR error", err, { route: matched?.route?.pattern || "unknown" });
4412
+ const reqLogger2 = getRequestLogger(req);
4413
+ reqLogger2.error("SSR error", err, { route: matched?.route?.pattern || "unknown" });
4294
4414
  }
4295
4415
  });
4296
4416
  req.on("close", () => {
@@ -4307,11 +4427,39 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4307
4427
  pathname: req.path,
4308
4428
  locals: { error }
4309
4429
  };
4310
- let loaderResult = await runRouteLoader(errorPage, ctx);
4430
+ const layoutProps = {};
4431
+ const reqLogger = getRequestLogger(req);
4432
+ if (errorPage.layoutServerHooks && errorPage.layoutServerHooks.length > 0) {
4433
+ for (let i = 0; i < errorPage.layoutServerHooks.length; i++) {
4434
+ const layoutServerHook = errorPage.layoutServerHooks[i];
4435
+ if (layoutServerHook) {
4436
+ try {
4437
+ const layoutResult = await layoutServerHook(ctx);
4438
+ if (layoutResult.props) {
4439
+ Object.assign(layoutProps, layoutResult.props);
4440
+ }
4441
+ } catch (err) {
4442
+ reqLogger.warn(`Layout server hook ${i} failed for error page`, {
4443
+ error: err,
4444
+ layoutFile: errorPage.layoutFiles[i]
4445
+ });
4446
+ }
4447
+ }
4448
+ }
4449
+ }
4450
+ let loaderResult = await runRouteServerHook(errorPage, ctx);
4311
4451
  if (!loaderResult.theme && theme) {
4312
4452
  loaderResult.theme = theme;
4313
4453
  }
4314
- const initialData = buildInitialData(req.path, { error: String(error) }, loaderResult);
4454
+ const combinedProps = {
4455
+ ...layoutProps,
4456
+ ...loaderResult.props || {}
4457
+ };
4458
+ const combinedLoaderResult = {
4459
+ ...loaderResult,
4460
+ props: combinedProps
4461
+ };
4462
+ const initialData = buildInitialData(req.path, { error: String(error) }, combinedLoaderResult);
4315
4463
  const routerData = buildRouterData(req);
4316
4464
  initialData.error = true;
4317
4465
  if (isDataReq) {
@@ -4321,8 +4469,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4321
4469
  error: true,
4322
4470
  message: String(error),
4323
4471
  props: initialData.props,
4324
- metadata: loaderResult.metadata ?? null,
4325
- theme: loaderResult.theme ?? theme ?? null
4472
+ metadata: combinedLoaderResult.metadata ?? null,
4473
+ theme: combinedLoaderResult.theme ?? theme ?? null
4326
4474
  }));
4327
4475
  return;
4328
4476
  }
@@ -4344,7 +4492,7 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4344
4492
  appTree,
4345
4493
  initialData,
4346
4494
  routerData,
4347
- meta: loaderResult.metadata ?? null,
4495
+ meta: combinedLoaderResult.metadata ?? null,
4348
4496
  titleFallback: "Error",
4349
4497
  descriptionFallback: "An error occurred",
4350
4498
  chunkHref,
@@ -4365,8 +4513,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4365
4513
  },
4366
4514
  onShellError(err) {
4367
4515
  didError = true;
4368
- const reqLogger = getRequestLogger(req);
4369
- reqLogger.error("Error rendering error page", err, { type: "shellError" });
4516
+ const reqLogger2 = getRequestLogger(req);
4517
+ reqLogger2.error("Error rendering error page", err, { type: "shellError" });
4370
4518
  if (!res.headersSent) {
4371
4519
  res.statusCode = 500;
4372
4520
  res.setHeader("Content-Type", "text/html; charset=utf-8");
@@ -4376,8 +4524,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4376
4524
  },
4377
4525
  onError(err) {
4378
4526
  didError = true;
4379
- const reqLogger = getRequestLogger(req);
4380
- reqLogger.error("Error in error page", err);
4527
+ const reqLogger2 = getRequestLogger(req);
4528
+ reqLogger2.error("Error in error page", err);
4381
4529
  }
4382
4530
  });
4383
4531
  req.on("close", () => {