mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(service-worker): allow specifying maxAge for entire application (#49601)
This commit adds an `applicationMaxAge` to the service worker configuration. When set, it will only assign a cached version to clients within the `maxAge`. Afterwards, it will ignored any expired application versions and fetch exclusively from the network. The default is `undefined`, for which the behaviour is the same as it currently is. PR Close #49601
This commit is contained in:
committed by
Andrew Kushnir
parent
925de81490
commit
8ddce80a0b
@@ -347,7 +347,7 @@ A request is considered to be a navigation request if:
|
||||
* The URL must not contain a file extension (that is, a `.`) in the last path segment
|
||||
* The URL must not contain `__`
|
||||
|
||||
HELPFUL: To configure whether navigation requests are sent through to the network or not, see the [navigationRequestStrategy](#navigationrequeststrategy) section.
|
||||
HELPFUL: To configure whether navigation requests are sent through to the network or not, see the [navigationRequestStrategy](#navigationrequeststrategy) section and [applicationMaxAge](#application-max-age) sections.
|
||||
|
||||
#### Matching navigation request URLs
|
||||
|
||||
@@ -391,3 +391,7 @@ This optional property enables you to configure how the service worker handles n
|
||||
| `'freshness'` | Passes the requests through to the network and falls back to the `performance` behavior when offline. This value is useful when the server redirects the navigation requests elsewhere using a `3xx` HTTP redirect status code. Reasons for using this value include: <ul> <li> Redirecting to an authentication website when authentication is not handled by the application </li> <li> Redirecting specific URLs to avoid breaking existing links/bookmarks after a website redesign </li> <li> Redirecting to a different website, such as a server-status page, while a page is temporarily down </li> </ul> |
|
||||
|
||||
IMPORTANT: The `freshness` strategy usually results in more requests sent to the server, which can increase response latency. It is recommended that you use the default performance strategy whenever possible.
|
||||
|
||||
### `applicationMaxAge`
|
||||
|
||||
This optional property enables you to configure how long the service worker will cache any requests. Within the `maxAge`, files will be served from cache. Beyond it, all requests will only be served from the network, including asset and data requests.
|
||||
|
||||
@@ -26,6 +26,8 @@ export interface Config {
|
||||
// (undocumented)
|
||||
appData?: {};
|
||||
// (undocumented)
|
||||
applicationMaxAge?: Duration;
|
||||
// (undocumented)
|
||||
assetGroups?: AssetGroup[];
|
||||
// (undocumented)
|
||||
dataGroups?: DataGroup[];
|
||||
|
||||
@@ -177,6 +177,10 @@
|
||||
],
|
||||
"default": "performance",
|
||||
"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."
|
||||
},
|
||||
"applicationMaxAge": {
|
||||
"type": "string",
|
||||
"description": "Indicates how long the entire application is allowed to remain in the cache before being considered invalid and bypassed. 'maxAge' is a duration string, using the following unit suffixes: d= days, h= hours, m= minutes, s= seconds, u= milliseconds. For example, the string '3d12h' will cache content for up to three and a half days."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -43,6 +43,9 @@ export class Generator {
|
||||
hashTable: withOrderedKeys(unorderedHashTable),
|
||||
navigationUrls: processNavigationUrls(this.baseHref, config.navigationUrls),
|
||||
navigationRequestStrategy: config.navigationRequestStrategy ?? 'performance',
|
||||
applicationMaxAge: config.applicationMaxAge
|
||||
? parseDurationToMs(config.applicationMaxAge)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface Config {
|
||||
dataGroups?: DataGroup[];
|
||||
navigationUrls?: string[];
|
||||
navigationRequestStrategy?: 'freshness' | 'performance';
|
||||
applicationMaxAge?: Duration;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,6 +58,7 @@ describe('Generator', () => {
|
||||
'http://example.com/included',
|
||||
'!http://example.com/excluded',
|
||||
],
|
||||
applicationMaxAge: '1d',
|
||||
});
|
||||
|
||||
expect(config).toEqual({
|
||||
@@ -115,6 +116,7 @@ describe('Generator', () => {
|
||||
{positive: false, regex: '^http:\\/\\/example\\.com\\/excluded$'},
|
||||
],
|
||||
navigationRequestStrategy: 'performance',
|
||||
applicationMaxAge: 86400000,
|
||||
hashTable: {
|
||||
'/test/foo/test.html': '18f6f8eb7b1c23d2bb61bff028b83d867a9e4643',
|
||||
'/test/index.html': 'a54d88e06612d820bc3be72877c74f257b561b19',
|
||||
@@ -208,6 +210,7 @@ describe('Generator', () => {
|
||||
{positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$'},
|
||||
],
|
||||
navigationRequestStrategy: 'performance',
|
||||
applicationMaxAge: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -234,6 +237,7 @@ describe('Generator', () => {
|
||||
{positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$'},
|
||||
],
|
||||
navigationRequestStrategy: 'performance',
|
||||
applicationMaxAge: undefined,
|
||||
hashTable: {},
|
||||
});
|
||||
});
|
||||
@@ -425,6 +429,7 @@ describe('Generator', () => {
|
||||
{positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$'},
|
||||
],
|
||||
navigationRequestStrategy: 'performance',
|
||||
applicationMaxAge: undefined,
|
||||
hashTable: {},
|
||||
});
|
||||
});
|
||||
@@ -498,6 +503,7 @@ describe('Generator', () => {
|
||||
{positive: false, regex: '^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$'},
|
||||
],
|
||||
navigationRequestStrategy: 'performance',
|
||||
applicationMaxAge: undefined,
|
||||
hashTable: {
|
||||
'/index.html': 'a54d88e06612d820bc3be72877c74f257b561b19',
|
||||
'/main.js': '41347a66676cdc0516934c76d9d13010df420f2c',
|
||||
|
||||
@@ -497,10 +497,14 @@ export class Driver implements Debuggable, UpdateSource {
|
||||
// Decide which version of the app to use to serve this request. This is asynchronous as in
|
||||
// some cases, a record will need to be written to disk about the assignment that is made.
|
||||
const appVersion = await this.assignVersion(event);
|
||||
// If there's a configured max age, check whether this version is within that age.
|
||||
const isVersionWithinMaxAge =
|
||||
appVersion?.manifest.applicationMaxAge === undefined ||
|
||||
this.adapter.time - appVersion.manifest.timestamp < appVersion.manifest.applicationMaxAge;
|
||||
let res: Response | null = null;
|
||||
|
||||
try {
|
||||
if (appVersion !== null) {
|
||||
if (appVersion !== null && isVersionWithinMaxAge) {
|
||||
try {
|
||||
// Handle the request. First try the AppVersion. If that doesn't work, fall back on the
|
||||
// network.
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface Manifest {
|
||||
dataGroups?: DataGroupConfig[];
|
||||
navigationUrls: {positive: boolean; regex: string}[];
|
||||
navigationRequestStrategy: 'freshness' | 'performance';
|
||||
applicationMaxAge?: number;
|
||||
hashTable: {[url: string]: string};
|
||||
}
|
||||
|
||||
|
||||
@@ -2562,6 +2562,70 @@ import {envIsSupported} from '../testing/utils';
|
||||
return {server, scope, driver};
|
||||
}
|
||||
});
|
||||
|
||||
describe('applicationMaxAge', () => {
|
||||
// When within the `applicationMaxAge`, the app should act like `performance` mode
|
||||
// When outside of it, it should act like `freshness` mode, except it also uncaches asset
|
||||
// requests
|
||||
it("doesn't create navigate requests within the maxAge", async () => {
|
||||
const {server, scope, driver} = createSwForMaxAge();
|
||||
|
||||
await makeRequest(scope, '/foo.txt');
|
||||
await driver.initialized;
|
||||
await server.clearRequests();
|
||||
|
||||
// Create multiple requests to prove no navigation OR asset requests were made.
|
||||
// By default the navigation request is not sent, it's replaced
|
||||
// with the index request - thus, the `this is foo` value.
|
||||
expect(await makeNavigationRequest(scope, '/', '')).toBe('this is foo');
|
||||
expect(await makeNavigationRequest(scope, '/foo', '')).toBe('this is foo');
|
||||
|
||||
expect(await makeRequest(scope, '/foo.txt')).toBe('this is foo');
|
||||
expect(await makeRequest(scope, '/bar.txt')).toBe('this is bar');
|
||||
|
||||
server.assertNoOtherRequests();
|
||||
});
|
||||
|
||||
it('creates navigate requests outside the maxAge', async () => {
|
||||
const {server, scope, driver} = createSwForMaxAge();
|
||||
|
||||
await makeRequest(scope, '/foo.txt');
|
||||
await driver.initialized;
|
||||
await server.clearRequests();
|
||||
|
||||
await scope.advance(3000);
|
||||
|
||||
// Create multiple requests to prove the navigation and asset requests are all made
|
||||
// When enabled, the navigation request is made each time and not replaced
|
||||
// with the index request - thus, the `null` value.
|
||||
expect(await makeNavigationRequest(scope, '/', '')).toBe(null);
|
||||
expect(await makeNavigationRequest(scope, '/foo', '')).toBe(null);
|
||||
|
||||
expect(await makeRequest(scope, '/foo.txt')).toBe('this is foo');
|
||||
expect(await makeRequest(scope, '/bar.txt')).toBe('this is bar');
|
||||
|
||||
server.assertSawRequestFor('/');
|
||||
server.assertSawRequestFor('/foo');
|
||||
server.assertSawRequestFor('/foo.txt');
|
||||
server.assertSawRequestFor('/bar.txt');
|
||||
server.assertNoOtherRequests();
|
||||
});
|
||||
|
||||
function createSwForMaxAge() {
|
||||
const scope = new SwTestHarnessBuilder().build();
|
||||
// set the timestamp of the manifest using the server time so it's always "new" on test start
|
||||
const maxAgeManifest: Manifest = {
|
||||
...manifest,
|
||||
timestamp: scope.time,
|
||||
applicationMaxAge: 2000,
|
||||
};
|
||||
const server = serverBuilderBase.withManifest(maxAgeManifest).build();
|
||||
const driver = new Driver(scope, scope, new CacheDatabase(scope));
|
||||
scope.updateServerState(server);
|
||||
|
||||
return {server, scope, driver};
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user