swpp-backends 0.0.1-alpha.0 → 0.0.2-alpha
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.
- package/dist/SwppConfig.js +2 -0
- package/dist/UpdateJsonBuilder.js +281 -0
- package/dist/VersionAnalyzer.js +62 -0
- package/dist/fileAnalyzer.js +325 -76
- package/dist/index.js +32 -25
- package/dist/resources/sw-template.js +15 -23
- package/dist/serviceWorkerBuilder.js +18 -10
- package/dist/swppRules.js +34 -16
- package/dist/utils.js +42 -14
- package/package.json +36 -21
- package/{src/SwppConfig.ts → types/SwppConfig.d.ts} +125 -125
- package/types/UpdateJsonBuilder.d.ts +50 -0
- package/types/VersionAnalyzer.d.ts +29 -0
- package/types/fileAnalyzer.d.ts +149 -0
- package/types/index.d.ts +55 -0
- package/types/serviceWorkerBuilder.d.ts +7 -0
- package/types/swppRules.d.ts +101 -0
- package/types/utils.d.ts +41 -0
- package/gulpfile.js +0 -3
- package/src/FileAnalyzer.ts +0 -417
- package/src/ServiceWorkerBuilder.ts +0 -156
- package/src/SwppRules.ts +0 -200
- package/src/UpdateJsonBuilder.ts +0 -279
- package/src/Utils.ts +0 -195
- package/src/VersionAnalyzer.ts +0 -77
- package/src/index.ts +0 -64
- package/src/resources/sw-dom.js +0 -51
- package/src/resources/sw-template.js +0 -257
- package/tsconfig.json +0 -109
package/src/FileAnalyzer.ts
DELETED
|
@@ -1,417 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import nodePath from 'path'
|
|
3
|
-
import {Request} from 'node-fetch'
|
|
4
|
-
import {readRules} from './SwppRules'
|
|
5
|
-
import * as crypto from 'crypto'
|
|
6
|
-
import {Buffer} from 'buffer'
|
|
7
|
-
import HTMLParser from 'fast-html-parser'
|
|
8
|
-
import CSSParser from 'css'
|
|
9
|
-
import {fetchFile, readEjectData} from './Utils'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 版本信息(可以用 JSON 序列化)
|
|
13
|
-
* @see VersionMap
|
|
14
|
-
*/
|
|
15
|
-
export interface VersionJson {
|
|
16
|
-
version: number,
|
|
17
|
-
list: VersionMap
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 版本列表
|
|
22
|
-
*
|
|
23
|
-
* + key 为文件的 URL
|
|
24
|
-
* + value {string} 为 URL 对应文件的 md5 值
|
|
25
|
-
* + value {string[]} 为 stable 文件其中包含的 URL
|
|
26
|
-
*/
|
|
27
|
-
export interface VersionMap {
|
|
28
|
-
[propName: string]: any
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 遍历指定目录及其子目录中包含的所有文件(不遍历文件夹)
|
|
33
|
-
* @param root 根目录
|
|
34
|
-
* @param cb 回调函数(接收的参数是文件的相对路径)
|
|
35
|
-
*/
|
|
36
|
-
function eachAllFile(root: string, cb: (path: string) => void) {
|
|
37
|
-
const stats = fs.statSync(root)
|
|
38
|
-
if (stats.isFile()) cb(root)
|
|
39
|
-
else {
|
|
40
|
-
const files = fs.readdirSync(root)
|
|
41
|
-
files.forEach(it => eachAllFile(nodePath.join(root, it), cb))
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* 判断指定 URL 是否排除
|
|
47
|
-
*
|
|
48
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
49
|
-
*
|
|
50
|
-
* @param webRoot 网站域名
|
|
51
|
-
* @param url 要判断的 URL
|
|
52
|
-
*/
|
|
53
|
-
export function isExclude(webRoot: string, url: string): boolean {
|
|
54
|
-
const exclude = readRules().config?.json?.exclude
|
|
55
|
-
if (!exclude) throw 'exclude 为空'
|
|
56
|
-
const list = isExternalLink(webRoot, url) ? exclude.other : exclude.localhost
|
|
57
|
-
for (let reg of list) {
|
|
58
|
-
if (url.match(reg)) return true
|
|
59
|
-
}
|
|
60
|
-
return false
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* 判断指定 URL 是否是 stable 的
|
|
65
|
-
*
|
|
66
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
67
|
-
*/
|
|
68
|
-
export function isStable(url: string): boolean {
|
|
69
|
-
const stable = readRules().config?.external?.stable
|
|
70
|
-
if (!stable) throw 'stable 为空'
|
|
71
|
-
for (let reg of stable) {
|
|
72
|
-
if (url.match(reg)) return true
|
|
73
|
-
}
|
|
74
|
-
return false
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 从指定 URL 加载 cache json
|
|
79
|
-
*
|
|
80
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
81
|
-
*/
|
|
82
|
-
export async function loadVersionJson(url: string): Promise<VersionJson> {
|
|
83
|
-
const response = await fetchFile(url)
|
|
84
|
-
return _oldVersionJson = (await response.json()) as VersionJson
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let _oldVersionJson: VersionJson
|
|
88
|
-
let _newVersionJson: VersionJson
|
|
89
|
-
let _mergeVersionMap: VersionMap
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* 读取最后一次加载的 version json
|
|
93
|
-
*
|
|
94
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
95
|
-
* + **调用该函数前必须调用过 [loadCacheJson]**
|
|
96
|
-
*/
|
|
97
|
-
export function readOldVersionJson(): VersionJson {
|
|
98
|
-
if (!_oldVersionJson) throw 'cache json 尚未初始化'
|
|
99
|
-
return _oldVersionJson
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* 读取最后一次构建的 VersionJson
|
|
104
|
-
*
|
|
105
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
106
|
-
* + **调用该函数前必须调用过 [loadCacheJson]**
|
|
107
|
-
* + **执行该函数前必须调用过 [buildVersionJson]**
|
|
108
|
-
* + **执行该函数前必须调用过 [calcEjectValues]**
|
|
109
|
-
*/
|
|
110
|
-
export function readNewVersionJson(): VersionJson {
|
|
111
|
-
if (!_newVersionJson) throw 'cache json 尚未初始化'
|
|
112
|
-
return _newVersionJson
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* 读取新旧版本文件合并后的版本地图
|
|
117
|
-
*
|
|
118
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
119
|
-
* + **调用该函数前必须调用过 [loadCacheJson]**
|
|
120
|
-
* + **执行该函数前必须调用过 [buildVersionJson]**
|
|
121
|
-
* + **执行该函数前必须调用过 [calcEjectValues]**
|
|
122
|
-
*/
|
|
123
|
-
export function readMergeVersionMap(): VersionMap {
|
|
124
|
-
if (_mergeVersionMap) return _mergeVersionMap
|
|
125
|
-
const map: VersionMap = {}
|
|
126
|
-
Object.assign(map, readOldVersionJson().list)
|
|
127
|
-
Object.assign(map, readNewVersionJson().list)
|
|
128
|
-
return _mergeVersionMap = map
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* 构建一个 cache json
|
|
133
|
-
*
|
|
134
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
135
|
-
* + **调用该函数前必须调用过 [loadCacheJson]**
|
|
136
|
-
* + **执行该函数前必须调用过 [calcEjectValues]**
|
|
137
|
-
*
|
|
138
|
-
* @param protocol 网站的网络协议
|
|
139
|
-
* @param webRoot 网站域名(包括二级域名)
|
|
140
|
-
* @param root 网页根目录(首页 index.html 所在目录)
|
|
141
|
-
*/
|
|
142
|
-
export async function buildVersionJson(
|
|
143
|
-
protocol: ('https://' | 'http://'), webRoot: string, root: string
|
|
144
|
-
): Promise<VersionJson> {
|
|
145
|
-
const list: VersionMap = {}
|
|
146
|
-
eachAllFile(root, async path => {
|
|
147
|
-
const endIndex = path.length - (path.endsWith('/index.html') ? 10 : 0)
|
|
148
|
-
const url = new URL(protocol + nodePath.join(webRoot, path.substring(root.length, endIndex)))
|
|
149
|
-
const pathname = url.pathname
|
|
150
|
-
if (isExclude(webRoot, pathname)) return
|
|
151
|
-
let content = null
|
|
152
|
-
if (findCache(url)) {
|
|
153
|
-
content = fs.readFileSync(path, 'utf-8')
|
|
154
|
-
const key = decodeURIComponent(url.pathname)
|
|
155
|
-
list[key] = crypto.createHash('md5').update(content).digest('hex')
|
|
156
|
-
}
|
|
157
|
-
if (pathname.endsWith('/') || pathname.endsWith('.html')) {
|
|
158
|
-
if (!content) content = fs.readFileSync(path, 'utf-8')
|
|
159
|
-
await eachAllLinkInHtml(webRoot, content, list)
|
|
160
|
-
} else if (pathname.endsWith('.css')) {
|
|
161
|
-
if (!content) content = fs.readFileSync(path, 'utf-8')
|
|
162
|
-
await eachAllLinkInCss(webRoot, content, list)
|
|
163
|
-
} else if (pathname.endsWith('.js')) {
|
|
164
|
-
if (!content) content = fs.readFileSync(path, 'utf-8')
|
|
165
|
-
await eachAllLinkInJavaScript(webRoot, content, list)
|
|
166
|
-
}
|
|
167
|
-
})
|
|
168
|
-
return _newVersionJson = {
|
|
169
|
-
version: 3, list
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* 检索一个 URL 指向的文件中所有地外部链接
|
|
175
|
-
*
|
|
176
|
-
* 该函数会处理该 URL 指向的文件和文件中直接或间接包含的所有 URL
|
|
177
|
-
*
|
|
178
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
179
|
-
* + **调用该函数前必须调用过 [loadCacheJson]**
|
|
180
|
-
* + **执行该函数前必须调用过 [calcEjectValues]**
|
|
181
|
-
*
|
|
182
|
-
* @param webRoot 网站域名
|
|
183
|
-
* @param url 要检索的 URL
|
|
184
|
-
* @param result 存放结果的对象
|
|
185
|
-
* @param event 检索到一个 URL 时触发的事件
|
|
186
|
-
*/
|
|
187
|
-
export async function eachAllLinkInUrl(
|
|
188
|
-
webRoot: string, url: string, result: VersionMap, event?: (url: string) => void
|
|
189
|
-
) {
|
|
190
|
-
if (url.startsWith('//')) url = 'http' + url
|
|
191
|
-
if (url in result) return event?.(url)
|
|
192
|
-
if (!url.startsWith('http') || isExclude(webRoot, url)) return
|
|
193
|
-
if (!(isExternalLink(webRoot, url) && findCache(new URL(url)))) return
|
|
194
|
-
event?.(url)
|
|
195
|
-
const stable = isStable(url)
|
|
196
|
-
if (stable) {
|
|
197
|
-
const old = readOldVersionJson().list
|
|
198
|
-
if (url in old) {
|
|
199
|
-
const copyTree = (key: string) => {
|
|
200
|
-
const value = old[key]
|
|
201
|
-
if (!value) return
|
|
202
|
-
result[key] = value
|
|
203
|
-
if (Array.isArray(value)) {
|
|
204
|
-
result[key] = value
|
|
205
|
-
for (let url of value) {
|
|
206
|
-
copyTree(url)
|
|
207
|
-
}
|
|
208
|
-
} else {
|
|
209
|
-
event?.(value)
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
copyTree(url)
|
|
213
|
-
return
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
const response = await fetchFile(url)
|
|
217
|
-
if (![200, 301, 302, 307, 308].includes(response.status))
|
|
218
|
-
throw response
|
|
219
|
-
const pathname = new URL(url).pathname
|
|
220
|
-
let content: string | undefined
|
|
221
|
-
const relay: string[] = []
|
|
222
|
-
const nextEvent = (it: string) => {
|
|
223
|
-
relay.push(it)
|
|
224
|
-
event?.(it)
|
|
225
|
-
}
|
|
226
|
-
switch (true) {
|
|
227
|
-
case pathname.endsWith('.html'): case pathname.endsWith('/'):
|
|
228
|
-
content = await response.text()
|
|
229
|
-
await eachAllLinkInHtml(webRoot, content, result, nextEvent)
|
|
230
|
-
break
|
|
231
|
-
case pathname.endsWith('.css'):
|
|
232
|
-
content = await response.text()
|
|
233
|
-
await eachAllLinkInCss(webRoot, content, result, nextEvent)
|
|
234
|
-
break
|
|
235
|
-
case pathname.endsWith('.js'):
|
|
236
|
-
content = await response.text()
|
|
237
|
-
await eachAllLinkInJavaScript(webRoot, content, result, nextEvent)
|
|
238
|
-
break
|
|
239
|
-
default:
|
|
240
|
-
if (stable) {
|
|
241
|
-
result[url] = []
|
|
242
|
-
} else {
|
|
243
|
-
const buffer = Buffer.from(await response.arrayBuffer())
|
|
244
|
-
result[url] = crypto.createHash('md5').update(buffer).digest('hex')
|
|
245
|
-
}
|
|
246
|
-
break
|
|
247
|
-
}
|
|
248
|
-
if (content) {
|
|
249
|
-
if (stable) {
|
|
250
|
-
result[url] = relay
|
|
251
|
-
} else {
|
|
252
|
-
result[url] = crypto.createHash('md5').update(content).digest('hex')
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* 检索 HTML 文件中的所有外部链接
|
|
259
|
-
*
|
|
260
|
-
* 该函数仅处理 HTML 当中直接或间接包含的 URL,不处理文件本身
|
|
261
|
-
*
|
|
262
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
263
|
-
* + **调用该函数前必须调用过 [loadCacheJson]**
|
|
264
|
-
*
|
|
265
|
-
* @param webRoot 网站域名
|
|
266
|
-
* @param content HTML 文件内容
|
|
267
|
-
* @param result 存放结果的对象
|
|
268
|
-
* @param event 检索到 URL 时触发的事件
|
|
269
|
-
*/
|
|
270
|
-
export async function eachAllLinkInHtml(
|
|
271
|
-
webRoot: string, content: string, result: VersionMap, event?: (url: string) => void
|
|
272
|
-
) {
|
|
273
|
-
const each = async (node: HTMLParser.HTMLElement) => {
|
|
274
|
-
let url: string | undefined = undefined
|
|
275
|
-
switch (node.tagName) {
|
|
276
|
-
case 'link':
|
|
277
|
-
url = node.attributes.href
|
|
278
|
-
break
|
|
279
|
-
case 'script': case 'img': case 'source': case 'iframe': case 'embed':
|
|
280
|
-
url = node.attributes.src
|
|
281
|
-
break
|
|
282
|
-
case 'object':
|
|
283
|
-
url = node.attributes.data
|
|
284
|
-
break
|
|
285
|
-
}
|
|
286
|
-
if (url) {
|
|
287
|
-
await eachAllLinkInUrl(webRoot, url, result, event)
|
|
288
|
-
} else if (node.tagName === 'script') {
|
|
289
|
-
await eachAllLinkInJavaScript(webRoot, node.rawText, result, event)
|
|
290
|
-
} else if (node.tagName === 'style') {
|
|
291
|
-
await eachAllLinkInCss(webRoot, node.rawText, result, event)
|
|
292
|
-
}
|
|
293
|
-
for (let childNode of node.childNodes) {
|
|
294
|
-
await each(childNode)
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
await each(HTMLParser.parse(content, { style: true, script: true }))
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* 检索 CSS 文件中的所有外部链
|
|
302
|
-
*
|
|
303
|
-
* 该函数仅处理 CSS 当中直接或间接包含的 URL,不处理文件本身
|
|
304
|
-
*
|
|
305
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
306
|
-
* + **调用该函数前必须调用过 [loadCacheJson]**
|
|
307
|
-
*
|
|
308
|
-
* @param webRoot 网站域名
|
|
309
|
-
* @param content CSS 文件内容
|
|
310
|
-
* @param result 存放结果的对象
|
|
311
|
-
* @param event 当检索到一个 URL 后触发的事件
|
|
312
|
-
*/
|
|
313
|
-
export async function eachAllLinkInCss(
|
|
314
|
-
webRoot: string, content: string, result: VersionMap, event?: (url: string) => void
|
|
315
|
-
) {
|
|
316
|
-
const each = async (any: Array<any> | undefined) => {
|
|
317
|
-
if (!any) return
|
|
318
|
-
for (let rule of any) {
|
|
319
|
-
switch (rule.type) {
|
|
320
|
-
case 'rule':
|
|
321
|
-
await each(rule.declarations)
|
|
322
|
-
break
|
|
323
|
-
case 'declaration':
|
|
324
|
-
const value: string = rule.value
|
|
325
|
-
const list = value.match(/url\(['"]?([^'")]+)['"]?\)/g)
|
|
326
|
-
?.map(it => it.replace(/(^url\(['"])|(['"]\)$)/g, ''))
|
|
327
|
-
if (list) {
|
|
328
|
-
for (let url of list) {
|
|
329
|
-
await eachAllLinkInUrl(webRoot, url, result, event)
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
break
|
|
333
|
-
case 'import':
|
|
334
|
-
const url = rule.import.trim().replace(/^["']|["']$/g, '')
|
|
335
|
-
await eachAllLinkInUrl(webRoot, url, result, event)
|
|
336
|
-
break
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
await each(CSSParser.parse(content).stylesheet?.rules)
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* 遍历 JS 文件中地所有外部链接
|
|
345
|
-
*
|
|
346
|
-
* 该函数仅处理 JS 当中直接或间接包含的 URL,不处理文件本身
|
|
347
|
-
*
|
|
348
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
349
|
-
* + **调用该函数前必须调用过 [loadCacheJson]**
|
|
350
|
-
*
|
|
351
|
-
* @param webRoot 网站域名
|
|
352
|
-
* @param content JS 文件内容
|
|
353
|
-
* @param result 存放结果的对象
|
|
354
|
-
* @param event 当检索到一个 URL 后触发的事件
|
|
355
|
-
*/
|
|
356
|
-
export async function eachAllLinkInJavaScript(
|
|
357
|
-
webRoot: string, content: string, result: VersionMap, event?: (url: string) => void
|
|
358
|
-
) {
|
|
359
|
-
const ruleList = readRules().config?.external?.js
|
|
360
|
-
if (!ruleList) throw 'ruleList 为空'
|
|
361
|
-
for (let value of ruleList) {
|
|
362
|
-
if (typeof value === 'function') {
|
|
363
|
-
const urls: string[] = value(content)
|
|
364
|
-
for (let url of urls) {
|
|
365
|
-
await eachAllLinkInUrl(webRoot, url, result, event)
|
|
366
|
-
}
|
|
367
|
-
} else {
|
|
368
|
-
const {head, tail} = value
|
|
369
|
-
const reg = new RegExp(`${head}(['"\`])(.*?)(['"\`])${tail}`, 'mg')
|
|
370
|
-
const list = content.match(reg)
|
|
371
|
-
?.map(it => it.substring(head.length, it.length - tail.length).trim())
|
|
372
|
-
?.map(it => it.replace(/^['"`]|['"`]$/g, ''))
|
|
373
|
-
if (list) {
|
|
374
|
-
for (let url of list) {
|
|
375
|
-
await eachAllLinkInUrl(webRoot, url, result, event)
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/** 判断一个 URL 是否是外部链接 */
|
|
383
|
-
function isExternalLink(webRoot: string, url: string): boolean {
|
|
384
|
-
return new RegExp(`^(https?:)?\\/\\/${webRoot}`).test(url)
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* 查询指定 URL 对应的缓存规则
|
|
389
|
-
*
|
|
390
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
391
|
-
* + **执行该函数前必须调用过 [calcEjectValues]**
|
|
392
|
-
*/
|
|
393
|
-
export function findCache(url: URL | string): any | null {
|
|
394
|
-
const {cacheRules} = readRules()
|
|
395
|
-
const eject = readEjectData()
|
|
396
|
-
if (typeof url === 'string') url = new URL(url)
|
|
397
|
-
url = new URL(replaceRequest(url.href))
|
|
398
|
-
for (let key in cacheRules) {
|
|
399
|
-
const value = cacheRules[key]
|
|
400
|
-
if (value.match(url, eject.nodeEject)) return value
|
|
401
|
-
}
|
|
402
|
-
return null
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* 替换请求
|
|
407
|
-
*
|
|
408
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
409
|
-
* + **执行该函数前必须调用过 [calcEjectValues]**
|
|
410
|
-
*/
|
|
411
|
-
export function replaceRequest(url: string): string {
|
|
412
|
-
const rules = readRules()
|
|
413
|
-
if (!('modifyRequest' in rules)) return url
|
|
414
|
-
const {modifyRequest} = rules
|
|
415
|
-
const request = new Request(url)
|
|
416
|
-
return modifyRequest?.(request, readEjectData().nodeEject)?.url ?? url
|
|
417
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import {ServiceWorkerConfig} from './SwppConfig'
|
|
2
|
-
import {readRules} from './SwppRules'
|
|
3
|
-
import {getSource, readEjectData} from './Utils'
|
|
4
|
-
import fs from 'fs'
|
|
5
|
-
import nodePath from 'path'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* 构建 sw
|
|
9
|
-
*
|
|
10
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
11
|
-
* + **执行该函数前必须调用过 [calcEjectValues]**
|
|
12
|
-
*/
|
|
13
|
-
export function buildServiceWorker(): string {
|
|
14
|
-
const rules = readRules()
|
|
15
|
-
const eject = readEjectData()
|
|
16
|
-
const {
|
|
17
|
-
modifyRequest,
|
|
18
|
-
fetchFile,
|
|
19
|
-
getRaceUrls,
|
|
20
|
-
getSpareUrls,
|
|
21
|
-
blockRequest,
|
|
22
|
-
config
|
|
23
|
-
} = rules
|
|
24
|
-
const serviceWorkerConfig = config.serviceWorker as ServiceWorkerConfig
|
|
25
|
-
const templatePath = nodePath.resolve('./', module.path, 'sw-template.js')
|
|
26
|
-
// 获取拓展文件
|
|
27
|
-
let cache = getSource(rules, undefined, [
|
|
28
|
-
'cacheList', 'modifyRequest', 'getCdnList', 'getSpareUrls', 'blockRequest', 'fetchFile',
|
|
29
|
-
...('external' in rules && Array.isArray(rules.external) ? rules.external : [])
|
|
30
|
-
], true) + '\n'
|
|
31
|
-
if (!fetchFile) {
|
|
32
|
-
if (getRaceUrls)
|
|
33
|
-
cache += JS_CODE_GET_CDN_LIST
|
|
34
|
-
else if (getSpareUrls)
|
|
35
|
-
cache += JS_CODE_GET_SPARE_URLS
|
|
36
|
-
else
|
|
37
|
-
cache += JS_CODE_DEF_FETCH_FILE
|
|
38
|
-
}
|
|
39
|
-
if (!getSpareUrls) cache += `\nconst getSpareUrls = _ => {}`
|
|
40
|
-
if ('afterJoin' in rules)
|
|
41
|
-
cache += `(${getSource(rules['afterJoin'])})()\n`
|
|
42
|
-
if ('afterTheme' in rules)
|
|
43
|
-
cache += `(${getSource(rules['afterTheme'])})()\n`
|
|
44
|
-
const keyword = "const { cacheList, fetchFile, getSpareUrls } = require('../sw-rules')"
|
|
45
|
-
// noinspection JSUnresolvedVariable
|
|
46
|
-
let content = fs.readFileSync(templatePath, 'utf8')
|
|
47
|
-
.replaceAll("// [insertion site] values", eject.strValue ?? '')
|
|
48
|
-
.replaceAll(keyword, cache)
|
|
49
|
-
.replaceAll("'@$$[escape]'", (serviceWorkerConfig.escape).toString())
|
|
50
|
-
.replaceAll("'@$$[cacheName]'", `'${serviceWorkerConfig.cacheName}'`)
|
|
51
|
-
if (modifyRequest) {
|
|
52
|
-
content = content.replaceAll('// [modifyRequest call]', `
|
|
53
|
-
const modify = modifyRequest(request)
|
|
54
|
-
if (modify) request = modify
|
|
55
|
-
`).replaceAll('// [modifyRequest else-if]', `
|
|
56
|
-
else if (modify) event.respondWith(fetch(request))
|
|
57
|
-
`)
|
|
58
|
-
}
|
|
59
|
-
if (blockRequest) {
|
|
60
|
-
content = content.replace('// [blockRequest call]', `
|
|
61
|
-
if (blockRequest(url))
|
|
62
|
-
return event.respondWith(new Response(null, {status: 208}))
|
|
63
|
-
`)
|
|
64
|
-
}
|
|
65
|
-
// noinspection JSUnresolvedVariable
|
|
66
|
-
if (serviceWorkerConfig.debug) {
|
|
67
|
-
content = content.replaceAll('// [debug delete]', `
|
|
68
|
-
console.debug(\`delete cache: \${url}\`)
|
|
69
|
-
`).replaceAll('// [debug put]', `
|
|
70
|
-
console.debug(\`put cache: \${key}\`)
|
|
71
|
-
`).replaceAll('// [debug message]', `
|
|
72
|
-
console.debug(\`receive: \${event.data}\`)
|
|
73
|
-
`).replaceAll('// [debug escape]', `
|
|
74
|
-
console.debug(\`escape: \${aid}\`)
|
|
75
|
-
`)
|
|
76
|
-
}
|
|
77
|
-
return content
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 缺省的 fetchFile 函数的代码
|
|
81
|
-
const JS_CODE_DEF_FETCH_FILE = `
|
|
82
|
-
const fetchFile = (request, banCache) => fetch(request, {
|
|
83
|
-
cache: banCache ? "no-store" : "default",
|
|
84
|
-
mode: 'cors',
|
|
85
|
-
credentials: 'same-origin'
|
|
86
|
-
})
|
|
87
|
-
`
|
|
88
|
-
|
|
89
|
-
// getCdnList 函数的代码
|
|
90
|
-
const JS_CODE_GET_CDN_LIST = `
|
|
91
|
-
const fetchFile = (request, banCache) => {
|
|
92
|
-
const fetchArgs = {
|
|
93
|
-
cache: banCache ? 'no-store' : 'default',
|
|
94
|
-
mode: 'cors',
|
|
95
|
-
credentials: 'same-origin'
|
|
96
|
-
}
|
|
97
|
-
const list = getCdnList(request.url)
|
|
98
|
-
if (!list || !Promise.any) return fetch(request, fetchArgs)
|
|
99
|
-
const res = list.map(url => new Request(url, request))
|
|
100
|
-
const controllers = []
|
|
101
|
-
return Promise.any(res.map(
|
|
102
|
-
(it, index) => fetch(it, Object.assign(
|
|
103
|
-
{signal: (controllers[index] = new AbortController()).signal},
|
|
104
|
-
fetchArgs
|
|
105
|
-
)).then(response => checkResponse(response) ? {index, response} : Promise.reject())
|
|
106
|
-
)).then(it => {
|
|
107
|
-
for (let i in controllers) {
|
|
108
|
-
if (i != it.index) controllers[i].abort()
|
|
109
|
-
}
|
|
110
|
-
return it.response
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
`
|
|
114
|
-
|
|
115
|
-
// getSpareUrls 函数的代码
|
|
116
|
-
const JS_CODE_GET_SPARE_URLS = `
|
|
117
|
-
const fetchFile = (request, banCache, spare = null) => {
|
|
118
|
-
const fetchArgs = {
|
|
119
|
-
cache: banCache ? 'no-store' : 'default',
|
|
120
|
-
mode: 'cors',
|
|
121
|
-
credentials: 'same-origin'
|
|
122
|
-
}
|
|
123
|
-
if (!spare) spare = getSpareUrls(request.url)
|
|
124
|
-
if (!spare) return fetch(request, fetchArgs)
|
|
125
|
-
const list = spare.list
|
|
126
|
-
const controllers = []
|
|
127
|
-
let error = 0
|
|
128
|
-
return new Promise((resolve, reject) => {
|
|
129
|
-
const pull = () => {
|
|
130
|
-
const flag = controllers.length
|
|
131
|
-
if (flag === list.length) return
|
|
132
|
-
const plusError = () => {
|
|
133
|
-
if (++error === list.length) reject(\`请求 \${request.url} 失败\`)
|
|
134
|
-
else if (flag + 1 === controllers.length) {
|
|
135
|
-
clearTimeout(controllers[flag].id)
|
|
136
|
-
pull()
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
controllers.push({
|
|
140
|
-
ctrl: new AbortController(),
|
|
141
|
-
id: setTimeout(pull, spare.timeout)
|
|
142
|
-
})
|
|
143
|
-
fetch(new Request(list[flag], request), fetchArgs).then(response => {
|
|
144
|
-
if (checkResponse(response)) {
|
|
145
|
-
for (let i in controllers) {
|
|
146
|
-
if (i !== flag) controllers[i].ctrl.abort()
|
|
147
|
-
clearTimeout(controllers[i].id)
|
|
148
|
-
}
|
|
149
|
-
resolve(response)
|
|
150
|
-
} else plusError()
|
|
151
|
-
}).catch(plusError)
|
|
152
|
-
}
|
|
153
|
-
pull()
|
|
154
|
-
})
|
|
155
|
-
}
|
|
156
|
-
`
|