@lolyjs/core 0.2.0-alpha.8 → 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 (57) 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 +28 -54
  27. package/dist/react/hooks.cjs.map +1 -1
  28. package/dist/react/hooks.d.mts +1 -24
  29. package/dist/react/hooks.d.ts +1 -24
  30. package/dist/react/{hooks.js → hooks.mjs} +27 -52
  31. package/dist/react/hooks.mjs.map +1 -0
  32. package/dist/react/sockets.cjs +5 -6
  33. package/dist/react/sockets.cjs.map +1 -1
  34. package/dist/react/sockets.mjs +21 -0
  35. package/dist/react/sockets.mjs.map +1 -0
  36. package/dist/react/themes.cjs +61 -18
  37. package/dist/react/themes.cjs.map +1 -1
  38. package/dist/react/{themes.js → themes.mjs} +64 -21
  39. package/dist/react/themes.mjs.map +1 -0
  40. package/dist/runtime.cjs +465 -117
  41. package/dist/runtime.cjs.map +1 -1
  42. package/dist/runtime.d.mts +2 -2
  43. package/dist/runtime.d.ts +2 -2
  44. package/dist/{runtime.js → runtime.mjs} +466 -118
  45. package/dist/runtime.mjs.map +1 -0
  46. package/package.json +26 -19
  47. package/dist/cli.js +0 -5291
  48. package/dist/cli.js.map +0 -1
  49. package/dist/index.js +0 -6015
  50. package/dist/index.js.map +0 -1
  51. package/dist/react/cache.js.map +0 -1
  52. package/dist/react/components.js.map +0 -1
  53. package/dist/react/hooks.js.map +0 -1
  54. package/dist/react/sockets.js +0 -22
  55. package/dist/react/sockets.js.map +0 -1
  56. package/dist/react/themes.js.map +0 -1
  57. package/dist/runtime.js.map +0 -1
@@ -8,12 +8,29 @@ var APP_CONTAINER_ID = "__app";
8
8
  var ROUTER_NAVIGATE_KEY = "__LOLY_ROUTER_NAVIGATE__";
9
9
 
10
10
  // modules/runtime/client/window-data.ts
11
+ var LAYOUT_PROPS_KEY = "__FW_LAYOUT_PROPS__";
11
12
  function getWindowData() {
12
13
  if (typeof window === "undefined") {
13
14
  return null;
14
15
  }
15
16
  return window[WINDOW_DATA_KEY] ?? null;
16
17
  }
