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.
This commit is contained in:
Jackson Gardner 2024-02-13 12:02:10 -08:00 committed by GitHub
parent 4f1fc5a883
commit 5a9fa1e7bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 992 additions and 468 deletions

View file

@ -6,8 +6,12 @@ found in the LICENSE file. -->
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Web Benchmarks</title> <title>Web Benchmarks</title>
<script src="flutter.js"></script>
</head> </head>
<body> <body>
<script src="main.dart.js" type="application/javascript"></script> <script>
{{flutter_build_config}}
_flutter.loader.load();
</script>
</body> </body>
</html> </html>

View file

@ -39,7 +39,7 @@ Future<TaskResult> runWebBenchmark(WebBenchmarkOptions benchmarkOptions) async {
'--omit-type-checks', '--omit-type-checks',
], ],
'--dart-define=FLUTTER_WEB_ENABLE_PROFILING=true', '--dart-define=FLUTTER_WEB_ENABLE_PROFILING=true',
'--web-renderer=${benchmarkOptions.webRenderer}', if (!benchmarkOptions.useWasm) '--web-renderer=${benchmarkOptions.webRenderer}',
'--profile', '--profile',
'--no-web-resources-cdn', '--no-web-resources-cdn',
'-t', '-t',
@ -125,7 +125,7 @@ Future<TaskResult> runWebBenchmark(WebBenchmarkOptions benchmarkOptions) async {
return Response.internalServerError(body: '$error'); return Response.internalServerError(body: '$error');
} }
}).add(createBuildDirectoryHandler( }).add(createBuildDirectoryHandler(
path.join(macrobenchmarksDirectory, 'build', benchmarkOptions.useWasm ? 'web_wasm' : 'web'), path.join(macrobenchmarksDirectory, 'build', 'web'),
)); ));
server = await io.HttpServer.bind('localhost', benchmarkServerPort); server = await io.HttpServer.bind('localhost', benchmarkServerPort);

View file

@ -5,14 +5,17 @@ found in the LICENSE file. -->
<html> <html>
<head> <head>
<title>Web Integration Tests</title> <title>Web Integration Tests</title>
<script> <script src="flutter.js"></script>
// Use the local CanvasKit bundle instead of the CDN to reduce test flakiness.
window.flutterConfiguration = {
canvasKitBaseUrl: "/canvaskit/"
};
</script>
</head> </head>
<body> <body>
<script src="main.dart.js"></script> <script>
{{flutter_build_config}}
_flutter.loader.load({
config: {
// Use the local CanvasKit bundle instead of the CDN to reduce test flakiness.
canvasKitBaseUrl: "/canvaskit/",
},
});
</script>
</body> </body>
</html> </html>

View file

