hexo-swpp 2.8.9 → 3.0.0-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/README.md +1 -11
- package/dist/index.js +193 -0
- package/package.json +33 -34
- package/types/index.d.ts +7 -0
- package/index.js +0 -59
- package/lib/configLoader.js +0 -236
- package/lib/jsonBuilder.js +0 -531
- package/lib/sort.js +0 -35
- package/lib/sw-dom.js +0 -51
- package/lib/sw-template.js +0 -265
- package/lib/swBuilder.js +0 -193
- package/lib/utils.js +0 -50
package/lib/jsonBuilder.js
DELETED
|
@@ -1,531 +0,0 @@
|
|
|
1
|
-
module.exports = (hexo, hexoConfig, swRules, ejectValues) => {
|
|
2
|
-
const logger = require('hexo-log')()
|
|
3
|
-
const fs = require('fs')
|
|
4
|
-
const fetch = require('node-fetch')
|
|
5
|
-
const nodePath = require('path')
|
|
6
|
-
const crypto = require("crypto")
|
|
7
|
-
const cheerio = require('cheerio')
|
|
8
|
-
const postcss = require('postcss')
|
|
9
|
-
const {
|
|
10
|
-
cacheList,
|
|
11
|
-
modifyRequest,
|
|
12
|
-
config
|
|
13
|
-
} = swRules
|
|
14
|
-
const root = hexoConfig.url + (hexoConfig.root ?? '/')
|
|
15
|
-
const domain = new URL(root).hostname
|
|
16
|
-
|
|
17
|
-
// noinspection JSUnresolvedVariable
|
|
18
|
-
hexo.extend.console.register('swpp', '生成前端更新需要的 json 文件以及相关缓存', {}, async () => {
|
|
19
|
-
// noinspection JSUnresolvedVariable
|
|
20
|
-
if (!fs.existsSync(hexoConfig.public_dir))
|
|
21
|
-
return logger.warn('[SWPP] 未检测到发布目录,跳过 swpp 执行')
|
|
22
|
-
// 读取拓展 json
|
|
23
|
-
const expandName = 'update.json'
|
|
24
|
-
const expand = fs.existsSync(expandName) ? JSON.parse(fs.readFileSync(expandName, 'utf-8')) : undefined
|
|
25
|
-
const cachePath = 'cacheList.json'
|
|
26
|
-
const updatePath = 'update.json'
|
|
27
|
-
const oldCache = await getJsonFromNetwork(cachePath)
|
|
28
|
-
const oldUpdate = await getJsonFromNetwork(updatePath)
|
|
29
|
-
const newCache = await buildNewJson(cachePath, expand?.flag)
|
|
30
|
-
const dif = compare(oldCache, newCache)
|
|
31
|
-
// 判断 update 文件是否有效
|
|
32
|
-
const updateValid = expand?.flag !== oldCache?.flag
|
|
33
|
-
buildUpdateJson(updatePath, dif, oldUpdate, updateValid ? expand : undefined)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
/** 遍历指定目录下的所有文件 */
|
|
37
|
-
const eachAllFile = (root, cb) => {
|
|
38
|
-
const stats = fs.statSync(root)
|
|
39
|
-
if (stats.isFile()) cb(root)
|
|
40
|
-
else {
|
|
41
|
-
const files = fs.readdirSync(root)
|
|
42
|
-
files.forEach(it => eachAllFile(nodePath.join(root, it), cb))
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** 判断指定文件是否需要排除 */
|
|
47
|
-
const isExclude = pathname => {
|
|
48
|
-
// noinspection JSUnresolvedVariable
|
|
49
|
-
const exclude = config.json?.exclude
|
|
50
|
-
for (let reg of exclude) {
|
|
51
|
-
if (pathname.match(reg)) return true
|
|
52
|
-
}
|
|
53
|
-
return false
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** 判断是否跳过网络拉取 */
|
|
57
|
-
const isSkipFetch = url => {
|
|
58
|
-
const skipList = config.external.skip
|
|
59
|
-
for (let reg of skipList) {
|
|
60
|
-
if (url.match(reg)) return true
|
|
61
|
-
}
|
|
62
|
-
return false
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* 构建 md5 缓存表并写入到发布目录中
|
|
67
|
-
*
|
|
68
|
-
* 格式为 `{"[path]": "[md5Value]"}`
|
|
69
|
-
*
|
|
70
|
-
* @param path 相对于根目录的路径
|
|
71
|
-
* @param flag 拓展 JSON 的 flag
|
|
72
|
-
* @return {Promise<Object>} 生成的 json 对象
|
|
73
|
-
*/
|
|
74
|
-
const buildNewJson = (path, flag) => new Promise(resolve => {
|
|
75
|
-
const result = {} // 存储新的 MD5 表
|
|
76
|
-
const taskList = [] // 拉取任务列表
|
|
77
|
-
const cache = new Set() // 已经计算过的文件
|
|
78
|
-
// noinspection JSUnresolvedVariable
|
|
79
|
-
eachAllFile(hexoConfig.public_dir, path => {
|
|
80
|
-
if (!fs.existsSync(path)) return logger.error(`[SWPP] ${path} 不存在!`)
|
|
81
|
-
const endIndex = path.length - (path.endsWith('/index.html') ? 10 : 0)
|
|
82
|
-
// noinspection JSUnresolvedReference
|
|
83
|
-
const url = new URL(nodePath.join(root, path.substring(hexoConfig.public_dir.length, endIndex)))
|
|
84
|
-
if (isExclude(url.href)) return
|
|
85
|
-
let content = null
|
|
86
|
-
if (findCache(url)) {
|
|
87
|
-
content = fs.readFileSync(path, 'utf-8')
|
|
88
|
-
const key = decodeURIComponent(url.pathname)
|
|
89
|
-
result[key] = crypto.createHash('md5').update(content).digest('hex')
|
|
90
|
-
}
|
|
91
|
-
// 外链监控
|
|
92
|
-
const external = config.external
|
|
93
|
-
if (!external) return
|
|
94
|
-
const indexOf = (str, ...chars) => {
|
|
95
|
-
let result = str.length
|
|
96
|
-
chars.forEach(it => {
|
|
97
|
-
const index = str.indexOf(it)
|
|
98
|
-
result = Math.min(result, index < 0 ? result : index)
|
|
99
|
-
})
|
|
100
|
-
return result
|
|
101
|
-
}
|
|
102
|
-
const lastIndexOf = (str, ...chars) => {
|
|
103
|
-
let result = -1
|
|
104
|
-
chars.forEach(it => result = Math.max(result, str.lastIndexOf(it)))
|
|
105
|
-
return result
|
|
106
|
-
}
|
|
107
|
-
// 处理指定链接
|
|
108
|
-
const handleLink = link => {
|
|
109
|
-
// 跳过本地文件的计算
|
|
110
|
-
if (!link.match(/^(http|\/\/)/) || cache.has(link)) return
|
|
111
|
-
cache.add(link)
|
|
112
|
-
const url = new URL(link.startsWith('/') ? `http:${link}` : link)
|
|
113
|
-
if (url.hostname === domain || !findCache(url) || isExclude(url.href)) return
|
|
114
|
-
if (isSkipFetch(url.href)) result[decodeURIComponent(link)] = '0'
|
|
115
|
-
else taskList.push(
|
|
116
|
-
fetchFile(link)
|
|
117
|
-
.then(response => response.text())
|
|
118
|
-
.then(text => {
|
|
119
|
-
const key = decodeURIComponent(link)
|
|
120
|
-
result[key] = crypto.createHash('md5').update(text).digest('hex')
|
|
121
|
-
if (key.endsWith('.js')) handleJsContent(text)
|
|
122
|
-
else if (key.endsWith('.css')) handleCssContent(text)
|
|
123
|
-
}).catch(err => {
|
|
124
|
-
logger.error(`[SWPP HandleLink] 拉取 ${url} 时出现 ${err.status ?? '未知'} 异常`)
|
|
125
|
-
logger.error(err)
|
|
126
|
-
}
|
|
127
|
-
)
|
|
128
|
-
)
|
|
129
|
-
}
|
|
130
|
-
// 处理指定 JS
|
|
131
|
-
const handleJsContent = text => {
|
|
132
|
-
// noinspection JSUnresolvedVariable
|
|
133
|
-
if (cache.has(text)) return
|
|
134
|
-
cache.add(text)
|
|
135
|
-
external.js.forEach(it => {
|
|
136
|
-
// noinspection JSUnresolvedVariable
|
|
137
|
-
const reg = new RegExp(`${it.head}(['"\`])(.*?)\\1${it.tail}`, 'mg')
|
|
138
|
-
text.match(reg)?.forEach(content => {
|
|
139
|
-
try {
|
|
140
|
-
const start = indexOf(content, "'", '"', '`') + 1
|
|
141
|
-
const end = lastIndexOf(content, "'", '"', '`')
|
|
142
|
-
const link = content.substring(start, end)
|
|
143
|
-
if (!link.match(/['"$`]/)) handleLink(link)
|
|
144
|
-
} catch (e) {
|
|
145
|
-
logger.error(`[SWPP JsHandler] 处理 ${content} 时出现异常`)
|
|
146
|
-
logger.error(e)
|
|
147
|
-
}
|
|
148
|
-
})
|
|
149
|
-
})
|
|
150
|
-
}
|
|
151
|
-
// 处理 CSS 内容
|
|
152
|
-
const handleCssContent = text => {
|
|
153
|
-
if (cache.has(text)) return
|
|
154
|
-
cache.add(text)
|
|
155
|
-
postcss.parse(text).walkDecls(decl => {
|
|
156
|
-
if (decl.value.includes('url')) {
|
|
157
|
-
decl.value.match(/url\(([^)]+)\)/g)
|
|
158
|
-
?.map(it => it.match(/^(url\(['"])/) ? it.substring(5, it.length - 2) : it.substring(4, it.length - 1))
|
|
159
|
-
?.forEach(link => handleLink(link))
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
|
-
}
|
|
163
|
-
// 如果是 html 则获取所有 script 和 link 标签拉取的文件
|
|
164
|
-
if (path.endsWith('/') || path.endsWith('.html')) {
|
|
165
|
-
if (!content) content = fs.readFileSync(path, 'utf-8')
|
|
166
|
-
const html = cheerio.load(content)
|
|
167
|
-
html('script[src]')
|
|
168
|
-
.map((i, ele) => html(ele).attr('src'))
|
|
169
|
-
.each((i, it) => handleLink(it))
|
|
170
|
-
const relList = ['stylesheet', 'manifest', 'icon', 'shortcut icon', 'mask-icon']
|
|
171
|
-
for (let value of relList) {
|
|
172
|
-
html(`link[href][rel='${value}']`)
|
|
173
|
-
.map((i, ele) => html(ele).attr('href'))
|
|
174
|
-
.each((i, it) => handleLink(it))
|
|
175
|
-
}
|
|
176
|
-
html('script:not([src])')
|
|
177
|
-
.map((i, ele) => html(ele).text())
|
|
178
|
-
.each((i, text) => handleJsContent(text))
|
|
179
|
-
html('style')
|
|
180
|
-
.map((i, ele) => html(ele).text())
|
|
181
|
-
.each((i, text) => handleCssContent(text))
|
|
182
|
-
} else if (path.endsWith('.js')) {
|
|
183
|
-
if (!content) content = fs.readFileSync(path, 'utf-8')
|
|
184
|
-
handleJsContent(content)
|
|
185
|
-
} else if (path.endsWith('.css')) {
|
|
186
|
-
if (!content) content = fs.readFileSync(path, 'utf-8')
|
|
187
|
-
handleCssContent(content)
|
|
188
|
-
}
|
|
189
|
-
})
|
|
190
|
-
Promise.all(taskList).then(() => {
|
|
191
|
-
const json = {
|
|
192
|
-
version: 2,
|
|
193
|
-
flag: flag,
|
|
194
|
-
list: result
|
|
195
|
-
}
|
|
196
|
-
// noinspection JSUnresolvedVariable
|
|
197
|
-
const publicRoot = hexoConfig.public_dir
|
|
198
|
-
fs.writeFileSync(
|
|
199
|
-
nodePath.join(publicRoot, path),
|
|
200
|
-
JSON.stringify(json),
|
|
201
|
-
'utf-8'
|
|
202
|
-
)
|
|
203
|
-
logger.info(`Generated: ${path}`)
|
|
204
|
-
resolve(json)
|
|
205
|
-
})
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* 从网络拉取一个文件
|
|
210
|
-
* @param link 文件链接
|
|
211
|
-
* @returns {Promise<*>} response
|
|
212
|
-
*/
|
|
213
|
-
const fetchFile = link => new Promise((resolve, reject) => {
|
|
214
|
-
link = replaceDevRequest(link)
|
|
215
|
-
// noinspection SpellCheckingInspection
|
|
216
|
-
fetch(link, {
|
|
217
|
-
headers: {
|
|
218
|
-
referer: new URL(link).hostname,
|
|
219
|
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.62'
|
|
220
|
-
},
|
|
221
|
-
timeout: config.external.timeout
|
|
222
|
-
}).then(response => {
|
|
223
|
-
switch (response.status) {
|
|
224
|
-
case 200: case 301: case 302: case 307: case 308:
|
|
225
|
-
resolve(response)
|
|
226
|
-
break
|
|
227
|
-
default:
|
|
228
|
-
reject(response)
|
|
229
|
-
break
|
|
230
|
-
}
|
|
231
|
-
}).catch(err => {
|
|
232
|
-
err.url = link
|
|
233
|
-
if (err.type === 'request-timeout') {
|
|
234
|
-
logger.error(
|
|
235
|
-
`[SWPP FetchFile] 拉取文件 [${link}] 时出现了超时错误,如果您构建所在的位置无法访问该资源,` +
|
|
236
|
-
"请尝试通过 DevReplace(https://kmar.top/posts/73014407/#4ea71e00)功能解决该问题。"
|
|
237
|
-
)
|
|
238
|
-
}
|
|
239
|
-
reject(err)
|
|
240
|
-
})
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* 从网络拉取 json 文件
|
|
245
|
-
* @param path 文件路径(相对于根目录)
|
|
246
|
-
*/
|
|
247
|
-
const getJsonFromNetwork = path => new Promise(resolve => {
|
|
248
|
-
const url = nodePath.join(root, path)
|
|
249
|
-
fetchFile(url)
|
|
250
|
-
.then(response => resolve(response.json()))
|
|
251
|
-
.catch(err => {
|
|
252
|
-
if (err.status === 404) {
|
|
253
|
-
logger.warn(`[SWPP JsonBuilder] 拉取 ${err.url} 时出现 404 错误,如果您是第一次构建请忽略这个警告。`)
|
|
254
|
-
resolve()
|
|
255
|
-
} else throw err
|
|
256
|
-
})
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* 对比两个 md5 缓存表的区别
|
|
261
|
-
* @return [string] 需要更新的文件路径
|
|
262
|
-
*/
|
|
263
|
-
const compare = (oldCache, newCache) => {
|
|
264
|
-
const result = []
|
|
265
|
-
if (!oldCache) return result
|
|
266
|
-
const refresh = oldCache.version !== newCache.version
|
|
267
|
-
if (refresh) return undefined
|
|
268
|
-
if (oldCache.version) oldCache = oldCache.list
|
|
269
|
-
for (let path in oldCache) {
|
|
270
|
-
if (newCache.list[path] !== oldCache[path]) result.push(path)
|
|
271
|
-
}
|
|
272
|
-
return result
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/** 判断指定资源是否需要合并 */
|
|
276
|
-
const isMerge = (pathname, tidied) => {
|
|
277
|
-
// noinspection JSUnresolvedVariable
|
|
278
|
-
const optional = config.json?.merge ?? pluginConfig.json.merge
|
|
279
|
-
const {tag_dir, archive_dir, category_dir} = hexoConfig
|
|
280
|
-
if (pathname.includes(`/${tag_dir}/`)) {
|
|
281
|
-
if (optional?.tags ?? true)
|
|
282
|
-
return tidied.tags = true
|
|
283
|
-
} else if (pathname.includes(`/${archive_dir}/`)) {
|
|
284
|
-
if (optional?.archives ?? true)
|
|
285
|
-
return tidied.archives = true
|
|
286
|
-
} else if (pathname.includes(`/${category_dir}/`)) {
|
|
287
|
-
if (optional?.categories ?? true)
|
|
288
|
-
return tidied.categories = true
|
|
289
|
-
} else if (pathname.startsWith('/page/') || pathname.length <= 1) {
|
|
290
|
-
if (optional?.index ?? true)
|
|
291
|
-
return tidied.index = true
|
|
292
|
-
} else {
|
|
293
|
-
const list = optional?.custom
|
|
294
|
-
if (!list) return false
|
|
295
|
-
for (let reg of list) {
|
|
296
|
-
if (pathname.startsWith(`/${reg}/`))
|
|
297
|
-
return tidied.custom[reg] = true
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* 从一个字符串中提取最后两个 / 之间的内容
|
|
304
|
-
* @param it {string} 要操作的字符串
|
|
305
|
-
* @param keep {boolean} 是否保留最后一个 / 及其后面的内容
|
|
306
|
-
*/
|
|
307
|
-
const clipPageName = (it, keep) => {
|
|
308
|
-
const end = it.lastIndexOf('/')
|
|
309
|
-
let index = end - 1
|
|
310
|
-
for (; index > 0; --index) {
|
|
311
|
-
if (it[index] === '/') break
|
|
312
|
-
}
|
|
313
|
-
return it.substring(index + 1, keep ? it.length : end)
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/** 构建新的 update.json */
|
|
317
|
-
const buildUpdateJson = (name, dif, oldUpdate, expand) => {
|
|
318
|
-
/** 将对象写入文件,如果对象为 null 或 undefined 则跳过写入 */
|
|
319
|
-
const writeJson = json => {
|
|
320
|
-
if (json) {
|
|
321
|
-
logger.info(`Generated: ${name}`)
|
|
322
|
-
fs.writeFileSync(`public/${name}`, JSON.stringify(json), 'utf-8')
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
// 获取上次最新的版本
|
|
326
|
-
let oldVersion = oldUpdate?.info?.at(0)?.version ?? 0
|
|
327
|
-
if (typeof oldVersion !== 'number') {
|
|
328
|
-
// 当上次最新的版本号不是数字是尝试对其进行转换,如果无法转换则直接置零
|
|
329
|
-
if (oldVersion.match('\D')) oldVersion = 0
|
|
330
|
-
else oldVersion = Number.parseInt(oldVersion)
|
|
331
|
-
}
|
|
332
|
-
// 存储本次更新的内容
|
|
333
|
-
const newInfo = {
|
|
334
|
-
version: oldVersion + 1,
|
|
335
|
-
change: expand?.change ?? []
|
|
336
|
-
}
|
|
337
|
-
// 整理更新的数据
|
|
338
|
-
const tidied = tidyDiff(dif, expand)
|
|
339
|
-
if (expand?.all || tidied.all) return writeJson({
|
|
340
|
-
global: (oldUpdate?.global ?? 0) + (tidied.updateGlobal ? 1 : 0),
|
|
341
|
-
info: [{version: newInfo.version}]
|
|
342
|
-
})
|
|
343
|
-
// 如果没有更新的文件就直接退出
|
|
344
|
-
if (
|
|
345
|
-
tidied.page.size === 0 && tidied.file.size === 0 &&
|
|
346
|
-
!(tidied.archives || tidied.categories || tidied.tags || tidied.index || Object.keys(tidied.custom).length !== 0)
|
|
347
|
-
) return writeJson(oldUpdate ?? {
|
|
348
|
-
global: 0,
|
|
349
|
-
info: [{version: 0}]
|
|
350
|
-
})
|
|
351
|
-
pushUpdateToInfo(newInfo, tidied)
|
|
352
|
-
const result = mergeUpdateWithOld(newInfo, oldUpdate, tidied)
|
|
353
|
-
return writeJson(result)
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const mergeUpdateWithOld = (newInfo, oldUpdate, tidied) => {
|
|
357
|
-
const result = {
|
|
358
|
-
global: (oldUpdate?.global ?? 0) + (tidied.updateGlobal ? 1 : 0),
|
|
359
|
-
info: [newInfo]
|
|
360
|
-
}
|
|
361
|
-
// noinspection JSUnresolvedVariable
|
|
362
|
-
const charLimit = config.json.charLimit
|
|
363
|
-
if (JSON.stringify(result).length > charLimit) {
|
|
364
|
-
return {
|
|
365
|
-
global: result.global,
|
|
366
|
-
info: [{version: newInfo.version}]
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
if (!oldUpdate) return result
|
|
370
|
-
for (let it of oldUpdate.info) {
|
|
371
|
-
if (it.change) it.change = zipInfo(newInfo, it)
|
|
372
|
-
result.info.push(it)
|
|
373
|
-
if (JSON.stringify(result).length > charLimit) {
|
|
374
|
-
result.info.pop()
|
|
375
|
-
break
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return result
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// 压缩相同项目
|
|
382
|
-
const zipInfo = (newInfo, oldInfo) => {
|
|
383
|
-
oldInfo = oldInfo.change
|
|
384
|
-
newInfo = newInfo.change
|
|
385
|
-
const result = []
|
|
386
|
-
for (let i = oldInfo.length - 1; i !== -1; --i) {
|
|
387
|
-
const value = oldInfo[i]
|
|
388
|
-
if (value.flag === 'page' && newInfo.find(it => it.flag === 'html')) continue
|
|
389
|
-
const newValue = newInfo.find(it => it.flag === value.flag)
|
|
390
|
-
if (!newValue) {
|
|
391
|
-
result.push(value)
|
|
392
|
-
continue
|
|
393
|
-
}
|
|
394
|
-
if (!value.value) continue
|
|
395
|
-
const isArray = Array.isArray(newValue.value)
|
|
396
|
-
if (Array.isArray(value.value)) {
|
|
397
|
-
const array = value.value
|
|
398
|
-
.filter(it => isArray ? !newValue.value.find(that => that === it) : it !== newValue.value)
|
|
399
|
-
if (array.length === 0) continue
|
|
400
|
-
result.push({flag: value.flag, value: array.length === 1 ? array[0] : array})
|
|
401
|
-
} else if (isArray) {
|
|
402
|
-
if (!newValue.value.find(it => it === value.value))
|
|
403
|
-
result.push(value)
|
|
404
|
-
} else {
|
|
405
|
-
if (newValue.value !== value.value)
|
|
406
|
-
result.push(value)
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return result.length === 0 ? undefined : result
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// 将更新推送到 info
|
|
413
|
-
const pushUpdateToInfo = (info, tidied) => {
|
|
414
|
-
const merges = [] // 要合并的更新
|
|
415
|
-
// 推送页面更新
|
|
416
|
-
// noinspection JSUnresolvedVariable
|
|
417
|
-
if (tidied.page.size > (config.json.maxHtml)) {
|
|
418
|
-
// 如果 html 数量超过阈值就直接清掉所有 html
|
|
419
|
-
info.change.push({flag: 'html'})
|
|
420
|
-
} else {
|
|
421
|
-
const pages = [] // 独立更新
|
|
422
|
-
tidied.page.forEach(it => pages.push(it))
|
|
423
|
-
const {tag_dir, archive_dir, category_dir} = hexoConfig
|
|
424
|
-
if (tidied.tags) merges.push(tag_dir)
|
|
425
|
-
if (tidied.archives) merges.push(archive_dir)
|
|
426
|
-
if (tidied.categories) merges.push(category_dir)
|
|
427
|
-
if (tidied.index) {
|
|
428
|
-
pages.push(clipPageName(root, false))
|
|
429
|
-
merges.push('page')
|
|
430
|
-
}
|
|
431
|
-
if (pages.length > 0)
|
|
432
|
-
info.change.push({flag: 'page', value: pages})
|
|
433
|
-
}
|
|
434
|
-
for (let key in tidied.custom) merges.push(key)
|
|
435
|
-
if (merges.length > 0)
|
|
436
|
-
info.change.push({flag: 'str', value: merges.map(it => `/${it}/`)})
|
|
437
|
-
// 推送文件更新
|
|
438
|
-
if (tidied.file.size > 0) {
|
|
439
|
-
const list = []
|
|
440
|
-
tidied.file.forEach(it => list.push(it))
|
|
441
|
-
info.change.push({flag: 'file', value: list})
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// 将 diff 整理分类
|
|
446
|
-
const tidyDiff = (dif, expand) => {
|
|
447
|
-
const tidied = {
|
|
448
|
-
/** 所有 HTML 页面 */
|
|
449
|
-
page: new Set(),
|
|
450
|
-
/** 所有文件 */
|
|
451
|
-
file: new Set(),
|
|
452
|
-
/** 标记 tags 是否更新 */
|
|
453
|
-
tags: false,
|
|
454
|
-
/** 标记 archives 是否更新 */
|
|
455
|
-
archives: false,
|
|
456
|
-
/** 标记 categories 是否更新 */
|
|
457
|
-
categories: false,
|
|
458
|
-
/** 标记 index 是否更新 */
|
|
459
|
-
index: false,
|
|
460
|
-
/** 自定义配置项 */
|
|
461
|
-
custom: {},
|
|
462
|
-
/** 标记是否更新 global 版本号 */
|
|
463
|
-
updateGlobal: expand?.global,
|
|
464
|
-
/** 是否刷新全部缓存 */
|
|
465
|
-
all: false
|
|
466
|
-
}
|
|
467
|
-
if (!dif) {
|
|
468
|
-
tidied.all = tidied.updateGlobal = true
|
|
469
|
-
return tidied
|
|
470
|
-
}
|
|
471
|
-
// noinspection JSUnresolvedVariable
|
|
472
|
-
const mode = config.json.precisionMode
|
|
473
|
-
let loseRule = []
|
|
474
|
-
for (let it of dif) {
|
|
475
|
-
const url = new URL(it.match(/^(https?|\/\/)/) ? it : nodePath.join(root, it)) // 当前文件的 URL
|
|
476
|
-
const cache = findCache(url) // 查询缓存
|
|
477
|
-
if (!cache) loseRule.push(url.href)
|
|
478
|
-
if (!cache?.clean) tidied.updateGlobal = true
|
|
479
|
-
if (isMerge(url.pathname, tidied)) continue
|
|
480
|
-
if (it.match(/(\/|\.html)$/)) { // 判断缓存是否是 html
|
|
481
|
-
if (mode?.html ?? false) tidied.page.add(url.pathname)
|
|
482
|
-
else tidied.page.add(clipPageName(url.href, !it.endsWith('/')))
|
|
483
|
-
} else {
|
|
484
|
-
const extendedName = (it.includes('.') ? it.match(/[^.]+$/)[0] : null) ?? 'default'
|
|
485
|
-
const setting = (mode && mode[extendedName]) ?? (mode?.default ?? false)
|
|
486
|
-
if (setting) tidied.file.add(url.pathname)
|
|
487
|
-
else {
|
|
488
|
-
let name = url.href.match(/[^/]+$/)[0]
|
|
489
|
-
if (!name) throw `${url.href} 格式错误`
|
|
490
|
-
tidied.file.add(name)
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
if (loseRule.length !== 0)
|
|
495
|
-
logger.warn(
|
|
496
|
-
'[SWPP BuildUpdate] 由于缓存规则变动,部分 URL 未查询到缓存规则,将按照 clean=false 处理。' +
|
|
497
|
-
'如果您没有修改缓存规则,希望您能向我反馈这一 BUG:\n' + loseRule.map((it, index) => `\t${index}: ${it}`).join('\n')
|
|
498
|
-
)
|
|
499
|
-
return tidied
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
function findCache(url) {
|
|
503
|
-
url = new URL(replaceRequest(url.href))
|
|
504
|
-
for (let key in cacheList) {
|
|
505
|
-
const value = cacheList[key]
|
|
506
|
-
if (value.match(url, ejectValues)) return value
|
|
507
|
-
}
|
|
508
|
-
return null
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function replaceRequest(url) {
|
|
512
|
-
if (!modifyRequest) return url
|
|
513
|
-
const request = new Request(url)
|
|
514
|
-
const newRequest = modifyRequest(request, ejectValues)
|
|
515
|
-
return newRequest?.url ?? url
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function replaceDevRequest(url) {
|
|
519
|
-
const external = config.external
|
|
520
|
-
if (!external || !external.replace) return url
|
|
521
|
-
for (let value of external.replace) {
|
|
522
|
-
for (let source of value.source) {
|
|
523
|
-
if (url.match(source)) {
|
|
524
|
-
// noinspection JSUnresolvedVariable
|
|
525
|
-
url = url.replace(source, value.dist)
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return url
|
|
530
|
-
}
|
|
531
|
-
}
|
package/lib/sort.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
const nodePath = require("path")
|
|
2
|
-
|
|
3
|
-
/** 对 Hexo 内的属性进行排序 */
|
|
4
|
-
module.exports = config => {
|
|
5
|
-
const compare = (a, b) => {
|
|
6
|
-
const result = a.length === b.length ? a < b : a.length < b.length
|
|
7
|
-
return result ? -1 : 1
|
|
8
|
-
}
|
|
9
|
-
const sort = (obj, value) => {
|
|
10
|
-
if (!obj) return
|
|
11
|
-
const target = obj.data ?? obj
|
|
12
|
-
if (!target.sort) return
|
|
13
|
-
if (value !== false) target.sort((a, b) => compare(a[value], b[value]))
|
|
14
|
-
else target.sort(compare)
|
|
15
|
-
}
|
|
16
|
-
const list = {
|
|
17
|
-
posts: 'title',
|
|
18
|
-
pages: 'title',
|
|
19
|
-
tags: 'name'
|
|
20
|
-
}
|
|
21
|
-
Object.assign(list, config.sort)
|
|
22
|
-
const Locals = require(`${nodePath.resolve('./', 'node_modules/hexo/lib/hexo/locals')}`).prototype
|
|
23
|
-
const get = Locals.get
|
|
24
|
-
Locals.get = function(name) {
|
|
25
|
-
const result = get.call(this, name)
|
|
26
|
-
if (name in list) sort(result, list[name])
|
|
27
|
-
if ('forEach' in result) {
|
|
28
|
-
result.forEach(it => {
|
|
29
|
-
for (let tag in list)
|
|
30
|
-
sort(it[tag], list[tag])
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
return result
|
|
34
|
-
}
|
|
35
|
-
}
|
package/lib/sw-dom.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
2
|
-
/** 检查 SW 是否可用 */
|
|
3
|
-
const checkServiceWorker = () => 'serviceWorker' in navigator && navigator.serviceWorker.controller
|
|
4
|
-
/** 发送信息到 sw */
|
|
5
|
-
const postMessage2SW = type => navigator.serviceWorker.controller.postMessage(type)
|
|
6
|
-
const pjaxUpdate = url => new Promise(resolve => {
|
|
7
|
-
const type = url.endsWith('js') ? 'script' : 'link'
|
|
8
|
-
const name = type.length === 4 ? 'href' : 'src'
|
|
9
|
-
for (let item of document.querySelectorAll(type)) {
|
|
10
|
-
const itUrl = item[name]
|
|
11
|
-
if (url.length > itUrl ? url.endsWith(itUrl) : itUrl.endsWith(url)) {
|
|
12
|
-
const newEle = document.createElement(type)
|
|
13
|
-
const content = item.text || item.textContent || item.innerHTML || ''
|
|
14
|
-
Array.from(item.attributes).forEach(attr => newEle.setAttribute(attr.name, attr.value))
|
|
15
|
-
newEle.appendChild(document.createTextNode(content))
|
|
16
|
-
item.parentNode.replaceChildren(newEle, item)
|
|
17
|
-
return resolve(true)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
resolve(false)
|
|
21
|
-
})
|
|
22
|
-
if (!checkServiceWorker()) return
|
|
23
|
-
if (sessionStorage.getItem('updated')) {
|
|
24
|
-
sessionStorage.removeItem('updated')
|
|
25
|
-
// ${onSuccess}
|
|
26
|
-
} else postMessage2SW('update')
|
|
27
|
-
navigator.serviceWorker.addEventListener('message', event => {
|
|
28
|
-
const data = event.data
|
|
29
|
-
switch (data.type) {
|
|
30
|
-
case 'update':
|
|
31
|
-
const list = data.update
|
|
32
|
-
if (!list) break
|
|
33
|
-
sessionStorage.setItem('updated', '1')
|
|
34
|
-
// noinspection JSUnresolvedVariable,JSUnresolvedFunction
|
|
35
|
-
if (window.Pjax?.isSupported()) {
|
|
36
|
-
Promise.all(list.map(url => {
|
|
37
|
-
if (url.endsWith('.js'))
|
|
38
|
-
return pjaxUpdate(url)
|
|
39
|
-
if (url.endsWith('.css'))
|
|
40
|
-
return pjaxUpdate(url)
|
|
41
|
-
return Promise.resolve()
|
|
42
|
-
})).then(() => location.reload())
|
|
43
|
-
} else location.reload()
|
|
44
|
-
break
|
|
45
|
-
case 'escape':
|
|
46
|
-
sessionStorage.setItem('updated', '1')
|
|
47
|
-
location.reload()
|
|
48
|
-
break
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
})
|