@lolyjs/core 0.2.0-alpha.18 → 0.2.0-alpha.19

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
@@ -15034,7 +15034,7 @@ async function runRouteServerHook(route, ctx) {
15034
15034
  }
15035
15035
 
15036
15036
  // modules/server/handlers/response.ts
15037
- function handleDataResponse(res, loaderResult, theme) {
15037
+ function handleDataResponse(res, loaderResult, theme, layoutProps, pageProps, error, message) {
15038
15038
  res.setHeader("Content-Type", "application/json; charset=utf-8");
15039
15039
  if (loaderResult.redirect) {
15040
15040
  res.statusCode = 200;
@@ -15046,14 +15046,26 @@ function handleDataResponse(res, loaderResult, theme) {
15046
15046
  res.end(JSON.stringify({ notFound: true }));
15047
15047
  return;
15048
15048
  }
15049
- res.statusCode = 200;
15050
- res.end(
15051
- JSON.stringify({
15052
- props: loaderResult.props ?? {},
15053
- metadata: loaderResult.metadata ?? null,
15054
- theme: loaderResult.theme ?? theme ?? null
15055
- })
15056
- );
15049
+ const response = {
15050
+ // Combined props for backward compatibility
15051
+ props: loaderResult.props ?? {},
15052
+ metadata: loaderResult.metadata ?? null,
15053
+ theme: loaderResult.theme ?? theme ?? null
15054
+ };
15055
+ if (layoutProps !== void 0 && layoutProps !== null) {
15056
+ response.layoutProps = layoutProps;
15057
+ }
15058
+ if (pageProps !== void 0 && pageProps !== null) {
15059
+ response.pageProps = pageProps;
15060
+ }
15061
+ if (error !== void 0) {
15062
+ response.error = error;
15063
+ }
15064
+ if (message !== void 0) {
15065
+ response.message = message;
15066
+ }
15067
+ res.statusCode = error ? 500 : 200;
15068
+ res.end(JSON.stringify(response));
15057
15069
  }
15058
15070
  function handleRedirect(res, redirect) {
15059
15071
  const { destination, permanent } = redirect;
@@ -15191,6 +15203,7 @@ async function handlePageRequestInternal(options) {
15191
15203
  const clientCssPath = env === "dev" ? "/static/client.css" : projectRoot ? getClientCssPath(projectRoot) : "/static/client.css";
15192
15204
  const assetManifest = env === "prod" && projectRoot ? loadAssetManifest(projectRoot) : null;
15193
15205
  const isDataReq = isDataRequest(req);
15206
+ const skipLayoutHooks = isDataReq && req.headers["x-skip-layout-hooks"] === "true";
15194
15207
  if (env === "prod" && ssgOutDir) {
15195
15208
  if (isDataReq) {
15196
15209
  if (tryServeSsgData(res, ssgOutDir, urlPath)) {
@@ -15214,7 +15227,7 @@ async function handlePageRequestInternal(options) {
15214
15227
  locals: {}
15215
15228
  };
15216
15229
  const layoutProps2 = {};
15217
- if (notFoundPage.layoutServerHooks && notFoundPage.layoutServerHooks.length > 0) {
15230
+ if (!skipLayoutHooks && notFoundPage.layoutServerHooks && notFoundPage.layoutServerHooks.length > 0) {
15218
15231
  for (let i = 0; i < notFoundPage.layoutServerHooks.length; i++) {
15219
15232
  const layoutServerHook = notFoundPage.layoutServerHooks[i];
15220
15233
  if (layoutServerHook) {
@@ -15249,6 +15262,17 @@ async function handlePageRequestInternal(options) {
15249
15262
  ...loaderResult2,
15250
15263
  props: combinedProps2
15251
15264
  };
15265
+ if (isDataReq) {
15266
+ const pagePropsOnly = loaderResult2.props || {};
15267
+ handleDataResponse(
15268
+ res,
15269
+ combinedLoaderResult2,
15270
+ theme,
15271
+ skipLayoutHooks ? null : Object.keys(layoutProps2).length > 0 ? layoutProps2 : null,
15272
+ pagePropsOnly
15273
+ );
15274
+ return;
15275
+ }
15252
15276
  const initialData2 = buildInitialData(urlPath, {}, combinedLoaderResult2);
15253
15277
  const appTree2 = buildAppTree(notFoundPage, {}, initialData2.props);
15254
15278
  initialData2.notFound = true;
@@ -15320,7 +15344,7 @@ async function handlePageRequestInternal(options) {
15320
15344
  const layoutProps = {};
15321
15345
  const layoutMetadata = [];
15322
15346
  const reqLogger = getRequestLogger(req);
15323
- if (route.layoutServerHooks && route.layoutServerHooks.length > 0) {
15347
+ if (!skipLayoutHooks && route.layoutServerHooks && route.layoutServerHooks.length > 0) {
15324
15348
  for (let i = 0; i < route.layoutServerHooks.length; i++) {
15325
15349
  const layoutServerHook = route.layoutServerHooks[i];
15326
15350
  if (layoutServerHook) {
@@ -15404,7 +15428,14 @@ async function handlePageRequestInternal(options) {
15404
15428
  metadata: combinedMetadata
15405
15429
  };
15406
15430
  if (isDataReq) {
15407
- handleDataResponse(res, combinedLoaderResult, theme);
15431
+ const pagePropsOnly = loaderResult.props || {};
15432
+ handleDataResponse(
15433
+ res,
15434
+ combinedLoaderResult,
15435
+ theme,
15436
+ skipLayoutHooks ? null : Object.keys(layoutProps).length > 0 ? layoutProps : null,
15437
+ pagePropsOnly
15438
+ );
15408
15439
  return;
15409
15440
  }
15410
15441
  if (loaderResult.redirect) {
@@ -15498,6 +15529,7 @@ async function handlePageRequestInternal(options) {
15498
15529
  async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot, env = "dev") {
15499
15530
  try {
15500
15531
  const isDataReq = isDataRequest(req);
15532
+ const skipLayoutHooks = isDataReq && req.headers["x-skip-layout-hooks"] === "true";
15501
15533
  const ctx = {
15502
15534
  req,
15503
15535
  res,
@@ -15507,7 +15539,7 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
15507
15539
  };
15508
15540
  const layoutProps = {};
15509
15541
  const reqLogger = getRequestLogger(req);
15510
- if (errorPage.layoutServerHooks && errorPage.layoutServerHooks.length > 0) {
15542
+ if (!skipLayoutHooks && errorPage.layoutServerHooks && errorPage.layoutServerHooks.length > 0) {
15511
15543
  for (let i = 0; i < errorPage.layoutServerHooks.length; i++) {
15512
15544
  const layoutServerHook = errorPage.layoutServerHooks[i];
15513
15545
  if (layoutServerHook) {
@@ -15545,15 +15577,18 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
15545
15577
  const routerData = buildRouterData(req);
15546
15578
  initialData.error = true;
15547
15579
  if (isDataReq) {
15548
- res.statusCode = 500;
15549
- res.setHeader("Content-Type", "application/json; charset=utf-8");
15550
- res.end(JSON.stringify({
15551
- error: true,
15552
- message: String(error),
15553
- props: initialData.props,
15554
- metadata: combinedLoaderResult.metadata ?? null,
15555
- theme: combinedLoaderResult.theme ?? theme ?? null
15556
- }));
15580
+ const pagePropsOnly = loaderResult.props || {};
15581
+ handleDataResponse(
15582
+ res,
15583
+ combinedLoaderResult,
15584
+ theme,
15585
+ skipLayoutHooks ? null : Object.keys(layoutProps).length > 0 ? layoutProps : null,
15586
+ pagePropsOnly,
15587
+ true,
15588
+ // error flag
15589
+ String(error)
15590
+ // error message
15591
+ );
15557
15592
  return;
15558
15593
  }
15559
15594
  const appTree = buildAppTree(errorPage, { error: String(error) }, initialData.props);
@@ -17559,12 +17594,29 @@ var APP_CONTAINER_ID2 = "__app";
17559
17594
  var ROUTER_NAVIGATE_KEY = "__LOLY_ROUTER_NAVIGATE__";
17560
17595
 
17561
17596
  // modules/runtime/client/window-data.ts
17597
+ var LAYOUT_PROPS_KEY = "__FW_LAYOUT_PROPS__";
17562
17598
  function getWindowData() {
17563
17599
  if (typeof window === "undefined") {
17564
17600
  return null;
17565
17601
  }
17566
17602
  return window[WINDOW_DATA_KEY2] ?? null;
17567
17603
  }
17604
+ function getPreservedLayoutProps() {
17605
+ if (typeof window === "undefined") {
17606
+ return null;
17607
+ }
17608
+ return window[LAYOUT_PROPS_KEY] ?? null;
17609
+ }
17610
+ function setPreservedLayoutProps(props) {
17611
+ if (typeof window === "undefined") {
17612
+ return;
17613
+ }
17614
+ if (props === null) {
17615
+ delete window[LAYOUT_PROPS_KEY];
17616
+ } else {
17617
+ window[LAYOUT_PROPS_KEY] = props;
17618
+ }
17619
+ }
17568
17620
  function getRouterData() {
17569
17621
  if (typeof window === "undefined") {
17570
17622
  return null;
@@ -17900,14 +17952,16 @@ function deleteCacheEntry(key) {
17900
17952
  function buildDataUrl(url) {
17901
17953
  return url + (url.includes("?") ? "&" : "?") + "__fw_data=1";
17902
17954
  }
17903
- async function fetchRouteDataOnce(url) {
17955
+ async function fetchRouteDataOnce(url, skipLayoutHooks = true) {
17904
17956
  const dataUrl = buildDataUrl(url);
17905
- const res = await fetch(dataUrl, {
17906
- headers: {
17907
- "x-fw-data": "1",
17908
- Accept: "application/json"
17909
- }
17910
- });
17957
+ const headers = {
17958
+ "x-fw-data": "1",
17959
+ Accept: "application/json"
17960
+ };
17961
+ if (skipLayoutHooks) {
17962
+ headers["x-skip-layout-hooks"] = "true";
17963
+ }
17964
+ const res = await fetch(dataUrl, { headers });
17911
17965
  let json = {};
17912
17966
  try {
17913
17967
  const text = await res.text();
@@ -17933,7 +17987,7 @@ async function getRouteData(url, options) {
17933
17987
  deleteCacheEntry(key);
17934
17988
  }
17935
17989
  const entry = dataCache.get(key);
17936
- if (entry) {
17990
+ if (entry && !options?.revalidate) {
17937
17991
  if (entry.status === "fulfilled") {
17938
17992
  updateLRU(key);
17939
17993
  return entry.value;
@@ -17942,12 +17996,29 @@ async function getRouteData(url, options) {
17942
17996
  return entry.promise;
17943
17997
  }
17944
17998
  }
17945
- const promise = fetchRouteDataOnce(url).then((value) => {
17946
- setCacheEntry(key, { status: "fulfilled", value });
17999
+ const skipLayoutHooks = !options?.revalidate;
18000
+ const currentEntry = dataCache.get(key);
18001
+ if (currentEntry && !options?.revalidate) {
18002
+ if (currentEntry.status === "fulfilled") {
18003
+ updateLRU(key);
18004
+ return currentEntry.value;
18005
+ }
18006
+ if (currentEntry.status === "pending") {
18007
+ return currentEntry.promise;
18008
+ }
18009
+ }
18010
+ const promise = fetchRouteDataOnce(url, skipLayoutHooks).then((value) => {
18011
+ const entryAfterFetch = dataCache.get(key);
18012
+ if (!entryAfterFetch || entryAfterFetch.status === "pending") {
18013
+ setCacheEntry(key, { status: "fulfilled", value });
18014
+ }
17947
18015
  return value;
17948
18016
  }).catch((error) => {
17949
18017
  console.error("[client][cache] Error fetching route data:", error);
17950
- dataCache.set(key, { status: "rejected", error });
18018
+ const entryAfterFetch = dataCache.get(key);
18019
+ if (!entryAfterFetch || entryAfterFetch.status === "pending") {
18020
+ dataCache.set(key, { status: "rejected", error });
18021
+ }
17951
18022
  throw error;
17952
18023
  });
17953
18024
  dataCache.set(key, { status: "pending", promise });
@@ -17972,10 +18043,22 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
17972
18043
  } else if (json.theme) {
17973
18044
  theme = json.theme;
17974
18045
  }
18046
+ let layoutProps = {};
18047
+ if (json.layoutProps !== void 0 && json.layoutProps !== null) {
18048
+ layoutProps = json.layoutProps;
18049
+ setPreservedLayoutProps(layoutProps);
18050
+ } else {
18051
+ const preserved = getPreservedLayoutProps();
18052
+ if (preserved) {
18053
+ layoutProps = preserved;
18054
+ }
18055
+ }
18056
+ const pageProps = json.pageProps ?? json.props ?? {
18057
+ error: json.message || "An error occurred"
18058
+ };
17975
18059
  const errorProps = {
17976
- ...json.props || {
17977
- error: json.message || "An error occurred"
17978
- },
18060
+ ...layoutProps,
18061
+ ...pageProps,
17979
18062
  theme
17980
18063
  };
17981
18064
  const windowData = {
@@ -18032,8 +18115,20 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
18032
18115
  } else if (json.theme) {
18033
18116
  theme = json.theme;
18034
18117
  }
18118
+ let layoutProps = {};
18119
+ if (json.layoutProps !== void 0 && json.layoutProps !== null) {
18120
+ layoutProps = json.layoutProps;
18121
+ setPreservedLayoutProps(layoutProps);
18122
+ } else {
18123
+ const preserved = getPreservedLayoutProps();
18124
+ if (preserved) {
18125
+ layoutProps = preserved;
18126
+ }
18127
+ }
18128
+ const pageProps = json.pageProps ?? json.props ?? {};
18035
18129
  const notFoundProps = {
18036
- ...json.props ?? {},
18130
+ ...layoutProps,
18131
+ ...pageProps,
18037
18132
  theme
18038
18133
  };
18039
18134
  const windowData = {
@@ -18090,8 +18185,20 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
18090
18185
  } else if (json.theme) {
18091
18186
  theme = json.theme;
18092
18187
  }
18093
- const newProps = {
18094
- ...json.props ?? {},
18188
+ let layoutProps = {};
18189
+ if (json.layoutProps !== void 0 && json.layoutProps !== null) {
18190
+ layoutProps = json.layoutProps;
18191
+ setPreservedLayoutProps(layoutProps);
18192
+ } else {
18193
+ const preserved = getPreservedLayoutProps();
18194
+ if (preserved) {
18195
+ layoutProps = preserved;
18196
+ }
18197
+ }
18198
+ const pageProps = json.pageProps ?? json.props ?? {};
18199
+ const combinedProps = {
18200
+ ...layoutProps,
18201
+ ...pageProps,
18095
18202
  theme
18096
18203
  // Always include theme
18097
18204
  };
@@ -18103,7 +18210,7 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
18103
18210
  const windowData = {
18104
18211
  pathname: nextUrl,
18105
18212
  params: matched.params,
18106
- props: newProps,
18213
+ props: combinedProps,
18107
18214
  metadata: json.metadata ?? null,
18108
18215
  theme,
18109
18216
  notFound: false,
@@ -18131,7 +18238,7 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
18131
18238
  route: matched.route,
18132
18239
  params: matched.params,
18133
18240
  components,
18134
- props: newProps
18241
+ props: combinedProps
18135
18242
  });
18136
18243
  return true;
18137
18244
  }
@@ -18364,7 +18471,7 @@ function AppShell({
18364
18471
  return () => {
18365
18472
  window.removeEventListener("fw-data-refresh", handleDataRefresh);
18366
18473
  };
18367
- }, [state.url]);
18474
+ }, []);
18368
18475
  const isError = state.route === errorRoute;
18369
18476
  const isNotFound = state.route === notFoundRoute;
18370
18477
  const routeType = isError ? "error" : isNotFound ? "notfound" : "normal";
@@ -18563,6 +18670,9 @@ function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
18563
18670
  }
18564
18671
  const initialData = getWindowData();
18565
18672
  const initialUrl = window.location.pathname + window.location.search;
18673
+ if (initialData?.props) {
18674
+ setPreservedLayoutProps(initialData.props);
18675
+ }
18566
18676
  initializeRouterData(initialUrl, initialData);
18567
18677
  await hydrateInitialRoute(
18568
18678
  container,