@ -12,7 +12,6 @@ import 'base/os.dart';
import 'base/utils.dart'; import 'base/utils.dart';
import 'convert.dart'; import 'convert.dart';
import 'globals.dart' as globals; import 'globals.dart' as globals;
import 'web/compile.dart';
/// Whether icon font subsetting is enabled by default. /// Whether icon font subsetting is enabled by default.
const bool kIconTreeShakerEnabledDefault = true; const bool kIconTreeShakerEnabledDefault = true;
@ -36,7 +35,6 @@ class BuildInfo {
List<String>? dartDefines, List<String>? dartDefines,
this.bundleSkSLPath, this.bundleSkSLPath,
List<String>? dartExperiments, List<String>? dartExperiments,
this.webRenderer = WebRendererMode.auto,
required this.treeShakeIcons, required this.treeShakeIcons,
this.performanceMeasurementFile, this.performanceMeasurementFile,
this.packagesPath = '.dart_tool/package_config.json', // TODO(zanderso): make this required and remove the default. 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. /// A list of Dart experiments.
final List<String> dartExperiments; final List<String> 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 /// The name of a file where flutter assemble will output performance
/// information in a JSON format. /// information in a JSON format.
/// ///
@ -803,10 +798,6 @@ HostPlatform getCurrentHostPlatform() {
return HostPlatform.linux_x64; return HostPlatform.linux_x64;
} }
FileSystemEntity getWebPlatformBinariesDirectory(Artifacts artifacts, WebRendererMode webRenderer) {
return artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder);
}
/// Returns the top-level build output directory. /// Returns the top-level build output directory.
String getBuildDirectory([Config? config, FileSystem? fileSystem]) { String getBuildDirectory([Config? config, FileSystem? fileSystem]) {
// TODO(johnmccutchan): Stop calling this function as part of setting // TODO(johnmccutchan): Stop calling this function as part of setting
@ -849,8 +840,8 @@ String getMacOSBuildDirectory() {
} }
/// Returns the web build output directory. /// Returns the web build output directory.
String getWebBuildDirectory([bool isWasm = false]) { String getWebBuildDirectory() {
return globals.fs.path.join(getBuildDirectory(), isWasm ? 'web_wasm' : 'web'); return globals.fs.path.join(getBuildDirectory(), 'web');
} }
/// Returns the Linux build output directory. /// Returns the Linux build output directory.

View file

@ -136,6 +136,15 @@ abstract class Target {
/// A list of zero or more depfiles, located directly under {BUILD_DIR}. /// A list of zero or more depfiles, located directly under {BUILD_DIR}.
List<String> get depfiles => const <String>[]; List<String> get depfiles => const <String>[];
/// 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]. /// Whether this target can be executed with the given [environment].
/// ///
/// Returning `true` will cause [build] to be skipped. This is equivalent /// Returning `true` will cause [build] to be skipped. This is equivalent
@ -156,6 +165,7 @@ abstract class Target {
<Node>[ <Node>[
for (final Target target in dependencies) target._toNode(environment), for (final Target target in dependencies) target._toNode(environment),
], ],
buildKey,
environment, environment,
inputsFiles.containsNewDepfile, inputsFiles.containsNewDepfile,
); );
@ -181,9 +191,11 @@ abstract class Target {
for (final File output in outputs) { for (final File output in outputs) {
outputPaths.add(output.path); outputPaths.add(output.path);
} }
final String? key = buildKey;
final Map<String, Object> result = <String, Object>{ final Map<String, Object> result = <String, Object>{
'inputs': inputPaths, 'inputs': inputPaths,
'outputs': outputPaths, 'outputs': outputPaths,
if (key != null) 'buildKey': key,
}; };
if (!stamp.existsSync()) { if (!stamp.existsSync()) {
stamp.createSync(); stamp.createSync();
@ -218,6 +230,7 @@ abstract class Target {
/// This requires constants from the [Environment] to resolve the paths of /// This requires constants from the [Environment] to resolve the paths of
/// inputs and the output stamp. /// inputs and the output stamp.
Map<String, Object> toJson(Environment environment) { Map<String, Object> toJson(Environment environment) {
final String? key = buildKey;
return <String, Object>{ return <String, Object>{
'name': name, 'name': name,
'dependencies': <String>[ 'dependencies': <String>[
@ -229,6 +242,7 @@ abstract class Target {
'outputs': <String>[ 'outputs': <String>[
for (final File file in resolveOutputs(environment).sources) file.path, for (final File file in resolveOutputs(environment).sources) file.path,
], ],
if (key != null) 'buildKey': key,
'stamp': _findStampFile(environment).absolute.path, 'stamp': _findStampFile(environment).absolute.path,
}; };
} }
@ -980,49 +994,85 @@ void verifyOutputDirectories(List<File> outputs, Environment environment, Target
/// A node in the build graph. /// A node in the build graph.
class Node { class Node {
Node( factory Node(
Target target,
List<File> inputs,
List<File> outputs,
List<Node> dependencies,
String? buildKey,
Environment environment,
bool missingDepfile,
) {
final File stamp = target._findStampFile(environment);
Map<String, Object?>? 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<Object?> && stampOutputs is List<Object?>) {
final Set<String> previousInputs = stampInputs.whereType<String>().toSet();
final Set<String> previousOutputs = stampOutputs.whereType<String>().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.target,
this.inputs, this.inputs,
this.outputs, this.outputs,
this.dependencies, this.dependencies,
Environment environment, this.buildKey,
this.missingDepfile, this.missingDepfile,
) { ) : previousInputs = <String>{},
final File stamp = target._findStampFile(environment); previousOutputs = <String>{},
previousBuildKey = null,
_dirty = true;
// If the stamp file doesn't exist, we haven't run this step before and Node.withStamp(
// all inputs were added. this.target,
if (!stamp.existsSync()) { this.inputs,
// No stamp file, not safe to skip. this.previousInputs,
_dirty = true; this.outputs,
return; this.previousOutputs,
} this.dependencies,
final String content = stamp.readAsStringSync(); this.buildKey,
// Something went wrong writing the stamp file. this.previousBuildKey,
if (content.isEmpty) { this.missingDepfile,
stamp.deleteSync(); ) : _dirty = false;
// Malformed stamp file, not safe to skip.
_dirty = true;
return;
}
Map<String, Object?>? 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<Object?> && outputs is List<Object?>) {
inputs.cast<String?>().whereType<String>().forEach(previousInputs.add);
outputs.cast<String?>().whereType<String>().forEach(previousOutputs.add);
} else {
// The json is malformed in some way.
_dirty = true;
}
}
/// The resolved input files. /// The resolved input files.
/// ///
@ -1034,6 +1084,11 @@ class Node {
/// These files may not yet exist if the target hasn't run yet. /// These files may not yet exist if the target hasn't run yet.
final List<File> outputs; final List<File> 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. /// Whether this node is missing a depfile.
/// ///
/// This requires an additional pass of source resolution after the target /// This requires an additional pass of source resolution after the target
@ -1047,10 +1102,15 @@ class Node {
final List<Node> dependencies; final List<Node> dependencies;
/// Output file paths from the previous invocation of this build node. /// Output file paths from the previous invocation of this build node.
final Set<String> previousOutputs = <String>{}; final Set<String> previousOutputs;
/// Input file paths from the previous invocation of this build node. /// Input file paths from the previous invocation of this build node.
final Set<String> previousInputs = <String>{}; final Set<String> 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. /// One or more reasons why a task was invalidated.
/// ///
@ -1074,6 +1134,10 @@ class Node {
FileSystem fileSystem, FileSystem fileSystem,
Logger logger, Logger logger,
) { ) {
if (buildKey != previousBuildKey) {
_invalidate(InvalidatedReasonKind.buildKeyChanged);
_dirty = true;
}
final Set<String> currentOutputPaths = <String>{ final Set<String> currentOutputPaths = <String>{
for (final File file in outputs) file.path, 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.inputChanged => 'The following inputs have updated contents: ${data.join(',')}',
InvalidatedReasonKind.outputChanged => 'The following outputs 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.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. /// The set of expected output files changed.
outputSetChanged, outputSetChanged,
/// The build key changed
buildKeyChanged,
} }

View file

@ -3,7 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../web/compile.dart'; import '../web/compiler_config.dart';
import './build_system.dart'; import './build_system.dart';
/// Commonly used build [Target]s. /// Commonly used build [Target]s.
@ -14,11 +14,7 @@ abstract class BuildTargets {
Target get releaseCopyFlutterBundle; Target get releaseCopyFlutterBundle;
Target get generateLocalizationsTarget; Target get generateLocalizationsTarget;
Target get dartPluginRegistrantTarget; Target get dartPluginRegistrantTarget;
Target webServiceWorker( Target webServiceWorker(FileSystem fileSystem, List<WebCompilerConfig> compileConfigs);
FileSystem fileSystem, {
required WebRendererMode webRenderer,
required bool isWasm
});
} }
/// BuildTargets that return NoOpTarget for every action. /// BuildTargets that return NoOpTarget for every action.
@ -38,11 +34,7 @@ class NoOpBuildTargets extends BuildTargets {
Target get dartPluginRegistrantTarget => const _NoOpTarget(); Target get dartPluginRegistrantTarget => const _NoOpTarget();
@override @override
Target webServiceWorker( Target webServiceWorker(FileSystem fileSystem, List<WebCompilerConfig> compileConfigs) => const _NoOpTarget();
FileSystem fileSystem, {
required WebRendererMode webRenderer,
required bool isWasm,
}) => const _NoOpTarget();
} }
/// A [Target] that does nothing. /// A [Target] that does nothing.

View file

@ -22,7 +22,6 @@ import '../../project.dart';
import '../../web/compile.dart'; import '../../web/compile.dart';
import '../../web/file_generators/flutter_service_worker_js.dart'; import '../../web/file_generators/flutter_service_worker_js.dart';
import '../../web/file_generators/main_dart.dart' as main_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 '../build_system.dart';
import '../depfile.dart'; import '../depfile.dart';
import '../exceptions.dart'; import '../exceptions.dart';
@ -95,11 +94,15 @@ class WebEntrypointTarget extends Target {
/// Compiles a web entry point with dart2js. /// Compiles a web entry point with dart2js.
abstract class Dart2WebTarget extends Target { abstract class Dart2WebTarget extends Target {
const Dart2WebTarget(this.webRenderer); const Dart2WebTarget();
final WebRendererMode webRenderer;
Source get compilerSnapshot; Source get compilerSnapshot;
WebCompilerConfig get compilerConfig;
Map<String, Object?> get buildConfig;
List<String> get buildFiles;
@override @override
List<Target> get dependencies => const <Target>[ List<Target> get dependencies => const <Target>[
WebEntrypointTarget(), WebEntrypointTarget(),
@ -116,11 +119,19 @@ abstract class Dart2WebTarget extends Target {
]; ];
@override @override
List<Source> get outputs => const <Source>[]; List<Source> get outputs => buildFiles.map(
(String file) => Source.pattern('{BUILD_DIR}/$file')
).toList();
@override
String get buildKey => compilerConfig.buildKey;
} }
class Dart2JSTarget extends Dart2WebTarget { class Dart2JSTarget extends Dart2WebTarget {
Dart2JSTarget(super.webRenderer); Dart2JSTarget(this.compilerConfig);
@override
final JsCompilerConfig compilerConfig;
@override @override
String get name => 'dart2js'; String get name => 'dart2js';
@ -140,9 +151,11 @@ class Dart2JSTarget extends Dart2WebTarget {
throw MissingDefineException(kBuildMode, name); throw MissingDefineException(kBuildMode, name);
} }
final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment); final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
final JsCompilerConfig compilerConfig = JsCompilerConfig.fromBuildSystemEnvironment(environment.defines);
final Artifacts artifacts = environment.artifacts; final Artifacts artifacts = environment.artifacts;
final String platformBinariesPath = getWebPlatformBinariesDirectory(artifacts, webRenderer).path; final String platformBinariesPath = artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder).path;
final List<String> dartDefines = compilerConfig.renderer.updateDartDefines(
decodeDartDefines(environment.defines, kDartDefines),
);
final List<String> sharedCommandOptions = <String>[ final List<String> sharedCommandOptions = <String>[
artifacts.getArtifactPath(Artifact.engineDartBinary, platform: TargetPlatform.web_javascript), artifacts.getArtifactPath(Artifact.engineDartBinary, platform: TargetPlatform.web_javascript),
'--disable-dart-dev', '--disable-dart-dev',
@ -154,7 +167,7 @@ class Dart2JSTarget extends Dart2WebTarget {
'-Ddart.vm.profile=true' '-Ddart.vm.profile=true'
else else
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
for (final String dartDefine in decodeDartDefines(environment.defines, kDartDefines)) for (final String dartDefine in dartDefines)
'-D$dartDefine', '-D$dartDefine',
]; ];
@ -208,10 +221,25 @@ class Dart2JSTarget extends Dart2WebTarget {
environment.buildDir.childFile('dart2js.d'), environment.buildDir.childFile('dart2js.d'),
); );
} }
@override
Map<String, Object?> get buildConfig => <String, Object?>{
'compileTarget': 'dart2js',
'renderer': compilerConfig.renderer.name,
'mainJsPath': 'main.dart.js',
};
@override
List<String> get buildFiles => <String>[
'main.dart.js',
];
} }
class Dart2WasmTarget extends Dart2WebTarget { class Dart2WasmTarget extends Dart2WebTarget {
Dart2WasmTarget(super.webRenderer); Dart2WasmTarget(this.compilerConfig);
@override
final WasmCompilerConfig compilerConfig;
@override @override
Future<void> build(Environment environment) async { Future<void> build(Environment environment) async {
@ -219,7 +247,6 @@ class Dart2WasmTarget extends Dart2WebTarget {
if (buildModeEnvironment == null) { if (buildModeEnvironment == null) {
throw MissingDefineException(kBuildMode, name); throw MissingDefineException(kBuildMode, name);
} }
final WasmCompilerConfig compilerConfig = WasmCompilerConfig.fromBuildSystemEnvironment(environment.defines);
final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment); final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
final Artifacts artifacts = environment.artifacts; final Artifacts artifacts = environment.artifacts;
final File outputWasmFile = environment.buildDir.childFile( final File outputWasmFile = environment.buildDir.childFile(
@ -227,8 +254,11 @@ class Dart2WasmTarget extends Dart2WebTarget {
); );
final File depFile = environment.buildDir.childFile('dart2wasm.d'); final File depFile = environment.buildDir.childFile('dart2wasm.d');
final String dartSdkPath = artifacts.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript); 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 String platformFilePath = environment.fileSystem.path.join(platformBinariesPath, 'dart2wasm_platform.dill');
final List<String> dartDefines = compilerConfig.renderer.updateDartDefines(
decodeDartDefines(environment.defines, kDartDefines),
);
final List<String> compilationArgs = <String>[ final List<String> compilationArgs = <String>[
artifacts.getArtifactPath(Artifact.engineDartAotRuntime, platform: TargetPlatform.web_javascript), artifacts.getArtifactPath(Artifact.engineDartAotRuntime, platform: TargetPlatform.web_javascript),
@ -242,10 +272,10 @@ class Dart2WasmTarget extends Dart2WebTarget {
else else
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
...decodeCommaSeparated(environment.defines, kExtraFrontEndOptions), ...decodeCommaSeparated(environment.defines, kExtraFrontEndOptions),
for (final String dartDefine in decodeDartDefines(environment.defines, kDartDefines)) for (final String dartDefine in dartDefines)
'-D$dartDefine', '-D$dartDefine',
...compilerConfig.toCommandOptions(), ...compilerConfig.toCommandOptions(),
if (webRenderer == WebRendererMode.skwasm) if (compilerConfig.renderer == WebRendererMode.skwasm)
...<String>[ ...<String>[
'--import-shared-memory', '--import-shared-memory',
'--shared-memory-max-pages=32768', '--shared-memory-max-pages=32768',
@ -310,74 +340,75 @@ class Dart2WasmTarget extends Dart2WebTarget {
]; ];
@override @override
List<Source> get outputs => const <Source>[ Map<String, Object?> get buildConfig => <String, Object?>{
Source.pattern('{OUTPUT_DIR}/main.dart.wasm'), 'compileTarget': 'dart2wasm',
Source.pattern('{OUTPUT_DIR}/main.dart.mjs'), 'renderer': compilerConfig.renderer.name,
'mainWasmPath': 'main.dart.wasm',
'jsSupportRuntimePath': 'main.dart.mjs',
};
@override
List<String> get buildFiles => <String>[
'main.dart.wasm',
'main.dart.mjs',
]; ];
} }
/// Unpacks the dart2js or dart2wasm compilation and resources to a given /// Unpacks the dart2js or dart2wasm compilation and resources to a given
/// output directory. /// output directory.
class WebReleaseBundle extends Target { class WebReleaseBundle extends Target {
const WebReleaseBundle(this.webRenderer, {required this.isWasm}); WebReleaseBundle(List<WebCompilerConfig> configs) : this._withTargets(
configs.map((WebCompilerConfig config) =>
switch (config) {
WasmCompilerConfig() => Dart2WasmTarget(config),
JsCompilerConfig() => Dart2JSTarget(config),
}
).toList()
);
final WebRendererMode webRenderer; const WebReleaseBundle._withTargets(this.compileTargets);
final bool isWasm;
String get outputFileNameNoSuffix => 'main.dart'; final List<Dart2WebTarget> compileTargets;
String get outputFileName => '$outputFileNameNoSuffix${isWasm ? '.wasm' : '.js'}';
String get wasmJSRuntimeFileName => '$outputFileNameNoSuffix.mjs'; List<String> get buildFiles => compileTargets.fold(
const Iterable<String>.empty(),
(Iterable<String> current, Dart2WebTarget target) => current.followedBy(target.buildFiles)
).toList();
@override @override
String get name => 'web_release_bundle'; String get name => 'web_release_bundle';
@override @override
List<Target> get dependencies => <Target>[ List<Target> get dependencies => compileTargets;
if (isWasm) Dart2WasmTarget(webRenderer) else Dart2JSTarget(webRenderer),
];
@override @override
List<Source> get inputs => <Source>[ List<Source> get inputs => <Source>[
Source.pattern('{BUILD_DIR}/$outputFileName'),
const Source.pattern('{PROJECT_DIR}/pubspec.yaml'), const Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
if (isWasm) Source.pattern('{BUILD_DIR}/$wasmJSRuntimeFileName'), ...buildFiles.map((String file) => Source.pattern('{BUILD_DIR}/$file'))
]; ];
@override @override
List<Source> get outputs => <Source>[ List<Source> get outputs => <Source>[
Source.pattern('{OUTPUT_DIR}/$outputFileName'), ...buildFiles.map((String file) => Source.pattern('{OUTPUT_DIR}/$file'))
if (isWasm) Source.pattern('{OUTPUT_DIR}/$wasmJSRuntimeFileName'),
]; ];
@override @override
List<String> get depfiles => const <String>[ List<String> get depfiles => const <String>[
'dart2js.d',
'flutter_assets.d', 'flutter_assets.d',
'web_resources.d', 'web_resources.d',
]; ];
bool shouldCopy(String name) =>
// Do not copy the deps file.
(name.contains(outputFileName) && !name.endsWith('.deps')) ||
(isWasm && name == wasmJSRuntimeFileName);
@override @override
Future<void> build(Environment environment) async { Future<void> build(Environment environment) async {
for (final File outputFile in environment.buildDir.listSync(recursive: true).whereType<File>()) { for (final File outputFile in environment.buildDir.listSync(recursive: true).whereType<File>()) {
final String basename = environment.fileSystem.path.basename(outputFile.path); final String basename = environment.fileSystem.path.basename(outputFile.path);
if (shouldCopy(basename)) { if (buildFiles.contains(basename)) {
outputFile.copySync( outputFile.copySync(
environment.outputDir.childFile(environment.fileSystem.path.basename(outputFile.path)).path 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); createVersionFile(environment, environment.defines);
final Directory outputDirectory = environment.outputDir.childDirectory('assets'); final Directory outputDirectory = environment.outputDir.childDirectory('assets');
outputDirectory.createSync(recursive: true); 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 // because it would need to be the hash for the entire bundle and not just the resource
// in question. // in question.
if (environment.fileSystem.path.basename(inputFile.path) == 'index.html') { if (environment.fileSystem.path.basename(inputFile.path) == 'index.html') {
final List<Map<String, Object?>> buildDescriptions = compileTargets.map(
(Dart2WebTarget target) => target.buildConfig
).toList();
final Map<String, Object?> buildConfig = <String, Object?>{
'engineRevision': globals.flutterVersion.engineRevision,
'builds': buildDescriptions,
};
final String buildConfigString = '_flutter.buildConfig = ${jsonEncode(buildConfig)};';
final IndexHtml indexHtml = IndexHtml(inputFile.readAsStringSync()); final IndexHtml indexHtml = IndexHtml(inputFile.readAsStringSync());
indexHtml.applySubstitutions( indexHtml.applySubstitutions(
baseHref: environment.defines[kBaseHref] ?? '/', baseHref: environment.defines[kBaseHref] ?? '/',
serviceWorkerVersion: Random().nextInt(4294967296).toString(), serviceWorkerVersion: Random().nextInt(4294967296).toString(),
buildConfig: buildConfigString,
); );
outputFile.writeAsStringSync(indexHtml.content); outputFile.writeAsStringSync(indexHtml.content);
continue; continue;
@ -456,11 +496,9 @@ class WebReleaseBundle extends Target {
/// These assets can be cached until a new version of the flutter web sdk is /// These assets can be cached until a new version of the flutter web sdk is
/// downloaded. /// downloaded.
class WebBuiltInAssets extends Target { class WebBuiltInAssets extends Target {
const WebBuiltInAssets(this.fileSystem, this.webRenderer, {required this.isWasm}); const WebBuiltInAssets(this.fileSystem);
final FileSystem fileSystem; final FileSystem fileSystem;
final WebRendererMode webRenderer;
final bool isWasm;
@override @override
String get name => 'web_static_assets'; String get name => 'web_static_assets';
@ -491,7 +529,6 @@ class WebBuiltInAssets extends Target {
@override @override
List<Source> get outputs => <Source>[ List<Source> get outputs => <Source>[
if (isWasm) const Source.pattern('{BUILD_DIR}/main.dart.js'),
const Source.pattern('{BUILD_DIR}/flutter.js'), const Source.pattern('{BUILD_DIR}/flutter.js'),
for (final File file in _canvasKitFiles) for (final File file in _canvasKitFiles)
Source.pattern('{BUILD_DIR}/canvaskit/${_filePathRelativeToCanvasKitDirectory(file)}'), Source.pattern('{BUILD_DIR}/canvaskit/${_filePathRelativeToCanvasKitDirectory(file)}'),
@ -505,13 +542,6 @@ class WebBuiltInAssets extends Target {
file.copySync(targetPath); 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 // Write the flutter.js file
final String flutterJsOut = fileSystem.path.join(environment.outputDir.path, 'flutter.js'); final String flutterJsOut = fileSystem.path.join(environment.outputDir.path, 'flutter.js');
final File flutterJsFile = fileSystem.file(fileSystem.path.join( final File flutterJsFile = fileSystem.file(fileSystem.path.join(
@ -524,20 +554,18 @@ class WebBuiltInAssets extends Target {
/// Generate a service worker for a web target. /// Generate a service worker for a web target.
class WebServiceWorker extends Target { class WebServiceWorker extends Target {
const WebServiceWorker(this.fileSystem, this.webRenderer, {required this.isWasm}); const WebServiceWorker(this.fileSystem, this.compileConfigs);
final FileSystem fileSystem; final FileSystem fileSystem;
final WebRendererMode webRenderer; final List<WebCompilerConfig> compileConfigs;
final bool isWasm;
@override @override
String get name => 'web_service_worker'; String get name => 'web_service_worker';
@override @override
List<Target> get dependencies => <Target>[ List<Target> get dependencies => <Target>[
if (isWasm) Dart2WasmTarget(webRenderer) else Dart2JSTarget(webRenderer), WebReleaseBundle(compileConfigs),
WebReleaseBundle(webRenderer, isWasm: isWasm), WebBuiltInAssets(fileSystem),
WebBuiltInAssets(fileSystem, webRenderer, isWasm: isWasm),
]; ];
@override @override
@ -592,6 +620,10 @@ class WebServiceWorker extends Target {
urlToHash, urlToHash,
<String>[ <String>[
'main.dart.js', 'main.dart.js',
if (compileConfigs.any((WebCompilerConfig config) => config is WasmCompilerConfig)) ...<String>[
'main.dart.wasm',
'main.dart.mjs',
],
'index.html', 'index.html',
if (urlToHash.containsKey('assets/AssetManifest.bin.json')) if (urlToHash.containsKey('assets/AssetManifest.bin.json'))
'assets/AssetManifest.bin.json', 'assets/AssetManifest.bin.json',

View file

@ -138,24 +138,50 @@ class BuildWebCommand extends BuildSubCommand {
throwToolExit('"build web" is not currently supported. To enable, run "flutter config --enable-web".'); throwToolExit('"build web" is not currently supported. To enable, run "flutter config --enable-web".');
} }
final WebCompilerConfig compilerConfig; final List<WebCompilerConfig> compilerConfigs;
if (boolArg('wasm')) { if (boolArg('wasm')) {
if (!featureFlags.isFlutterWebWasmEnabled) { if (!featureFlags.isFlutterWebWasmEnabled) {
throwToolExit('Compiling to WebAssembly (wasm) is only available on the master channel.'); throwToolExit('Compiling to WebAssembly (wasm) is only available on the master channel.');
} }
compilerConfig = WasmCompilerConfig( if (stringArg(FlutterOptions.kWebRendererFlag) != argParser.defaultFor(FlutterOptions.kWebRendererFlag)) {
omitTypeChecks: boolArg('omit-type-checks'), throwToolExit('"--${FlutterOptions.kWebRendererFlag}" cannot be combined with "--${FlutterOptions.kWebWasmFlag}"');
wasmOpt: WasmOptLevel.values.byName(stringArg('wasm-opt')!), }
globals.logger.printBox(
title: 'Experimental feature',
'''
WebAssembly compilation is experimental.
$kWasmMoreInfo''',
); );
compilerConfigs = <WebCompilerConfig>[
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 { } else {
compilerConfig = JsCompilerConfig( WebRendererMode webRenderer = WebRendererMode.auto;
if (argParser.options.containsKey(FlutterOptions.kWebRendererFlag)) {
webRenderer = WebRendererMode.values.byName(stringArg(FlutterOptions.kWebRendererFlag)!);
}
compilerConfigs = <WebCompilerConfig>[JsCompilerConfig(
csp: boolArg('csp'), csp: boolArg('csp'),
optimizationLevel: stringArg('dart2js-optimization') ?? JsCompilerConfig.kDart2jsDefaultOptimizationLevel, optimizationLevel: stringArg('dart2js-optimization') ?? JsCompilerConfig.kDart2jsDefaultOptimizationLevel,
dumpInfo: boolArg('dump-info'), dumpInfo: boolArg('dump-info'),
nativeNullAssertions: boolArg('native-null-assertions'), nativeNullAssertions: boolArg('native-null-assertions'),
noFrequencyBasedMinification: boolArg('no-frequency-based-minification'), noFrequencyBasedMinification: boolArg('no-frequency-based-minification'),
sourceMaps: boolArg('source-maps'), sourceMaps: boolArg('source-maps'),
); renderer: webRenderer,
)];
} }
final FlutterProject flutterProject = FlutterProject.current(); final FlutterProject flutterProject = FlutterProject.current();
@ -205,7 +231,7 @@ class BuildWebCommand extends BuildSubCommand {
target, target,
buildInfo, buildInfo,
ServiceWorkerStrategy.fromCliName(stringArg('pwa-strategy')), ServiceWorkerStrategy.fromCliName(stringArg('pwa-strategy')),
compilerConfig: compilerConfig, compilerConfigs: compilerConfigs,
baseHref: baseHref, baseHref: baseHref,
outputDirectoryPath: outputDirectoryPath, outputDirectoryPath: outputDirectoryPath,
); );

View file

@ -29,6 +29,7 @@ import '../runner/flutter_command.dart';
import '../runner/flutter_command_runner.dart'; import '../runner/flutter_command_runner.dart';
import '../tracing.dart'; import '../tracing.dart';
import '../vmservice.dart'; import '../vmservice.dart';
import '../web/compile.dart';
import '../web/web_runner.dart'; import '../web/web_runner.dart';
import 'daemon.dart'; import 'daemon.dart';
@ -241,6 +242,10 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
final Map<String, String> webHeaders = featureFlags.isWebEnabled final Map<String, String> webHeaders = featureFlags.isWebEnabled
? extractWebHeaders() ? extractWebHeaders()
: const <String, String>{}; : const <String, String>{};
final String? webRendererString = stringArg('web-renderer');
final WebRendererMode webRenderer = (webRendererString != null)
? WebRendererMode.values.byName(webRendererString)
: WebRendererMode.auto;
if (buildInfo.mode.isRelease) { if (buildInfo.mode.isRelease) {
return DebuggingOptions.disabled( return DebuggingOptions.disabled(
@ -258,6 +263,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
webBrowserDebugPort: webBrowserDebugPort, webBrowserDebugPort: webBrowserDebugPort,
webBrowserFlags: webBrowserFlags, webBrowserFlags: webBrowserFlags,
webHeaders: webHeaders, webHeaders: webHeaders,
webRenderer: webRenderer,
enableImpeller: enableImpeller, enableImpeller: enableImpeller,
enableVulkanValidation: enableVulkanValidation, enableVulkanValidation: enableVulkanValidation,
uninstallFirst: uninstallFirst, uninstallFirst: uninstallFirst,
@ -307,6 +313,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
webEnableExpressionEvaluation: featureFlags.isWebEnabled && boolArg('web-enable-expression-evaluation'), webEnableExpressionEvaluation: featureFlags.isWebEnabled && boolArg('web-enable-expression-evaluation'),
webLaunchUrl: featureFlags.isWebEnabled ? stringArg('web-launch-url') : null, webLaunchUrl: featureFlags.isWebEnabled ? stringArg('web-launch-url') : null,
webHeaders: webHeaders, webHeaders: webHeaders,
webRenderer: webRenderer,
vmserviceOutFile: stringArg('vmservice-out-file'), vmserviceOutFile: stringArg('vmservice-out-file'),
fastStart: argParser.options.containsKey('fast-start') fastStart: argParser.options.containsKey('fast-start')
&& boolArg('fast-start') && boolArg('fast-start')

View file

@ -22,6 +22,7 @@ import '../test/runner.dart';
import '../test/test_time_recorder.dart'; import '../test/test_time_recorder.dart';
import '../test/test_wrapper.dart'; import '../test/test_wrapper.dart';
import '../test/watcher.dart'; import '../test/watcher.dart';
import '../web/compile.dart';
/// The name of the directory where Integration Tests are placed. /// 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( final DebuggingOptions debuggingOptions = DebuggingOptions.enabled(
buildInfo, buildInfo,
startPaused: startPaused, startPaused: startPaused,
@ -369,6 +374,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
usingCISystem: usingCISystem, usingCISystem: usingCISystem,
enableImpeller: ImpellerStatus.fromBool(argResults!['enable-impeller'] as bool?), enableImpeller: ImpellerStatus.fromBool(argResults!['enable-impeller'] as bool?),
debugLogsDirectoryPath: debugLogsDirectoryPath, debugLogsDirectoryPath: debugLogsDirectoryPath,
webRenderer: webRenderer,
); );
String? testAssetDirectory; String? testAssetDirectory;

View file

@ -18,6 +18,7 @@ import 'devfs.dart';
import 'device_port_forwarder.dart'; import 'device_port_forwarder.dart';
import 'project.dart'; import 'project.dart';
import 'vmservice.dart'; import 'vmservice.dart';
import 'web/compile.dart';
DeviceManager? get deviceManager => context.get<DeviceManager>(); DeviceManager? get deviceManager => context.get<DeviceManager>();
@ -952,6 +953,7 @@ class DebuggingOptions {
this.webEnableExpressionEvaluation = false, this.webEnableExpressionEvaluation = false,
this.webHeaders = const <String, String>{}, this.webHeaders = const <String, String>{},
this.webLaunchUrl, this.webLaunchUrl,
this.webRenderer = WebRendererMode.auto,
this.vmserviceOutFile, this.vmserviceOutFile,
this.fastStart = false, this.fastStart = false,
this.nullAssertions = false, this.nullAssertions = false,
@ -981,6 +983,7 @@ class DebuggingOptions {
this.webBrowserFlags = const <String>[], this.webBrowserFlags = const <String>[],
this.webLaunchUrl, this.webLaunchUrl,
this.webHeaders = const <String, String>{}, this.webHeaders = const <String, String>{},
this.webRenderer = WebRendererMode.auto,
this.cacheSkSL = false, this.cacheSkSL = false,
this.traceAllowlist, this.traceAllowlist,
this.enableImpeller = ImpellerStatus.platformDefault, this.enableImpeller = ImpellerStatus.platformDefault,
@ -1060,6 +1063,7 @@ class DebuggingOptions {
required this.webEnableExpressionEvaluation, required this.webEnableExpressionEvaluation,
required this.webHeaders, required this.webHeaders,
required this.webLaunchUrl, required this.webLaunchUrl,
required this.webRenderer,
required this.vmserviceOutFile, required this.vmserviceOutFile,
required this.fastStart, required this.fastStart,
required this.nullAssertions, required this.nullAssertions,
@ -1144,6 +1148,9 @@ class DebuggingOptions {
/// Allow developers to add custom headers to web server /// Allow developers to add custom headers to web server
final Map<String, String> webHeaders; final Map<String, String> 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. /// A file where the VM Service URL should be written after the application is started.
final String? vmserviceOutFile; final String? vmserviceOutFile;
final bool fastStart; final bool fastStart;
@ -1252,6 +1259,7 @@ class DebuggingOptions {
'webEnableExpressionEvaluation': webEnableExpressionEvaluation, 'webEnableExpressionEvaluation': webEnableExpressionEvaluation,
'webLaunchUrl': webLaunchUrl, 'webLaunchUrl': webLaunchUrl,
'webHeaders': webHeaders, 'webHeaders': webHeaders,
'webRenderer': webRenderer.name,
'vmserviceOutFile': vmserviceOutFile, 'vmserviceOutFile': vmserviceOutFile,
'fastStart': fastStart, 'fastStart': fastStart,
'nullAssertions': nullAssertions, 'nullAssertions': nullAssertions,
@ -1307,6 +1315,7 @@ class DebuggingOptions {
webEnableExpressionEvaluation: json['webEnableExpressionEvaluation']! as bool, webEnableExpressionEvaluation: json['webEnableExpressionEvaluation']! as bool,
webHeaders: (json['webHeaders']! as Map<dynamic, dynamic>).cast<String, String>(), webHeaders: (json['webHeaders']! as Map<dynamic, dynamic>).cast<String, String>(),
webLaunchUrl: json['webLaunchUrl'] as String?, webLaunchUrl: json['webLaunchUrl'] as String?,
webRenderer: WebRendererMode.values.byName(json['webRenderer']! as String),
vmserviceOutFile: json['vmserviceOutFile'] as String?, vmserviceOutFile: json['vmserviceOutFile'] as String?,
fastStart: json['fastStart']! as bool, fastStart: json['fastStart']! as bool,
nullAssertions: json['nullAssertions']! as bool, nullAssertions: json['nullAssertions']! as bool,

View file

@ -78,12 +78,14 @@ class WebDriverService extends DriverService {
buildInfo, buildInfo,
port: debuggingOptions.port, port: debuggingOptions.port,
hostname: debuggingOptions.hostname, hostname: debuggingOptions.hostname,
webRenderer: debuggingOptions.webRenderer,
) )
: DebuggingOptions.enabled( : DebuggingOptions.enabled(
buildInfo, buildInfo,
port: debuggingOptions.port, port: debuggingOptions.port,
hostname: debuggingOptions.hostname, hostname: debuggingOptions.hostname,
disablePortPublication: debuggingOptions.disablePortPublication, disablePortPublication: debuggingOptions.disablePortPublication,
webRenderer: debuggingOptions.webRenderer,
), ),
stayResident: true, stayResident: true,
flutterProject: FlutterProject.current(), flutterProject: FlutterProject.current(),

View file

@ -62,6 +62,7 @@ class IndexHtml {
void applySubstitutions({ void applySubstitutions({
required String baseHref, required String baseHref,
required String? serviceWorkerVersion, required String? serviceWorkerVersion,
String? buildConfig,
}) { }) {
if (_content.contains(kBaseHrefPlaceholder)) { if (_content.contains(kBaseHrefPlaceholder)) {
_content = _content.replaceAll(kBaseHrefPlaceholder, baseHref); _content = _content.replaceAll(kBaseHrefPlaceholder, baseHref);
@ -81,6 +82,12 @@ class IndexHtml {
"navigator.serviceWorker.register('flutter_service_worker.js?v=$serviceWorkerVersion')", "navigator.serviceWorker.register('flutter_service_worker.js?v=$serviceWorkerVersion')",
); );
} }
if (buildConfig != null) {
_content = _content.replaceFirst(
'{{flutter_build_config}}',
buildConfig,
);
}
} }
} }

View file

@ -9,7 +9,7 @@ import '../build_system/targets/common.dart';
import '../build_system/targets/dart_plugin_registrant.dart'; import '../build_system/targets/dart_plugin_registrant.dart';
import '../build_system/targets/localizations.dart'; import '../build_system/targets/localizations.dart';
import '../build_system/targets/web.dart'; import '../build_system/targets/web.dart';
import '../web/compile.dart'; import '../web/compiler_config.dart';
class BuildTargetsImpl extends BuildTargets { class BuildTargetsImpl extends BuildTargets {
const BuildTargetsImpl(); const BuildTargetsImpl();
@ -27,9 +27,6 @@ class BuildTargetsImpl extends BuildTargets {
Target get dartPluginRegistrantTarget => const DartPluginRegistrantTarget(); Target get dartPluginRegistrantTarget => const DartPluginRegistrantTarget();
@override @override
Target webServiceWorker( Target webServiceWorker(FileSystem fileSystem, List<WebCompilerConfig> compileConfigs) =>
FileSystem fileSystem, { WebServiceWorker(fileSystem, compileConfigs);
required WebRendererMode webRenderer,
required bool isWasm,
}) => WebServiceWorker(fileSystem, webRenderer, isWasm: isWasm);
} }

View file

@ -117,8 +117,9 @@ class WebAssetServer implements AssetReader {
this.internetAddress, this.internetAddress,
this._modules, this._modules,
this._digests, this._digests,
this._nullSafetyMode, this._nullSafetyMode, {
) : basePath = _getIndexHtml().getBaseHref(); required this.webRenderer,
}) : basePath = _getIndexHtml().getBaseHref();
// Fallback to "application/octet-stream" on null which // Fallback to "application/octet-stream" on null which
// makes no claims as to the structure of the data. // makes no claims as to the structure of the data.
@ -177,6 +178,7 @@ class WebAssetServer implements AssetReader {
ExpressionCompiler? expressionCompiler, ExpressionCompiler? expressionCompiler,
Map<String, String> extraHeaders, Map<String, String> extraHeaders,
NullSafetyMode nullSafetyMode, { NullSafetyMode nullSafetyMode, {
required WebRendererMode webRenderer,
bool testMode = false, bool testMode = false,
DwdsLauncher dwdsLauncher = Dwds.start, DwdsLauncher dwdsLauncher = Dwds.start,
}) async { }) async {
@ -225,6 +227,7 @@ class WebAssetServer implements AssetReader {
modules, modules,
digests, digests,
nullSafetyMode, nullSafetyMode,
webRenderer: webRenderer,
); );
if (testMode) { if (testMode) {
return server; return server;
@ -504,16 +507,29 @@ class WebAssetServer implements AssetReader {
} }
/// Determines what rendering backed to use. /// Determines what rendering backed to use.
WebRendererMode webRenderer = WebRendererMode.html; final WebRendererMode webRenderer;
shelf.Response _serveIndex() { shelf.Response _serveIndex() {
final IndexHtml indexHtml = _getIndexHtml(); final IndexHtml indexHtml = _getIndexHtml();
final Map<String, dynamic> buildConfig = <String, dynamic>{
'engineRevision': globals.flutterVersion.engineRevision,
'builds': <dynamic>[
<String, dynamic>{
'compileTarget': 'dartdevc',
'renderer': webRenderer.name,
'mainJsPath': 'main.dart.js',
},
],
};
final String buildConfigString = '_flutter.buildConfig = ${jsonEncode(buildConfig)};';
indexHtml.applySubstitutions( indexHtml.applySubstitutions(
// Currently, we don't support --base-href for the "run" command. // Currently, we don't support --base-href for the "run" command.
baseHref: '/', baseHref: '/',
serviceWorkerVersion: null, serviceWorkerVersion: null,
buildConfig: buildConfigString,
); );
final Map<String, String> headers = <String, String>{ final Map<String, String> headers = <String, String>{
@ -663,6 +679,7 @@ class WebDevFS implements DevFS {
required this.nullAssertions, required this.nullAssertions,
required this.nativeNullAssertions, required this.nativeNullAssertions,
required this.nullSafetyMode, required this.nullSafetyMode,
required this.webRenderer,
this.testMode = false, this.testMode = false,
}) : _port = port; }) : _port = port;
@ -686,6 +703,7 @@ class WebDevFS implements DevFS {
final NullSafetyMode nullSafetyMode; final NullSafetyMode nullSafetyMode;
final String? tlsCertPath; final String? tlsCertPath;
final String? tlsCertKeyPath; final String? tlsCertKeyPath;
final WebRendererMode webRenderer;
late WebAssetServer webAssetServer; late WebAssetServer webAssetServer;
@ -785,15 +803,11 @@ class WebDevFS implements DevFS {
expressionCompiler, expressionCompiler,
extraHeaders, extraHeaders,
nullSafetyMode, nullSafetyMode,
webRenderer: webRenderer,
testMode: testMode, testMode: testMode,
); );
final int selectedPort = webAssetServer.selectedPort; 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'; String url = '$hostname:$selectedPort';
if (hostname == 'any') { if (hostname == 'any') {
url ='localhost:$selectedPort'; url ='localhost:$selectedPort';

View file

@ -309,6 +309,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
nullAssertions: debuggingOptions.nullAssertions, nullAssertions: debuggingOptions.nullAssertions,
nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode, nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode,
nativeNullAssertions: debuggingOptions.nativeNullAssertions, nativeNullAssertions: debuggingOptions.nativeNullAssertions,
webRenderer: debuggingOptions.webRenderer,
); );
Uri url = await device!.devFS!.create(); Uri url = await device!.devFS!.create();
if (debuggingOptions.tlsCertKeyPath != null && debuggingOptions.tlsCertPath != null) { 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, target,
debuggingOptions.buildInfo, debuggingOptions.buildInfo,
ServiceWorkerStrategy.none, ServiceWorkerStrategy.none,
compilerConfig: JsCompilerConfig.run(nativeNullAssertions: debuggingOptions.nativeNullAssertions) compilerConfigs: <WebCompilerConfig>[
JsCompilerConfig.run(
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
renderer: debuggingOptions.webRenderer,
)
]
); );
} }
await device!.device!.startApp( await device!.device!.startApp(
@ -418,7 +424,12 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
target, target,
debuggingOptions.buildInfo, debuggingOptions.buildInfo,
ServiceWorkerStrategy.none, ServiceWorkerStrategy.none,
compilerConfig: JsCompilerConfig.run(nativeNullAssertions: debuggingOptions.nativeNullAssertions), compilerConfigs: <WebCompilerConfig>[
JsCompilerConfig.run(
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
renderer: debuggingOptions.webRenderer,
)
],
); );
} on ToolExit { } on ToolExit {
return OperationResult(1, 'Failed to recompile application.'); return OperationResult(1, 'Failed to recompile application.');

View file

@ -135,7 +135,7 @@ class FlutterDevice {
} }
final String platformDillPath = globals.fs.path.join( final String platformDillPath = globals.fs.path.join(
getWebPlatformBinariesDirectory(globals.artifacts!, buildInfo.webRenderer).path, globals.artifacts!.getHostArtifact(HostArtifact.webPlatformKernelFolder).path,
platformDillName, platformDillName,
); );

View file

@ -1273,13 +1273,7 @@ abstract class FlutterCommand extends Command<void> {
: null; : null;
final Map<String, Object?> defineConfigJsonMap = extractDartDefineConfigJsonMap(); final Map<String, Object?> defineConfigJsonMap = extractDartDefineConfigJsonMap();
List<String> dartDefines = extractDartDefines(defineConfigJsonMap: defineConfigJsonMap); final List<String> 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);
}
if (argParser.options.containsKey(FlutterOptions.kWebResourcesCdnFlag)) { if (argParser.options.containsKey(FlutterOptions.kWebResourcesCdnFlag)) {
final bool hasLocalWebSdk = argParser.options.containsKey('local-web-sdk') && stringArg('local-web-sdk') != null; final bool hasLocalWebSdk = argParser.options.containsKey('local-web-sdk') && stringArg('local-web-sdk') != null;
@ -1327,7 +1321,6 @@ abstract class FlutterCommand extends Command<void> {
dartDefines: dartDefines, dartDefines: dartDefines,
bundleSkSLPath: bundleSkSLPath, bundleSkSLPath: bundleSkSLPath,
dartExperiments: experiments, dartExperiments: experiments,
webRenderer: webRenderer,
performanceMeasurementFile: performanceMeasurementFile, performanceMeasurementFile: performanceMeasurementFile,
packagesPath: packagesPath ?? globals.fs.path.absolute('.dart_tool', 'package_config.json'), packagesPath: packagesPath ?? globals.fs.path.absolute('.dart_tool', 'package_config.json'),
nullSafetyMode: nullSafetyMode, nullSafetyMode: nullSafetyMode,
@ -1566,19 +1559,6 @@ abstract class FlutterCommand extends Command<void> {
return jsonEncode(propertyMap); return jsonEncode(propertyMap);
} }
/// Updates dart-defines based on [webRenderer].
@visibleForTesting
static List<String> updateDartDefines(List<String> dartDefines, WebRendererMode webRenderer) {
final Set<String> 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<String, String> extractWebHeaders() { Map<String, String> extractWebHeaders() {
final Map<String, String> webHeaders = <String, String>{}; final Map<String, String> webHeaders = <String, String>{};

View file

@ -7,12 +7,12 @@ import 'dart:typed_data';
import 'package:async/async.dart'; import 'package:async/async.dart';
import 'package:http_multi_server/http_multi_server.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:package_config/package_config.dart';
import 'package:pool/pool.dart'; import 'package:pool/pool.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import 'package:shelf/shelf.dart' as shelf; import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as shelf_io; 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:shelf_web_socket/shelf_web_socket.dart';
import 'package:stream_channel/stream_channel.dart'; import 'package:stream_channel/stream_channel.dart';
import 'package:test_core/src/platform.dart'; // ignore: implementation_imports 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_compiler.dart';
import 'test_time_recorder.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: <String, String>{
if (contentType != null) 'Content-Type': contentType
},
);
};
}
class FlutterWebPlatform extends PlatformPlugin { class FlutterWebPlatform extends PlatformPlugin {
FlutterWebPlatform._(this._server, this._config, this._root, { FlutterWebPlatform._(this._server, this._config, this._root, {
FlutterProject? flutterProject, FlutterProject? flutterProject,
@ -46,31 +74,32 @@ class FlutterWebPlatform extends PlatformPlugin {
required this.buildInfo, required this.buildInfo,
required this.webMemoryFS, required this.webMemoryFS,
required FileSystem fileSystem, required FileSystem fileSystem,
required PackageConfig flutterToolPackageConfig, required File testDartJs,
required File testHostDartJs,
required ChromiumLauncher chromiumLauncher, required ChromiumLauncher chromiumLauncher,
required Logger logger, required Logger logger,
required Artifacts? artifacts, required Artifacts? artifacts,
required ProcessManager processManager, required ProcessManager processManager,
required this.webRenderer,
TestTimeRecorder? testTimeRecorder, TestTimeRecorder? testTimeRecorder,
}) : _fileSystem = fileSystem, }) : _fileSystem = fileSystem,
_flutterToolPackageConfig = flutterToolPackageConfig, _testDartJs = testDartJs,
_testHostDartJs = testHostDartJs,
_chromiumLauncher = chromiumLauncher, _chromiumLauncher = chromiumLauncher,
_logger = logger, _logger = logger,
_artifacts = artifacts { _artifacts = artifacts {
final shelf.Cascade cascade = shelf.Cascade() final shelf.Cascade cascade = shelf.Cascade()
.add(_webSocketHandler.handler) .add(_webSocketHandler.handler)
.add(createStaticHandler( .add(createDirectoryHandler(
fileSystem.path.join(Cache.flutterRoot!, 'packages', 'flutter_tools'), fileSystem.directory(fileSystem.path.join(Cache.flutterRoot!, 'packages', 'flutter_tools')),
serveFilesOutsidePath: true,
)) ))
.add(_handleStaticArtifact) .add(_handleStaticArtifact)
.add(_localCanvasKitHandler) .add(_localCanvasKitHandler)
.add(_goldenFileHandler) .add(_goldenFileHandler)
.add(_wrapperHandler) .add(_wrapperHandler)
.add(_handleTestRequest) .add(_handleTestRequest)
.add(createStaticHandler( .add(createDirectoryHandler(
fileSystem.path.join(fileSystem.currentDirectory.path, 'test'), fileSystem.directory(fileSystem.path.join(fileSystem.currentDirectory.path, 'test'))
serveFilesOutsidePath: true,
)) ))
.add(_packageFilesHandler); .add(_packageFilesHandler);
_server.mount(cascade.handler); _server.mount(cascade.handler);
@ -80,14 +109,15 @@ class FlutterWebPlatform extends PlatformPlugin {
fileSystem: _fileSystem, fileSystem: _fileSystem,
logger: _logger, logger: _logger,
processManager: processManager, processManager: processManager,
webRenderer: _rendererMode, webRenderer: webRenderer,
); );
} }
final WebMemoryFS webMemoryFS; final WebMemoryFS webMemoryFS;
final BuildInfo buildInfo; final BuildInfo buildInfo;
final FileSystem _fileSystem; final FileSystem _fileSystem;
final PackageConfig _flutterToolPackageConfig; final File _testDartJs;
final File _testHostDartJs;
final ChromiumLauncher _chromiumLauncher; final ChromiumLauncher _chromiumLauncher;
final Logger _logger; final Logger _logger;
final Artifacts? _artifacts; final Artifacts? _artifacts;
@ -96,6 +126,7 @@ class FlutterWebPlatform extends PlatformPlugin {
final OneOffHandler _webSocketHandler = OneOffHandler(); final OneOffHandler _webSocketHandler = OneOffHandler();
final AsyncMemoizer<void> _closeMemo = AsyncMemoizer<void>(); final AsyncMemoizer<void> _closeMemo = AsyncMemoizer<void>();
final String _root; final String _root;
final WebRendererMode webRenderer;
/// Allows only one test suite (typically one test file) to be loaded and run /// 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 /// 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; BrowserManager? _browserManager;
late TestGoldenComparator _testGoldenComparator; late TestGoldenComparator _testGoldenComparator;
static Future<shelf.Server> defaultServerFactory() async {
return shelf_io.IOServer(await HttpMultiServer.loopback(0));
}
static Future<FlutterWebPlatform> start(String root, { static Future<FlutterWebPlatform> start(String root, {
FlutterProject? flutterProject, FlutterProject? flutterProject,
String? shellPath, String? shellPath,
@ -118,19 +153,37 @@ class FlutterWebPlatform extends PlatformPlugin {
required ChromiumLauncher chromiumLauncher, required ChromiumLauncher chromiumLauncher,
required Artifacts? artifacts, required Artifacts? artifacts,
required ProcessManager processManager, required ProcessManager processManager,
required WebRendererMode webRenderer,
TestTimeRecorder? testTimeRecorder, TestTimeRecorder? testTimeRecorder,
Uri? testPackageUri,
Future<shelf.Server> Function() serverFactory = defaultServerFactory,
}) async { }) async {
final shelf_io.IOServer server = shelf_io.IOServer(await HttpMultiServer.loopback(0)); final shelf.Server server = await serverFactory();
final PackageConfig packageConfig = await loadPackageConfigWithLogging( if (testPackageUri == null) {
fileSystem.file(fileSystem.path.join( final PackageConfig packageConfig = await loadPackageConfigWithLogging(
Cache.flutterRoot!, fileSystem.file(fileSystem.path.join(
'packages', Cache.flutterRoot!,
'flutter_tools', 'packages',
'.dart_tool', 'flutter_tools',
'package_config.json', '.dart_tool',
)), 'package_config.json',
logger: logger, )),
); 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._( return FlutterWebPlatform._(
server, server,
Configuration.current.change(pauseAfterLoad: pauseAfterLoad), Configuration.current.change(pauseAfterLoad: pauseAfterLoad),
@ -140,28 +193,21 @@ class FlutterWebPlatform extends PlatformPlugin {
updateGoldens: updateGoldens, updateGoldens: updateGoldens,
buildInfo: buildInfo, buildInfo: buildInfo,
webMemoryFS: webMemoryFS, webMemoryFS: webMemoryFS,
flutterToolPackageConfig: packageConfig, testDartJs: testDartJs,
testHostDartJs: testHostDartJs,
fileSystem: fileSystem, fileSystem: fileSystem,
chromiumLauncher: chromiumLauncher, chromiumLauncher: chromiumLauncher,
artifacts: artifacts, artifacts: artifacts,
logger: logger, logger: logger,
nullAssertions: nullAssertions, nullAssertions: nullAssertions,
processManager: processManager, processManager: processManager,
webRenderer: webRenderer,
testTimeRecorder: testTimeRecorder, testTimeRecorder: testTimeRecorder,
); );
} }
bool get _closed => _closeMemo.hasRun; 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 { NullSafetyMode get _nullSafetyMode {
return buildInfo.nullSafetyMode == NullSafetyMode.sound return buildInfo.nullSafetyMode == NullSafetyMode.sound
? NullSafetyMode.sound ? NullSafetyMode.sound
@ -200,25 +246,10 @@ class FlutterWebPlatform extends PlatformPlugin {
)); ));
File get _dartSdk => _fileSystem.file( File get _dartSdk => _fileSystem.file(
_artifacts!.getHostArtifact(kDartSdkJsArtifactMap[_rendererMode]![_nullSafetyMode]!)); _artifacts!.getHostArtifact(kDartSdkJsArtifactMap[webRenderer]![_nullSafetyMode]!));
File get _dartSdkSourcemaps => _fileSystem.file( File get _dartSdkSourcemaps => _fileSystem.file(
_artifacts!.getHostArtifact(kDartSdkJsMapArtifactMap[_rendererMode]![_nullSafetyMode]!)); _artifacts!.getHostArtifact(kDartSdkJsMapArtifactMap[webRenderer]![_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',
));
File _canvasKitFile(String relativePath) { File _canvasKitFile(String relativePath) {
final String canvasKitPath = _fileSystem.path.join( final String canvasKitPath = _fileSystem.path.join(
@ -314,7 +345,7 @@ class FlutterWebPlatform extends PlatformPlugin {
if (fileUri != null) { if (fileUri != null) {
final String dirname = _fileSystem.path.dirname(fileUri.toFilePath()); final String dirname = _fileSystem.path.dirname(fileUri.toFilePath());
final String basename = _fileSystem.path.basename(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( final shelf.Request modifiedRequest = shelf.Request(
request.method, request.method,
request.requestedUri.replace(path: basename), request.requestedUri.replace(path: basename),

View file

@ -151,6 +151,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
testOutputDir: tempBuildDir, testOutputDir: tempBuildDir,
testFiles: testFiles.map((Uri uri) => uri.toFilePath()).toList(), testFiles: testFiles.map((Uri uri) => uri.toFilePath()).toList(),
buildInfo: debuggingOptions.buildInfo, buildInfo: debuggingOptions.buildInfo,
webRenderer: debuggingOptions.webRenderer,
); );
testArgs testArgs
..add('--platform=chrome') ..add('--platform=chrome')
@ -181,6 +182,7 @@ class _FlutterTestRunnerImpl implements FlutterTestRunner {
logger: globals.logger, logger: globals.logger,
), ),
testTimeRecorder: testTimeRecorder, testTimeRecorder: testTimeRecorder,
webRenderer: debuggingOptions.webRenderer,
); );
}, },
); );

View file

@ -17,6 +17,7 @@ import '../cache.dart';
import '../compile.dart'; import '../compile.dart';
import '../dart/language_version.dart'; import '../dart/language_version.dart';
import '../web/bootstrap.dart'; import '../web/bootstrap.dart';
import '../web/compile.dart';
import '../web/memory_fs.dart'; import '../web/memory_fs.dart';
import 'test_config.dart'; import 'test_config.dart';
@ -48,6 +49,7 @@ class WebTestCompiler {
required String testOutputDir, required String testOutputDir,
required List<String> testFiles, required List<String> testFiles,
required BuildInfo buildInfo, required BuildInfo buildInfo,
required WebRendererMode webRenderer,
}) async { }) async {
LanguageVersion languageVersion = LanguageVersion(2, 8); LanguageVersion languageVersion = LanguageVersion(2, 8);
late final String platformDillName; late final String platformDillName;
@ -69,7 +71,7 @@ class WebTestCompiler {
} }
final String platformDillPath = _fileSystem.path.join( final String platformDillPath = _fileSystem.path.join(
getWebPlatformBinariesDirectory(_artifacts, buildInfo.webRenderer).path, _artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder).path,
platformDillName platformDillName
); );
@ -109,6 +111,7 @@ class WebTestCompiler {
fileSystem: _fileSystem, fileSystem: _fileSystem,
config: _config, config: _config,
); );
final List<String> dartDefines = webRenderer.updateDartDefines(buildInfo.dartDefines);
final ResidentCompiler residentCompiler = ResidentCompiler( final ResidentCompiler residentCompiler = ResidentCompiler(
_artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path, _artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path,
buildMode: buildInfo.mode, buildMode: buildInfo.mode,
@ -124,7 +127,7 @@ class WebTestCompiler {
targetModel: TargetModel.dartdevc, targetModel: TargetModel.dartdevc,
extraFrontEndOptions: extraFrontEndOptions, extraFrontEndOptions: extraFrontEndOptions,
platformDill: _fileSystem.file(platformDillPath).absolute.uri.toString(), platformDill: _fileSystem.file(platformDillPath).absolute.uri.toString(),
dartDefines: buildInfo.dartDefines, dartDefines: dartDefines,
librariesSpec: _artifacts.getHostArtifact(HostArtifact.flutterWebLibrariesJson).uri.toString(), librariesSpec: _artifacts.getHostArtifact(HostArtifact.flutterWebLibrariesJson).uri.toString(),
packagesPath: buildInfo.packagesPath, packagesPath: buildInfo.packagesPath,
artifacts: _artifacts, artifacts: _artifacts,

View file

@ -24,7 +24,6 @@ import '../version.dart';
import 'compiler_config.dart'; import 'compiler_config.dart';
import 'file_generators/flutter_service_worker_js.dart'; import 'file_generators/flutter_service_worker_js.dart';
import 'migrations/scrub_generated_plugin_registrant.dart'; import 'migrations/scrub_generated_plugin_registrant.dart';
import 'web_constants.dart';
export 'compiler_config.dart'; export 'compiler_config.dart';
@ -67,23 +66,14 @@ class WebBuilder {
String target, String target,
BuildInfo buildInfo, BuildInfo buildInfo,
ServiceWorkerStrategy serviceWorkerStrategy, { ServiceWorkerStrategy serviceWorkerStrategy, {
required WebCompilerConfig compilerConfig, required List<WebCompilerConfig> compilerConfigs,
String? baseHref, String? baseHref,
String? outputDirectoryPath, String? outputDirectoryPath,
}) async { }) async {
if (compilerConfig.isWasm) {
globals.logger.printBox(
title: 'Experimental feature',
'''
WebAssembly compilation is experimental.
$kWasmMoreInfo''',
);
}
final bool hasWebPlugins = final bool hasWebPlugins =
(await findPlugins(flutterProject)).any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey)); (await findPlugins(flutterProject)).any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey));
final Directory outputDirectory = outputDirectoryPath == null final Directory outputDirectory = outputDirectoryPath == null
? _fileSystem.directory(getWebBuildDirectory(compilerConfig.isWasm)) ? _fileSystem.directory(getWebBuildDirectory())
: _fileSystem.directory(outputDirectoryPath); : _fileSystem.directory(outputDirectoryPath);
outputDirectory.createSync(recursive: true); outputDirectory.createSync(recursive: true);
@ -99,11 +89,7 @@ class WebBuilder {
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
try { try {
final BuildResult result = await _buildSystem.build( final BuildResult result = await _buildSystem.build(
globals.buildTargets.webServiceWorker( globals.buildTargets.webServiceWorker(_fileSystem, compilerConfigs),
_fileSystem,
webRenderer: buildInfo.webRenderer,
isWasm: compilerConfig.isWasm,
),
Environment( Environment(
projectDir: _fileSystem.currentDirectory, projectDir: _fileSystem.currentDirectory,
outputDir: outputDirectory, outputDir: outputDirectory,
@ -113,7 +99,6 @@ class WebBuilder {
kHasWebPlugins: hasWebPlugins.toString(), kHasWebPlugins: hasWebPlugins.toString(),
if (baseHref != null) kBaseHref: baseHref, if (baseHref != null) kBaseHref: baseHref,
kServiceWorkerStrategy: serviceWorkerStrategy.cliName, kServiceWorkerStrategy: serviceWorkerStrategy.cliName,
...compilerConfig.toBuildSystemEnvironment(),
...buildInfo.toBuildSystemEnvironment(), ...buildInfo.toBuildSystemEnvironment(),
}, },
artifacts: globals.artifacts!, artifacts: globals.artifacts!,
@ -146,8 +131,7 @@ class WebBuilder {
} }
final String buildSettingsString = _buildEventAnalyticsSettings( final String buildSettingsString = _buildEventAnalyticsSettings(
config: compilerConfig, configs: compilerConfigs,
buildInfo: buildInfo,
); );
BuildEvent( BuildEvent(
@ -163,14 +147,15 @@ class WebBuilder {
)); ));
final Duration elapsedDuration = sw.elapsed; final Duration elapsedDuration = sw.elapsed;
final String variableName = compilerConfigs.length > 1 ? 'dual-compile' : 'dart2js';
_flutterUsage.sendTiming( _flutterUsage.sendTiming(
'build', 'build',
compilerConfig.isWasm ? 'dart2wasm' : 'dart2js', variableName,
elapsedDuration, elapsedDuration,
); );
_analytics.send(Event.timing( _analytics.send(Event.timing(
workflow: 'build', workflow: 'build',
variableName: compilerConfig.isWasm ? 'dart2wasm' : 'dart2js', variableName: variableName,
elapsedMilliseconds: elapsedDuration.inMilliseconds, elapsedMilliseconds: elapsedDuration.inMilliseconds,
)); ));
} }
@ -222,6 +207,16 @@ enum WebRendererMode implements CliEnum {
'FLUTTER_WEB_USE_SKWASM=true', 'FLUTTER_WEB_USE_SKWASM=true',
] ]
}; };
List<String> updateDartDefines(List<String> inputDefines) {
final Set<String> 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. /// The correct precompiled artifact to use for each build and render mode.
@ -257,13 +252,18 @@ const Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> kDartSdkJsMapArtif
}; };
String _buildEventAnalyticsSettings({ String _buildEventAnalyticsSettings({
required WebCompilerConfig config, required List<WebCompilerConfig> configs,
required BuildInfo buildInfo,
}) { }) {
final Map<String, Object> values = <String, Object>{ final Map<String, Object> values = <String, Object>{};
...config.buildEventAnalyticsValues, final List<String> renderers = <String>[];
'web-renderer': buildInfo.webRenderer.cliName, final List<String> targets = <String>[];
}; 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<String> sortedList = values.entries final List<String> sortedList = values.entries
.map((MapEntry<String, Object> e) => '${e.key}: ${e.value};') .map((MapEntry<String, Object> e) => '${e.key}: ${e.value};')

View file

@ -3,58 +3,48 @@
// found in the LICENSE file. // found in the LICENSE file.
import '../base/utils.dart'; import '../base/utils.dart';
import '../convert.dart';
import 'compile.dart';
abstract class WebCompilerConfig { enum CompileTarget {
const WebCompilerConfig(); js,
wasm,
}
/// Returns `true` if `this` represents configuration for the Wasm compiler. sealed class WebCompilerConfig {
/// const WebCompilerConfig({required this.renderer});
/// Otherwise, `false`represents the JavaScript compiler.
bool get isWasm;
Map<String, String> toBuildSystemEnvironment(); /// Returns which target this compiler outputs (js or wasm)
CompileTarget get compileTarget;
final WebRendererMode renderer;
Map<String, Object> get buildEventAnalyticsValues => <String, Object>{ String get buildKey;
'wasm-compile': isWasm,
}; Map<String, Object> get buildEventAnalyticsValues => <String, Object>{};
} }
/// Configuration for the Dart-to-Javascript compiler (dart2js). /// Configuration for the Dart-to-Javascript compiler (dart2js).
class JsCompilerConfig extends WebCompilerConfig { class JsCompilerConfig extends WebCompilerConfig {
const JsCompilerConfig({ const JsCompilerConfig({
required this.csp, this.csp = false,
required this.dumpInfo, this.dumpInfo = false,
required this.nativeNullAssertions, this.nativeNullAssertions = false,
required this.optimizationLevel, this.optimizationLevel = kDart2jsDefaultOptimizationLevel,
required this.noFrequencyBasedMinification, this.noFrequencyBasedMinification = false,
required this.sourceMaps, this.sourceMaps = true,
super.renderer = WebRendererMode.auto,
}); });
/// Instantiates [JsCompilerConfig] suitable for the `flutter run` command. /// Instantiates [JsCompilerConfig] suitable for the `flutter run` command.
const JsCompilerConfig.run({required bool nativeNullAssertions}) const JsCompilerConfig.run({
: this( required bool nativeNullAssertions,
csp: false, required WebRendererMode renderer,
dumpInfo: false, }) : this(
nativeNullAssertions: nativeNullAssertions, nativeNullAssertions: nativeNullAssertions,
noFrequencyBasedMinification: false,
optimizationLevel: kDart2jsDefaultOptimizationLevel, optimizationLevel: kDart2jsDefaultOptimizationLevel,
sourceMaps: true, renderer: renderer,
); );
/// Creates a new [JsCompilerConfig] from build system environment values.
///
/// Should correspond exactly with [toBuildSystemEnvironment].
factory JsCompilerConfig.fromBuildSystemEnvironment(
Map<String, String> 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. /// The default optimization level for dart2js.
/// ///
/// Maps to [kDart2jsOptimization]. /// Maps to [kDart2jsOptimization].
@ -102,17 +92,7 @@ class JsCompilerConfig extends WebCompilerConfig {
final bool sourceMaps; final bool sourceMaps;
@override @override
bool get isWasm => false; CompileTarget get compileTarget => CompileTarget.js;
@override
Map<String, String> toBuildSystemEnvironment() => <String, String>{
kCspMode: csp.toString(),
kDart2jsDumpInfo: dumpInfo.toString(),
kNativeNullAssertions: nativeNullAssertions.toString(),
kDart2jsNoFrequencyBasedMinification: noFrequencyBasedMinification.toString(),
kDart2jsOptimization: optimizationLevel,
kSourceMapsEnabled: sourceMaps.toString(),
};
/// Arguments to use in both phases: full JS compile and CFE-only. /// Arguments to use in both phases: full JS compile and CFE-only.
List<String> toSharedCommandOptions() => <String>[ List<String> toSharedCommandOptions() => <String>[
@ -130,25 +110,29 @@ class JsCompilerConfig extends WebCompilerConfig {
if (noFrequencyBasedMinification) '--no-frequency-based-minification', if (noFrequencyBasedMinification) '--no-frequency-based-minification',
if (csp) '--csp', if (csp) '--csp',
]; ];
@override
String get buildKey {
final Map<String, dynamic> settings = <String, dynamic>{
'csp': csp,
'dumpInfo': dumpInfo,
'nativeNullAssertions': nativeNullAssertions,
'noFrequencyBasedMinification': noFrequencyBasedMinification,
'optimizationLevel': optimizationLevel,
'sourceMaps': sourceMaps,
};
return jsonEncode(settings);
}
} }
/// Configuration for the Wasm compiler. /// Configuration for the Wasm compiler.
class WasmCompilerConfig extends WebCompilerConfig { class WasmCompilerConfig extends WebCompilerConfig {
const WasmCompilerConfig({ const WasmCompilerConfig({
required this.omitTypeChecks, this.omitTypeChecks = false,
required this.wasmOpt, 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<String, String> defines) =>
WasmCompilerConfig(
omitTypeChecks: defines[kOmitTypeChecks] == 'true',
wasmOpt: WasmOptLevel.values.byName(defines[kRunWasmOpt]!),
);
/// Build environment for [omitTypeChecks]. /// Build environment for [omitTypeChecks].
static const String kOmitTypeChecks = 'WasmOmitTypeChecks'; static const String kOmitTypeChecks = 'WasmOmitTypeChecks';
@ -162,25 +146,31 @@ class WasmCompilerConfig extends WebCompilerConfig {
final WasmOptLevel wasmOpt; final WasmOptLevel wasmOpt;
@override @override
bool get isWasm => true; CompileTarget get compileTarget => CompileTarget.wasm;
bool get runWasmOpt => wasmOpt == WasmOptLevel.full || wasmOpt == WasmOptLevel.debug; bool get runWasmOpt =>
wasmOpt == WasmOptLevel.full || wasmOpt == WasmOptLevel.debug;
@override
Map<String, String> toBuildSystemEnvironment() => <String, String>{
kOmitTypeChecks: omitTypeChecks.toString(),
kRunWasmOpt: wasmOpt.name,
};
List<String> toCommandOptions() => <String>[ List<String> toCommandOptions() => <String>[
if (omitTypeChecks) '--omit-type-checks', if (omitTypeChecks) '--omit-type-checks',
]; ];
@override @override
Map<String, Object> get buildEventAnalyticsValues => <String, Object>{ Map<String, Object> get buildEventAnalyticsValues => <String, Object>{
...super.buildEventAnalyticsValues, ...super.buildEventAnalyticsValues,
...toBuildSystemEnvironment(), kOmitTypeChecks: omitTypeChecks.toString(),
}; kRunWasmOpt: wasmOpt.name,
};
@override
String get buildKey {
final Map<String, dynamic> settings = <String, dynamic>{
'omitTypeChecks': omitTypeChecks,
'wasmOpt': wasmOpt.name,
};
return jsonEncode(settings);
}
} }
enum WasmOptLevel implements CliEnum { enum WasmOptLevel implements CliEnum {
@ -195,8 +185,11 @@ enum WasmOptLevel implements CliEnum {
@override @override
String get helpText => switch (this) { String get helpText => switch (this) {
WasmOptLevel.none => 'wasm-opt is not run. Fastest build; bigger, slower output.', WasmOptLevel.none =>
WasmOptLevel.debug => 'Similar to `${WasmOptLevel.full.name}`, but member names are preserved. Debugging is easier, but size is a bit bigger.', 'wasm-opt is not run. Fastest build; bigger, slower output.',
WasmOptLevel.full => 'wasm-opt is run. Build time is slower, but output is smaller and faster.', 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.',
};
} }

View file

@ -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/base/process.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/web.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart'; import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/commands/build_web.dart'; import 'package:flutter_tools/src/commands/build_web.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/web/compile.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
@ -147,15 +149,9 @@ void main() {
expect(environment.defines, <String, String>{ expect(environment.defines, <String, String>{
'TargetFile': 'lib/main.dart', 'TargetFile': 'lib/main.dart',
'HasWebPlugins': 'true', 'HasWebPlugins': 'true',
'cspMode': 'false',
'SourceMaps': 'false',
'NativeNullAssertions': 'true',
'ServiceWorkerStrategy': 'offline-first', 'ServiceWorkerStrategy': 'offline-first',
'Dart2jsDumpInfo': 'false',
'Dart2jsNoFrequencyBasedMinification': 'false',
'Dart2jsOptimization': 'O3',
'BuildMode': 'release', 'BuildMode': 'release',
'DartDefines': 'Zm9vPWE=,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==', 'DartDefines': 'Zm9vPWE=',
'DartObfuscation': 'false', 'DartObfuscation': 'false',
'TrackWidgetCreation': 'false', 'TrackWidgetCreation': 'false',
'TreeShakeIcons': 'true', 'TreeShakeIcons': 'true',
@ -249,15 +245,8 @@ void main() {
expect(environment.defines, <String, String>{ expect(environment.defines, <String, String>{
'TargetFile': 'lib/main.dart', 'TargetFile': 'lib/main.dart',
'HasWebPlugins': 'true', 'HasWebPlugins': 'true',
'cspMode': 'false',
'SourceMaps': 'false',
'NativeNullAssertions': 'true',
'ServiceWorkerStrategy': 'offline-first', 'ServiceWorkerStrategy': 'offline-first',
'Dart2jsDumpInfo': 'false',
'Dart2jsNoFrequencyBasedMinification': 'false',
'Dart2jsOptimization': 'O4',
'BuildMode': 'release', 'BuildMode': 'release',
'DartDefines': 'RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==',
'DartObfuscation': 'false', 'DartObfuscation': 'false',
'TrackWidgetCreation': 'false', 'TrackWidgetCreation': 'false',
'TreeShakeIcons': 'true', 'TreeShakeIcons': 'true',
@ -288,15 +277,17 @@ void main() {
final CommandRunner<void> runner = createTestCommandRunner(buildCommand); final CommandRunner<void> runner = createTestCommandRunner(buildCommand);
setupFileSystemForEndToEndTest(fileSystem); setupFileSystemForEndToEndTest(fileSystem);
await runner.run(<String>['build', 'web', '--no-pub']); await runner.run(<String>['build', 'web', '--no-pub']);
final BuildInfo buildInfo =
await buildCommand.webCommand.getBuildInfo(forcedBuildMode: BuildMode.debug);
expect(buildInfo.dartDefines, contains('FLUTTER_WEB_AUTO_DETECT=true'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => fakePlatform, Platform: () => fakePlatform,
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true), FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
ProcessManager: () => processManager, ProcessManager: () => processManager,
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)), BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) {
expect(target, isA<WebServiceWorker>());
final List<WebCompilerConfig> 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 { testUsingContext('Web build supports build-name and build-number', () async {

View file

@ -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<void> 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>[
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: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Logger: () => logger,
});
}

View file

@ -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/run_hot.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/vmservice.dart'; import 'package:flutter_tools/src/vmservice.dart';
import 'package:flutter_tools/src/web/compile.dart';
import 'package:test/fake.dart'; import 'package:test/fake.dart';
import 'package:unified_analytics/unified_analytics.dart' as analytics; import 'package:unified_analytics/unified_analytics.dart' as analytics;
import 'package:vm_service/vm_service.dart'; import 'package:vm_service/vm_service.dart';
@ -1087,47 +1086,6 @@ void main() {
}); });
}); });
group('dart-defines and web-renderer options', () {
late List<String> dartDefines;
setUp(() {
dartDefines = <String>[];
});
test('auto web-renderer with no dart-defines', () {
dartDefines = FlutterCommand.updateDartDefines(dartDefines, WebRendererMode.auto);
expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=true']);
});
test('canvaskit web-renderer with no dart-defines', () {
dartDefines = FlutterCommand.updateDartDefines(dartDefines, WebRendererMode.canvaskit);
expect(dartDefines, <String>['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, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']);
});
test('auto web-renderer with existing dart-defines', () {
dartDefines = <String>['FLUTTER_WEB_USE_SKIA=false'];
dartDefines = FlutterCommand.updateDartDefines(dartDefines, WebRendererMode.auto);
expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=true']);
});
test('canvaskit web-renderer with no dart-defines', () {
dartDefines = <String>['FLUTTER_WEB_USE_SKIA=false'];
dartDefines = FlutterCommand.updateDartDefines(dartDefines, WebRendererMode.canvaskit);
expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=true']);
});
test('html web-renderer with no dart-defines', () {
dartDefines = <String>['FLUTTER_WEB_USE_SKIA=true'];
dartDefines = FlutterCommand.updateDartDefines(dartDefines, WebRendererMode.html);
expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']);
});
});
group('terminal', () { group('terminal', () {
late FakeAnsiTerminal fakeTerminal; late FakeAnsiTerminal fakeTerminal;

View file

@ -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_time_recorder.dart';
import 'package:flutter_tools/src/test/test_wrapper.dart'; import 'package:flutter_tools/src/test/test_wrapper.dart';
import 'package:flutter_tools/src/test/watcher.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:stream_channel/stream_channel.dart';
import 'package:vm_service/vm_service.dart'; import 'package:vm_service/vm_service.dart';
@ -1058,6 +1059,24 @@ dev_dependencies:
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('Passes web renderer into debugging options', () async {
final FakeFlutterTestRunner testRunner = FakeFlutterTestRunner(0);
final TestCommand testCommand = TestCommand(testRunner: testRunner);
final CommandRunner<void> commandRunner = createTestCommandRunner(testCommand);
await commandRunner.run(const <String>[
'test',
'--no-pub',
'--platform=chrome',
'--web-renderer=canvaskit',
]);
expect(testRunner.lastDebuggingOptionsValue.webRenderer, WebRendererMode.canvaskit);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
}); });
} }

View file

@ -21,10 +21,10 @@ import '../../src/fake_process_manager.dart';
void main() { void main() {
late FileSystem fileSystem; late FileSystem fileSystem;
late Environment environment; late Environment environment;
late Target fooTarget; late TestTarget fooTarget;
late Target barTarget; late TestTarget barTarget;
late Target fizzTarget; late TestTarget fizzTarget;
late Target sharedTarget; late TestTarget sharedTarget;
late int fooInvocations; late int fooInvocations;
late int barInvocations; late int barInvocations;
late int shared; late int shared;
@ -138,6 +138,23 @@ void main() {
json.decode(stampFile.readAsStringSync())); json.decode(stampFile.readAsStringSync()));
expect(stampContents, containsPair('inputs', <Object>['/foo.dart'])); expect(stampContents, containsPair('inputs', <Object>['/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<String, Object?>? stampContents = castStringKeyedMap(
json.decode(stampFile.readAsStringSync()));
expect(stampContents, containsPair('inputs', <Object>['/foo.dart']));
expect(stampContents, containsPair('buildKey', 'fooBuildKey'));
}); });
testWithoutContext('Creates a BuildResult with inputs and outputs', () async { testWithoutContext('Creates a BuildResult with inputs and outputs', () async {
@ -168,6 +185,19 @@ void main() {
expect(fooInvocations, 2); 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 { testWithoutContext('does not re-invoke build if input timestamp changes', () async {
final BuildSystem buildSystem = setUpBuildSystem(fileSystem); final BuildSystem buildSystem = setUpBuildSystem(fileSystem);
await buildSystem.build(fooTarget, environment); await buildSystem.build(fooTarget, environment);
@ -723,4 +753,7 @@ class TestTarget extends Target {
@override @override
List<Source> outputs = <Source>[]; List<Source> outputs = <Source>[];
@override
String? buildKey;
} }

View file

@ -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<String> dartDefines;
setUp(() {
dartDefines = <String>[];
});
test('auto web-renderer with no dart-defines', () {
dartDefines = WebRendererMode.auto.updateDartDefines(dartDefines);
expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=true']);
});
test('canvaskit web-renderer with no dart-defines', () {
dartDefines = WebRendererMode.canvaskit.updateDartDefines(dartDefines);
expect(dartDefines, <String>['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, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']);
});
test('auto web-renderer with existing dart-defines', () {
dartDefines = <String>['FLUTTER_WEB_USE_SKIA=false'];
dartDefines = WebRendererMode.auto.updateDartDefines(dartDefines);
expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=true']);
});
test('canvaskit web-renderer with no dart-defines', () {
dartDefines = <String>['FLUTTER_WEB_USE_SKIA=false'];
dartDefines = WebRendererMode.canvaskit.updateDartDefines(dartDefines);
expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=true']);
});
test('html web-renderer with no dart-defines', () {
dartDefines = <String>['FLUTTER_WEB_USE_SKIA=true'];
dartDefines = WebRendererMode.html.updateDartDefines(dartDefines);
expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']);
});
});
}

View file

@ -120,7 +120,9 @@ void main() {
webResources.childFile('index.html') webResources.childFile('index.html')
.createSync(recursive: true); .createSync(recursive: true);
environment.buildDir.childFile('main.dart.js').createSync(); environment.buildDir.childFile('main.dart.js').createSync();
await const WebReleaseBundle(WebRendererMode.auto, isWasm: false).build(environment); await WebReleaseBundle(<WebCompilerConfig>[
const JsCompilerConfig()
]).build(environment);
expect(environment.outputDir.childFile('version.json'), exists); expect(environment.outputDir.childFile('version.json'), exists);
})); }));
@ -132,7 +134,9 @@ void main() {
final Directory webResources = environment.projectDir.childDirectory('web'); final Directory webResources = environment.projectDir.childDirectory('web');
webResources.childFile('index.html').createSync(recursive: true); webResources.childFile('index.html').createSync(recursive: true);
environment.buildDir.childFile('main.dart.js').createSync(); environment.buildDir.childFile('main.dart.js').createSync();
await const WebReleaseBundle(WebRendererMode.auto, isWasm: false).build(environment); await WebReleaseBundle(<WebCompilerConfig>[
const JsCompilerConfig()
]).build(environment);
final String versionFile = environment.outputDir final String versionFile = environment.outputDir
.childFile('version.json') .childFile('version.json')
@ -150,7 +154,9 @@ void main() {
<!DOCTYPE html><html><base href="$kBaseHrefPlaceholder"><head></head></html> <!DOCTYPE html><html><base href="$kBaseHrefPlaceholder"><head></head></html>
'''); ''');
environment.buildDir.childFile('main.dart.js').createSync(); environment.buildDir.childFile('main.dart.js').createSync();
await const WebReleaseBundle(WebRendererMode.auto, isWasm: false).build(environment); await WebReleaseBundle(<WebCompilerConfig>[
const JsCompilerConfig()
]).build(environment);
expect(environment.outputDir.childFile('index.html').readAsStringSync(), contains('/basehreftest/')); expect(environment.outputDir.childFile('index.html').readAsStringSync(), contains('/basehreftest/'));
})); }));
@ -163,7 +169,9 @@ void main() {
<!DOCTYPE html><html><head><base href='/basehreftest/'></head></html> <!DOCTYPE html><html><head><base href='/basehreftest/'></head></html>
'''); ''');
environment.buildDir.childFile('main.dart.js').createSync(); environment.buildDir.childFile('main.dart.js').createSync();
await const WebReleaseBundle(WebRendererMode.auto, isWasm: false).build(environment); await WebReleaseBundle(<WebCompilerConfig>[
const JsCompilerConfig()
]).build(environment);
expect(environment.outputDir.childFile('index.html').readAsStringSync(), contains('/basehreftest/')); expect(environment.outputDir.childFile('index.html').readAsStringSync(), contains('/basehreftest/'));
})); }));
@ -185,7 +193,9 @@ void main() {
.writeAsStringSync('A'); .writeAsStringSync('A');
environment.buildDir.childFile('main.dart.js').createSync(); environment.buildDir.childFile('main.dart.js').createSync();
await const WebReleaseBundle(WebRendererMode.auto, isWasm: false).build(environment); await WebReleaseBundle(<WebCompilerConfig>[
const JsCompilerConfig()
]).build(environment);
expect(environment.outputDir.childFile('foo.txt') expect(environment.outputDir.childFile('foo.txt')
.readAsStringSync(), 'A'); .readAsStringSync(), 'A');
@ -197,7 +207,9 @@ void main() {
// Update to arbitrary resource file triggers rebuild. // Update to arbitrary resource file triggers rebuild.
webResources.childFile('foo.txt').writeAsStringSync('B'); webResources.childFile('foo.txt').writeAsStringSync('B');
await const WebReleaseBundle(WebRendererMode.auto, isWasm: false).build(environment); await WebReleaseBundle(<WebCompilerConfig>[
const JsCompilerConfig()
]).build(environment);
expect(environment.outputDir.childFile('foo.txt') expect(environment.outputDir.childFile('foo.txt')
.readAsStringSync(), 'B'); .readAsStringSync(), 'B');
@ -207,6 +219,36 @@ void main() {
contains('flutter_service_worker.js?v='), 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(<WebCompilerConfig>[
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(<WebCompilerConfig>[
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 { 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')) final File mainFile = globals.fs.file(globals.fs.path.join('other', 'lib', 'main.dart'))
..createSync(recursive: true) ..createSync(recursive: true)
@ -353,6 +395,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-o', '-o',
environment.buildDir.childFile('app.dill').absolute.path, environment.buildDir.childFile('app.dill').absolute.path,
@ -365,6 +408,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-minify', '--no-minify',
'--no-source-maps', '--no-source-maps',
'-O4', '-O4',
@ -375,7 +419,12 @@ void main() {
] ]
)); ));
await Dart2JSTarget(WebRendererMode.auto).build(environment); await Dart2JSTarget(
const JsCompilerConfig(
csp: true,
sourceMaps: false,
)
).build(environment);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -387,6 +436,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-o', '-o',
environment.buildDir.childFile('app.dill').absolute.path, environment.buildDir.childFile('app.dill').absolute.path,
@ -399,6 +449,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-minify', '--no-minify',
'--no-source-maps', '--no-source-maps',
'-O4', '-O4',
@ -408,7 +459,11 @@ void main() {
] ]
)); ));
await Dart2JSTarget(WebRendererMode.auto).build(environment); await Dart2JSTarget(
const JsCompilerConfig(
sourceMaps: false,
)
).build(environment);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -421,6 +476,7 @@ void main() {
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'--enable-experiment=non-nullable', '--enable-experiment=non-nullable',
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-o', '-o',
environment.buildDir.childFile('app.dill').absolute.path, environment.buildDir.childFile('app.dill').absolute.path,
@ -434,6 +490,7 @@ void main() {
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'--enable-experiment=non-nullable', '--enable-experiment=non-nullable',
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-minify', '--no-minify',
'--no-source-maps', '--no-source-maps',
'-O4', '-O4',
@ -443,7 +500,11 @@ void main() {
] ]
)); ));
await Dart2JSTarget(WebRendererMode.auto).build(environment); await Dart2JSTarget(
const JsCompilerConfig(
sourceMaps: false,
)
).build(environment);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -454,6 +515,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-o', '-o',
environment.buildDir.childFile('app.dill').absolute.path, environment.buildDir.childFile('app.dill').absolute.path,
@ -466,6 +528,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-minify', '--no-minify',
'--no-source-maps', '--no-source-maps',
'-O4', '-O4',
@ -475,7 +538,11 @@ void main() {
] ]
)); ));
await Dart2JSTarget(WebRendererMode.auto).build(environment); await Dart2JSTarget(
const JsCompilerConfig(
sourceMaps: false,
)
).build(environment);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -486,6 +553,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-o', '-o',
environment.buildDir.childFile('app.dill').absolute.path, environment.buildDir.childFile('app.dill').absolute.path,
@ -498,6 +566,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-O4', '-O4',
'-o', '-o',
@ -506,7 +575,11 @@ void main() {
] ]
)); ));
await Dart2JSTarget(WebRendererMode.auto).build(environment); await Dart2JSTarget(
const JsCompilerConfig(
sourceMaps: false,
)
).build(environment);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -518,6 +591,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--native-null-assertions', '--native-null-assertions',
'--no-source-maps', '--no-source-maps',
'-o', '-o',
@ -531,6 +605,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--native-null-assertions', '--native-null-assertions',
'--no-source-maps', '--no-source-maps',
'-O4', '-O4',
@ -540,7 +615,12 @@ void main() {
] ]
)); ));
await Dart2JSTarget(WebRendererMode.auto).build(environment); await Dart2JSTarget(
const JsCompilerConfig(
nativeNullAssertions: true,
sourceMaps: false,
)
).build(environment);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -552,6 +632,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-o', '-o',
environment.buildDir.childFile('app.dill').absolute.path, environment.buildDir.childFile('app.dill').absolute.path,
@ -564,6 +645,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-O3', '-O3',
'-o', '-o',
@ -572,7 +654,12 @@ void main() {
] ]
)); ));
await Dart2JSTarget(WebRendererMode.auto).build(environment); await Dart2JSTarget(
const JsCompilerConfig(
optimizationLevel: 'O3',
sourceMaps: false,
)
).build(environment);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -583,6 +670,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-o', '-o',
environment.buildDir.childFile('app.dill').absolute.path, environment.buildDir.childFile('app.dill').absolute.path,
@ -598,6 +686,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-O4', '-O4',
'-o', '-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); expect(environment.buildDir.childFile('dart2js.d'), exists);
final Depfile depfile = environment.depFileService.parse(environment.buildDir.childFile('dart2js.d')); final Depfile depfile = environment.depFileService.parse(environment.buildDir.childFile('dart2js.d'));
@ -627,6 +720,7 @@ void main() {
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFOO=bar', '-DFOO=bar',
'-DBAZ=qux', '-DBAZ=qux',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-o', '-o',
environment.buildDir.childFile('app.dill').absolute.path, environment.buildDir.childFile('app.dill').absolute.path,
@ -641,6 +735,7 @@ void main() {
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFOO=bar', '-DFOO=bar',
'-DBAZ=qux', '-DBAZ=qux',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-O4', '-O4',
'-o', '-o',
@ -649,7 +744,11 @@ void main() {
] ]
)); ));
await Dart2JSTarget(WebRendererMode.auto).build(environment); await Dart2JSTarget(
const JsCompilerConfig(
sourceMaps: false,
)
).build(environment);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -661,6 +760,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'-o', '-o',
environment.buildDir.childFile('app.dill').absolute.path, environment.buildDir.childFile('app.dill').absolute.path,
'--packages=.dart_tool/package_config.json', '--packages=.dart_tool/package_config.json',
@ -672,6 +772,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'-O4', '-O4',
'-o', '-o',
environment.buildDir.childFile('main.dart.js').absolute.path, 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: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -694,6 +797,7 @@ void main() {
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFOO=bar', '-DFOO=bar',
'-DBAZ=qux', '-DBAZ=qux',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-o', '-o',
environment.buildDir.childFile('app.dill').absolute.path, environment.buildDir.childFile('app.dill').absolute.path,
@ -708,6 +812,7 @@ void main() {
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFOO=bar', '-DFOO=bar',
'-DBAZ=qux', '-DBAZ=qux',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-minify', '--no-minify',
'--no-source-maps', '--no-source-maps',
'-O4', '-O4',
@ -717,7 +822,11 @@ void main() {
] ]
)); ));
await Dart2JSTarget(WebRendererMode.auto).build(environment); await Dart2JSTarget(
const JsCompilerConfig(
sourceMaps: false,
)
).build(environment);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -729,6 +838,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-o', '-o',
environment.buildDir.childFile('app.dill').absolute.path, environment.buildDir.childFile('app.dill').absolute.path,
@ -741,6 +851,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-minify', '--no-minify',
'--no-source-maps', '--no-source-maps',
'-O4', '-O4',
@ -751,7 +862,12 @@ void main() {
] ]
)); ));
await Dart2JSTarget(WebRendererMode.canvaskit).build(environment); await Dart2JSTarget(
const JsCompilerConfig(
dumpInfo: true,
sourceMaps: false,
)
).build(environment);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -763,6 +879,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-source-maps', '--no-source-maps',
'-o', '-o',
environment.buildDir.childFile('app.dill').absolute.path, environment.buildDir.childFile('app.dill').absolute.path,
@ -775,6 +892,7 @@ void main() {
command: <String>[ command: <String>[
..._kDart2jsLinuxArgs, ..._kDart2jsLinuxArgs,
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFLUTTER_WEB_AUTO_DETECT=true',
'--no-minify', '--no-minify',
'--no-source-maps', '--no-source-maps',
'-O4', '-O4',
@ -785,7 +903,12 @@ void main() {
] ]
)); ));
await Dart2JSTarget(WebRendererMode.canvaskit).build(environment); await Dart2JSTarget(
const JsCompilerConfig(
noFrequencyBasedMinification: true,
sourceMaps: false,
)
).build(environment);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -804,6 +927,8 @@ void main() {
'-Ddart.vm.profile=true', '-Ddart.vm.profile=true',
'-DFOO=bar', '-DFOO=bar',
'-DBAZ=qux', '-DBAZ=qux',
'-DFLUTTER_WEB_AUTO_DETECT=false',
'-DFLUTTER_WEB_USE_SKIA=true',
'--depfile=${depFile.absolute.path}', '--depfile=${depFile.absolute.path}',
environment.buildDir.childFile('main.dart').absolute.path, environment.buildDir.childFile('main.dart').absolute.path,
environment.buildDir.childFile('main.dart.unopt.wasm').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); expect(outputJsFile.existsSync(), isFalse);
final File movedJsFile = environment.buildDir.childFile('main.dart.mjs'); final File movedJsFile = environment.buildDir.childFile('main.dart.mjs');
@ -842,6 +971,8 @@ void main() {
command: <String>[ command: <String>[
..._kDart2WasmLinuxArgs, ..._kDart2WasmLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=false',
'-DFLUTTER_WEB_USE_SKIA=true',
'--omit-type-checks', '--omit-type-checks',
'--depfile=${depFile.absolute.path}', '--depfile=${depFile.absolute.path}',
environment.buildDir.childFile('main.dart').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); expect(outputJsFile.existsSync(), isFalse);
final File movedJsFile = environment.buildDir.childFile('main.dart.mjs'); final File movedJsFile = environment.buildDir.childFile('main.dart.mjs');
@ -880,6 +1016,8 @@ void main() {
command: <String>[ command: <String>[
..._kDart2WasmLinuxArgs, ..._kDart2WasmLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=false',
'-DFLUTTER_WEB_USE_SKIA=true',
'--depfile=${depFile.absolute.path}', '--depfile=${depFile.absolute.path}',
environment.buildDir.childFile('main.dart').absolute.path, environment.buildDir.childFile('main.dart').absolute.path,
environment.buildDir.childFile('main.dart.unopt.wasm').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, 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); expect(outputJsFile.existsSync(), isFalse);
final File movedJsFile = environment.buildDir.childFile('main.dart.mjs'); final File movedJsFile = environment.buildDir.childFile('main.dart.mjs');
@ -915,12 +1058,19 @@ void main() {
command: <String>[ command: <String>[
..._kDart2WasmLinuxArgs, ..._kDart2WasmLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=false',
'-DFLUTTER_WEB_USE_SKIA=true',
'--depfile=${depFile.absolute.path}', '--depfile=${depFile.absolute.path}',
environment.buildDir.childFile('main.dart').absolute.path, environment.buildDir.childFile('main.dart').absolute.path,
environment.buildDir.childFile('main.dart.wasm').absolute.path, environment.buildDir.childFile('main.dart.wasm').absolute.path,
], onRun: (_) => outputJsFile..createSync()..writeAsStringSync('foo'))); ], onRun: (_) => outputJsFile..createSync()..writeAsStringSync('foo')));
await Dart2WasmTarget(WebRendererMode.canvaskit).build(environment); await Dart2WasmTarget(
const WasmCompilerConfig(
wasmOpt: WasmOptLevel.none,
renderer: WebRendererMode.canvaskit
)
).build(environment);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -935,6 +1085,9 @@ void main() {
command: <String>[ command: <String>[
..._kDart2WasmLinuxArgs, ..._kDart2WasmLinuxArgs,
'-Ddart.vm.product=true', '-Ddart.vm.product=true',
'-DFLUTTER_WEB_AUTO_DETECT=false',
'-DFLUTTER_WEB_USE_SKIA=false',
'-DFLUTTER_WEB_USE_SKWASM=true',
'--import-shared-memory', '--import-shared-memory',
'--shared-memory-max-pages=32768', '--shared-memory-max-pages=32768',
'--depfile=${depFile.absolute.path}', '--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: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => processManager, ProcessManager: () => processManager,
})); }));
@ -1001,7 +1158,9 @@ void main() {
environment.outputDir.childDirectory('a').childFile('a.txt') environment.outputDir.childDirectory('a').childFile('a.txt')
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('A'); ..writeAsStringSync('A');
await WebServiceWorker(globals.fs, WebRendererMode.auto, isWasm: false).build(environment); await WebServiceWorker(globals.fs, <WebCompilerConfig>[
const JsCompilerConfig()
]).build(environment);
expect(environment.outputDir.childFile('flutter_service_worker.js'), exists); expect(environment.outputDir.childFile('flutter_service_worker.js'), exists);
// Contains file hash. // Contains file hash.
@ -1024,7 +1183,9 @@ void main() {
.childFile('assets/index.html') .childFile('assets/index.html')
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('A'); ..writeAsStringSync('A');
await WebServiceWorker(globals.fs, WebRendererMode.auto, isWasm: false).build(environment); await WebServiceWorker(globals.fs, <WebCompilerConfig>[
const JsCompilerConfig()
]).build(environment);
expect(environment.outputDir.childFile('flutter_service_worker.js'), exists); expect(environment.outputDir.childFile('flutter_service_worker.js'), exists);
// Contains the same file hash for both `/` and the root index.html file. // Contains the same file hash for both `/` and the root index.html file.
@ -1046,7 +1207,9 @@ void main() {
environment.outputDir environment.outputDir
.childFile('main.dart.js.map') .childFile('main.dart.js.map')
.createSync(recursive: true); .createSync(recursive: true);
await WebServiceWorker(globals.fs, WebRendererMode.auto, isWasm: false).build(environment); await WebServiceWorker(globals.fs, <WebCompilerConfig>[
const JsCompilerConfig()
]).build(environment);
// No caching of source maps. // No caching of source maps.
expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(), expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(),
@ -1056,24 +1219,12 @@ void main() {
contains('"main.dart.js"')); contains('"main.dart.js"'));
})); }));
test('wasm build copies and generates specific files', () => testbed.run(() async { test('WebBuiltInAssets copies over canvaskit again if the web sdk changes', () => 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 {
final File canvasKitInput = globals.fs.file('bin/cache/flutter_web_sdk/canvaskit/canvaskit.wasm') final File canvasKitInput = globals.fs.file('bin/cache/flutter_web_sdk/canvaskit/canvaskit.wasm')
..createSync(recursive: true); ..createSync(recursive: true);
canvasKitInput.writeAsStringSync('foo', flush: 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') final File canvasKitOutputBefore = environment.outputDir.childDirectory('canvaskit')
.childFile('canvaskit.wasm'); .childFile('canvaskit.wasm');
@ -1082,7 +1233,7 @@ void main() {
canvasKitInput.writeAsStringSync('bar', flush: true); 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') final File canvasKitOutputAfter = environment.outputDir.childDirectory('canvaskit')
.childFile('canvaskit.wasm'); .childFile('canvaskit.wasm');

View file

@ -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/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/test/web_test_compiler.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 'package:test/expect.dart';
import '../../src/context.dart'; import '../../src/context.dart';
@ -44,6 +45,8 @@ void main() {
'--incremental', '--incremental',
'--target=dartdevc', '--target=dartdevc',
'--experimental-emit-debug-metadata', '--experimental-emit-debug-metadata',
'-DFLUTTER_WEB_AUTO_DETECT=false',
'-DFLUTTER_WEB_USE_SKIA=true',
'--output-dill', '--output-dill',
'build/out', 'build/out',
'--packages', '--packages',
@ -87,6 +90,7 @@ void main() {
testOutputDir: 'build', testOutputDir: 'build',
testFiles: <String>['project/test/fake_test.dart'], testFiles: <String>['project/test/fake_test.dart'],
buildInfo: buildInfo, buildInfo: buildInfo,
webRenderer: WebRendererMode.canvaskit,
); );
expect(processManager.hasRemainingExpectations, isFalse); expect(processManager.hasRemainingExpectations, isFalse);

View file

@ -43,16 +43,11 @@ void main() {
testUsingContext('WebBuilder sets environment on success', () async { testUsingContext('WebBuilder sets environment on success', () async {
final TestBuildSystem buildSystem = final TestBuildSystem buildSystem =
TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) {
final WebServiceWorker webServiceWorker = target as WebServiceWorker; expect(target, isA<WebServiceWorker>());
expect(webServiceWorker.isWasm, isTrue, reason: 'should be wasm');
expect(webServiceWorker.webRenderer, WebRendererMode.auto);
expect(environment.defines, <String, String>{ expect(environment.defines, <String, String>{
'TargetFile': 'target', 'TargetFile': 'target',
'HasWebPlugins': 'false', 'HasWebPlugins': 'false',
'ServiceWorkerStrategy': ServiceWorkerStrategy.offlineFirst.cliName, 'ServiceWorkerStrategy': ServiceWorkerStrategy.offlineFirst.cliName,
'WasmOmitTypeChecks': 'false',
'RunWasmOpt': 'none',
'BuildMode': 'debug', 'BuildMode': 'debug',
'DartObfuscation': 'false', 'DartObfuscation': 'false',
'TrackWidgetCreation': 'true', 'TrackWidgetCreation': 'true',
@ -77,10 +72,16 @@ void main() {
'target', 'target',
BuildInfo.debug, BuildInfo.debug,
ServiceWorkerStrategy.offlineFirst, ServiceWorkerStrategy.offlineFirst,
compilerConfig: const WasmCompilerConfig( compilerConfigs: <WebCompilerConfig>[
omitTypeChecks: false, const WasmCompilerConfig(
wasmOpt: WasmOptLevel.none, wasmOpt: WasmOptLevel.none,
), renderer: WebRendererMode.skwasm,
),
const JsCompilerConfig.run(
nativeNullAssertions: true,
renderer: WebRendererMode.canvaskit,
),
],
); );
expect(logger.statusText, contains('Compiling target for the Web...')); expect(logger.statusText, contains('Compiling target for the Web...'));
@ -102,7 +103,7 @@ void main() {
label: 'web-compile', label: 'web-compile',
parameters: CustomDimensions( parameters: CustomDimensions(
buildEventSettings: 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( Event.flutterBuildInfo(
label: 'web-compile', label: 'web-compile',
buildType: 'web', 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. // Sends timing event.
final TestTimingEvent timingEvent = testUsage.timings.single; final TestTimingEvent timingEvent = testUsage.timings.single;
expect(timingEvent.category, 'build'); expect(timingEvent.category, 'build');
expect(timingEvent.variableName, 'dart2wasm'); expect(timingEvent.variableName, 'dual-compile');
expect( expect(
analyticsTimingEventExists( analyticsTimingEventExists(
sentEvents: fakeAnalytics.sentEvents, sentEvents: fakeAnalytics.sentEvents,
workflow: 'build', workflow: 'build',
variableName: 'dart2wasm', variableName: 'dual-compile',
), ),
true, true,
); );
@ -161,7 +162,9 @@ void main() {
'target', 'target',
BuildInfo.debug, BuildInfo.debug,
ServiceWorkerStrategy.offlineFirst, ServiceWorkerStrategy.offlineFirst,
compilerConfig: const JsCompilerConfig.run(nativeNullAssertions: true), compilerConfigs: <WebCompilerConfig>[
const JsCompilerConfig.run(nativeNullAssertions: true, renderer: WebRendererMode.auto),
]
), ),
throwsToolExit(message: 'Failed to compile application for the Web.')); throwsToolExit(message: 'Failed to compile application for the Web.'));

View file

@ -66,6 +66,7 @@ void main() {
<String, String>{}, <String, String>{},
<String, String>{}, <String, String>{},
NullSafetyMode.unsound, NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
); );
releaseAssetServer = ReleaseAssetServer( releaseAssetServer = ReleaseAssetServer(
globals.fs.file('main.dart').uri, globals.fs.file('main.dart').uri,
@ -291,6 +292,7 @@ void main() {
<String, String>{}, <String, String>{},
<String, String>{}, <String, String>{},
NullSafetyMode.unsound, NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
); );
expect(webAssetServer.basePath, 'foo/bar'); expect(webAssetServer.basePath, 'foo/bar');
@ -310,6 +312,7 @@ void main() {
<String, String>{}, <String, String>{},
<String, String>{}, <String, String>{},
NullSafetyMode.unsound, NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
); );
// Defaults to "/" when there's no base element. // Defaults to "/" when there's no base element.
@ -331,6 +334,7 @@ void main() {
<String, String>{}, <String, String>{},
<String, String>{}, <String, String>{},
NullSafetyMode.unsound, NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
), ),
throwsToolExit(), throwsToolExit(),
); );
@ -351,6 +355,7 @@ void main() {
<String, String>{}, <String, String>{},
<String, String>{}, <String, String>{},
NullSafetyMode.unsound, NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
), ),
throwsToolExit(), throwsToolExit(),
); );
@ -684,6 +689,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.unsound, nullSafetyMode: NullSafetyMode.unsound,
webRenderer: WebRendererMode.html,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
webDevFS.flutterJs.createSync(recursive: true); webDevFS.flutterJs.createSync(recursive: true);
@ -745,13 +751,6 @@ void main() {
// New SDK should be visible.. // New SDK should be visible..
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'BELLOW'); 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. // Generated entrypoint.
expect(await webDevFS.webAssetServer.dartSourceContents('web_entrypoint.dart'), expect(await webDevFS.webAssetServer.dartSourceContents('web_entrypoint.dart'),
contains('GENERATED')); contains('GENERATED'));
@ -800,6 +799,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.sound, nullSafetyMode: NullSafetyMode.sound,
webRenderer: WebRendererMode.html,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
webDevFS.flutterJs.createSync(recursive: true); webDevFS.flutterJs.createSync(recursive: true);
@ -861,11 +861,6 @@ void main() {
// New SDK should be visible.. // New SDK should be visible..
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'BELLOW'); 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. // Generated entrypoint.
expect(await webDevFS.webAssetServer.dartSourceContents('web_entrypoint.dart'), expect(await webDevFS.webAssetServer.dartSourceContents('web_entrypoint.dart'),
contains('GENERATED')); contains('GENERATED'));
@ -913,6 +908,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.sound, nullSafetyMode: NullSafetyMode.sound,
webRenderer: WebRendererMode.canvaskit,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true);
@ -974,6 +970,7 @@ void main() {
nullAssertions: true, nullAssertions: true,
nativeNullAssertions: true, nativeNullAssertions: true,
nullSafetyMode: NullSafetyMode.sound, nullSafetyMode: NullSafetyMode.sound,
webRenderer: WebRendererMode.canvaskit,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true);
@ -1019,6 +1016,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.sound, nullSafetyMode: NullSafetyMode.sound,
webRenderer: WebRendererMode.canvaskit,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true);
@ -1065,6 +1063,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.sound, nullSafetyMode: NullSafetyMode.sound,
webRenderer: WebRendererMode.auto,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true);
@ -1112,6 +1111,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.unsound, nullSafetyMode: NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true);
@ -1148,7 +1148,9 @@ void main() {
null, null,
const <String, String>{}, const <String, String>{},
NullSafetyMode.unsound, NullSafetyMode.unsound,
testMode: true); webRenderer: WebRendererMode.canvaskit,
testMode: true
);
expect(webAssetServer.defaultResponseHeaders['x-frame-options'], null); expect(webAssetServer.defaultResponseHeaders['x-frame-options'], null);
await webAssetServer.dispose(); await webAssetServer.dispose();
@ -1180,7 +1182,9 @@ void main() {
extraHeaderKey: extraHeaderValue, extraHeaderKey: extraHeaderValue,
}, },
NullSafetyMode.unsound, NullSafetyMode.unsound,
testMode: true); webRenderer: WebRendererMode.canvaskit,
testMode: true
);
expect(webAssetServer.defaultResponseHeaders[extraHeaderKey], <String>[extraHeaderValue]); expect(webAssetServer.defaultResponseHeaders[extraHeaderKey], <String>[extraHeaderValue]);
@ -1215,6 +1219,7 @@ void main() {
<String, String>{}, <String, String>{},
<String, String>{}, <String, String>{},
NullSafetyMode.sound, NullSafetyMode.sound,
webRenderer: WebRendererMode.canvaskit,
); );
expect(await webAssetServer.metadataContents('foo/main_module.ddc_merged_metadata'), null); expect(await webAssetServer.metadataContents('foo/main_module.ddc_merged_metadata'), null);
@ -1257,6 +1262,7 @@ void main() {
extraHeaders: const <String, String>{}, extraHeaders: const <String, String>{},
chromiumLauncher: null, chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.unsound, nullSafetyMode: NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
); );
webDevFS.requireJS.createSync(recursive: true); webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true); webDevFS.stackTraceMapper.createSync(recursive: true);

View file

@ -51,7 +51,7 @@ void main() {
final Directory appBuildDir = fileSystem.directory(fileSystem.path.join( final Directory appBuildDir = fileSystem.directory(fileSystem.path.join(
exampleAppDir.path, exampleAppDir.path,
'build', 'build',
'web_wasm', 'web',
)); ));
for (final String filename in const <String>[ for (final String filename in const <String>[
'flutter.js', 'flutter.js',

View file

@ -19,6 +19,7 @@ void main() async {
await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsCallback), name: 'flutter.js (callback)'); await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsCallback), name: 'flutter.js (callback)');
await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsPromisesFull), name: 'flutter.js (promises)'); await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsPromisesFull), name: 'flutter.js (promises)');
await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsPromisesShort), name: 'flutter.js (promises, short)'); 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'); await _testProject(HotReloadProject(indexHtml: indexHtmlNoFlutterJs), name: 'No flutter.js');
} }

View file

@ -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 /// index_without_flutterjs.html
String indexHtmlNoFlutterJs = ''' String indexHtmlNoFlutterJs = '''
<!DOCTYPE HTML> <!DOCTYPE HTML>