From 5a9fa1e7bf5c2094e1a2ccfc7b496111ae8e4f9e Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 13 Feb 2024 12:02:10 -0800 Subject: [PATCH] Dual compile reland (#143262) This is an attempt at a reland of https://github.com/flutter/flutter/pull/141396 The main changes here that are different than the original PR is fixes to wire up the `flutter test` command properly with the web renderer. --- dev/benchmarks/macrobenchmarks/web/index.html | 6 +- dev/devicelab/lib/tasks/web_benchmarks.dart | 4 +- .../web_e2e_tests/web/index.html | 17 +- .../flutter_tools/lib/src/build_info.dart | 13 +- .../lib/src/build_system/build_system.dart | 148 ++++++++--- .../lib/src/build_system/build_targets.dart | 14 +- .../lib/src/build_system/targets/web.dart | 150 ++++++----- .../lib/src/commands/build_web.dart | 40 ++- .../flutter_tools/lib/src/commands/run.dart | 7 + .../flutter_tools/lib/src/commands/test.dart | 6 + packages/flutter_tools/lib/src/device.dart | 9 + .../lib/src/drive/web_driver_service.dart | 2 + .../flutter_tools/lib/src/html_utils.dart | 7 + .../lib/src/isolated/build_targets.dart | 9 +- .../lib/src/isolated/devfs_web.dart | 30 ++- .../lib/src/isolated/resident_web_runner.dart | 15 +- .../lib/src/resident_runner.dart | 2 +- .../lib/src/runner/flutter_command.dart | 22 +- .../lib/src/test/flutter_web_platform.dart | 131 ++++++---- .../flutter_tools/lib/src/test/runner.dart | 2 + .../lib/src/test/web_test_compiler.dart | 7 +- .../flutter_tools/lib/src/web/compile.dart | 56 ++--- .../lib/src/web/compiler_config.dart | 145 +++++------ .../hermetic/build_web_test.dart | 27 +- .../hermetic/flutter_web_platform_test.dart | 109 ++++++++ .../commands.shard/hermetic/run_test.dart | 42 ---- .../commands.shard/hermetic/test_test.dart | 19 ++ .../build_system/build_system_test.dart | 41 ++- .../targets/web_defines_test.dart | 50 ++++ .../build_system/targets/web_test.dart | 235 ++++++++++++++---- .../test/web_test_compiler_test.dart | 4 + .../general.shard/web/compile_web_test.dart | 33 +-- .../general.shard/web/devfs_web_test.dart | 34 +-- .../flutter_build_wasm_test.dart | 2 +- .../test/web.shard/hot_reload_web_test.dart | 1 + .../hot_reload_index_html_samples.dart | 21 ++ 36 files changed, 992 insertions(+), 468 deletions(-) create mode 100644 packages/flutter_tools/test/commands.shard/hermetic/flutter_web_platform_test.dart create mode 100644 packages/flutter_tools/test/general.shard/build_system/targets/web_defines_test.dart diff --git a/dev/benchmarks/macrobenchmarks/web/index.html b/dev/benchmarks/macrobenchmarks/web/index.html index dec5a5150c7..f73aee43082 100644 --- a/dev/benchmarks/macrobenchmarks/web/index.html +++ b/dev/benchmarks/macrobenchmarks/web/index.html @@ -6,8 +6,12 @@ found in the LICENSE file. --> Web Benchmarks + - + diff --git a/dev/devicelab/lib/tasks/web_benchmarks.dart b/dev/devicelab/lib/tasks/web_benchmarks.dart index a4748b8f9f8..031d478fcfa 100644 --- a/dev/devicelab/lib/tasks/web_benchmarks.dart +++ b/dev/devicelab/lib/tasks/web_benchmarks.dart @@ -39,7 +39,7 @@ Future runWebBenchmark(WebBenchmarkOptions benchmarkOptions) async { '--omit-type-checks', ], '--dart-define=FLUTTER_WEB_ENABLE_PROFILING=true', - '--web-renderer=${benchmarkOptions.webRenderer}', + if (!benchmarkOptions.useWasm) '--web-renderer=${benchmarkOptions.webRenderer}', '--profile', '--no-web-resources-cdn', '-t', @@ -125,7 +125,7 @@ Future runWebBenchmark(WebBenchmarkOptions benchmarkOptions) async { return Response.internalServerError(body: '$error'); } }).add(createBuildDirectoryHandler( - path.join(macrobenchmarksDirectory, 'build', benchmarkOptions.useWasm ? 'web_wasm' : 'web'), + path.join(macrobenchmarksDirectory, 'build', 'web'), )); server = await io.HttpServer.bind('localhost', benchmarkServerPort); diff --git a/dev/integration_tests/web_e2e_tests/web/index.html b/dev/integration_tests/web_e2e_tests/web/index.html index 9dcd43f6d12..70d8bac2919 100644 --- a/dev/integration_tests/web_e2e_tests/web/index.html +++ b/dev/integration_tests/web_e2e_tests/web/index.html @@ -5,14 +5,17 @@ found in the LICENSE file. --> Web Integration Tests - + - + diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index d54abcfdef7..feca4d7931f 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -12,7 +12,6 @@ import 'base/os.dart'; import 'base/utils.dart'; import 'convert.dart'; import 'globals.dart' as globals; -import 'web/compile.dart'; /// Whether icon font subsetting is enabled by default. const bool kIconTreeShakerEnabledDefault = true; @@ -36,7 +35,6 @@ class BuildInfo { List? dartDefines, this.bundleSkSLPath, List? dartExperiments, - this.webRenderer = WebRendererMode.auto, required this.treeShakeIcons, this.performanceMeasurementFile, this.packagesPath = '.dart_tool/package_config.json', // TODO(zanderso): make this required and remove the default. @@ -131,9 +129,6 @@ class BuildInfo { /// A list of Dart experiments. final List dartExperiments; - /// When compiling to web, which web renderer mode we are using (html, canvaskit, auto) - final WebRendererMode webRenderer; - /// The name of a file where flutter assemble will output performance /// information in a JSON format. /// @@ -803,10 +798,6 @@ HostPlatform getCurrentHostPlatform() { return HostPlatform.linux_x64; } -FileSystemEntity getWebPlatformBinariesDirectory(Artifacts artifacts, WebRendererMode webRenderer) { - return artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder); -} - /// Returns the top-level build output directory. String getBuildDirectory([Config? config, FileSystem? fileSystem]) { // TODO(johnmccutchan): Stop calling this function as part of setting @@ -849,8 +840,8 @@ String getMacOSBuildDirectory() { } /// Returns the web build output directory. -String getWebBuildDirectory([bool isWasm = false]) { - return globals.fs.path.join(getBuildDirectory(), isWasm ? 'web_wasm' : 'web'); +String getWebBuildDirectory() { + return globals.fs.path.join(getBuildDirectory(), 'web'); } /// Returns the Linux build output directory. diff --git a/packages/flutter_tools/lib/src/build_system/build_system.dart b/packages/flutter_tools/lib/src/build_system/build_system.dart index 2438202015e..849f4f3ce06 100644 --- a/packages/flutter_tools/lib/src/build_system/build_system.dart +++ b/packages/flutter_tools/lib/src/build_system/build_system.dart @@ -136,6 +136,15 @@ abstract class Target { /// A list of zero or more depfiles, located directly under {BUILD_DIR}. List get depfiles => const []; + /// A string that differentiates different build variants from each other + /// with regards to build flags or settings on the target. This string should + /// represent each build variant as a different unique value. If this value + /// changes between builds, the target will be invalidated and rebuilt. + /// + /// By default, this returns null, which indicates there is only one build + /// variant, and the target won't invalidate or rebuild due to this property. + String? get buildKey => null; + /// Whether this target can be executed with the given [environment]. /// /// Returning `true` will cause [build] to be skipped. This is equivalent @@ -156,6 +165,7 @@ abstract class Target { [ for (final Target target in dependencies) target._toNode(environment), ], + buildKey, environment, inputsFiles.containsNewDepfile, ); @@ -181,9 +191,11 @@ abstract class Target { for (final File output in outputs) { outputPaths.add(output.path); } + final String? key = buildKey; final Map result = { 'inputs': inputPaths, 'outputs': outputPaths, + if (key != null) 'buildKey': key, }; if (!stamp.existsSync()) { stamp.createSync(); @@ -218,6 +230,7 @@ abstract class Target { /// This requires constants from the [Environment] to resolve the paths of /// inputs and the output stamp. Map toJson(Environment environment) { + final String? key = buildKey; return { 'name': name, 'dependencies': [ @@ -229,6 +242,7 @@ abstract class Target { 'outputs': [ for (final File file in resolveOutputs(environment).sources) file.path, ], + if (key != null) 'buildKey': key, 'stamp': _findStampFile(environment).absolute.path, }; } @@ -980,49 +994,85 @@ void verifyOutputDirectories(List outputs, Environment environment, Target /// A node in the build graph. class Node { - Node( + factory Node( + Target target, + List inputs, + List outputs, + List dependencies, + String? buildKey, + Environment environment, + bool missingDepfile, + ) { + final File stamp = target._findStampFile(environment); + Map? stampValues; + + // If the stamp file doesn't exist, we haven't run this step before and + // all inputs were added. + if (stamp.existsSync()) { + final String content = stamp.readAsStringSync(); + if (content.isEmpty) { + stamp.deleteSync(); + } else { + try { + stampValues = castStringKeyedMap(json.decode(content)); + } on FormatException { + // The json is malformed in some way. + } + } + } + if (stampValues != null) { + final String? previousBuildKey = stampValues['buildKey'] as String?; + final Object? stampInputs = stampValues['inputs']; + final Object? stampOutputs = stampValues['outputs']; + if (stampInputs is List && stampOutputs is List) { + final Set previousInputs = stampInputs.whereType().toSet(); + final Set previousOutputs = stampOutputs.whereType().toSet(); + return Node.withStamp( + target, + inputs, + previousInputs, + outputs, + previousOutputs, + dependencies, + buildKey, + previousBuildKey, + missingDepfile, + ); + } + } + return Node.withNoStamp( + target, + inputs, + outputs, + dependencies, + buildKey, + missingDepfile, + ); + } + + Node.withNoStamp( this.target, this.inputs, this.outputs, this.dependencies, - Environment environment, + this.buildKey, this.missingDepfile, - ) { - final File stamp = target._findStampFile(environment); + ) : previousInputs = {}, + previousOutputs = {}, + previousBuildKey = null, + _dirty = true; - // If the stamp file doesn't exist, we haven't run this step before and - // all inputs were added. - if (!stamp.existsSync()) { - // No stamp file, not safe to skip. - _dirty = true; - return; - } - final String content = stamp.readAsStringSync(); - // Something went wrong writing the stamp file. - if (content.isEmpty) { - stamp.deleteSync(); - // Malformed stamp file, not safe to skip. - _dirty = true; - return; - } - Map? values; - try { - values = castStringKeyedMap(json.decode(content)); - } on FormatException { - // The json is malformed in some way. - _dirty = true; - return; - } - final Object? inputs = values?['inputs']; - final Object? outputs = values?['outputs']; - if (inputs is List && outputs is List) { - inputs.cast().whereType().forEach(previousInputs.add); - outputs.cast().whereType().forEach(previousOutputs.add); - } else { - // The json is malformed in some way. - _dirty = true; - } - } + Node.withStamp( + this.target, + this.inputs, + this.previousInputs, + this.outputs, + this.previousOutputs, + this.dependencies, + this.buildKey, + this.previousBuildKey, + this.missingDepfile, + ) : _dirty = false; /// The resolved input files. /// @@ -1034,6 +1084,11 @@ class Node { /// These files may not yet exist if the target hasn't run yet. final List outputs; + /// The current build key of the target + /// + /// See `buildKey` in the `Target` class for more information. + final String? buildKey; + /// Whether this node is missing a depfile. /// /// This requires an additional pass of source resolution after the target @@ -1047,10 +1102,15 @@ class Node { final List dependencies; /// Output file paths from the previous invocation of this build node. - final Set previousOutputs = {}; + final Set previousOutputs; /// Input file paths from the previous invocation of this build node. - final Set previousInputs = {}; + final Set previousInputs; + + /// The buildKey from the previous invocation of this build node. + /// + /// See `buildKey` in the `Target` class for more information. + final String? previousBuildKey; /// One or more reasons why a task was invalidated. /// @@ -1074,6 +1134,10 @@ class Node { FileSystem fileSystem, Logger logger, ) { + if (buildKey != previousBuildKey) { + _invalidate(InvalidatedReasonKind.buildKeyChanged); + _dirty = true; + } final Set currentOutputPaths = { for (final File file in outputs) file.path, }; @@ -1173,7 +1237,8 @@ class InvalidatedReason { InvalidatedReasonKind.inputChanged => 'The following inputs have updated contents: ${data.join(',')}', InvalidatedReasonKind.outputChanged => 'The following outputs have updated contents: ${data.join(',')}', InvalidatedReasonKind.outputMissing => 'The following outputs were missing: ${data.join(',')}', - InvalidatedReasonKind.outputSetChanged => 'The following outputs were removed from the output set: ${data.join(',')}' + InvalidatedReasonKind.outputSetChanged => 'The following outputs were removed from the output set: ${data.join(',')}', + InvalidatedReasonKind.buildKeyChanged => 'The target build key changed.', }; } } @@ -1195,4 +1260,7 @@ enum InvalidatedReasonKind { /// The set of expected output files changed. outputSetChanged, + + /// The build key changed + buildKeyChanged, } diff --git a/packages/flutter_tools/lib/src/build_system/build_targets.dart b/packages/flutter_tools/lib/src/build_system/build_targets.dart index 755e1482c63..8a71c0bc233 100644 --- a/packages/flutter_tools/lib/src/build_system/build_targets.dart +++ b/packages/flutter_tools/lib/src/build_system/build_targets.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import '../base/file_system.dart'; -import '../web/compile.dart'; +import '../web/compiler_config.dart'; import './build_system.dart'; /// Commonly used build [Target]s. @@ -14,11 +14,7 @@ abstract class BuildTargets { Target get releaseCopyFlutterBundle; Target get generateLocalizationsTarget; Target get dartPluginRegistrantTarget; - Target webServiceWorker( - FileSystem fileSystem, { - required WebRendererMode webRenderer, - required bool isWasm - }); + Target webServiceWorker(FileSystem fileSystem, List compileConfigs); } /// BuildTargets that return NoOpTarget for every action. @@ -38,11 +34,7 @@ class NoOpBuildTargets extends BuildTargets { Target get dartPluginRegistrantTarget => const _NoOpTarget(); @override - Target webServiceWorker( - FileSystem fileSystem, { - required WebRendererMode webRenderer, - required bool isWasm, - }) => const _NoOpTarget(); + Target webServiceWorker(FileSystem fileSystem, List compileConfigs) => const _NoOpTarget(); } /// A [Target] that does nothing. diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart index 4ffe6150b12..b55b4285e3e 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -22,7 +22,6 @@ import '../../project.dart'; import '../../web/compile.dart'; import '../../web/file_generators/flutter_service_worker_js.dart'; import '../../web/file_generators/main_dart.dart' as main_dart; -import '../../web/file_generators/wasm_bootstrap.dart' as wasm_bootstrap; import '../build_system.dart'; import '../depfile.dart'; import '../exceptions.dart'; @@ -95,11 +94,15 @@ class WebEntrypointTarget extends Target { /// Compiles a web entry point with dart2js. abstract class Dart2WebTarget extends Target { - const Dart2WebTarget(this.webRenderer); + const Dart2WebTarget(); - final WebRendererMode webRenderer; Source get compilerSnapshot; + WebCompilerConfig get compilerConfig; + + Map get buildConfig; + List get buildFiles; + @override List get dependencies => const [ WebEntrypointTarget(), @@ -116,11 +119,19 @@ abstract class Dart2WebTarget extends Target { ]; @override - List get outputs => const []; + List get outputs => buildFiles.map( + (String file) => Source.pattern('{BUILD_DIR}/$file') + ).toList(); + + @override + String get buildKey => compilerConfig.buildKey; } class Dart2JSTarget extends Dart2WebTarget { - Dart2JSTarget(super.webRenderer); + Dart2JSTarget(this.compilerConfig); + + @override + final JsCompilerConfig compilerConfig; @override String get name => 'dart2js'; @@ -140,9 +151,11 @@ class Dart2JSTarget extends Dart2WebTarget { throw MissingDefineException(kBuildMode, name); } final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment); - final JsCompilerConfig compilerConfig = JsCompilerConfig.fromBuildSystemEnvironment(environment.defines); final Artifacts artifacts = environment.artifacts; - final String platformBinariesPath = getWebPlatformBinariesDirectory(artifacts, webRenderer).path; + final String platformBinariesPath = artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder).path; + final List dartDefines = compilerConfig.renderer.updateDartDefines( + decodeDartDefines(environment.defines, kDartDefines), + ); final List sharedCommandOptions = [ artifacts.getArtifactPath(Artifact.engineDartBinary, platform: TargetPlatform.web_javascript), '--disable-dart-dev', @@ -154,7 +167,7 @@ class Dart2JSTarget extends Dart2WebTarget { '-Ddart.vm.profile=true' else '-Ddart.vm.product=true', - for (final String dartDefine in decodeDartDefines(environment.defines, kDartDefines)) + for (final String dartDefine in dartDefines) '-D$dartDefine', ]; @@ -208,10 +221,25 @@ class Dart2JSTarget extends Dart2WebTarget { environment.buildDir.childFile('dart2js.d'), ); } + + @override + Map get buildConfig => { + 'compileTarget': 'dart2js', + 'renderer': compilerConfig.renderer.name, + 'mainJsPath': 'main.dart.js', + }; + + @override + List get buildFiles => [ + 'main.dart.js', + ]; } class Dart2WasmTarget extends Dart2WebTarget { - Dart2WasmTarget(super.webRenderer); + Dart2WasmTarget(this.compilerConfig); + + @override + final WasmCompilerConfig compilerConfig; @override Future build(Environment environment) async { @@ -219,7 +247,6 @@ class Dart2WasmTarget extends Dart2WebTarget { if (buildModeEnvironment == null) { throw MissingDefineException(kBuildMode, name); } - final WasmCompilerConfig compilerConfig = WasmCompilerConfig.fromBuildSystemEnvironment(environment.defines); final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment); final Artifacts artifacts = environment.artifacts; final File outputWasmFile = environment.buildDir.childFile( @@ -227,8 +254,11 @@ class Dart2WasmTarget extends Dart2WebTarget { ); final File depFile = environment.buildDir.childFile('dart2wasm.d'); final String dartSdkPath = artifacts.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript); - final String platformBinariesPath = getWebPlatformBinariesDirectory(artifacts, webRenderer).path; + final String platformBinariesPath = artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder).path; final String platformFilePath = environment.fileSystem.path.join(platformBinariesPath, 'dart2wasm_platform.dill'); + final List dartDefines = compilerConfig.renderer.updateDartDefines( + decodeDartDefines(environment.defines, kDartDefines), + ); final List compilationArgs = [ artifacts.getArtifactPath(Artifact.engineDartAotRuntime, platform: TargetPlatform.web_javascript), @@ -242,10 +272,10 @@ class Dart2WasmTarget extends Dart2WebTarget { else '-Ddart.vm.product=true', ...decodeCommaSeparated(environment.defines, kExtraFrontEndOptions), - for (final String dartDefine in decodeDartDefines(environment.defines, kDartDefines)) + for (final String dartDefine in dartDefines) '-D$dartDefine', ...compilerConfig.toCommandOptions(), - if (webRenderer == WebRendererMode.skwasm) + if (compilerConfig.renderer == WebRendererMode.skwasm) ...[ '--import-shared-memory', '--shared-memory-max-pages=32768', @@ -310,74 +340,75 @@ class Dart2WasmTarget extends Dart2WebTarget { ]; @override - List get outputs => const [ - Source.pattern('{OUTPUT_DIR}/main.dart.wasm'), - Source.pattern('{OUTPUT_DIR}/main.dart.mjs'), + Map get buildConfig => { + 'compileTarget': 'dart2wasm', + 'renderer': compilerConfig.renderer.name, + 'mainWasmPath': 'main.dart.wasm', + 'jsSupportRuntimePath': 'main.dart.mjs', + }; + + @override + List get buildFiles => [ + 'main.dart.wasm', + 'main.dart.mjs', ]; } /// Unpacks the dart2js or dart2wasm compilation and resources to a given /// output directory. class WebReleaseBundle extends Target { - const WebReleaseBundle(this.webRenderer, {required this.isWasm}); + WebReleaseBundle(List configs) : this._withTargets( + configs.map((WebCompilerConfig config) => + switch (config) { + WasmCompilerConfig() => Dart2WasmTarget(config), + JsCompilerConfig() => Dart2JSTarget(config), + } + ).toList() + ); - final WebRendererMode webRenderer; - final bool isWasm; + const WebReleaseBundle._withTargets(this.compileTargets); - String get outputFileNameNoSuffix => 'main.dart'; - String get outputFileName => '$outputFileNameNoSuffix${isWasm ? '.wasm' : '.js'}'; - String get wasmJSRuntimeFileName => '$outputFileNameNoSuffix.mjs'; + final List compileTargets; + + List get buildFiles => compileTargets.fold( + const Iterable.empty(), + (Iterable current, Dart2WebTarget target) => current.followedBy(target.buildFiles) + ).toList(); @override String get name => 'web_release_bundle'; @override - List get dependencies => [ - if (isWasm) Dart2WasmTarget(webRenderer) else Dart2JSTarget(webRenderer), - ]; + List get dependencies => compileTargets; @override List get inputs => [ - Source.pattern('{BUILD_DIR}/$outputFileName'), const Source.pattern('{PROJECT_DIR}/pubspec.yaml'), - if (isWasm) Source.pattern('{BUILD_DIR}/$wasmJSRuntimeFileName'), + ...buildFiles.map((String file) => Source.pattern('{BUILD_DIR}/$file')) ]; @override List get outputs => [ - Source.pattern('{OUTPUT_DIR}/$outputFileName'), - if (isWasm) Source.pattern('{OUTPUT_DIR}/$wasmJSRuntimeFileName'), + ...buildFiles.map((String file) => Source.pattern('{OUTPUT_DIR}/$file')) ]; @override List get depfiles => const [ - 'dart2js.d', 'flutter_assets.d', 'web_resources.d', ]; - bool shouldCopy(String name) => - // Do not copy the deps file. - (name.contains(outputFileName) && !name.endsWith('.deps')) || - (isWasm && name == wasmJSRuntimeFileName); - @override Future build(Environment environment) async { for (final File outputFile in environment.buildDir.listSync(recursive: true).whereType()) { final String basename = environment.fileSystem.path.basename(outputFile.path); - if (shouldCopy(basename)) { + if (buildFiles.contains(basename)) { outputFile.copySync( environment.outputDir.childFile(environment.fileSystem.path.basename(outputFile.path)).path ); } } - if (isWasm) { - // TODO(jacksongardner): Enable icon tree shaking once dart2wasm can do a two-phase compile. - // https://github.com/flutter/flutter/issues/117248 - environment.defines[kIconTreeShakerFlag] = 'false'; - } - createVersionFile(environment, environment.defines); final Directory outputDirectory = environment.outputDir.childDirectory('assets'); outputDirectory.createSync(recursive: true); @@ -413,10 +444,19 @@ class WebReleaseBundle extends Target { // because it would need to be the hash for the entire bundle and not just the resource // in question. if (environment.fileSystem.path.basename(inputFile.path) == 'index.html') { + final List> buildDescriptions = compileTargets.map( + (Dart2WebTarget target) => target.buildConfig + ).toList(); + final Map buildConfig = { + 'engineRevision': globals.flutterVersion.engineRevision, + 'builds': buildDescriptions, + }; + final String buildConfigString = '_flutter.buildConfig = ${jsonEncode(buildConfig)};'; final IndexHtml indexHtml = IndexHtml(inputFile.readAsStringSync()); indexHtml.applySubstitutions( baseHref: environment.defines[kBaseHref] ?? '/', serviceWorkerVersion: Random().nextInt(4294967296).toString(), + buildConfig: buildConfigString, ); outputFile.writeAsStringSync(indexHtml.content); continue; @@ -456,11 +496,9 @@ class WebReleaseBundle extends Target { /// These assets can be cached until a new version of the flutter web sdk is /// downloaded. class WebBuiltInAssets extends Target { - const WebBuiltInAssets(this.fileSystem, this.webRenderer, {required this.isWasm}); + const WebBuiltInAssets(this.fileSystem); final FileSystem fileSystem; - final WebRendererMode webRenderer; - final bool isWasm; @override String get name => 'web_static_assets'; @@ -491,7 +529,6 @@ class WebBuiltInAssets extends Target { @override List get outputs => [ - if (isWasm) const Source.pattern('{BUILD_DIR}/main.dart.js'), const Source.pattern('{BUILD_DIR}/flutter.js'), for (final File file in _canvasKitFiles) Source.pattern('{BUILD_DIR}/canvaskit/${_filePathRelativeToCanvasKitDirectory(file)}'), @@ -505,13 +542,6 @@ class WebBuiltInAssets extends Target { file.copySync(targetPath); } - if (isWasm) { - final File bootstrapFile = environment.outputDir.childFile('main.dart.js'); - bootstrapFile.writeAsStringSync( - wasm_bootstrap.generateWasmBootstrapFile(webRenderer == WebRendererMode.skwasm) - ); - } - // Write the flutter.js file final String flutterJsOut = fileSystem.path.join(environment.outputDir.path, 'flutter.js'); final File flutterJsFile = fileSystem.file(fileSystem.path.join( @@ -524,20 +554,18 @@ class WebBuiltInAssets extends Target { /// Generate a service worker for a web target. class WebServiceWorker extends Target { - const WebServiceWorker(this.fileSystem, this.webRenderer, {required this.isWasm}); + const WebServiceWorker(this.fileSystem, this.compileConfigs); final FileSystem fileSystem; - final WebRendererMode webRenderer; - final bool isWasm; + final List compileConfigs; @override String get name => 'web_service_worker'; @override List get dependencies => [ - if (isWasm) Dart2WasmTarget(webRenderer) else Dart2JSTarget(webRenderer), - WebReleaseBundle(webRenderer, isWasm: isWasm), - WebBuiltInAssets(fileSystem, webRenderer, isWasm: isWasm), + WebReleaseBundle(compileConfigs), + WebBuiltInAssets(fileSystem), ]; @override @@ -592,6 +620,10 @@ class WebServiceWorker extends Target { urlToHash, [ 'main.dart.js', + if (compileConfigs.any((WebCompilerConfig config) => config is WasmCompilerConfig)) ...[ + 'main.dart.wasm', + 'main.dart.mjs', + ], 'index.html', if (urlToHash.containsKey('assets/AssetManifest.bin.json')) 'assets/AssetManifest.bin.json', diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart index 52e2410ffac..7be2209167c 100644 --- a/packages/flutter_tools/lib/src/commands/build_web.dart +++ b/packages/flutter_tools/lib/src/commands/build_web.dart @@ -138,24 +138,50 @@ class BuildWebCommand extends BuildSubCommand { throwToolExit('"build web" is not currently supported. To enable, run "flutter config --enable-web".'); } - final WebCompilerConfig compilerConfig; + final List compilerConfigs; if (boolArg('wasm')) { if (!featureFlags.isFlutterWebWasmEnabled) { throwToolExit('Compiling to WebAssembly (wasm) is only available on the master channel.'); } - compilerConfig = WasmCompilerConfig( - omitTypeChecks: boolArg('omit-type-checks'), - wasmOpt: WasmOptLevel.values.byName(stringArg('wasm-opt')!), + if (stringArg(FlutterOptions.kWebRendererFlag) != argParser.defaultFor(FlutterOptions.kWebRendererFlag)) { + throwToolExit('"--${FlutterOptions.kWebRendererFlag}" cannot be combined with "--${FlutterOptions.kWebWasmFlag}"'); + } + globals.logger.printBox( + title: 'Experimental feature', + ''' + WebAssembly compilation is experimental. + $kWasmMoreInfo''', ); + + compilerConfigs = [ + WasmCompilerConfig( + omitTypeChecks: boolArg('omit-type-checks'), + wasmOpt: WasmOptLevel.values.byName(stringArg('wasm-opt')!), + renderer: WebRendererMode.skwasm, + ), + JsCompilerConfig( + csp: boolArg('csp'), + optimizationLevel: stringArg('dart2js-optimization') ?? JsCompilerConfig.kDart2jsDefaultOptimizationLevel, + dumpInfo: boolArg('dump-info'), + nativeNullAssertions: boolArg('native-null-assertions'), + noFrequencyBasedMinification: boolArg('no-frequency-based-minification'), + sourceMaps: boolArg('source-maps'), + renderer: WebRendererMode.canvaskit, + )]; } else { - compilerConfig = JsCompilerConfig( + WebRendererMode webRenderer = WebRendererMode.auto; + if (argParser.options.containsKey(FlutterOptions.kWebRendererFlag)) { + webRenderer = WebRendererMode.values.byName(stringArg(FlutterOptions.kWebRendererFlag)!); + } + compilerConfigs = [JsCompilerConfig( csp: boolArg('csp'), optimizationLevel: stringArg('dart2js-optimization') ?? JsCompilerConfig.kDart2jsDefaultOptimizationLevel, dumpInfo: boolArg('dump-info'), nativeNullAssertions: boolArg('native-null-assertions'), noFrequencyBasedMinification: boolArg('no-frequency-based-minification'), sourceMaps: boolArg('source-maps'), - ); + renderer: webRenderer, + )]; } final FlutterProject flutterProject = FlutterProject.current(); @@ -205,7 +231,7 @@ class BuildWebCommand extends BuildSubCommand { target, buildInfo, ServiceWorkerStrategy.fromCliName(stringArg('pwa-strategy')), - compilerConfig: compilerConfig, + compilerConfigs: compilerConfigs, baseHref: baseHref, outputDirectoryPath: outputDirectoryPath, ); diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index 993a685abd6..ceccfbd939d 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -29,6 +29,7 @@ import '../runner/flutter_command.dart'; import '../runner/flutter_command_runner.dart'; import '../tracing.dart'; import '../vmservice.dart'; +import '../web/compile.dart'; import '../web/web_runner.dart'; import 'daemon.dart'; @@ -241,6 +242,10 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment final Map webHeaders = featureFlags.isWebEnabled ? extractWebHeaders() : const {}; + final String? webRendererString = stringArg('web-renderer'); + final WebRendererMode webRenderer = (webRendererString != null) + ? WebRendererMode.values.byName(webRendererString) + : WebRendererMode.auto; if (buildInfo.mode.isRelease) { return DebuggingOptions.disabled( @@ -258,6 +263,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment webBrowserDebugPort: webBrowserDebugPort, webBrowserFlags: webBrowserFlags, webHeaders: webHeaders, + webRenderer: webRenderer, enableImpeller: enableImpeller, enableVulkanValidation: enableVulkanValidation, uninstallFirst: uninstallFirst, @@ -307,6 +313,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment webEnableExpressionEvaluation: featureFlags.isWebEnabled && boolArg('web-enable-expression-evaluation'), webLaunchUrl: featureFlags.isWebEnabled ? stringArg('web-launch-url') : null, webHeaders: webHeaders, + webRenderer: webRenderer, vmserviceOutFile: stringArg('vmservice-out-file'), fastStart: argParser.options.containsKey('fast-start') && boolArg('fast-start') diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index 9601fe28ebb..01e0a447ef8 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -22,6 +22,7 @@ import '../test/runner.dart'; import '../test/test_time_recorder.dart'; import '../test/test_wrapper.dart'; import '../test/watcher.dart'; +import '../web/compile.dart'; /// The name of the directory where Integration Tests are placed. /// @@ -357,6 +358,10 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { ); } + final String? webRendererString = stringArg('web-renderer'); + final WebRendererMode webRenderer = (webRendererString != null) + ? WebRendererMode.values.byName(webRendererString) + : WebRendererMode.auto; final DebuggingOptions debuggingOptions = DebuggingOptions.enabled( buildInfo, startPaused: startPaused, @@ -369,6 +374,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { usingCISystem: usingCISystem, enableImpeller: ImpellerStatus.fromBool(argResults!['enable-impeller'] as bool?), debugLogsDirectoryPath: debugLogsDirectoryPath, + webRenderer: webRenderer, ); String? testAssetDirectory; diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 69938a9b784..1f0cc0b2414 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -18,6 +18,7 @@ import 'devfs.dart'; import 'device_port_forwarder.dart'; import 'project.dart'; import 'vmservice.dart'; +import 'web/compile.dart'; DeviceManager? get deviceManager => context.get(); @@ -952,6 +953,7 @@ class DebuggingOptions { this.webEnableExpressionEvaluation = false, this.webHeaders = const {}, this.webLaunchUrl, + this.webRenderer = WebRendererMode.auto, this.vmserviceOutFile, this.fastStart = false, this.nullAssertions = false, @@ -981,6 +983,7 @@ class DebuggingOptions { this.webBrowserFlags = const [], this.webLaunchUrl, this.webHeaders = const {}, + this.webRenderer = WebRendererMode.auto, this.cacheSkSL = false, this.traceAllowlist, this.enableImpeller = ImpellerStatus.platformDefault, @@ -1060,6 +1063,7 @@ class DebuggingOptions { required this.webEnableExpressionEvaluation, required this.webHeaders, required this.webLaunchUrl, + required this.webRenderer, required this.vmserviceOutFile, required this.fastStart, required this.nullAssertions, @@ -1144,6 +1148,9 @@ class DebuggingOptions { /// Allow developers to add custom headers to web server final Map webHeaders; + /// Which web renderer to use for the debugging session + final WebRendererMode webRenderer; + /// A file where the VM Service URL should be written after the application is started. final String? vmserviceOutFile; final bool fastStart; @@ -1252,6 +1259,7 @@ class DebuggingOptions { 'webEnableExpressionEvaluation': webEnableExpressionEvaluation, 'webLaunchUrl': webLaunchUrl, 'webHeaders': webHeaders, + 'webRenderer': webRenderer.name, 'vmserviceOutFile': vmserviceOutFile, 'fastStart': fastStart, 'nullAssertions': nullAssertions, @@ -1307,6 +1315,7 @@ class DebuggingOptions { webEnableExpressionEvaluation: json['webEnableExpressionEvaluation']! as bool, webHeaders: (json['webHeaders']! as Map).cast(), webLaunchUrl: json['webLaunchUrl'] as String?, + webRenderer: WebRendererMode.values.byName(json['webRenderer']! as String), vmserviceOutFile: json['vmserviceOutFile'] as String?, fastStart: json['fastStart']! as bool, nullAssertions: json['nullAssertions']! as bool, diff --git a/packages/flutter_tools/lib/src/drive/web_driver_service.dart b/packages/flutter_tools/lib/src/drive/web_driver_service.dart index 8f6241a5de5..fa10cfb1cf1 100644 --- a/packages/flutter_tools/lib/src/drive/web_driver_service.dart +++ b/packages/flutter_tools/lib/src/drive/web_driver_service.dart @@ -78,12 +78,14 @@ class WebDriverService extends DriverService { buildInfo, port: debuggingOptions.port, hostname: debuggingOptions.hostname, + webRenderer: debuggingOptions.webRenderer, ) : DebuggingOptions.enabled( buildInfo, port: debuggingOptions.port, hostname: debuggingOptions.hostname, disablePortPublication: debuggingOptions.disablePortPublication, + webRenderer: debuggingOptions.webRenderer, ), stayResident: true, flutterProject: FlutterProject.current(), diff --git a/packages/flutter_tools/lib/src/html_utils.dart b/packages/flutter_tools/lib/src/html_utils.dart index ad394aaa909..2477a90b2d4 100644 --- a/packages/flutter_tools/lib/src/html_utils.dart +++ b/packages/flutter_tools/lib/src/html_utils.dart @@ -62,6 +62,7 @@ class IndexHtml { void applySubstitutions({ required String baseHref, required String? serviceWorkerVersion, + String? buildConfig, }) { if (_content.contains(kBaseHrefPlaceholder)) { _content = _content.replaceAll(kBaseHrefPlaceholder, baseHref); @@ -81,6 +82,12 @@ class IndexHtml { "navigator.serviceWorker.register('flutter_service_worker.js?v=$serviceWorkerVersion')", ); } + if (buildConfig != null) { + _content = _content.replaceFirst( + '{{flutter_build_config}}', + buildConfig, + ); + } } } diff --git a/packages/flutter_tools/lib/src/isolated/build_targets.dart b/packages/flutter_tools/lib/src/isolated/build_targets.dart index 5a7a70fabcc..e721495a289 100644 --- a/packages/flutter_tools/lib/src/isolated/build_targets.dart +++ b/packages/flutter_tools/lib/src/isolated/build_targets.dart @@ -9,7 +9,7 @@ import '../build_system/targets/common.dart'; import '../build_system/targets/dart_plugin_registrant.dart'; import '../build_system/targets/localizations.dart'; import '../build_system/targets/web.dart'; -import '../web/compile.dart'; +import '../web/compiler_config.dart'; class BuildTargetsImpl extends BuildTargets { const BuildTargetsImpl(); @@ -27,9 +27,6 @@ class BuildTargetsImpl extends BuildTargets { Target get dartPluginRegistrantTarget => const DartPluginRegistrantTarget(); @override - Target webServiceWorker( - FileSystem fileSystem, { - required WebRendererMode webRenderer, - required bool isWasm, - }) => WebServiceWorker(fileSystem, webRenderer, isWasm: isWasm); + Target webServiceWorker(FileSystem fileSystem, List compileConfigs) => + WebServiceWorker(fileSystem, compileConfigs); } diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index 2e499025e1e..b6ac161c989 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart @@ -117,8 +117,9 @@ class WebAssetServer implements AssetReader { this.internetAddress, this._modules, this._digests, - this._nullSafetyMode, - ) : basePath = _getIndexHtml().getBaseHref(); + this._nullSafetyMode, { + required this.webRenderer, + }) : basePath = _getIndexHtml().getBaseHref(); // Fallback to "application/octet-stream" on null which // makes no claims as to the structure of the data. @@ -177,6 +178,7 @@ class WebAssetServer implements AssetReader { ExpressionCompiler? expressionCompiler, Map extraHeaders, NullSafetyMode nullSafetyMode, { + required WebRendererMode webRenderer, bool testMode = false, DwdsLauncher dwdsLauncher = Dwds.start, }) async { @@ -225,6 +227,7 @@ class WebAssetServer implements AssetReader { modules, digests, nullSafetyMode, + webRenderer: webRenderer, ); if (testMode) { return server; @@ -504,16 +507,29 @@ class WebAssetServer implements AssetReader { } /// Determines what rendering backed to use. - WebRendererMode webRenderer = WebRendererMode.html; + final WebRendererMode webRenderer; shelf.Response _serveIndex() { final IndexHtml indexHtml = _getIndexHtml(); + final Map buildConfig = { + 'engineRevision': globals.flutterVersion.engineRevision, + 'builds': [ + { + 'compileTarget': 'dartdevc', + 'renderer': webRenderer.name, + 'mainJsPath': 'main.dart.js', + }, + ], + }; + final String buildConfigString = '_flutter.buildConfig = ${jsonEncode(buildConfig)};'; + indexHtml.applySubstitutions( // Currently, we don't support --base-href for the "run" command. baseHref: '/', serviceWorkerVersion: null, + buildConfig: buildConfigString, ); final Map headers = { @@ -663,6 +679,7 @@ class WebDevFS implements DevFS { required this.nullAssertions, required this.nativeNullAssertions, required this.nullSafetyMode, + required this.webRenderer, this.testMode = false, }) : _port = port; @@ -686,6 +703,7 @@ class WebDevFS implements DevFS { final NullSafetyMode nullSafetyMode; final String? tlsCertPath; final String? tlsCertKeyPath; + final WebRendererMode webRenderer; late WebAssetServer webAssetServer; @@ -785,15 +803,11 @@ class WebDevFS implements DevFS { expressionCompiler, extraHeaders, nullSafetyMode, + webRenderer: webRenderer, testMode: testMode, ); final int selectedPort = webAssetServer.selectedPort; - if (buildInfo.dartDefines.contains('FLUTTER_WEB_AUTO_DETECT=true')) { - webAssetServer.webRenderer = WebRendererMode.auto; - } else if (buildInfo.dartDefines.contains('FLUTTER_WEB_USE_SKIA=true')) { - webAssetServer.webRenderer = WebRendererMode.canvaskit; - } String url = '$hostname:$selectedPort'; if (hostname == 'any') { url ='localhost:$selectedPort'; diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index 45a7b184117..7a62b7815e0 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -309,6 +309,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). nullAssertions: debuggingOptions.nullAssertions, nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode, nativeNullAssertions: debuggingOptions.nativeNullAssertions, + webRenderer: debuggingOptions.webRenderer, ); Uri url = await device!.devFS!.create(); if (debuggingOptions.tlsCertKeyPath != null && debuggingOptions.tlsCertPath != null) { @@ -339,7 +340,12 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). target, debuggingOptions.buildInfo, ServiceWorkerStrategy.none, - compilerConfig: JsCompilerConfig.run(nativeNullAssertions: debuggingOptions.nativeNullAssertions) + compilerConfigs: [ + JsCompilerConfig.run( + nativeNullAssertions: debuggingOptions.nativeNullAssertions, + renderer: debuggingOptions.webRenderer, + ) + ] ); } await device!.device!.startApp( @@ -418,7 +424,12 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). target, debuggingOptions.buildInfo, ServiceWorkerStrategy.none, - compilerConfig: JsCompilerConfig.run(nativeNullAssertions: debuggingOptions.nativeNullAssertions), + compilerConfigs: [ + JsCompilerConfig.run( + nativeNullAssertions: debuggingOptions.nativeNullAssertions, + renderer: debuggingOptions.webRenderer, + ) + ], ); } on ToolExit { return OperationResult(1, 'Failed to recompile application.'); diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index c918031f876..a0f6713eb20 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -135,7 +135,7 @@ class FlutterDevice { } final String platformDillPath = globals.fs.path.join( - getWebPlatformBinariesDirectory(globals.artifacts!, buildInfo.webRenderer).path, + globals.artifacts!.getHostArtifact(HostArtifact.webPlatformKernelFolder).path, platformDillName, ); diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index dd9e5c8b549..87f2f0e40de 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -1273,13 +1273,7 @@ abstract class FlutterCommand extends Command { : null; final Map defineConfigJsonMap = extractDartDefineConfigJsonMap(); - List dartDefines = extractDartDefines(defineConfigJsonMap: defineConfigJsonMap); - - WebRendererMode webRenderer = WebRendererMode.auto; - if (argParser.options.containsKey(FlutterOptions.kWebRendererFlag)) { - webRenderer = WebRendererMode.values.byName(stringArg(FlutterOptions.kWebRendererFlag)!); - dartDefines = updateDartDefines(dartDefines, webRenderer); - } + final List dartDefines = extractDartDefines(defineConfigJsonMap: defineConfigJsonMap); if (argParser.options.containsKey(FlutterOptions.kWebResourcesCdnFlag)) { final bool hasLocalWebSdk = argParser.options.containsKey('local-web-sdk') && stringArg('local-web-sdk') != null; @@ -1327,7 +1321,6 @@ abstract class FlutterCommand extends Command { dartDefines: dartDefines, bundleSkSLPath: bundleSkSLPath, dartExperiments: experiments, - webRenderer: webRenderer, performanceMeasurementFile: performanceMeasurementFile, packagesPath: packagesPath ?? globals.fs.path.absolute('.dart_tool', 'package_config.json'), nullSafetyMode: nullSafetyMode, @@ -1566,19 +1559,6 @@ abstract class FlutterCommand extends Command { return jsonEncode(propertyMap); } - /// Updates dart-defines based on [webRenderer]. - @visibleForTesting - static List updateDartDefines(List dartDefines, WebRendererMode webRenderer) { - final Set dartDefinesSet = dartDefines.toSet(); - if (!dartDefines.any((String d) => d.startsWith('FLUTTER_WEB_AUTO_DETECT=')) - && dartDefines.any((String d) => d.startsWith('FLUTTER_WEB_USE_SKIA='))) { - dartDefinesSet.removeWhere((String d) => d.startsWith('FLUTTER_WEB_USE_SKIA=')); - } - dartDefinesSet.addAll(webRenderer.dartDefines); - return dartDefinesSet.toList(); - } - - Map extractWebHeaders() { final Map webHeaders = {}; diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart index 5a2fc29ab4e..32ca9beedd9 100644 --- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart @@ -7,12 +7,12 @@ import 'dart:typed_data'; import 'package:async/async.dart'; import 'package:http_multi_server/http_multi_server.dart'; +import 'package:mime/mime.dart' as mime; import 'package:package_config/package_config.dart'; import 'package:pool/pool.dart'; import 'package:process/process.dart'; import 'package:shelf/shelf.dart' as shelf; import 'package:shelf/shelf_io.dart' as shelf_io; -import 'package:shelf_static/shelf_static.dart'; import 'package:shelf_web_socket/shelf_web_socket.dart'; import 'package:stream_channel/stream_channel.dart'; import 'package:test_core/src/platform.dart'; // ignore: implementation_imports @@ -37,6 +37,34 @@ import 'flutter_web_goldens.dart'; import 'test_compiler.dart'; import 'test_time_recorder.dart'; +shelf.Handler createDirectoryHandler(Directory directory) { + final mime.MimeTypeResolver resolver = mime.MimeTypeResolver(); + final FileSystem fileSystem = directory.fileSystem; + return (shelf.Request request) async { + String uriPath = request.requestedUri.path; + + // Strip any leading slashes + if (uriPath.startsWith('/')) { + uriPath = uriPath.substring(1); + } + final String filePath = fileSystem.path.join( + directory.path, + uriPath, + ); + final File file = fileSystem.file(filePath); + if (!file.existsSync()) { + return shelf.Response.notFound('Not Found'); + } + final String? contentType = resolver.lookup(file.path); + return shelf.Response.ok( + file.openRead(), + headers: { + if (contentType != null) 'Content-Type': contentType + }, + ); + }; +} + class FlutterWebPlatform extends PlatformPlugin { FlutterWebPlatform._(this._server, this._config, this._root, { FlutterProject? flutterProject, @@ -46,31 +74,32 @@ class FlutterWebPlatform extends PlatformPlugin { required this.buildInfo, required this.webMemoryFS, required FileSystem fileSystem, - required PackageConfig flutterToolPackageConfig, + required File testDartJs, + required File testHostDartJs, required ChromiumLauncher chromiumLauncher, required Logger logger, required Artifacts? artifacts, required ProcessManager processManager, + required this.webRenderer, TestTimeRecorder? testTimeRecorder, }) : _fileSystem = fileSystem, - _flutterToolPackageConfig = flutterToolPackageConfig, + _testDartJs = testDartJs, + _testHostDartJs = testHostDartJs, _chromiumLauncher = chromiumLauncher, _logger = logger, _artifacts = artifacts { final shelf.Cascade cascade = shelf.Cascade() .add(_webSocketHandler.handler) - .add(createStaticHandler( - fileSystem.path.join(Cache.flutterRoot!, 'packages', 'flutter_tools'), - serveFilesOutsidePath: true, + .add(createDirectoryHandler( + fileSystem.directory(fileSystem.path.join(Cache.flutterRoot!, 'packages', 'flutter_tools')), )) .add(_handleStaticArtifact) .add(_localCanvasKitHandler) .add(_goldenFileHandler) .add(_wrapperHandler) .add(_handleTestRequest) - .add(createStaticHandler( - fileSystem.path.join(fileSystem.currentDirectory.path, 'test'), - serveFilesOutsidePath: true, + .add(createDirectoryHandler( + fileSystem.directory(fileSystem.path.join(fileSystem.currentDirectory.path, 'test')) )) .add(_packageFilesHandler); _server.mount(cascade.handler); @@ -80,14 +109,15 @@ class FlutterWebPlatform extends PlatformPlugin { fileSystem: _fileSystem, logger: _logger, processManager: processManager, - webRenderer: _rendererMode, + webRenderer: webRenderer, ); } final WebMemoryFS webMemoryFS; final BuildInfo buildInfo; final FileSystem _fileSystem; - final PackageConfig _flutterToolPackageConfig; + final File _testDartJs; + final File _testHostDartJs; final ChromiumLauncher _chromiumLauncher; final Logger _logger; final Artifacts? _artifacts; @@ -96,6 +126,7 @@ class FlutterWebPlatform extends PlatformPlugin { final OneOffHandler _webSocketHandler = OneOffHandler(); final AsyncMemoizer _closeMemo = AsyncMemoizer(); final String _root; + final WebRendererMode webRenderer; /// Allows only one test suite (typically one test file) to be loaded and run /// at any given point in time. Loading more than one file at a time is known @@ -105,6 +136,10 @@ class FlutterWebPlatform extends PlatformPlugin { BrowserManager? _browserManager; late TestGoldenComparator _testGoldenComparator; + static Future defaultServerFactory() async { + return shelf_io.IOServer(await HttpMultiServer.loopback(0)); + } + static Future start(String root, { FlutterProject? flutterProject, String? shellPath, @@ -118,19 +153,37 @@ class FlutterWebPlatform extends PlatformPlugin { required ChromiumLauncher chromiumLauncher, required Artifacts? artifacts, required ProcessManager processManager, + required WebRendererMode webRenderer, TestTimeRecorder? testTimeRecorder, + Uri? testPackageUri, + Future Function() serverFactory = defaultServerFactory, }) async { - final shelf_io.IOServer server = shelf_io.IOServer(await HttpMultiServer.loopback(0)); - final PackageConfig packageConfig = await loadPackageConfigWithLogging( - fileSystem.file(fileSystem.path.join( - Cache.flutterRoot!, - 'packages', - 'flutter_tools', - '.dart_tool', - 'package_config.json', - )), - logger: logger, - ); + final shelf.Server server = await serverFactory(); + if (testPackageUri == null) { + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + fileSystem.file(fileSystem.path.join( + Cache.flutterRoot!, + 'packages', + 'flutter_tools', + '.dart_tool', + 'package_config.json', + )), + logger: logger, + ); + testPackageUri = packageConfig['test']!.packageUriRoot; + } + final File testDartJs = fileSystem.file(fileSystem.path.join( + testPackageUri.toFilePath(), + 'dart.js', + )); + final File testHostDartJs = fileSystem.file(fileSystem.path.join( + testPackageUri.toFilePath(), + 'src', + 'runner', + 'browser', + 'static', + 'host.dart.js', + )); return FlutterWebPlatform._( server, Configuration.current.change(pauseAfterLoad: pauseAfterLoad), @@ -140,28 +193,21 @@ class FlutterWebPlatform extends PlatformPlugin { updateGoldens: updateGoldens, buildInfo: buildInfo, webMemoryFS: webMemoryFS, - flutterToolPackageConfig: packageConfig, + testDartJs: testDartJs, + testHostDartJs: testHostDartJs, fileSystem: fileSystem, chromiumLauncher: chromiumLauncher, artifacts: artifacts, logger: logger, nullAssertions: nullAssertions, processManager: processManager, + webRenderer: webRenderer, testTimeRecorder: testTimeRecorder, ); } bool get _closed => _closeMemo.hasRun; - /// Uri of the test package. - Uri get testUri => _flutterToolPackageConfig['test']!.packageUriRoot; - - WebRendererMode get _rendererMode { - return buildInfo.dartDefines.contains('FLUTTER_WEB_USE_SKIA=true') - ? WebRendererMode.canvaskit - : WebRendererMode.html; - } - NullSafetyMode get _nullSafetyMode { return buildInfo.nullSafetyMode == NullSafetyMode.sound ? NullSafetyMode.sound @@ -200,25 +246,10 @@ class FlutterWebPlatform extends PlatformPlugin { )); File get _dartSdk => _fileSystem.file( - _artifacts!.getHostArtifact(kDartSdkJsArtifactMap[_rendererMode]![_nullSafetyMode]!)); + _artifacts!.getHostArtifact(kDartSdkJsArtifactMap[webRenderer]![_nullSafetyMode]!)); File get _dartSdkSourcemaps => _fileSystem.file( - _artifacts!.getHostArtifact(kDartSdkJsMapArtifactMap[_rendererMode]![_nullSafetyMode]!)); - - /// The precompiled test javascript. - File get _testDartJs => _fileSystem.file(_fileSystem.path.join( - testUri.toFilePath(), - 'dart.js', - )); - - File get _testHostDartJs => _fileSystem.file(_fileSystem.path.join( - testUri.toFilePath(), - 'src', - 'runner', - 'browser', - 'static', - 'host.dart.js', - )); + _artifacts!.getHostArtifact(kDartSdkJsMapArtifactMap[webRenderer]![_nullSafetyMode]!)); File _canvasKitFile(String relativePath) { final String canvasKitPath = _fileSystem.path.join( @@ -314,7 +345,7 @@ class FlutterWebPlatform extends PlatformPlugin { if (fileUri != null) { final String dirname = _fileSystem.path.dirname(fileUri.toFilePath()); final String basename = _fileSystem.path.basename(fileUri.toFilePath()); - final shelf.Handler handler = createStaticHandler(dirname); + final shelf.Handler handler = createDirectoryHandler(_fileSystem.directory(dirname)); final shelf.Request modifiedRequest = shelf.Request( request.method, request.requestedUri.replace(path: basename), diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart index 41822c81fde..6d1e40bb1e6 100644 --- a/packages/flutter_tools/lib/src/test/runner.dart +++ b/packages/flutter_tools/lib/src/test/runner.dart @@ -151,6 +151,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { testOutputDir: tempBuildDir, testFiles: testFiles.map((Uri uri) => uri.toFilePath()).toList(), buildInfo: debuggingOptions.buildInfo, + webRenderer: debuggingOptions.webRenderer, ); testArgs ..add('--platform=chrome') @@ -181,6 +182,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner { logger: globals.logger, ), testTimeRecorder: testTimeRecorder, + webRenderer: debuggingOptions.webRenderer, ); }, ); diff --git a/packages/flutter_tools/lib/src/test/web_test_compiler.dart b/packages/flutter_tools/lib/src/test/web_test_compiler.dart index ecf28ae080f..7787a5ff7bf 100644 --- a/packages/flutter_tools/lib/src/test/web_test_compiler.dart +++ b/packages/flutter_tools/lib/src/test/web_test_compiler.dart @@ -17,6 +17,7 @@ import '../cache.dart'; import '../compile.dart'; import '../dart/language_version.dart'; import '../web/bootstrap.dart'; +import '../web/compile.dart'; import '../web/memory_fs.dart'; import 'test_config.dart'; @@ -48,6 +49,7 @@ class WebTestCompiler { required String testOutputDir, required List testFiles, required BuildInfo buildInfo, + required WebRendererMode webRenderer, }) async { LanguageVersion languageVersion = LanguageVersion(2, 8); late final String platformDillName; @@ -69,7 +71,7 @@ class WebTestCompiler { } final String platformDillPath = _fileSystem.path.join( - getWebPlatformBinariesDirectory(_artifacts, buildInfo.webRenderer).path, + _artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder).path, platformDillName ); @@ -109,6 +111,7 @@ class WebTestCompiler { fileSystem: _fileSystem, config: _config, ); + final List dartDefines = webRenderer.updateDartDefines(buildInfo.dartDefines); final ResidentCompiler residentCompiler = ResidentCompiler( _artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path, buildMode: buildInfo.mode, @@ -124,7 +127,7 @@ class WebTestCompiler { targetModel: TargetModel.dartdevc, extraFrontEndOptions: extraFrontEndOptions, platformDill: _fileSystem.file(platformDillPath).absolute.uri.toString(), - dartDefines: buildInfo.dartDefines, + dartDefines: dartDefines, librariesSpec: _artifacts.getHostArtifact(HostArtifact.flutterWebLibrariesJson).uri.toString(), packagesPath: buildInfo.packagesPath, artifacts: _artifacts, diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index c5fc910feaa..00d5a0337ed 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -24,7 +24,6 @@ import '../version.dart'; import 'compiler_config.dart'; import 'file_generators/flutter_service_worker_js.dart'; import 'migrations/scrub_generated_plugin_registrant.dart'; -import 'web_constants.dart'; export 'compiler_config.dart'; @@ -67,23 +66,14 @@ class WebBuilder { String target, BuildInfo buildInfo, ServiceWorkerStrategy serviceWorkerStrategy, { - required WebCompilerConfig compilerConfig, + required List compilerConfigs, String? baseHref, String? outputDirectoryPath, }) async { - if (compilerConfig.isWasm) { - globals.logger.printBox( - title: 'Experimental feature', - ''' - WebAssembly compilation is experimental. - $kWasmMoreInfo''', - ); - } - final bool hasWebPlugins = (await findPlugins(flutterProject)).any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey)); final Directory outputDirectory = outputDirectoryPath == null - ? _fileSystem.directory(getWebBuildDirectory(compilerConfig.isWasm)) + ? _fileSystem.directory(getWebBuildDirectory()) : _fileSystem.directory(outputDirectoryPath); outputDirectory.createSync(recursive: true); @@ -99,11 +89,7 @@ class WebBuilder { final Stopwatch sw = Stopwatch()..start(); try { final BuildResult result = await _buildSystem.build( - globals.buildTargets.webServiceWorker( - _fileSystem, - webRenderer: buildInfo.webRenderer, - isWasm: compilerConfig.isWasm, - ), + globals.buildTargets.webServiceWorker(_fileSystem, compilerConfigs), Environment( projectDir: _fileSystem.currentDirectory, outputDir: outputDirectory, @@ -113,7 +99,6 @@ class WebBuilder { kHasWebPlugins: hasWebPlugins.toString(), if (baseHref != null) kBaseHref: baseHref, kServiceWorkerStrategy: serviceWorkerStrategy.cliName, - ...compilerConfig.toBuildSystemEnvironment(), ...buildInfo.toBuildSystemEnvironment(), }, artifacts: globals.artifacts!, @@ -146,8 +131,7 @@ class WebBuilder { } final String buildSettingsString = _buildEventAnalyticsSettings( - config: compilerConfig, - buildInfo: buildInfo, + configs: compilerConfigs, ); BuildEvent( @@ -163,14 +147,15 @@ class WebBuilder { )); final Duration elapsedDuration = sw.elapsed; + final String variableName = compilerConfigs.length > 1 ? 'dual-compile' : 'dart2js'; _flutterUsage.sendTiming( 'build', - compilerConfig.isWasm ? 'dart2wasm' : 'dart2js', + variableName, elapsedDuration, ); _analytics.send(Event.timing( workflow: 'build', - variableName: compilerConfig.isWasm ? 'dart2wasm' : 'dart2js', + variableName: variableName, elapsedMilliseconds: elapsedDuration.inMilliseconds, )); } @@ -222,6 +207,16 @@ enum WebRendererMode implements CliEnum { 'FLUTTER_WEB_USE_SKWASM=true', ] }; + + List updateDartDefines(List inputDefines) { + final Set dartDefinesSet = inputDefines.toSet(); + if (!inputDefines.any((String d) => d.startsWith('FLUTTER_WEB_AUTO_DETECT=')) + && inputDefines.any((String d) => d.startsWith('FLUTTER_WEB_USE_SKIA='))) { + dartDefinesSet.removeWhere((String d) => d.startsWith('FLUTTER_WEB_USE_SKIA=')); + } + dartDefinesSet.addAll(dartDefines); + return dartDefinesSet.toList(); + } } /// The correct precompiled artifact to use for each build and render mode. @@ -257,13 +252,18 @@ const Map> kDartSdkJsMapArtif }; String _buildEventAnalyticsSettings({ - required WebCompilerConfig config, - required BuildInfo buildInfo, + required List configs, }) { - final Map values = { - ...config.buildEventAnalyticsValues, - 'web-renderer': buildInfo.webRenderer.cliName, - }; + final Map values = {}; + final List renderers = []; + final List targets = []; + for (final WebCompilerConfig config in configs) { + values.addAll(config.buildEventAnalyticsValues); + renderers.add(config.renderer.name); + targets.add(config.compileTarget.name); + } + values['web-renderer'] = renderers.join(','); + values['web-target'] = targets.join(','); final List sortedList = values.entries .map((MapEntry e) => '${e.key}: ${e.value};') diff --git a/packages/flutter_tools/lib/src/web/compiler_config.dart b/packages/flutter_tools/lib/src/web/compiler_config.dart index 286725b6048..676c8dcc39b 100644 --- a/packages/flutter_tools/lib/src/web/compiler_config.dart +++ b/packages/flutter_tools/lib/src/web/compiler_config.dart @@ -3,58 +3,48 @@ // found in the LICENSE file. import '../base/utils.dart'; +import '../convert.dart'; +import 'compile.dart'; -abstract class WebCompilerConfig { - const WebCompilerConfig(); +enum CompileTarget { + js, + wasm, +} - /// Returns `true` if `this` represents configuration for the Wasm compiler. - /// - /// Otherwise, `false`–represents the JavaScript compiler. - bool get isWasm; +sealed class WebCompilerConfig { + const WebCompilerConfig({required this.renderer}); - Map toBuildSystemEnvironment(); + /// Returns which target this compiler outputs (js or wasm) + CompileTarget get compileTarget; + final WebRendererMode renderer; - Map get buildEventAnalyticsValues => { - 'wasm-compile': isWasm, - }; + String get buildKey; + + Map get buildEventAnalyticsValues => {}; } /// Configuration for the Dart-to-Javascript compiler (dart2js). class JsCompilerConfig extends WebCompilerConfig { const JsCompilerConfig({ - required this.csp, - required this.dumpInfo, - required this.nativeNullAssertions, - required this.optimizationLevel, - required this.noFrequencyBasedMinification, - required this.sourceMaps, + this.csp = false, + this.dumpInfo = false, + this.nativeNullAssertions = false, + this.optimizationLevel = kDart2jsDefaultOptimizationLevel, + this.noFrequencyBasedMinification = false, + this.sourceMaps = true, + super.renderer = WebRendererMode.auto, }); /// Instantiates [JsCompilerConfig] suitable for the `flutter run` command. - const JsCompilerConfig.run({required bool nativeNullAssertions}) - : this( - csp: false, - dumpInfo: false, + const JsCompilerConfig.run({ + required bool nativeNullAssertions, + required WebRendererMode renderer, + }) : this( nativeNullAssertions: nativeNullAssertions, - noFrequencyBasedMinification: false, optimizationLevel: kDart2jsDefaultOptimizationLevel, - sourceMaps: true, + renderer: renderer, ); - /// Creates a new [JsCompilerConfig] from build system environment values. - /// - /// Should correspond exactly with [toBuildSystemEnvironment]. - factory JsCompilerConfig.fromBuildSystemEnvironment( - Map defines) => - JsCompilerConfig( - csp: defines[kCspMode] == 'true', - dumpInfo: defines[kDart2jsDumpInfo] == 'true', - nativeNullAssertions: defines[kNativeNullAssertions] == 'true', - optimizationLevel: defines[kDart2jsOptimization] ?? kDart2jsDefaultOptimizationLevel, - noFrequencyBasedMinification: defines[kDart2jsNoFrequencyBasedMinification] == 'true', - sourceMaps: defines[kSourceMapsEnabled] == 'true', - ); - /// The default optimization level for dart2js. /// /// Maps to [kDart2jsOptimization]. @@ -102,17 +92,7 @@ class JsCompilerConfig extends WebCompilerConfig { final bool sourceMaps; @override - bool get isWasm => false; - - @override - Map toBuildSystemEnvironment() => { - kCspMode: csp.toString(), - kDart2jsDumpInfo: dumpInfo.toString(), - kNativeNullAssertions: nativeNullAssertions.toString(), - kDart2jsNoFrequencyBasedMinification: noFrequencyBasedMinification.toString(), - kDart2jsOptimization: optimizationLevel, - kSourceMapsEnabled: sourceMaps.toString(), - }; + CompileTarget get compileTarget => CompileTarget.js; /// Arguments to use in both phases: full JS compile and CFE-only. List toSharedCommandOptions() => [ @@ -130,25 +110,29 @@ class JsCompilerConfig extends WebCompilerConfig { if (noFrequencyBasedMinification) '--no-frequency-based-minification', if (csp) '--csp', ]; + + @override + String get buildKey { + final Map settings = { + 'csp': csp, + 'dumpInfo': dumpInfo, + 'nativeNullAssertions': nativeNullAssertions, + 'noFrequencyBasedMinification': noFrequencyBasedMinification, + 'optimizationLevel': optimizationLevel, + 'sourceMaps': sourceMaps, + }; + return jsonEncode(settings); + } } /// Configuration for the Wasm compiler. class WasmCompilerConfig extends WebCompilerConfig { const WasmCompilerConfig({ - required this.omitTypeChecks, - required this.wasmOpt, + this.omitTypeChecks = false, + this.wasmOpt = WasmOptLevel.defaultValue, + super.renderer = WebRendererMode.auto, }); - /// Creates a new [WasmCompilerConfig] from build system environment values. - /// - /// Should correspond exactly with [toBuildSystemEnvironment]. - factory WasmCompilerConfig.fromBuildSystemEnvironment( - Map defines) => - WasmCompilerConfig( - omitTypeChecks: defines[kOmitTypeChecks] == 'true', - wasmOpt: WasmOptLevel.values.byName(defines[kRunWasmOpt]!), - ); - /// Build environment for [omitTypeChecks]. static const String kOmitTypeChecks = 'WasmOmitTypeChecks'; @@ -162,25 +146,31 @@ class WasmCompilerConfig extends WebCompilerConfig { final WasmOptLevel wasmOpt; @override - bool get isWasm => true; + CompileTarget get compileTarget => CompileTarget.wasm; - bool get runWasmOpt => wasmOpt == WasmOptLevel.full || wasmOpt == WasmOptLevel.debug; - - @override - Map toBuildSystemEnvironment() => { - kOmitTypeChecks: omitTypeChecks.toString(), - kRunWasmOpt: wasmOpt.name, - }; + bool get runWasmOpt => + wasmOpt == WasmOptLevel.full || wasmOpt == WasmOptLevel.debug; List toCommandOptions() => [ - if (omitTypeChecks) '--omit-type-checks', - ]; + if (omitTypeChecks) '--omit-type-checks', + ]; @override Map get buildEventAnalyticsValues => { - ...super.buildEventAnalyticsValues, - ...toBuildSystemEnvironment(), - }; + ...super.buildEventAnalyticsValues, + kOmitTypeChecks: omitTypeChecks.toString(), + kRunWasmOpt: wasmOpt.name, + }; + + @override + String get buildKey { + final Map settings = { + 'omitTypeChecks': omitTypeChecks, + 'wasmOpt': wasmOpt.name, + }; + return jsonEncode(settings); + } + } enum WasmOptLevel implements CliEnum { @@ -195,8 +185,11 @@ enum WasmOptLevel implements CliEnum { @override String get helpText => switch (this) { - WasmOptLevel.none => 'wasm-opt is not run. Fastest build; bigger, slower output.', - WasmOptLevel.debug => 'Similar to `${WasmOptLevel.full.name}`, but member names are preserved. Debugging is easier, but size is a bit bigger.', - WasmOptLevel.full => 'wasm-opt is run. Build time is slower, but output is smaller and faster.', - }; + WasmOptLevel.none => + 'wasm-opt is not run. Fastest build; bigger, slower output.', + WasmOptLevel.debug => + 'Similar to `${WasmOptLevel.full.name}`, but member names are preserved. Debugging is easier, but size is a bit bigger.', + WasmOptLevel.full => + 'wasm-opt is run. Build time is slower, but output is smaller and faster.', + }; } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart index 073bc90dfb5..15a8ed0e221 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart @@ -11,11 +11,13 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/process.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/targets/web.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/build.dart'; import 'package:flutter_tools/src/commands/build_web.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; +import 'package:flutter_tools/src/web/compile.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -147,15 +149,9 @@ void main() { expect(environment.defines, { 'TargetFile': 'lib/main.dart', 'HasWebPlugins': 'true', - 'cspMode': 'false', - 'SourceMaps': 'false', - 'NativeNullAssertions': 'true', 'ServiceWorkerStrategy': 'offline-first', - 'Dart2jsDumpInfo': 'false', - 'Dart2jsNoFrequencyBasedMinification': 'false', - 'Dart2jsOptimization': 'O3', 'BuildMode': 'release', - 'DartDefines': 'Zm9vPWE=,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==', + 'DartDefines': 'Zm9vPWE=', 'DartObfuscation': 'false', 'TrackWidgetCreation': 'false', 'TreeShakeIcons': 'true', @@ -249,15 +245,8 @@ void main() { expect(environment.defines, { 'TargetFile': 'lib/main.dart', 'HasWebPlugins': 'true', - 'cspMode': 'false', - 'SourceMaps': 'false', - 'NativeNullAssertions': 'true', 'ServiceWorkerStrategy': 'offline-first', - 'Dart2jsDumpInfo': 'false', - 'Dart2jsNoFrequencyBasedMinification': 'false', - 'Dart2jsOptimization': 'O4', 'BuildMode': 'release', - 'DartDefines': 'RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==', 'DartObfuscation': 'false', 'TrackWidgetCreation': 'false', 'TreeShakeIcons': 'true', @@ -288,15 +277,17 @@ void main() { final CommandRunner runner = createTestCommandRunner(buildCommand); setupFileSystemForEndToEndTest(fileSystem); await runner.run(['build', 'web', '--no-pub']); - final BuildInfo buildInfo = - await buildCommand.webCommand.getBuildInfo(forcedBuildMode: BuildMode.debug); - expect(buildInfo.dartDefines, contains('FLUTTER_WEB_AUTO_DETECT=true')); }, overrides: { Platform: () => fakePlatform, FileSystem: () => fileSystem, FeatureFlags: () => TestFeatureFlags(isWebEnabled: true), ProcessManager: () => processManager, - BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)), + BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { + expect(target, isA()); + final List configs = (target as WebServiceWorker).compileConfigs; + expect(configs.length, 1); + expect(configs.first.renderer, WebRendererMode.auto); + }), }); testUsingContext('Web build supports build-name and build-number', () async { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/flutter_web_platform_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/flutter_web_platform_test.dart new file mode 100644 index 00000000000..54faa715085 --- /dev/null +++ b/packages/flutter_tools/test/commands.shard/hermetic/flutter_web_platform_test.dart @@ -0,0 +1,109 @@ +// 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:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/artifacts.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/test/flutter_web_platform.dart'; +import 'package:flutter_tools/src/web/chrome.dart'; +import 'package:flutter_tools/src/web/compile.dart'; +import 'package:flutter_tools/src/web/memory_fs.dart'; +import 'package:shelf/shelf.dart' as shelf; +import 'package:test/test.dart'; + +import '../../src/context.dart'; +import '../../src/fakes.dart'; + +class MockServer implements shelf.Server { + shelf.Handler? mountedHandler; + + @override + Future close() async {} + + @override + void mount(shelf.Handler handler) { + mountedHandler = handler; + } + + @override + Uri get url => Uri.parse(''); +} + +void main() { + late FileSystem fileSystem; + late BufferLogger logger; + late Platform platform; + late Artifacts artifacts; + late ProcessManager processManager; + late FakeOperatingSystemUtils operatingSystemUtils; + + setUp(() { + fileSystem = MemoryFileSystem.test(); + logger = BufferLogger.test(); + platform = FakePlatform(); + artifacts = Artifacts.test(fileSystem: fileSystem); + processManager = FakeProcessManager.empty(); + operatingSystemUtils = FakeOperatingSystemUtils(); + + for (final HostArtifact artifact in [ + HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk, + HostArtifact.webPrecompiledCanvaskitAndHtmlSdk, + HostArtifact.webPrecompiledCanvaskitSoundSdk, + HostArtifact.webPrecompiledCanvaskitSdk, + HostArtifact.webPrecompiledSoundSdk, + HostArtifact.webPrecompiledSdk, + ]) { + final File artifactFile = artifacts.getHostArtifact(artifact) as File; + artifactFile.createSync(); + artifactFile.writeAsStringSync(artifact.name); + } + }); + + testUsingContext('FlutterWebPlatform serves the correct dart_sdk.js for the passed web renderer', () async { + final ChromiumLauncher chromiumLauncher = ChromiumLauncher( + fileSystem: fileSystem, + platform: platform, + processManager: processManager, + operatingSystemUtils: operatingSystemUtils, + browserFinder: (Platform platform, FileSystem filesystem) => 'chrome', + logger: logger, + ); + final MockServer server = MockServer(); + fileSystem.directory('/test').createSync(); + final FlutterWebPlatform webPlatform = await FlutterWebPlatform.start( + 'ProjectRoot', + buildInfo: const BuildInfo( + BuildMode.debug, + '', + treeShakeIcons: false + ), + webMemoryFS: WebMemoryFS(), + fileSystem: fileSystem, + logger: logger, + chromiumLauncher: chromiumLauncher, + artifacts: artifacts, + processManager: processManager, + webRenderer: WebRendererMode.canvaskit, + serverFactory: () async => server, + testPackageUri: Uri.parse('test'), + ); + final shelf.Handler? handler = server.mountedHandler; + expect(handler, isNotNull); + handler!; + final shelf.Response response = await handler(shelf.Request( + 'GET', + Uri.parse('http://localhost/dart_sdk.js'), + )); + final String contents = await response.readAsString(); + expect(contents, HostArtifact.webPrecompiledCanvaskitSoundSdk.name); + await webPlatform.close(); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + Logger: () => logger, + }); +} diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart index bcc218c7889..47951c7b906 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -32,7 +32,6 @@ import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/run_hot.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/vmservice.dart'; -import 'package:flutter_tools/src/web/compile.dart'; import 'package:test/fake.dart'; import 'package:unified_analytics/unified_analytics.dart' as analytics; import 'package:vm_service/vm_service.dart'; @@ -1087,47 +1086,6 @@ void main() { }); }); - group('dart-defines and web-renderer options', () { - late List dartDefines; - - setUp(() { - dartDefines = []; - }); - - test('auto web-renderer with no dart-defines', () { - dartDefines = FlutterCommand.updateDartDefines(dartDefines, WebRendererMode.auto); - expect(dartDefines, ['FLUTTER_WEB_AUTO_DETECT=true']); - }); - - test('canvaskit web-renderer with no dart-defines', () { - dartDefines = FlutterCommand.updateDartDefines(dartDefines, WebRendererMode.canvaskit); - expect(dartDefines, ['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=true']); - }); - - test('html web-renderer with no dart-defines', () { - dartDefines = FlutterCommand.updateDartDefines(dartDefines, WebRendererMode.html); - expect(dartDefines, ['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']); - }); - - test('auto web-renderer with existing dart-defines', () { - dartDefines = ['FLUTTER_WEB_USE_SKIA=false']; - dartDefines = FlutterCommand.updateDartDefines(dartDefines, WebRendererMode.auto); - expect(dartDefines, ['FLUTTER_WEB_AUTO_DETECT=true']); - }); - - test('canvaskit web-renderer with no dart-defines', () { - dartDefines = ['FLUTTER_WEB_USE_SKIA=false']; - dartDefines = FlutterCommand.updateDartDefines(dartDefines, WebRendererMode.canvaskit); - expect(dartDefines, ['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=true']); - }); - - test('html web-renderer with no dart-defines', () { - dartDefines = ['FLUTTER_WEB_USE_SKIA=true']; - dartDefines = FlutterCommand.updateDartDefines(dartDefines, WebRendererMode.html); - expect(dartDefines, ['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']); - }); - }); - group('terminal', () { late FakeAnsiTerminal fakeTerminal; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart index d99939ded31..212f051b3a0 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart @@ -23,6 +23,7 @@ import 'package:flutter_tools/src/test/test_device.dart'; import 'package:flutter_tools/src/test/test_time_recorder.dart'; import 'package:flutter_tools/src/test/test_wrapper.dart'; import 'package:flutter_tools/src/test/watcher.dart'; +import 'package:flutter_tools/src/web/compile.dart'; import 'package:stream_channel/stream_channel.dart'; import 'package:vm_service/vm_service.dart'; @@ -1058,6 +1059,24 @@ dev_dependencies: FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), }); + + testUsingContext('Passes web renderer into debugging options', () async { + final FakeFlutterTestRunner testRunner = FakeFlutterTestRunner(0); + + final TestCommand testCommand = TestCommand(testRunner: testRunner); + final CommandRunner commandRunner = createTestCommandRunner(testCommand); + + await commandRunner.run(const [ + 'test', + '--no-pub', + '--platform=chrome', + '--web-renderer=canvaskit', + ]); + expect(testRunner.lastDebuggingOptionsValue.webRenderer, WebRendererMode.canvaskit); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); }); } diff --git a/packages/flutter_tools/test/general.shard/build_system/build_system_test.dart b/packages/flutter_tools/test/general.shard/build_system/build_system_test.dart index 22bfa2ae942..a5c67ab92f4 100644 --- a/packages/flutter_tools/test/general.shard/build_system/build_system_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/build_system_test.dart @@ -21,10 +21,10 @@ import '../../src/fake_process_manager.dart'; void main() { late FileSystem fileSystem; late Environment environment; - late Target fooTarget; - late Target barTarget; - late Target fizzTarget; - late Target sharedTarget; + late TestTarget fooTarget; + late TestTarget barTarget; + late TestTarget fizzTarget; + late TestTarget sharedTarget; late int fooInvocations; late int barInvocations; late int shared; @@ -138,6 +138,23 @@ void main() { json.decode(stampFile.readAsStringSync())); expect(stampContents, containsPair('inputs', ['/foo.dart'])); + expect(stampContents!.containsKey('buildKey'), false); + }); + + testWithoutContext('Saves a stamp file with inputs, outputs and build key', () async { + fooTarget.buildKey = 'fooBuildKey'; + final BuildSystem buildSystem = setUpBuildSystem(fileSystem); + await buildSystem.build(fooTarget, environment); + final File stampFile = fileSystem.file( + '${environment.buildDir.path}/foo.stamp'); + + expect(stampFile, exists); + + final Map? stampContents = castStringKeyedMap( + json.decode(stampFile.readAsStringSync())); + + expect(stampContents, containsPair('inputs', ['/foo.dart'])); + expect(stampContents, containsPair('buildKey', 'fooBuildKey')); }); testWithoutContext('Creates a BuildResult with inputs and outputs', () async { @@ -168,6 +185,19 @@ void main() { expect(fooInvocations, 2); }); + testWithoutContext('Re-invoke build if build key is modified', () async { + final BuildSystem buildSystem = setUpBuildSystem(fileSystem); + fooTarget.buildKey = 'old'; + + await buildSystem.build(fooTarget, environment); + + fooTarget.buildKey = 'new'; + + await buildSystem.build(fooTarget, environment); + + expect(fooInvocations, 2); + }); + testWithoutContext('does not re-invoke build if input timestamp changes', () async { final BuildSystem buildSystem = setUpBuildSystem(fileSystem); await buildSystem.build(fooTarget, environment); @@ -723,4 +753,7 @@ class TestTarget extends Target { @override List outputs = []; + + @override + String? buildKey; } diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_defines_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_defines_test.dart new file mode 100644 index 00000000000..80f52b00ee2 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_defines_test.dart @@ -0,0 +1,50 @@ +// 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:flutter_tools/src/web/compile.dart'; +import 'package:test/test.dart'; + +void main() { + + group('dart-defines and web-renderer options', () { + late List dartDefines; + + setUp(() { + dartDefines = []; + }); + + test('auto web-renderer with no dart-defines', () { + dartDefines = WebRendererMode.auto.updateDartDefines(dartDefines); + expect(dartDefines, ['FLUTTER_WEB_AUTO_DETECT=true']); + }); + + test('canvaskit web-renderer with no dart-defines', () { + dartDefines = WebRendererMode.canvaskit.updateDartDefines(dartDefines); + expect(dartDefines, ['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=true']); + }); + + test('html web-renderer with no dart-defines', () { + dartDefines = WebRendererMode.html.updateDartDefines(dartDefines); + expect(dartDefines, ['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']); + }); + + test('auto web-renderer with existing dart-defines', () { + dartDefines = ['FLUTTER_WEB_USE_SKIA=false']; + dartDefines = WebRendererMode.auto.updateDartDefines(dartDefines); + expect(dartDefines, ['FLUTTER_WEB_AUTO_DETECT=true']); + }); + + test('canvaskit web-renderer with no dart-defines', () { + dartDefines = ['FLUTTER_WEB_USE_SKIA=false']; + dartDefines = WebRendererMode.canvaskit.updateDartDefines(dartDefines); + expect(dartDefines, ['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=true']); + }); + + test('html web-renderer with no dart-defines', () { + dartDefines = ['FLUTTER_WEB_USE_SKIA=true']; + dartDefines = WebRendererMode.html.updateDartDefines(dartDefines); + expect(dartDefines, ['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']); + }); + }); +} diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart index 6ce8012d9e0..c74fb25e02b 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart @@ -120,7 +120,9 @@ void main() { webResources.childFile('index.html') .createSync(recursive: true); environment.buildDir.childFile('main.dart.js').createSync(); - await const WebReleaseBundle(WebRendererMode.auto, isWasm: false).build(environment); + await WebReleaseBundle([ + const JsCompilerConfig() + ]).build(environment); expect(environment.outputDir.childFile('version.json'), exists); })); @@ -132,7 +134,9 @@ void main() { final Directory webResources = environment.projectDir.childDirectory('web'); webResources.childFile('index.html').createSync(recursive: true); environment.buildDir.childFile('main.dart.js').createSync(); - await const WebReleaseBundle(WebRendererMode.auto, isWasm: false).build(environment); + await WebReleaseBundle([ + const JsCompilerConfig() + ]).build(environment); final String versionFile = environment.outputDir .childFile('version.json') @@ -150,7 +154,9 @@ void main() { '''); environment.buildDir.childFile('main.dart.js').createSync(); - await const WebReleaseBundle(WebRendererMode.auto, isWasm: false).build(environment); + await WebReleaseBundle([ + const JsCompilerConfig() + ]).build(environment); expect(environment.outputDir.childFile('index.html').readAsStringSync(), contains('/basehreftest/')); })); @@ -163,7 +169,9 @@ void main() { '''); environment.buildDir.childFile('main.dart.js').createSync(); - await const WebReleaseBundle(WebRendererMode.auto, isWasm: false).build(environment); + await WebReleaseBundle([ + const JsCompilerConfig() + ]).build(environment); expect(environment.outputDir.childFile('index.html').readAsStringSync(), contains('/basehreftest/')); })); @@ -185,7 +193,9 @@ void main() { .writeAsStringSync('A'); environment.buildDir.childFile('main.dart.js').createSync(); - await const WebReleaseBundle(WebRendererMode.auto, isWasm: false).build(environment); + await WebReleaseBundle([ + const JsCompilerConfig() + ]).build(environment); expect(environment.outputDir.childFile('foo.txt') .readAsStringSync(), 'A'); @@ -197,7 +207,9 @@ void main() { // Update to arbitrary resource file triggers rebuild. webResources.childFile('foo.txt').writeAsStringSync('B'); - await const WebReleaseBundle(WebRendererMode.auto, isWasm: false).build(environment); + await WebReleaseBundle([ + const JsCompilerConfig() + ]).build(environment); expect(environment.outputDir.childFile('foo.txt') .readAsStringSync(), 'B'); @@ -207,6 +219,36 @@ void main() { contains('flutter_service_worker.js?v='), )); })); + + test('WebReleaseBundle copies over output files when they change', () => testbed.run(() async { + final Directory webResources = environment.projectDir.childDirectory('web'); + webResources.childFile('foo.txt') + ..createSync(recursive: true) + ..writeAsStringSync('A'); + + environment.buildDir.childFile('main.dart.wasm')..createSync()..writeAsStringSync('old wasm'); + environment.buildDir.childFile('main.dart.mjs')..createSync()..writeAsStringSync('old mjs'); + await WebReleaseBundle([ + const WasmCompilerConfig() + ]).build(environment); + expect(environment.outputDir.childFile('main.dart.wasm') + .readAsStringSync(), 'old wasm'); + expect(environment.outputDir.childFile('main.dart.mjs') + .readAsStringSync(), 'old mjs'); + + environment.buildDir.childFile('main.dart.wasm')..createSync()..writeAsStringSync('new wasm'); + environment.buildDir.childFile('main.dart.mjs')..createSync()..writeAsStringSync('new mjs'); + + await WebReleaseBundle([ + const WasmCompilerConfig() + ]).build(environment); + + expect(environment.outputDir.childFile('main.dart.wasm') + .readAsStringSync(), 'new wasm'); + expect(environment.outputDir.childFile('main.dart.mjs') + .readAsStringSync(), 'new mjs'); + })); + test('WebEntrypointTarget generates an entrypoint for a file outside of main', () => testbed.run(() async { final File mainFile = globals.fs.file(globals.fs.path.join('other', 'lib', 'main.dart')) ..createSync(recursive: true) @@ -353,6 +395,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, @@ -365,6 +408,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', @@ -375,7 +419,12 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.auto).build(environment); + await Dart2JSTarget( + const JsCompilerConfig( + csp: true, + sourceMaps: false, + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -387,6 +436,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, @@ -399,6 +449,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', @@ -408,7 +459,11 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.auto).build(environment); + await Dart2JSTarget( + const JsCompilerConfig( + sourceMaps: false, + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -421,6 +476,7 @@ void main() { ..._kDart2jsLinuxArgs, '--enable-experiment=non-nullable', '-Ddart.vm.profile=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, @@ -434,6 +490,7 @@ void main() { ..._kDart2jsLinuxArgs, '--enable-experiment=non-nullable', '-Ddart.vm.profile=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', @@ -443,7 +500,11 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.auto).build(environment); + await Dart2JSTarget( + const JsCompilerConfig( + sourceMaps: false, + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -454,6 +515,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, @@ -466,6 +528,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', @@ -475,7 +538,11 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.auto).build(environment); + await Dart2JSTarget( + const JsCompilerConfig( + sourceMaps: false, + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -486,6 +553,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, @@ -498,6 +566,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-O4', '-o', @@ -506,7 +575,11 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.auto).build(environment); + await Dart2JSTarget( + const JsCompilerConfig( + sourceMaps: false, + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -518,6 +591,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--native-null-assertions', '--no-source-maps', '-o', @@ -531,6 +605,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--native-null-assertions', '--no-source-maps', '-O4', @@ -540,7 +615,12 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.auto).build(environment); + await Dart2JSTarget( + const JsCompilerConfig( + nativeNullAssertions: true, + sourceMaps: false, + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -552,6 +632,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, @@ -564,6 +645,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-O3', '-o', @@ -572,7 +654,12 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.auto).build(environment); + await Dart2JSTarget( + const JsCompilerConfig( + optimizationLevel: 'O3', + sourceMaps: false, + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -583,6 +670,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, @@ -598,6 +686,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-O4', '-o', @@ -606,7 +695,11 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.auto).build(environment); + await Dart2JSTarget( + const JsCompilerConfig( + sourceMaps: false, + ) + ).build(environment); expect(environment.buildDir.childFile('dart2js.d'), exists); final Depfile depfile = environment.depFileService.parse(environment.buildDir.childFile('dart2js.d')); @@ -627,6 +720,7 @@ void main() { '-Ddart.vm.product=true', '-DFOO=bar', '-DBAZ=qux', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, @@ -641,6 +735,7 @@ void main() { '-Ddart.vm.product=true', '-DFOO=bar', '-DBAZ=qux', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-O4', '-o', @@ -649,7 +744,11 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.auto).build(environment); + await Dart2JSTarget( + const JsCompilerConfig( + sourceMaps: false, + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -661,6 +760,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', @@ -672,6 +772,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '-O4', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, @@ -679,7 +780,9 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.auto).build(environment); + await Dart2JSTarget( + const JsCompilerConfig() + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -694,6 +797,7 @@ void main() { '-Ddart.vm.profile=true', '-DFOO=bar', '-DBAZ=qux', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, @@ -708,6 +812,7 @@ void main() { '-Ddart.vm.profile=true', '-DFOO=bar', '-DBAZ=qux', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', @@ -717,7 +822,11 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.auto).build(environment); + await Dart2JSTarget( + const JsCompilerConfig( + sourceMaps: false, + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -729,6 +838,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, @@ -741,6 +851,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', @@ -751,7 +862,12 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.canvaskit).build(environment); + await Dart2JSTarget( + const JsCompilerConfig( + dumpInfo: true, + sourceMaps: false, + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -763,6 +879,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, @@ -775,6 +892,7 @@ void main() { command: [ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', + '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', @@ -785,7 +903,12 @@ void main() { ] )); - await Dart2JSTarget(WebRendererMode.canvaskit).build(environment); + await Dart2JSTarget( + const JsCompilerConfig( + noFrequencyBasedMinification: true, + sourceMaps: false, + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -804,6 +927,8 @@ void main() { '-Ddart.vm.profile=true', '-DFOO=bar', '-DBAZ=qux', + '-DFLUTTER_WEB_AUTO_DETECT=false', + '-DFLUTTER_WEB_USE_SKIA=true', '--depfile=${depFile.absolute.path}', environment.buildDir.childFile('main.dart').absolute.path, environment.buildDir.childFile('main.dart.unopt.wasm').absolute.path, @@ -820,7 +945,11 @@ void main() { ]) ); - await Dart2WasmTarget(WebRendererMode.canvaskit).build(environment); + await Dart2WasmTarget( + const WasmCompilerConfig( + renderer: WebRendererMode.canvaskit + ) + ).build(environment); expect(outputJsFile.existsSync(), isFalse); final File movedJsFile = environment.buildDir.childFile('main.dart.mjs'); @@ -842,6 +971,8 @@ void main() { command: [ ..._kDart2WasmLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=false', + '-DFLUTTER_WEB_USE_SKIA=true', '--omit-type-checks', '--depfile=${depFile.absolute.path}', environment.buildDir.childFile('main.dart').absolute.path, @@ -859,7 +990,12 @@ void main() { ]) ); - await Dart2WasmTarget(WebRendererMode.canvaskit).build(environment); + await Dart2WasmTarget( + const WasmCompilerConfig( + omitTypeChecks: true, + renderer: WebRendererMode.canvaskit + ) + ).build(environment); expect(outputJsFile.existsSync(), isFalse); final File movedJsFile = environment.buildDir.childFile('main.dart.mjs'); @@ -880,6 +1016,8 @@ void main() { command: [ ..._kDart2WasmLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=false', + '-DFLUTTER_WEB_USE_SKIA=true', '--depfile=${depFile.absolute.path}', environment.buildDir.childFile('main.dart').absolute.path, environment.buildDir.childFile('main.dart.unopt.wasm').absolute.path, @@ -894,7 +1032,12 @@ void main() { environment.buildDir.childFile('main.dart.wasm').absolute.path, ])); - await Dart2WasmTarget(WebRendererMode.canvaskit).build(environment); + await Dart2WasmTarget( + const WasmCompilerConfig( + wasmOpt: WasmOptLevel.debug, + renderer: WebRendererMode.canvaskit + ) + ).build(environment); expect(outputJsFile.existsSync(), isFalse); final File movedJsFile = environment.buildDir.childFile('main.dart.mjs'); @@ -915,12 +1058,19 @@ void main() { command: [ ..._kDart2WasmLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=false', + '-DFLUTTER_WEB_USE_SKIA=true', '--depfile=${depFile.absolute.path}', environment.buildDir.childFile('main.dart').absolute.path, environment.buildDir.childFile('main.dart.wasm').absolute.path, ], onRun: (_) => outputJsFile..createSync()..writeAsStringSync('foo'))); - await Dart2WasmTarget(WebRendererMode.canvaskit).build(environment); + await Dart2WasmTarget( + const WasmCompilerConfig( + wasmOpt: WasmOptLevel.none, + renderer: WebRendererMode.canvaskit + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -935,6 +1085,9 @@ void main() { command: [ ..._kDart2WasmLinuxArgs, '-Ddart.vm.product=true', + '-DFLUTTER_WEB_AUTO_DETECT=false', + '-DFLUTTER_WEB_USE_SKIA=false', + '-DFLUTTER_WEB_USE_SKWASM=true', '--import-shared-memory', '--shared-memory-max-pages=32768', '--depfile=${depFile.absolute.path}', @@ -953,7 +1106,11 @@ void main() { ]) ); - await Dart2WasmTarget(WebRendererMode.skwasm).build(environment); + await Dart2WasmTarget( + const WasmCompilerConfig( + renderer: WebRendererMode.skwasm, + ) + ).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -1001,7 +1158,9 @@ void main() { environment.outputDir.childDirectory('a').childFile('a.txt') ..createSync(recursive: true) ..writeAsStringSync('A'); - await WebServiceWorker(globals.fs, WebRendererMode.auto, isWasm: false).build(environment); + await WebServiceWorker(globals.fs, [ + const JsCompilerConfig() + ]).build(environment); expect(environment.outputDir.childFile('flutter_service_worker.js'), exists); // Contains file hash. @@ -1024,7 +1183,9 @@ void main() { .childFile('assets/index.html') ..createSync(recursive: true) ..writeAsStringSync('A'); - await WebServiceWorker(globals.fs, WebRendererMode.auto, isWasm: false).build(environment); + await WebServiceWorker(globals.fs, [ + const JsCompilerConfig() + ]).build(environment); expect(environment.outputDir.childFile('flutter_service_worker.js'), exists); // Contains the same file hash for both `/` and the root index.html file. @@ -1046,7 +1207,9 @@ void main() { environment.outputDir .childFile('main.dart.js.map') .createSync(recursive: true); - await WebServiceWorker(globals.fs, WebRendererMode.auto, isWasm: false).build(environment); + await WebServiceWorker(globals.fs, [ + const JsCompilerConfig() + ]).build(environment); // No caching of source maps. expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(), @@ -1056,24 +1219,12 @@ void main() { contains('"main.dart.js"')); })); - test('wasm build copies and generates specific files', () => testbed.run(() async { - globals.fs.file('bin/cache/flutter_web_sdk/canvaskit/canvaskit.wasm') - .createSync(recursive: true); - - await WebBuiltInAssets(globals.fs, WebRendererMode.auto, isWasm: true).build(environment); - - expect(environment.outputDir.childFile('main.dart.js').existsSync(), true); - expect(environment.outputDir.childDirectory('canvaskit') - .childFile('canvaskit.wasm') - .existsSync(), true); - })); - - test('wasm copies over canvaskit again if the web sdk changes', () => testbed.run(() async { + test('WebBuiltInAssets copies over canvaskit again if the web sdk changes', () => testbed.run(() async { final File canvasKitInput = globals.fs.file('bin/cache/flutter_web_sdk/canvaskit/canvaskit.wasm') ..createSync(recursive: true); canvasKitInput.writeAsStringSync('foo', flush: true); - await WebBuiltInAssets(globals.fs, WebRendererMode.auto, isWasm: true).build(environment); + await WebBuiltInAssets(globals.fs).build(environment); final File canvasKitOutputBefore = environment.outputDir.childDirectory('canvaskit') .childFile('canvaskit.wasm'); @@ -1082,7 +1233,7 @@ void main() { canvasKitInput.writeAsStringSync('bar', flush: true); - await WebBuiltInAssets(globals.fs, WebRendererMode.auto, isWasm: true).build(environment); + await WebBuiltInAssets(globals.fs).build(environment); final File canvasKitOutputAfter = environment.outputDir.childDirectory('canvaskit') .childFile('canvaskit.wasm'); diff --git a/packages/flutter_tools/test/general.shard/test/web_test_compiler_test.dart b/packages/flutter_tools/test/general.shard/test/web_test_compiler_test.dart index 26ed691651c..578d3c30278 100644 --- a/packages/flutter_tools/test/general.shard/test/web_test_compiler_test.dart +++ b/packages/flutter_tools/test/general.shard/test/web_test_compiler_test.dart @@ -9,6 +9,7 @@ import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/test/web_test_compiler.dart'; +import 'package:flutter_tools/src/web/compile.dart'; import 'package:test/expect.dart'; import '../../src/context.dart'; @@ -44,6 +45,8 @@ void main() { '--incremental', '--target=dartdevc', '--experimental-emit-debug-metadata', + '-DFLUTTER_WEB_AUTO_DETECT=false', + '-DFLUTTER_WEB_USE_SKIA=true', '--output-dill', 'build/out', '--packages', @@ -87,6 +90,7 @@ void main() { testOutputDir: 'build', testFiles: ['project/test/fake_test.dart'], buildInfo: buildInfo, + webRenderer: WebRendererMode.canvaskit, ); expect(processManager.hasRemainingExpectations, isFalse); diff --git a/packages/flutter_tools/test/general.shard/web/compile_web_test.dart b/packages/flutter_tools/test/general.shard/web/compile_web_test.dart index b3557413f84..39a8b12e610 100644 --- a/packages/flutter_tools/test/general.shard/web/compile_web_test.dart +++ b/packages/flutter_tools/test/general.shard/web/compile_web_test.dart @@ -43,16 +43,11 @@ void main() { testUsingContext('WebBuilder sets environment on success', () async { final TestBuildSystem buildSystem = TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { - final WebServiceWorker webServiceWorker = target as WebServiceWorker; - expect(webServiceWorker.isWasm, isTrue, reason: 'should be wasm'); - expect(webServiceWorker.webRenderer, WebRendererMode.auto); - + expect(target, isA()); expect(environment.defines, { 'TargetFile': 'target', 'HasWebPlugins': 'false', 'ServiceWorkerStrategy': ServiceWorkerStrategy.offlineFirst.cliName, - 'WasmOmitTypeChecks': 'false', - 'RunWasmOpt': 'none', 'BuildMode': 'debug', 'DartObfuscation': 'false', 'TrackWidgetCreation': 'true', @@ -77,10 +72,16 @@ void main() { 'target', BuildInfo.debug, ServiceWorkerStrategy.offlineFirst, - compilerConfig: const WasmCompilerConfig( - omitTypeChecks: false, - wasmOpt: WasmOptLevel.none, - ), + compilerConfigs: [ + const WasmCompilerConfig( + wasmOpt: WasmOptLevel.none, + renderer: WebRendererMode.skwasm, + ), + const JsCompilerConfig.run( + nativeNullAssertions: true, + renderer: WebRendererMode.canvaskit, + ), + ], ); expect(logger.statusText, contains('Compiling target for the Web...')); @@ -102,7 +103,7 @@ void main() { label: 'web-compile', parameters: CustomDimensions( buildEventSettings: - 'RunWasmOpt: none; WasmOmitTypeChecks: false; wasm-compile: true; web-renderer: auto;', + 'RunWasmOpt: none; WasmOmitTypeChecks: false; web-renderer: skwasm,canvaskit; web-target: wasm,js;', ), ), ], @@ -115,7 +116,7 @@ void main() { Event.flutterBuildInfo( label: 'web-compile', buildType: 'web', - settings: 'RunWasmOpt: none; WasmOmitTypeChecks: false; wasm-compile: true; web-renderer: auto;', + settings: 'RunWasmOpt: none; WasmOmitTypeChecks: false; web-renderer: skwasm,canvaskit; web-target: wasm,js;', ), ]), ); @@ -123,12 +124,12 @@ void main() { // Sends timing event. final TestTimingEvent timingEvent = testUsage.timings.single; expect(timingEvent.category, 'build'); - expect(timingEvent.variableName, 'dart2wasm'); + expect(timingEvent.variableName, 'dual-compile'); expect( analyticsTimingEventExists( sentEvents: fakeAnalytics.sentEvents, workflow: 'build', - variableName: 'dart2wasm', + variableName: 'dual-compile', ), true, ); @@ -161,7 +162,9 @@ void main() { 'target', BuildInfo.debug, ServiceWorkerStrategy.offlineFirst, - compilerConfig: const JsCompilerConfig.run(nativeNullAssertions: true), + compilerConfigs: [ + const JsCompilerConfig.run(nativeNullAssertions: true, renderer: WebRendererMode.auto), + ] ), throwsToolExit(message: 'Failed to compile application for the Web.')); diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart index 0a9ac22ce52..143282e76a2 100644 --- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart +++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart @@ -66,6 +66,7 @@ void main() { {}, {}, NullSafetyMode.unsound, + webRenderer: WebRendererMode.canvaskit, ); releaseAssetServer = ReleaseAssetServer( globals.fs.file('main.dart').uri, @@ -291,6 +292,7 @@ void main() { {}, {}, NullSafetyMode.unsound, + webRenderer: WebRendererMode.canvaskit, ); expect(webAssetServer.basePath, 'foo/bar'); @@ -310,6 +312,7 @@ void main() { {}, {}, NullSafetyMode.unsound, + webRenderer: WebRendererMode.canvaskit, ); // Defaults to "/" when there's no base element. @@ -331,6 +334,7 @@ void main() { {}, {}, NullSafetyMode.unsound, + webRenderer: WebRendererMode.canvaskit, ), throwsToolExit(), ); @@ -351,6 +355,7 @@ void main() { {}, {}, NullSafetyMode.unsound, + webRenderer: WebRendererMode.canvaskit, ), throwsToolExit(), ); @@ -684,6 +689,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.unsound, + webRenderer: WebRendererMode.html, ); webDevFS.requireJS.createSync(recursive: true); webDevFS.flutterJs.createSync(recursive: true); @@ -745,13 +751,6 @@ void main() { // New SDK should be visible.. expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'BELLOW'); - // Toggle CanvasKit - expect(webDevFS.webAssetServer.webRenderer, WebRendererMode.html); - webDevFS.webAssetServer.webRenderer = WebRendererMode.canvaskit; - - expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'OL'); - expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js.map'), 'CHUM'); - // Generated entrypoint. expect(await webDevFS.webAssetServer.dartSourceContents('web_entrypoint.dart'), contains('GENERATED')); @@ -800,6 +799,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.sound, + webRenderer: WebRendererMode.html, ); webDevFS.requireJS.createSync(recursive: true); webDevFS.flutterJs.createSync(recursive: true); @@ -861,11 +861,6 @@ void main() { // New SDK should be visible.. expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'BELLOW'); - // Toggle CanvasKit - webDevFS.webAssetServer.webRenderer = WebRendererMode.canvaskit; - expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'OL'); - expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js.map'), 'CHUM'); - // Generated entrypoint. expect(await webDevFS.webAssetServer.dartSourceContents('web_entrypoint.dart'), contains('GENERATED')); @@ -913,6 +908,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.sound, + webRenderer: WebRendererMode.canvaskit, ); webDevFS.requireJS.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true); @@ -974,6 +970,7 @@ void main() { nullAssertions: true, nativeNullAssertions: true, nullSafetyMode: NullSafetyMode.sound, + webRenderer: WebRendererMode.canvaskit, ); webDevFS.requireJS.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true); @@ -1019,6 +1016,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.sound, + webRenderer: WebRendererMode.canvaskit, ); webDevFS.requireJS.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true); @@ -1065,6 +1063,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.sound, + webRenderer: WebRendererMode.auto, ); webDevFS.requireJS.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true); @@ -1112,6 +1111,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.unsound, + webRenderer: WebRendererMode.canvaskit, ); webDevFS.requireJS.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true); @@ -1148,7 +1148,9 @@ void main() { null, const {}, NullSafetyMode.unsound, - testMode: true); + webRenderer: WebRendererMode.canvaskit, + testMode: true + ); expect(webAssetServer.defaultResponseHeaders['x-frame-options'], null); await webAssetServer.dispose(); @@ -1180,7 +1182,9 @@ void main() { extraHeaderKey: extraHeaderValue, }, NullSafetyMode.unsound, - testMode: true); + webRenderer: WebRendererMode.canvaskit, + testMode: true + ); expect(webAssetServer.defaultResponseHeaders[extraHeaderKey], [extraHeaderValue]); @@ -1215,6 +1219,7 @@ void main() { {}, {}, NullSafetyMode.sound, + webRenderer: WebRendererMode.canvaskit, ); expect(await webAssetServer.metadataContents('foo/main_module.ddc_merged_metadata'), null); @@ -1257,6 +1262,7 @@ void main() { extraHeaders: const {}, chromiumLauncher: null, nullSafetyMode: NullSafetyMode.unsound, + webRenderer: WebRendererMode.canvaskit, ); webDevFS.requireJS.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true); diff --git a/packages/flutter_tools/test/integration.shard/flutter_build_wasm_test.dart b/packages/flutter_tools/test/integration.shard/flutter_build_wasm_test.dart index 4290db33756..40716aed39e 100644 --- a/packages/flutter_tools/test/integration.shard/flutter_build_wasm_test.dart +++ b/packages/flutter_tools/test/integration.shard/flutter_build_wasm_test.dart @@ -51,7 +51,7 @@ void main() { final Directory appBuildDir = fileSystem.directory(fileSystem.path.join( exampleAppDir.path, 'build', - 'web_wasm', + 'web', )); for (final String filename in const [ 'flutter.js', diff --git a/packages/flutter_tools/test/web.shard/hot_reload_web_test.dart b/packages/flutter_tools/test/web.shard/hot_reload_web_test.dart index e01e0b9e1ec..6220347af5c 100644 --- a/packages/flutter_tools/test/web.shard/hot_reload_web_test.dart +++ b/packages/flutter_tools/test/web.shard/hot_reload_web_test.dart @@ -19,6 +19,7 @@ void main() async { await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsCallback), name: 'flutter.js (callback)'); await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsPromisesFull), name: 'flutter.js (promises)'); await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsPromisesShort), name: 'flutter.js (promises, short)'); + await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsLoad), name: 'flutter.js (load)'); await _testProject(HotReloadProject(indexHtml: indexHtmlNoFlutterJs), name: 'No flutter.js'); } diff --git a/packages/flutter_tools/test/web.shard/test_data/hot_reload_index_html_samples.dart b/packages/flutter_tools/test/web.shard/test_data/hot_reload_index_html_samples.dart index b1d5d2a18dc..873604bf97f 100644 --- a/packages/flutter_tools/test/web.shard/test_data/hot_reload_index_html_samples.dart +++ b/packages/flutter_tools/test/web.shard/test_data/hot_reload_index_html_samples.dart @@ -56,6 +56,27 @@ String indexHtmlFlutterJsPromisesFull = _generateFlutterJsIndexHtml(''' }); '''); +/// index_with_flutterjs.html +String indexHtmlFlutterJsLoad = _generateFlutterJsIndexHtml(''' + window.addEventListener('load', function(ev) { + _flutter.buildConfig = { + builds: [ + { + "compileTarget": "dartdevc", + "renderer": "html", + "mainJsPath": "main.dart.js", + } + ] + }; + // Download main.dart.js + _flutter.loader.load({ + serviceWorkerSettings: { + serviceWorkerVersion: serviceWorkerVersion, + }, + }); + }); +'''); + /// index_without_flutterjs.html String indexHtmlNoFlutterJs = '''