@lolyjs/core 0.2.0-alpha.9 → 0.3.0-alpha.0

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.
Files changed (55) hide show
  1. package/README.md +1593 -762
  2. package/dist/{bootstrap-DgvWWDim.d.mts → bootstrap-BfGTMUkj.d.mts} +12 -0
  3. package/dist/{bootstrap-DgvWWDim.d.ts → bootstrap-BfGTMUkj.d.ts} +12 -0
  4. package/dist/cli.cjs +16397 -2601
  5. package/dist/cli.cjs.map +1 -1
  6. package/dist/cli.mjs +19096 -0
  7. package/dist/cli.mjs.map +1 -0
  8. package/dist/index.cjs +17419 -3204
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.mts +323 -56
  11. package/dist/index.d.ts +323 -56
  12. package/dist/index.mjs +20236 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/dist/index.types-Duhjyfit.d.mts +280 -0
  15. package/dist/index.types-Duhjyfit.d.ts +280 -0
  16. package/dist/react/cache.cjs +82 -32
  17. package/dist/react/cache.cjs.map +1 -1
  18. package/dist/react/cache.d.mts +29 -21
  19. package/dist/react/cache.d.ts +29 -21
  20. package/dist/react/{cache.js → cache.mjs} +84 -34
  21. package/dist/react/cache.mjs.map +1 -0
  22. package/dist/react/components.cjs +11 -12
  23. package/dist/react/components.cjs.map +1 -1
  24. package/dist/react/{components.js → components.mjs} +12 -13
  25. package/dist/react/components.mjs.map +1 -0
  26. package/dist/react/hooks.cjs +16 -6
  27. package/dist/react/hooks.cjs.map +1 -1
  28. package/dist/react/{hooks.js → hooks.mjs} +24 -14
  29. package/dist/react/hooks.mjs.map +1 -0
  30. package/dist/react/sockets.cjs +5 -6
  31. package/dist/react/sockets.cjs.map +1 -1
  32. package/dist/react/sockets.mjs +21 -0
  33. package/dist/react/sockets.mjs.map +1 -0
  34. package/dist/react/themes.cjs +61 -18
  35. package/dist/react/themes.cjs.map +1 -1
  36. package/dist/react/{themes.js → themes.mjs} +64 -21
  37. package/dist/react/themes.mjs.map +1 -0
  38. package/dist/runtime.cjs +465 -117
  39. package/dist/runtime.cjs.map +1 -1
  40. package/dist/runtime.d.mts +2 -2
  41. package/dist/runtime.d.ts +2 -2
  42. package/dist/{runtime.js → runtime.mjs} +466 -118
  43. package/dist/runtime.mjs.map +1 -0
  44. package/package.json +26 -26
  45. package/dist/cli.js +0 -5291
  46. package/dist/cli.js.map +0 -1
  47. package/dist/index.js +0 -6015
  48. package/dist/index.js.map +0 -1
  49. package/dist/react/cache.js.map +0 -1
  50. package/dist/react/components.js.map +0 -1
  51. package/dist/react/hooks.js.map +0 -1
  52. package/dist/react/sockets.js +0 -22
  53. package/dist/react/sockets.js.map +0 -1
  54. package/dist/react/themes.js.map +0 -1
  55. package/dist/runtime.js.map +0 -1
package/dist/runtime.cjs CHANGED
@@ -34,12 +34,29 @@ var APP_CONTAINER_ID = "__app";
34
34
  var ROUTER_NAVIGATE_KEY = "__LOLY_ROUTER_NAVIGATE__";
35
35
 
36
36
  // modules/runtime/client/window-data.ts
37
+ var LAYOUT_PROPS_KEY = "__FW_LAYOUT_PROPS__";
37
38
  function getWindowData() {
38
39
  if (typeof window === "undefined") {
39
40
  return null;
40
41
  }
41
42
  return window[WINDOW_DATA_KEY] ?? null;
42
43
  }
