Skip to content

Commit a206852

Browse files
klemenoslajmhevery
authored andcommittedSep 22, 2020
feat(service-worker): add the option to prefer network for navigation requests (#38565)
This commit introduces a new option for the service worker, called `navigationRequestStrategy`, which adds the possibility to force the service worker to always create a network request for navigation requests. This enables the server redirects while retaining the offline behavior. Fixes #38194 PR Close #38565
1 parent 145ab3d commit a206852

File tree

13 files changed

+120
-0
lines changed

13 files changed

+120
-0
lines changed
 

‎aio/content/guide/service-worker-config.md

+35
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,12 @@ By default, these criteria are:
267267
1. The URL must not contain a file extension (i.e. a `.`) in the last path segment.
268268
2. The URL must not contain `__`.
269269

270+
<div class="alert is-helpful">
271+
272+
To configure whether navigation requests are sent through to the network or not, see the [navigationRequestStrategy](#navigation-request-strategy) section.
273+
274+
</div>
275+
270276
### Matching navigation request URLs
271277

272278
While these default criteria are fine in most cases, it is sometimes desirable to configure different rules. For example, you may want to ignore specific routes (that are not part of the Angular app) and pass them through to the server.
@@ -285,3 +291,32 @@ If the field is omitted, it defaults to:
285291
'!/**/*__*/**', // Exclude URLs containing `__` in any other segment.
286292
]
287293
```
294+
295+
{@a navigation-request-strategy}
296+
297+
## `navigationRequestStrategy`
298+
299+
This optional property enables you to configure how the service worker handles navigation requests:
300+
301+
```json
302+
{
303+
"navigationRequestStrategy": "freshness"
304+
}
305+
```
306+
307+
Possible values:
308+
309+
- `'performance'`: The default setting. Serves the specified [index file](#index-file), which is typically cached.
310+
- `'freshness'`: Passes the requests through to the network and falls back to the `performance` behavior when offline.
311+
This value is useful when the server redirects the navigation requests elsewhere using an HTTP redirect (3xx status code).
312+
Reasons for using this value include:
313+
- Redirecting to an authentication website when authentication is not handled by the application.
314+
- Redirecting specific URLs to avoid breaking existing links/bookmarks after a website redesign.
315+
- Redirecting to a different website, such as a server-status page, while a page is temporarily down.
316+
317+
<div class="alert is-important">
318+
319+
The `freshness` strategy usually results in more requests sent to the server, which can increase response latency.
320+
It is recommended that you use the default performance strategy whenever possible.
321+
322+
</div>

‎goldens/public-api/service-worker/config/config.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export declare interface Config {
1414
assetGroups?: AssetGroup[];
1515
dataGroups?: DataGroup[];
1616
index: string;
17+
navigationRequestStrategy?: 'freshness' | 'performance';
1718
navigationUrls?: string[];
1819
}
1920

‎packages/service-worker/config/schema.json

+8
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@
161161
"type": "string"
162162
},
163163
"uniqueItems": true
164+
},
165+
"navigationRequestStrategy": {
166+
"enum": [
167+
"freshness",
168+
"performance"
169+
],
170+
"default": "performance",
171+
"description": "The Angular service worker can use two request strategies for navigation requests. 'performance', the default, skips navigation requests. The other strategy, 'freshness', forces all navigation requests through the network."
164172
}
165173
},
166174
"required": [

‎packages/service-worker/config/src/generator.ts

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export class Generator {
3939
dataGroups: this.processDataGroups(config),
4040
hashTable: withOrderedKeys(unorderedHashTable),
4141
navigationUrls: processNavigationUrls(this.baseHref, config.navigationUrls),
42+
navigationRequestStrategy: config.navigationRequestStrategy ?? 'performance',
4243
};
4344
}
4445

‎packages/service-worker/config/src/in.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface Config {
2727
assetGroups?: AssetGroup[];
2828
dataGroups?: DataGroup[];
2929
navigationUrls?: string[];
30+
navigationRequestStrategy?: 'freshness'|'performance';
3031
}
3132

3233
/**

‎packages/service-worker/config/test/generator_spec.ts

+3
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ describe('Generator', () => {
117117
{positive: true, regex: '^http:\\/\\/example\\.com\\/included$'},
118118
{positive: false, regex: '^http:\\/\\/example\\.com\\/excluded$'},
119119
],
120+
navigationRequestStrategy: 'performance',
120121
hashTable: {
121122
'/test/foo/test.html': '18f6f8eb7b1c23d2bb61bff028b83d867a9e4643',
122123
'/test/index.html': 'a54d88e06612d820bc3be72877c74f257b561b19',
@@ -149,6 +150,7 @@ describe('Generator', () => {
149150
{positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*$'},
150151
{positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$'},
151152
],
153+
navigationRequestStrategy: 'performance',
152154
hashTable: {},
153155
});
154156
});
@@ -249,6 +251,7 @@ describe('Generator', () => {
249251
{positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*$'},
250252
{positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$'},
251253
],
254+
navigationRequestStrategy: 'performance',
252255
hashTable: {
253256
'/index.html': 'a54d88e06612d820bc3be72877c74f257b561b19',
254257
'/main.js': '41347a66676cdc0516934c76d9d13010df420f2c',

‎packages/service-worker/test/integration_spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const manifest: Manifest = {
4747
cacheQueryOptions: {ignoreVary: true},
4848
}],
4949
navigationUrls: [],
50+
navigationRequestStrategy: 'performance',
5051
hashTable: tmpHashTableForFs(dist),
5152
};
5253

@@ -64,6 +65,7 @@ const manifestUpdate: Manifest = {
6465
cacheQueryOptions: {ignoreVary: true},
6566
}],
6667
navigationUrls: [],
68+
navigationRequestStrategy: 'performance',
6769
hashTable: tmpHashTableForFs(distUpdate),
6870
};
6971

‎packages/service-worker/worker/src/app-version.ts

+12
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,18 @@ export class AppVersion implements UpdateSource {
185185
// Next, check if this is a navigation request for a route. Detect circular
186186
// navigations by checking if the request URL is the same as the index URL.
187187
if (this.adapter.normalizeUrl(req.url) !== this.indexUrl && this.isNavigationRequest(req)) {
188+
if (this.manifest.navigationRequestStrategy === 'freshness') {
189+
// For navigation requests the freshness was configured. The request will always go trough
190+
// the network and fallback to default `handleFetch` behavior in case of failure.
191+
try {
192+
return await this.scope.fetch(req);
193+
} catch {
194+
// Navigation request failed - application is likely offline.
195+
// Proceed forward to the default `handleFetch` behavior, where
196+
// `indexUrl` will be requested and it should be available in the cache.
197+
}
198+
}
199+
188200
// This was a navigation request. Re-enter `handleFetch` with a request for
189201
// the URL.
190202
return this.handleFetch(this.adapter.newRequest(this.indexUrl), context);

‎packages/service-worker/worker/src/manifest.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface Manifest {
1818
assetGroups?: AssetGroupConfig[];
1919
dataGroups?: DataGroupConfig[];
2020
navigationUrls: {positive: boolean, regex: string}[];
21+
navigationRequestStrategy: 'freshness'|'performance';
2122
hashTable: {[url: string]: string};
2223
}
2324

‎packages/service-worker/worker/test/data_spec.ts

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ const manifest: Manifest = {
9393
},
9494
],
9595
navigationUrls: [],
96+
navigationRequestStrategy: 'performance',
9697
hashTable: tmpHashTableForFs(dist),
9798
};
9899

‎packages/service-worker/worker/test/happy_spec.ts

+53
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const brokenManifest: Manifest = {
7474
}],
7575
dataGroups: [],
7676
navigationUrls: processNavigationUrls(''),
77+
navigationRequestStrategy: 'performance',
7778
hashTable: tmpHashTableForFs(brokenFs, {'/foo.txt': true}),
7879
};
7980

@@ -105,6 +106,7 @@ const brokenLazyManifest: Manifest = {
105106
],
106107
dataGroups: [],
107108
navigationUrls: processNavigationUrls(''),
109+
navigationRequestStrategy: 'performance',
108110
hashTable: tmpHashTableForFs(brokenFs, {'/bar.txt': true}),
109111
};
110112

@@ -198,6 +200,7 @@ const manifest: Manifest = {
198200
},
199201
],
200202
navigationUrls: processNavigationUrls(''),
203+
navigationRequestStrategy: 'performance',
201204
hashTable: tmpHashTableForFs(dist),
202205
};
203206

@@ -256,6 +259,7 @@ const manifestUpdate: Manifest = {
256259
'!/ignored/file1',
257260
'!/ignored/dir/**',
258261
]),
262+
navigationRequestStrategy: 'performance',
259263
hashTable: tmpHashTableForFs(distUpdate),
260264
};
261265

@@ -983,6 +987,7 @@ describe('Driver', () => {
983987
},
984988
],
985989
navigationUrls: processNavigationUrls(baseHref),
990+
navigationRequestStrategy: 'performance',
986991
hashTable: tmpHashTableForFs(distDir, {}, baseHref),
987992
});
988993

@@ -1343,6 +1348,7 @@ describe('Driver', () => {
13431348
}
13441349
],
13451350
navigationUrls: processNavigationUrls('./'),
1351+
navigationRequestStrategy: 'performance',
13461352
hashTable: tmpHashTableForFs(distDir, {}, './'),
13471353
});
13481354

@@ -1754,6 +1760,7 @@ describe('Driver', () => {
17541760
}],
17551761
dataGroups: [],
17561762
navigationUrls: processNavigationUrls(''),
1763+
navigationRequestStrategy: 'performance',
17571764
hashTable: tmpHashTableForFs(fileSystem),
17581765
};
17591766

@@ -1922,6 +1929,52 @@ describe('Driver', () => {
19221929
});
19231930
});
19241931
});
1932+
1933+
describe('navigationRequestStrategy', () => {
1934+
it('doesn\'t create navigate request in performance mode', async () => {
1935+
await makeRequest(scope, '/foo.txt');
1936+
await driver.initialized;
1937+
await server.clearRequests();
1938+
1939+
// Create multiple navigation requests to prove no navigation request was made.
1940+
// By default the navigation request is not sent, it's replaced
1941+
// with the index request - thus, the `this is foo` value.
1942+
expect(await makeNavigationRequest(scope, '/', '')).toBe('this is foo');
1943+
expect(await makeNavigationRequest(scope, '/foo', '')).toBe('this is foo');
1944+
expect(await makeNavigationRequest(scope, '/foo/bar', '')).toBe('this is foo');
1945+
1946+
server.assertNoOtherRequests();
1947+
});
1948+
1949+
it('sends the request to the server in freshness mode', async () => {
1950+
const {server, scope, driver} = createSwForFreshnessStrategy();
1951+
1952+
await makeRequest(scope, '/foo.txt');
1953+
await driver.initialized;
1954+
await server.clearRequests();
1955+
1956+
// Create multiple navigation requests to prove the navigation request is constantly made.
1957+
// When enabled, the navigation request is made each time and not replaced
1958+
// with the index request - thus, the `null` value.
1959+
expect(await makeNavigationRequest(scope, '/', '')).toBe(null);
1960+
expect(await makeNavigationRequest(scope, '/foo', '')).toBe(null);
1961+
expect(await makeNavigationRequest(scope, '/foo/bar', '')).toBe(null);
1962+
1963+
server.assertSawRequestFor('/');
1964+
server.assertSawRequestFor('/foo');
1965+
server.assertSawRequestFor('/foo/bar');
1966+
server.assertNoOtherRequests();
1967+
});
1968+
1969+
function createSwForFreshnessStrategy() {
1970+
const freshnessManifest: Manifest = {...manifest, navigationRequestStrategy: 'freshness'};
1971+
const server = serverBuilderBase.withManifest(freshnessManifest).build();
1972+
const scope = new SwTestHarnessBuilder().withServerState(server).build();
1973+
const driver = new Driver(scope, scope, new CacheDatabase(scope, scope));
1974+
1975+
return {server, scope, driver};
1976+
}
1977+
});
19251978
});
19261979
})();
19271980

‎packages/service-worker/worker/testing/mock.ts

+1
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ export function tmpManifestSingleAssetGroup(fs: MockFileSystem): Manifest {
248248
},
249249
],
250250
navigationUrls: [],
251+
navigationRequestStrategy: 'performance',
251252
hashTable,
252253
};
253254
}

‎packages/service-worker/worker/testing/scope.ts

+1
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ export class ConfigBuilder {
346346
index: '/index.html',
347347
assetGroups,
348348
navigationUrls: [],
349+
navigationRequestStrategy: 'performance',
349350
hashTable,
350351
};
351352
}

0 commit comments

Comments
 (0)
Please sign in to comment.