[tools][web] Make Plugin Registrant file ephemeral. (#102185)
This commit is contained in:
parent
40627e9ed0
commit
1af8cc1183
@ -231,7 +231,6 @@ Future<void> runWebServiceWorkerTest({
|
|||||||
'main.dart.js': 1,
|
'main.dart.js': 1,
|
||||||
'flutter_service_worker.js': 1,
|
'flutter_service_worker.js': 1,
|
||||||
'assets/FontManifest.json': 1,
|
'assets/FontManifest.json': 1,
|
||||||
'assets/NOTICES': 1,
|
|
||||||
'assets/AssetManifest.json': 1,
|
'assets/AssetManifest.json': 1,
|
||||||
'CLOSE': 1,
|
'CLOSE': 1,
|
||||||
// In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
|
// In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
|
||||||
@ -273,7 +272,6 @@ Future<void> runWebServiceWorkerTest({
|
|||||||
'flutter.js': 1,
|
'flutter.js': 1,
|
||||||
'flutter_service_worker.js': 2,
|
'flutter_service_worker.js': 2,
|
||||||
'main.dart.js': 1,
|
'main.dart.js': 1,
|
||||||
'assets/NOTICES': 1,
|
|
||||||
'assets/AssetManifest.json': 1,
|
'assets/AssetManifest.json': 1,
|
||||||
'assets/FontManifest.json': 1,
|
'assets/FontManifest.json': 1,
|
||||||
'CLOSE': 1,
|
'CLOSE': 1,
|
||||||
@ -305,7 +303,6 @@ Future<void> runWebServiceWorkerTest({
|
|||||||
'main.dart.js': 2,
|
'main.dart.js': 2,
|
||||||
'assets/FontManifest.json': 2,
|
'assets/FontManifest.json': 2,
|
||||||
'flutter_service_worker.js': 1,
|
'flutter_service_worker.js': 1,
|
||||||
'assets/NOTICES': 1,
|
|
||||||
'assets/AssetManifest.json': 1,
|
'assets/AssetManifest.json': 1,
|
||||||
'CLOSE': 1,
|
'CLOSE': 1,
|
||||||
// In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
|
// In headless mode Chrome does not load 'manifest.json' and 'favicon.ico'.
|
||||||
@ -358,7 +355,6 @@ Future<void> runWebServiceWorkerTest({
|
|||||||
'flutter.js': 1,
|
'flutter.js': 1,
|
||||||
'flutter_service_worker.js': 2,
|
'flutter_service_worker.js': 2,
|
||||||
'main.dart.js': 2,
|
'main.dart.js': 2,
|
||||||
'assets/NOTICES': 1,
|
|
||||||
'assets/AssetManifest.json': 1,
|
'assets/AssetManifest.json': 1,
|
||||||
'assets/FontManifest.json': 2,
|
'assets/FontManifest.json': 2,
|
||||||
'CLOSE': 1,
|
'CLOSE': 1,
|
||||||
|
@ -15,9 +15,12 @@ import '../../cache.dart';
|
|||||||
import '../../convert.dart';
|
import '../../convert.dart';
|
||||||
import '../../dart/language_version.dart';
|
import '../../dart/language_version.dart';
|
||||||
import '../../dart/package_map.dart';
|
import '../../dart/package_map.dart';
|
||||||
|
import '../../flutter_plugins.dart';
|
||||||
import '../../globals.dart' as globals;
|
import '../../globals.dart' as globals;
|
||||||
import '../../project.dart';
|
import '../../project.dart';
|
||||||
import '../../web/flutter_js.dart' as flutter_js;
|
import '../../web/file_generators/flutter_js.dart' as flutter_js;
|
||||||
|
import '../../web/file_generators/flutter_service_worker_js.dart';
|
||||||
|
import '../../web/file_generators/main_dart.dart' as main_dart;
|
||||||
import '../build_system.dart';
|
import '../build_system.dart';
|
||||||
import '../depfile.dart';
|
import '../depfile.dart';
|
||||||
import '../exceptions.dart';
|
import '../exceptions.dart';
|
||||||
@ -50,15 +53,6 @@ const String kSourceMapsEnabled = 'SourceMaps';
|
|||||||
/// Whether the dart2js native null assertions are enabled.
|
/// Whether the dart2js native null assertions are enabled.
|
||||||
const String kNativeNullAssertions = 'NativeNullAssertions';
|
const String kNativeNullAssertions = 'NativeNullAssertions';
|
||||||
|
|
||||||
/// 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 kOfflineFirst = 'offline-first';
|
||||||
const String kNoneWorker = 'none';
|
const String kNoneWorker = 'none';
|
||||||
|
|
||||||
@ -97,7 +91,6 @@ class WebEntrypointTarget extends Target {
|
|||||||
@override
|
@override
|
||||||
Future<void> build(Environment environment) async {
|
Future<void> build(Environment environment) async {
|
||||||
final String? targetFile = environment.defines[kTargetFile];
|
final String? targetFile = environment.defines[kTargetFile];
|
||||||
final bool hasWebPlugins = environment.defines[kHasWebPlugins] == 'true';
|
|
||||||
final Uri importUri = environment.fileSystem.file(targetFile).absolute.uri;
|
final Uri importUri = environment.fileSystem.file(targetFile).absolute.uri;
|
||||||
// TODO(zanderso): support configuration of this file.
|
// TODO(zanderso): support configuration of this file.
|
||||||
const String packageFile = '.packages';
|
const String packageFile = '.packages';
|
||||||
@ -124,50 +117,15 @@ class WebEntrypointTarget extends Target {
|
|||||||
final String importedEntrypoint = packageConfig.toPackageUri(importUri)?.toString()
|
final String importedEntrypoint = packageConfig.toPackageUri(importUri)?.toString()
|
||||||
?? importUri.toString();
|
?? importUri.toString();
|
||||||
|
|
||||||
String? generatedImport;
|
await injectBuildTimePluginFiles(flutterProject, webPlatform: true, destination: environment.buildDir);
|
||||||
if (hasWebPlugins) {
|
// The below works because `injectBuildTimePluginFiles` is configured to write
|
||||||
final Uri generatedUri = environment.projectDir
|
// the web_plugin_registrant.dart file alongside the generated main.dart
|
||||||
.childDirectory('lib')
|
const String generatedImport = 'web_plugin_registrant.dart';
|
||||||
.childFile('generated_plugin_registrant.dart')
|
|
||||||
.absolute
|
|
||||||
.uri;
|
|
||||||
generatedImport = packageConfig.toPackageUri(generatedUri)?.toString()
|
|
||||||
?? generatedUri.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
final String contents = <String>[
|
final String contents = main_dart.generateMainDartFile(importedEntrypoint,
|
||||||
'// @dart=${languageVersion.major}.${languageVersion.minor}',
|
languageVersion: languageVersion,
|
||||||
'// Flutter web bootstrap script for $importedEntrypoint.',
|
pluginRegistrantEntrypoint: generatedImport,
|
||||||
'',
|
);
|
||||||
"import 'dart:ui' as ui;",
|
|
||||||
"import 'dart:async';",
|
|
||||||
'',
|
|
||||||
"import '$importedEntrypoint' as entrypoint;",
|
|
||||||
if (hasWebPlugins)
|
|
||||||
"import 'package:flutter_web_plugins/flutter_web_plugins.dart';",
|
|
||||||
if (hasWebPlugins)
|
|
||||||
"import '$generatedImport';",
|
|
||||||
'',
|
|
||||||
'typedef _UnaryFunction = dynamic Function(List<String> args);',
|
|
||||||
'typedef _NullaryFunction = dynamic Function();',
|
|
||||||
'',
|
|
||||||
'Future<void> main() async {',
|
|
||||||
' await ui.webOnlyWarmupEngine(',
|
|
||||||
' runApp: () {',
|
|
||||||
' if (entrypoint.main is _UnaryFunction) {',
|
|
||||||
' return (entrypoint.main as _UnaryFunction)(<String>[]);',
|
|
||||||
' }',
|
|
||||||
' return (entrypoint.main as _NullaryFunction)();',
|
|
||||||
' },',
|
|
||||||
if (hasWebPlugins) ...<String>[
|
|
||||||
' registerPlugins: () {',
|
|
||||||
' registerPlugins(webPluginRegistrar);',
|
|
||||||
' },',
|
|
||||||
],
|
|
||||||
' );',
|
|
||||||
'}',
|
|
||||||
'',
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
environment.buildDir.childFile('main.dart')
|
environment.buildDir.childFile('main.dart')
|
||||||
.writeAsStringSync(contents);
|
.writeAsStringSync(contents);
|
||||||
@ -547,7 +505,6 @@ class WebServiceWorker extends Target {
|
|||||||
<String>[
|
<String>[
|
||||||
'main.dart.js',
|
'main.dart.js',
|
||||||
'index.html',
|
'index.html',
|
||||||
'assets/NOTICES',
|
|
||||||
if (urlToHash.containsKey('assets/AssetManifest.json'))
|
if (urlToHash.containsKey('assets/AssetManifest.json'))
|
||||||
'assets/AssetManifest.json',
|
'assets/AssetManifest.json',
|
||||||
if (urlToHash.containsKey('assets/FontManifest.json'))
|
if (urlToHash.containsKey('assets/FontManifest.json'))
|
||||||
@ -567,194 +524,3 @@ class WebServiceWorker extends Target {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a service worker with an app-specific cache name a map of
|
|
||||||
/// resource files.
|
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
String generateServiceWorker(
|
|
||||||
Map<String, String> resources,
|
|
||||||
List<String> coreBundle, {
|
|
||||||
required ServiceWorkerStrategy serviceWorkerStrategy,
|
|
||||||
}) {
|
|
||||||
if (serviceWorkerStrategy == ServiceWorkerStrategy.none) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
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")}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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) => {
|
|
||||||
self.skipWaiting();
|
|
||||||
return event.waitUntil(
|
|
||||||
caches.open(TEMP).then((cache) => {
|
|
||||||
return cache.addAll(
|
|
||||||
CORE.map((value) => new Request(value, {'cache': 'reload'})));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
contentCache = await caches.open(CACHE_NAME);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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) => {
|
|
||||||
if (event.request.method !== 'GET') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var origin = self.location.origin;
|
|
||||||
var key = event.request.url.substring(origin.length + 1);
|
|
||||||
// Redirect URLs to the index.html
|
|
||||||
if (key.indexOf('?v=') != -1) {
|
|
||||||
key = key.split('?v=')[0];
|
|
||||||
}
|
|
||||||
if (event.request.url == origin || event.request.url.startsWith(origin + '/#') || key == '') {
|
|
||||||
key = '/';
|
|
||||||
}
|
|
||||||
// If the URL is not the RESOURCE list then return to signal that the
|
|
||||||
// browser should take over.
|
|
||||||
if (!RESOURCES[key]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If the URL is the index.html, perform an online-first request.
|
|
||||||
if (key == '/') {
|
|
||||||
return onlineFirst(event);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('message', (event) => {
|
|
||||||
// SkipWaiting can be used to immediately activate a waiting service worker.
|
|
||||||
// This will also require a page refresh triggered by the main worker.
|
|
||||||
if (event.data === 'skipWaiting') {
|
|
||||||
self.skipWaiting();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.data === 'downloadOffline') {
|
|
||||||
downloadOffline();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Download offline will check the RESOURCES for all files not in the cache
|
|
||||||
// and populate them.
|
|
||||||
async function downloadOffline() {
|
|
||||||
var resources = [];
|
|
||||||
var contentCache = await caches.open(CACHE_NAME);
|
|
||||||
var currentContent = {};
|
|
||||||
for (var request of await contentCache.keys()) {
|
|
||||||
var key = request.url.substring(origin.length + 1);
|
|
||||||
if (key == "") {
|
|
||||||
key = "/";
|
|
||||||
}
|
|
||||||
currentContent[key] = true;
|
|
||||||
}
|
|
||||||
for (var resourceKey of Object.keys(RESOURCES)) {
|
|
||||||
if (!currentContent[resourceKey]) {
|
|
||||||
resources.push(resourceKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return contentCache.addAll(resources);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to download the resource online before falling back to
|
|
||||||
// the offline cache.
|
|
||||||
function onlineFirst(event) {
|
|
||||||
return event.respondWith(
|
|
||||||
fetch(event.request).then((response) => {
|
|
||||||
return caches.open(CACHE_NAME).then((cache) => {
|
|
||||||
cache.put(event.request, response.clone());
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
}).catch((error) => {
|
|
||||||
return caches.open(CACHE_NAME).then((cache) => {
|
|
||||||
return cache.match(event.request).then((response) => {
|
|
||||||
if (response != null) {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
''';
|
|
||||||
}
|
|
||||||
|
@ -550,23 +550,32 @@ Depends on all your plugins, and provides a function to register them.
|
|||||||
end
|
end
|
||||||
''';
|
''';
|
||||||
|
|
||||||
const String _dartPluginRegistryTemplate = '''
|
const String _noopDartPluginRegistryTemplate = '''
|
||||||
|
// Flutter web plugin registrant file.
|
||||||
//
|
//
|
||||||
// Generated file. Do not edit.
|
// Generated file. Do not edit.
|
||||||
//
|
//
|
||||||
|
|
||||||
// ignore_for_file: directives_ordering
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
// ignore_for_file: depend_on_referenced_packages
|
void registerPlugins() {}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const String _dartPluginRegistryTemplate = '''
|
||||||
|
// Flutter web plugin registrant file.
|
||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
|
||||||
{{#methodChannelPlugins}}
|
{{#methodChannelPlugins}}
|
||||||
import 'package:{{name}}/{{file}}';
|
import 'package:{{name}}/{{file}}';
|
||||||
{{/methodChannelPlugins}}
|
{{/methodChannelPlugins}}
|
||||||
|
|
||||||
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||||
|
|
||||||
// ignore: public_member_api_docs
|
void registerPlugins([final Registrar? pluginRegistrar]) {
|
||||||
void registerPlugins(final Registrar registrar) {
|
final Registrar registrar = pluginRegistrar ?? webPluginRegistrar;
|
||||||
{{#methodChannelPlugins}}
|
{{#methodChannelPlugins}}
|
||||||
{{class}}.registerWith(registrar);
|
{{class}}.registerWith(registrar);
|
||||||
{{/methodChannelPlugins}}
|
{{/methodChannelPlugins}}
|
||||||
@ -937,22 +946,22 @@ Future<void> _writeCppPluginRegistrant(Directory destination, Map<String, Object
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _writeWebPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
|
Future<void> _writeWebPluginRegistrant(FlutterProject project, List<Plugin> plugins, Directory destination) async {
|
||||||
final List<Map<String, Object?>> webPlugins = _extractPlatformMaps(plugins, WebPlugin.kConfigKey);
|
final List<Map<String, Object?>> webPlugins = _extractPlatformMaps(plugins, WebPlugin.kConfigKey);
|
||||||
final Map<String, Object> context = <String, Object>{
|
final Map<String, Object> context = <String, Object>{
|
||||||
'methodChannelPlugins': webPlugins,
|
'methodChannelPlugins': webPlugins,
|
||||||
};
|
};
|
||||||
final File pluginFile = project.web.libDirectory.childFile('generated_plugin_registrant.dart');
|
|
||||||
if (webPlugins.isEmpty) {
|
final File pluginFile = destination.childFile('web_plugin_registrant.dart');
|
||||||
ErrorHandlingFileSystem.deleteIfExists(pluginFile);
|
|
||||||
} else {
|
final String template = webPlugins.isEmpty ? _noopDartPluginRegistryTemplate : _dartPluginRegistryTemplate;
|
||||||
_renderTemplateToFile(
|
|
||||||
_dartPluginRegistryTemplate,
|
_renderTemplateToFile(
|
||||||
context,
|
template,
|
||||||
pluginFile,
|
context,
|
||||||
globals.templateRenderer,
|
pluginFile,
|
||||||
);
|
globals.templateRenderer,
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For each platform that uses them, creates symlinks within the platform
|
/// For each platform that uses them, creates symlinks within the platform
|
||||||
@ -1068,8 +1077,41 @@ Future<void> refreshPluginsList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Injects plugins found in `pubspec.yaml` into the platform-specific projects
|
||||||
|
/// only at build-time.
|
||||||
|
///
|
||||||
|
/// This method is similar to [injectPlugins], but used only for platforms where
|
||||||
|
/// the plugin files are not required when the app is created (currently: Web).
|
||||||
|
///
|
||||||
|
/// This method will create files in the temporary flutter build directory
|
||||||
|
/// specified by `destination`.
|
||||||
|
///
|
||||||
|
/// In the Web platform, `destination` can point to a real filesystem (`flutter build`)
|
||||||
|
/// or an in-memory filesystem (`flutter run`).
|
||||||
|
Future<void> injectBuildTimePluginFiles(
|
||||||
|
FlutterProject project, {
|
||||||
|
required Directory destination,
|
||||||
|
bool webPlatform = false,
|
||||||
|
}) async {
|
||||||
|
final List<Plugin> plugins = await findPlugins(project);
|
||||||
|
// Sort the plugins by name to keep ordering stable in generated files.
|
||||||
|
plugins.sort((Plugin left, Plugin right) => left.name.compareTo(right.name));
|
||||||
|
if (webPlatform) {
|
||||||
|
await _writeWebPluginRegistrant(project, plugins, destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Injects plugins found in `pubspec.yaml` into the platform-specific projects.
|
/// Injects plugins found in `pubspec.yaml` into the platform-specific projects.
|
||||||
///
|
///
|
||||||
|
/// The injected files are required by the flutter app as soon as possible, so
|
||||||
|
/// it can be built.
|
||||||
|
///
|
||||||
|
/// Files written by this method end up in platform-specific locations that are
|
||||||
|
/// configured by each [FlutterProject] subclass (except for the Web).
|
||||||
|
///
|
||||||
|
/// Web tooling uses [injectBuildTimePluginFiles] instead, which places files in the
|
||||||
|
/// current build (temp) directory, and doesn't modify the users' working copy.
|
||||||
|
///
|
||||||
/// Assumes [refreshPluginsList] has been called since last change to `pubspec.yaml`.
|
/// Assumes [refreshPluginsList] has been called since last change to `pubspec.yaml`.
|
||||||
Future<void> injectPlugins(
|
Future<void> injectPlugins(
|
||||||
FlutterProject project, {
|
FlutterProject project, {
|
||||||
@ -1078,7 +1120,6 @@ Future<void> injectPlugins(
|
|||||||
bool linuxPlatform = false,
|
bool linuxPlatform = false,
|
||||||
bool macOSPlatform = false,
|
bool macOSPlatform = false,
|
||||||
bool windowsPlatform = false,
|
bool windowsPlatform = false,
|
||||||
bool webPlatform = false,
|
|
||||||
}) async {
|
}) async {
|
||||||
final List<Plugin> plugins = await findPlugins(project);
|
final List<Plugin> plugins = await findPlugins(project);
|
||||||
// Sort the plugins by name to keep ordering stable in generated files.
|
// Sort the plugins by name to keep ordering stable in generated files.
|
||||||
@ -1114,9 +1155,6 @@ Future<void> injectPlugins(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (webPlatform) {
|
|
||||||
await _writeWebPluginRegistrant(project, plugins);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the specified Flutter [project] has any plugin dependencies.
|
/// Returns whether the specified Flutter [project] has any plugin dependencies.
|
||||||
|
@ -41,7 +41,7 @@ import '../vmservice.dart';
|
|||||||
import '../web/bootstrap.dart';
|
import '../web/bootstrap.dart';
|
||||||
import '../web/chrome.dart';
|
import '../web/chrome.dart';
|
||||||
import '../web/compile.dart';
|
import '../web/compile.dart';
|
||||||
import '../web/flutter_js.dart' as flutter_js;
|
import '../web/file_generators/flutter_js.dart' as flutter_js;
|
||||||
import '../web/memory_fs.dart';
|
import '../web/memory_fs.dart';
|
||||||
import 'sdk_web_configuration.dart';
|
import 'sdk_web_configuration.dart';
|
||||||
|
|
||||||
|
@ -30,8 +30,6 @@ import '../dart/language_version.dart';
|
|||||||
import '../devfs.dart';
|
import '../devfs.dart';
|
||||||
import '../device.dart';
|
import '../device.dart';
|
||||||
import '../flutter_plugins.dart';
|
import '../flutter_plugins.dart';
|
||||||
import '../platform_plugins.dart';
|
|
||||||
import '../plugins.dart';
|
|
||||||
import '../project.dart';
|
import '../project.dart';
|
||||||
import '../reporting/reporting.dart';
|
import '../reporting/reporting.dart';
|
||||||
import '../resident_devtools_handler.dart';
|
import '../resident_devtools_handler.dart';
|
||||||
@ -40,6 +38,7 @@ import '../run_hot.dart';
|
|||||||
import '../vmservice.dart';
|
import '../vmservice.dart';
|
||||||
import '../web/chrome.dart';
|
import '../web/chrome.dart';
|
||||||
import '../web/compile.dart';
|
import '../web/compile.dart';
|
||||||
|
import '../web/file_generators/main_dart.dart' as main_dart;
|
||||||
import '../web/web_device.dart';
|
import '../web/web_device.dart';
|
||||||
import '../web/web_runner.dart';
|
import '../web/web_runner.dart';
|
||||||
import 'devfs_web.dart';
|
import 'devfs_web.dart';
|
||||||
@ -433,15 +432,12 @@ class ResidentWebRunner extends ResidentRunner {
|
|||||||
..createSync();
|
..createSync();
|
||||||
result = _generatedEntrypointDirectory.childFile('web_entrypoint.dart');
|
result = _generatedEntrypointDirectory.childFile('web_entrypoint.dart');
|
||||||
|
|
||||||
final bool hasWebPlugins = (await findPlugins(flutterProject))
|
// Generates the generated_plugin_registrar
|
||||||
.any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey));
|
await injectBuildTimePluginFiles(flutterProject, webPlatform: true, destination: _generatedEntrypointDirectory);
|
||||||
await injectPlugins(flutterProject, webPlatform: true);
|
// The below works because `injectBuildTimePluginFiles` is configured to write
|
||||||
|
// the web_plugin_registrant.dart file alongside the generated main.dart
|
||||||
|
const String/*?*/ generatedImport = 'web_plugin_registrant.dart';
|
||||||
|
|
||||||
final Uri generatedUri = _fileSystem.currentDirectory
|
|
||||||
.childDirectory('lib')
|
|
||||||
.childFile('generated_plugin_registrant.dart')
|
|
||||||
.absolute.uri;
|
|
||||||
final Uri generatedImport = packageConfig.toPackageUri(generatedUri);
|
|
||||||
Uri importedEntrypoint = packageConfig.toPackageUri(mainUri);
|
Uri importedEntrypoint = packageConfig.toPackageUri(mainUri);
|
||||||
// Special handling for entrypoints that are not under lib, such as test scripts.
|
// Special handling for entrypoints that are not under lib, such as test scripts.
|
||||||
if (importedEntrypoint == null) {
|
if (importedEntrypoint == null) {
|
||||||
@ -453,44 +449,17 @@ class ResidentWebRunner extends ResidentRunner {
|
|||||||
path: '/${mainUri.pathSegments.last}',
|
path: '/${mainUri.pathSegments.last}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final LanguageVersion languageVersion = determineLanguageVersion(
|
final LanguageVersion languageVersion = determineLanguageVersion(
|
||||||
_fileSystem.file(mainUri),
|
_fileSystem.file(mainUri),
|
||||||
packageConfig[flutterProject.manifest.appName],
|
packageConfig[flutterProject.manifest.appName],
|
||||||
Cache.flutterRoot,
|
Cache.flutterRoot,
|
||||||
);
|
);
|
||||||
|
|
||||||
final String entrypoint = <String>[
|
final String entrypoint = main_dart.generateMainDartFile(importedEntrypoint.toString(),
|
||||||
'// @dart=${languageVersion.major}.${languageVersion.minor}',
|
languageVersion: languageVersion,
|
||||||
'// Flutter web bootstrap script for $importedEntrypoint.',
|
pluginRegistrantEntrypoint: generatedImport,
|
||||||
'',
|
);
|
||||||
"import 'dart:ui' as ui;",
|
|
||||||
"import 'dart:async';",
|
|
||||||
'',
|
|
||||||
"import '$importedEntrypoint' as entrypoint;",
|
|
||||||
if (hasWebPlugins)
|
|
||||||
"import 'package:flutter_web_plugins/flutter_web_plugins.dart';",
|
|
||||||
if (hasWebPlugins)
|
|
||||||
"import '$generatedImport';",
|
|
||||||
'',
|
|
||||||
'typedef _UnaryFunction = dynamic Function(List<String> args);',
|
|
||||||
'typedef _NullaryFunction = dynamic Function();',
|
|
||||||
'Future<void> main() async {',
|
|
||||||
' await ui.webOnlyWarmupEngine(',
|
|
||||||
' runApp: () {',
|
|
||||||
' if (entrypoint.main is _UnaryFunction) {',
|
|
||||||
' return (entrypoint.main as _UnaryFunction)(<String>[]);',
|
|
||||||
' }',
|
|
||||||
' return (entrypoint.main as _NullaryFunction)();',
|
|
||||||
' },',
|
|
||||||
if (hasWebPlugins) ...<String>[
|
|
||||||
' registerPlugins: () {',
|
|
||||||
' registerPlugins(webPluginRegistrar);',
|
|
||||||
' },',
|
|
||||||
],
|
|
||||||
' );',
|
|
||||||
'}',
|
|
||||||
'',
|
|
||||||
].join('\n');
|
|
||||||
result.writeAsStringSync(entrypoint);
|
result.writeAsStringSync(entrypoint);
|
||||||
}
|
}
|
||||||
return result.absolute.uri;
|
return result.absolute.uri;
|
||||||
|
@ -373,7 +373,6 @@ class FlutterProject {
|
|||||||
linuxPlatform: linuxPlatform,
|
linuxPlatform: linuxPlatform,
|
||||||
macOSPlatform: macOSPlatform,
|
macOSPlatform: macOSPlatform,
|
||||||
windowsPlatform: windowsPlatform,
|
windowsPlatform: windowsPlatform,
|
||||||
webPlatform: webPlatform,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import '../artifacts.dart';
|
|||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
import '../base/logger.dart';
|
import '../base/logger.dart';
|
||||||
|
import '../base/project_migrator.dart';
|
||||||
import '../build_info.dart';
|
import '../build_info.dart';
|
||||||
import '../build_system/build_system.dart';
|
import '../build_system/build_system.dart';
|
||||||
import '../build_system/targets/web.dart';
|
import '../build_system/targets/web.dart';
|
||||||
@ -15,6 +16,7 @@ import '../globals.dart' as globals;
|
|||||||
import '../platform_plugins.dart';
|
import '../platform_plugins.dart';
|
||||||
import '../plugins.dart';
|
import '../plugins.dart';
|
||||||
import '../project.dart';
|
import '../project.dart';
|
||||||
|
import 'migrations/scrub_generated_plugin_registrant.dart';
|
||||||
|
|
||||||
Future<void> buildWeb(
|
Future<void> buildWeb(
|
||||||
FlutterProject flutterProject,
|
FlutterProject flutterProject,
|
||||||
@ -32,7 +34,16 @@ Future<void> buildWeb(
|
|||||||
final Directory outputDirectory = globals.fs.directory(getWebBuildDirectory());
|
final Directory outputDirectory = globals.fs.directory(getWebBuildDirectory());
|
||||||
outputDirectory.createSync(recursive: true);
|
outputDirectory.createSync(recursive: true);
|
||||||
|
|
||||||
await injectPlugins(flutterProject, webPlatform: true);
|
// The migrators to apply to a Web project.
|
||||||
|
final List<ProjectMigrator> migrators = <ProjectMigrator>[
|
||||||
|
ScrubGeneratedPluginRegistrant(flutterProject.web, globals.logger),
|
||||||
|
];
|
||||||
|
|
||||||
|
final ProjectMigration migration = ProjectMigration(migrators);
|
||||||
|
if (!migration.run()) {
|
||||||
|
throwToolExit('Failed to run all web migrations.');
|
||||||
|
}
|
||||||
|
|
||||||
final Status status = globals.logger.startProgress('Compiling $target for the Web...');
|
final Status status = globals.logger.startProgress('Compiling $target for the Web...');
|
||||||
final Stopwatch sw = Stopwatch()..start();
|
final Stopwatch sw = Stopwatch()..start();
|
||||||
try {
|
try {
|
||||||
|
@ -0,0 +1,203 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a service worker with an app-specific cache name a map of
|
||||||
|
/// resource files.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
String generateServiceWorker(
|
||||||
|
Map<String, String> resources,
|
||||||
|
List<String> coreBundle, {
|
||||||
|
required ServiceWorkerStrategy serviceWorkerStrategy,
|
||||||
|
}) {
|
||||||
|
if (serviceWorkerStrategy == ServiceWorkerStrategy.none) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
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")}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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) => {
|
||||||
|
self.skipWaiting();
|
||||||
|
return event.waitUntil(
|
||||||
|
caches.open(TEMP).then((cache) => {
|
||||||
|
return cache.addAll(
|
||||||
|
CORE.map((value) => new Request(value, {'cache': 'reload'})));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
contentCache = await caches.open(CACHE_NAME);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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) => {
|
||||||
|
if (event.request.method !== 'GET') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var origin = self.location.origin;
|
||||||
|
var key = event.request.url.substring(origin.length + 1);
|
||||||
|
// Redirect URLs to the index.html
|
||||||
|
if (key.indexOf('?v=') != -1) {
|
||||||
|
key = key.split('?v=')[0];
|
||||||
|
}
|
||||||
|
if (event.request.url == origin || event.request.url.startsWith(origin + '/#') || key == '') {
|
||||||
|
key = '/';
|
||||||
|
}
|
||||||
|
// If the URL is not the RESOURCE list then return to signal that the
|
||||||
|
// browser should take over.
|
||||||
|
if (!RESOURCES[key]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If the URL is the index.html, perform an online-first request.
|
||||||
|
if (key == '/') {
|
||||||
|
return onlineFirst(event);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('message', (event) => {
|
||||||
|
// SkipWaiting can be used to immediately activate a waiting service worker.
|
||||||
|
// This will also require a page refresh triggered by the main worker.
|
||||||
|
if (event.data === 'skipWaiting') {
|
||||||
|
self.skipWaiting();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.data === 'downloadOffline') {
|
||||||
|
downloadOffline();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Download offline will check the RESOURCES for all files not in the cache
|
||||||
|
// and populate them.
|
||||||
|
async function downloadOffline() {
|
||||||
|
var resources = [];
|
||||||
|
var contentCache = await caches.open(CACHE_NAME);
|
||||||
|
var currentContent = {};
|
||||||
|
for (var request of await contentCache.keys()) {
|
||||||
|
var key = request.url.substring(origin.length + 1);
|
||||||
|
if (key == "") {
|
||||||
|
key = "/";
|
||||||
|
}
|
||||||
|
currentContent[key] = true;
|
||||||
|
}
|
||||||
|
for (var resourceKey of Object.keys(RESOURCES)) {
|
||||||
|
if (!currentContent[resourceKey]) {
|
||||||
|
resources.push(resourceKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contentCache.addAll(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to download the resource online before falling back to
|
||||||
|
// the offline cache.
|
||||||
|
function onlineFirst(event) {
|
||||||
|
return event.respondWith(
|
||||||
|
fetch(event.request).then((response) => {
|
||||||
|
return caches.open(CACHE_NAME).then((cache) => {
|
||||||
|
cache.put(event.request, response.clone());
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
return caches.open(CACHE_NAME).then((cache) => {
|
||||||
|
return cache.match(event.request).then((response) => {
|
||||||
|
if (response != null) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:package_config/package_config.dart';
|
||||||
|
|
||||||
|
/// Generates the main.dart file.
|
||||||
|
String generateMainDartFile(String appEntrypoint, {
|
||||||
|
required String pluginRegistrantEntrypoint,
|
||||||
|
LanguageVersion? languageVersion,
|
||||||
|
}) {
|
||||||
|
return <String>[
|
||||||
|
if (languageVersion != null)
|
||||||
|
'// @dart=${languageVersion.major}.${languageVersion.minor}',
|
||||||
|
'// Flutter web bootstrap script for $appEntrypoint.',
|
||||||
|
'//',
|
||||||
|
'// Generated file. Do not edit.',
|
||||||
|
'//',
|
||||||
|
'',
|
||||||
|
'// ignore_for_file: type=lint',
|
||||||
|
'',
|
||||||
|
"import 'dart:ui' as ui;",
|
||||||
|
"import 'dart:async';",
|
||||||
|
'',
|
||||||
|
"import '$appEntrypoint' as entrypoint;",
|
||||||
|
"import '$pluginRegistrantEntrypoint' as pluginRegistrant;",
|
||||||
|
'',
|
||||||
|
'typedef _UnaryFunction = dynamic Function(List<String> args);',
|
||||||
|
'typedef _NullaryFunction = dynamic Function();',
|
||||||
|
'',
|
||||||
|
'Future<void> main() async {',
|
||||||
|
' await ui.webOnlyWarmupEngine(',
|
||||||
|
' runApp: () {',
|
||||||
|
' if (entrypoint.main is _UnaryFunction) {',
|
||||||
|
' return (entrypoint.main as _UnaryFunction)(<String>[]);',
|
||||||
|
' }',
|
||||||
|
' return (entrypoint.main as _NullaryFunction)();',
|
||||||
|
' },',
|
||||||
|
' registerPlugins: () {',
|
||||||
|
' pluginRegistrant.registerPlugins();',
|
||||||
|
' },',
|
||||||
|
' );',
|
||||||
|
'}',
|
||||||
|
'',
|
||||||
|
].join('\n');
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import '../../base/file_system.dart';
|
||||||
|
import '../../base/logger.dart';
|
||||||
|
import '../../base/project_migrator.dart';
|
||||||
|
import '../../project.dart';
|
||||||
|
|
||||||
|
/// Remove lib/generated_plugin_registrant.dart if it exists.
|
||||||
|
class ScrubGeneratedPluginRegistrant extends ProjectMigrator {
|
||||||
|
ScrubGeneratedPluginRegistrant(
|
||||||
|
WebProject project,
|
||||||
|
super.logger,
|
||||||
|
) : _project = project, _logger = logger;
|
||||||
|
|
||||||
|
final WebProject _project;
|
||||||
|
final Logger _logger;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool migrate() {
|
||||||
|
final File registrant = _project.libDirectory.childFile('generated_plugin_registrant.dart');
|
||||||
|
final File gitignore = _project.parent.directory.childFile('.gitignore');
|
||||||
|
|
||||||
|
if (!removeFile(registrant)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (gitignore.existsSync()) {
|
||||||
|
processFileLines(gitignore);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleans up the .gitignore by removing the line that mentions generated_plugin_registrant.
|
||||||
|
@override
|
||||||
|
String? migrateLine(String line) {
|
||||||
|
return line.contains('lib/generated_plugin_registrant.dart') ? null : line;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool removeFile(File file) {
|
||||||
|
if (!file.existsSync()) {
|
||||||
|
_logger.printTrace('${file.basename} not found. Skipping.');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
file.deleteSync();
|
||||||
|
_logger.printStatus('${file.basename} found. Deleted.');
|
||||||
|
return true;
|
||||||
|
} on FileSystemException catch (e, s) {
|
||||||
|
_logger.printError(e.message, stackTrace: s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -32,9 +32,6 @@ migrate_working_dir/
|
|||||||
.pub/
|
.pub/
|
||||||
/build/
|
/build/
|
||||||
|
|
||||||
# Web related
|
|
||||||
lib/generated_plugin_registrant.dart
|
|
||||||
|
|
||||||
# Symbolication related
|
# Symbolication related
|
||||||
app.*.symbols
|
app.*.symbols
|
||||||
|
|
||||||
|
@ -92,7 +92,9 @@ void main() {
|
|||||||
setupFileSystemForEndToEndTest(fileSystem);
|
setupFileSystemForEndToEndTest(fileSystem);
|
||||||
await runner.run(<String>['build', 'web', '--no-pub', '--dart-define=foo=a', '--dart2js-optimization=O3']);
|
await runner.run(<String>['build', 'web', '--no-pub', '--dart-define=foo=a', '--dart2js-optimization=O3']);
|
||||||
|
|
||||||
expect(fileSystem.file(fileSystem.path.join('lib', 'generated_plugin_registrant.dart')).existsSync(), true);
|
final Directory buildDir = fileSystem.directory(fileSystem.path.join('build', 'web'));
|
||||||
|
|
||||||
|
expect(buildDir.existsSync(), true);
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Platform: () => fakePlatform,
|
Platform: () => fakePlatform,
|
||||||
FileSystem: () => fileSystem,
|
FileSystem: () => fileSystem,
|
||||||
|
@ -8,12 +8,15 @@ import 'package:file_testing/file_testing.dart';
|
|||||||
import 'package:flutter_tools/src/artifacts.dart';
|
import 'package:flutter_tools/src/artifacts.dart';
|
||||||
import 'package:flutter_tools/src/base/file_system.dart';
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
import 'package:flutter_tools/src/base/platform.dart';
|
import 'package:flutter_tools/src/base/platform.dart';
|
||||||
|
import 'package:flutter_tools/src/base/template.dart';
|
||||||
import 'package:flutter_tools/src/build_info.dart';
|
import 'package:flutter_tools/src/build_info.dart';
|
||||||
import 'package:flutter_tools/src/build_system/build_system.dart';
|
import 'package:flutter_tools/src/build_system/build_system.dart';
|
||||||
import 'package:flutter_tools/src/build_system/depfile.dart';
|
import 'package:flutter_tools/src/build_system/depfile.dart';
|
||||||
import 'package:flutter_tools/src/build_system/targets/web.dart';
|
import 'package:flutter_tools/src/build_system/targets/web.dart';
|
||||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||||
import 'package:flutter_tools/src/web/flutter_js.dart' as flutter_js;
|
import 'package:flutter_tools/src/isolated/mustache_template.dart';
|
||||||
|
import 'package:flutter_tools/src/web/file_generators/flutter_js.dart' as flutter_js;
|
||||||
|
import 'package:flutter_tools/src/web/file_generators/flutter_service_worker_js.dart';
|
||||||
|
|
||||||
import '../../../src/common.dart';
|
import '../../../src/common.dart';
|
||||||
import '../../../src/fake_process_manager.dart';
|
import '../../../src/fake_process_manager.dart';
|
||||||
@ -80,8 +83,8 @@ void main() {
|
|||||||
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';"));
|
expect(generated, contains("import 'web_plugin_registrant.dart' as pluginRegistrant;"));
|
||||||
expect(generated, contains('registerPlugins(webPluginRegistrar);'));
|
expect(generated, contains('pluginRegistrant.registerPlugins();'));
|
||||||
|
|
||||||
// Import.
|
// Import.
|
||||||
expect(generated, contains("import 'package:foo/main.dart' as entrypoint;"));
|
expect(generated, contains("import 'package:foo/main.dart' as entrypoint;"));
|
||||||
@ -89,6 +92,8 @@ void main() {
|
|||||||
// Main
|
// Main
|
||||||
expect(generated, contains('ui.webOnlyWarmupEngine('));
|
expect(generated, contains('ui.webOnlyWarmupEngine('));
|
||||||
expect(generated, contains('entrypoint.main as _'));
|
expect(generated, contains('entrypoint.main as _'));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
TemplateRenderer: () => const MustacheTemplateRenderer(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
test('version.json is created after release build', () => testbed.run(() async {
|
test('version.json is created after release build', () => testbed.run(() async {
|
||||||
@ -196,6 +201,8 @@ void main() {
|
|||||||
|
|
||||||
// Import.
|
// Import.
|
||||||
expect(generated, contains("import 'file:///other/lib/main.dart' as entrypoint;"));
|
expect(generated, contains("import 'file:///other/lib/main.dart' as entrypoint;"));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
TemplateRenderer: () => const MustacheTemplateRenderer(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
test('WebEntrypointTarget generates a plugin registrant for a file outside of main', () => testbed.run(() async {
|
test('WebEntrypointTarget generates a plugin registrant for a file outside of main', () => testbed.run(() async {
|
||||||
@ -210,7 +217,9 @@ void main() {
|
|||||||
|
|
||||||
// Import.
|
// Import.
|
||||||
expect(generated, contains("import 'file:///other/lib/main.dart' as entrypoint;"));
|
expect(generated, contains("import 'file:///other/lib/main.dart' as entrypoint;"));
|
||||||
expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';"));
|
expect(generated, contains("import 'web_plugin_registrant.dart' as pluginRegistrant;"));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
TemplateRenderer: () => const MustacheTemplateRenderer(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
@ -226,8 +235,8 @@ void main() {
|
|||||||
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
expect(generated, contains("import 'package:foo/generated_plugin_registrant.dart';"));
|
expect(generated, contains("import 'web_plugin_registrant.dart' as pluginRegistrant;"));
|
||||||
expect(generated, contains('registerPlugins(webPluginRegistrar);'));
|
expect(generated, contains('pluginRegistrant.registerPlugins();'));
|
||||||
|
|
||||||
// Import.
|
// Import.
|
||||||
expect(generated, contains("import 'package:foo/main.dart' as entrypoint;"));
|
expect(generated, contains("import 'package:foo/main.dart' as entrypoint;"));
|
||||||
@ -237,6 +246,7 @@ void main() {
|
|||||||
expect(generated, contains('entrypoint.main as _'));
|
expect(generated, contains('entrypoint.main as _'));
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Platform: () => windows,
|
Platform: () => windows,
|
||||||
|
TemplateRenderer: () => const MustacheTemplateRenderer(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
test('WebEntrypointTarget generates an entrypoint without plugins and init platform', () => testbed.run(() async {
|
test('WebEntrypointTarget generates an entrypoint without plugins and init platform', () => testbed.run(() async {
|
||||||
@ -249,9 +259,9 @@ void main() {
|
|||||||
|
|
||||||
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
||||||
|
|
||||||
// Plugins
|
// Plugins (the generated file is a noop)
|
||||||
expect(generated, isNot(contains("import 'package:foo/generated_plugin_registrant.dart';")));
|
expect(generated, contains("import 'web_plugin_registrant.dart' as pluginRegistrant;"));
|
||||||
expect(generated, isNot(contains('registerPlugins(webPluginRegistrar);')));
|
expect(generated, contains('pluginRegistrant.registerPlugins();'));
|
||||||
|
|
||||||
// Import.
|
// Import.
|
||||||
expect(generated, contains("import 'package:foo/main.dart' as entrypoint;"));
|
expect(generated, contains("import 'package:foo/main.dart' as entrypoint;"));
|
||||||
@ -259,7 +269,8 @@ void main() {
|
|||||||
// Main
|
// Main
|
||||||
expect(generated, contains('ui.webOnlyWarmupEngine('));
|
expect(generated, contains('ui.webOnlyWarmupEngine('));
|
||||||
expect(generated, contains('entrypoint.main as _'));
|
expect(generated, contains('entrypoint.main as _'));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
TemplateRenderer: () => const MustacheTemplateRenderer(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
test('WebEntrypointTarget generates an entrypoint with a language version', () => testbed.run(() async {
|
test('WebEntrypointTarget generates an entrypoint with a language version', () => testbed.run(() async {
|
||||||
@ -273,6 +284,8 @@ void main() {
|
|||||||
|
|
||||||
// Language version
|
// Language version
|
||||||
expect(generated, contains('// @dart=2.8'));
|
expect(generated, contains('// @dart=2.8'));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
TemplateRenderer: () => const MustacheTemplateRenderer(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
test('WebEntrypointTarget generates an entrypoint with a language version from a package config', () => testbed.run(() async {
|
test('WebEntrypointTarget generates an entrypoint with a language version from a package config', () => testbed.run(() async {
|
||||||
@ -288,6 +301,8 @@ void main() {
|
|||||||
|
|
||||||
// Language version
|
// Language version
|
||||||
expect(generated, contains('// @dart=2.7'));
|
expect(generated, contains('// @dart=2.7'));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
TemplateRenderer: () => const MustacheTemplateRenderer(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
test('WebEntrypointTarget generates an entrypoint without plugins and without init platform', () => testbed.run(() async {
|
test('WebEntrypointTarget generates an entrypoint without plugins and without init platform', () => testbed.run(() async {
|
||||||
@ -301,8 +316,8 @@ void main() {
|
|||||||
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
expect(generated, isNot(contains("import 'package:foo/generated_plugin_registrant.dart';")));
|
expect(generated, contains("import 'web_plugin_registrant.dart' as pluginRegistrant;"));
|
||||||
expect(generated, isNot(contains('registerPlugins(webPluginRegistrar);')));
|
expect(generated, contains('pluginRegistrant.registerPlugins();'));
|
||||||
|
|
||||||
// Import.
|
// Import.
|
||||||
expect(generated, contains("import 'package:foo/main.dart' as entrypoint;"));
|
expect(generated, contains("import 'package:foo/main.dart' as entrypoint;"));
|
||||||
@ -310,6 +325,8 @@ void main() {
|
|||||||
// Main
|
// Main
|
||||||
expect(generated, contains('ui.webOnlyWarmupEngine('));
|
expect(generated, contains('ui.webOnlyWarmupEngine('));
|
||||||
expect(generated, contains('entrypoint.main as _'));
|
expect(generated, contains('entrypoint.main as _'));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
TemplateRenderer: () => const MustacheTemplateRenderer(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
test('Dart2JSTarget calls dart2js with expected args with csp', () => testbed.run(() async {
|
test('Dart2JSTarget calls dart2js with expected args with csp', () => testbed.run(() async {
|
||||||
@ -676,9 +693,9 @@ void main() {
|
|||||||
// Depends on resource file.
|
// Depends on resource file.
|
||||||
expect(environment.buildDir.childFile('service_worker.d').readAsStringSync(),
|
expect(environment.buildDir.childFile('service_worker.d').readAsStringSync(),
|
||||||
contains('a/a.txt'));
|
contains('a/a.txt'));
|
||||||
// Contains NOTICES
|
// Does NOT contain NOTICES
|
||||||
expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(),
|
expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(),
|
||||||
contains('NOTICES'));
|
isNot(contains('NOTICES')));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
test('WebServiceWorker contains baseUrl cache', () => testbed.run(() async {
|
test('WebServiceWorker contains baseUrl cache', () => testbed.run(() async {
|
||||||
|
@ -1079,11 +1079,12 @@ dependencies:
|
|||||||
web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri}
|
web_plugin_with_nested:${webPluginWithNestedFile.childDirectory('lib').uri}
|
||||||
''');
|
''');
|
||||||
|
|
||||||
await injectPlugins(flutterProject, webPlatform: true);
|
final Directory destination = flutterProject.directory.childDirectory('lib');
|
||||||
|
await injectBuildTimePluginFiles(flutterProject, webPlatform: true, destination: destination);
|
||||||
|
|
||||||
final File registrant = flutterProject.directory
|
final File registrant = flutterProject.directory
|
||||||
.childDirectory('lib')
|
.childDirectory('lib')
|
||||||
.childFile('generated_plugin_registrant.dart');
|
.childFile('web_plugin_registrant.dart');
|
||||||
|
|
||||||
expect(registrant.existsSync(), isTrue);
|
expect(registrant.existsSync(), isTrue);
|
||||||
expect(registrant.readAsStringSync(), contains("import 'package:web_plugin_with_nested/src/web_plugin.dart';"));
|
expect(registrant.readAsStringSync(), contains("import 'package:web_plugin_with_nested/src/web_plugin.dart';"));
|
||||||
|
@ -0,0 +1,200 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// @dart = 2.8
|
||||||
|
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
|
import 'package:flutter_tools/src/build_system/build_system.dart';
|
||||||
|
import 'package:flutter_tools/src/cache.dart';
|
||||||
|
import 'package:flutter_tools/src/commands/build.dart';
|
||||||
|
|
||||||
|
import '../../../src/context.dart'; // legacy
|
||||||
|
import '../../../src/test_build_system.dart';
|
||||||
|
import '../../../src/test_flutter_command_runner.dart'; // legacy
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUpAll(() {
|
||||||
|
Cache.flutterRoot = '';
|
||||||
|
Cache.disableLocking();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('ScrubGeneratedPluginRegistrant', () {
|
||||||
|
// The files this migration deals with
|
||||||
|
File gitignore;
|
||||||
|
File registrant;
|
||||||
|
|
||||||
|
// Environment overrides
|
||||||
|
FileSystem fileSystem;
|
||||||
|
ProcessManager processManager;
|
||||||
|
BuildSystem buildSystem;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
// Prepare environment overrides
|
||||||
|
fileSystem = MemoryFileSystem.test();
|
||||||
|
processManager = FakeProcessManager.any();
|
||||||
|
buildSystem = TestBuildSystem.all(BuildResult(success: true));
|
||||||
|
// Write some initial state into our testing filesystem
|
||||||
|
setupFileSystemForEndToEndTest(fileSystem);
|
||||||
|
// Initialize fileSystem references
|
||||||
|
gitignore = fileSystem.file('.gitignore');
|
||||||
|
registrant = fileSystem.file(fileSystem.path.join('lib', 'generated_plugin_registrant.dart'));
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('noop - nothing to do - build runs', () async {
|
||||||
|
expect(gitignore.existsSync(), isFalse);
|
||||||
|
expect(registrant.existsSync(), isFalse);
|
||||||
|
|
||||||
|
await createTestCommandRunner(BuildCommand())
|
||||||
|
.run(<String>['build', 'web', '--no-pub']);
|
||||||
|
|
||||||
|
final Directory buildDir = fileSystem.directory(fileSystem.path.join('build', 'web'));
|
||||||
|
expect(buildDir.existsSync(), true);
|
||||||
|
}, overrides: <Type, Generator> {
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => processManager,
|
||||||
|
BuildSystem: () => buildSystem,
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('noop - .gitignore does not reference generated_plugin_registrant.dart - untouched', () async {
|
||||||
|
writeGitignore(fileSystem, mentionsPluginRegistrant: false);
|
||||||
|
|
||||||
|
final String contentsBeforeBuild = gitignore.readAsStringSync();
|
||||||
|
expect(contentsBeforeBuild, isNot(contains('lib/generated_plugin_registrant.dart')));
|
||||||
|
|
||||||
|
await createTestCommandRunner(BuildCommand())
|
||||||
|
.run(<String>['build', 'web', '--no-pub']);
|
||||||
|
|
||||||
|
expect(gitignore.readAsStringSync(), contentsBeforeBuild);
|
||||||
|
}, overrides: <Type, Generator> {
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => processManager,
|
||||||
|
BuildSystem: () => buildSystem,
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('.gitignore references generated_plugin_registrant - cleans it up', () async {
|
||||||
|
writeGitignore(fileSystem);
|
||||||
|
|
||||||
|
expect(gitignore.existsSync(), isTrue);
|
||||||
|
expect(gitignore.readAsStringSync(), contains('lib/generated_plugin_registrant.dart'));
|
||||||
|
|
||||||
|
await createTestCommandRunner(BuildCommand())
|
||||||
|
.run(<String>['build', 'web', '--no-pub']);
|
||||||
|
|
||||||
|
expect(gitignore.readAsStringSync(), isNot(contains('lib/generated_plugin_registrant.dart')));
|
||||||
|
}, overrides: <Type, Generator> {
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => processManager,
|
||||||
|
BuildSystem: () => buildSystem,
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('generated_plugin_registrant.dart exists - gets deleted', () async {
|
||||||
|
writeGeneratedPluginRegistrant(fileSystem);
|
||||||
|
|
||||||
|
expect(registrant.existsSync(), isTrue);
|
||||||
|
|
||||||
|
await createTestCommandRunner(BuildCommand())
|
||||||
|
.run(<String>['build', 'web', '--no-pub']);
|
||||||
|
|
||||||
|
expect(registrant.existsSync(), isFalse);
|
||||||
|
}, overrides: <Type, Generator> {
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => processManager,
|
||||||
|
BuildSystem: () => buildSystem,
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('scrubs generated_plugin_registrant file and cleans .gitignore', () async {
|
||||||
|
writeGitignore(fileSystem);
|
||||||
|
writeGeneratedPluginRegistrant(fileSystem);
|
||||||
|
|
||||||
|
expect(registrant.existsSync(), isTrue);
|
||||||
|
expect(gitignore.readAsStringSync(), contains('lib/generated_plugin_registrant.dart'));
|
||||||
|
|
||||||
|
await createTestCommandRunner(BuildCommand())
|
||||||
|
.run(<String>['build', 'web', '--no-pub']);
|
||||||
|
|
||||||
|
expect(registrant.existsSync(), isFalse);
|
||||||
|
expect(gitignore.readAsStringSync(), isNot(contains('lib/generated_plugin_registrant.dart')));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => fileSystem,
|
||||||
|
ProcessManager: () => processManager,
|
||||||
|
BuildSystem: () => buildSystem,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes something that resembles the contents of Flutter's .gitignore file
|
||||||
|
void writeGitignore(FileSystem fs, { bool mentionsPluginRegistrant = true }) {
|
||||||
|
fs.file('.gitignore').createSync(recursive: true);
|
||||||
|
fs.file('.gitignore')
|
||||||
|
.writeAsStringSync('''
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Web related
|
||||||
|
${mentionsPluginRegistrant ? 'lib/generated_plugin_registrant.dart':'another_file.dart'}
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
''');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates an empty generated_plugin_registrant.dart file
|
||||||
|
void writeGeneratedPluginRegistrant(FileSystem fs) {
|
||||||
|
final String path = fs.path.join('lib', 'generated_plugin_registrant.dart');
|
||||||
|
fs.file(path).createSync(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a bunch of files to the filesystem
|
||||||
|
// (taken from commands.shard/hermetic/build_web_test.dart)
|
||||||
|
void setupFileSystemForEndToEndTest(FileSystem fileSystem) {
|
||||||
|
final List<String> dependencies = <String>[
|
||||||
|
'.packages',
|
||||||
|
fileSystem.path.join('web', 'index.html'),
|
||||||
|
fileSystem.path.join('lib', 'main.dart'),
|
||||||
|
fileSystem.path.join('packages', 'flutter_tools', 'lib', 'src', 'build_system', 'targets', 'web.dart'),
|
||||||
|
fileSystem.path.join('bin', 'cache', 'flutter_web_sdk'),
|
||||||
|
fileSystem.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'),
|
||||||
|
fileSystem.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'),
|
||||||
|
fileSystem.path.join('bin', 'cache', 'dart-sdk '),
|
||||||
|
];
|
||||||
|
for (final String dependency in dependencies) {
|
||||||
|
fileSystem.file(dependency).createSync(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project files.
|
||||||
|
fileSystem.file('.packages')
|
||||||
|
.writeAsStringSync('''
|
||||||
|
foo:lib/
|
||||||
|
fizz:bar/lib/
|
||||||
|
''');
|
||||||
|
fileSystem.file('pubspec.yaml')
|
||||||
|
.writeAsStringSync('''
|
||||||
|
name: foo
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
fizz:
|
||||||
|
path:
|
||||||
|
bar/
|
||||||
|
''');
|
||||||
|
fileSystem.file(fileSystem.path.join('bar', 'pubspec.yaml'))
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('''
|
||||||
|
name: bar
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
plugin:
|
||||||
|
platforms:
|
||||||
|
web:
|
||||||
|
pluginClass: UrlLauncherPlugin
|
||||||
|
fileName: url_launcher_web.dart
|
||||||
|
''');
|
||||||
|
fileSystem.file(fileSystem.path.join('bar', 'lib', 'url_launcher_web.dart'))
|
||||||
|
..createSync(recursive: true)
|
||||||
|
..writeAsStringSync('''
|
||||||
|
class UrlLauncherPlugin {}
|
||||||
|
''');
|
||||||
|
fileSystem.file(fileSystem.path.join('lib', 'main.dart'))
|
||||||
|
.writeAsStringSync('void main() { }');
|
||||||
|
}
|
@ -49,12 +49,20 @@ void main() {
|
|||||||
// the generated_plugin_registrant generation.
|
// the generated_plugin_registrant generation.
|
||||||
await _addDependency(projectDir, 'shared_preferences',
|
await _addDependency(projectDir, 'shared_preferences',
|
||||||
version: '^2.0.0');
|
version: '^2.0.0');
|
||||||
await _analyzeProject(projectDir);
|
// The plugin registrant is only created after a build...
|
||||||
|
await _buildWebProject(projectDir);
|
||||||
|
|
||||||
|
// Find the web_plugin_registrant, now that it lives outside "lib":
|
||||||
|
final Directory buildDir = projectDir
|
||||||
|
.childDirectory('.dart_tool/flutter_build')
|
||||||
|
.listSync()
|
||||||
|
.firstWhere((FileSystemEntity entity) => entity is Directory) as Directory;
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
projectDir.childFile('lib/generated_plugin_registrant.dart'),
|
buildDir.childFile('web_plugin_registrant.dart'),
|
||||||
exists,
|
exists,
|
||||||
);
|
);
|
||||||
|
await _analyzeEntity(buildDir.childFile('web_plugin_registrant.dart'));
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Pub: () => Pub(
|
Pub: () => Pub(
|
||||||
fileSystem: globals.fs,
|
fileSystem: globals.fs,
|
||||||
@ -88,12 +96,20 @@ void main() {
|
|||||||
'test_web_plugin_with_a_purposefully_extremely_long_package_name',
|
'test_web_plugin_with_a_purposefully_extremely_long_package_name',
|
||||||
path: '../test_plugin',
|
path: '../test_plugin',
|
||||||
);
|
);
|
||||||
await _analyzeProject(projectDir);
|
// The plugin registrant is only created after a build...
|
||||||
|
await _buildWebProject(projectDir);
|
||||||
|
|
||||||
|
// Find the web_plugin_registrant, now that it lives outside "lib":
|
||||||
|
final Directory buildDir = projectDir
|
||||||
|
.childDirectory('.dart_tool/flutter_build')
|
||||||
|
.listSync()
|
||||||
|
.firstWhere((FileSystemEntity entity) => entity is Directory) as Directory;
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
projectDir.childFile('lib/generated_plugin_registrant.dart'),
|
buildDir.childFile('web_plugin_registrant.dart'),
|
||||||
exists,
|
exists,
|
||||||
);
|
);
|
||||||
|
await _analyzeEntity(buildDir.childFile('web_plugin_registrant.dart'));
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Pub: () => Pub(
|
Pub: () => Pub(
|
||||||
fileSystem: globals.fs,
|
fileSystem: globals.fs,
|
||||||
@ -215,7 +231,7 @@ ${linterRules.map((String rule) => ' - $rule').join('\n')}
|
|||||||
''');
|
''');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _analyzeProject(Directory workingDir) async {
|
Future<void> _analyzeEntity(FileSystemEntity target) async {
|
||||||
final String flutterToolsSnapshotPath = globals.fs.path.absolute(
|
final String flutterToolsSnapshotPath = globals.fs.path.absolute(
|
||||||
globals.fs.path.join(
|
globals.fs.path.join(
|
||||||
'..',
|
'..',
|
||||||
@ -229,6 +245,35 @@ Future<void> _analyzeProject(Directory workingDir) async {
|
|||||||
final List<String> args = <String>[
|
final List<String> args = <String>[
|
||||||
flutterToolsSnapshotPath,
|
flutterToolsSnapshotPath,
|
||||||
'analyze',
|
'analyze',
|
||||||
|
target.path,
|
||||||
|
];
|
||||||
|
|
||||||
|
final ProcessResult exec = await Process.run(
|
||||||
|
globals.artifacts.getHostArtifact(HostArtifact.engineDartBinary).path,
|
||||||
|
args,
|
||||||
|
workingDirectory: target is Directory ? target.path : target.dirname,
|
||||||
|
);
|
||||||
|
printOnFailure('Output of flutter analyze:');
|
||||||
|
printOnFailure(exec.stdout.toString());
|
||||||
|
printOnFailure(exec.stderr.toString());
|
||||||
|
expect(exec.exitCode, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _buildWebProject(Directory workingDir) async {
|
||||||
|
final String flutterToolsSnapshotPath = globals.fs.path.absolute(
|
||||||
|
globals.fs.path.join(
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'bin',
|
||||||
|
'cache',
|
||||||
|
'flutter_tools.snapshot',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<String> args = <String>[
|
||||||
|
flutterToolsSnapshotPath,
|
||||||
|
'build',
|
||||||
|
'web',
|
||||||
];
|
];
|
||||||
|
|
||||||
final ProcessResult exec = await Process.run(
|
final ProcessResult exec = await Process.run(
|
||||||
@ -236,7 +281,7 @@ Future<void> _analyzeProject(Directory workingDir) async {
|
|||||||
args,
|
args,
|
||||||
workingDirectory: workingDir.path,
|
workingDirectory: workingDir.path,
|
||||||
);
|
);
|
||||||
printOnFailure('Output of flutter analyze:');
|
printOnFailure('Output of flutter build web:');
|
||||||
printOnFailure(exec.stdout.toString());
|
printOnFailure(exec.stdout.toString());
|
||||||
printOnFailure(exec.stderr.toString());
|
printOnFailure(exec.stderr.toString());
|
||||||
expect(exec.exitCode, 0);
|
expect(exec.exitCode, 0);
|
Loading…
x
Reference in New Issue
Block a user