44
+ function getPreservedLayoutProps() {
45
+ if (typeof window === "undefined") {
46
+ return null;
47
+ }
48
+ return window[LAYOUT_PROPS_KEY] ?? null;
49
+ }
50
+ function setPreservedLayoutProps(props) {
51
+ if (typeof window === "undefined") {
52
+ return;
53
+ }
54
+ if (props === null) {
55
+ delete window[LAYOUT_PROPS_KEY];
56
+ } else {
57
+ window[LAYOUT_PROPS_KEY] = props;
58
+ }
59
+ }
43
60
  function getRouterData() {
44
61
  if (typeof window === "undefined") {
45
62
  return null;
@@ -111,22 +128,158 @@ function matchRouteClient(pathWithSearch, routes) {
111
128
  }
112
129
 
113
130
  // modules/runtime/client/metadata.ts
131
+ function getOrCreateMeta(selector, attributes) {
132
+ let meta = document.querySelector(selector);
133
+ if (!meta) {
134
+ meta = document.createElement("meta");
135
+ if (attributes.name) meta.name = attributes.name;
136
+ if (attributes.property) meta.setAttribute("property", attributes.property);
137
+ if (attributes.httpEquiv) meta.httpEquiv = attributes.httpEquiv;
138
+ document.head.appendChild(meta);
139
+ }
140
+ return meta;
141
+ }
142
+ function getOrCreateLink(rel, href) {
143
+ const selector = `link[rel="${rel}"]`;
144
+ let link = document.querySelector(selector);
145
+ if (!link) {
146
+ link = document.createElement("link");
147
+ link.rel = rel;
148
+ link.href = href;
149
+ document.head.appendChild(link);
150
+ } else {
151
+ link.href = href;
152
+ }
153
+ return link;
154
+ }
114
155
  function applyMetadata(md) {
115
156
  if (!md) return;
116
157
  if (md.title) {
117
158
  document.title = md.title;
118
159
  }
119
160
  if (md.description) {
120
- let meta = document.querySelector(
121
- 'meta[name="description"]'
122
- );
123
- if (!meta) {
124
- meta = document.createElement("meta");
125
- meta.name = "description";
126
- document.head.appendChild(meta);
127
- }
161
+ const meta = getOrCreateMeta('meta[name="description"]', { name: "description" });
128
162
  meta.content = md.description;
129
163
  }
164
+ if (md.robots) {
165
+ const meta = getOrCreateMeta('meta[name="robots"]', { name: "robots" });
166
+ meta.content = md.robots;
167
+ }
168
+ if (md.themeColor) {
169
+ const meta = getOrCreateMeta('meta[name="theme-color"]', { name: "theme-color" });
170
+ meta.content = md.themeColor;
171
+ }
172
+ if (md.viewport) {
173
+ const meta = getOrCreateMeta('meta[name="viewport"]', { name: "viewport" });
174
+ meta.content = md.viewport;
175
+ }
176
+ if (md.canonical) {
177
+ getOrCreateLink("canonical", md.canonical);
178
+ }
179
+ if (md.openGraph) {
180
+ const og = md.openGraph;
181
+ if (og.title) {
182
+ const meta = getOrCreateMeta('meta[property="og:title"]', { property: "og:title" });
183
+ meta.content = og.title;
184
+ }
185
+ if (og.description) {
186
+ const meta = getOrCreateMeta('meta[property="og:description"]', { property: "og:description" });
187
+ meta.content = og.description;
188
+ }
189
+ if (og.type) {
190
+ const meta = getOrCreateMeta('meta[property="og:type"]', { property: "og:type" });
191
+ meta.content = og.type;
192
+ }
193
+ if (og.url) {
194
+ const meta = getOrCreateMeta('meta[property="og:url"]', { property: "og:url" });
195
+ meta.content = og.url;
196
+ }
197
+ if (og.image) {
198
+ if (typeof og.image === "string") {
199
+ const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
200
+ meta.content = og.image;
201
+ } else {
202
+ const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
203
+ meta.content = og.image.url;
204
+ if (og.image.width) {
205
+ const metaWidth = getOrCreateMeta('meta[property="og:image:width"]', { property: "og:image:width" });
206
+ metaWidth.content = String(og.image.width);
207
+ }
208
+ if (og.image.height) {
209
+ const metaHeight = getOrCreateMeta('meta[property="og:image:height"]', { property: "og:image:height" });
210
+ metaHeight.content = String(og.image.height);
211
+ }
212
+ if (og.image.alt) {
213
+ const metaAlt = getOrCreateMeta('meta[property="og:image:alt"]', { property: "og:image:alt" });
214
+ metaAlt.content = og.image.alt;
215
+ }
216
+ }
217
+ }
218
+ if (og.siteName) {
219
+ const meta = getOrCreateMeta('meta[property="og:site_name"]', { property: "og:site_name" });
220
+ meta.content = og.siteName;
221
+ }
222
+ if (og.locale) {
223
+ const meta = getOrCreateMeta('meta[property="og:locale"]', { property: "og:locale" });
224
+ meta.content = og.locale;
225
+ }
226
+ }
227
+ if (md.twitter) {
228
+ const twitter = md.twitter;
229
+ if (twitter.card) {
230
+ const meta = getOrCreateMeta('meta[name="twitter:card"]', { name: "twitter:card" });
231
+ meta.content = twitter.card;
232
+ }
233
+ if (twitter.title) {
234
+ const meta = getOrCreateMeta('meta[name="twitter:title"]', { name: "twitter:title" });
235
+ meta.content = twitter.title;
236
+ }
237
+ if (twitter.description) {
238
+ const meta = getOrCreateMeta('meta[name="twitter:description"]', { name: "twitter:description" });
239
+ meta.content = twitter.description;
240
+ }
241
+ if (twitter.image) {
242
+ const meta = getOrCreateMeta('meta[name="twitter:image"]', { name: "twitter:image" });
243
+ meta.content = twitter.image;
244
+ }
245
+ if (twitter.imageAlt) {
246
+ const meta = getOrCreateMeta('meta[name="twitter:image:alt"]', { name: "twitter:image:alt" });
247
+ meta.content = twitter.imageAlt;
248
+ }
249
+ if (twitter.site) {
250
+ const meta = getOrCreateMeta('meta[name="twitter:site"]', { name: "twitter:site" });
251
+ meta.content = twitter.site;
252
+ }
253
+ if (twitter.creator) {
254
+ const meta = getOrCreateMeta('meta[name="twitter:creator"]', { name: "twitter:creator" });
255
+ meta.content = twitter.creator;
256
+ }
257
+ }
258
+ if (md.metaTags && Array.isArray(md.metaTags)) {
259
+ md.metaTags.forEach((tag) => {
260
+ let selector = "";
261
+ if (tag.name) {
262
+ selector = `meta[name="${tag.name}"]`;
263
+ } else if (tag.property) {
264
+ selector = `meta[property="${tag.property}"]`;
265
+ } else if (tag.httpEquiv) {
266
+ selector = `meta[http-equiv="${tag.httpEquiv}"]`;
267
+ }
268
+ if (selector) {
269
+ const meta = getOrCreateMeta(selector, {
270
+ name: tag.name,
271
+ property: tag.property,
272
+ httpEquiv: tag.httpEquiv
273
+ });
274
+ meta.content = tag.content;
275
+ }
276
+ });
277
+ }
278
+ if (md.links && Array.isArray(md.links)) {
279
+ md.links.forEach((link) => {
280
+ getOrCreateLink(link.rel, link.href);
281
+ });
282
+ }
130
283
  }
131
284
 
132
285
  // modules/runtime/client/AppShell.tsx
@@ -239,14 +392,16 @@ function deleteCacheEntry(key) {
239
392
  function buildDataUrl(url) {
240
393
  return url + (url.includes("?") ? "&" : "?") + "__fw_data=1";
241
394
  }
242
- async function fetchRouteDataOnce(url) {
395
+ async function fetchRouteDataOnce(url, skipLayoutHooks = true) {
243
396
  const dataUrl = buildDataUrl(url);
244
- const res = await fetch(dataUrl, {
245
- headers: {
246
- "x-fw-data": "1",
247
- Accept: "application/json"
248
- }
249
- });
397
+ const headers = {
398
+ "x-fw-data": "1",
399
+ Accept: "application/json"
400
+ };
401
+ if (skipLayoutHooks) {
402
+ headers["x-skip-layout-hooks"] = "true";
403
+ }
404
+ const res = await fetch(dataUrl, { headers });
250
405
  let json = {};
251
406
  try {
252
407
  const text = await res.text();
@@ -272,7 +427,7 @@ async function getRouteData(url, options) {
272
427
  deleteCacheEntry(key);
273
428
  }
274
429
  const entry = dataCache.get(key);
275
- if (entry) {
430
+ if (entry && !options?.revalidate) {
276
431
  if (entry.status === "fulfilled") {
277
432
  updateLRU(key);
278
433
  return entry.value;
@@ -281,12 +436,29 @@ async function getRouteData(url, options) {
281
436
  return entry.promise;
282
437
  }
283
438
  }
284
- const promise = fetchRouteDataOnce(url).then((value) => {
285
- setCacheEntry(key, { status: "fulfilled", value });
439
+ const skipLayoutHooks = !options?.revalidate;
440
+ const currentEntry = dataCache.get(key);
441
+ if (currentEntry && !options?.revalidate) {
442
+ if (currentEntry.status === "fulfilled") {
443
+ updateLRU(key);
444
+ return currentEntry.value;
445
+ }
446
+ if (currentEntry.status === "pending") {
447
+ return currentEntry.promise;
448
+ }
449
+ }
450
+ const promise = fetchRouteDataOnce(url, skipLayoutHooks).then((value) => {
451
+ const entryAfterFetch = dataCache.get(key);
452
+ if (!entryAfterFetch || entryAfterFetch.status === "pending") {
453
+ setCacheEntry(key, { status: "fulfilled", value });
454
+ }
286
455
  return value;
287
456
  }).catch((error) => {
288
457
  console.error("[client][cache] Error fetching route data:", error);
289
- dataCache.set(key, { status: "rejected", error });
458
+ const entryAfterFetch = dataCache.get(key);
459
+ if (!entryAfterFetch || entryAfterFetch.status === "pending") {
460
+ dataCache.set(key, { status: "rejected", error });
461
+ }
290
462
  throw error;
291
463
  });
292
464
  dataCache.set(key, { status: "pending", promise });
@@ -311,10 +483,22 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
311
483
  } else if (json.theme) {
312
484
  theme = json.theme;
313
485
  }
486
+ let layoutProps = {};
487
+ if (json.layoutProps !== void 0 && json.layoutProps !== null) {
488
+ layoutProps = json.layoutProps;
489
+ setPreservedLayoutProps(layoutProps);
490
+ } else {
491
+ const preserved = getPreservedLayoutProps();
492
+ if (preserved) {
493
+ layoutProps = preserved;
494
+ }
495
+ }
496
+ const pageProps = json.pageProps ?? json.props ?? {
497
+ error: json.message || "An error occurred"
498
+ };
314
499
  const errorProps = {
315
- ...json.props || {
316
- error: json.message || "An error occurred"
317
- },
500
+ ...layoutProps,
501
+ ...pageProps,
318
502
  theme
319
503
  };
320
504
  const windowData = {
@@ -343,10 +527,15 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
343
527
  });
344
528
  return true;
345
529
  } catch (loadError) {
346
- console.error(
347
- "[client] Error loading error route components:",
348
- loadError
349
- );
530
+ console.error("\n\u274C [client] Error loading error route components:");
531
+ console.error(loadError);
532
+ if (loadError instanceof Error) {
533
+ console.error(` Message: ${loadError.message}`);
534
+ if (loadError.stack) {
535
+ console.error(` Stack: ${loadError.stack.split("\n").slice(0, 3).join("\n ")}`);
536
+ }
537
+ }
538
+ console.error("\u{1F4A1} Falling back to full page reload\n");
350
539
  window.location.href = nextUrl;
351
540
  return false;
352
541
  }
@@ -366,8 +555,20 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
366
555
  } else if (json.theme) {
367
556
  theme = json.theme;
368
557
  }
558
+ let layoutProps = {};
559
+ if (json.layoutProps !== void 0 && json.layoutProps !== null) {
560
+ layoutProps = json.layoutProps;
561
+ setPreservedLayoutProps(layoutProps);
562
+ } else {
563
+ const preserved = getPreservedLayoutProps();
564
+ if (preserved) {
565
+ layoutProps = preserved;
566
+ }
567
+ }
568
+ const pageProps = json.pageProps ?? json.props ?? {};
369
569
  const notFoundProps = {
370
- ...json.props ?? {},
570
+ ...layoutProps,
571
+ ...pageProps,
371
572
  theme
372
573
  };
373
574
  const windowData = {
@@ -424,20 +625,41 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
424
625
  } else if (json.theme) {
425
626
  theme = json.theme;
426
627
  }
427
- const newProps = {
428
- ...json.props ?? {},
628
+ let layoutProps = {};
629
+ if (json.layoutProps !== void 0 && json.layoutProps !== null) {
630
+ layoutProps = json.layoutProps;
631
+ setPreservedLayoutProps(layoutProps);
632
+ } else {
633
+ const preserved = getPreservedLayoutProps();
634
+ if (preserved) {
635
+ layoutProps = preserved;
636
+ }
637
+ }
638
+ const pageProps = json.pageProps ?? json.props ?? {};
639
+ const combinedProps = {
640
+ ...layoutProps,
641
+ ...pageProps,
429
642
  theme
430
643
  // Always include theme
431
644
  };
432
- const matched = matchRouteClient(nextUrl, routes);
645
+ const pathnameForMatch = json.pathname || nextUrl;
646
+ let matched = matchRouteClient(pathnameForMatch, routes);
433
647
  if (!matched) {
648
+ matched = matchRouteClient(nextUrl, routes);
649
+ }
650
+ if (!matched) {
651
+ console.warn(
652
+ `[client] Server returned data for ${nextUrl} but no route match found. Available routes:`,
653
+ routes.map((r) => r.pattern)
654
+ );
434
655
  window.location.href = nextUrl;
435
656
  return false;
436
657
  }
658
+ const finalPathname = json.pathname || nextUrl;
437
659
  const windowData = {
438
- pathname: nextUrl,
660
+ pathname: finalPathname,
439
661
  params: matched.params,
440
- props: newProps,
662
+ props: combinedProps,
441
663
  metadata: json.metadata ?? null,
442
664
  theme,
443
665
  notFound: false,
@@ -451,7 +673,11 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
451
673
  searchParams: Object.fromEntries(url.searchParams.entries())
452
674
  };
453
675
  setRouterData(routerData);
454
- const components = await matched.route.load();
676
+ const prefetched = prefetchedRoutes.get(matched.route);
677
+ const components = prefetched ? await prefetched : await matched.route.load();
678
+ if (!prefetched) {
679
+ prefetchedRoutes.set(matched.route, Promise.resolve(components));
680
+ }
455
681
  window.scrollTo({
456
682
  top: 0,
457
683
  behavior: "smooth"
@@ -461,7 +687,7 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
461
687
  route: matched.route,
462
688
  params: matched.params,
463
689
  components,
464
- props: newProps
690
+ props: combinedProps
465
691
  });
466
692
  return true;
467
693
  }
@@ -490,7 +716,7 @@ async function navigate(nextUrl, handlers, options) {
490
716
  }
491
717
  }
492
718
  if (!ok) {
493
- if (json && json.redirect) {
719
+ if (json?.redirect) {
494
720
  window.location.href = json.redirect.destination;
495
721
  return;
496
722
  }
@@ -511,6 +737,47 @@ async function navigate(nextUrl, handlers, options) {
511
737
  window.location.href = nextUrl;
512
738
  }
513
739
  }
740
+ var prefetchedRoutes = /* @__PURE__ */ new WeakMap();
741
+ function prefetchRoute(url, routes, notFoundRoute) {
742
+ const [pathname] = url.split("?");
743
+ const matched = matchRouteClient(pathname, routes);
744
+ if (!matched) {
745
+ if (notFoundRoute) {
746
+ const existing2 = prefetchedRoutes.get(notFoundRoute);
747
+ if (!existing2) {
748
+ const promise = notFoundRoute.load();
749
+ prefetchedRoutes.set(notFoundRoute, promise);
750
+ }
751
+ }
752
+ return;
753
+ }
754
+ const existing = prefetchedRoutes.get(matched.route);
755
+ if (!existing) {
756
+ const promise = matched.route.load();
757
+ prefetchedRoutes.set(matched.route, promise);
758
+ }
759
+ }
760
+ function createHoverHandler(routes, notFoundRoute) {
761
+ return function handleHover(ev) {
762
+ try {
763
+ const target = ev.target;
764
+ if (!target) return;
765
+ const anchor = target.closest("a[href]");
766
+ if (!anchor) return;
767
+ const href = anchor.getAttribute("href");
768
+ if (!href) return;
769
+ if (href.startsWith("#")) return;
770
+ const url = new URL(href, window.location.href);
771
+ if (url.origin !== window.location.origin) return;
772
+ if (anchor.target && anchor.target !== "_self") return;
773
+ const nextUrl = url.pathname + url.search;
774
+ const currentUrl = window.location.pathname + window.location.search;
775
+ if (nextUrl === currentUrl) return;
776
+ prefetchRoute(nextUrl, routes, notFoundRoute);
777
+ } catch (error) {
778
+ }
779
+ };
780
+ }
514
781
  function createClickHandler(navigate2) {
515
782
  return function handleClick(ev) {
516
783
  try {
@@ -621,17 +888,20 @@ function AppShell({
621
888
  }
622
889
  const handleClick = createClickHandler(handleNavigateInternal);
623
890
  const handlePopState = createPopStateHandler(handleNavigateInternal);
891
+ const handleHover = createHoverHandler(routes, notFoundRoute);
624
892
  window.addEventListener("click", handleClick, false);
625
893
  window.addEventListener("popstate", handlePopState, false);
894
+ window.addEventListener("mouseover", handleHover, false);
626
895
  return () => {
627
896
  isMounted = false;
628
897
  window.removeEventListener("click", handleClick, false);
629
898
  window.removeEventListener("popstate", handlePopState, false);
899
+ window.removeEventListener("mouseover", handleHover, false);
630
900
  };
631
- }, []);
901
+ }, [routes, notFoundRoute]);
632
902
  (0, import_react2.useEffect)(() => {
633
903
  const handleDataRefresh = () => {
634
- const freshData = window?.__FW_DATA__;
904
+ const freshData = window[WINDOW_DATA_KEY];
635
905
  if (!freshData) return;
636
906
  const currentPathname = window.location.pathname;
637
907
  const freshPathname = freshData.pathname;
@@ -650,7 +920,7 @@ function AppShell({
650
920
  return () => {
651
921
  window.removeEventListener("fw-data-refresh", handleDataRefresh);
652
922
  };
653
- }, [state.url]);
923
+ }, []);
654
924
  const isError = state.route === errorRoute;
655
925
  const isNotFound = state.route === notFoundRoute;
656
926
  const routeType = isError ? "error" : isNotFound ? "notfound" : "normal";
@@ -658,6 +928,89 @@ function AppShell({
658
928
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterContext.Provider, { value: { navigate: handleNavigate }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RouterView, { state }, routeKey) });
659
929
  }
660
930
 
931
+ // modules/runtime/client/hot-reload.ts
932
+ function setupHotReload() {
933
+ const nodeEnv = process.env.NODE_ENV || "production";
934
+ const isDev = nodeEnv === "development";
935
+ if (!isDev) {
936
+ return;
937
+ }
938
+ console.log("[hot-reload] Setting up hot reload client...");
939
+ let eventSource = null;
940
+ let reloadTimeout = null;
941
+ let reconnectTimeout = null;
942
+ let reconnectAttempts = 0;
943
+ const MAX_RECONNECT_ATTEMPTS = 10;
944
+ const RECONNECT_DELAY = 1e3;
945
+ const RELOAD_DELAY = 100;
946
+ function connect() {
947
+ try {
948
+ if (eventSource) {
949
+ console.log("[hot-reload] Closing existing EventSource connection");
950
+ eventSource.close();
951
+ }
952
+ const endpoint = "/__fw/hot";
953
+ eventSource = new EventSource(endpoint);
954
+ eventSource.addEventListener("ping", (event) => {
955
+ if ("data" in event) {
956
+ console.log("[hot-reload] \u2705 Connected to hot reload server");
957
+ }
958
+ reconnectAttempts = 0;
959
+ });
960
+ eventSource.addEventListener("message", (event) => {
961
+ const data = event.data;
962
+ if (data && typeof data === "string" && data.startsWith("reload:")) {
963
+ const filePath = data.slice(7);
964
+ console.log(`[hot-reload] \u{1F4DD} File changed: ${filePath}, reloading...`);
965
+ if (reloadTimeout) {
966
+ clearTimeout(reloadTimeout);
967
+ }
968
+ reloadTimeout = setTimeout(() => {
969
+ try {
970
+ window.location.reload();
971
+ } catch (error) {
972
+ console.error("[hot-reload] \u274C Error reloading page:", error);
973
+ setTimeout(() => window.location.reload(), 100);
974
+ }
975
+ }, RELOAD_DELAY);
976
+ }
977
+ });
978
+ eventSource.onopen = () => {
979
+ reconnectAttempts = 0;
980
+ };
981
+ eventSource.onerror = (error) => {
982
+ const states = ["CONNECTING", "OPEN", "CLOSED"];
983
+ const state = states[eventSource?.readyState ?? 0] || "UNKNOWN";
984
+ if (eventSource?.readyState === EventSource.CONNECTING) {
985
+ console.log("[hot-reload] \u23F3 Still connecting...");
986
+ return;
987
+ } else if (eventSource?.readyState === EventSource.OPEN) {
988
+ console.warn("[hot-reload] \u26A0\uFE0F Connection error (but connection is open):", error);
989
+ } else {
990
+ console.warn(`[hot-reload] \u274C Connection closed (readyState: ${state})`);
991
+ if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
992
+ reconnectAttempts++;
993
+ const delay = RECONNECT_DELAY * reconnectAttempts;
994
+ if (reconnectTimeout) {
995
+ clearTimeout(reconnectTimeout);
996
+ }
997
+ reconnectTimeout = setTimeout(() => {
998
+ console.log(`[hot-reload] \u{1F504} Reconnecting... (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
999
+ connect();
1000
+ }, delay);
1001
+ } else {
1002
+ console.error("[hot-reload] \u274C Max reconnect attempts reached. Please refresh the page manually.");
1003
+ }
1004
+ }
1005
+ };
1006
+ } catch (error) {
1007
+ console.error("[hot-reload] \u274C Failed to create EventSource:", error);
1008
+ console.error("[hot-reload] EventSource may not be supported in this browser.");
1009
+ }
1010
+ }
1011
+ connect();
1012
+ }
1013
+
661
1014
  // modules/runtime/client/bootstrap.tsx
662
1015
  var import_jsx_runtime3 = require("react/jsx-runtime");
663
1016
  async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute, errorRoute) {
@@ -699,101 +1052,96 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
699
1052
  props: initialData?.props ?? {}
700
1053
  };
701
1054
  }
702
- function setupHotReload() {
703
- const nodeEnv = typeof process !== "undefined" && process?.env?.NODE_ENV || "production";
704
- const isDev = nodeEnv !== "production";
705
- if (!isDev) {
706
- return;
1055
+ function initializeRouterData(initialUrl, initialData) {
1056
+ let routerData = getRouterData();
1057
+ if (!routerData) {
1058
+ const url = new URL(initialUrl, window.location.origin);
1059
+ const pathname = initialData?.pathname || url.pathname;
1060
+ routerData = {
1061
+ pathname,
1062
+ params: initialData?.params || {},
1063
+ searchParams: Object.fromEntries(url.searchParams.entries())
1064
+ };
1065
+ setRouterData(routerData);
707
1066
  }
1067
+ }
1068
+ async function hydrateInitialRoute(container, initialUrl, initialData, routes, notFoundRoute, errorRoute) {
708
1069
  try {
709
- console.log("[hot-reload] Attempting to connect to /__fw/hot...");
710
- const eventSource = new EventSource("/__fw/hot");
711
- let reloadTimeout = null;
712
- eventSource.addEventListener("message", (event) => {
713
- const data = event.data;
714
- if (data && data.startsWith("reload:")) {
715
- const filePath = data.slice(7);
716
- console.log(`[hot-reload] File changed: ${filePath}`);
717
- if (reloadTimeout) {
718
- clearTimeout(reloadTimeout);
719
- }
720
- reloadTimeout = setTimeout(() => {
721
- console.log("[hot-reload] Reloading page...");
722
- window.location.reload();
723
- }, 500);
724
- }
725
- });
726
- eventSource.addEventListener("ping", () => {
727
- console.log("[hot-reload] \u2713 Connected to hot reload server");
728
- });
729
- eventSource.onopen = () => {
730
- console.log("[hot-reload] \u2713 SSE connection opened");
731
- };
732
- eventSource.onerror = (error) => {
733
- const states = ["CONNECTING", "OPEN", "CLOSED"];
734
- const state = states[eventSource.readyState] || "UNKNOWN";
735
- if (eventSource.readyState === EventSource.CONNECTING) {
736
- console.log("[hot-reload] Connecting...");
737
- } else if (eventSource.readyState === EventSource.OPEN) {
738
- console.warn("[hot-reload] Connection error (but connection is open):", error);
739
- } else {
740
- console.log("[hot-reload] Connection closed (readyState:", state, ")");
1070
+ const initialState = await loadInitialRoute(
1071
+ initialUrl,
1072
+ initialData,
1073
+ routes,
1074
+ notFoundRoute,
1075
+ errorRoute
1076
+ );
1077
+ if (initialData?.metadata) {
1078
+ try {
1079
+ applyMetadata(initialData.metadata);
1080
+ } catch (metadataError) {
1081
+ console.warn("[client] Error applying metadata:", metadataError);
741
1082
  }
742
- };
1083
+ }
1084
+ (0, import_client.hydrateRoot)(
1085
+ container,
1086
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1087
+ AppShell,
1088
+ {
1089
+ initialState,
1090
+ routes,
1091
+ notFoundRoute,
1092
+ errorRoute
1093
+ }
1094
+ )
1095
+ );
743
1096
  } catch (error) {
744
- console.log("[hot-reload] EventSource not supported or error:", error);
1097
+ console.error(
1098
+ "[client] Error loading initial route components for",
1099
+ initialUrl,
1100
+ error
1101
+ );
1102
+ throw error;
745
1103
  }
746
1104
  }
747
1105
  function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
748
- console.log("[client] Bootstrap starting, setting up hot reload...");
749
1106
  setupHotReload();
750
- (async function bootstrap() {
751
- const container = document.getElementById(APP_CONTAINER_ID);
752
- const initialData = getWindowData();
753
- if (!container) {
754
- console.error(`Container #${APP_CONTAINER_ID} not found for hydration`);
755
- return;
756
- }
757
- const initialUrl = window.location.pathname + window.location.search;
758
- let routerData = getRouterData();
759
- if (!routerData) {
760
- const url = new URL(initialUrl, window.location.origin);
761
- routerData = {
762
- pathname: url.pathname,
763
- params: initialData?.params || {},
764
- searchParams: Object.fromEntries(url.searchParams.entries())
765
- };
766
- setRouterData(routerData);
767
- }
1107
+ (async () => {
768
1108
  try {
769
- const initialState = await loadInitialRoute(
1109
+ const container = document.getElementById(APP_CONTAINER_ID);
1110
+ if (!container) {
1111
+ console.error(`
1112
+ \u274C [client] Hydration failed: Container #${APP_CONTAINER_ID} not found`);
1113
+ console.error("\u{1F4A1} This usually means:");
1114
+ console.error(" \u2022 The HTML structure doesn't match what React expects");
1115
+ console.error(" \u2022 The container was removed before hydration");
1116
+ console.error(" \u2022 There's a mismatch between SSR and client HTML\n");
1117
+ return;
1118
+ }
1119
+ const initialData = getWindowData();
1120
+ const initialUrl = (initialData?.pathname || window.location.pathname) + window.location.search;
1121
+ if (initialData?.props) {
1122
+ setPreservedLayoutProps(initialData.props);
1123
+ }
1124
+ const routerPathname = initialData?.pathname || window.location.pathname;
1125
+ initializeRouterData(routerPathname + window.location.search, initialData);
1126
+ await hydrateInitialRoute(
1127
+ container,
770
1128
  initialUrl,
771
1129
  initialData,
772
1130
  routes,
773
1131
  notFoundRoute,
774
1132
  errorRoute
775
1133
  );
776
- if (initialData?.metadata) {
777
- applyMetadata(initialData.metadata);
778
- }
779
- (0, import_client.hydrateRoot)(
780
- container,
781
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
782
- AppShell,
783
- {
784
- initialState,
785
- routes,
786
- notFoundRoute,
787
- errorRoute
788
- }
789
- )
790
- );
791
1134
  } catch (error) {
792
- console.error(
793
- "[client] Error loading initial route components for",
794
- initialUrl,
795
- error
796
- );
1135
+ console.error("\n\u274C [client] Fatal error during bootstrap:");
1136
+ console.error(error);
1137
+ if (error instanceof Error) {
1138
+ console.error("\nError details:");
1139
+ console.error(` Message: ${error.message}`);
1140
+ if (error.stack) {
1141
+ console.error(` Stack: ${error.stack}`);
1142
+ }
1143
+ }
1144
+ console.error("\n\u{1F4A1} Attempting page reload to recover...\n");
797
1145
  window.location.reload();
798
1146
  }
799
1147
  })();