Unverified Commit 418681d6 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] allow disabling pwa from build command, fix run release build caching (#64587)

Fix run release/profile modes generating a full service worker.
parent 0699c18e
...@@ -21,6 +21,7 @@ import '../base/net.dart'; ...@@ -21,6 +21,7 @@ import '../base/net.dart';
import '../base/terminal.dart'; import '../base/terminal.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../build_system/targets/web.dart';
import '../cache.dart'; import '../cache.dart';
import '../dart/language_version.dart'; import '../dart/language_version.dart';
import '../dart/pub.dart'; import '../dart/pub.dart';
...@@ -488,6 +489,7 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -488,6 +489,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
debuggingOptions.buildInfo, debuggingOptions.buildInfo,
debuggingOptions.initializePlatform, debuggingOptions.initializePlatform,
false, false,
kNoneWorker,
); );
} }
await device.device.startApp( await device.device.startApp(
...@@ -557,6 +559,7 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -557,6 +559,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
debuggingOptions.buildInfo, debuggingOptions.buildInfo,
debuggingOptions.initializePlatform, debuggingOptions.initializePlatform,
false, false,
kNoneWorker,
); );
} on ToolExit { } on ToolExit {
return OperationResult(1, 'Failed to recompile application.'); return OperationResult(1, 'Failed to recompile application.');
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:math'; import 'dart:math';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart'; import 'package:package_config/package_config.dart';
import '../../artifacts.dart'; import '../../artifacts.dart';
...@@ -33,6 +34,32 @@ const String kDart2jsOptimization = 'Dart2jsOptimization'; ...@@ -33,6 +34,32 @@ const String kDart2jsOptimization = 'Dart2jsOptimization';
/// Whether to disable dynamic generation code to satisfy csp policies. /// Whether to disable dynamic generation code to satisfy csp policies.
const String kCspMode = 'cspMode'; const String kCspMode = 'cspMode';
/// The caching strategy to use for service worker generation.
const String kServiceWorkerStrategy = 'ServiceWorkerStratgey';
/// The caching strategy for the generated service worker.
enum ServiceWorkerStrategy {
/// Download the app shell eagerly and all other assets lazily.
/// Prefer the offline cached version.
offlineFirst,
/// Do not generate a service worker,
none,
}
const String kOfflineFirst = 'offline-first';
const String kNoneWorker = 'none';
/// Convert a [value] into a [ServiceWorkerStrategy].
ServiceWorkerStrategy _serviceWorkerStrategyfromString(String value) {
switch (value) {
case kNoneWorker:
return ServiceWorkerStrategy.none;
// offline-first is the default value for any invalid requests.
default:
return ServiceWorkerStrategy.offlineFirst;
}
}
/// Generates an entry point for a web target. /// Generates an entry point for a web target.
// Keep this in sync with build_runner/resident_web_runner.dart // Keep this in sync with build_runner/resident_web_runner.dart
class WebEntrypointTarget extends Target { class WebEntrypointTarget extends Target {
...@@ -384,16 +411,23 @@ class WebServiceWorker extends Target { ...@@ -384,16 +411,23 @@ class WebServiceWorker extends Target {
final File serviceWorkerFile = environment.outputDir final File serviceWorkerFile = environment.outputDir
.childFile('flutter_service_worker.js'); .childFile('flutter_service_worker.js');
final Depfile depfile = Depfile(contents, <File>[serviceWorkerFile]); final Depfile depfile = Depfile(contents, <File>[serviceWorkerFile]);
final String serviceWorker = generateServiceWorker(urlToHash, <String>[ final ServiceWorkerStrategy serviceWorkerStrategy = _serviceWorkerStrategyfromString(
'/', environment.defines[kServiceWorkerStrategy],
'main.dart.js', );
'index.html', final String serviceWorker = generateServiceWorker(
'assets/NOTICES', urlToHash,
if (urlToHash.containsKey('assets/AssetManifest.json')) <String>[
'assets/AssetManifest.json', '/',
if (urlToHash.containsKey('assets/FontManifest.json')) 'main.dart.js',
'assets/FontManifest.json', 'index.html',
]); 'assets/NOTICES',
if (urlToHash.containsKey('assets/AssetManifest.json'))
'assets/AssetManifest.json',
if (urlToHash.containsKey('assets/FontManifest.json'))
'assets/FontManifest.json',
],
serviceWorkerStrategy: serviceWorkerStrategy,
);
serviceWorkerFile serviceWorkerFile
.writeAsStringSync(serviceWorker); .writeAsStringSync(serviceWorker);
final DepfileService depfileService = DepfileService( final DepfileService depfileService = DepfileService(
...@@ -413,7 +447,14 @@ class WebServiceWorker extends Target { ...@@ -413,7 +447,14 @@ class WebServiceWorker extends Target {
/// The tool embeds 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 /// invalidation will automatically reactivate workers whenever a new
/// version is deployed. /// version is deployed.
String generateServiceWorker(Map<String, String> resources, List<String> coreBundle) { String generateServiceWorker(
Map<String, String> resources,
List<String> coreBundle, {
@required ServiceWorkerStrategy serviceWorkerStrategy,
}) {
if (serviceWorkerStrategy == ServiceWorkerStrategy.none) {
return '';
}
return ''' return '''
'use strict'; 'use strict';
const MANIFEST = 'flutter-app-manifest'; const MANIFEST = 'flutter-app-manifest';
...@@ -427,7 +468,6 @@ const RESOURCES = { ...@@ -427,7 +468,6 @@ const RESOURCES = {
// start. // start.
const CORE = [ const CORE = [
${coreBundle.map((String file) => '"$file"').join(',\n')}]; ${coreBundle.map((String file) => '"$file"').join(',\n')}];
// During install, the TEMP cache is populated with the application shell files. // During install, the TEMP cache is populated with the application shell files.
self.addEventListener("install", (event) => { self.addEventListener("install", (event) => {
return event.waitUntil( return event.waitUntil(
...@@ -448,7 +488,6 @@ self.addEventListener("activate", function(event) { ...@@ -448,7 +488,6 @@ self.addEventListener("activate", function(event) {
var tempCache = await caches.open(TEMP); var tempCache = await caches.open(TEMP);
var manifestCache = await caches.open(MANIFEST); var manifestCache = await caches.open(MANIFEST);
var manifest = await manifestCache.match('manifest'); var manifest = await manifestCache.match('manifest');
// When there is no prior manifest, clear the entire cache. // When there is no prior manifest, clear the entire cache.
if (!manifest) { if (!manifest) {
await caches.delete(CACHE_NAME); await caches.delete(CACHE_NAME);
...@@ -462,7 +501,6 @@ self.addEventListener("activate", function(event) { ...@@ -462,7 +501,6 @@ self.addEventListener("activate", function(event) {
await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES))); await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
return; return;
} }
var oldManifest = await manifest.json(); var oldManifest = await manifest.json();
var origin = self.location.origin; var origin = self.location.origin;
for (var request of await contentCache.keys()) { for (var request of await contentCache.keys()) {
...@@ -537,7 +575,6 @@ self.addEventListener('message', (event) => { ...@@ -537,7 +575,6 @@ self.addEventListener('message', (event) => {
if (event.data === 'skipWaiting') { if (event.data === 'skipWaiting') {
return self.skipWaiting(); return self.skipWaiting();
} }
if (event.message === 'downloadOffline') { if (event.message === 'downloadOffline') {
downloadOffline(); downloadOffline();
} }
......
...@@ -8,6 +8,7 @@ import 'package:meta/meta.dart'; ...@@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../build_system/targets/web.dart';
import '../features.dart'; import '../features.dart';
import '../project.dart'; import '../project.dart';
import '../runner/flutter_command.dart' import '../runner/flutter_command.dart'
...@@ -38,6 +39,22 @@ class BuildWebCommand extends BuildSubCommand { ...@@ -38,6 +39,22 @@ class BuildWebCommand extends BuildSubCommand {
help: 'Disable dynamic generation of code in the generated output. ' help: 'Disable dynamic generation of code in the generated output. '
'This is necessary to satisfy CSP restrictions (see http://www.w3.org/TR/CSP/).' 'This is necessary to satisfy CSP restrictions (see http://www.w3.org/TR/CSP/).'
); );
argParser.addOption('pwa-strategy',
defaultsTo: kOfflineFirst,
help:
'The caching strategy to be used by the PWA service worker.\n'
'offline-first will attempt to cache the app shell eagerly and '
'then lazily cache all subsequent assets as they are loaded. When '
'making a network request for an asset, the offline cache will be '
'preferred.\n'
'none will generate a service worker with no body. This is useful for '
'local testing or in cases where the service worker caching functionality '
'is not desirable',
allowed: <String>[
kOfflineFirst,
kNoneWorker,
]
);
} }
@override @override
...@@ -72,6 +89,7 @@ class BuildWebCommand extends BuildSubCommand { ...@@ -72,6 +89,7 @@ class BuildWebCommand extends BuildSubCommand {
buildInfo, buildInfo,
boolArg('web-initialize-platform'), boolArg('web-initialize-platform'),
boolArg('csp'), boolArg('csp'),
stringArg('pwa-strategy'),
); );
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
......
...@@ -28,6 +28,7 @@ Future<void> buildWeb( ...@@ -28,6 +28,7 @@ Future<void> buildWeb(
BuildInfo buildInfo, BuildInfo buildInfo,
bool initializePlatform, bool initializePlatform,
bool csp, bool csp,
String serviceWorkerStrategy,
) async { ) async {
if (!flutterProject.web.existsSync()) { if (!flutterProject.web.existsSync()) {
throwToolExit('Missing index.html.'); throwToolExit('Missing index.html.');
...@@ -52,6 +53,8 @@ Future<void> buildWeb( ...@@ -52,6 +53,8 @@ Future<void> buildWeb(
kDartDefines: encodeDartDefines(buildInfo.dartDefines), kDartDefines: encodeDartDefines(buildInfo.dartDefines),
kCspMode: csp.toString(), kCspMode: csp.toString(),
kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(), kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(),
if (serviceWorkerStrategy != null)
kServiceWorkerStrategy: serviceWorkerStrategy,
if (buildInfo.extraFrontEndOptions?.isNotEmpty ?? false) if (buildInfo.extraFrontEndOptions?.isNotEmpty ?? false)
kExtraFrontEndOptions: encodeDartDefines(buildInfo.extraFrontEndOptions), kExtraFrontEndOptions: encodeDartDefines(buildInfo.extraFrontEndOptions),
}, },
......
...@@ -56,6 +56,7 @@ void main() { ...@@ -56,6 +56,7 @@ void main() {
BuildInfo.debug, BuildInfo.debug,
false, false,
false, false,
null,
), throwsToolExit()); ), throwsToolExit());
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => fakePlatform, Platform: () => fakePlatform,
......
...@@ -475,14 +475,20 @@ void main() { ...@@ -475,14 +475,20 @@ void main() {
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
test('Generated service worker is empty with none-strategy', () {
final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'}, <String>[], serviceWorkerStrategy: ServiceWorkerStrategy.none);
expect(result, '');
});
test('Generated service worker correctly inlines file hashes', () { test('Generated service worker correctly inlines file hashes', () {
final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'}, <String>[]); final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'}, <String>[], serviceWorkerStrategy: ServiceWorkerStrategy.offlineFirst);
expect(result, contains('{\n "/foo": "abcd"\n};')); expect(result, contains('{\n "/foo": "abcd"\n};'));
}); });
test('Generated service worker includes core files', () { test('Generated service worker includes core files', () {
final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'}, <String>['foo', 'bar']); final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'}, <String>['foo', 'bar'], serviceWorkerStrategy: ServiceWorkerStrategy.offlineFirst);
expect(result, contains('"foo",\n"bar"')); expect(result, contains('"foo",\n"bar"'));
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment