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/SwppRules.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import {Request, Response} from 'node-fetch'
|
|
3
|
-
import nodePath from 'path'
|
|
4
|
-
import {SwppConfig, SwppConfigTemplate} from './SwppConfig'
|
|
5
|
-
|
|
6
|
-
const defConfig: SwppConfigTemplate = {
|
|
7
|
-
serviceWorker: {
|
|
8
|
-
escape: 0,
|
|
9
|
-
cacheName: 'kmarBlogCache',
|
|
10
|
-
debug: false
|
|
11
|
-
},
|
|
12
|
-
register: {
|
|
13
|
-
onerror: () => console.error('Service Worker 注册失败!可能是由于您的浏览器不支持该功能!'),
|
|
14
|
-
builder: (root: string, _: any, pluginConfig: SwppConfig) => {
|
|
15
|
-
const registerConfig = pluginConfig.register as any
|
|
16
|
-
const {onerror, onsuccess} = registerConfig
|
|
17
|
-
return `<script>
|
|
18
|
-
(() => {
|
|
19
|
-
let sw = navigator.serviceWorker
|
|
20
|
-
let error = ${onerror.toString()}
|
|
21
|
-
if (!sw?.register('${new URL(root).pathname}sw.js')
|
|
22
|
-
${onsuccess ? '?.then(' + onsuccess.toString() + ')' : ''}
|
|
23
|
-
?.catch(error)
|
|
24
|
-
) error()
|
|
25
|
-
})()
|
|
26
|
-
</script>`
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
dom: {},
|
|
30
|
-
json: {
|
|
31
|
-
maxHtml: 15,
|
|
32
|
-
charLimit: 1024,
|
|
33
|
-
merge: [],
|
|
34
|
-
exclude: {
|
|
35
|
-
localhost: [/^\/sw\.js$/],
|
|
36
|
-
other: []
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
external: {
|
|
40
|
-
timeout: 5000,
|
|
41
|
-
js: [],
|
|
42
|
-
stable: [],
|
|
43
|
-
replacer: it => it
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const eventList: ((rules: any) => void)[] = []
|
|
48
|
-
let _rules: SwppRules
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* 读取最后一次构建的 rules
|
|
52
|
-
*
|
|
53
|
-
* **执行该函数前必须调用过 [loadRules]**
|
|
54
|
-
*/
|
|
55
|
-
export function readRules(): SwppRules {
|
|
56
|
-
if (!_rules) throw 'rules 尚未初始化'
|
|
57
|
-
return _rules
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 添加一个 rules 映射事件,这个事件允许用户修改 rules 的内容
|
|
62
|
-
*
|
|
63
|
-
* 执行时按照注册的顺序执行
|
|
64
|
-
*/
|
|
65
|
-
export function addRulesMapEvent(mapper: (rules: any) => void) {
|
|
66
|
-
eventList.push(mapper)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 加载 rules 文件
|
|
71
|
-
* @param root 项目根目录
|
|
72
|
-
* @param fileName rules 文件名称
|
|
73
|
-
* @param selects 附加的可选目录,优先级低于 [root]
|
|
74
|
-
*/
|
|
75
|
-
export function loadRules(root: string, fileName: string, selects: string[]): SwppRules {
|
|
76
|
-
// 支持的拓展名
|
|
77
|
-
const extensions = ['cjs', 'js']
|
|
78
|
-
// 根目录下的 rules 文件
|
|
79
|
-
const rootPath = extensions.map(it => nodePath.resolve(root, `${fileName}.${it}`))
|
|
80
|
-
.find(it => fs.existsSync(it))
|
|
81
|
-
// 其它可选目录下的 rules 文件
|
|
82
|
-
const selectPath = selects.flatMap(
|
|
83
|
-
value => extensions.map(it => nodePath.resolve(value, `${fileName}.${it}`))
|
|
84
|
-
).find(it => fs.existsSync(it))
|
|
85
|
-
if (!(rootPath || selectPath))
|
|
86
|
-
throw '未查询到 rules 文件'
|
|
87
|
-
const rootRules = rootPath ? { ...require(rootPath) } : {}
|
|
88
|
-
const selectRules = selectPath ? require(selectPath) : {}
|
|
89
|
-
const config = rootRules.config ?? {}
|
|
90
|
-
mergeConfig(config, selectRules.config ?? {})
|
|
91
|
-
mergeConfig(config, defConfig)
|
|
92
|
-
Object.assign(rootRules, selectRules)
|
|
93
|
-
rootRules.config = config
|
|
94
|
-
for (let event of eventList) {
|
|
95
|
-
event(rootRules)
|
|
96
|
-
}
|
|
97
|
-
return _rules = rootRules
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** 合并配置项 */
|
|
101
|
-
function mergeConfig(dist: any, that: any): any {
|
|
102
|
-
for (let key in that) {
|
|
103
|
-
const distValue = dist[key]
|
|
104
|
-
const thatValue = that[key]
|
|
105
|
-
if (!thatValue) continue
|
|
106
|
-
switch (typeof distValue) {
|
|
107
|
-
case "undefined":
|
|
108
|
-
dist[key] = thatValue
|
|
109
|
-
break
|
|
110
|
-
case "object":
|
|
111
|
-
mergeConfig(distValue, thatValue)
|
|
112
|
-
break
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return dist
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export interface SwppRules {
|
|
119
|
-
/** 配置项 */
|
|
120
|
-
config: SwppConfig,
|
|
121
|
-
/** 缓存规则 */
|
|
122
|
-
cacheRules?: {
|
|
123
|
-
[propName: string]: CacheRules
|
|
124
|
-
},
|
|
125
|
-
/**
|
|
126
|
-
* 修改 Request
|
|
127
|
-
* @param request 原始 request
|
|
128
|
-
* @param $eject 用于访问通过 [ejectValues] 函数插入的变量,变量名必须为 `$eject`
|
|
129
|
-
* @return 修改后的 Request,不修改的话返回 null 或不返回数据
|
|
130
|
-
*/
|
|
131
|
-
modifyRequest?: (request: Request, $eject: any) => Request | undefined,
|
|
132
|
-
/**
|
|
133
|
-
* 获取一个 URL 对应的多个 CDN 的 URL
|
|
134
|
-
*
|
|
135
|
-
* 竞速时除了 URL 外所有参数保持一致
|
|
136
|
-
*
|
|
137
|
-
* @param url 原始 URL
|
|
138
|
-
* @return {?string[]} 返回值不包含则表示去除对原始 URL 地访问。返回 undefined 表示该 URL 不启用竞速
|
|
139
|
-
*/
|
|
140
|
-
getRaceUrls?: (url: string) => string[] | undefined,
|
|
141
|
-
/**
|
|
142
|
-
* 获取一个 URL 对应的 URL 列表
|
|
143
|
-
*
|
|
144
|
-
* 访问顺序按列表顺序,所有 URL 访问时参数一致
|
|
145
|
-
*
|
|
146
|
-
* @param url 原始 URL
|
|
147
|
-
* @return {?SpareURLs} 返回 null 或不反悔表示对该 URL 不启用备用 URL 功能
|
|
148
|
-
*/
|
|
149
|
-
getSpareUrls?: (url: string) => SpareURLs | undefined,
|
|
150
|
-
/**
|
|
151
|
-
* 判断是否阻塞指定响应
|
|
152
|
-
* @return {boolean} 返回 true 表示阻塞,false 表示不阻塞
|
|
153
|
-
*/
|
|
154
|
-
blockRequest?: (url: URL) => boolean,
|
|
155
|
-
/** 插入到 sw 但不在 node 中执行的代码 */
|
|
156
|
-
afterJoin?: VoidFunction,
|
|
157
|
-
/** 插入到 sw 但不在 node 中执行的代码,框架主题禁止覆盖该项 */
|
|
158
|
-
afterTheme?: VoidFunction,
|
|
159
|
-
/** 获取要插入到 sw 中的变量和常量 */
|
|
160
|
-
ejectValues?: (framework: any, rules: SwppRules) => {
|
|
161
|
-
[propName: string]: EjectValue
|
|
162
|
-
},
|
|
163
|
-
/** 允许插入到 sw 的值 */
|
|
164
|
-
external?: string[],
|
|
165
|
-
/**
|
|
166
|
-
* 向指定的 request 发起网络请求(GET)
|
|
167
|
-
*
|
|
168
|
-
* **注意:声明该项后 swpp 内置的“CDN 竞速”“备用 URL”都将失效**
|
|
169
|
-
*
|
|
170
|
-
* @param request 请求信息
|
|
171
|
-
* @param banCache 是否禁用缓存
|
|
172
|
-
* @param spare 备用 URL
|
|
173
|
-
*/
|
|
174
|
-
fetchFile?: (request: Request, banCache: boolean, spare?: SpareURLs) => Promise<Response>
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export interface CacheRules {
|
|
178
|
-
/** 符合该规则的缓存在进行全局清理时是否清除 */
|
|
179
|
-
clean: boolean,
|
|
180
|
-
/** 是否检查 URL 参数(问号及问号之后的内容) */
|
|
181
|
-
search?: boolean,
|
|
182
|
-
/**
|
|
183
|
-
* 规则匹配器
|
|
184
|
-
* @param url 链接的 URL 对象(对象包括 hash 和 search,但禁止使用 hash,search 为 false 或留空时禁止使用 search)
|
|
185
|
-
* @param $eject 用于访问通过 [ejectValues] 函数插入的变量,变量名必须为 `$eject`
|
|
186
|
-
*/
|
|
187
|
-
match: (url: URL, $eject?: any) => boolean
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export interface SpareURLs {
|
|
191
|
-
/** 超时时间 */
|
|
192
|
-
timeout: number,
|
|
193
|
-
/** URL 列表 */
|
|
194
|
-
list: string[]
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export interface EjectValue {
|
|
198
|
-
prefix: string,
|
|
199
|
-
value: string | number | boolean | bigint | object | string[] | number[] | boolean[] | bigint[] | object[]
|
|
200
|
-
}
|
package/src/UpdateJsonBuilder.ts
DELETED
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
import {readMergeVersionMap} from './FileAnalyzer'
|
|
2
|
-
import {readRules} from './SwppRules'
|
|
3
|
-
import {fetchFile, warn} from './Utils'
|
|
4
|
-
import {AnalyzerResult} from './VersionAnalyzer'
|
|
5
|
-
|
|
6
|
-
let _oldJson: UpdateJson | undefined | null = undefined
|
|
7
|
-
const externalChange: ChangeExpression[] = []
|
|
8
|
-
|
|
9
|
-
/** 提交修改 */
|
|
10
|
-
export function submitChange(...change: ChangeExpression[]) {
|
|
11
|
-
externalChange.push(...change)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 加载版本文件
|
|
16
|
-
*
|
|
17
|
-
* + **调用该函数前必须调用过 [loadRules]**
|
|
18
|
-
*/
|
|
19
|
-
export async function loadUpdateJson(url: string): Promise<UpdateJson | null> {
|
|
20
|
-
if (_oldJson !== undefined) return _oldJson
|
|
21
|
-
const response = await fetchFile(url)
|
|
22
|
-
if (response.status === 404) {
|
|
23
|
-
warn('LoadUpdateJson', `拉取 ${url} 时出现 404 错误,如果您是第一次构建请忽略这个警告。`)
|
|
24
|
-
return _oldJson = null
|
|
25
|
-
}
|
|
26
|
-
return _oldJson = (await response.json()) as UpdateJson
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* 读取最后一次加载的版本文件
|
|
31
|
-
*
|
|
32
|
-
* + **调用该函数前必须调用过 [loadRules]**
|
|
33
|
-
* + **调用该函数前必须调用过 [loadUpdateJson]**
|
|
34
|
-
*/
|
|
35
|
-
export function readUpdateJson(): UpdateJson | null {
|
|
36
|
-
if (_oldJson === undefined) throw 'UpdateJson 未初始化'
|
|
37
|
-
return _oldJson
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* 构建新的 update json
|
|
42
|
-
*
|
|
43
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
44
|
-
* + **调用该函数前必须调用过 [loadCacheJson]**
|
|
45
|
-
* + **执行该函数前必须调用过 [buildVersionJson]**
|
|
46
|
-
* + **执行该函数前必须调用过 [calcEjectValues]**
|
|
47
|
-
*
|
|
48
|
-
* @param root 网站根路径(包括网络协议)
|
|
49
|
-
* @param dif 网站文件变化
|
|
50
|
-
*/
|
|
51
|
-
export function buildNewInfo(root: string, dif: AnalyzerResult): UpdateJson {
|
|
52
|
-
const config = readRules().config.json
|
|
53
|
-
if (!config) throw '功能未开启'
|
|
54
|
-
const old = readUpdateJson()
|
|
55
|
-
let global = old?.global ?? 0
|
|
56
|
-
if (dif.force) return {
|
|
57
|
-
global: global + 1,
|
|
58
|
-
info: [{
|
|
59
|
-
version: old ? old.info[0].version + 1 : 0
|
|
60
|
-
}]
|
|
61
|
-
}
|
|
62
|
-
const change: ChangeExpression[] = []
|
|
63
|
-
const info: UpdateVersionInfo = {
|
|
64
|
-
version: old ? old.info[0].version + 1 : 0,
|
|
65
|
-
change
|
|
66
|
-
}
|
|
67
|
-
const list = [...dif.refresh, ...dif.deleted, ...dif.variational, ...dif.rules.remove]
|
|
68
|
-
const records = {
|
|
69
|
-
merge: new Set<string>(),
|
|
70
|
-
html: new Set<string>()
|
|
71
|
-
}
|
|
72
|
-
for (let url of list) {
|
|
73
|
-
if (url.startsWith('/')) {
|
|
74
|
-
// 本地链接
|
|
75
|
-
const merge = config.merge.find(it => url.startsWith(`/${it}/`))
|
|
76
|
-
if (merge) {
|
|
77
|
-
records.merge.add(merge)
|
|
78
|
-
continue
|
|
79
|
-
}
|
|
80
|
-
url = root + url
|
|
81
|
-
}
|
|
82
|
-
if (/(\/|\.html)$/.test(url)) { // is html
|
|
83
|
-
records.html.add(getShorthand(url, 1))
|
|
84
|
-
} else { // not html
|
|
85
|
-
change.push({
|
|
86
|
-
flag: 'end',
|
|
87
|
-
value: getShorthand(url)
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (records.html.size !== 0) {
|
|
92
|
-
if (records.html.size > config.maxHtml) {
|
|
93
|
-
change.push({flag: 'html'})
|
|
94
|
-
} else {
|
|
95
|
-
change.push({
|
|
96
|
-
flag: 'end',
|
|
97
|
-
value: Array.from(records.html)
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (records.merge.size !== 0) {
|
|
102
|
-
change.push({
|
|
103
|
-
flag: 'begin',
|
|
104
|
-
value: Array.from(records.merge).map(it => `/${it}/`)
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
change.push(...externalChange)
|
|
108
|
-
return zipJson({
|
|
109
|
-
global,
|
|
110
|
-
info: [info, ...(old?.info ?? [])]
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function zipJson(json: UpdateJson): UpdateJson {
|
|
115
|
-
const record = new Map<FlagStr, string[]>()
|
|
116
|
-
/** 合并同名项目 */
|
|
117
|
-
function merge(info: UpdateVersionInfo) {
|
|
118
|
-
const localRecord = new Map<FlagStr, Set<string>>()
|
|
119
|
-
if (!info.change) return
|
|
120
|
-
for (let exp of info.change) {
|
|
121
|
-
const value = exp.value
|
|
122
|
-
if (!localRecord.has(exp.flag))
|
|
123
|
-
localRecord.set(exp.flag, new Set())
|
|
124
|
-
if (!value) continue
|
|
125
|
-
const set = localRecord.get(exp.flag)!
|
|
126
|
-
if (typeof value === 'string') {
|
|
127
|
-
set.add(value)
|
|
128
|
-
} else {
|
|
129
|
-
value.forEach(it => set.add(it))
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
type FunResult = [FlagStr, string[]]
|
|
133
|
-
info.change = Array.from(localRecord)
|
|
134
|
-
.map(it => {
|
|
135
|
-
if (it[0] === 'html') return [it[0], []] as FunResult
|
|
136
|
-
const values = Array.from(it[1])
|
|
137
|
-
if (it[0] === 'str' || it[0] === 'reg') return [it[0], values] as FunResult
|
|
138
|
-
const filtered = values.filter((value, index) => {
|
|
139
|
-
if (it[0] === 'end' && record.has('html') && /(\/|\.html)$/.test(value))
|
|
140
|
-
return false
|
|
141
|
-
for (let i = 0; i < values.length; i++) {
|
|
142
|
-
if (i === index) continue
|
|
143
|
-
const that = values[i]
|
|
144
|
-
switch (it[0]) {
|
|
145
|
-
case 'end':
|
|
146
|
-
if (value.endsWith(that)) return false
|
|
147
|
-
break
|
|
148
|
-
case 'begin':
|
|
149
|
-
if (value.startsWith(that)) return false
|
|
150
|
-
break
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return true
|
|
154
|
-
})
|
|
155
|
-
return [it[0], filtered] as FunResult
|
|
156
|
-
}).filter(it => it[1].length !== 0 || it[0] === 'html')
|
|
157
|
-
.map(it => {
|
|
158
|
-
record.set(it[0], it[1])
|
|
159
|
-
if (it[1].length === 0) return { flag: it[0] }
|
|
160
|
-
return {
|
|
161
|
-
flag: it[0],
|
|
162
|
-
value: it[1].length === 1 ? it[1][0] : it[1]
|
|
163
|
-
}
|
|
164
|
-
})
|
|
165
|
-
}
|
|
166
|
-
/** 移除不可达的表达式 */
|
|
167
|
-
function deleteUnreachableExp(list: UpdateVersionInfo[]) {
|
|
168
|
-
for (let i = list.length - 1; i > 0; i--) {
|
|
169
|
-
const info = list[i]
|
|
170
|
-
let change = info.change
|
|
171
|
-
if (!change) continue
|
|
172
|
-
for (let k = 0; k < change.length; k++) {
|
|
173
|
-
const exp = change[k]
|
|
174
|
-
const top = record.get(exp.flag)
|
|
175
|
-
if (exp.flag === 'html') {
|
|
176
|
-
change.splice(k--, 1)
|
|
177
|
-
continue
|
|
178
|
-
}
|
|
179
|
-
let array = typeof exp.value === 'string' ? [exp.value] : exp.value!
|
|
180
|
-
const find = (test: (it: string) => boolean) => {
|
|
181
|
-
if (!top) return false
|
|
182
|
-
return top.find(test)
|
|
183
|
-
}
|
|
184
|
-
switch (exp.flag) {
|
|
185
|
-
case 'end':
|
|
186
|
-
array = array.filter(value => {
|
|
187
|
-
if (/(\/|\.html)$/.test(value) && record.has('html'))
|
|
188
|
-
return false
|
|
189
|
-
if (!top) return true
|
|
190
|
-
return !find(it => value.endsWith(it))
|
|
191
|
-
})
|
|
192
|
-
break
|
|
193
|
-
case 'begin':
|
|
194
|
-
array = array.filter(value => !find(it => value.startsWith(it)))
|
|
195
|
-
break
|
|
196
|
-
case 'str':
|
|
197
|
-
array = array.filter(value => !find(it => value.includes(it)))
|
|
198
|
-
break
|
|
199
|
-
case 'reg':
|
|
200
|
-
array = array.filter(value => !top?.includes(value))
|
|
201
|
-
break
|
|
202
|
-
}
|
|
203
|
-
switch (array.length) {
|
|
204
|
-
case 0:
|
|
205
|
-
change.splice(k--, 1)
|
|
206
|
-
break
|
|
207
|
-
case 1:
|
|
208
|
-
exp.value = array[0]
|
|
209
|
-
break
|
|
210
|
-
default:
|
|
211
|
-
exp.value = array
|
|
212
|
-
break
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (change.length === 0)
|
|
216
|
-
delete info.change
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
merge(json.info[0])
|
|
220
|
-
deleteUnreachableExp(json.info)
|
|
221
|
-
return json
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* 获取 URL 的缩写形式
|
|
226
|
-
*
|
|
227
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
228
|
-
* + **调用该函数前必须调用过 [loadCacheJson]**
|
|
229
|
-
* + **执行该函数前必须调用过 [buildVersionJson]**
|
|
230
|
-
* + **执行该函数前必须调用过 [calcEjectValues]**
|
|
231
|
-
*/
|
|
232
|
-
export function getShorthand(url: string, offset: number = 0): string {
|
|
233
|
-
const map = readMergeVersionMap()
|
|
234
|
-
let collide = new Set<string>()
|
|
235
|
-
for (let mapKey in map) {
|
|
236
|
-
collide.add(mapKey)
|
|
237
|
-
}
|
|
238
|
-
let index = Math.max(url.lastIndexOf('/', url.length - offset - 1), url.length - 20)
|
|
239
|
-
let result: string
|
|
240
|
-
while (true) {
|
|
241
|
-
result = url.substring(index)
|
|
242
|
-
let count = 0
|
|
243
|
-
const removeSet = new Set<string>()
|
|
244
|
-
for (let url of collide) {
|
|
245
|
-
if (url.endsWith(result)) {
|
|
246
|
-
++count
|
|
247
|
-
if (count === 2) break
|
|
248
|
-
} else {
|
|
249
|
-
removeSet.add(url)
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
switch (count) {
|
|
253
|
-
case 1: return result
|
|
254
|
-
case 2:
|
|
255
|
-
--index
|
|
256
|
-
removeSet.forEach(it => collide.delete(it))
|
|
257
|
-
break
|
|
258
|
-
default:
|
|
259
|
-
throw '意料之外的错误:' + count
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export interface UpdateJson {
|
|
265
|
-
global: number,
|
|
266
|
-
info: UpdateVersionInfo[]
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export interface UpdateVersionInfo {
|
|
270
|
-
version: number,
|
|
271
|
-
change?: ChangeExpression[]
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export interface ChangeExpression {
|
|
275
|
-
flag: FlagStr,
|
|
276
|
-
value?: string | string[]
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export type FlagStr = 'html' | 'end' | 'begin' | 'str' | 'reg'
|
package/src/Utils.ts
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import fetch from 'node-fetch'
|
|
2
|
-
import {readRules} from './SwppRules'
|
|
3
|
-
|
|
4
|
-
const logger = require('hexo-log').default({
|
|
5
|
-
debug: false,
|
|
6
|
-
silent: false
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
export function error(type: string, message: string) {
|
|
10
|
-
logger.error(`[SWPP ${type}] ${message}`)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function warn(type: string, message: string) {
|
|
14
|
-
logger.warn(`[SWPP ${type}] ${message}`)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface EjectCache {
|
|
18
|
-
strValue: string,
|
|
19
|
-
nodeEject: any
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let ejectData: EjectCache | undefined = undefined
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 获取 eject values
|
|
26
|
-
*
|
|
27
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
28
|
-
*
|
|
29
|
-
* @param framework 框架对象
|
|
30
|
-
*/
|
|
31
|
-
export function calcEjectValues(framework: any) {
|
|
32
|
-
const rules = readRules()
|
|
33
|
-
if (!('ejectValues' in rules)) return
|
|
34
|
-
// noinspection JSUnresolvedReference
|
|
35
|
-
const eject = rules.ejectValues?.(framework, rules)
|
|
36
|
-
const nodeEject: any = {}
|
|
37
|
-
let ejectStr = ''
|
|
38
|
-
for (let key in eject) {
|
|
39
|
-
if (!key.match(/^[A-Za-z0-9]+$/)) {
|
|
40
|
-
logger.error(`[SWPP EjectValues] 变量名 [${key}] 仅允许包含英文字母和阿拉伯数字!`)
|
|
41
|
-
throw '变量名违规:' + key
|
|
42
|
-
}
|
|
43
|
-
const data = eject[key]
|
|
44
|
-
const str = getSource(data.value, name => {
|
|
45
|
-
if (['string', 'number', 'boolean', 'object', 'array', 'bigint'].includes(name))
|
|
46
|
-
return true
|
|
47
|
-
logger.error(`[SWPP EjectValue] 不支持导出 ${name} 类型的数据`)
|
|
48
|
-
throw `不支持的键值:key=${key}, value type=${name}`
|
|
49
|
-
})
|
|
50
|
-
ejectStr += ` ${data.prefix} ${key} = ${str}\n`
|
|
51
|
-
nodeEject[key] = data.value
|
|
52
|
-
}
|
|
53
|
-
ejectData = {
|
|
54
|
-
strValue: ejectStr,
|
|
55
|
-
nodeEject
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 读取最近的已计算的 eject 数据
|
|
61
|
-
*
|
|
62
|
-
* + **执行该函数前必须调用过 [loadRules]**
|
|
63
|
-
* + **执行该函数前必须调用过 [calcEjectValues]**
|
|
64
|
-
*/
|
|
65
|
-
export function readEjectData(): EjectCache {
|
|
66
|
-
if (!ejectData) throw 'eject data 尚未初始化'
|
|
67
|
-
return ejectData
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 获取指定值的 js 源码表达形式
|
|
72
|
-
* @param obj 要转换的对象
|
|
73
|
-
* @param typeChecker 类型检查器,用于筛除不希望映射的类型
|
|
74
|
-
* @param whiteList 白名单,当 obj 为 Object 时将只转换在白名单中的值(不会传递)
|
|
75
|
-
* @param isTop 是否为顶层元素,为 true 且 obj 为 Object 时将去除最外层的大括号,改为 let(不会传递)
|
|
76
|
-
*/
|
|
77
|
-
export function getSource(
|
|
78
|
-
obj: any,
|
|
79
|
-
typeChecker: ((name: string) => boolean) | undefined = undefined,
|
|
80
|
-
whiteList: string[] | undefined = undefined,
|
|
81
|
-
isTop: boolean = false
|
|
82
|
-
): string {
|
|
83
|
-
const type = typeof obj
|
|
84
|
-
if (typeChecker) {
|
|
85
|
-
let value
|
|
86
|
-
if (type === 'object') {
|
|
87
|
-
value = Array.isArray(obj) ? 'array' : type
|
|
88
|
-
} else value = type
|
|
89
|
-
if (!typeChecker(value)) return ''
|
|
90
|
-
}
|
|
91
|
-
switch (type) {
|
|
92
|
-
case "undefined": return 'undefined'
|
|
93
|
-
case "object":
|
|
94
|
-
if (Array.isArray(obj)) {
|
|
95
|
-
return '[' + (obj as Array<any>).map(it => getSource(it)).join(', ') + ']'
|
|
96
|
-
} else {
|
|
97
|
-
let result = isTop ? '' : '{\n'
|
|
98
|
-
result += Object.getOwnPropertyNames(obj)
|
|
99
|
-
.filter(key => !whiteList || whiteList.includes(key))
|
|
100
|
-
.map(key => {
|
|
101
|
-
const value = obj[key]
|
|
102
|
-
let str = getSource(value, typeChecker)
|
|
103
|
-
if (str.length === 0) return ''
|
|
104
|
-
if (isTop && whiteList && ['cacheList', 'modifyRequest'].includes(key)) {
|
|
105
|
-
str = str
|
|
106
|
-
.replace(
|
|
107
|
-
/\(\s*(.*?)\s*,\s*\$eject\s*\)/g, "$1"
|
|
108
|
-
) // 去掉箭头函数参数表中的 $eject
|
|
109
|
-
.replaceAll(
|
|
110
|
-
/\$eject\.(\w+)/g,
|
|
111
|
-
(_, match) => `eject${match[0].toUpperCase()}${match.substring(1)}`
|
|
112
|
-
) // 将函数体中的 $eject.xxx 替换为 ejectXxx
|
|
113
|
-
}
|
|
114
|
-
return isTop ? `let ${key} = ${str}` : `${key}: ${str}`
|
|
115
|
-
})
|
|
116
|
-
.filter(it => it.length !== 0)
|
|
117
|
-
.join(isTop ? '\n' : ',\n')
|
|
118
|
-
result += isTop ? '' : '}\n'
|
|
119
|
-
return result
|
|
120
|
-
}
|
|
121
|
-
case "string":
|
|
122
|
-
if (!obj.includes("'"))
|
|
123
|
-
return `'${obj}'`
|
|
124
|
-
else if (!obj.includes('"'))
|
|
125
|
-
return `"${obj}"`
|
|
126
|
-
else if (!obj.includes('`'))
|
|
127
|
-
return `\`${obj}\``
|
|
128
|
-
else
|
|
129
|
-
return `'${(obj as string).replaceAll("'", "\\'")}'`
|
|
130
|
-
case "bigint": return `${obj.toString()}n`
|
|
131
|
-
default: return obj.toString()
|
|
132
|
-
case "symbol":
|
|
133
|
-
logger.error("[SWPP ServiceWorkerBuilder] 不支持写入 symbol 类型,请从 sw-rules.js 中移除相关内容!")
|
|
134
|
-
throw '不支持写入 symbol 类型'
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* 拉取文件
|
|
140
|
-
*
|
|
141
|
-
* **调用该函数前必须调用过 [loadRules]**
|
|
142
|
-
*/
|
|
143
|
-
export async function fetchFile(link: string) {
|
|
144
|
-
const config = readRules().config
|
|
145
|
-
const url = replaceDevRequest(link)
|
|
146
|
-
const opts = {
|
|
147
|
-
headers: {
|
|
148
|
-
referer: 'kmar-swpp',
|
|
149
|
-
'User-Agent': 'kmar-swpp'
|
|
150
|
-
},
|
|
151
|
-
timeout: (config.external as any).timeout
|
|
152
|
-
}
|
|
153
|
-
try {
|
|
154
|
-
if (typeof url === 'string') {
|
|
155
|
-
return await fetch(url, opts)
|
|
156
|
-
} else {
|
|
157
|
-
return await fetchSpeed(url)
|
|
158
|
-
}
|
|
159
|
-
} catch (err) {
|
|
160
|
-
// @ts-ignore
|
|
161
|
-
if (err.type === 'request-timeout') {
|
|
162
|
-
logger.error(
|
|
163
|
-
`[SWPP FetchFile] 拉取文件 [${link}] 时出现了超时错误,如果您构建所在的位置无法访问该资源,` +
|
|
164
|
-
"请尝试通过 DevReplace(https://kmar.top/posts/73014407/#4ea71e00)功能解决该问题。"
|
|
165
|
-
)
|
|
166
|
-
throw 'timeout'
|
|
167
|
-
}
|
|
168
|
-
throw err
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* 替换编译期的 URL(CDN 竞速)
|
|
174
|
-
*
|
|
175
|
-
* **调用该函数前必须调用过 [loadRules]**
|
|
176
|
-
*/
|
|
177
|
-
export function replaceDevRequest(link: string): string[] | string {
|
|
178
|
-
const config = readRules().config
|
|
179
|
-
return config.external?.replacer(link) ?? link
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** 通过 CDN 竞速的方式拉取文件 */
|
|
183
|
-
async function fetchSpeed(list: string[]) {
|
|
184
|
-
const controllers: AbortController[] = new Array(list.length)
|
|
185
|
-
const result = await Promise.any(
|
|
186
|
-
list.map((it, index) => fetch(it, {
|
|
187
|
-
signal: (controllers[index] = new AbortController()).signal
|
|
188
|
-
}).then(response => [200, 301, 302, 307, 308].includes(response.status) ? {index, response} : Promise.reject())
|
|
189
|
-
)
|
|
190
|
-
)
|
|
191
|
-
for (let i = 0; i < controllers.length; i++) {
|
|
192
|
-
if (i !== result.index) controllers[i].abort()
|
|
193
|
-
}
|
|
194
|
-
return result.response
|
|
195
|
-
}
|