@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/cli.js CHANGED
@@ -164,7 +164,7 @@ function loadLayoutsForDir(pageDir, appDir) {
164
164
  };
165
165
  }
166
166
 
167
- // modules/router/loader.ts
167
+ // modules/router/server-hook.ts
168
168
  import fs2 from "fs";
169
169
  import path2 from "path";
170
170
  var NAMING = {
@@ -176,14 +176,16 @@ var NAMING = {
176
176
  // Files
177
177
  SERVER_HOOK: "server.hook"
178
178
  };
179
- function loadLoaderForDir(currentDir) {
180
- const loaderTs = path2.join(currentDir, `${NAMING.SERVER_HOOK}.ts`);
181
- const loaderJs = path2.join(currentDir, `${NAMING.SERVER_HOOK}.js`);
182
- const file = fs2.existsSync(loaderTs) ? loaderTs : fs2.existsSync(loaderJs) ? loaderJs : null;
179
+ function loadServerHookForDir(currentDir) {
180
+ const pageServerHookTs = path2.join(currentDir, `page.server.hook.ts`);
181
+ const pageServerHookJs = path2.join(currentDir, `page.server.hook.js`);
182
+ const serverHookTs = path2.join(currentDir, `${NAMING.SERVER_HOOK}.ts`);
183
+ const serverHookJs = path2.join(currentDir, `${NAMING.SERVER_HOOK}.js`);
184
+ const file = fs2.existsSync(pageServerHookTs) ? pageServerHookTs : fs2.existsSync(pageServerHookJs) ? pageServerHookJs : fs2.existsSync(serverHookTs) ? serverHookTs : fs2.existsSync(serverHookJs) ? serverHookJs : null;
183
185
  if (!file) {
184
186
  return {
185
187
  middlewares: [],
186
- loader: null,
188
+ serverHook: null,
187
189
  dynamic: "auto",
188
190
  generateStaticParams: null
189
191
  };
@@ -199,12 +201,12 @@ function loadLoaderForDir(currentDir) {
199
201
  mod = __require(file);
200
202
  } catch (error) {
201
203
  console.error(
202
- `[framework][loader] Error loading server hook from ${file}:`,
204
+ `[framework][server-hook] Error loading server hook from ${file}:`,
203
205
  error
204
206
  );
205
207
  return {
206
208
  middlewares: [],
207
- loader: null,
209
+ serverHook: null,
208
210
  dynamic: "auto",
209
211
  generateStaticParams: null
210
212
  };
@@ -212,16 +214,43 @@ function loadLoaderForDir(currentDir) {
212
214
  const middlewares = Array.isArray(
213
215
  mod?.[NAMING.BEFORE_MIDDLEWARES]
214
216
  ) ? mod[NAMING.BEFORE_MIDDLEWARES] : [];
215
- const loader = typeof mod?.[NAMING.GET_SERVER_DATA_FN] === "function" ? mod[NAMING.GET_SERVER_DATA_FN] : null;
217
+ const serverHook = typeof mod?.[NAMING.GET_SERVER_DATA_FN] === "function" ? mod[NAMING.GET_SERVER_DATA_FN] : null;
216
218
  const dynamic = mod?.[NAMING.RENDER_TYPE_CONST] === "force-static" || mod?.[NAMING.RENDER_TYPE_CONST] === "force-dynamic" ? mod.dynamic : "auto";
217
219
  const generateStaticParams = typeof mod?.[NAMING.GENERATE_SSG_PARAMS] === "function" ? mod[NAMING.GENERATE_SSG_PARAMS] : null;
218
220
  return {
219
221
  middlewares,
220
- loader,
222
+ serverHook,
221
223
  dynamic,
222
224
  generateStaticParams
223
225
  };
224
226
  }
227
+ function loadLayoutServerHook(layoutFile) {
228
+ const layoutDir = path2.dirname(layoutFile);
229
+ const layoutBasename = path2.basename(layoutFile, path2.extname(layoutFile));
230
+ const serverHookTs = path2.join(layoutDir, `${layoutBasename}.server.hook.ts`);
231
+ const serverHookJs = path2.join(layoutDir, `${layoutBasename}.server.hook.js`);
232
+ const file = fs2.existsSync(serverHookTs) ? serverHookTs : fs2.existsSync(serverHookJs) ? serverHookJs : null;
233
+ if (!file) {
234
+ return null;
235
+ }
236
+ if (file.endsWith(".ts") || file.endsWith(".tsx")) {
237
+ try {
238
+ __require("tsx/cjs");
239
+ } catch (e) {
240
+ }
241
+ }
242
+ try {
243
+ const mod = __require(file);
244
+ const serverHook = typeof mod?.getServerSideProps === "function" ? mod.getServerSideProps : null;
245
+ return serverHook;
246
+ } catch (error) {
247
+ console.error(
248
+ `[framework][server-hook] Error loading layout server hook from ${file}:`,
249
+ error
250
+ );
251
+ return null;
252
+ }
253
+ }
225
254
 
226
255
  // modules/router/loader-pages.ts
227
256
  function loadRoutes(appDir) {
@@ -253,7 +282,12 @@ function loadRoutes(appDir) {
253
282
  currentDir,
254
283
  appDir
255
284
  );
256
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(currentDir);
285
+ const layoutServerHooks = [];
286
+ for (const layoutFile of layoutFiles) {
287
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
288
+ layoutServerHooks.push(layoutServerHook);
289
+ }
290
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(currentDir);
257
291
  routes.push({
258
292
  pattern: routePath,
259
293
  regex,
@@ -263,7 +297,10 @@ function loadRoutes(appDir) {
263
297
  pageFile: fullPath,
264
298
  layoutFiles,
265
299
  middlewares,
266
- loader,
300
+ loader: serverHook,
301
+ // Keep 'loader' field name for backward compatibility
302
+ layoutServerHooks,
303
+ // Server hooks for each layout (same order as layouts)
267
304
  dynamic,
268
305
  generateStaticParams
269
306
  });
@@ -782,7 +819,12 @@ function loadRoutesFromManifest(projectRoot) {
782
819
  (f) => path7.join(projectRoot, f)
783
820
  );
784
821
  const pageDir = path7.dirname(pageFile);
785
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(pageDir);
822
+ const layoutServerHooks = [];
823
+ for (const layoutFile of layoutFiles) {
824
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
825
+ layoutServerHooks.push(layoutServerHook);
826
+ }
827
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(pageDir);
786
828
  pageRoutes.push({
787
829
  pattern: entry.pattern,
788
830
  regex,
@@ -792,7 +834,10 @@ function loadRoutesFromManifest(projectRoot) {
792
834
  pageFile,
793
835
  layoutFiles,
794
836
  middlewares,
795
- loader,
837
+ loader: serverHook,
838
+ // Keep 'loader' field name for backward compatibility
839
+ layoutServerHooks,
840
+ // Server hooks for each layout (same order as layouts)
796
841
  dynamic: entry.dynamic ?? dynamic,
797
842
  generateStaticParams
798
843
  });
@@ -1046,7 +1091,12 @@ function loadNotFoundRouteFromFilesystem(appDir) {
1046
1091
  notFoundDir,
1047
1092
  appDir
1048
1093
  );
1049
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(notFoundDir);
1094
+ const layoutServerHooks = [];
1095
+ for (const layoutFile of layoutFiles) {
1096
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
1097
+ layoutServerHooks.push(layoutServerHook);
1098
+ }
1099
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(notFoundDir);
1050
1100
  return {
1051
1101
  pattern: NOT_FOUND_PATTERN,
1052
1102
  regex: new RegExp(`^${NOT_FOUND_PATTERN}/?$`),
@@ -1056,7 +1106,10 @@ function loadNotFoundRouteFromFilesystem(appDir) {
1056
1106
  pageFile: notFoundFile,
1057
1107
  layoutFiles,
1058
1108
  middlewares,
1059
- loader,
1109
+ loader: serverHook,
1110
+ // Keep 'loader' field name for backward compatibility
1111
+ layoutServerHooks,
1112
+ // Server hooks for each layout (same order as layouts)
1060
1113
  dynamic,
1061
1114
  generateStaticParams
1062
1115
  };
@@ -1087,7 +1140,12 @@ function loadErrorRouteFromFilesystem(appDir) {
1087
1140
  appDir,
1088
1141
  appDir
1089
1142
  );
1090
- const { middlewares, loader, dynamic, generateStaticParams } = loadLoaderForDir(appDir);
1143
+ const layoutServerHooks = [];
1144
+ for (const layoutFile of layoutFiles) {
1145
+ const layoutServerHook = loadLayoutServerHook(layoutFile);
1146
+ layoutServerHooks.push(layoutServerHook);
1147
+ }
1148
+ const { middlewares, serverHook, dynamic, generateStaticParams } = loadServerHookForDir(appDir);
1091
1149
  return {
1092
1150
  pattern: ERROR_PATTERN,
1093
1151
  regex: new RegExp(`^${ERROR_PATTERN}/?$`),
@@ -1097,7 +1155,10 @@ function loadErrorRouteFromFilesystem(appDir) {
1097
1155
  pageFile: errorFile,
1098
1156
  layoutFiles,
1099
1157
  middlewares,
1100
- loader,
1158
+ loader: serverHook,
1159
+ // Keep 'loader' field name for backward compatibility
1160
+ layoutServerHooks,
1161
+ // Server hooks for each layout (same order as layouts)
1101
1162
  dynamic,
1102
1163
  generateStaticParams
1103
1164
  };
@@ -4326,8 +4387,8 @@ async function runRouteMiddlewares(route, ctx) {
4326
4387
  }
4327
4388
  }
4328
4389
 
4329
- // modules/server/handlers/loader.ts
4330
- async function runRouteLoader(route, ctx) {
4390
+ // modules/server/handlers/server-hook.ts
4391
+ async function runRouteServerHook(route, ctx) {
4331
4392
  if (!route.loader) {
4332
4393
  return { props: {} };
4333
4394
  }
@@ -4484,11 +4545,39 @@ async function handlePageRequestInternal(options) {
4484
4545
  pathname: urlPath,
4485
4546
  locals: {}
4486
4547
  };
4487
- let loaderResult2 = await runRouteLoader(notFoundPage, ctx2);
4548
+ const layoutProps2 = {};
4549
+ if (notFoundPage.layoutServerHooks && notFoundPage.layoutServerHooks.length > 0) {
4550
+ for (let i = 0; i < notFoundPage.layoutServerHooks.length; i++) {
4551
+ const layoutServerHook = notFoundPage.layoutServerHooks[i];
4552
+ if (layoutServerHook) {
4553
+ try {
4554
+ const layoutResult = await layoutServerHook(ctx2);
4555
+ if (layoutResult.props) {
4556
+ Object.assign(layoutProps2, layoutResult.props);
4557
+ }
4558
+ } catch (error) {
4559
+ const reqLogger2 = getRequestLogger(req);
4560
+ reqLogger2.warn(`Layout server hook ${i} failed for not-found`, {
4561
+ error,
4562
+ layoutFile: notFoundPage.layoutFiles[i]
4563
+ });
4564
+ }
4565
+ }
4566
+ }
4567
+ }
4568
+ let loaderResult2 = await runRouteServerHook(notFoundPage, ctx2);
4488
4569
  if (!loaderResult2.theme) {
4489
4570
  loaderResult2.theme = theme;
4490
4571
  }
4491
- const initialData2 = buildInitialData(urlPath, {}, loaderResult2);
4572
+ const combinedProps2 = {
4573
+ ...layoutProps2,
4574
+ ...loaderResult2.props || {}
4575
+ };
4576
+ const combinedLoaderResult2 = {
4577
+ ...loaderResult2,
4578
+ props: combinedProps2
4579
+ };
4580
+ const initialData2 = buildInitialData(urlPath, {}, combinedLoaderResult2);
4492
4581
  const appTree2 = buildAppTree(notFoundPage, {}, initialData2.props);
4493
4582
  initialData2.notFound = true;
4494
4583
  const nonce2 = res.locals.nonce || void 0;
@@ -4496,7 +4585,7 @@ async function handlePageRequestInternal(options) {
4496
4585
  appTree: appTree2,
4497
4586
  initialData: initialData2,
4498
4587
  routerData,
4499
- meta: loaderResult2.metadata ?? null,
4588
+ meta: combinedLoaderResult2.metadata ?? null,
4500
4589
  titleFallback: "Not found",
4501
4590
  descriptionFallback: "Loly demo",
4502
4591
  chunkHref: null,
@@ -4515,8 +4604,8 @@ async function handlePageRequestInternal(options) {
4515
4604
  },
4516
4605
  onShellError(err) {
4517
4606
  didError2 = true;
4518
- const reqLogger = getRequestLogger(req);
4519
- reqLogger.error("SSR shell error", err, { route: "not-found" });
4607
+ const reqLogger2 = getRequestLogger(req);
4608
+ reqLogger2.error("SSR shell error", err, { route: "not-found" });
4520
4609
  if (!res.headersSent && errorPage) {
4521
4610
  renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
4522
4611
  } else if (!res.headersSent) {
@@ -4528,8 +4617,8 @@ async function handlePageRequestInternal(options) {
4528
4617
  },
4529
4618
  onError(err) {
4530
4619
  didError2 = true;
4531
- const reqLogger = getRequestLogger(req);
4532
- reqLogger.error("SSR error", err, { route: "not-found" });
4620
+ const reqLogger2 = getRequestLogger(req);
4621
+ reqLogger2.error("SSR error", err, { route: "not-found" });
4533
4622
  }
4534
4623
  });
4535
4624
  req.on("close", () => abort2());
@@ -4551,9 +4640,30 @@ async function handlePageRequestInternal(options) {
4551
4640
  if (res.headersSent) {
4552
4641
  return;
4553
4642
  }
4643
+ const layoutProps = {};
4644
+ const reqLogger = getRequestLogger(req);
4645
+ if (route.layoutServerHooks && route.layoutServerHooks.length > 0) {
4646
+ for (let i = 0; i < route.layoutServerHooks.length; i++) {
4647
+ const layoutServerHook = route.layoutServerHooks[i];
4648
+ if (layoutServerHook) {
4649
+ try {
4650
+ const layoutResult = await layoutServerHook(ctx);
4651
+ if (layoutResult.props) {
4652
+ Object.assign(layoutProps, layoutResult.props);
4653
+ }
4654
+ } catch (error) {
4655
+ reqLogger.warn(`Layout server hook ${i} failed`, {
4656
+ error,
4657
+ layoutFile: route.layoutFiles[i],
4658
+ route: route.pattern
4659
+ });
4660
+ }
4661
+ }
4662
+ }
4663
+ }
4554
4664
  let loaderResult;
4555
4665
  try {
4556
- loaderResult = await runRouteLoader(route, ctx);
4666
+ loaderResult = await runRouteServerHook(route, ctx);
4557
4667
  if (!loaderResult.theme) {
4558
4668
  loaderResult.theme = theme;
4559
4669
  }
@@ -4572,8 +4682,18 @@ async function handlePageRequestInternal(options) {
4572
4682
  }
4573
4683
  }
4574
4684
  }
4685
+ const combinedProps = {
4686
+ ...layoutProps,
4687
+ // Props from layouts (stable)
4688
+ ...loaderResult.props || {}
4689
+ // Props from page (overrides layout)
4690
+ };
4691
+ const combinedLoaderResult = {
4692
+ ...loaderResult,
4693
+ props: combinedProps
4694
+ };
4575
4695
  if (isDataReq) {
4576
- handleDataResponse(res, loaderResult, theme);
4696
+ handleDataResponse(res, combinedLoaderResult, theme);
4577
4697
  return;
4578
4698
  }
4579
4699
  if (loaderResult.redirect) {
@@ -4588,7 +4708,7 @@ async function handlePageRequestInternal(options) {
4588
4708
  }
4589
4709
  return;
4590
4710
  }
4591
- const initialData = buildInitialData(urlPath, params, loaderResult);
4711
+ const initialData = buildInitialData(urlPath, params, combinedLoaderResult);
4592
4712
  const appTree = buildAppTree(route, params, initialData.props);
4593
4713
  const chunkName = routeChunks[route.pattern];
4594
4714
  let chunkHref = null;
@@ -4604,7 +4724,7 @@ async function handlePageRequestInternal(options) {
4604
4724
  appTree,
4605
4725
  initialData,
4606
4726
  routerData,
4607
- meta: loaderResult.metadata,
4727
+ meta: combinedLoaderResult.metadata,
4608
4728
  titleFallback: "Loly framework",
4609
4729
  descriptionFallback: "Loly demo",
4610
4730
  chunkHref,
@@ -4625,8 +4745,8 @@ async function handlePageRequestInternal(options) {
4625
4745
  },
4626
4746
  onShellError(err) {
4627
4747
  didError = true;
4628
- const reqLogger = getRequestLogger(req);
4629
- reqLogger.error("SSR shell error", err, { route: matched?.route?.pattern || "unknown" });
4748
+ const reqLogger2 = getRequestLogger(req);
4749
+ reqLogger2.error("SSR shell error", err, { route: matched?.route?.pattern || "unknown" });
4630
4750
  if (!res.headersSent && errorPage) {
4631
4751
  renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
4632
4752
  } else if (!res.headersSent) {
@@ -4638,8 +4758,8 @@ async function handlePageRequestInternal(options) {
4638
4758
  },
4639
4759
  onError(err) {
4640
4760
  didError = true;
4641
- const reqLogger = getRequestLogger(req);
4642
- reqLogger.error("SSR error", err, { route: matched?.route?.pattern || "unknown" });
4761
+ const reqLogger2 = getRequestLogger(req);
4762
+ reqLogger2.error("SSR error", err, { route: matched?.route?.pattern || "unknown" });
4643
4763
  }
4644
4764
  });
4645
4765
  req.on("close", () => {
@@ -4656,11 +4776,39 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4656
4776
  pathname: req.path,
4657
4777
  locals: { error }
4658
4778
  };
4659
- let loaderResult = await runRouteLoader(errorPage, ctx);
4779
+ const layoutProps = {};
4780
+ const reqLogger = getRequestLogger(req);
4781
+ if (errorPage.layoutServerHooks && errorPage.layoutServerHooks.length > 0) {
4782
+ for (let i = 0; i < errorPage.layoutServerHooks.length; i++) {
4783
+ const layoutServerHook = errorPage.layoutServerHooks[i];
4784
+ if (layoutServerHook) {
4785
+ try {
4786
+ const layoutResult = await layoutServerHook(ctx);
4787
+ if (layoutResult.props) {
4788
+ Object.assign(layoutProps, layoutResult.props);
4789
+ }
4790
+ } catch (err) {
4791
+ reqLogger.warn(`Layout server hook ${i} failed for error page`, {
4792
+ error: err,
4793
+ layoutFile: errorPage.layoutFiles[i]
4794
+ });
4795
+ }
4796
+ }
4797
+ }
4798
+ }
4799
+ let loaderResult = await runRouteServerHook(errorPage, ctx);
4660
4800
  if (!loaderResult.theme && theme) {
4661
4801
  loaderResult.theme = theme;
4662
4802
  }
4663
- const initialData = buildInitialData(req.path, { error: String(error) }, loaderResult);
4803
+ const combinedProps = {
4804
+ ...layoutProps,
4805
+ ...loaderResult.props || {}
4806
+ };
4807
+ const combinedLoaderResult = {
4808
+ ...loaderResult,
4809
+ props: combinedProps
4810
+ };
4811
+ const initialData = buildInitialData(req.path, { error: String(error) }, combinedLoaderResult);
4664
4812
  const routerData = buildRouterData(req);
4665
4813
  initialData.error = true;
4666
4814
  if (isDataReq) {
@@ -4670,8 +4818,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4670
4818
  error: true,
4671
4819
  message: String(error),
4672
4820
  props: initialData.props,
4673
- metadata: loaderResult.metadata ?? null,
4674
- theme: loaderResult.theme ?? theme ?? null
4821
+ metadata: combinedLoaderResult.metadata ?? null,
4822
+ theme: combinedLoaderResult.theme ?? theme ?? null
4675
4823
  }));
4676
4824
  return;
4677
4825
  }
@@ -4693,7 +4841,7 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4693
4841
  appTree,
4694
4842
  initialData,
4695
4843
  routerData,
4696
- meta: loaderResult.metadata ?? null,
4844
+ meta: combinedLoaderResult.metadata ?? null,
4697
4845
  titleFallback: "Error",
4698
4846
  descriptionFallback: "An error occurred",
4699
4847
  chunkHref,
@@ -4714,8 +4862,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4714
4862
  },
4715
4863
  onShellError(err) {
4716
4864
  didError = true;
4717
- const reqLogger = getRequestLogger(req);
4718
- reqLogger.error("Error rendering error page", err, { type: "shellError" });
4865
+ const reqLogger2 = getRequestLogger(req);
4866
+ reqLogger2.error("Error rendering error page", err, { type: "shellError" });
4719
4867
  if (!res.headersSent) {
4720
4868
  res.statusCode = 500;
4721
4869
  res.setHeader("Content-Type", "text/html; charset=utf-8");
@@ -4725,8 +4873,8 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4725
4873
  },
4726
4874
  onError(err) {
4727
4875
  didError = true;
4728
- const reqLogger = getRequestLogger(req);
4729
- reqLogger.error("Error in error page", err);
4876
+ const reqLogger2 = getRequestLogger(req);
4877
+ reqLogger2.error("Error in error page", err);
4730
4878
  }
4731
4879
  });
4732
4880
  req.on("close", () => {