feat(service-worker): add support for ? in SW config globbing (#24105)

The globbing is used in the following sections:
- `assetGroups` > `resources` > `files`/`versionedFiles`
- `assetGroups` > `resources` > `urls`
- `dataGroups` > `urls`
- `navigationUrls`

Query params are ignored for `files`/`versionedFiles` and
`navigationUrls`, but they are still taken into account for
`assetGroups`/`dataGroups` `urls`. To avoid a breaking change, `?` is
matched literally for these patterns.

PR Close #24105
This commit is contained in:
George Kalpakas
2018-05-24 17:51:45 +03:00
committed by Miško Hevery
parent 94076c934c
commit 250527ca68
4 changed files with 46 additions and 21 deletions

View File

@ -75,7 +75,7 @@ export class Generator {
installMode: group.installMode || 'prefetch',
updateMode: group.updateMode || group.installMode || 'prefetch',
urls: matchedFiles.map(url => joinUrls(this.baseHref, url)),
patterns: (group.resources.urls || []).map(url => urlToRegex(url, this.baseHref)),
patterns: (group.resources.urls || []).map(url => urlToRegex(url, this.baseHref, true)),
};
}));
}
@ -84,7 +84,7 @@ export class Generator {
return (config.dataGroups || []).map(group => {
return {
name: group.name,
patterns: group.urls.map(url => urlToRegex(url, this.baseHref)),
patterns: group.urls.map(url => urlToRegex(url, this.baseHref, true)),
strategy: group.cacheConfig.strategy || 'performance',
maxSize: group.cacheConfig.maxSize,
maxAge: parseDurationToMs(group.cacheConfig.maxAge),
@ -132,12 +132,12 @@ function matches(file: string, patterns: {positive: boolean, regex: RegExp}[]):
return res;
}
function urlToRegex(url: string, baseHref: string): string {
function urlToRegex(url: string, baseHref: string, literalQuestionMark?: boolean): string {
if (!url.startsWith('/') && url.indexOf('://') === -1) {
url = joinUrls(baseHref, url);
}
return globToRegex(url);
return globToRegex(url, literalQuestionMark);
}
function joinUrls(a: string, b: string): string {

View File

@ -6,17 +6,26 @@
* found in the LICENSE file at https://angular.io/license
*/
const WILD_SINGLE = '[^\\/]*';
const QUESTION_MARK = '[^/]';
const WILD_SINGLE = '[^/]*';
const WILD_OPEN = '(?:.+\\/)?';
const TO_ESCAPE = [
const TO_ESCAPE_BASE = [
{replace: /\./g, with: '\\.'},
{replace: /\?/g, with: '\\?'},
{replace: /\+/g, with: '\\+'},
{replace: /\*/g, with: WILD_SINGLE},
];
const TO_ESCAPE_WILDCARD_QM = [
...TO_ESCAPE_BASE,
{replace: /\?/g, with: QUESTION_MARK},
];
const TO_ESCAPE_LITERAL_QM = [
...TO_ESCAPE_BASE,
{replace: /\?/g, with: '\\?'},
];
export function globToRegex(glob: string): string {
export function globToRegex(glob: string, literalQuestionMark = false): string {
const toEscape = literalQuestionMark ? TO_ESCAPE_LITERAL_QM : TO_ESCAPE_WILDCARD_QM;
const segments = glob.split('/').reverse();
let regex: string = '';
while (segments.length > 0) {
@ -28,7 +37,7 @@ export function globToRegex(glob: string): string {
regex += '.*';
}
} else {
const processed = TO_ESCAPE.reduce(
const processed = toEscape.reduce(
(segment, escape) => segment.replace(escape.replace, escape.with), segment);
regex += processed;
if (segments.length > 0) {

View File

@ -14,6 +14,9 @@ import {MockFilesystem} from '../testing/mock';
it('generates a correct config', done => {
const fs = new MockFilesystem({
'/index.html': 'This is a test',
'/main.css': 'This is a CSS file',
'/main.js': 'This is a JS file',
'/main.ts': 'This is a TS file',
'/test.txt': 'Another test',
'/foo/test.html': 'Another test',
'/ignored/x.html': 'should be ignored',
@ -28,8 +31,9 @@ import {MockFilesystem} from '../testing/mock';
name: 'test',
resources: {
files: [
'/**/*.html', '!/ignored/**',
// '/*.html',
'/**/*.html',
'/**/*.?s',
'!/ignored/**',
],
versionedFiles: [
'/**/*.txt',
@ -46,6 +50,7 @@ import {MockFilesystem} from '../testing/mock';
urls: [
'/api/**',
'relapi/**',
'https://example.com/**/*?with+escaped+chars',
],
cacheConfig: {
maxSize: 100,
@ -56,8 +61,9 @@ import {MockFilesystem} from '../testing/mock';
navigationUrls: [
'/included/absolute/**',
'!/excluded/absolute/**',
'/included/some/url?with+escaped+chars',
'/included/some/url/with+escaped+chars',
'!excluded/relative/*.txt',
'!/api/?*',
'http://example.com/included',
'!http://example.com/excluded',
],
@ -76,17 +82,23 @@ import {MockFilesystem} from '../testing/mock';
urls: [
'/test/foo/test.html',
'/test/index.html',
'/test/main.js',
'/test/main.ts',
'/test/test.txt',
],
patterns: [
'\\/absolute\\/.*',
'\\/some\\/url\\?with\\+escaped\\+chars',
'\\/test\\/relative\\/[^\\/]*\\.txt',
'\\/test\\/relative\\/[^/]*\\.txt',
]
}],
dataGroups: [{
name: 'other',
patterns: ['\\/api\\/.*', '\\/test\\/relapi\\/.*'],
patterns: [
'\\/api\\/.*',
'\\/test\\/relapi\\/.*',
'https:\\/\\/example\\.com\\/(?:.+\\/)?[^/]*\\?with\\+escaped\\+chars',
],
strategy: 'performance',
maxSize: 100,
maxAge: 259200000,
@ -96,14 +108,17 @@ import {MockFilesystem} from '../testing/mock';
navigationUrls: [
{positive: true, regex: '^\\/included\\/absolute\\/.*$'},
{positive: false, regex: '^\\/excluded\\/absolute\\/.*$'},
{positive: true, regex: '^\\/included\\/some\\/url\\?with\\+escaped\\+chars$'},
{positive: false, regex: '^\\/test\\/excluded\\/relative\\/[^\\/]*\\.txt$'},
{positive: true, regex: '^\\/included\\/some\\/url\\/with\\+escaped\\+chars$'},
{positive: false, regex: '^\\/test\\/excluded\\/relative\\/[^/]*\\.txt$'},
{positive: false, regex: '^\\/api\\/[^/][^/]*$'},
{positive: true, regex: '^http:\\/\\/example\\.com\\/included$'},
{positive: false, regex: '^http:\\/\\/example\\.com\\/excluded$'},
],
hashTable: {
'/test/foo/test.html': '18f6f8eb7b1c23d2bb61bff028b83d867a9e4643',
'/test/index.html': 'a54d88e06612d820bc3be72877c74f257b561b19',
'/test/main.js': '41347a66676cdc0516934c76d9d13010df420f2c',
'/test/main.ts': '7d333e31f0bfc4f8152732bb211a93629484c035',
'/test/test.txt': '18f6f8eb7b1c23d2bb61bff028b83d867a9e4643'
}
});
@ -129,9 +144,9 @@ import {MockFilesystem} from '../testing/mock';
dataGroups: [],
navigationUrls: [
{positive: true, regex: '^\\/.*$'},
{positive: false, regex: '^\\/(?:.+\\/)?[^\\/]*\\.[^\\/]*$'},
{positive: false, regex: '^\\/(?:.+\\/)?[^\\/]*__[^\\/]*$'},
{positive: false, regex: '^\\/(?:.+\\/)?[^\\/]*__[^\\/]*\\/.*$'},
{positive: false, regex: '^\\/(?:.+\\/)?[^/]*\\.[^/]*$'},
{positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*$'},
{positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$'},
],
hashTable: {}
});