swpp-backends 3.0.0-alpha.3 → 3.0.0-alpha.310
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/{types → dist}/index.d.ts +5 -4
- package/dist/index.js +25 -17
- package/{types → dist}/swpp/FileParser.d.ts +11 -6
- package/dist/swpp/FileParser.js +27 -13
- package/{types → dist}/swpp/JsonBuilder.d.ts +1 -3
- package/dist/swpp/JsonBuilder.js +3 -7
- package/dist/swpp/NetworkFileHandler.js +3 -3
- package/{types → dist}/swpp/ResourcesScanner.d.ts +11 -14
- package/dist/swpp/ResourcesScanner.js +94 -70
- package/{types → dist}/swpp/SwCompiler.d.ts +21 -8
- package/dist/swpp/SwCompiler.js +49 -15
- package/{types → dist}/swpp/config/ConfigCluster.d.ts +98 -63
- package/dist/swpp/config/ConfigCluster.js +60 -48
- package/dist/swpp/config/ConfigLoader.d.ts +78 -0
- package/dist/swpp/config/ConfigLoader.js +180 -89
- package/dist/swpp/config/SpecialConfig.d.ts +29 -0
- package/dist/swpp/config/SpecialConfig.js +53 -0
- package/{types → dist}/swpp/database/CompilationEnv.d.ts +19 -16
- package/dist/swpp/database/CompilationEnv.js +41 -197
- package/dist/swpp/database/CompilationFileParser.d.ts +81 -0
- package/dist/swpp/database/CompilationFileParser.js +267 -0
- package/{types → dist}/swpp/database/CrossDepCode.d.ts +4 -0
- package/dist/swpp/database/CrossDepCode.js +27 -1
- package/dist/swpp/database/CrossEnv.js +19 -2
- package/{types → dist}/swpp/database/DomCode.d.ts +4 -1
- package/dist/swpp/database/DomCode.js +34 -5
- package/dist/swpp/database/KeyValueDatabase.d.ts +72 -0
- package/dist/swpp/database/KeyValueDatabase.js +122 -27
- package/{types → dist}/swpp/database/RuntimeCoreCode.d.ts +2 -1
- package/dist/swpp/database/RuntimeCoreCode.js +6 -5
- package/{types → dist}/swpp/database/RuntimeDepCode.d.ts +8 -14
- package/dist/swpp/database/RuntimeDepCode.js +30 -50
- package/{types → dist}/swpp/database/RuntimeEventCode.d.ts +4 -0
- package/dist/swpp/database/RuntimeEventCode.js +8 -1
- package/{types → dist}/swpp/database/RuntimeKeyValueDatabase.d.ts +1 -1
- package/dist/swpp/database/RuntimeKeyValueDatabase.js +2 -2
- package/dist/swpp/debug/CallChainRecorder.d.ts +5 -0
- package/dist/swpp/debug/CallChainRecorder.js +29 -0
- package/{types → dist}/swpp/untils.d.ts +25 -18
- package/dist/swpp/untils.js +47 -53
- package/package.json +2 -2
- package/types/DomBuilder.d.ts +0 -6
- package/types/FileAnalyzer.d.ts +0 -96
- package/types/ServiceWorkerBuilder.d.ts +0 -7
- package/types/SwppConfig.d.ts +0 -139
- package/types/SwppRules.d.ts +0 -117
- package/types/UpdateJsonBuilder.d.ts +0 -44
- package/types/Utils.d.ts +0 -43
- package/types/Variant.d.ts +0 -58
- package/types/VersionAnalyzer.d.ts +0 -27
- package/types/browser/ServiceWorkerRuntimeTypes.d.ts +0 -18
- package/types/swpp/RuntimeEnv.d.ts +0 -39
- package/types/swpp/SwCodeInject.d.ts +0 -17
- package/types/swpp/config/ConfigLoader.d.ts +0 -53
- package/types/swpp/database/KeyValueDatabase.d.ts +0 -53
- package/types/swpp/database/RuntimeEnv.d.ts +0 -5
- /package/{types → dist}/swpp/NetworkFileHandler.d.ts +0 -0
- /package/{types → dist}/swpp/database/CrossEnv.d.ts +0 -0
|
@@ -1,45 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
4
|
};
|
|
28
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
6
|
exports.AllowNotFoundEnum = exports.CompilationEnv = void 0;
|
|
30
7
|
const fs_1 = __importDefault(require("fs"));
|
|
31
|
-
const FileParser_1 = require("../FileParser");
|
|
32
8
|
const NetworkFileHandler_1 = require("../NetworkFileHandler");
|
|
9
|
+
const ResourcesScanner_1 = require("../ResourcesScanner");
|
|
33
10
|
const untils_1 = require("../untils");
|
|
34
11
|
const KeyValueDatabase_1 = require("./KeyValueDatabase");
|
|
35
|
-
const HTMLParser = __importStar(require("fast-html-parser"));
|
|
36
12
|
/**
|
|
37
13
|
* 仅在编译期生效的配置项
|
|
38
14
|
*/
|
|
39
15
|
class CompilationEnv extends KeyValueDatabase_1.KeyValueDatabase {
|
|
40
|
-
constructor(
|
|
41
|
-
super();
|
|
42
|
-
this.lazyInit(buildCommon(this
|
|
16
|
+
constructor() {
|
|
17
|
+
super('CompilationEnv');
|
|
18
|
+
this.lazyInit(buildCommon(this));
|
|
43
19
|
}
|
|
44
20
|
}
|
|
45
21
|
exports.CompilationEnv = CompilationEnv;
|
|
@@ -53,32 +29,36 @@ var AllowNotFoundEnum;
|
|
|
53
29
|
/** 拒绝任意形式的 404 */
|
|
54
30
|
AllowNotFoundEnum[AllowNotFoundEnum["REJECT_ALL"] = 2] = "REJECT_ALL";
|
|
55
31
|
})(AllowNotFoundEnum || (exports.AllowNotFoundEnum = AllowNotFoundEnum = {}));
|
|
56
|
-
function buildCommon(_env
|
|
32
|
+
function buildCommon(_env) {
|
|
57
33
|
const env = _env;
|
|
58
34
|
return {
|
|
59
35
|
DOMAIN_HOST: (0, KeyValueDatabase_1.buildEnv)({
|
|
60
|
-
default:
|
|
36
|
+
default: new URL("https://www.example.com"),
|
|
61
37
|
checker(value) {
|
|
62
|
-
if (value === 'www.example.com')
|
|
63
|
-
return {
|
|
64
|
-
value, message: '应当手动设置一个域名而非使用默认域名'
|
|
65
|
-
};
|
|
66
|
-
if (value.includes('/'))
|
|
38
|
+
if (value.host === 'www.example.com')
|
|
67
39
|
return {
|
|
68
|
-
value, message: '
|
|
40
|
+
value, message: 'DOMAIN_HOST 必须手动设置而非使用默认值'
|
|
69
41
|
};
|
|
70
|
-
if (value.
|
|
42
|
+
if (value.hash || value.search)
|
|
71
43
|
return {
|
|
72
44
|
value, message: '传入的域名不应当包含查询参数和片段标识符'
|
|
73
45
|
};
|
|
74
|
-
if (
|
|
46
|
+
if (value.protocol !== 'https:' && value.host !== '127.0.0.1' && value.host !== 'localhost')
|
|
75
47
|
return {
|
|
76
|
-
value, message: '
|
|
48
|
+
value, message: '传入的 URL 必须使用 https 协议'
|
|
77
49
|
};
|
|
78
50
|
return false;
|
|
79
51
|
}
|
|
80
52
|
}),
|
|
81
|
-
|
|
53
|
+
SERVICE_WORKER: (0, KeyValueDatabase_1.buildEnv)({
|
|
54
|
+
default: 'sw',
|
|
55
|
+
checker(value) {
|
|
56
|
+
return value.endsWith('.js') ? {
|
|
57
|
+
value, message: 'SW 文件名不需要包含拓展名'
|
|
58
|
+
} : false;
|
|
59
|
+
}
|
|
60
|
+
}),
|
|
61
|
+
/** HTML 数量限制,设置为 <= 0 表示不限制 */
|
|
82
62
|
JSON_HTML_LIMIT: (0, KeyValueDatabase_1.buildEnv)({
|
|
83
63
|
default: 0
|
|
84
64
|
}),
|
|
@@ -96,16 +76,20 @@ function buildCommon(_env, crossEnv, crossCode) {
|
|
|
96
76
|
return false;
|
|
97
77
|
}
|
|
98
78
|
}),
|
|
99
|
-
/**
|
|
100
|
-
|
|
79
|
+
/** swpp 的 JSON 文件的基本信息 */
|
|
80
|
+
SWPP_JSON_FILE: (0, KeyValueDatabase_1.buildEnv)({
|
|
101
81
|
default: {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
82
|
+
swppPath: 'swpp',
|
|
83
|
+
trackerPath: 'tracker.json',
|
|
84
|
+
versionPath: 'update.json',
|
|
85
|
+
async fetchVersionFile() {
|
|
86
|
+
const baseUrl = env.read('DOMAIN_HOST');
|
|
87
|
+
const fetcher = env.read('NETWORK_FILE_FETCHER');
|
|
88
|
+
const isNotFound = env.read('isNotFound');
|
|
107
89
|
try {
|
|
108
|
-
const
|
|
90
|
+
const swppPath = (0, KeyValueDatabase_1.readThisValue)(this, 'swppPath');
|
|
91
|
+
const versionPath = (0, KeyValueDatabase_1.readThisValue)(this, 'versionPath');
|
|
92
|
+
const response = await fetcher.fetch(untils_1.utils.splicingUrl(baseUrl, swppPath, versionPath));
|
|
109
93
|
if (!isNotFound.response(response)) {
|
|
110
94
|
const json = await response.json();
|
|
111
95
|
return json;
|
|
@@ -116,11 +100,14 @@ function buildCommon(_env, crossEnv, crossCode) {
|
|
|
116
100
|
throw e;
|
|
117
101
|
}
|
|
118
102
|
return { global: 0, info: [] };
|
|
103
|
+
},
|
|
104
|
+
async fetchTrackerFile(compilation) {
|
|
105
|
+
return await ResourcesScanner_1.FileUpdateTracker.parserJsonFromNetwork(compilation);
|
|
119
106
|
}
|
|
120
107
|
}
|
|
121
108
|
}),
|
|
122
109
|
/** 读取一个本地文件 */
|
|
123
|
-
|
|
110
|
+
readLocalFile: (0, KeyValueDatabase_1.buildEnv)({
|
|
124
111
|
default: (path) => {
|
|
125
112
|
return new Promise((resolve, reject) => {
|
|
126
113
|
fs_1.default.readFile(path, 'utf8', (err, data) => {
|
|
@@ -133,11 +120,11 @@ function buildCommon(_env, crossEnv, crossCode) {
|
|
|
133
120
|
}
|
|
134
121
|
}),
|
|
135
122
|
/** 拉取网络文件 */
|
|
136
|
-
|
|
123
|
+
NETWORK_FILE_FETCHER: (0, KeyValueDatabase_1.buildEnv)({
|
|
137
124
|
default: new NetworkFileHandler_1.FiniteConcurrencyFetcher()
|
|
138
125
|
}),
|
|
139
126
|
/** 判断文件是否是 404 */
|
|
140
|
-
|
|
127
|
+
isNotFound: (0, KeyValueDatabase_1.buildEnv)({
|
|
141
128
|
default: {
|
|
142
129
|
response: (response) => response.status == 404,
|
|
143
130
|
error: (err) => err?.cause?.code === 'ENOTFOUND'
|
|
@@ -157,152 +144,9 @@ function buildCommon(_env, crossEnv, crossCode) {
|
|
|
157
144
|
}
|
|
158
145
|
}
|
|
159
146
|
}),
|
|
160
|
-
/**
|
|
161
|
-
|
|
162
|
-
default:
|
|
147
|
+
/** 检查一个链接是否是稳定的(也就是 URL 不变其返回的结果永远不变) */
|
|
148
|
+
isStable: (0, KeyValueDatabase_1.buildEnv)({
|
|
149
|
+
default: (_url) => false
|
|
163
150
|
})
|
|
164
151
|
};
|
|
165
152
|
}
|
|
166
|
-
function createRegister(env, crossEnv, crossCode) {
|
|
167
|
-
const register = new FileParser_1.FileParserRegistry({
|
|
168
|
-
compilationEnv: env,
|
|
169
|
-
crossEnv,
|
|
170
|
-
crossDep: crossCode
|
|
171
|
-
});
|
|
172
|
-
register.registry('html', (0, FileParser_1.buildFileParser)({
|
|
173
|
-
readFromLocal(compilation, path) {
|
|
174
|
-
return compilation.compilationEnv.read('LOCAL_FILE_READER')(path);
|
|
175
|
-
},
|
|
176
|
-
readFromNetwork(_, response) {
|
|
177
|
-
return response.text();
|
|
178
|
-
},
|
|
179
|
-
async extractUrls(compilation, content) {
|
|
180
|
-
const host = compilation.compilationEnv.read("DOMAIN_HOST");
|
|
181
|
-
const html = HTMLParser.parse(content, {
|
|
182
|
-
script: true, style: true
|
|
183
|
-
});
|
|
184
|
-
const queue = [html];
|
|
185
|
-
const result = new Set();
|
|
186
|
-
async function handleItem(item) {
|
|
187
|
-
queue.push(...item.childNodes);
|
|
188
|
-
switch (item.tagName.toLowerCase()) {
|
|
189
|
-
case 'script': {
|
|
190
|
-
if (!register.containsType('script'))
|
|
191
|
-
break;
|
|
192
|
-
const src = item.attributes.src;
|
|
193
|
-
if (src) {
|
|
194
|
-
const son = await register.parserContent('script', item.rawText);
|
|
195
|
-
son.forEach(it => result.add(it));
|
|
196
|
-
}
|
|
197
|
-
else if (!untils_1.utils.isSameHost(src, host)) {
|
|
198
|
-
result.add(src);
|
|
199
|
-
}
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
case 'link': {
|
|
203
|
-
if (item.attributes.rel !== 'preconnect') {
|
|
204
|
-
const href = item.attributes.href;
|
|
205
|
-
if (!untils_1.utils.isSameHost(href, host))
|
|
206
|
-
result.add(href);
|
|
207
|
-
}
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
case 'img':
|
|
211
|
-
case 'source':
|
|
212
|
-
case 'iframe':
|
|
213
|
-
case 'embed': {
|
|
214
|
-
const src = item.attributes.src;
|
|
215
|
-
if (src && !untils_1.utils.isSameHost(src, host)) {
|
|
216
|
-
result.add(src);
|
|
217
|
-
}
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
case 'object': {
|
|
221
|
-
const data = item.attributes.data;
|
|
222
|
-
if (data && !untils_1.utils.isSameHost(data, host)) {
|
|
223
|
-
result.add(data);
|
|
224
|
-
}
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
case 'style': {
|
|
228
|
-
const son = await register.parserContent('css', item.rawText);
|
|
229
|
-
son.forEach(it => result.add(it));
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
do {
|
|
235
|
-
const item = queue.pop();
|
|
236
|
-
try {
|
|
237
|
-
await handleItem(item);
|
|
238
|
-
}
|
|
239
|
-
catch (e) {
|
|
240
|
-
untils_1.utils.printError('PARSER HTML', e);
|
|
241
|
-
}
|
|
242
|
-
} while (queue.length > 0);
|
|
243
|
-
return result;
|
|
244
|
-
}
|
|
245
|
-
}));
|
|
246
|
-
register.registry('css', (0, FileParser_1.buildFileParser)({
|
|
247
|
-
readFromLocal(compilation, path) {
|
|
248
|
-
return compilation.compilationEnv.read('LOCAL_FILE_READER')(path);
|
|
249
|
-
},
|
|
250
|
-
readFromNetwork(_, response) {
|
|
251
|
-
return response.text();
|
|
252
|
-
},
|
|
253
|
-
async extractUrls(compilation, content) {
|
|
254
|
-
const host = compilation.compilationEnv.read('DOMAIN_HOST');
|
|
255
|
-
const urls = new Set();
|
|
256
|
-
/** 从指定位置开始查询注释 */
|
|
257
|
-
const findComment = (tag, start) => {
|
|
258
|
-
for (let i = start; i < content.length;) {
|
|
259
|
-
const item = content[i];
|
|
260
|
-
switch (item) {
|
|
261
|
-
case tag[0]:
|
|
262
|
-
if (content[i + 1] === tag[1])
|
|
263
|
-
return i;
|
|
264
|
-
++i;
|
|
265
|
-
break;
|
|
266
|
-
case '"':
|
|
267
|
-
case '\'':
|
|
268
|
-
while (true) {
|
|
269
|
-
const index = content.indexOf(item, i + 1);
|
|
270
|
-
if (index < 0)
|
|
271
|
-
return -1;
|
|
272
|
-
i = index + 1;
|
|
273
|
-
if (content[index - 1] !== '\\')
|
|
274
|
-
break;
|
|
275
|
-
}
|
|
276
|
-
break;
|
|
277
|
-
default:
|
|
278
|
-
++i;
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
return -1;
|
|
283
|
-
};
|
|
284
|
-
for (let i = 0; i < content.length;) {
|
|
285
|
-
const left = findComment('/*', i);
|
|
286
|
-
let sub;
|
|
287
|
-
if (left === -1) {
|
|
288
|
-
sub = content.substring(i);
|
|
289
|
-
i = Number.MAX_VALUE;
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
sub = content.substring(i, left);
|
|
293
|
-
const right = findComment('*/', left + 2);
|
|
294
|
-
if (right === -1)
|
|
295
|
-
i = Number.MAX_VALUE;
|
|
296
|
-
else
|
|
297
|
-
i = right + 2;
|
|
298
|
-
}
|
|
299
|
-
sub.match(/(url\(.*?\))|(@import\s+['"].*?['"])|((https?:)?\/\/[^\s/$.?#].\S*)/g)
|
|
300
|
-
?.map(it => it.replace(/(^url\(\s*(['"]?))|((['"]?\s*)\)$)|(^@import\s+['"])|(['"]$)/g, ''))
|
|
301
|
-
?.filter(it => !untils_1.utils.isSameHost(it, host))
|
|
302
|
-
?.forEach(it => urls.add(it));
|
|
303
|
-
}
|
|
304
|
-
return urls;
|
|
305
|
-
}
|
|
306
|
-
}));
|
|
307
|
-
return register;
|
|
308
|
-
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import { CompilationData } from '../SwCompiler';
|
|
3
|
+
import { KeyValueDatabase } from './KeyValueDatabase';
|
|
4
|
+
export type COMMON_TYPE_COMP_FP = ReturnType<typeof buildCommon>;
|
|
5
|
+
export declare class CompilationFileParser extends KeyValueDatabase<FileParser<crypto.BinaryLike>, COMMON_TYPE_COMP_FP> {
|
|
6
|
+
constructor();
|
|
7
|
+
/** 解析本地文件 */
|
|
8
|
+
parserLocalFile(path: string, cb?: (content: crypto.BinaryLike) => void, force?: boolean): Promise<Set<string>>;
|
|
9
|
+
/** 解析网络文件 */
|
|
10
|
+
parserNetworkFile(response: Response, callback?: (content: crypto.BinaryLike) => Promise<any> | any): Promise<Set<string>>;
|
|
11
|
+
/**
|
|
12
|
+
* 解析指定的 URL
|
|
13
|
+
* @param url 链接
|
|
14
|
+
* @param isCached 该链接指向的资源是否需要缓存
|
|
15
|
+
*/
|
|
16
|
+
parserUrlFile(url: string, isCached: boolean): Promise<FileMark>;
|
|
17
|
+
/** 解析指定类型的文件内容 */
|
|
18
|
+
parserContent(type: string, content: string): Promise<Set<string>>;
|
|
19
|
+
}
|
|
20
|
+
declare function buildCommon($this: any): {
|
|
21
|
+
readonly html: {
|
|
22
|
+
readonly default: {
|
|
23
|
+
readonly readFromLocal: (compilation: CompilationData, path: string) => Promise<string>;
|
|
24
|
+
readonly readFromNetwork: (_: CompilationData, response: Response) => Promise<string>;
|
|
25
|
+
readonly extractUrls: (compilation: CompilationData, content: string) => Promise<Set<string>>;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
readonly css: {
|
|
29
|
+
readonly default: {
|
|
30
|
+
readonly readFromLocal: (compilation: CompilationData, path: string) => Promise<string>;
|
|
31
|
+
readonly readFromNetwork: (_: CompilationData, response: Response) => Promise<string>;
|
|
32
|
+
readonly extractUrls: (compilation: CompilationData, content: string) => Promise<Set<string>>;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* 文件处理器
|
|
38
|
+
*
|
|
39
|
+
* 用于处理指定类型的文件,从中提取文件的 mark 和外部链接列表
|
|
40
|
+
*/
|
|
41
|
+
export interface FileParser<T extends crypto.BinaryLike> {
|
|
42
|
+
/**
|
|
43
|
+
* 从本地读取一个文件
|
|
44
|
+
* @param compilation 编译期依赖
|
|
45
|
+
* @param path 文件路径
|
|
46
|
+
*/
|
|
47
|
+
readFromLocal(compilation: CompilationData, path: string): Promise<T>;
|
|
48
|
+
/**
|
|
49
|
+
* 从网络读取一个文件
|
|
50
|
+
* @param compilation 编译期依赖
|
|
51
|
+
* @param response 拉取的结果
|
|
52
|
+
*/
|
|
53
|
+
readFromNetwork(compilation: CompilationData, response: Response): Promise<T>;
|
|
54
|
+
/**
|
|
55
|
+
* 从文件内容中提取 URL
|
|
56
|
+
* @param compilation 编译期依赖
|
|
57
|
+
* @param content 文件内容
|
|
58
|
+
*/
|
|
59
|
+
extractUrls(compilation: CompilationData, content: T): Promise<Set<string>>;
|
|
60
|
+
/**
|
|
61
|
+
* 计算一个链接对应的资源的标识符及其内部资源
|
|
62
|
+
* @return 返回 undefined/null 表示使用缺省逻辑
|
|
63
|
+
*/
|
|
64
|
+
calcUrl?(url: string): Promise<Omit<FileMark, 'file'> | undefined | null>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 存储文件标识信息
|
|
68
|
+
*/
|
|
69
|
+
export interface FileMark {
|
|
70
|
+
/** URL */
|
|
71
|
+
file: string;
|
|
72
|
+
/**
|
|
73
|
+
* 文件标识符或子文件列表
|
|
74
|
+
*
|
|
75
|
+
* 如果链接为稳定链接,则为子文件列表,否则为文件标识符
|
|
76
|
+
*/
|
|
77
|
+
mark: string | Set<string>;
|
|
78
|
+
/** URL 列表 */
|
|
79
|
+
urls: Set<string>;
|
|
80
|
+
}
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.CompilationFileParser = void 0;
|
|
30
|
+
const HTMLParser = __importStar(require("fast-html-parser"));
|
|
31
|
+
const path_1 = __importDefault(require("path"));
|
|
32
|
+
const untils_1 = require("../untils");
|
|
33
|
+
const KeyValueDatabase_1 = require("./KeyValueDatabase");
|
|
34
|
+
class CompilationFileParser extends KeyValueDatabase_1.KeyValueDatabase {
|
|
35
|
+
constructor() {
|
|
36
|
+
super('CompilationFileParser');
|
|
37
|
+
this.lazyInit(buildCommon(this));
|
|
38
|
+
}
|
|
39
|
+
/** 解析本地文件 */
|
|
40
|
+
async parserLocalFile(path, cb, force) {
|
|
41
|
+
const extname = path_1.default.extname(path).substring(1);
|
|
42
|
+
if (this.hasKey(extname)) {
|
|
43
|
+
const parser = this.read(extname);
|
|
44
|
+
const content = await parser.readFromLocal(this.compilation, path);
|
|
45
|
+
cb?.(content);
|
|
46
|
+
return await parser.extractUrls(this.compilation, content);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
if (force && cb) {
|
|
50
|
+
const reader = this.compilation.compilationEnv.read('readLocalFile');
|
|
51
|
+
const content = await reader(path);
|
|
52
|
+
cb(content);
|
|
53
|
+
}
|
|
54
|
+
return new Set();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** 解析网络文件 */
|
|
58
|
+
async parserNetworkFile(response, callback) {
|
|
59
|
+
const fileHandler = this.compilation.compilationEnv.read('NETWORK_FILE_FETCHER');
|
|
60
|
+
const contentType = fileHandler.getUrlContentType(response.url, response);
|
|
61
|
+
if (this.hasKey(contentType)) {
|
|
62
|
+
const parser = this.read(contentType);
|
|
63
|
+
const content = await parser.readFromNetwork(this.compilation, response);
|
|
64
|
+
if (callback)
|
|
65
|
+
await callback(content);
|
|
66
|
+
return await parser.extractUrls(this.compilation, content);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
if (callback) {
|
|
70
|
+
const blob = await response.blob();
|
|
71
|
+
const array = await blob.stream().getReader().read();
|
|
72
|
+
callback(array.value);
|
|
73
|
+
}
|
|
74
|
+
return new Set();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 解析指定的 URL
|
|
79
|
+
* @param url 链接
|
|
80
|
+
* @param isCached 该链接指向的资源是否需要缓存
|
|
81
|
+
*/
|
|
82
|
+
async parserUrlFile(url, isCached) {
|
|
83
|
+
const fileHandler = this.compilation.compilationEnv.read('NETWORK_FILE_FETCHER');
|
|
84
|
+
const contentType = fileHandler.getUrlContentType(url);
|
|
85
|
+
if (!contentType && !isCached)
|
|
86
|
+
return { file: url, mark: '', urls: new Set() };
|
|
87
|
+
const parser = this.hasKey(contentType) ? this.read(contentType) : undefined;
|
|
88
|
+
if (!parser && !isCached)
|
|
89
|
+
return { file: url, mark: '', urls: new Set() };
|
|
90
|
+
if (parser?.calcUrl) {
|
|
91
|
+
const result = await parser.calcUrl(url);
|
|
92
|
+
if (result)
|
|
93
|
+
return {
|
|
94
|
+
file: url,
|
|
95
|
+
...result
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const fetcher = this.compilation.compilationEnv.read('NETWORK_FILE_FETCHER');
|
|
99
|
+
const urls = new Set();
|
|
100
|
+
let mark = '';
|
|
101
|
+
await fetcher.fetch(url)
|
|
102
|
+
.then(response => this.parserNetworkFile(response, isCached ? content => {
|
|
103
|
+
mark = untils_1.utils.calcHash(content);
|
|
104
|
+
} : undefined))
|
|
105
|
+
.then(urls => urls.forEach(it => urls.add(it)));
|
|
106
|
+
return { file: url, mark, urls };
|
|
107
|
+
}
|
|
108
|
+
/** 解析指定类型的文件内容 */
|
|
109
|
+
async parserContent(type, content) {
|
|
110
|
+
if (!this.hasKey(type))
|
|
111
|
+
return new Set();
|
|
112
|
+
const parser = this.read(type);
|
|
113
|
+
return await parser.extractUrls(this.compilation, content);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.CompilationFileParser = CompilationFileParser;
|
|
117
|
+
function buildCommon($this) {
|
|
118
|
+
const registry = $this;
|
|
119
|
+
return {
|
|
120
|
+
html: {
|
|
121
|
+
default: {
|
|
122
|
+
readFromLocal(compilation, path) {
|
|
123
|
+
return compilation.compilationEnv.read('readLocalFile')(path);
|
|
124
|
+
},
|
|
125
|
+
readFromNetwork(_, response) {
|
|
126
|
+
return response.text();
|
|
127
|
+
},
|
|
128
|
+
async extractUrls(compilation, content) {
|
|
129
|
+
const baseUrl = compilation.compilationEnv.read("DOMAIN_HOST");
|
|
130
|
+
const html = HTMLParser.parse(content, {
|
|
131
|
+
script: true, style: true
|
|
132
|
+
});
|
|
133
|
+
const queue = [html];
|
|
134
|
+
const result = new Set();
|
|
135
|
+
async function handleItem(item) {
|
|
136
|
+
queue.push(...(item.childNodes ?? []));
|
|
137
|
+
if (!item.tagName)
|
|
138
|
+
return;
|
|
139
|
+
switch (item.tagName.toLowerCase()) {
|
|
140
|
+
case 'script': {
|
|
141
|
+
const src = item.attributes.src;
|
|
142
|
+
if (src) {
|
|
143
|
+
if (!untils_1.utils.isSameHost(src, baseUrl)) {
|
|
144
|
+
result.add(src);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const son = await registry.parserContent('js', item.rawText);
|
|
149
|
+
son.forEach(it => result.add(it));
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case 'link': {
|
|
154
|
+
if (item.attributes.rel !== 'preconnect') {
|
|
155
|
+
const href = item.attributes.href;
|
|
156
|
+
if (!href) {
|
|
157
|
+
const son = await registry.parserContent('css', item.rawText);
|
|
158
|
+
son.forEach(it => result.add(it));
|
|
159
|
+
}
|
|
160
|
+
else if (!untils_1.utils.isSameHost(href, baseUrl)) {
|
|
161
|
+
result.add(href);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case 'img':
|
|
167
|
+
case 'source':
|
|
168
|
+
case 'iframe':
|
|
169
|
+
case 'embed': {
|
|
170
|
+
const src = item.attributes.src;
|
|
171
|
+
if (src && !untils_1.utils.isSameHost(src, baseUrl)) {
|
|
172
|
+
result.add(src);
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'object': {
|
|
177
|
+
const data = item.attributes.data;
|
|
178
|
+
if (data && !untils_1.utils.isSameHost(data, baseUrl)) {
|
|
179
|
+
result.add(data);
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case 'style': {
|
|
184
|
+
const son = await registry.parserContent('css', item.rawText);
|
|
185
|
+
son.forEach(it => result.add(it));
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
do {
|
|
192
|
+
const item = queue.pop();
|
|
193
|
+
await handleItem(item);
|
|
194
|
+
} while (queue.length > 0);
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
throw new untils_1.RuntimeException(untils_1.exceptionNames.error, '解析 HTML 时出现错误', { cause: e });
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
css: {
|
|
204
|
+
default: {
|
|
205
|
+
readFromLocal(compilation, path) {
|
|
206
|
+
return compilation.compilationEnv.read('readLocalFile')(path);
|
|
207
|
+
},
|
|
208
|
+
readFromNetwork(_, response) {
|
|
209
|
+
return response.text();
|
|
210
|
+
},
|
|
211
|
+
async extractUrls(compilation, content) {
|
|
212
|
+
const baseUrl = compilation.compilationEnv.read('DOMAIN_HOST');
|
|
213
|
+
const urls = new Set();
|
|
214
|
+
/** 从指定位置开始查询注释 */
|
|
215
|
+
const findComment = (tag, start) => {
|
|
216
|
+
for (let i = start; i < content.length;) {
|
|
217
|
+
const item = content[i];
|
|
218
|
+
switch (item) {
|
|
219
|
+
case tag[0]:
|
|
220
|
+
if (content[i + 1] === tag[1])
|
|
221
|
+
return i;
|
|
222
|
+
++i;
|
|
223
|
+
break;
|
|
224
|
+
case '"':
|
|
225
|
+
case '\'':
|
|
226
|
+
while (true) {
|
|
227
|
+
const index = content.indexOf(item, i + 1);
|
|
228
|
+
if (index < 0)
|
|
229
|
+
return -1;
|
|
230
|
+
i = index + 1;
|
|
231
|
+
if (content[index - 1] !== '\\')
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
default:
|
|
236
|
+
++i;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return -1;
|
|
241
|
+
};
|
|
242
|
+
for (let i = 0; i < content.length;) {
|
|
243
|
+
const left = findComment('/*', i);
|
|
244
|
+
let sub;
|
|
245
|
+
if (left === -1) {
|
|
246
|
+
sub = content.substring(i);
|
|
247
|
+
i = Number.MAX_VALUE;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
sub = content.substring(i, left);
|
|
251
|
+
const right = findComment('*/', left + 2);
|
|
252
|
+
if (right === -1)
|
|
253
|
+
i = Number.MAX_VALUE;
|
|
254
|
+
else
|
|
255
|
+
i = right + 2;
|
|
256
|
+
}
|
|
257
|
+
sub.match(/(url\(.*?\))|(@import\s+['"].*?['"])|((https?:)?\/\/[^\s/$.?#].\S*)/g)
|
|
258
|
+
?.map(it => it.replace(/(^url\(\s*(['"]?))|((['"]?\s*)\)$)|(^@import\s+['"])|(['"]$)/g, ''))
|
|
259
|
+
?.filter(it => !untils_1.utils.isSameHost(it, baseUrl))
|
|
260
|
+
?.forEach(it => urls.add(it));
|
|
261
|
+
}
|
|
262
|
+
return urls;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
@@ -22,6 +22,10 @@ declare function buildCommon(): {
|
|
|
22
22
|
readonly matchCacheRule: {
|
|
23
23
|
readonly default: FunctionInBrowserAndNode<[_url: URL], number | false | null | undefined>;
|
|
24
24
|
};
|
|
25
|
+
/** 归一化 URL */
|
|
26
|
+
readonly normalizeUrl: {
|
|
27
|
+
readonly default: FunctionInBrowserAndNode<[url: string], string>;
|
|
28
|
+
};
|
|
25
29
|
/** 匹配缓存更新规则 */
|
|
26
30
|
readonly matchUpdateRule: {
|
|
27
31
|
readonly default: FunctionInBrowserAndNode<[exp: UpdateChangeExp], (url: string) => boolean | undefined | null>;
|