mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[flutter_tools] reduce initial cache size on web (#56103)
This commit is contained in:
parent
960b75719e
commit
2d26cbb2bf
@ -357,7 +357,8 @@ class WebServiceWorker extends Target {
|
||||
final Map<String, String> urlToHash = <String, String>{};
|
||||
for (final File file in contents) {
|
||||
// Do not force caching of source maps.
|
||||
if (file.path.endsWith('main.dart.js.map')) {
|
||||
if (file.path.endsWith('main.dart.js.map') ||
|
||||
file.path.endsWith('.part.js.map')) {
|
||||
continue;
|
||||
}
|
||||
final String url = globals.fs.path.toUri(
|
||||
@ -376,7 +377,15 @@ class WebServiceWorker extends Target {
|
||||
final File serviceWorkerFile = environment.outputDir
|
||||
.childFile('flutter_service_worker.js');
|
||||
final Depfile depfile = Depfile(contents, <File>[serviceWorkerFile]);
|
||||
final String serviceWorker = generateServiceWorker(urlToHash);
|
||||
final String serviceWorker = generateServiceWorker(urlToHash, <String>[
|
||||
'main.dart.js',
|
||||
'/',
|
||||
'index.html',
|
||||
'assets/LICENSE',
|
||||
'assets/AssetManifest.json',
|
||||
if (urlToHash.containsKey('assets/FontManifest.json'))
|
||||
'assets/FontManifest.json',
|
||||
]);
|
||||
serviceWorkerFile
|
||||
.writeAsStringSync(serviceWorker);
|
||||
final DepfileService depfileService = DepfileService(
|
||||
@ -393,40 +402,113 @@ class WebServiceWorker extends Target {
|
||||
/// Generate a service worker with an app-specific cache name a map of
|
||||
/// resource files.
|
||||
///
|
||||
/// We embed file hashes directly into the worker so that the byte for byte
|
||||
/// The tool embeds file hashes directly into the worker so that the byte for byte
|
||||
/// invalidation will automatically reactivate workers whenever a new
|
||||
/// version is deployed.
|
||||
// TODO(jonahwilliams): on re-activate, only evict stale assets.
|
||||
String generateServiceWorker(Map<String, String> resources) {
|
||||
String generateServiceWorker(Map<String, String> resources, List<String> coreBundle) {
|
||||
return '''
|
||||
'use strict';
|
||||
const MANIFEST = 'flutter-app-manifest';
|
||||
const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
${resources.entries.map((MapEntry<String, String> entry) => '"${entry.key}": "${entry.value}"').join(",\n")}
|
||||
};
|
||||
|
||||
self.addEventListener('activate', function (event) {
|
||||
event.waitUntil(
|
||||
caches.keys().then(function (cacheName) {
|
||||
return caches.delete(cacheName);
|
||||
}).then(function (_) {
|
||||
return caches.open(CACHE_NAME);
|
||||
}).then(function (cache) {
|
||||
return cache.addAll(Object.keys(RESOURCES));
|
||||
// The application shell files that are downloaded before a service worker can
|
||||
// start.
|
||||
const CORE = [
|
||||
${coreBundle.map((String file) => '"$file"').join(',\n')}];
|
||||
|
||||
// During install, the TEMP cache is populated with the application shell files.
|
||||
self.addEventListener("install", (event) => {
|
||||
return event.waitUntil(
|
||||
caches.open(TEMP).then((cache) => {
|
||||
return cache.addAll(CORE);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function (event) {
|
||||
event.respondWith(
|
||||
caches.match(event.request)
|
||||
.then(function (response) {
|
||||
if (response) {
|
||||
return response;
|
||||
// During activate, the cache is populated with the temp files downloaded in
|
||||
// install. If this service worker is upgrading from one with a saved
|
||||
// MANIFEST, then use this to retain unchanged resource files.
|
||||
self.addEventListener("activate", function(event) {
|
||||
return event.waitUntil(async function() {
|
||||
try {
|
||||
var contentCache = await caches.open(CACHE_NAME);
|
||||
var tempCache = await caches.open(TEMP);
|
||||
var manifestCache = await caches.open(MANIFEST);
|
||||
var manifest = await manifestCache.match('manifest');
|
||||
|
||||
// When there is no prior manifest, clear the entire cache.
|
||||
if (!manifest) {
|
||||
await caches.delete(CACHE_NAME);
|
||||
for (var request of await tempCache.keys()) {
|
||||
var response = await tempCache.match(request);
|
||||
await contentCache.put(request, response);
|
||||
}
|
||||
return fetch(event.request);
|
||||
await caches.delete(TEMP);
|
||||
// Save the manifest to make future upgrades efficient.
|
||||
await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
|
||||
return;
|
||||
}
|
||||
|
||||
var oldManifest = await manifest.json();
|
||||
var origin = self.location.origin;
|
||||
for (var request of await contentCache.keys()) {
|
||||
var key = request.url.substring(origin.length + 1);
|
||||
if (key == "") {
|
||||
key = "/";
|
||||
}
|
||||
// If a resource from the old manifest is not in the new cache, or if
|
||||
// the MD5 sum has changed, delete it. Otherwise the resource is left
|
||||
// in the cache and can be reused by the new service worker.
|
||||
if (!RESOURCES[key] || RESOURCES[key] != oldManifest[key]) {
|
||||
await contentCache.delete(request);
|
||||
}
|
||||
}
|
||||
// Populate the cache with the app shell TEMP files, potentially overwriting
|
||||
// cache files preserved above.
|
||||
for (var request of await tempCache.keys()) {
|
||||
var response = await tempCache.match(request);
|
||||
await contentCache.put(request, response);
|
||||
}
|
||||
await caches.delete(TEMP);
|
||||
// Save the manifest to make future upgrades efficient.
|
||||
await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
|
||||
return;
|
||||
} catch (err) {
|
||||
// On an unhandled exception the state of the cache cannot be guaranteed.
|
||||
console.error('Failed to upgrade service worker: ' + err);
|
||||
await caches.delete(CACHE_NAME);
|
||||
await caches.delete(TEMP);
|
||||
await caches.delete(MANIFEST);
|
||||
}
|
||||
}());
|
||||
});
|
||||
|
||||
// The fetch handler redirects requests for RESOURCE files to the service
|
||||
// worker cache.
|
||||
self.addEventListener("fetch", (event) => {
|
||||
var origin = self.location.origin;
|
||||
var key = event.request.url.substring(origin.length + 1);
|
||||
// If the URL is not the the RESOURCE list, skip the cache.
|
||||
if (!RESOURCES[key]) {
|
||||
return event.respondWith(fetch(event.request));
|
||||
}
|
||||
event.respondWith(caches.open(CACHE_NAME)
|
||||
.then((cache) => {
|
||||
return cache.match(event.request).then((response) => {
|
||||
// Either respond with the cached resource, or perform a fetch and
|
||||
// lazily populate the cache.
|
||||
return response || fetch(event.request).then((response) => {
|
||||
cache.put(event.request, response.clone());
|
||||
return response;
|
||||
});
|
||||
})
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
''';
|
||||
}
|
||||
|
||||
@ -463,11 +463,17 @@ void main() {
|
||||
}));
|
||||
|
||||
test('Generated service worker correctly inlines file hashes', () {
|
||||
final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'});
|
||||
final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'}, <String>[]);
|
||||
|
||||
expect(result, contains('{\n "/foo": "abcd"\n};'));
|
||||
});
|
||||
|
||||
test('Generated service worker includes core files', () {
|
||||
final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'}, <String>['foo', 'bar']);
|
||||
|
||||
expect(result, contains('"foo",\n"bar"'));
|
||||
});
|
||||
|
||||
test('WebServiceWorker generates a service_worker for a web resource folder', () => testbed.run(() async {
|
||||
environment.outputDir.childDirectory('a').childFile('a.txt')
|
||||
..createSync(recursive: true)
|
||||
@ -497,6 +503,23 @@ void main() {
|
||||
contains('"index.html": "d41d8cd98f00b204e9800998ecf8427e"'));
|
||||
expect(environment.buildDir.childFile('service_worker.d'), exists);
|
||||
}));
|
||||
|
||||
test('WebServiceWorker does not cache source maps', () => testbed.run(() async {
|
||||
environment.outputDir
|
||||
.childFile('main.dart.js')
|
||||
.createSync(recursive: true);
|
||||
environment.outputDir
|
||||
.childFile('main.dart.js.map')
|
||||
.createSync(recursive: true);
|
||||
await const WebServiceWorker().build(environment);
|
||||
|
||||
// No caching of source maps.
|
||||
expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(),
|
||||
isNot(contains('"main.dart.js.map"')));
|
||||
// Expected twice, once for RESOURCES and once for CORE.
|
||||
expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(),
|
||||
contains('"main.dart.js"'));
|
||||
}));
|
||||
}
|
||||
|
||||
class MockProcessManager extends Mock implements ProcessManager {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user