1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | import { __awaiter } from 'tslib';
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | const PARSE_TO_PAIRS = /([0-9]+[^0-9]+)/g;
|
17 | const PAIR_SPLIT = /^([0-9]+)([dhmsu]+)$/;
|
18 | function parseDurationToMs(duration) {
|
19 | const matches = [];
|
20 | let array;
|
21 | while ((array = PARSE_TO_PAIRS.exec(duration)) !== null) {
|
22 | matches.push(array[0]);
|
23 | }
|
24 | return matches
|
25 | .map(match => {
|
26 | const res = PAIR_SPLIT.exec(match);
|
27 | if (res === null) {
|
28 | throw new Error(`Not a valid duration: ${match}`);
|
29 | }
|
30 | let factor = 0;
|
31 | switch (res[2]) {
|
32 | case 'd':
|
33 | factor = 86400000;
|
34 | break;
|
35 | case 'h':
|
36 | factor = 3600000;
|
37 | break;
|
38 | case 'm':
|
39 | factor = 60000;
|
40 | break;
|
41 | case 's':
|
42 | factor = 1000;
|
43 | break;
|
44 | case 'u':
|
45 | factor = 1;
|
46 | break;
|
47 | default:
|
48 | throw new Error(`Not a valid duration unit: ${res[2]}`);
|
49 | }
|
50 | return parseInt(res[1]) * factor;
|
51 | })
|
52 | .reduce((total, value) => total + value, 0);
|
53 | }
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | const QUESTION_MARK = '[^/]';
|
63 | const WILD_SINGLE = '[^/]*';
|
64 | const WILD_OPEN = '(?:.+\\/)?';
|
65 | const TO_ESCAPE_BASE = [
|
66 | { replace: /\./g, with: '\\.' },
|
67 | { replace: /\+/g, with: '\\+' },
|
68 | { replace: /\*/g, with: WILD_SINGLE },
|
69 | ];
|
70 | const TO_ESCAPE_WILDCARD_QM = [
|
71 | ...TO_ESCAPE_BASE,
|
72 | { replace: /\?/g, with: QUESTION_MARK },
|
73 | ];
|
74 | const TO_ESCAPE_LITERAL_QM = [
|
75 | ...TO_ESCAPE_BASE,
|
76 | { replace: /\?/g, with: '\\?' },
|
77 | ];
|
78 | function globToRegex(glob, literalQuestionMark = false) {
|
79 | const toEscape = literalQuestionMark ? TO_ESCAPE_LITERAL_QM : TO_ESCAPE_WILDCARD_QM;
|
80 | const segments = glob.split('/').reverse();
|
81 | let regex = '';
|
82 | while (segments.length > 0) {
|
83 | const segment = segments.pop();
|
84 | if (segment === '**') {
|
85 | if (segments.length > 0) {
|
86 | regex += WILD_OPEN;
|
87 | }
|
88 | else {
|
89 | regex += '.*';
|
90 | }
|
91 | }
|
92 | else {
|
93 | const processed = toEscape.reduce((segment, escape) => segment.replace(escape.replace, escape.with), segment);
|
94 | regex += processed;
|
95 | if (segments.length > 0) {
|
96 | regex += '\\/';
|
97 | }
|
98 | }
|
99 | }
|
100 | return regex;
|
101 | }
|
102 |
|
103 | const DEFAULT_NAVIGATION_URLS = [
|
104 | '/**',
|
105 | '!/**/*.*',
|
106 | '!/**/*__*',
|
107 | '!/**/*__*/**',
|
108 | ];
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | class Generator {
|
115 | constructor(fs, baseHref) {
|
116 | this.fs = fs;
|
117 | this.baseHref = baseHref;
|
118 | }
|
119 | process(config) {
|
120 | var _a;
|
121 | return __awaiter(this, void 0, void 0, function* () {
|
122 | const unorderedHashTable = {};
|
123 | const assetGroups = yield this.processAssetGroups(config, unorderedHashTable);
|
124 | return {
|
125 | configVersion: 1,
|
126 | timestamp: Date.now(),
|
127 | appData: config.appData,
|
128 | index: joinUrls(this.baseHref, config.index),
|
129 | assetGroups,
|
130 | dataGroups: this.processDataGroups(config),
|
131 | hashTable: withOrderedKeys(unorderedHashTable),
|
132 | navigationUrls: processNavigationUrls(this.baseHref, config.navigationUrls),
|
133 | navigationRequestStrategy: (_a = config.navigationRequestStrategy) !== null && _a !== void 0 ? _a : 'performance',
|
134 | };
|
135 | });
|
136 | }
|
137 | processAssetGroups(config, hashTable) {
|
138 | return __awaiter(this, void 0, void 0, function* () {
|
139 |
|
140 | const allFiles = yield this.fs.list('/');
|
141 | const seenMap = new Set();
|
142 | const filesPerGroup = new Map();
|
143 |
|
144 | for (const group of (config.assetGroups || [])) {
|
145 | if (group.resources.versionedFiles) {
|
146 | throw new Error(`Asset-group '${group.name}' in 'ngsw-config.json' uses the 'versionedFiles' option, ` +
|
147 | 'which is no longer supported. Use \'files\' instead.');
|
148 | }
|
149 | const fileMatcher = globListToMatcher(group.resources.files || []);
|
150 | const matchedFiles = allFiles.filter(fileMatcher).filter(file => !seenMap.has(file)).sort();
|
151 | matchedFiles.forEach(file => seenMap.add(file));
|
152 | filesPerGroup.set(group, matchedFiles);
|
153 | }
|
154 |
|
155 | const allMatchedFiles = [].concat(...Array.from(filesPerGroup.values())).sort();
|
156 | const allMatchedHashes = yield processInBatches(allMatchedFiles, 500, file => this.fs.hash(file));
|
157 | allMatchedFiles.forEach((file, idx) => {
|
158 | hashTable[joinUrls(this.baseHref, file)] = allMatchedHashes[idx];
|
159 | });
|
160 |
|
161 | return Array.from(filesPerGroup.entries())
|
162 | .map(([group, matchedFiles]) => ({
|
163 | name: group.name,
|
164 | installMode: group.installMode || 'prefetch',
|
165 | updateMode: group.updateMode || group.installMode || 'prefetch',
|
166 | cacheQueryOptions: buildCacheQueryOptions(group.cacheQueryOptions),
|
167 | urls: matchedFiles.map(url => joinUrls(this.baseHref, url)),
|
168 | patterns: (group.resources.urls || []).map(url => urlToRegex(url, this.baseHref, true)),
|
169 | }));
|
170 | });
|
171 | }
|
172 | processDataGroups(config) {
|
173 | return (config.dataGroups || []).map(group => {
|
174 | return {
|
175 | name: group.name,
|
176 | patterns: group.urls.map(url => urlToRegex(url, this.baseHref, true)),
|
177 | strategy: group.cacheConfig.strategy || 'performance',
|
178 | maxSize: group.cacheConfig.maxSize,
|
179 | maxAge: parseDurationToMs(group.cacheConfig.maxAge),
|
180 | timeoutMs: group.cacheConfig.timeout && parseDurationToMs(group.cacheConfig.timeout),
|
181 | cacheOpaqueResponses: group.cacheConfig.cacheOpaqueResponses,
|
182 | cacheQueryOptions: buildCacheQueryOptions(group.cacheQueryOptions),
|
183 | version: group.version !== undefined ? group.version : 1,
|
184 | };
|
185 | });
|
186 | }
|
187 | }
|
188 | function processNavigationUrls(baseHref, urls = DEFAULT_NAVIGATION_URLS) {
|
189 | return urls.map(url => {
|
190 | const positive = !url.startsWith('!');
|
191 | url = positive ? url : url.slice(1);
|
192 | return { positive, regex: `^${urlToRegex(url, baseHref)}$` };
|
193 | });
|
194 | }
|
195 | function processInBatches(items, batchSize, processFn) {
|
196 | return __awaiter(this, void 0, void 0, function* () {
|
197 | const batches = [];
|
198 | for (let i = 0; i < items.length; i += batchSize) {
|
199 | batches.push(items.slice(i, i + batchSize));
|
200 | }
|
201 | return batches.reduce((prev, batch) => __awaiter(this, void 0, void 0, function* () { return (yield prev).concat(yield Promise.all(batch.map(item => processFn(item)))); }), Promise.resolve([]));
|
202 | });
|
203 | }
|
204 | function globListToMatcher(globs) {
|
205 | const patterns = globs.map(pattern => {
|
206 | if (pattern.startsWith('!')) {
|
207 | return {
|
208 | positive: false,
|
209 | regex: new RegExp('^' + globToRegex(pattern.slice(1)) + '$'),
|
210 | };
|
211 | }
|
212 | else {
|
213 | return {
|
214 | positive: true,
|
215 | regex: new RegExp('^' + globToRegex(pattern) + '$'),
|
216 | };
|
217 | }
|
218 | });
|
219 | return (file) => matches(file, patterns);
|
220 | }
|
221 | function matches(file, patterns) {
|
222 | const res = patterns.reduce((isMatch, pattern) => {
|
223 | if (pattern.positive) {
|
224 | return isMatch || pattern.regex.test(file);
|
225 | }
|
226 | else {
|
227 | return isMatch && !pattern.regex.test(file);
|
228 | }
|
229 | }, false);
|
230 | return res;
|
231 | }
|
232 | function urlToRegex(url, baseHref, literalQuestionMark) {
|
233 | if (!url.startsWith('/') && url.indexOf('://') === -1) {
|
234 |
|
235 |
|
236 |
|
237 | url = joinUrls(baseHref.replace(/^\.(?=\/)/, ''), url);
|
238 | }
|
239 | return globToRegex(url, literalQuestionMark);
|
240 | }
|
241 | function joinUrls(a, b) {
|
242 | if (a.endsWith('/') && b.startsWith('/')) {
|
243 | return a + b.slice(1);
|
244 | }
|
245 | else if (!a.endsWith('/') && !b.startsWith('/')) {
|
246 | return a + '/' + b;
|
247 | }
|
248 | return a + b;
|
249 | }
|
250 | function withOrderedKeys(unorderedObj) {
|
251 | const orderedObj = {};
|
252 | Object.keys(unorderedObj).sort().forEach(key => orderedObj[key] = unorderedObj[key]);
|
253 | return orderedObj;
|
254 | }
|
255 | function buildCacheQueryOptions(inOptions) {
|
256 | return Object.assign({ ignoreVary: true }, inOptions);
|
257 | }
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | export { Generator };
|
280 |
|