Skip to content

Commit 8450ac9

Browse files
committed
feat: add prefetching for docsearch results
1 parent 33f9797 commit 8450ac9

File tree

4 files changed

+179
-63
lines changed

4 files changed

+179
-63
lines changed

components/Hit.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/**
2+
* Prefetching logic is modified based on https://github.com/nuxt/nuxt.js/blob/86570cf6ab82cd7f58fb7973bd4c43a55307175b/packages/vue-app/template/components/nuxt-link.client.js
3+
*/
4+
import { Component, createRef, createElement } from 'preact'
5+
6+
function isSpecialClick (event) {
7+
return (
8+
event.button === 1 ||
9+
event.altKey ||
10+
event.ctrlKey ||
11+
event.metaKey ||
12+
event.shiftKey
13+
)
14+
}
15+
16+
function normalizeURL (url) {
17+
if (url.endsWith('/')) {
18+
return url.slice(0, -1)
19+
}
20+
21+
return url.replace(/\/([#?])/, '$1')
22+
}
23+
24+
const requestIdleCallback =
25+
typeof window !== 'undefined'
26+
? window.requestIdleCallback ||
27+
function (cb) {
28+
const start = Date.now()
29+
return setTimeout(() => {
30+
// eslint-disable-next-line standard/no-callback-literal
31+
cb({
32+
didTimeout: false,
33+
timeRemaining: () => Math.max(0, 50 - (Date.now() - start))
34+
})
35+
}, 1)
36+
}
37+
: () => {}
38+
39+
const cancelIdleCallback =
40+
typeof window !== 'undefined'
41+
? window.cancelIdleCallback ||
42+
function (id) {
43+
clearTimeout(id)
44+
}
45+
: () => {}
46+
47+
const observer =
48+
typeof window !== 'undefined'
49+
? window.IntersectionObserver &&
50+
new window.IntersectionObserver(entries => {
51+
entries.forEach(({ intersectionRatio, target }) => {
52+
if (intersectionRatio <= 0 || !target.__prefetch) {
53+
return
54+
}
55+
target.__prefetch()
56+
})
57+
})
58+
: null
59+
60+
export function createComponent ({ isOffline, router, route }) {
61+
return class Hit extends Component {
62+
handleId = null;
63+
observed = false;
64+
link = createRef();
65+
66+
canPrefetch () {
67+
const conn = navigator.connection
68+
const hasBadConnection =
69+
isOffline ||
70+
(conn && ((conn.effectiveType || '').includes('2g') || conn.saveData))
71+
72+
return !hasBadConnection
73+
}
74+
75+
getPrefetchComponents () {
76+
const { hit } = this.props
77+
const ref = router.resolve(normalizeURL(hit.url), route)
78+
const Components = ref.resolved.matched.map(r => r.components.default)
79+
80+
return Components.filter(
81+
Component =>
82+
typeof Component === 'function' &&
83+
!Component.options &&
84+
!Component.__prefetched
85+
)
86+
}
87+
88+
prefetchLink (el) {
89+
if (!this.canPrefetch()) {
90+
return
91+
}
92+
// Stop observing this link (in case of internet connection changes)
93+
observer.unobserve(el)
94+
const Components = this.getPrefetchComponents()
95+
96+
for (const Component of Components) {
97+
const componentOrPromise = Component()
98+
if (componentOrPromise instanceof Promise) {
99+
componentOrPromise.catch(() => {})
100+
}
101+
Component.__prefetched = true
102+
}
103+
}
104+
105+
observe (el) {
106+
// If no IntersectionObserver, avoid prefetching
107+
if (!observer) {
108+
return
109+
}
110+
// Add to observer
111+
if (this.getPrefetchComponents().length > 0) {
112+
el.__prefetch = () => this.prefetchLink(el)
113+
observer.observe(el)
114+
this.observed = true
115+
}
116+
}
117+
118+
componentDidMount () {
119+
this.handleId = requestIdleCallback(
120+
() => this.observe(this.link.current),
121+
{
122+
timeout: 2e3
123+
}
124+
)
125+
}
126+
127+
componentWillUnmount () {
128+
cancelIdleCallback(this.handleId)
129+
130+
if (this.observed) {
131+
observer.unobserve(this.link.current)
132+
delete this.link.current.__prefetch
133+
}
134+
}
135+
136+
render ({ hit, children }) {
137+
const url = normalizeURL(hit.url)
138+
return createElement('a', {
139+
href: url,
140+
ref: this.link,
141+
onClick: event => {
142+
if (isSpecialClick(event)) {
143+
return
144+
}
145+
// We rely on the native link scrolling when user is
146+
// already on the right anchor because Vue Router doesn't
147+
// support duplicated history entries.
148+
if (router.history.current.fullPath === hit.url) {
149+
return
150+
}
151+
const { pathname: hitPathname } = new URL(
152+
window.location.origin + hit.url
153+
)
154+
// If the hits goes to another page, we prevent the native link behavior
155+
// to leverage the Vue Router loading feature.
156+
if (router.history.current.path !== hitPathname) {
157+
event.preventDefault()
158+
}
159+
router.push(url)
160+
},
161+
children
162+
})
163+
}
164+
}
165+
}

components/OneSearch.vue

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,8 @@
1616

1717
<script>
1818
import { SearchBox } from 'veui'
19-
import { createElement } from 'preact'
2019
import i18n from '../common/i18n'
21-
22-
function isSpecialClick (event) {
23-
return (
24-
event.button === 1 ||
25-
event.altKey ||
26-
event.ctrlKey ||
27-
event.metaKey ||
28-
event.shiftKey
29-
)
30-
}
31-
32-
function normalizeURL (url) {
33-
if (url.endsWith('/')) {
34-
return url.slice(0, -1)
35-
}
36-
37-
return url.replace(/\/([#?])/, '$1')
38-
}
39-
40-
function wait (time) {
41-
return new Promise(resolve => {
42-
setTimeout(resolve, time)
43-
})
44-
}
20+
import { createComponent } from './Hit'
4521
4622
export default {
4723
name: 'one-search',
@@ -116,36 +92,11 @@ export default {
11692
}
11793
})
11894
},
119-
hitComponent: ({ hit, children }) => {
120-
const url = normalizeURL(hit.url)
121-
return createElement(
122-
'a',
123-
{
124-
href: url,
125-
onClick: event => {
126-
if (isSpecialClick(event)) {
127-
return
128-
}
129-
// We rely on the native link scrolling when user is
130-
// already on the right anchor because Vue Router doesn't
131-
// support duplicated history entries.
132-
if (this.$router.history.current.fullPath === hit.url) {
133-
return
134-
}
135-
const { pathname: hitPathname } = new URL(
136-
window.location.origin + hit.url
137-
)
138-
// If the hits goes to another page, we prevent the native link behavior
139-
// to leverage the Vue Router loading feature.
140-
if (this.$router.history.current.path !== hitPathname) {
141-
event.preventDefault()
142-
}
143-
this.$router.push(url)
144-
},
145-
children
146-
}
147-
)
148-
}
95+
hitComponent: createComponent({
96+
router: this.$router,
97+
route: this.$route,
98+
isOffline: this.$nuxt.isOffline
99+
})
149100
})
150101
151102
this.preparing = false

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"monaco-themes": "^0.4.0",
6464
"nuxt": "^2.15.7",
6565
"object-deep-search": "0.0.7",
66-
"preact": "^10.5.14",
66+
"preact": "^10.10.2",
6767
"prettier": "^1.16.4",
6868
"prettier-eslint": "^8.8.2",
6969
"raw-loader": "^4.0.2",

0 commit comments

Comments
 (0)