Unverified Commit 0fb4406c authored by Kevin Chisholm's avatar Kevin Chisholm Committed by GitHub

Revert "[web] Move JS content to its own `.js` files (#117691)" (#120275)

This reverts commit e03029ef.
parent ef854a3d
...@@ -9,7 +9,6 @@ import 'base/common.dart'; ...@@ -9,7 +9,6 @@ import 'base/common.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'base/os.dart'; import 'base/os.dart';
import 'base/platform.dart'; import 'base/platform.dart';
import 'base/user_messages.dart';
import 'base/utils.dart'; import 'base/utils.dart';
import 'build_info.dart'; import 'build_info.dart';
import 'cache.dart'; import 'cache.dart';
...@@ -63,9 +62,6 @@ enum Artifact { ...@@ -63,9 +62,6 @@ enum Artifact {
/// Tools related to subsetting or icon font files. /// Tools related to subsetting or icon font files.
fontSubset, fontSubset,
constFinder, constFinder,
/// The location of file generators.
flutterToolsFileGenerators,
} }
/// A subset of [Artifact]s that are platform and build mode independent /// A subset of [Artifact]s that are platform and build mode independent
...@@ -206,8 +202,6 @@ String? _artifactToFileName(Artifact artifact, Platform hostPlatform, [ BuildMod ...@@ -206,8 +202,6 @@ String? _artifactToFileName(Artifact artifact, Platform hostPlatform, [ BuildMod
return 'font-subset$exe'; return 'font-subset$exe';
case Artifact.constFinder: case Artifact.constFinder:
return 'const_finder.dart.snapshot'; return 'const_finder.dart.snapshot';
case Artifact.flutterToolsFileGenerators:
return '';
} }
} }
...@@ -531,8 +525,6 @@ class CachedArtifacts implements Artifacts { ...@@ -531,8 +525,6 @@ class CachedArtifacts implements Artifacts {
case Artifact.windowsCppClientWrapper: case Artifact.windowsCppClientWrapper:
case Artifact.windowsDesktopPath: case Artifact.windowsDesktopPath:
return _getHostArtifactPath(artifact, platform, mode); return _getHostArtifactPath(artifact, platform, mode);
case Artifact.flutterToolsFileGenerators:
return _getFileGeneratorsPath();
} }
} }
...@@ -570,8 +562,6 @@ class CachedArtifacts implements Artifacts { ...@@ -570,8 +562,6 @@ class CachedArtifacts implements Artifacts {
case Artifact.windowsCppClientWrapper: case Artifact.windowsCppClientWrapper:
case Artifact.windowsDesktopPath: case Artifact.windowsDesktopPath:
return _getHostArtifactPath(artifact, platform, mode); return _getHostArtifactPath(artifact, platform, mode);
case Artifact.flutterToolsFileGenerators:
return _getFileGeneratorsPath();
} }
} }
...@@ -621,8 +611,6 @@ class CachedArtifacts implements Artifacts { ...@@ -621,8 +611,6 @@ class CachedArtifacts implements Artifacts {
case Artifact.windowsCppClientWrapper: case Artifact.windowsCppClientWrapper:
case Artifact.windowsDesktopPath: case Artifact.windowsDesktopPath:
return _getHostArtifactPath(artifact, platform, mode); return _getHostArtifactPath(artifact, platform, mode);
case Artifact.flutterToolsFileGenerators:
return _getFileGeneratorsPath();
} }
} }
...@@ -697,8 +685,6 @@ class CachedArtifacts implements Artifacts { ...@@ -697,8 +685,6 @@ class CachedArtifacts implements Artifacts {
case Artifact.fuchsiaFlutterRunner: case Artifact.fuchsiaFlutterRunner:
case Artifact.fuchsiaKernelCompiler: case Artifact.fuchsiaKernelCompiler:
throw StateError('Artifact $artifact not available for platform $platform.'); throw StateError('Artifact $artifact not available for platform $platform.');
case Artifact.flutterToolsFileGenerators:
return _getFileGeneratorsPath();
} }
} }
...@@ -966,8 +952,6 @@ class CachedLocalEngineArtifacts implements Artifacts { ...@@ -966,8 +952,6 @@ class CachedLocalEngineArtifacts implements Artifacts {
case Artifact.dart2wasmSnapshot: case Artifact.dart2wasmSnapshot:
case Artifact.frontendServerSnapshotForEngineDartSdk: case Artifact.frontendServerSnapshotForEngineDartSdk:
return _fileSystem.path.join(_getDartSdkPath(), 'bin', 'snapshots', artifactFileName); return _fileSystem.path.join(_getDartSdkPath(), 'bin', 'snapshots', artifactFileName);
case Artifact.flutterToolsFileGenerators:
return _getFileGeneratorsPath();
} }
} }
...@@ -1115,7 +1099,6 @@ class CachedLocalWebSdkArtifacts implements Artifacts { ...@@ -1115,7 +1099,6 @@ class CachedLocalWebSdkArtifacts implements Artifacts {
case Artifact.fuchsiaFlutterRunner: case Artifact.fuchsiaFlutterRunner:
case Artifact.fontSubset: case Artifact.fontSubset:
case Artifact.constFinder: case Artifact.constFinder:
case Artifact.flutterToolsFileGenerators:
break; break;
} }
} }
...@@ -1315,11 +1298,6 @@ class _TestArtifacts implements Artifacts { ...@@ -1315,11 +1298,6 @@ class _TestArtifacts implements Artifacts {
BuildMode? mode, BuildMode? mode,
EnvironmentType? environmentType, EnvironmentType? environmentType,
}) { }) {
// The path to file generators is the same even in the test environment.
if (artifact == Artifact.flutterToolsFileGenerators) {
return _getFileGeneratorsPath();
}
final StringBuffer buffer = StringBuffer(); final StringBuffer buffer = StringBuffer();
buffer.write(artifact); buffer.write(artifact);
if (platform != null) { if (platform != null) {
...@@ -1362,20 +1340,3 @@ class _TestLocalEngine extends _TestArtifacts { ...@@ -1362,20 +1340,3 @@ class _TestLocalEngine extends _TestArtifacts {
@override @override
final LocalEngineInfo localEngineInfo; final LocalEngineInfo localEngineInfo;
} }
String _getFileGeneratorsPath() {
final String flutterRoot = Cache.defaultFlutterRoot(
fileSystem: globals.localFileSystem,
platform: const LocalPlatform(),
userMessages: UserMessages(),
);
return globals.localFileSystem.path.join(
flutterRoot,
'packages',
'flutter_tools',
'lib',
'src',
'web',
'file_generators',
);
}
...@@ -529,10 +529,7 @@ class WebBuiltInAssets extends Target { ...@@ -529,10 +529,7 @@ class WebBuiltInAssets extends Target {
// Write the flutter.js file // Write the flutter.js file
final File flutterJsFile = environment.outputDir.childFile('flutter.js'); final File flutterJsFile = environment.outputDir.childFile('flutter.js');
final String fileGeneratorsPath = flutterJsFile.writeAsStringSync(flutter_js.generateFlutterJsFile());
globals.artifacts!.getArtifactPath(Artifact.flutterToolsFileGenerators);
flutterJsFile.writeAsStringSync(
flutter_js.generateFlutterJsFile(fileGeneratorsPath));
} }
} }
...@@ -601,10 +598,7 @@ class WebServiceWorker extends Target { ...@@ -601,10 +598,7 @@ class WebServiceWorker extends Target {
final ServiceWorkerStrategy serviceWorkerStrategy = _serviceWorkerStrategyFromString( final ServiceWorkerStrategy serviceWorkerStrategy = _serviceWorkerStrategyFromString(
environment.defines[kServiceWorkerStrategy], environment.defines[kServiceWorkerStrategy],
); );
final String fileGeneratorsPath =
globals.artifacts!.getArtifactPath(Artifact.flutterToolsFileGenerators);
final String serviceWorker = generateServiceWorker( final String serviceWorker = generateServiceWorker(
fileGeneratorsPath,
urlToHash, urlToHash,
<String>[ <String>[
'main.dart.js', 'main.dart.js',
......
...@@ -828,10 +828,7 @@ class WebDevFS implements DevFS { ...@@ -828,10 +828,7 @@ class WebDevFS implements DevFS {
'stack_trace_mapper.js', stackTraceMapper.readAsBytesSync()); 'stack_trace_mapper.js', stackTraceMapper.readAsBytesSync());
webAssetServer.writeFile( webAssetServer.writeFile(
'manifest.json', '{"info":"manifest not generated in run mode."}'); 'manifest.json', '{"info":"manifest not generated in run mode."}');
final String fileGeneratorsPath = globals.artifacts! webAssetServer.writeFile('flutter.js', flutter_js.generateFlutterJsFile());
.getArtifactPath(Artifact.flutterToolsFileGenerators);
webAssetServer.writeFile(
'flutter.js', flutter_js.generateFlutterJsFile(fileGeneratorsPath));
webAssetServer.writeFile('flutter_service_worker.js', webAssetServer.writeFile('flutter_service_worker.js',
'// Service worker not loaded in run mode.'); '// Service worker not loaded in run mode.');
webAssetServer.writeFile( webAssetServer.writeFile(
......
...@@ -2,17 +2,388 @@ ...@@ -2,17 +2,388 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import '../../globals.dart' as globals;
/// Generates the flutter.js file. /// Generates the flutter.js file.
/// ///
/// flutter.js should be completely static, so **do not use any parameter or /// flutter.js should be completely static, so **do not use any parameter or
/// environment variable to generate this file**. /// environment variable to generate this file**.
String generateFlutterJsFile(String fileGeneratorsPath) { String generateFlutterJsFile() {
final String flutterJsPath = globals.localFileSystem.path.join( return r'''
fileGeneratorsPath, // Copyright 2014 The Flutter Authors. All rights reserved.
'js', // Use of this source code is governed by a BSD-style license that can be
'flutter.js', // found in the LICENSE file.
);
return globals.localFileSystem.file(flutterJsPath).readAsStringSync(); if (!_flutter) {
var _flutter = {};
}
_flutter.loader = null;
(function () {
"use strict";
const baseUri = ensureTrailingSlash(getBaseURI());
function getBaseURI() {
const base = document.querySelector("base");
return (base && base.getAttribute("href")) || "";
}
function ensureTrailingSlash(uri) {
if (uri == "") {
return uri;
}
return uri.endsWith("/") ? uri : `${uri}/`;
}
/**
* Wraps `promise` in a timeout of the given `duration` in ms.
*
* Resolves/rejects with whatever the original `promises` does, or rejects
* if `promise` takes longer to complete than `duration`. In that case,
* `debugName` is used to compose a legible error message.
*
* If `duration` is < 0, the original `promise` is returned unchanged.
* @param {Promise} promise
* @param {number} duration
* @param {string} debugName
* @returns {Promise} a wrapped promise.
*/
async function timeout(promise, duration, debugName) {
if (duration < 0) {
return promise;
}
let timeoutId;
const _clock = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(
new Error(
`${debugName} took more than ${duration}ms to resolve. Moving on.`,
{
cause: timeout,
}
)
);
}, duration);
});
return Promise.race([promise, _clock]).finally(() => {
clearTimeout(timeoutId);
});
}
/**
* Handles the creation of a TrustedTypes `policy` that validates URLs based
* on an (optional) incoming array of RegExes.
*/
class FlutterTrustedTypesPolicy {
/**
* Constructs the policy.
* @param {[RegExp]} validPatterns the patterns to test URLs
* @param {String} policyName the policy name (optional)
*/
constructor(validPatterns, policyName = "flutter-js") {
const patterns = validPatterns || [
/\.dart\.js$/,
/^flutter_service_worker.js$/
];
if (window.trustedTypes) {
this.policy = trustedTypes.createPolicy(policyName, {
createScriptURL: function(url) {
const parsed = new URL(url, window.location);
const file = parsed.pathname.split("/").pop();
const matches = patterns.some((pattern) => pattern.test(file));
if (matches) {
return parsed.toString();
}
console.error(
"URL rejected by TrustedTypes policy",
policyName, ":", url, "(download prevented)");
}
});
}
}
}
/**
* Handles loading/reloading Flutter's service worker, if configured.
*
* @see: https://developers.google.com/web/fundamentals/primers/service-workers
*/
class FlutterServiceWorkerLoader {
/**
* Injects a TrustedTypesPolicy (or undefined if the feature is not supported).
* @param {TrustedTypesPolicy | undefined} policy
*/
setTrustedTypesPolicy(policy) {
this._ttPolicy = policy;
}
/**
* Returns a Promise that resolves when the latest Flutter service worker,
* configured by `settings` has been loaded and activated.
*
* Otherwise, the promise is rejected with an error message.
* @param {*} settings Service worker settings
* @returns {Promise} that resolves when the latest serviceWorker is ready.
*/
loadServiceWorker(settings) {
if (!("serviceWorker" in navigator) || settings == null) {
// In the future, settings = null -> uninstall service worker?
return Promise.reject(
new Error("Service worker not supported (or configured).")
);
}
const {
serviceWorkerVersion,
serviceWorkerUrl = `${baseUri}flutter_service_worker.js?v=${serviceWorkerVersion}`,
timeoutMillis = 4000,
} = settings;
// Apply the TrustedTypes policy, if present.
let url = serviceWorkerUrl;
if (this._ttPolicy != null) {
url = this._ttPolicy.createScriptURL(url);
}
const serviceWorkerActivation = navigator.serviceWorker
.register(url)
.then(this._getNewServiceWorker)
.then(this._waitForServiceWorkerActivation);
// Timeout race promise
return timeout(
serviceWorkerActivation,
timeoutMillis,
"prepareServiceWorker"
);
}
/**
* Returns the latest service worker for the given `serviceWorkerRegistrationPromise`.
*
* This might return the current service worker, if there's no new service worker
* awaiting to be installed/updated.
*
* @param {Promise<ServiceWorkerRegistration>} serviceWorkerRegistrationPromise
* @returns {Promise<ServiceWorker>}
*/
async _getNewServiceWorker(serviceWorkerRegistrationPromise) {
const reg = await serviceWorkerRegistrationPromise;
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
console.debug("Installing/Activating first service worker.");
return reg.installing || reg.waiting;
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
return reg.update().then((newReg) => {
console.debug("Updating service worker.");
return newReg.installing || newReg.waiting || newReg.active;
});
} else {
console.debug("Loading from existing service worker.");
return reg.active;
}
}
/**
* Returns a Promise that resolves when the `latestServiceWorker` changes its
* state to "activated".
*
* @param {Promise<ServiceWorker>} latestServiceWorkerPromise
* @returns {Promise<void>}
*/
async _waitForServiceWorkerActivation(latestServiceWorkerPromise) {
const serviceWorker = await latestServiceWorkerPromise;
if (!serviceWorker || serviceWorker.state == "activated") {
if (!serviceWorker) {
return Promise.reject(
new Error("Cannot activate a null service worker!")
);
} else {
console.debug("Service worker already active.");
return Promise.resolve();
}
}
return new Promise((resolve, _) => {
serviceWorker.addEventListener("statechange", () => {
if (serviceWorker.state == "activated") {
console.debug("Activated new service worker.");
resolve();
}
});
});
}
}
/**
* Handles injecting the main Flutter web entrypoint (main.dart.js), and notifying
* the user when Flutter is ready, through `didCreateEngineInitializer`.
*
* @see https://docs.flutter.dev/development/platform-integration/web/initialization
*/
class FlutterEntrypointLoader {
/**
* Creates a FlutterEntrypointLoader.
*/
constructor() {
// Watchdog to prevent injecting the main entrypoint multiple times.
this._scriptLoaded = false;
}
/**
* Injects a TrustedTypesPolicy (or undefined if the feature is not supported).
* @param {TrustedTypesPolicy | undefined} policy
*/
setTrustedTypesPolicy(policy) {
this._ttPolicy = policy;
}
/**
* Loads flutter main entrypoint, specified by `entrypointUrl`, and calls a
* user-specified `onEntrypointLoaded` callback with an EngineInitializer
* object when it's done.
*
* @param {*} options
* @returns {Promise | undefined} that will eventually resolve with an
* EngineInitializer, or will be rejected with the error caused by the loader.
* Returns undefined when an `onEntrypointLoaded` callback is supplied in `options`.
*/
async loadEntrypoint(options) {
const { entrypointUrl = `${baseUri}main.dart.js`, onEntrypointLoaded } =
options || {};
return this._loadEntrypoint(entrypointUrl, onEntrypointLoaded);
}
/**
* Resolves the promise created by loadEntrypoint, and calls the `onEntrypointLoaded`
* function supplied by the user (if needed).
*
* Called by Flutter through `_flutter.loader.didCreateEngineInitializer` method,
* which is bound to the correct instance of the FlutterEntrypointLoader by
* the FlutterLoader object.
*
* @param {Function} engineInitializer @see https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/js_interop/js_loader.dart#L42
*/
didCreateEngineInitializer(engineInitializer) {
if (typeof this._didCreateEngineInitializerResolve === "function") {
this._didCreateEngineInitializerResolve(engineInitializer);
// Remove the resolver after the first time, so Flutter Web can hot restart.
this._didCreateEngineInitializerResolve = null;
// Make the engine revert to "auto" initialization on hot restart.
delete _flutter.loader.didCreateEngineInitializer;
}
if (typeof this._onEntrypointLoaded === "function") {
this._onEntrypointLoaded(engineInitializer);
}
}
/**
* Injects a script tag into the DOM, and configures this loader to be able to
* handle the "entrypoint loaded" notifications received from Flutter web.
*
* @param {string} entrypointUrl the URL of the script that will initialize
* Flutter.
* @param {Function} onEntrypointLoaded a callback that will be called when
* Flutter web notifies this object that the entrypoint is
* loaded.
* @returns {Promise | undefined} a Promise that resolves when the entrypoint
* is loaded, or undefined if `onEntrypointLoaded`
* is a function.
*/
_loadEntrypoint(entrypointUrl, onEntrypointLoaded) {
const useCallback = typeof onEntrypointLoaded === "function";
if (!this._scriptLoaded) {
this._scriptLoaded = true;
const scriptTag = this._createScriptTag(entrypointUrl);
if (useCallback) {
// Just inject the script tag, and return nothing; Flutter will call
// `didCreateEngineInitializer` when it's done.
console.debug("Injecting <script> tag. Using callback.");
this._onEntrypointLoaded = onEntrypointLoaded;
document.body.append(scriptTag);
} else {
// Inject the script tag and return a promise that will get resolved
// with the EngineInitializer object from Flutter when it calls
// `didCreateEngineInitializer` later.
return new Promise((resolve, reject) => {
console.debug(
"Injecting <script> tag. Using Promises. Use the callback approach instead!"
);
this._didCreateEngineInitializerResolve = resolve;
scriptTag.addEventListener("error", reject);
document.body.append(scriptTag);
});
}
}
}
/**
* Creates a script tag for the given URL.
* @param {string} url
* @returns {HTMLScriptElement}
*/
_createScriptTag(url) {
const scriptTag = document.createElement("script");
scriptTag.type = "application/javascript";
// Apply TrustedTypes validation, if available.
let trustedUrl = url;
if (this._ttPolicy != null) {
trustedUrl = this._ttPolicy.createScriptURL(url);
}
scriptTag.src = trustedUrl;
return scriptTag;
}
}
/**
* The public interface of _flutter.loader. Exposes two methods:
* * loadEntrypoint (which coordinates the default Flutter web loading procedure)
* * didCreateEngineInitializer (which is called by Flutter to notify that its
* Engine is ready to be initialized)
*/
class FlutterLoader {
/**
* Initializes the Flutter web app.
* @param {*} options
* @returns {Promise?} a (Deprecated) Promise that will eventually resolve
* with an EngineInitializer, or will be rejected with
* any error caused by the loader. Or Null, if the user
* supplies an `onEntrypointLoaded` Function as an option.
*/
async loadEntrypoint(options) {
const { serviceWorker, ...entrypoint } = options || {};
// A Trusted Types policy that is going to be used by the loader.
const flutterTT = new FlutterTrustedTypesPolicy();
// The FlutterServiceWorkerLoader instance could be injected as a dependency
// (and dynamically imported from a module if not present).
const serviceWorkerLoader = new FlutterServiceWorkerLoader();
serviceWorkerLoader.setTrustedTypesPolicy(flutterTT.policy);
await serviceWorkerLoader.loadServiceWorker(serviceWorker).catch(e => {
// Regardless of what happens with the injection of the SW, the show must go on
console.warn("Exception while loading service worker:", e);
});
// The FlutterEntrypointLoader instance could be injected as a dependency
// (and dynamically imported from a module if not present).
const entrypointLoader = new FlutterEntrypointLoader();
entrypointLoader.setTrustedTypesPolicy(flutterTT.policy);
// Install the `didCreateEngineInitializer` listener where Flutter web expects it to be.
this.didCreateEngineInitializer =
entrypointLoader.didCreateEngineInitializer.bind(entrypointLoader);
return entrypointLoader.loadEntrypoint(entrypoint);
}
}
_flutter.loader = new FlutterLoader();
})();
''';
} }
...@@ -2,14 +2,11 @@ ...@@ -2,14 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import '../../globals.dart' as globals;
/// The caching strategy for the generated service worker. /// The caching strategy for the generated service worker.
enum ServiceWorkerStrategy { enum ServiceWorkerStrategy {
/// Download the app shell eagerly and all other assets lazily. /// Download the app shell eagerly and all other assets lazily.
/// Prefer the offline cached version. /// Prefer the offline cached version.
offlineFirst, offlineFirst,
/// Do not generate a service worker, /// Do not generate a service worker,
none, none,
} }
...@@ -21,7 +18,6 @@ enum ServiceWorkerStrategy { ...@@ -21,7 +18,6 @@ enum ServiceWorkerStrategy {
/// invalidation will automatically reactivate workers whenever a new /// invalidation will automatically reactivate workers whenever a new
/// version is deployed. /// version is deployed.
String generateServiceWorker( String generateServiceWorker(
String fileGeneratorsPath,
Map<String, String> resources, Map<String, String> resources,
List<String> coreBundle, { List<String> coreBundle, {
required ServiceWorkerStrategy serviceWorkerStrategy, required ServiceWorkerStrategy serviceWorkerStrategy,
...@@ -29,21 +25,185 @@ String generateServiceWorker( ...@@ -29,21 +25,185 @@ String generateServiceWorker(
if (serviceWorkerStrategy == ServiceWorkerStrategy.none) { if (serviceWorkerStrategy == ServiceWorkerStrategy.none) {
return ''; 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)));
// Claim client to enable caching on first launch
self.clients.claim();
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)));
// Claim client to enable caching on first launch
self.clients.claim();
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 only if the resource was successfully fetched.
return response || fetch(event.request).then((response) => {
if (response && Boolean(response.ok)) {
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;
}
});
final String flutterServiceWorkerJsPath = globals.localFileSystem.path.join( // Download offline will check the RESOURCES for all files not in the cache
fileGeneratorsPath, // and populate them.
'js', async function downloadOffline() {
'flutter_service_worker.js', 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;
});
});
})
); );
return globals.localFileSystem }
.file(flutterServiceWorkerJsPath) ''';
.readAsStringSync()
.replaceAll(
r'$$RESOURCES_MAP',
'{${resources.entries.map((MapEntry<String, String> entry) => '"${entry.key}": "${entry.value}"').join(",\n")}}',
)
.replaceAll(
r'$$CORE_LIST',
'[${coreBundle.map((String file) => '"$file"').join(',\n')}]',
);
} }
// 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.
if (!_flutter) {
var _flutter = {};
}
_flutter.loader = null;
(function () {
"use strict";
const baseUri = ensureTrailingSlash(getBaseURI());
function getBaseURI() {
const base = document.querySelector("base");
return (base && base.getAttribute("href")) || "";
}
function ensureTrailingSlash(uri) {
if (uri == "") {
return uri;
}
return uri.endsWith("/") ? uri : `${uri}/`;
}
/**
* Wraps `promise` in a timeout of the given `duration` in ms.
*
* Resolves/rejects with whatever the original `promises` does, or rejects
* if `promise` takes longer to complete than `duration`. In that case,
* `debugName` is used to compose a legible error message.
*
* If `duration` is < 0, the original `promise` is returned unchanged.
* @param {Promise} promise
* @param {number} duration
* @param {string} debugName
* @returns {Promise} a wrapped promise.
*/
async function timeout(promise, duration, debugName) {
if (duration < 0) {
return promise;
}
let timeoutId;
const _clock = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(
new Error(
`${debugName} took more than ${duration}ms to resolve. Moving on.`,
{
cause: timeout,
}
)
);
}, duration);
});
return Promise.race([promise, _clock]).finally(() => {
clearTimeout(timeoutId);
});
}
/**
* Handles the creation of a TrustedTypes `policy` that validates URLs based
* on an (optional) incoming array of RegExes.
*/
class FlutterTrustedTypesPolicy {
/**
* Constructs the policy.
* @param {[RegExp]} validPatterns the patterns to test URLs
* @param {String} policyName the policy name (optional)
*/
constructor(validPatterns, policyName = "flutter-js") {
const patterns = validPatterns || [
/\.dart\.js$/,
/^flutter_service_worker.js$/
];
if (window.trustedTypes) {
this.policy = trustedTypes.createPolicy(policyName, {
createScriptURL: function(url) {
const parsed = new URL(url, window.location);
const file = parsed.pathname.split("/").pop();
const matches = patterns.some((pattern) => pattern.test(file));
if (matches) {
return parsed.toString();
}
console.error(
"URL rejected by TrustedTypes policy",
policyName, ":", url, "(download prevented)");
}
});
}
}
}
/**
* Handles loading/reloading Flutter's service worker, if configured.
*
* @see: https://developers.google.com/web/fundamentals/primers/service-workers
*/
class FlutterServiceWorkerLoader {
/**
* Injects a TrustedTypesPolicy (or undefined if the feature is not supported).
* @param {TrustedTypesPolicy | undefined} policy
*/
setTrustedTypesPolicy(policy) {
this._ttPolicy = policy;
}
/**
* Returns a Promise that resolves when the latest Flutter service worker,
* configured by `settings` has been loaded and activated.
*
* Otherwise, the promise is rejected with an error message.
* @param {*} settings Service worker settings
* @returns {Promise} that resolves when the latest serviceWorker is ready.
*/
loadServiceWorker(settings) {
if (!("serviceWorker" in navigator) || settings == null) {
// In the future, settings = null -> uninstall service worker?
return Promise.reject(
new Error("Service worker not supported (or configured).")
);
}
const {
serviceWorkerVersion,
serviceWorkerUrl = `${baseUri}flutter_service_worker.js?v=${serviceWorkerVersion}`,
timeoutMillis = 4000,
} = settings;
// Apply the TrustedTypes policy, if present.
let url = serviceWorkerUrl;
if (this._ttPolicy != null) {
url = this._ttPolicy.createScriptURL(url);
}
const serviceWorkerActivation = navigator.serviceWorker
.register(url)
.then(this._getNewServiceWorker)
.then(this._waitForServiceWorkerActivation);
// Timeout race promise
return timeout(
serviceWorkerActivation,
timeoutMillis,
"prepareServiceWorker"
);
}
/**
* Returns the latest service worker for the given `serviceWorkerRegistrationPromise`.
*
* This might return the current service worker, if there's no new service worker
* awaiting to be installed/updated.
*
* @param {Promise<ServiceWorkerRegistration>} serviceWorkerRegistrationPromise
* @returns {Promise<ServiceWorker>}
*/
async _getNewServiceWorker(serviceWorkerRegistrationPromise) {
const reg = await serviceWorkerRegistrationPromise;
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
console.debug("Installing/Activating first service worker.");
return reg.installing || reg.waiting;
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
return reg.update().then((newReg) => {
console.debug("Updating service worker.");
return newReg.installing || newReg.waiting || newReg.active;
});
} else {
console.debug("Loading from existing service worker.");
return reg.active;
}
}
/**
* Returns a Promise that resolves when the `latestServiceWorker` changes its
* state to "activated".
*
* @param {Promise<ServiceWorker>} latestServiceWorkerPromise
* @returns {Promise<void>}
*/
async _waitForServiceWorkerActivation(latestServiceWorkerPromise) {
const serviceWorker = await latestServiceWorkerPromise;
if (!serviceWorker || serviceWorker.state == "activated") {
if (!serviceWorker) {
return Promise.reject(
new Error("Cannot activate a null service worker!")
);
} else {
console.debug("Service worker already active.");
return Promise.resolve();
}
}
return new Promise((resolve, _) => {
serviceWorker.addEventListener("statechange", () => {
if (serviceWorker.state == "activated") {
console.debug("Activated new service worker.");
resolve();
}
});
});
}
}
/**
* Handles injecting the main Flutter web entrypoint (main.dart.js), and notifying
* the user when Flutter is ready, through `didCreateEngineInitializer`.
*
* @see https://docs.flutter.dev/development/platform-integration/web/initialization
*/
class FlutterEntrypointLoader {
/**
* Creates a FlutterEntrypointLoader.
*/
constructor() {
// Watchdog to prevent injecting the main entrypoint multiple times.
this._scriptLoaded = false;
}
/**
* Injects a TrustedTypesPolicy (or undefined if the feature is not supported).
* @param {TrustedTypesPolicy | undefined} policy
*/
setTrustedTypesPolicy(policy) {
this._ttPolicy = policy;
}
/**
* Loads flutter main entrypoint, specified by `entrypointUrl`, and calls a
* user-specified `onEntrypointLoaded` callback with an EngineInitializer
* object when it's done.
*
* @param {*} options
* @returns {Promise | undefined} that will eventually resolve with an
* EngineInitializer, or will be rejected with the error caused by the loader.
* Returns undefined when an `onEntrypointLoaded` callback is supplied in `options`.
*/
async loadEntrypoint(options) {
const { entrypointUrl = `${baseUri}main.dart.js`, onEntrypointLoaded } =
options || {};
return this._loadEntrypoint(entrypointUrl, onEntrypointLoaded);
}
/**
* Resolves the promise created by loadEntrypoint, and calls the `onEntrypointLoaded`
* function supplied by the user (if needed).
*
* Called by Flutter through `_flutter.loader.didCreateEngineInitializer` method,
* which is bound to the correct instance of the FlutterEntrypointLoader by
* the FlutterLoader object.
*
* @param {Function} engineInitializer @see https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/js_interop/js_loader.dart#L42
*/
didCreateEngineInitializer(engineInitializer) {
if (typeof this._didCreateEngineInitializerResolve === "function") {
this._didCreateEngineInitializerResolve(engineInitializer);
// Remove the resolver after the first time, so Flutter Web can hot restart.
this._didCreateEngineInitializerResolve = null;
// Make the engine revert to "auto" initialization on hot restart.
delete _flutter.loader.didCreateEngineInitializer;
}
if (typeof this._onEntrypointLoaded === "function") {
this._onEntrypointLoaded(engineInitializer);
}
}
/**
* Injects a script tag into the DOM, and configures this loader to be able to
* handle the "entrypoint loaded" notifications received from Flutter web.
*
* @param {string} entrypointUrl the URL of the script that will initialize
* Flutter.
* @param {Function} onEntrypointLoaded a callback that will be called when
* Flutter web notifies this object that the entrypoint is
* loaded.
* @returns {Promise | undefined} a Promise that resolves when the entrypoint
* is loaded, or undefined if `onEntrypointLoaded`
* is a function.
*/
_loadEntrypoint(entrypointUrl, onEntrypointLoaded) {
const useCallback = typeof onEntrypointLoaded === "function";
if (!this._scriptLoaded) {
this._scriptLoaded = true;
const scriptTag = this._createScriptTag(entrypointUrl);
if (useCallback) {
// Just inject the script tag, and return nothing; Flutter will call
// `didCreateEngineInitializer` when it's done.
console.debug("Injecting <script> tag. Using callback.");
this._onEntrypointLoaded = onEntrypointLoaded;
document.body.append(scriptTag);
} else {
// Inject the script tag and return a promise that will get resolved
// with the EngineInitializer object from Flutter when it calls
// `didCreateEngineInitializer` later.
return new Promise((resolve, reject) => {
console.debug(
"Injecting <script> tag. Using Promises. Use the callback approach instead!"
);
this._didCreateEngineInitializerResolve = resolve;
scriptTag.addEventListener("error", reject);
document.body.append(scriptTag);
});
}
}
}
/**
* Creates a script tag for the given URL.
* @param {string} url
* @returns {HTMLScriptElement}
*/
_createScriptTag(url) {
const scriptTag = document.createElement("script");
scriptTag.type = "application/javascript";
// Apply TrustedTypes validation, if available.
let trustedUrl = url;
if (this._ttPolicy != null) {
trustedUrl = this._ttPolicy.createScriptURL(url);
}
scriptTag.src = trustedUrl;
return scriptTag;
}
}
/**
* The public interface of _flutter.loader. Exposes two methods:
* * loadEntrypoint (which coordinates the default Flutter web loading procedure)
* * didCreateEngineInitializer (which is called by Flutter to notify that its
* Engine is ready to be initialized)
*/
class FlutterLoader {
/**
* Initializes the Flutter web app.
* @param {*} options
* @returns {Promise?} a (Deprecated) Promise that will eventually resolve
* with an EngineInitializer, or will be rejected with
* any error caused by the loader. Or Null, if the user
* supplies an `onEntrypointLoaded` Function as an option.
*/
async loadEntrypoint(options) {
const { serviceWorker, ...entrypoint } = options || {};
// A Trusted Types policy that is going to be used by the loader.
const flutterTT = new FlutterTrustedTypesPolicy();
// The FlutterServiceWorkerLoader instance could be injected as a dependency
// (and dynamically imported from a module if not present).
const serviceWorkerLoader = new FlutterServiceWorkerLoader();
serviceWorkerLoader.setTrustedTypesPolicy(flutterTT.policy);
await serviceWorkerLoader.loadServiceWorker(serviceWorker).catch(e => {
// Regardless of what happens with the injection of the SW, the show must go on
console.warn("Exception while loading service worker:", e);
});
// The FlutterEntrypointLoader instance could be injected as a dependency
// (and dynamically imported from a module if not present).
const entrypointLoader = new FlutterEntrypointLoader();
entrypointLoader.setTrustedTypesPolicy(flutterTT.policy);
// Install the `didCreateEngineInitializer` listener where Flutter web expects it to be.
this.didCreateEngineInitializer =
entrypointLoader.didCreateEngineInitializer.bind(entrypointLoader);
return entrypointLoader.loadEntrypoint(entrypoint);
}
}
_flutter.loader = new FlutterLoader();
})();
'use strict';
const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = $$RESOURCES_MAP;
// The application shell files that are downloaded before a service worker can
// start.
const CORE = $$CORE_LIST;
// 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)));
// Claim client to enable caching on first launch
self.clients.claim();
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)));
// Claim client to enable caching on first launch
self.clients.claim();
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 only if the resource was successfully fetched.
return response || fetch(event.request).then((response) => {
if (response && Boolean(response.ok)) {
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;
});
});
})
);
}
...@@ -785,40 +785,19 @@ void main() { ...@@ -785,40 +785,19 @@ void main() {
})); }));
test('Generated service worker is empty with none-strategy', () { test('Generated service worker is empty with none-strategy', () {
final String fileGeneratorsPath = final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'}, <String>[], serviceWorkerStrategy: ServiceWorkerStrategy.none);
environment.artifacts.getArtifactPath(Artifact.flutterToolsFileGenerators);
final String result = generateServiceWorker(
fileGeneratorsPath,
<String, String>{'/foo': 'abcd'},
<String>[],
serviceWorkerStrategy: ServiceWorkerStrategy.none,
);
expect(result, ''); expect(result, '');
}); });
test('Generated service worker correctly inlines file hashes', () { test('Generated service worker correctly inlines file hashes', () {
final String fileGeneratorsPath = final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'}, <String>[], serviceWorkerStrategy: ServiceWorkerStrategy.offlineFirst);
environment.artifacts.getArtifactPath(Artifact.flutterToolsFileGenerators);
final String result = generateServiceWorker(
fileGeneratorsPath,
<String, String>{'/foo': 'abcd'},
<String>[],
serviceWorkerStrategy: ServiceWorkerStrategy.offlineFirst,
);
expect(result, contains('{"/foo": "abcd"};')); expect(result, contains('{\n "/foo": "abcd"\n};'));
}); });
test('Generated service worker includes core files', () { test('Generated service worker includes core files', () {
final String fileGeneratorsPath = final String result = generateServiceWorker(<String, String>{'/foo': 'abcd'}, <String>['foo', 'bar'], serviceWorkerStrategy: ServiceWorkerStrategy.offlineFirst);
environment.artifacts.getArtifactPath(Artifact.flutterToolsFileGenerators);
final String result = generateServiceWorker(
fileGeneratorsPath,
<String, String>{'/foo': 'abcd'},
<String>['foo', 'bar'],
serviceWorkerStrategy: ServiceWorkerStrategy.offlineFirst,
);
expect(result, contains('"foo",\n"bar"')); expect(result, contains('"foo",\n"bar"'));
}); });
...@@ -875,10 +854,7 @@ void main() { ...@@ -875,10 +854,7 @@ void main() {
})); }));
test('flutter.js sanity checks', () { test('flutter.js sanity checks', () {
final String fileGeneratorsPath = environment.artifacts final String flutterJsContents = flutter_js.generateFlutterJsFile();
.getArtifactPath(Artifact.flutterToolsFileGenerators);
final String flutterJsContents =
flutter_js.generateFlutterJsFile(fileGeneratorsPath);
expect(flutterJsContents, contains('"use strict";')); expect(flutterJsContents, contains('"use strict";'));
expect(flutterJsContents, contains('main.dart.js')); expect(flutterJsContents, contains('main.dart.js'));
expect(flutterJsContents, contains('flutter_service_worker.js?v=')); expect(flutterJsContents, contains('flutter_service_worker.js?v='));
...@@ -897,14 +873,8 @@ void main() { ...@@ -897,14 +873,8 @@ void main() {
await WebBuiltInAssets(globals.fs, globals.cache, false).build(environment); await WebBuiltInAssets(globals.fs, globals.cache, false).build(environment);
// No caching of source maps. // No caching of source maps.
final String fileGeneratorsPath = environment.artifacts expect(environment.outputDir.childFile('flutter.js').readAsStringSync(),
.getArtifactPath(Artifact.flutterToolsFileGenerators); equals(flutter_js.generateFlutterJsFile()));
final String flutterJsContents =
flutter_js.generateFlutterJsFile(fileGeneratorsPath);
expect(
environment.outputDir.childFile('flutter.js').readAsStringSync(),
equals(flutterJsContents),
);
})); }));
test('wasm build copies and generates specific files', () => testbed.run(() async { test('wasm build copies and generates specific files', () => testbed.run(() async {
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/web/file_generators/flutter_js.dart'; import 'package:flutter_tools/src/web/file_generators/flutter_js.dart';
import '../test_utils.dart'; import '../test_utils.dart';
...@@ -60,13 +59,9 @@ abstract class Project { ...@@ -60,13 +59,9 @@ abstract class Project {
} }
deferredComponents?.setUpIn(dir); deferredComponents?.setUpIn(dir);
final String fileGeneratorsPath =
Artifacts.test().getArtifactPath(Artifact.flutterToolsFileGenerators);
final String flutterJsContents = generateFlutterJsFile(fileGeneratorsPath);
// Setup for different flutter web initializations // Setup for different flutter web initializations
writeFile(fileSystem.path.join(dir.path, 'web', 'index.html'), indexHtml); writeFile(fileSystem.path.join(dir.path, 'web', 'index.html'), indexHtml);
writeFile(fileSystem.path.join(dir.path, 'web', 'flutter.js'), flutterJsContents); writeFile(fileSystem.path.join(dir.path, 'web', 'flutter.js'), generateFlutterJsFile());
writeFile(fileSystem.path.join(dir.path, 'web', 'flutter_service_worker.js'), ''); writeFile(fileSystem.path.join(dir.path, 'web', 'flutter_service_worker.js'), '');
writePackages(dir.path); writePackages(dir.path);
await getPackages(dir.path); await getPackages(dir.path);
......
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