-
Notifications
You must be signed in to change notification settings - Fork 34
/
_worker.js
128 lines (105 loc) · 4.3 KB
/
_worker.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
const initialModuleScriptsString = INJECT_INITIAL_MODULE_SCRIPTS_STRING_HERE
const initialScripts = INJECT_INITIAL_SCRIPTS_HERE
const asyncScripts = INJECT_ASYNC_SCRIPTS_HERE
const html = INJECT_HTML_HERE
const documentEtag = INJECT_DOCUMENT_ETAG_HERE
const documentHeaders = { 'Cache-Control': 'public, max-age=0', 'Content-Type': 'text/html; charset=utf-8' }
const BOT_AGENTS = [
'bingbot',
'yahoo! slurp',
'yandex',
'baiduspider',
'facebookexternalhit',
'twitterbot',
'rogerbot',
'linkedinbot',
'embedly',
'quora link preview',
'showyoubot',
'outbrain',
'pinterest/0.',
'developers.google.com/+/web/snippet',
'slackbot',
'vkshare',
'w3c_validator',
'redditbot',
'applebot',
'whatsapp',
'flipboard',
'tumblr',
'bitlybot',
'skypeuripreview',
'nuzzel',
'discordbot',
'qwantify',
'pinterestbot',
'bitrix link preview',
'xing-contenttabreceiver',
'telegrambot'
]
const fetchPrerendered = async request => {
const { url, headers } = request
const headersToSend = new Headers(headers)
const prerenderUrl = new URL(`https://renderless.theninthsky.workers.dev?url=${url}`)
const prerenderRequest = new Request(prerenderUrl, {
headers: headersToSend,
redirect: 'manual'
})
const { body, headers: responseHeaders } = await fetch(prerenderRequest)
return new Response(body, { headers: responseHeaders })
}
const isMatch = (pathname, path) => {
if (pathname === path) return { exact: true, match: true }
if (!path.includes(':')) return { match: false }
const pathnameParts = pathname.split('/')
const pathParts = path.split('/')
const match = pathnameParts.every((part, ind) => part === pathParts[ind] || pathParts[ind]?.startsWith(':'))
return {
exact: match && pathnameParts.length === pathParts.length,
match
}
}
export default {
fetch(request, env) {
let { 'If-None-Match': etag, 'X-Cached': xCached } = JSON.parse(
request.headers.get('service-worker-navigation-preload') || '{}'
)
etag ||= request.headers.get('If-None-Match')
if (etag === documentEtag) return new Response(null, { status: 304, headers: documentHeaders })
const pathname = new URL(request.url).pathname.toLowerCase()
const userAgent = (request.headers.get('User-Agent') || '').toLowerCase()
const bypassWorker = ['prerender', 'googlebot'].includes(userAgent) || pathname.includes('.')
if (bypassWorker) return env.ASSETS.fetch(request)
if (BOT_AGENTS.some(agent => userAgent.includes(agent))) return fetchPrerendered(request)
xCached ||= request.headers.get('X-Cached')
const cachedScripts = JSON.parse(xCached || '[]')
const uncachedScripts = [...initialScripts, ...asyncScripts].filter(({ url }) => !cachedScripts.includes(url))
if (!uncachedScripts.length) {
return new Response(html, { headers: { ...documentHeaders, ETag: documentEtag, 'X-ETag': documentEtag } })
}
let body = html.replace(initialModuleScriptsString, () => '')
const injectedInitialScriptsString = initialScripts
.map(({ url, source }) =>
cachedScripts.includes(url) ? `<script src="${url}"></script>` : `<script id="${url}">${source}</script>`
)
.join('\n')
body = body.replace('</body>', () => `<!-- INJECT_ASYNC_SCRIPTS_HERE -->${injectedInitialScriptsString}\n</body>`)
const matchingPageScripts = asyncScripts
.map(asset => {
const parentsPaths = asset.parentPaths.map(path => ({ path, ...isMatch(pathname, path) }))
const parentPathsExactMatch = parentsPaths.some(({ exact }) => exact)
const parentPathsMatch = parentsPaths.some(({ match }) => match)
return { ...asset, exact: parentPathsExactMatch, match: parentPathsMatch }
})
.filter(({ match }) => match)
const exactMatchingPageScripts = matchingPageScripts.filter(({ exact }) => exact)
const pageScripts = exactMatchingPageScripts.length ? exactMatchingPageScripts : matchingPageScripts
const uncachedPageScripts = pageScripts.filter(({ url }) => !cachedScripts.includes(url))
const injectedAsyncScriptsString = uncachedPageScripts.reduce(
(str, { url, source }) => `${str}\n<script id="${url}">${source}</script>`,
''
)
body = body.replace('<!-- INJECT_ASYNC_SCRIPTS_HERE -->', () => injectedAsyncScriptsString)
return new Response(body, { headers: documentHeaders })
}
}