swpp-backends 2.3.12 → 3.0.0-alpha.0

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.
Files changed (51) hide show
  1. package/README.md +1 -38
  2. package/dist/index.js +5 -38
  3. package/dist/swpp/FileParser.js +85 -0
  4. package/dist/swpp/JsonBuilder.js +232 -0
  5. package/dist/swpp/NetworkFileHandler.js +68 -0
  6. package/dist/swpp/ResourcesScanner.js +246 -0
  7. package/dist/swpp/SwCompiler.js +62 -0
  8. package/dist/swpp/config/ConfigLoader.js +185 -0
  9. package/dist/swpp/database/CompilationEnv.js +280 -0
  10. package/dist/swpp/database/CrossDepCode.js +76 -0
  11. package/dist/swpp/database/CrossEnv.js +70 -0
  12. package/dist/swpp/database/KeyValueDatabase.js +76 -0
  13. package/dist/swpp/database/RuntimeCoreCode.js +146 -0
  14. package/dist/swpp/database/RuntimeDepCode.js +316 -0
  15. package/dist/swpp/database/RuntimeEventCode.js +54 -0
  16. package/dist/swpp/database/RuntimeKeyValueDatabase.js +11 -0
  17. package/dist/swpp/untils.js +256 -0
  18. package/package.json +2 -5
  19. package/types/FileAnalyzer.d.ts +2 -2
  20. package/types/Utils.d.ts +1 -2
  21. package/types/browser/ServiceWorkerRuntimeTypes.d.ts +2 -0
  22. package/types/index.d.ts +2 -64
  23. package/types/swpp/FileParser.d.ts +67 -0
  24. package/types/swpp/JsonBuilder.d.ts +28 -0
  25. package/types/swpp/NetworkFileHandler.d.ts +28 -0
  26. package/types/swpp/ResourcesScanner.d.ts +67 -0
  27. package/types/swpp/RuntimeEnv.d.ts +39 -0
  28. package/types/swpp/SwCodeInject.d.ts +17 -0
  29. package/types/swpp/SwCompiler.d.ts +47 -0
  30. package/types/swpp/config/ConfigLoader.d.ts +210 -0
  31. package/types/swpp/database/CompilationEnv.d.ts +33 -0
  32. package/types/swpp/database/CrossDepCode.d.ts +30 -0
  33. package/types/swpp/database/CrossEnv.d.ts +25 -0
  34. package/types/swpp/database/KeyValueDatabase.d.ts +53 -0
  35. package/types/swpp/database/RuntimeCoreCode.d.ts +29 -0
  36. package/types/swpp/database/RuntimeDepCode.d.ts +104 -0
  37. package/types/swpp/database/RuntimeEnv.d.ts +5 -0
  38. package/types/swpp/database/RuntimeEventCode.d.ts +25 -0
  39. package/types/swpp/database/RuntimeKeyValueDatabase.d.ts +7 -0
  40. package/types/swpp/untils.d.ts +89 -0
  41. package/dist/DomBuilder.js +0 -32
  42. package/dist/FileAnalyzer.js +0 -496
  43. package/dist/ServiceWorkerBuilder.js +0 -100
  44. package/dist/SwppConfig.js +0 -54
  45. package/dist/SwppRules.js +0 -85
  46. package/dist/UpdateJsonBuilder.js +0 -320
  47. package/dist/Utils.js +0 -227
  48. package/dist/Variant.js +0 -111
  49. package/dist/VersionAnalyzer.js +0 -67
  50. package/dist/browser/ServiceWorkerRuntimeTypes.js +0 -1
  51. package/dist/resources/sw-template.js +0 -348
package/README.md CHANGED
@@ -2,41 +2,4 @@
2
2
 
3
3
  ## 欢迎使用 SwppBackends
4
4
 
