Skip to content

Commit 1af8cc1

Browse files
authored
[tools][web] Make Plugin Registrant file ephemeral. (#102185)
1 parent 40627e9 commit 1af8cc1

File tree

17 files changed

+690
-344
lines changed

17 files changed

+690
-344
lines changed

dev/bots/service_worker_test.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,6 @@ Future<void> runWebServiceWorkerTest({
231231
'main.dart.js': 1,
232232
'flutter_service_worker.js': 1,
233233
'assets/FontManifest.json': 1,
234-
'assets/NOTICES': 1,
235234
'assets/AssetManifest.json': 1,
236235
'CLOSE': 1,
237236
// In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
@@ -273,7 +272,6 @@ Future<void> runWebServiceWorkerTest({
273272
'flutter.js': 1,
274273
'flutter_service_worker.js': 2,
275274
'main.dart.js': 1,
276-
'assets/NOTICES': 1,
277275
'assets/AssetManifest.json': 1,
278276
'assets/FontManifest.json': 1,
279277
'CLOSE': 1,
@@ -305,7 +303,6 @@ Future<void> runWebServiceWorkerTest({
305303
'main.dart.js': 2,
306304
'assets/FontManifest.json': 2,
307305
'flutter_service_worker.js': 1,
308-
'assets/NOTICES': 1,
309306
'assets/AssetManifest.json': 1,
310307
'CLOSE': 1,
311308
// In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
@@ -358,7 +355,6 @@ Future<void> runWebServiceWorkerTest({
358355
'flutter.js': 1,
359356
'flutter_service_worker.js': 2,
360357
'main.dart.js': 2,
361-
'assets/NOTICES': 1,
362358
'assets/AssetManifest.json': 1,
363359
'assets/FontManifest.json': 2,
364360
'CLOSE': 1,

packages/flutter_tools/lib/src/build_system/targets/web.dart

Lines changed: 12 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ import '../../cache.dart';
1515
import '../../convert.dart';
1616
import '../../dart/language_version.dart';
1717
import '../../dart/package_map.dart';
18+
import '../../flutter_plugins.dart';
1819
import '../../globals.dart' as globals;
1920
import '../../project.dart';
20-
import '../../web/flutter_js.dart' as flutter_js;
21+
import '../../web/file_generators/flutter_js.dart' as flutter_js;
22+
import '../../web/file_generators/flutter_service_worker_js.dart';
23+
import '../../web/file_generators/main_dart.dart' as main_dart;
2124
import '../build_system.dart';
2225
import '../depfile.dart';
2326
import '../exceptions.dart';
@@ -50,15 +53,6 @@ const String kSourceMapsEnabled = 'SourceMaps';
5053
/// Whether the dart2js native null assertions are enabled.
5154
const String kNativeNullAssertions = 'NativeNullAssertions';
5255

53-
/// The caching strategy for the generated service worker.
54-
enum ServiceWorkerStrategy {
55-
/// Download the app shell eagerly and all other assets lazily.
56-
/// Prefer the offline cached version.
57-
offlineFirst,
58-
/// Do not generate a service worker,
59-
none,
60-
}
61-
6256
const String kOfflineFirst = 'offline-first';
6357
const String kNoneWorker = 'none';
6458

@@ -97,7 +91,6 @@ class WebEntrypointTarget extends Target {
9791
@override
9892
Future<void> build(Environment environment) async {
9993
final String? targetFile = environment.defines[kTargetFile];
100-
final bool hasWebPlugins = environment.defines[kHasWebPlugins] == 'true';
10194
final Uri importUri = environment.fileSystem.file(targetFile).absolute.uri;
10295
// TODO(zanderso): support configuration of this file.
10396
const String packageFile = '.packages';
@@ -124,50 +117,15 @@ class WebEntrypointTarget extends Target {
124117
final String importedEntrypoint = packageConfig.toPackageUri(importUri)?.toString()
125118
?? importUri.toString();
126119

127-
String? generatedImport;
128-
if (hasWebPlugins) {
129-
final Uri generatedUri = environment.projectDir
130-
.childDirectory('lib')
131-
.childFile('generated_plugin_registrant.dart')
132-
.absolute
133-
.uri;
134-
generatedImport = packageConfig.toPackageUri(generatedUri)?.toString()
135-
?? generatedUri.toString();
136-
}
120+
await injectBuildTimePluginFiles(flutterProject, webPlatform: true, destination: environment.buildDir);
121+
// The below works because `injectBuildTimePluginFiles` is configured to write
122+
// the web_plugin_registrant.dart file alongside the generated main.dart
123+
const String generatedImport = 'web_plugin_registrant.dart';
137124

138-
final String contents = <String>[
139-
'// @dart=${languageVersion.major}.${languageVersion.minor}',
140-
'// Flutter web bootstrap script for $importedEntrypoint.',
141-
'',
142-
"import 'dart:ui' as ui;",
143-
"import 'dart:async';",
144-
'',
145-
"import '$importedEntrypoint' as entrypoint;",
146-
if (hasWebPlugins)
147-
"import 'package:flutter_web_plugins/flutter_web_plugins.dart';",
148-
if (hasWebPlugins)
149-
"import '$generatedImport';",
150-
'',
151-
'typedef _UnaryFunction = dynamic Function(List<String> args);',
152-
'typedef _NullaryFunction = dynamic Function();',
153-
'',
154-
'Future<void> main() async {',
155-
' await ui.webOnlyWarmupEngine(',
156-
' runApp: () {',
157-
' if (entrypoint.main is _UnaryFunction) {',
158-
' return (entrypoint.main as _UnaryFunction)(<String>[]);',
159-
' }',
160-
' return (entrypoint.main as _NullaryFunction)();',
161-
' },',
162-
if (hasWebPlugins) ...<String>[
163-
' registerPlugins: () {',
164-
' registerPlugins(webPluginRegistrar);',
165-
' },',
166-
],
167-
' );',
168-
'}',
169-
'',
170-
].join('\n');
125+
final String contents = main_dart.generateMainDartFile(importedEntrypoint,
126+
languageVersion: languageVersion,
127+
pluginRegistrantEntrypoint: generatedImport,
128+
);
171129

172130
environment.buildDir.childFile('main.dart')
173131
.writeAsStringSync(contents);
@@ -547,7 +505,6 @@ class WebServiceWorker extends Target {
547505
<String>[
548506
'main.dart.js',
549507
'index.html',
550-
'assets/NOTICES',
551508
if (urlToHash.containsKey('assets/AssetManifest.json'))
552509
'assets/AssetManifest.json',
553510
if (urlToHash.containsKey('assets/FontManifest.json'))
@@ -567,194 +524,3 @@ class WebServiceWorker extends Target {
567524
);
568525
}
569526
}
570-
571-
/// Generate a service worker with an app-specific cache name a map of
572-
/// resource files.
573-
///
574-
/// The tool embeds file hashes directly into the worker so that the byte for byte
575-
/// invalidation will automatically reactivate workers whenever a new
576-
/// version is deployed.
577-
String generateServiceWorker(
578-
Map<String, String> resources,
579-
List<String> coreBundle, {
580-
required ServiceWorkerStrategy serviceWorkerStrategy,
581-
}) {
582-
if (serviceWorkerStrategy == ServiceWorkerStrategy.none) {
583-
return '';
584-
}
585-
return '''
586-
'use strict';
587-
const MANIFEST = 'flutter-app-manifest';
588-
const TEMP = 'flutter-temp-cache';
589-
const CACHE_NAME = 'flutter-app-cache';
590-
const RESOURCES = {
591-
${resources.entries.map((MapEntry<String, String> entry) => '"${entry.key}": "${entry.value}"').join(",\n")}
592-
};
593-
594-
// The application shell files that are downloaded before a service worker can
595-
// start.
596-
const CORE = [
597-
${coreBundle.map((String file) => '"$file"').join(',\n')}];
598-
// During install, the TEMP cache is populated with the application shell files.
599-
self.addEventListener("install", (event) => {
600-
self.skipWaiting();
601-
return event.waitUntil(
602-
caches.open(TEMP).then((cache) => {
603-
return cache.addAll(
604-
CORE.map((value) => new Request(value, {'cache': 'reload'})));
605-
})
606-
);
607-
});
608-
609-
// During activate, the cache is populated with the temp files downloaded in
610-
// install. If this service worker is upgrading from one with a saved
611-
// MANIFEST, then use this to retain unchanged resource files.
612-
self.addEventListener("activate", function(event) {
613-
return event.waitUntil(async function() {
614-
try {
615-
var contentCache = await caches.open(CACHE_NAME);
616-
var tempCache = await caches.open(TEMP);
617-
var manifestCache = await caches.open(MANIFEST);
618-
var manifest = await manifestCache.match('manifest');
619-
// When there is no prior manifest, clear the entire cache.
620-
if (!manifest) {
621-
await caches.delete(CACHE_NAME);
622-
contentCache = await caches.open(CACHE_NAME);
623-
for (var request of await tempCache.keys()) {
624-
var response = await tempCache.match(request);
625-
await contentCache.put(request, response);
626-
}
627-
await caches.delete(TEMP);
628-
// Save the manifest to make future upgrades efficient.
629-
await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
630-
return;
631-
}
632-
var oldManifest = await manifest.json();
633-
var origin = self.location.origin;
634-
for (var request of await contentCache.keys()) {
635-
var key = request.url.substring(origin.length + 1);
636-
if (key == "") {
637-
key = "/";
638-
}
639-
// If a resource from the old manifest is not in the new cache, or if
640-
// the MD5 sum has changed, delete it. Otherwise the resource is left
641-
// in the cache and can be reused by the new service worker.
642-
if (!RESOURCES[key] || RESOURCES[key] != oldManifest[key]) {
643-
await contentCache.delete(request);
644-
}
645-
}
646-
// Populate the cache with the app shell TEMP files, potentially overwriting
647-
// cache files preserved above.
648-
for (var request of await tempCache.keys()) {
649-
var response = await tempCache.match(request);
650-
await contentCache.put(request, response);
651-
}
652-
await caches.delete(TEMP);
653-
// Save the manifest to make future upgrades efficient.
654-
await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES)));
655-
return;
656-
} catch (err) {
657-
// On an unhandled exception the state of the cache cannot be guaranteed.
658-
console.error('Failed to upgrade service worker: ' + err);
659-
await caches.delete(CACHE_NAME);
660-
await caches.delete(TEMP);
661-
await caches.delete(MANIFEST);
662-
}
663-
}());
664-
});
665-
666-
// The fetch handler redirects requests for RESOURCE files to the service
667-
// worker cache.
668-
self.addEventListener("fetch", (event) => {
669-
if (event.request.method !== 'GET') {
670-
return;
671-
}
672-
var origin = self.location.origin;
673-
var key = event.request.url.substring(origin.length + 1);
674-
// Redirect URLs to the index.html
675-
if (key.indexOf('?v=') != -1) {
676-
key = key.split('?v=')[0];
677-
}
678-
if (event.request.url == origin || event.request.url.startsWith(origin + '/#') || key == '') {
679-
key = '/';
680-
}
681-
// If the URL is not the RESOURCE list then return to signal that the
682-
// browser should take over.
683-
if (!RESOURCES[key]) {
684-
return;
685-
}
686-
// If the URL is the index.html, perform an online-first request.
687-
if (key == '/') {
688-
return onlineFirst(event);
689-
}
690-
event.respondWith(caches.open(CACHE_NAME)
691-
.then((cache) => {
692-
return cache.match(event.request).then((response) => {
693-
// Either respond with the cached resource, or perform a fetch and
694-
// lazily populate the cache.
695-
return response || fetch(event.request).then((response) => {
696-
cache.put(event.request, response.clone());
697-
return response;
698-
});
699-
})
700-
})
701-
);
702-
});
703-
704-
self.addEventListener('message', (event) => {
705-
// SkipWaiting can be used to immediately activate a waiting service worker.
706-
// This will also require a page refresh triggered by the main worker.
707-
if (event.data === 'skipWaiting') {
708-
self.skipWaiting();
709-
return;
710-
}
711-
if (event.data === 'downloadOffline') {
712-
downloadOffline();
713-
return;
714-
}
715-
});
716-
717-
// Download offline will check the RESOURCES for all files not in the cache
718-
// and populate them.
719-
async function downloadOffline() {
720-
var resources = [];
721-
var contentCache = await caches.open(CACHE_NAME);
722-
var currentContent = {};
723-
for (var request of await contentCache.keys()) {
724-
var key = request.url.substring(origin.length + 1);
725-
if (key == "") {
726-
key = "/";
727-
}
728-
currentContent[key] = true;
729-
}
730-
for (var resourceKey of Object.keys(RESOURCES)) {
731-
if (!currentContent[resourceKey]) {
732-
resources.push(resourceKey);
733-
}
734-
}
735-
return contentCache.addAll(resources);
736-
}
737-
738-
// Attempt to download the resource online before falling back to
739-
// the offline cache.
740-
function onlineFirst(event) {
741-
return event.respondWith(
742-
fetch(event.request).then((response) => {
743-
return caches.open(CACHE_NAME).then((cache) => {
744-
cache.put(event.request, response.clone());
745-
return response;
746-
});
747-
}).catch((error) => {
748-
return caches.open(CACHE_NAME).then((cache) => {
749-
return cache.match(event.request).then((response) => {
750-
if (response != null) {
751-
return response;
752-
}
753-
throw error;
754-
});
755-
});
756-
})
757-
);
758-
}
759-
''';
760-
}

0 commit comments

Comments
 (0)