@shuvi/platform-web 2.0.0-dev.21 → 2.0.0-dev.22

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.
@@ -1,7 +1,13 @@
1
1
  import { LinkProps } from '@shuvi/router-react';
2
- export declare const Link: ({ to, ref, prefetch, onMouseEnter, ...rest }: LinkWrapperProps) => JSX.Element;
2
+ export declare const Link: ({ prefetch, ...rest }: LinkWrapperProps) => JSX.Element;
3
3
  interface LinkWrapperProps extends LinkProps {
4
- prefetch?: boolean;
4
+ /**
5
+ * Controls link prefetching behavior:
6
+ * - `undefined` or `true`: Auto prefetch when visible + hover prefetch (default)
7
+ * - `false`: Disable auto prefetch, only prefetch on hover
8
+ * - `'none'`: Completely disable all prefetching (no auto, no hover)
9
+ */
10
+ prefetch?: boolean | 'none';
5
11
  ref?: any;
6
12
  }
7
13
  export {};
@@ -75,24 +75,45 @@ function prefetchFn(router, to) {
75
75
  const isAbsoluteUrl = (url) => {
76
76
  return ABSOLUTE_URL_REGEX.test(url);
77
77
  };
78
- export const Link = function LinkWithPrefetch(_a) {
78
+ // Internal component with prefetch logic (contains hooks)
79
+ const LinkWithPrefetch = function (_a) {
79
80
  var { to, ref, prefetch, onMouseEnter } = _a, rest = __rest(_a, ["to", "ref", "prefetch", "onMouseEnter"]);
80
81
  const isHrefValid = typeof to === 'string' && !isAbsoluteUrl(to);
82
+ const shouldAutoPrefetch = prefetch !== false;
81
83
  const previousHref = React.useRef(to);
82
- const [setIntersectionRef, isVisible, resetVisible] = useIntersection({});
84
+ const isMountedRef = React.useRef(true);
83
85
  const { router } = React.useContext(RouterContext);
86
+ const [setIntersectionRef, isVisible, resetVisible] = useIntersection({
87
+ disabled: !shouldAutoPrefetch
88
+ });
89
+ React.useEffect(() => {
90
+ isMountedRef.current = true;
91
+ return () => {
92
+ isMountedRef.current = false;
93
+ };
94
+ }, []);
84
95
  const setRef = React.useCallback((el) => __awaiter(this, void 0, void 0, function* () {
85
- /**
86
- * Lazy prefetching to avoid negative performance impact for the first page.
87
- */
88
- yield awaitPageLoadAndIdle({ remainingTime: 49, timeout: 10 * 1000 });
89
- // Before the link getting observed, check if visible state need to be reset
90
- if (isHrefValid && previousHref.current !== to) {
91
- resetVisible();
92
- previousHref.current = to;
96
+ // Handle unmount: cleanup IntersectionObserver
97
+ if (!el) {
98
+ setIntersectionRef(null);
99
+ return;
100
+ }
101
+ if (shouldAutoPrefetch) {
102
+ /**
103
+ * Lazy prefetching to avoid negative performance impact for the first page.
104
+ */
105
+ yield awaitPageLoadAndIdle({ remainingTime: 49, timeout: 10 * 1000 });
106
+ // Check if component is still mounted after async operation
107
+ if (!isMountedRef.current || !el.isConnected)
108
+ return;
109
+ // Before the link getting observed, check if visible state need to be reset
110
+ if (isHrefValid && previousHref.current !== to) {
111
+ resetVisible();
112
+ previousHref.current = to;
113
+ }
114
+ if (isHrefValid)
115
+ setIntersectionRef(el);
93
116
  }
94
- if (isHrefValid && prefetch !== false)
95
- setIntersectionRef(el);
96
117
  if (ref) {
97
118
  if (typeof ref === 'function')
98
119
  ref(el);
@@ -100,14 +121,13 @@ export const Link = function LinkWithPrefetch(_a) {
100
121
  ref.current = el;
101
122
  }
102
123
  }
103
- }), [to, isHrefValid, prefetch, resetVisible, setIntersectionRef, ref]);
124
+ }), [to, isHrefValid, shouldAutoPrefetch, resetVisible, setIntersectionRef, ref]);
104
125
  React.useEffect(() => {
105
- const shouldPrefetch = isHrefValid && prefetch !== false && isVisible;
106
- if (shouldPrefetch && !prefetched[to]) {
126
+ if (shouldAutoPrefetch && isHrefValid && isVisible && !prefetched[to]) {
107
127
  prefetchFn(router, to);
108
128
  prefetched[to] = true;
109
129
  }
110
- }, [to, prefetch, isVisible]);
130
+ }, [to, isVisible, isHrefValid, shouldAutoPrefetch]);
111
131
  const childProps = {
112
132
  ref: setRef,
113
133
  onMouseEnter: (e) => {
@@ -122,3 +142,13 @@ export const Link = function LinkWithPrefetch(_a) {
122
142
  };
123
143
  return <LinkFromRouterReact to={to} {...rest} {...childProps}/>;
124
144
  };
145
+ // Wrapper component (no hooks, safe to return early)
146
+ export const Link = function (_a) {
147
+ var { prefetch } = _a, rest = __rest(_a, ["prefetch"]);
148
+ // When prefetch is "none", completely disable all prefetching (auto + hover)
149
+ if (prefetch === 'none') {
150
+ return <LinkFromRouterReact {...rest}/>;
151
+ }
152
+ // Otherwise use the full prefetch logic
153
+ return <LinkWithPrefetch prefetch={prefetch} {...rest}/>;
154
+ };
@@ -21,18 +21,28 @@ export default function useIntersection({ rootRef, rootMargin = '0px', disabled
21
21
  setVisible(false);
22
22
  }, []);
23
23
  useEffect(() => {
24
- if (!hasIntersectionObserver) {
24
+ // Only execute when IntersectionObserver is unavailable AND not disabled
25
+ if (!hasIntersectionObserver && !isDisabled) {
25
26
  if (!visible) {
26
27
  const idleCallback = requestIdleCallback(() => setVisible(true));
27
28
  return () => cancelIdleCallback(idleCallback);
28
29
  }
29
30
  }
30
31
  return () => { };
31
- }, [visible]);
32
+ }, [visible, isDisabled]);
32
33
  useEffect(() => {
33
- if (rootRef)
34
+ if (rootRef && !isDisabled)
34
35
  setRoot(rootRef.current);
35
- }, [rootRef]);
36
+ }, [rootRef, isDisabled]);
37
+ // Cleanup on unmount
38
+ useEffect(() => {
39
+ return () => {
40
+ if (unobserve.current) {
41
+ unobserve.current();
42
+ unobserve.current = undefined;
43
+ }
44
+ };
45
+ }, []);
36
46
  return [setRef, visible, resetVisible];
