veryfront 0.1.851 → 0.1.852

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/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.851",
3
+ "version": "0.1.852",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "minimumDependencyAge": {
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../../../src/src/html/hydration-script-builder/templates/router.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,cA+9B3B,CAAC"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../../../src/src/html/hydration-script-builder/templates/router.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,cAklC3B,CAAC"}
@@ -36,6 +36,10 @@ export const getRouterScript = () => `
36
36
  const BACKGROUND_REFRESH_INTERVAL_MS = 30 * 1000;
37
37
  const PREFETCH_DELAY_MS = 100;
38
38
  const MAX_PREFETCH_PATHS = 100;
39
+ const IDLE_PREFETCH_DELAY_MS = 1200;
40
+ const IDLE_PREFETCH_MAX_LINKS = 4;
41
+ const VIEWPORT_PREFETCH_MAX_LINKS = 8;
42
+ const VIEWPORT_PREFETCH_ROOT_MARGIN = '200px';
39
43
  const MAX_ROUTE_TIMINGS = 100;
40
44
  const MAX_SERVER_TIMING_LENGTH = 1024;
41
45
 
@@ -758,6 +762,7 @@ export const getRouterScript = () => `
758
762
  container.__reactRoot.render(tree);
759
763
  perfEnd('render:reactRender');
760
764
  log('Page re-rendered via SPA');
765
+ scheduleRoutePrefetchRefresh();
761
766
  return;
762
767
  }
763
768
 
@@ -773,6 +778,9 @@ export const getRouterScript = () => `
773
778
  // ============================================
774
779
  let prefetchTimeout = null;
775
780
  let currentHoverLink = null;
781
+ let routePrefetchRefreshPending = false;
782
+ let viewportPrefetchObserver = null;
783
+ const observedPrefetchLinks = new WeakSet();
776
784
  const prefetchedPaths = new Set();
777
785
  const inFlightPrefetches = new Set();
778
786
 
@@ -794,6 +802,51 @@ export const getRouterScript = () => `
794
802
  return allPaths;
795
803
  }
796
804
 
