-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
preFetch.ts
120 lines (104 loc) · 3.19 KB
/
preFetch.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Customized pre-fetch for page chunks based on
// https://github.com/GoogleChromeLabs/quicklink
import { useRoute } from '../router'
import { onMounted, onUnmounted, watch } from 'vue'
import { inBrowser, pathToFile } from '../utils'
const hasFetched = new Set<string>()
const createLink = () => document.createElement('link')
const viaDOM = (url: string) => {
const link = createLink()
link.rel = `prefetch`
link.href = url
document.head.appendChild(link)
}
const viaXHR = (url: string) => {
const req = new XMLHttpRequest()
req.open('GET', url, (req.withCredentials = true))
req.send()
}
let link
const doFetch: (url: string) => void =
inBrowser &&
(link = createLink()) &&
link.relList &&
link.relList.supports &&
link.relList.supports('prefetch')
? viaDOM
: viaXHR
export function usePrefetch() {
if (!inBrowser) {
return
}
if (!window.IntersectionObserver) {
return
}
let conn
if (
(conn = (navigator as any).connection) &&
(conn.saveData || /2g/.test(conn.effectiveType))
) {
// Don't prefetch if using 2G or if Save-Data is enabled.
return
}
const rIC = window.requestIdleCallback || setTimeout
let observer: IntersectionObserver | null = null
const observeLinks = () => {
if (observer) {
observer.disconnect()
}
observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const link = entry.target as HTMLAnchorElement
observer!.unobserve(link)
const { pathname } = link
if (!hasFetched.has(pathname)) {
hasFetched.add(pathname)
const pageChunkPath = pathToFile(pathname)
if (pageChunkPath) doFetch(pageChunkPath)
}
}
})
})
rIC(() => {
document
.querySelectorAll<HTMLAnchorElement | SVGAElement>('#app a')
.forEach((link) => {
const { hostname, pathname } = new URL(
link.href instanceof SVGAnimatedString
? link.href.animVal
: link.href,
link.baseURI
)
const extMatch = pathname.match(/\.\w+$/)
if (extMatch && extMatch[0] !== '.html') {
return
}
if (
// only prefetch same tab navigation, since a new tab will load
// the lean js chunk instead.
link.target !== '_blank' &&
// only prefetch inbound links
hostname === location.hostname
) {
if (pathname !== location.pathname) {
observer!.observe(link)
} else {
// No need to prefetch chunk for the current page, but also mark
// it as already fetched. This is because the initial page uses its
// lean chunk, and if we don't mark it, navigation to another page
// with a link back to the first page will fetch its full chunk
// which isn't needed.
hasFetched.add(pathname)
}
}
})
})
}
onMounted(observeLinks)
const route = useRoute()
watch(() => route.path, observeLinks)
onUnmounted(() => {
observer && observer.disconnect()
})
}