37
47
  }
38
48
  function observe(element, callback, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shuvi/platform-web",
3
- "version": "2.0.0-dev.21",
3
+ "version": "2.0.0-dev.22",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/shuvijs/shuvi.git",
@@ -72,15 +72,15 @@
72
72
  },
73
73
  "dependencies": {
74
74
  "@rspack/plugin-react-refresh": "^1.4.3",
75
- "@shuvi/error-overlay": "2.0.0-dev.21",
76
- "@shuvi/hook": "2.0.0-dev.21",
77
- "@shuvi/platform-shared": "2.0.0-dev.21",
78
- "@shuvi/router": "2.0.0-dev.21",
79
- "@shuvi/router-react": "2.0.0-dev.21",
80
- "@shuvi/runtime": "2.0.0-dev.21",
81
- "@shuvi/shared": "2.0.0-dev.21",
82
- "@shuvi/toolpack": "2.0.0-dev.21",
83
- "@shuvi/utils": "2.0.0-dev.21",
75
+ "@shuvi/error-overlay": "2.0.0-dev.22",
76
+ "@shuvi/hook": "2.0.0-dev.22",
77
+ "@shuvi/platform-shared": "2.0.0-dev.22",
78
+ "@shuvi/router": "2.0.0-dev.22",
79
+ "@shuvi/router-react": "2.0.0-dev.22",
80
+ "@shuvi/runtime": "2.0.0-dev.22",
81
+ "@shuvi/shared": "2.0.0-dev.22",
82
+ "@shuvi/toolpack": "2.0.0-dev.22",
83
+ "@shuvi/utils": "2.0.0-dev.22",
84
84
  "content-type": "1.0.4",
85
85
  "core-js": "3.6.5",
86
86
  "doura": "0.0.13",
@@ -98,7 +98,7 @@
98
98
  "whatwg-fetch": "3.0.0"
99
99
  },
100
100
  "peerDependencies": {
101
- "@shuvi/service": "2.0.0-dev.21"
101
+ "@shuvi/service": "2.0.0-dev.22"
102
102
  },
103
103
  "devDependencies": {
104
104
  "@shuvi/service": "workspace:*",