805
+ function getCurrentRouteHref() {
806
+ return window.location.pathname + window.location.search;
807
+ }
808
+
809
+ function getInternalRouteHrefFromLink(link) {
810
+ if (
811
+ !link ||
812
+ link.target === '_blank' ||
813
+ link.hasAttribute('download') ||
814
+ link.getAttribute('data-prefetch') === 'false'
815
+ ) {
816
+ return null;
817
+ }
818
+
819
+ const href = link.getAttribute('href');
820
+ if (!href || href.startsWith('#') || href.startsWith('//') || !href.startsWith('/')) return null;
821
+
822
+ try {
823
+ const url = new URL(href, window.location.origin);
824
+ if (url.origin !== window.location.origin) return null;
825
+
826
+ const routeHref = url.pathname + url.search;
827
+ return routeHref === getCurrentRouteHref() ? null : routeHref;
828
+ } catch (_) {
829
+ return null;
830
+ }
831
+ }
832
+
833
+ function getEligiblePrefetchLinks(limit) {
834
+ const links = [];
835
+ const seenHrefs = new Set();
836
+
837
+ for (const link of document.querySelectorAll('a[href]')) {
838
+ const href = getInternalRouteHrefFromLink(link);
839
+ if (!href || seenHrefs.has(href)) continue;
840
+
841
+ seenHrefs.add(href);
842
+ links.push({ link, href });
843
+
844
+ if (links.length >= limit) break;
845
+ }
846
+
847
+ return links;
848
+ }
849
+
797
850
  async function preloadModulesForPageData(pageData, path) {
798
851
  if (!pageData) return;
799
852
  if (pageData.releaseId && window.__veryfrontSetReleaseId) {
@@ -842,6 +895,62 @@ export const getRouterScript = () => `
842
895
  });
843
896
  }
844
897
 
898
+ function prefetchEligibleRouteLinks(limit) {
899
+ for (const { href } of getEligiblePrefetchLinks(limit)) {
900
+ prefetchPage(href);
901
+ }
902
+ }
903
+
904
+ function ensureViewportPrefetchObserver() {
905
+ if (viewportPrefetchObserver || typeof IntersectionObserver !== 'function') {
906
+ return viewportPrefetchObserver;
907
+ }
908
+
909
+ viewportPrefetchObserver = new IntersectionObserver((entries) => {
910
+ for (const entry of entries) {
911
+ if (!entry.isIntersecting) continue;
912
+
913
+ viewportPrefetchObserver?.unobserve(entry.target);
914
+ const href = getInternalRouteHrefFromLink(entry.target);
915
+ if (href) prefetchPage(href);
916
+ }
917
+ }, { rootMargin: VIEWPORT_PREFETCH_ROOT_MARGIN });
918
+
919
+ return viewportPrefetchObserver;
920
+ }
921
+
922
+ function observeViewportPrefetchLinks() {
923
+ const observer = ensureViewportPrefetchObserver();
924
+ if (!observer) return;
925
+
926
+ for (const { link } of getEligiblePrefetchLinks(VIEWPORT_PREFETCH_MAX_LINKS)) {
927
+ if (observedPrefetchLinks.has(link)) continue;
928
+
929
+ observedPrefetchLinks.add(link);
930
+ observer.observe(link);
931
+ }
932
+ }
933
+
934
+ function runRoutePrefetchRefresh() {
935
+ routePrefetchRefreshPending = false;
936
+ prefetchEligibleRouteLinks(IDLE_PREFETCH_MAX_LINKS);
937
+ observeViewportPrefetchLinks();
938
+ }
939
+
940
+ function scheduleRoutePrefetchRefresh() {
941
+ if (routePrefetchRefreshPending) return;
942
+
943
+ routePrefetchRefreshPending = true;
944
+ setTimeout(() => {
945
+ if (typeof requestIdleCallback === 'function') {
946
+ requestIdleCallback(runRoutePrefetchRefresh, { timeout: IDLE_PREFETCH_DELAY_MS });
947
+ return;
948
+ }
949
+
950
+ runRoutePrefetchRefresh();
951
+ }, IDLE_PREFETCH_DELAY_MS);
952
+ }
953
+
845
954
  // ============================================
846
955
  // Router object
847
956
  // ============================================
@@ -946,8 +1055,8 @@ export const getRouterScript = () => `
946
1055
  const link = e.target.closest('a[href]');
947
1056
  if (!link) return;
948
1057
 
949
- const href = link.getAttribute('href');
950
- if (!href?.startsWith('/') || href.startsWith('//')) return;
1058
+ const href = getInternalRouteHrefFromLink(link);
1059
+ if (!href) return;
951
1060
 
952
1061
  if (currentHoverLink === link) return;
953
1062
 
@@ -978,6 +1087,12 @@ export const getRouterScript = () => `
978
1087
  true
979
1088
  );
980
1089
 
1090
+ if (document.readyState === 'loading') {
1091
+ document.addEventListener('DOMContentLoaded', scheduleRoutePrefetchRefresh, { once: true });
1092
+ } else {
1093
+ scheduleRoutePrefetchRefresh();
1094
+ }
1095
+
981
1096
  // ============================================
982
1097
  // Router hooks
983
1098
  // ============================================
@@ -60,7 +60,7 @@ export function useRouter() {
60
60
  }
61
61
  /** Renders an anchor element annotated for Veryfront prefetch handling. */
62
62
  export function Link({ prefetch = true, children, ...rest }) {
63
- return React.createElement("a", { ...rest, "data-prefetch": prefetch ? "true" : undefined }, children);
63
+ return React.createElement("a", { ...rest, "data-prefetch": prefetch ? "true" : "false" }, children);
64
64
  }
65
65
  /** Provides page context to route and MDX descendants. */
66
66
  export function PageContextProvider({ children, pageContext, }) {
@@ -1,3 +1,3 @@
1
1
  /** Shared version value. */
2
- export declare const VERSION = "0.1.851";
2
+ export declare const VERSION = "0.1.852";
3
3
  //# sourceMappingURL=version-constant.d.ts.map
@@ -1,4 +1,4 @@
1
1
  // Keep in sync with deno.json version.
2
2
  // scripts/release.ts updates this constant during releases.
3
3
  /** Shared version value. */
4
- export const VERSION = "0.1.851";
4
+ export const VERSION = "0.1.852";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.851",
3
+ "version": "0.1.852",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",