@shuvi/platform-web 2.0.0-dev.21 → 2.0.0-dev.23
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: ({
|
|
2
|
+
export declare const Link: ({ prefetch, ...rest }: LinkWrapperProps) => JSX.Element;
|
|
3
3
|
interface LinkWrapperProps extends LinkProps {
|
|
4
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
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,
|
|
124
|
+
}), [to, isHrefValid, shouldAutoPrefetch, resetVisible, setIntersectionRef, ref]);
|
|
104
125
|
React.useEffect(() => {
|
|
105
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "2.0.0-dev.23",
|
|
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.
|
|
76
|
-
"@shuvi/hook": "2.0.0-dev.
|
|
77
|
-
"@shuvi/platform-shared": "2.0.0-dev.
|
|
78
|
-
"@shuvi/router": "2.0.0-dev.
|
|
79
|
-
"@shuvi/router-react": "2.0.0-dev.
|
|
80
|
-
"@shuvi/runtime": "2.0.0-dev.
|
|
81
|
-
"@shuvi/shared": "2.0.0-dev.
|
|
82
|
-
"@shuvi/toolpack": "2.0.0-dev.
|
|
83
|
-
"@shuvi/utils": "2.0.0-dev.
|
|
75
|
+
"@shuvi/error-overlay": "2.0.0-dev.23",
|
|
76
|
+
"@shuvi/hook": "2.0.0-dev.23",
|
|
77
|
+
"@shuvi/platform-shared": "2.0.0-dev.23",
|
|
78
|
+
"@shuvi/router": "2.0.0-dev.23",
|
|
79
|
+
"@shuvi/router-react": "2.0.0-dev.23",
|
|
80
|
+
"@shuvi/runtime": "2.0.0-dev.23",
|
|
81
|
+
"@shuvi/shared": "2.0.0-dev.23",
|
|
82
|
+
"@shuvi/toolpack": "2.0.0-dev.23",
|
|
83
|
+
"@shuvi/utils": "2.0.0-dev.23",
|
|
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.
|
|
101
|
+
"@shuvi/service": "2.0.0-dev.23"
|
|
102
102
|
},
|
|
103
103
|
"devDependencies": {
|
|
104
104
|
"@shuvi/service": "workspace:*",
|