forked from vercel/next.js
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathprefetch.js
128 lines (105 loc) · 3.22 KB
/
prefetch.js
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
121
122
123
124
125
126
127
128
import React from 'react'
import Link, { isLocal } from './link'
import { parse as urlParse } from 'url'
class Messenger {
constructor () {
this.id = 0
this.callacks = {}
this.serviceWorkerReadyCallbacks = []
this.serviceWorkerState = null
navigator.serviceWorker.addEventListener('message', ({ data }) => {
if (data.action !== 'REPLY') return
if (this.callacks[data.replyFor]) {
this.callacks[data.replyFor](data)
}
})
// Reset the cache always.
// Sometimes, there's an already running service worker with cached requests.
// If the app doesn't use any prefetch calls, `ensureInitialized` won't get
// called and cleanup resources.
// So, that's why we do this.
this._resetCache()
}
send (payload, cb) {
if (this.serviceWorkerState === 'REGISTERED') {
this._send(payload, cb)
} else {
this.serviceWorkerReadyCallbacks.push(() => {
this._send(payload, cb)
})
}
}
_send (payload, cb = () => {}) {
const id = this.id ++
const newPayload = { ...payload, id }
this.callacks[id] = (data) => {
if (data.error) {
cb(data.error)
} else {
cb(null, data.result)
}
delete this.callacks[id]
}
navigator.serviceWorker.controller.postMessage(newPayload)
}
_resetCache (cb) {
const reset = () => {
this._send({ action: 'RESET' }, cb)
}
if (navigator.serviceWorker.controller) {
reset()
} else {
navigator.serviceWorker.oncontrollerchange = reset
}
}
ensureInitialized () {
if (this.serviceWorkerState) {
return
}
this.serviceWorkerState = 'REGISTERING'
navigator.serviceWorker.register('/_next-prefetcher.js')
// Reset the cache after registered
// We don't need to have any old caches since service workers lives beyond
// life time of the webpage.
// With this prefetching won't work 100% if multiple pages of the same app
// loads in the same browser in same time.
// Basically, cache will only have prefetched resourses for the last loaded
// page of a given app.
// We could mitigate this, when we add a hash to a every file we fetch.
this._resetCache((err) => {
if (err) throw err
this.serviceWorkerState = 'REGISTERED'
this.serviceWorkerReadyCallbacks.forEach(cb => cb())
this.serviceWorkerReadyCallbacks = []
})
}
}
function hasServiceWorkerSupport () {
return (typeof navigator !== 'undefined' && navigator.serviceWorker)
}
const PREFETCHED_URLS = {}
let messenger
if (hasServiceWorkerSupport()) {
messenger = new Messenger()
}
export function prefetch (href) {
if (!hasServiceWorkerSupport()) return
if (!isLocal(href)) return
// Register the service worker if it's not.
messenger.ensureInitialized()
let { pathname } = urlParse(href)
// Add support for the index page
const url = `/_next/pages${pathname}`
if (PREFETCHED_URLS[url]) return
messenger.send({ action: 'ADD_URL', url: url })
PREFETCHED_URLS[url] = true
}
export default class LinkPrefetch extends React.Component {
render () {
const { href } = this.props
if (this.props.prefetch !== false) {
prefetch(href)
}
return (<Link {...this.props} />)
}
}