18
+ function getPreservedLayoutProps() {
19
+ if (typeof window === "undefined") {
20
+ return null;
21
+ }
22
+ return window[LAYOUT_PROPS_KEY] ?? null;
23
+ }
24
+ function setPreservedLayoutProps(props) {
25
+ if (typeof window === "undefined") {
26
+ return;
27
+ }
28
+ if (props === null) {
29
+ delete window[LAYOUT_PROPS_KEY];
30
+ } else {
31
+ window[LAYOUT_PROPS_KEY] = props;
32
+ }
33
+ }
17
34
  function getRouterData() {
18
35
  if (typeof window === "undefined") {
19
36
  return null;
@@ -85,22 +102,158 @@ function matchRouteClient(pathWithSearch, routes) {
85
102
  }
86
103
 
87
104
  // modules/runtime/client/metadata.ts
105
+ function getOrCreateMeta(selector, attributes) {
106
+ let meta = document.querySelector(selector);
107
+ if (!meta) {
108
+ meta = document.createElement("meta");
109
+ if (attributes.name) meta.name = attributes.name;
110
+ if (attributes.property) meta.setAttribute("property", attributes.property);
111
+ if (attributes.httpEquiv) meta.httpEquiv = attributes.httpEquiv;
112
+ document.head.appendChild(meta);
113
+ }
114
+ return meta;
115
+ }
116
+ function getOrCreateLink(rel, href) {
117
+ const selector = `link[rel="${rel}"]`;
118
+ let link = document.querySelector(selector);
119
+ if (!link) {
120
+ link = document.createElement("link");
121
+ link.rel = rel;
122
+ link.href = href;
123
+ document.head.appendChild(link);
124
+ } else {
125
+ link.href = href;
126
+ }
127
+ return link;
128
+ }
88
129
  function applyMetadata(md) {
89
130
  if (!md) return;
90
131
  if (md.title) {
91
132
  document.title = md.title;
92
133
  }
93
134
  if (md.description) {
94
- let meta = document.querySelector(
95
- 'meta[name="description"]'
96
- );
97
- if (!meta) {
98
- meta = document.createElement("meta");
99
- meta.name = "description";
100
- document.head.appendChild(meta);
101
- }
135
+ const meta = getOrCreateMeta('meta[name="description"]', { name: "description" });
102
136
  meta.content = md.description;
103
137
  }
138
+ if (md.robots) {
139
+ const meta = getOrCreateMeta('meta[name="robots"]', { name: "robots" });
140
+ meta.content = md.robots;
141
+ }
142
+ if (md.themeColor) {
143
+ const meta = getOrCreateMeta('meta[name="theme-color"]', { name: "theme-color" });
144
+ meta.content = md.themeColor;
145
+ }
146
+ if (md.viewport) {
147
+ const meta = getOrCreateMeta('meta[name="viewport"]', { name: "viewport" });
148
+ meta.content = md.viewport;
149
+ }
150
+ if (md.canonical) {
151
+ getOrCreateLink("canonical", md.canonical);
152
+ }
153
+ if (md.openGraph) {
154
+ const og = md.openGraph;
155
+ if (og.title) {
156
+ const meta = getOrCreateMeta('meta[property="og:title"]', { property: "og:title" });
157
+ meta.content = og.title;
158
+ }
159
+ if (og.description) {
160
+ const meta = getOrCreateMeta('meta[property="og:description"]', { property: "og:description" });
161
+ meta.content = og.description;
162
+ }
163
+ if (og.type) {
164
+ const meta = getOrCreateMeta('meta[property="og:type"]', { property: "og:type" });
165
+ meta.content = og.type;
166
+ }
167
+ if (og.url) {
168
+ const meta = getOrCreateMeta('meta[property="og:url"]', { property: "og:url" });
169
+ meta.content = og.url;
170
+ }
171
+ if (og.image) {
172
+ if (typeof og.image === "string") {
173
+ const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
174
+ meta.content = og.image;
175
+ } else {
176
+ const meta = getOrCreateMeta('meta[property="og:image"]', { property: "og:image" });
177
+ meta.content = og.image.url;
178
+ if (og.image.width) {
179
+ const metaWidth = getOrCreateMeta('meta[property="og:image:width"]', { property: "og:image:width" });
180
+ metaWidth.content = String(og.image.width);
181
+ }
182
+ if (og.image.height) {
183
+ const metaHeight = getOrCreateMeta('meta[property="og:image:height"]', { property: "og:image:height" });
184
+ metaHeight.content = String(og.image.height);
185
+ }
186
+ if (og.image.alt) {
187
+ const metaAlt = getOrCreateMeta('meta[property="og:image:alt"]', { property: "og:image:alt" });
188
+ metaAlt.content = og.image.alt;
189
+ }
190
+ }
191
+ }
192
+ if (og.siteName) {
193
+ const meta = getOrCreateMeta('meta[property="og:site_name"]', { property: "og:site_name" });
194
+ meta.content = og.siteName;
195
+ }
196
+ if (og.locale) {
197
+ const meta = getOrCreateMeta('meta[property="og:locale"]', { property: "og:locale" });
198
+ meta.content = og.locale;
199
+ }
200
+ }
201
+ if (md.twitter) {
202
+ const twitter = md.twitter;
203
+ if (twitter.card) {
204
+ const meta = getOrCreateMeta('meta[name="twitter:card"]', { name: "twitter:card" });
205
+ meta.content = twitter.card;
206
+ }
207
+ if (twitter.title) {
208
+ const meta = getOrCreateMeta('meta[name="twitter:title"]', { name: "twitter:title" });
209
+ meta.content = twitter.title;
210
+ }
211
+ if (twitter.description) {
212
+ const meta = getOrCreateMeta('meta[name="twitter:description"]', { name: "twitter:description" });
213
+ meta.content = twitter.description;
214
+ }
215
+ if (twitter.image) {
216
+ const meta = getOrCreateMeta('meta[name="twitter:image"]', { name: "twitter:image" });
217
+ meta.content = twitter.image;
218
+ }
219
+ if (twitter.imageAlt) {
220
+ const meta = getOrCreateMeta('meta[name="twitter:image:alt"]', { name: "twitter:image:alt" });
221
+ meta.content = twitter.imageAlt;
222
+ }
223
+ if (twitter.site) {
224
+ const meta = getOrCreateMeta('meta[name="twitter:site"]', { name: "twitter:site" });
225
+ meta.content = twitter.site;
226
+ }
227
+ if (twitter.creator) {
228
+ const meta = getOrCreateMeta('meta[name="twitter:creator"]', { name: "twitter:creator" });
229
+ meta.content = twitter.creator;
230
+ }
231
+ }
232
+ if (md.metaTags && Array.isArray(md.metaTags)) {
233
+ md.metaTags.forEach((tag) => {
234
+ let selector = "";
235
+ if (tag.name) {
236
+ selector = `meta[name="${tag.name}"]`;
237
+ } else if (tag.property) {
238
+ selector = `meta[property="${tag.property}"]`;
239
+ } else if (tag.httpEquiv) {
240
+ selector = `meta[http-equiv="${tag.httpEquiv}"]`;
241
+ }
242
+ if (selector) {
243
+ const meta = getOrCreateMeta(selector, {
244
+ name: tag.name,
245
+ property: tag.property,
246
+ httpEquiv: tag.httpEquiv
247
+ });
248
+ meta.content = tag.content;
249
+ }
250
+ });
251
+ }
252
+ if (md.links && Array.isArray(md.links)) {
253
+ md.links.forEach((link) => {
254
+ getOrCreateLink(link.rel, link.href);
255
+ });
256
+ }
104
257
  }
105
258
 
106
259
  // modules/runtime/client/AppShell.tsx
@@ -213,14 +366,16 @@ function deleteCacheEntry(key) {
213
366
  function buildDataUrl(url) {
214
367
  return url + (url.includes("?") ? "&" : "?") + "__fw_data=1";
215
368
  }
216
- async function fetchRouteDataOnce(url) {
369
+ async function fetchRouteDataOnce(url, skipLayoutHooks = true) {
217
370
  const dataUrl = buildDataUrl(url);
218
- const res = await fetch(dataUrl, {
219
- headers: {
220
- "x-fw-data": "1",
221
- Accept: "application/json"
222
- }
223
- });
371
+ const headers = {
372
+ "x-fw-data": "1",
373
+ Accept: "application/json"
374
+ };
375
+ if (skipLayoutHooks) {
376
+ headers["x-skip-layout-hooks"] = "true";
377
+ }
378
+ const res = await fetch(dataUrl, { headers });
224
379
  let json = {};
225
380
  try {
226
381
  const text = await res.text();
@@ -246,7 +401,7 @@ async function getRouteData(url, options) {
246
401
  deleteCacheEntry(key);
247
402
  }
248
403
  const entry = dataCache.get(key);
249
- if (entry) {
404
+ if (entry && !options?.revalidate) {
250
405
  if (entry.status === "fulfilled") {
251
406
  updateLRU(key);
252
407
  return entry.value;
@@ -255,12 +410,29 @@ async function getRouteData(url, options) {
255
410
  return entry.promise;
256
411
  }
257
412
  }
258
- const promise = fetchRouteDataOnce(url).then((value) => {
259
- setCacheEntry(key, { status: "fulfilled", value });
413
+ const skipLayoutHooks = !options?.revalidate;
414
+ const currentEntry = dataCache.get(key);
415
+ if (currentEntry && !options?.revalidate) {
416
+ if (currentEntry.status === "fulfilled") {
417
+ updateLRU(key);
418
+ return currentEntry.value;
419
+ }
420
+ if (currentEntry.status === "pending") {
421
+ return currentEntry.promise;
422
+ }
423
+ }
424
+ const promise = fetchRouteDataOnce(url, skipLayoutHooks).then((value) => {
425
+ const entryAfterFetch = dataCache.get(key);
426
+ if (!entryAfterFetch || entryAfterFetch.status === "pending") {
427
+ setCacheEntry(key, { status: "fulfilled", value });
428
+ }
260
429
  return value;
261
430
  }).catch((error) => {
262
431
  console.error("[client][cache] Error fetching route data:", error);
263
- dataCache.set(key, { status: "rejected", error });
432
+ const entryAfterFetch = dataCache.get(key);
433
+ if (!entryAfterFetch || entryAfterFetch.status === "pending") {
434
+ dataCache.set(key, { status: "rejected", error });
435
+ }
264
436
  throw error;
265
437
  });
266
438
  dataCache.set(key, { status: "pending", promise });
@@ -285,10 +457,22 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
285
457
  } else if (json.theme) {
286
458
  theme = json.theme;
287
459
  }
460
+ let layoutProps = {};
461
+ if (json.layoutProps !== void 0 && json.layoutProps !== null) {
462
+ layoutProps = json.layoutProps;
463
+ setPreservedLayoutProps(layoutProps);
464
+ } else {
465
+ const preserved = getPreservedLayoutProps();
466
+ if (preserved) {
467
+ layoutProps = preserved;
468
+ }
469
+ }
470
+ const pageProps = json.pageProps ?? json.props ?? {
471
+ error: json.message || "An error occurred"
472
+ };
288
473
  const errorProps = {
289
- ...json.props || {
290
- error: json.message || "An error occurred"
291
- },
474
+ ...layoutProps,
475
+ ...pageProps,
292
476
  theme
293
477
  };
294
478
  const windowData = {
@@ -317,10 +501,15 @@ async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
317
501
  });
318
502
  return true;
319
503
  } catch (loadError) {
320
- console.error(
321
- "[client] Error loading error route components:",
322
- loadError
323
- );
504
+ console.error("\n\u274C [client] Error loading error route components:");
505
+ console.error(loadError);
506
+ if (loadError instanceof Error) {
507
+ console.error(` Message: ${loadError.message}`);
508
+ if (loadError.stack) {
509
+ console.error(` Stack: ${loadError.stack.split("\n").slice(0, 3).join("\n ")}`);
510
+ }
511
+ }
512
+ console.error("\u{1F4A1} Falling back to full page reload\n");
324
513
  window.location.href = nextUrl;
325
514
  return false;
326
515
  }
@@ -340,8 +529,20 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
340
529
  } else if (json.theme) {
341
530
  theme = json.theme;
342
531
  }
532
+ let layoutProps = {};
533
+ if (json.layoutProps !== void 0 && json.layoutProps !== null) {
534
+ layoutProps = json.layoutProps;
535
+ setPreservedLayoutProps(layoutProps);
536
+ } else {
537
+ const preserved = getPreservedLayoutProps();
538
+ if (preserved) {
539
+ layoutProps = preserved;
540
+ }
541
+ }
542
+ const pageProps = json.pageProps ?? json.props ?? {};
343
543
  const notFoundProps = {
344
- ...json.props ?? {},
544
+ ...layoutProps,
545
+ ...pageProps,
345
546
  theme
346
547
  };
347
548
  const windowData = {
@@ -398,20 +599,41 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
398
599
  } else if (json.theme) {
399
600
  theme = json.theme;
400
601
  }
401
- const newProps = {
402
- ...json.props ?? {},
602
+ let layoutProps = {};
603
+ if (json.layoutProps !== void 0 && json.layoutProps !== null) {
604
+ layoutProps = json.layoutProps;
605
+ setPreservedLayoutProps(layoutProps);
606
+ } else {
607
+ const preserved = getPreservedLayoutProps();
608
+ if (preserved) {
609
+ layoutProps = preserved;
610
+ }
611
+ }
612
+ const pageProps = json.pageProps ?? json.props ?? {};
613
+ const combinedProps = {
614
+ ...layoutProps,
615
+ ...pageProps,
403
616
  theme
404
617
  // Always include theme
405
618
  };
406
- const matched = matchRouteClient(nextUrl, routes);
619
+ const pathnameForMatch = json.pathname || nextUrl;
620
+ let matched = matchRouteClient(pathnameForMatch, routes);
407
621
  if (!matched) {
622
+ matched = matchRouteClient(nextUrl, routes);
623
+ }
624
+ if (!matched) {
625
+ console.warn(
626
+ `[client] Server returned data for ${nextUrl} but no route match found. Available routes:`,
627
+ routes.map((r) => r.pattern)
628
+ );
408
629
  window.location.href = nextUrl;
409
630
  return false;
410
631
  }
632
+ const finalPathname = json.pathname || nextUrl;
411
633
  const windowData = {
412
- pathname: nextUrl,
634
+ pathname: finalPathname,
413
635
  params: matched.params,
414
- props: newProps,
636
+ props: combinedProps,
415
637
  metadata: json.metadata ?? null,
416
638
  theme,
417
639
  notFound: false,
@@ -425,7 +647,11 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
425
647
  searchParams: Object.fromEntries(url.searchParams.entries())
426
648
  };
427
649
  setRouterData(routerData);
428
- const components = await matched.route.load();
650
+ const prefetched = prefetchedRoutes.get(matched.route);
651
+ const components = prefetched ? await prefetched : await matched.route.load();
652
+ if (!prefetched) {
653
+ prefetchedRoutes.set(matched.route, Promise.resolve(components));
654
+ }
429
655
  window.scrollTo({
430
656
  top: 0,
431
657
  behavior: "smooth"
@@ -435,7 +661,7 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
435
661
  route: matched.route,
436
662
  params: matched.params,
437
663
  components,
438
- props: newProps
664
+ props: combinedProps
439
665
  });
440
666
  return true;
441
667
  }
@@ -464,7 +690,7 @@ async function navigate(nextUrl, handlers, options) {
464
690
  }
465
691
  }
466
692
  if (!ok) {
467
- if (json && json.redirect) {
693
+ if (json?.redirect) {
468
694
  window.location.href = json.redirect.destination;
469
695
  return;
470
696
  }
@@ -485,6 +711,47 @@ async function navigate(nextUrl, handlers, options) {
485
711
  window.location.href = nextUrl;
486
712
  }
487
713
  }
714
+ var prefetchedRoutes = /* @__PURE__ */ new WeakMap();
715
+ function prefetchRoute(url, routes, notFoundRoute) {
716
+ const [pathname] = url.split("?");
717
+ const matched = matchRouteClient(pathname, routes);
718
+ if (!matched) {
719
+ if (notFoundRoute) {
720
+ const existing2 = prefetchedRoutes.get(notFoundRoute);
721
+ if (!existing2) {
722
+ const promise = notFoundRoute.load();
723
+ prefetchedRoutes.set(notFoundRoute, promise);
724
+ }
725
+ }
726
+ return;
727
+ }
728
+ const existing = prefetchedRoutes.get(matched.route);
729
+ if (!existing) {
730
+ const promise = matched.route.load();
731
+ prefetchedRoutes.set(matched.route, promise);
732
+ }
733
+ }
734
+ function createHoverHandler(routes, notFoundRoute) {
735
+ return function handleHover(ev) {
736
+ try {
737
+ const target = ev.target;
738
+ if (!target) return;
739
+ const anchor = target.closest("a[href]");
740
+ if (!anchor) return;
741
+ const href = anchor.getAttribute("href");
742
+ if (!href) return;
743
+ if (href.startsWith("#")) return;
744
+ const url = new URL(href, window.location.href);
745
+ if (url.origin !== window.location.origin) return;
746
+ if (anchor.target && anchor.target !== "_self") return;
747
+ const nextUrl = url.pathname + url.search;
748
+ const currentUrl = window.location.pathname + window.location.search;
749
+ if (nextUrl === currentUrl) return;
750
+ prefetchRoute(nextUrl, routes, notFoundRoute);
751
+ } catch (error) {
752
+ }
753
+ };
754
+ }
488
755
  function createClickHandler(navigate2) {
489
756
  return function handleClick(ev) {
490
757
  try {
@@ -595,17 +862,20 @@ function AppShell({
595
862
  }
596
863
  const handleClick = createClickHandler(handleNavigateInternal);
597
864
  const handlePopState = createPopStateHandler(handleNavigateInternal);
865
+ const handleHover = createHoverHandler(routes, notFoundRoute);
598
866
  window.addEventListener("click", handleClick, false);
599
867
  window.addEventListener("popstate", handlePopState, false);
868
+ window.addEventListener("mouseover", handleHover, false);
600
869
  return () => {
601
870
  isMounted = false;
602
871
  window.removeEventListener("click", handleClick, false);
603
872
  window.removeEventListener("popstate", handlePopState, false);
873
+ window.removeEventListener("mouseover", handleHover, false);
604
874
  };
605
- }, []);
875
+ }, [routes, notFoundRoute]);
606
876
  useEffect(() => {
607
877
  const handleDataRefresh = () => {
608
- const freshData = window?.__FW_DATA__;
878
+ const freshData = window[WINDOW_DATA_KEY];
609
879
  if (!freshData) return;
610
880
  const currentPathname = window.location.pathname;
611
881
  const freshPathname = freshData.pathname;
@@ -624,7 +894,7 @@ function AppShell({
624
894
  return () => {
625
895
  window.removeEventListener("fw-data-refresh", handleDataRefresh);
626
896
  };
627
- }, [state.url]);
897
+ }, []);
628
898
  const isError = state.route === errorRoute;
629
899
  const isNotFound = state.route === notFoundRoute;
630
900
  const routeType = isError ? "error" : isNotFound ? "notfound" : "normal";
@@ -632,6 +902,89 @@ function AppShell({
632
902
  return /* @__PURE__ */ jsx2(RouterContext.Provider, { value: { navigate: handleNavigate }, children: /* @__PURE__ */ jsx2(RouterView, { state }, routeKey) });
633
903
  }
634
904
 
905
+ // modules/runtime/client/hot-reload.ts
906
+ function setupHotReload() {
907
+ const nodeEnv = process.env.NODE_ENV || "production";
908
+ const isDev = nodeEnv === "development";
909
+ if (!isDev) {
910
+ return;
911
+ }
912
+ console.log("[hot-reload] Setting up hot reload client...");
913
+ let eventSource = null;
914
+ let reloadTimeout = null;
915
+ let reconnectTimeout = null;
916
+ let reconnectAttempts = 0;
917
+ const MAX_RECONNECT_ATTEMPTS = 10;
918
+ const RECONNECT_DELAY = 1e3;
919
+ const RELOAD_DELAY = 100;
920
+ function connect() {
921
+ try {
922
+ if (eventSource) {
923
+ console.log("[hot-reload] Closing existing EventSource connection");
924
+ eventSource.close();
925
+ }
926
+ const endpoint = "/__fw/hot";
927
+ eventSource = new EventSource(endpoint);
928
+ eventSource.addEventListener("ping", (event) => {
929
+ if ("data" in event) {
930
+ console.log("[hot-reload] \u2705 Connected to hot reload server");
931
+ }
932
+ reconnectAttempts = 0;
933
+ });
934
+ eventSource.addEventListener("message", (event) => {
935
+ const data = event.data;
936
+ if (data && typeof data === "string" && data.startsWith("reload:")) {
937
+ const filePath = data.slice(7);
938
+ console.log(`[hot-reload] \u{1F4DD} File changed: ${filePath}, reloading...`);
939
+ if (reloadTimeout) {
940
+ clearTimeout(reloadTimeout);
941
+ }
942
+ reloadTimeout = setTimeout(() => {
943
+ try {
944
+ window.location.reload();
945
+ } catch (error) {
946
+ console.error("[hot-reload] \u274C Error reloading page:", error);
947
+ setTimeout(() => window.location.reload(), 100);
948
+ }
949
+ }, RELOAD_DELAY);
950
+ }
951
+ });
952
+ eventSource.onopen = () => {
953
+ reconnectAttempts = 0;
954
+ };
955
+ eventSource.onerror = (error) => {
956
+ const states = ["CONNECTING", "OPEN", "CLOSED"];
957
+ const state = states[eventSource?.readyState ?? 0] || "UNKNOWN";
958
+ if (eventSource?.readyState === EventSource.CONNECTING) {
959
+ console.log("[hot-reload] \u23F3 Still connecting...");
960
+ return;
961
+ } else if (eventSource?.readyState === EventSource.OPEN) {
962
+ console.warn("[hot-reload] \u26A0\uFE0F Connection error (but connection is open):", error);
963
+ } else {
964
+ console.warn(`[hot-reload] \u274C Connection closed (readyState: ${state})`);
965
+ if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
966
+ reconnectAttempts++;
967
+ const delay = RECONNECT_DELAY * reconnectAttempts;
968
+ if (reconnectTimeout) {
969
+ clearTimeout(reconnectTimeout);
970
+ }
971
+ reconnectTimeout = setTimeout(() => {
972
+ console.log(`[hot-reload] \u{1F504} Reconnecting... (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
973
+ connect();
974
+ }, delay);
975
+ } else {
976
+ console.error("[hot-reload] \u274C Max reconnect attempts reached. Please refresh the page manually.");
977
+ }
978
+ }
979
+ };
980
+ } catch (error) {
981
+ console.error("[hot-reload] \u274C Failed to create EventSource:", error);
982
+ console.error("[hot-reload] EventSource may not be supported in this browser.");
983
+ }
984
+ }
985
+ connect();
986
+ }
987
+
635
988
  // modules/runtime/client/bootstrap.tsx
636
989
  import { jsx as jsx3 } from "react/jsx-runtime";
637
990
  async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute, errorRoute) {
@@ -673,101 +1026,96 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
673
1026
  props: initialData?.props ?? {}
674
1027
  };
675
1028
  }
676
- function setupHotReload() {
677
- const nodeEnv = typeof process !== "undefined" && process?.env?.NODE_ENV || "production";
678
- const isDev = nodeEnv !== "production";
679
- if (!isDev) {
680
- return;
1029
+ function initializeRouterData(initialUrl, initialData) {
1030
+ let routerData = getRouterData();
1031
+ if (!routerData) {
1032
+ const url = new URL(initialUrl, window.location.origin);
1033
+ const pathname = initialData?.pathname || url.pathname;
1034
+ routerData = {
1035
+ pathname,
1036
+ params: initialData?.params || {},
1037
+ searchParams: Object.fromEntries(url.searchParams.entries())
1038
+ };
1039
+ setRouterData(routerData);
681
1040
  }
1041
+ }
1042
+ async function hydrateInitialRoute(container, initialUrl, initialData, routes, notFoundRoute, errorRoute) {
682
1043
  try {
683
- console.log("[hot-reload] Attempting to connect to /__fw/hot...");
684
- const eventSource = new EventSource("/__fw/hot");
685
- let reloadTimeout = null;
686
- eventSource.addEventListener("message", (event) => {
687
- const data = event.data;
688
- if (data && data.startsWith("reload:")) {
689
- const filePath = data.slice(7);
690
- console.log(`[hot-reload] File changed: ${filePath}`);
691
- if (reloadTimeout) {
692
- clearTimeout(reloadTimeout);
693
- }
694
- reloadTimeout = setTimeout(() => {
695
- console.log("[hot-reload] Reloading page...");
696
- window.location.reload();
697
- }, 500);
698
- }
699
- });
700
- eventSource.addEventListener("ping", () => {
701
- console.log("[hot-reload] \u2713 Connected to hot reload server");
702
- });
703
- eventSource.onopen = () => {
704
- console.log("[hot-reload] \u2713 SSE connection opened");
705
- };
706
- eventSource.onerror = (error) => {
707
- const states = ["CONNECTING", "OPEN", "CLOSED"];
708
- const state = states[eventSource.readyState] || "UNKNOWN";
709
- if (eventSource.readyState === EventSource.CONNECTING) {
710
- console.log("[hot-reload] Connecting...");
711
- } else if (eventSource.readyState === EventSource.OPEN) {
712
- console.warn("[hot-reload] Connection error (but connection is open):", error);
713
- } else {
714
- console.log("[hot-reload] Connection closed (readyState:", state, ")");
1044
+ const initialState = await loadInitialRoute(
1045
+ initialUrl,
1046
+ initialData,
1047
+ routes,
1048
+ notFoundRoute,
1049
+ errorRoute
1050
+ );
1051
+ if (initialData?.metadata) {
1052
+ try {
1053
+ applyMetadata(initialData.metadata);
1054
+ } catch (metadataError) {
1055
+ console.warn("[client] Error applying metadata:", metadataError);
715
1056
  }
716
- };
1057
+ }
1058
+ hydrateRoot(
1059
+ container,
1060
+ /* @__PURE__ */ jsx3(
1061
+ AppShell,
1062
+ {
1063
+ initialState,
1064
+ routes,
1065
+ notFoundRoute,
1066
+ errorRoute
1067
+ }
1068
+ )
1069
+ );
717
1070
  } catch (error) {
718
- console.log("[hot-reload] EventSource not supported or error:", error);
1071
+ console.error(
1072
+ "[client] Error loading initial route components for",
1073
+ initialUrl,
1074
+ error
1075
+ );
1076
+ throw error;
719
1077
  }
720
1078
  }
721
1079
  function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
722
- console.log("[client] Bootstrap starting, setting up hot reload...");
723
1080
  setupHotReload();
724
- (async function bootstrap() {
725
- const container = document.getElementById(APP_CONTAINER_ID);
726
- const initialData = getWindowData();
727
- if (!container) {
728
- console.error(`Container #${APP_CONTAINER_ID} not found for hydration`);
729
- return;
730
- }
731
- const initialUrl = window.location.pathname + window.location.search;
732
- let routerData = getRouterData();
733
- if (!routerData) {
734
- const url = new URL(initialUrl, window.location.origin);
735
- routerData = {
736
- pathname: url.pathname,
737
- params: initialData?.params || {},
738
- searchParams: Object.fromEntries(url.searchParams.entries())
739
- };
740
- setRouterData(routerData);
741
- }
1081
+ (async () => {
742
1082
  try {
743
- const initialState = await loadInitialRoute(
1083
+ const container = document.getElementById(APP_CONTAINER_ID);
1084
+ if (!container) {
1085
+ console.error(`
1086
+ \u274C [client] Hydration failed: Container #${APP_CONTAINER_ID} not found`);
1087
+ console.error("\u{1F4A1} This usually means:");
1088
+ console.error(" \u2022 The HTML structure doesn't match what React expects");
1089
+ console.error(" \u2022 The container was removed before hydration");
1090
+ console.error(" \u2022 There's a mismatch between SSR and client HTML\n");
1091
+ return;
1092
+ }
1093
+ const initialData = getWindowData();
1094
+ const initialUrl = (initialData?.pathname || window.location.pathname) + window.location.search;
1095
+ if (initialData?.props) {
1096
+ setPreservedLayoutProps(initialData.props);
1097
+ }
1098
+ const routerPathname = initialData?.pathname || window.location.pathname;
1099
+ initializeRouterData(routerPathname + window.location.search, initialData);
1100
+ await hydrateInitialRoute(
1101
+ container,
744
1102
  initialUrl,
745
1103
  initialData,
746
1104
  routes,
747
1105
  notFoundRoute,
748
1106
  errorRoute
749
1107
  );
750
- if (initialData?.metadata) {
751
- applyMetadata(initialData.metadata);
752
- }
753
- hydrateRoot(
754
- container,
755
- /* @__PURE__ */ jsx3(
756
- AppShell,
757
- {
758
- initialState,
759
- routes,
760
- notFoundRoute,
761
- errorRoute
762
- }
763
- )
764
- );
765
1108
  } catch (error) {
766
- console.error(
767
- "[client] Error loading initial route components for",
768
- initialUrl,
769
- error
770
- );
1109
+ console.error("\n\u274C [client] Fatal error during bootstrap:");
1110
+ console.error(error);
1111
+ if (error instanceof Error) {
1112
+ console.error("\nError details:");
1113
+ console.error(` Message: ${error.message}`);
1114
+ if (error.stack) {
1115
+ console.error(` Stack: ${error.stack}`);
1116
+ }
1117
+ }
1118
+ console.error("\n\u{1F4A1} Attempting page reload to recover...\n");
771
1119
  window.location.reload();
772
1120
  }
773
1121
  })();
@@ -775,4 +1123,4 @@ function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
775
1123
  export {
776
1124
  bootstrapClient
777
1125
  };
778
- //# sourceMappingURL=runtime.js.map
1126
+ //# sourceMappingURL=runtime.mjs.map