5
- swpp-backends(以下简称 swpp)插件的功能是为网站生成一个高度可用的 ServiceWorker(以下简称 SW),为网站优化二次加载、提供离线体验、提高可靠性,并为此附带了一些其它的功能。
6
-
7
- swpp 的全拼为“Service Worker Plus Plus”(或“Service Worker++”),但是其与已有的插件“hexo-service-worker”并没有关系,插件中所有代码均为我个人开发,这一点请不要误解。
8
-
9
- swpp 生成的 SW 与其它插件的对比:
10
-
11
- | | swpp | hexo-offline |
12
- |:---------------:|:-------------:|:------------:|
13
- | 本地缓存 | ✔️ | ✔️ |
14
- | 缓存增量更新 | ✔️ | ❌ |
15
- | 缓存过期时间 | ❌ | ✔️ |
16
- | 缓存大小限制 | ❌ | ✔️ |
17
- | 预缓存 | ❌<sup>1</sup> | ✔️ |
18
- | Request 篡改 | ✔️ | ❌ |
19
- | URL 竞速 | ✔️ | ❌ |
20
- | 备用 URL | ✔️ | ❌ |
21
- | 204 阻塞响应 | ✔️ | ❌ |
22
- | 逃生门 | ✔️ | ❌ |
23
- | 请求合并 | ✔️ | ❌ |
24
- | 跨平台<sup>2</sup> | ✔️ | ❌ |
25
- | 高度自由 | ✔️ | ❌ |
26
- | 更新 | 非常频繁 | 超过两年没有更新 |
27
-
28
- + ✔️:支持
29
- + ❌:不支持
30
-
31
- 1. 预缓存可以在前端实现,SW 实现这个功能容易拖延注册时间
32
- 2. 跨平台是指跨越框架(可在 NodeJS 平台中的任意框架下工作)
33
-
34
- 目前支持的平台:
35
-
36
- | 平台 | 插件名 | 文档 | 作者 |
37
- |:----:|:-----------:|:---------------------------------------------------------:|:-----------------------:|
38
- | hexo | `hexo-swpp` | [github](https://github.com/EmptyDreams/hexo-swpp#readme) | [空梦](https://kmar.top/) |
39
-
40
- 如果你为某一个平台做了适配,可以在 gh 上发布 issue 或者在文档页面发布评论~
41
-
42
- 文档:[Swpp Backends 官方文档 | 山岳库博](https://kmar.top/posts/b70ec88f/)
5
+ 本分支是 swpp V3 分支,V3 将重构包括 sw.js 在内的所有代码,目前仍在开发中。
package/dist/index.js CHANGED
@@ -1,40 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const Utils_1 = require("./Utils");
4
- const FileAnalyzer_1 = require("./FileAnalyzer");
5
- const ServiceWorkerBuilder_1 = require("./ServiceWorkerBuilder");
6
- const SwppRules_1 = require("./SwppRules");
7
- const UpdateJsonBuilder_1 = require("./UpdateJsonBuilder");
8
- const Variant_1 = require("./Variant");
9
- const VersionAnalyzer_1 = require("./VersionAnalyzer");
10
- const DomBuilder_1 = require("./DomBuilder");
11
- // noinspection JSUnusedGlobalSymbols
12
- exports.default = {
13
- version: require('../package.json').version,
14
- cache: {
15
- readEjectData: Utils_1.readEjectData, readUpdateJson: Variant_1.readUpdateJson,
16
- readRules: Variant_1.readRules, readMergeVersionMap: Variant_1.readMergeVersionMap,
17
- readOldVersionJson: Variant_1.readOldVersionJson, readNewVersionJson: Variant_1.readNewVersionJson,
18
- readAnalyzeResult: Variant_1.readAnalyzeResult
19
- },
20
- builder: {
21
- buildServiceWorker: ServiceWorkerBuilder_1.buildServiceWorker,
22
- buildDomJs: DomBuilder_1.buildDomJs,
23
- buildVersionJson: FileAnalyzer_1.buildVersionJson,
24
- buildUpdateJson: UpdateJsonBuilder_1.buildUpdateJson,
25
- calcEjectValues: Utils_1.calcEjectValues,
26
- analyzeVersion: VersionAnalyzer_1.analyzeVersion
27
- },
28
- loader: {
29
- loadRules: SwppRules_1.loadRules, loadUpdateJson: UpdateJsonBuilder_1.loadUpdateJson, loadVersionJson: FileAnalyzer_1.loadVersionJson
30
- },
31
- event: {
32
- addRulesMapEvent: SwppRules_1.addRulesMapEvent, refreshUrl: VersionAnalyzer_1.refreshUrl, submitChange: UpdateJsonBuilder_1.submitChange, submitCacheInfo: FileAnalyzer_1.submitCacheInfo, submitExternalUrl: FileAnalyzer_1.submitExternalUrl, registryFileHandler: FileAnalyzer_1.registryFileHandler
33
- },
34
- utils: {
35
- getSource: Utils_1.getSource, getShorthand: UpdateJsonBuilder_1.getShorthand, findCache: FileAnalyzer_1.findCache,
36
- fetchFile: Utils_1.fetchFile, replaceDevRequest: Utils_1.replaceDevRequest, replaceRequest: FileAnalyzer_1.replaceRequest,
37
- isStable: FileAnalyzer_1.isStable, isExclude: FileAnalyzer_1.isExclude, findFileHandler: FileAnalyzer_1.findFileHandler,
38
- eachAllLinkInUrl: FileAnalyzer_1.eachAllLinkInUrl, deepFreeze: Utils_1.deepFreeze, writeVariant: Variant_1.writeVariant, readVariant: Variant_1.readVariant, deleteVariant: Variant_1.deleteVariant
39
- }
40
- };
3
+ exports.version = void 0;
4
+ const untils_1 = require("./swpp/untils");
5
+ /** 版本号 */
6
+ exports.version = require('../package.json').version;
7
+ untils_1.utils.printInfo('INDEX', `欢迎使用 swpp@${exports.version}`);
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildFileParser = exports.FileParserRegistry = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const untils_1 = require("./untils");
9
+ class FileParserRegistry {
10
+ constructor(compilation, obj = {}) {
11
+ this.compilation = compilation;
12
+ this.map = new Map();
13
+ for (let key in obj) {
14
+ this.map.set(key, obj[key]);
15
+ }
16
+ }
17
+ registry(type, parser) {
18
+ this.map.set(type, parser);
19
+ }
20
+ /** 判断是否支持指定类型 */
21
+ containsType(type) {
22
+ return this.map.has(type);
23
+ }
24
+ /** 解析本地文件 */
25
+ async parserLocalFile(path) {
26
+ const parser = this.map.get(path_1.default.extname(path));
27
+ if (!parser)
28
+ return new Set();
29
+ const content = await parser.readFromLocal(this.compilation, path);
30
+ return await parser.extractUrls(this.compilation, content);
31
+ }
32
+ /** 解析网络文件 */
33
+ async parserNetworkFile(response, callback) {
34
+ const fileHandler = this.compilation.compilationEnv.read('FETCH_NETWORK_FILE');
35
+ const contentType = fileHandler.getUrlContentType(response.url, response);
36
+ const parser = this.map.get(contentType);
37
+ if (!parser)
38
+ return new Set();
39
+ const content = await parser.readFromNetwork(this.compilation, response);
40
+ if (callback)
41
+ await callback(content);
42
+ return await parser.extractUrls(this.compilation, content);
43
+ }
44
+ /**
45
+ * 解析指定的 URL
46
+ * @param url 链接
47
+ * @param isCached 该链接指向的资源是否需要缓存
48
+ */
49
+ async parserUrlFile(url, isCached) {
50
+ const fileHandler = this.compilation.compilationEnv.read('FETCH_NETWORK_FILE');
51
+ const contentType = fileHandler.getUrlContentType(url);
52
+ if (!contentType && !isCached)
53
+ return { file: url, mark: '', urls: new Set() };
54
+ const parser = this.map.get(contentType);
55
+ if (parser?.calcUrl) {
56
+ const result = await parser.calcUrl(url);
57
+ if (result)
58
+ return {
59
+ file: url,
60
+ ...result
61
+ };
62
+ }
63
+ const fetcher = this.compilation.compilationEnv.read('FETCH_NETWORK_FILE');
64
+ const urls = new Set();
65
+ let mark = '';
66
+ await fetcher.fetch(url)
67
+ .then(response => this.parserNetworkFile(response, content => {
68
+ mark = untils_1.utils.calcHash(content);
69
+ }))
70
+ .then(urls => urls.forEach(it => urls.add(it)));
71
+ return { file: url, mark, urls };
72
+ }
73
+ /** 解析指定类型的文件内容 */
74
+ async parserContent(type, content) {
75
+ const parser = this.map.get(type);
76
+ if (!parser)
77
+ return new Set();
78
+ return await parser.extractUrls(this.compilation, content);
79
+ }
80
+ }
81
+ exports.FileParserRegistry = FileParserRegistry;
82
+ function buildFileParser(parser) {
83
+ return parser;
84
+ }
85
+ exports.buildFileParser = buildFileParser;
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JsonBuilder = void 0;
4
+ const untils_1 = require("./untils");
5
+ class JsonBuilder {
6
+ constructor(compilation, urls, headers = new Map(), map = new Map()) {
7
+ this.compilation = compilation;
8
+ this.urls = urls;
9
+ this.headers = headers;
10
+ this.map = map;
11
+ }
12
+ update(key, value) {
13
+ this.map.set(key, value);
14
+ }
15
+ putHeader(key, value) {
16
+ this.headers.set(key, value);
17
+ }
18
+ async buildJson() {
19
+ const json = await this.compilation.compilationEnv.read('VERSION_FILE')();
20
+ if (json.info.length == 0) {
21
+ json.info.push({ version: 1 });
22
+ return json;
23
+ }
24
+ const newChange = createUpdateChangeExps(this.urls, this.map.values());
25
+ json.info.unshift({
26
+ version: json.info[0].version + 1,
27
+ change: [newChange]
28
+ });
29
+ this.zipJson(json);
30
+ this.limitJson(json);
31
+ return json;
32
+ }
33
+ zipJson(json) {
34
+ const matchUpdateRule = this.compilation.crossDep.read('matchUpdateRule');
35
+ const htmlMatcher = matchUpdateRule.runOnNode({ flag: 'html' });
36
+ // 打散新版本的所有规则
37
+ json.info[0].change = json.info[0].change?.flatMap(exp => {
38
+ if (!Array.isArray(exp.value))
39
+ return [exp];
40
+ return exp.value.map(it => ({
41
+ flag: exp.flag, value: it
42
+ }));
43
+ });
44
+ // 压缩第一个版本的内容
45
+ const indexes = (() => {
46
+ const change = json.info[0].change;
47
+ if (!change)
48
+ return new Set();
49
+ let htmlCount = 0;
50
+ const indexes = new Set();
51
+ // 统计每个表达式匹配的资源及刷新的 HTML 总量
52
+ const indexesArray = change.map(exp => {
53
+ const matcher = matchUpdateRule.runOnNode(exp);
54
+ const result = new Set();
55
+ untils_1.utils.findValueInIterable(this.urls, it => !!matcher(it))
56
+ .forEach(item => {
57
+ indexes.add(item.index);
58
+ result.add(item.index);
59
+ if (htmlMatcher(item.value))
60
+ ++htmlCount;
61
+ });
62
+ return result;
63
+ });
64
+ // 如果 HTML 更新数量超过阈值,则直接清除所有 HTML 的缓存
65
+ if (htmlCount > 0) {
66
+ const htmlLimit = this.compilation.compilationEnv.read('JSON_HTML_LIMIT');
67
+ if (htmlLimit > 0 && htmlCount > htmlLimit) {
68
+ change.unshift({ flag: 'html' });
69
+ const indexes = new Set();
70
+ untils_1.utils.findValueInIterable(this.urls, it => !!htmlMatcher(it))
71
+ .forEach(({ index }) => indexes.add(index));
72
+ indexesArray.unshift(indexes);
73
+ }
74
+ }
75
+ // 分析哪些表达式是冗余的
76
+ const invalidIndex = new Array(indexesArray.length);
77
+ for (let i = 0; i < indexesArray.length; i++) {
78
+ if (invalidIndex[i])
79
+ continue;
80
+ const parent = indexesArray[0];
81
+ o: for (let k = 0; k < indexesArray.length; k++) {
82
+ if (i == k || invalidIndex[k])
83
+ continue;
84
+ for (let item of indexesArray[k]) {
85
+ if (!parent.has(item)) {
86
+ continue o;
87
+ }
88
+ }
89
+ invalidIndex[k] = true;
90
+ }
91
+ }
92
+ // 生成新的表达式
93
+ const validExp = new Map();
94
+ for (let i = 0; i < invalidIndex.length; i++) {
95
+ if (invalidIndex[i])
96
+ continue;
97
+ const oldExpList = validExp.get(change[i].flag);
98
+ const expList = oldExpList ?? [];
99
+ if (change[i].value) {
100
+ console.assert(typeof change[i].value == 'string', `change[${i}].value = ${change[i].value} 应当为字符串`);
101
+ expList.push(change[i].value);
102
+ }
103
+ if (!oldExpList)
104
+ validExp.set(change[i].flag, expList);
105
+ }
106
+ const newChange = json.info[0].change = [];
107
+ validExp.forEach((value, flag) => {
108
+ switch (value.length) {
109
+ case 0:
110
+ newChange.push({ flag });
111
+ break;
112
+ case 1:
113
+ newChange.push({ flag, value: value[0] });
114
+ break;
115
+ default:
116
+ newChange.push({ flag, value });
117
+ }
118
+ });
119
+ return indexes;
120
+ })();
121
+ // 移除后续表达式中冗余的内容
122
+ if (indexes.size == 0)
123
+ return;
124
+ for (let i = 1; i < json.info.length; i++) {
125
+ const changes = json.info[i].change;
126
+ if (!changes)
127
+ continue;
128
+ for (let k = changes.length - 1; k >= 0; k--) {
129
+ const change = json.info[i].change[k];
130
+ const values = change.value ? (Array.isArray(change.value) ? change.value : [change.value]) : [];
131
+ const tmpChange = {
132
+ flag: change.flag,
133
+ value: ''
134
+ };
135
+ for (let j = values.length - 1; j >= 0; j--) {
136
+ tmpChange.value = values[j];
137
+ const matcher = matchUpdateRule.runOnNode(tmpChange);
138
+ const matchIndex = untils_1.utils.findValueInIterable(this.urls, url => !!matcher(url));
139
+ if (matchIndex.every(it => indexes.has(it.index))) {
140
+ values.splice(j, 1);
141
+ }
142
+ }
143
+ if (values.length == 0)
144
+ delete json.info[i].change;
145
+ else if (values.length == 1)
146
+ change.value = values[0];
147
+ else
148
+ change.value = values;
149
+ }
150
+ }
151
+ }
152
+ limitJson(json) {
153
+ const lengthLimit = this.compilation.compilationEnv.read('VERSION_LENGTH_LIMIT');
154
+ if (lengthLimit == 0)
155
+ return;
156
+ let sum = 0;
157
+ for (let i = 0; i < json.info.length; i++) {
158
+ sum += JSON.stringify(json.info[i]).length;
159
+ if (sum > lengthLimit) {
160
+ if (i == 0)
161
+ json.info = [{ version: json.info[0].version }];
162
+ else
163
+ json.info.splice(i);
164
+ return;
165
+ }
166
+ }
167
+ }
168
+ }
169
+ exports.JsonBuilder = JsonBuilder;
170
+ /**
171
+ * 构建更新表达式
172
+ *
173
+ * 具体实现为使用字典树构建最优后缀匹配表达式,时间复杂度 O(N + M)
174
+ */
175
+ function createUpdateChangeExps(urls, refresh) {
176
+ function newNode() {
177
+ return {
178
+ next: new Array(128),
179
+ flag: false,
180
+ isEnd: false
181
+ };
182
+ }
183
+ const head = newNode();
184
+ const insert = (content, flag) => {
185
+ let cur = head;
186
+ for (let i = content.length - 1; i >= 0; i--) {
187
+ const index = content.charCodeAt(i);
188
+ if (cur.next[index]) {
189
+ cur = cur.next[index];
190
+ }
191
+ else {
192
+ cur = cur.next[index] = newNode();
193
+ }
194
+ }
195
+ cur.flag = flag;
196
+ cur.isEnd = true;
197
+ };
198
+ urls.forEach(it => insert(it, false));
199
+ for (let item of refresh) {
200
+ insert(item, true);
201
+ }
202
+ function dfs(node) {
203
+ if (node.isEnd)
204
+ return;
205
+ node.flag = true;
206
+ for (let next of node.next) {
207
+ if (next) {
208
+ dfs(next);
209
+ node.flag = node.flag && next.flag;
210
+ }
211
+ }
212
+ }
213
+ dfs(head);
214
+ let dfs2S = [];
215
+ const result = [];
216
+ function dfs2(node) {
217
+ if (node.flag) {
218
+ result.push(dfs2S.reduceRight((a, b) => a + b, ''));
219
+ return;
220
+ }
221
+ for (let i = 0; i < node.next.length; i++) {
222
+ const next = node.next[i];
223
+ if (next) {
224
+ dfs2S.push(String.fromCharCode(i));
225
+ dfs2(next);
226
+ dfs2S.pop();
227
+ }
228
+ }
229
+ }
230
+ dfs2(head);
231
+ return { flag: 'suf', value: result };
232
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FiniteConcurrencyFetcher = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ /** 支持并发控制的网络文件拉取工具 */
9
+ class FiniteConcurrencyFetcher {
10
+ constructor() {
11
+ this.fetchingCount = 0;
12
+ this.waitList = [];
13
+ this.limit = 100;
14
+ this.referer = 'swpp-backends';
15
+ this.userAgent = 'swpp-backends';
16
+ this.headers = {};
17
+ }
18
+ fetch(request) {
19
+ if (this.fetchingCount !== this.limit) {
20
+ return this.createFetchTask(request);
21
+ }
22
+ else {
23
+ return new Promise((resolve, reject) => {
24
+ this.waitList.push({ request, resolve, reject });
25
+ });
26
+ }
27
+ }
28
+ async createFetchTask(url) {
29
+ ++this.fetchingCount;
30
+ try {
31
+ return await fetch(url, {
32
+ referrer: this.referer,
33
+ headers: {
34
+ ...this.headers,
35
+ 'User-Agent': this.userAgent
36
+ }
37
+ });
38
+ }
39
+ finally {
40
+ --this.fetchingCount;
41
+ if (this.waitList.length !== 0) {
42
+ const item = this.waitList.pop();
43
+ this.createFetchTask(item.request)
44
+ .then(response => item.resolve(response))
45
+ .catch(err => item.reject(err));
46
+ }
47
+ }
48
+ }
49
+ getUrlContentType(url, response) {
50
+ let contentType;
51
+ if (url.endsWith('/')) {
52
+ contentType = 'html';
53
+ }
54
+ else {
55
+ contentType = path_1.default.extname(url);
56
+ }
57
+ if (!contentType) {
58
+ if (response)
59
+ contentType = response.headers.get('content-type') ?? '';
60
+ if (contentType.startsWith('text/'))
61
+ contentType = contentType.substring(5);
62
+ if (contentType === 'javascript')
63
+ contentType = 'script';
64
+ }
65
+ return contentType;
66
+ }
67
+ }
68
+ exports.FiniteConcurrencyFetcher = FiniteConcurrencyFetcher;