Unverified Commit ba626dc8 authored by Jackson Gardner's avatar Jackson Gardner Committed by GitHub

Wasm/JS Dual Compile with the flutter tool (#141396)

This implements dual compile via the newly available flutter.js bootstrapping APIs for intelligent build fallback.
* Users can now use the `FlutterLoader.load` API from flutter.js
* Flutter tool injects build info into the `index.html` of the user so that the bootstrapper knows which build variants are available to bootstrap
* The semantics of the `--wasm` flag for `flutter build web` have changed:
  - Instead of producing a separate `build/web_wasm` directory, the output goes to the `build/web` directory like a normal web build
  - Produces a dual build that contains two build variants: dart2wasm+skwasm and dart2js+CanvasKit. The dart2wasm+skwasm will only work on Chrome in a cross-origin isolated context, all other environments will fall back to dart2js+CanvasKit.
  - `--wasm` and `--web-renderer` are now mutually exclusive. Since there are multiple build variants with `--wasm`, the web renderer cannot be expressed via a single command-line flag. For now, we are hard coding what build variants are produced with the `--wasm` flag, but I plan on making this more customizable in the future.
* Build targets now can optionally provide a "build key" which can uniquely identify any specific parameterization of that build target. This way, the build target can invalidate itself by changing its build key. This works a bit better than just stuffing everything into the environment defines because (a) it doesn't invalidate the entire build, just the targets which are affected and (b) settings for multiple build variants don't translate well to the flat map of environment defines.
parent c6f2cea6
......@@ -6,8 +6,12 @@ found in the LICENSE file. -->
<head>
<meta charset="UTF-8">
<title>Web Benchmarks</title>
<script src="flutter.js"></script>
</head>
<body>
<script src="main.dart.js" type="application/javascript"></script>
<script>
{{flutter_build_config}}
_flutter.loader.load();
</script>
</body>
</html>
......@@ -39,7 +39,7 @@ Future<TaskResult> runWebBenchmark(WebBenchmarkOptions benchmarkOptions) async {
'--omit-type-checks',
],
'--dart-define=FLUTTER_WEB_ENABLE_PROFILING=true',
'--web-renderer=${benchmarkOptions.webRenderer}',
if (!benchmarkOptions.useWasm) '--web-renderer=${benchmarkOptions.webRenderer}',
'--profile',
'--no-web-resources-cdn',
'-t',
......@@ -125,7 +125,7 @@ Future<TaskResult> runWebBenchmark(WebBenchmarkOptions benchmarkOptions) async {
return Response.internalServerError(body: '$error');
}
}).add(createBuildDirectoryHandler(
path.join(macrobenchmarksDirectory, 'build', benchmarkOptions.useWasm ? 'web_wasm' : 'web'),
path.join(macrobenchmarksDirectory, 'build', 'web'),
));
server = await io.HttpServer.bind('localhost', benchmarkServerPort);
......
......@@ -5,14 +5,17 @@ found in the LICENSE file. -->
<html>
<head>
<title>Web Integration Tests</title>
<script>
// Use the local CanvasKit bundle instead of the CDN to reduce test flakiness.
window.flutterConfiguration = {
canvasKitBaseUrl: "/canvaskit/"
};
</script>
<script src="flutter.js"></script>
</head>
<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>
</html>
......@@ -12,7 +12,6 @@ import 'base/os.dart';
import 'base/utils.dart';
import 'convert.dart';
import 'globals.dart' as globals;
import 'web/compile.dart';
/// Whether icon font subsetting is enabled by default.
const bool kIconTreeShakerEnabledDefault = true;
......@@ -36,7 +35,6 @@ class BuildInfo {
List<String>? dartDefines,
this.bundleSkSLPath,
List<String>? dartExperiments,
this.webRenderer = WebRendererMode.auto,
required this.treeShakeIcons,
this.performanceMeasurementFile,
this.packagesPath = '.dart_tool/package_config.json', // TODO(zanderso): make this required and remove the default.
......@@ -130,9 +128,6 @@ class BuildInfo {
/// A list of Dart experiments.
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
/// information in a JSON format.
///
......@@ -798,10 +793,6 @@ HostPlatform getCurrentHostPlatform() {
return HostPlatform.linux_x64;
}
FileSystemEntity getWebPlatformBinariesDirectory(Artifacts artifacts, WebRendererMode webRenderer) {
return artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder);
}
/// Returns the top-level build output directory.
String getBuildDirectory([Config? config, FileSystem? fileSystem]) {
// TODO(johnmccutchan): Stop calling this function as part of setting
......@@ -844,8 +835,8 @@ String getMacOSBuildDirectory() {
}
/// Returns the web build output directory.
String getWebBuildDirectory([bool isWasm = false]) {
return globals.fs.path.join(getBuildDirectory(), isWasm ? 'web_wasm' : 'web');
String getWebBuildDirectory() {
return globals.fs.path.join(getBuildDirectory(), 'web');
}
/// Returns the Linux build output directory.
......
......@@ -136,6 +136,15 @@ abstract class Target {
/// A list of zero or more depfiles, located directly under {BUILD_DIR}.
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].
///
/// Returning `true` will cause [build] to be skipped. This is equivalent
......@@ -156,6 +165,7 @@ abstract class Target {
<Node>[
for (final Target target in dependencies) target._toNode(environment),
],
buildKey,
environment,
inputsFiles.containsNewDepfile,
);
......@@ -181,9 +191,11 @@ abstract class Target {
for (final File output in outputs) {
outputPaths.add(output.path);
}
final String? key = buildKey;
final Map<String, Object> result = <String, Object>{
'inputs': inputPaths,
'outputs': outputPaths,
if (key != null) 'buildKey': key,
};
if (!stamp.existsSync()) {
stamp.createSync();
......@@ -218,6 +230,7 @@ abstract class Target {
/// This requires constants from the [Environment] to resolve the paths of
/// inputs and the output stamp.
Map<String, Object> toJson(Environment environment) {
final String? key = buildKey;
return <String, Object>{
'name': name,
'dependencies': <String>[
......@@ -229,6 +242,7 @@ abstract class Target {
'outputs': <String>[
for (final File file in resolveOutputs(environment).sources) file.path,
],
if (key != null) 'buildKey': key,
'stamp': _findStampFile(environment).absolute.path,
};
}
......@@ -980,50 +994,86 @@ void verifyOutputDirectories(List<File> outputs, Environment environment, Target
/// A node in the build graph.
class Node {
Node(
this.target,
this.inputs,
this.outputs,
this.dependencies,
factory Node(
Target target,
List<File> inputs,
List<File> outputs,
List<Node> dependencies,
String? buildKey,
Environment environment,
this.missingDepfile,
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()) {
// No stamp file, not safe to skip.
_dirty = true;
return;
}
final String content = stamp.readAsStringSync();
// Something went wrong writing the stamp file.
if (content.isEmpty) {
stamp.deleteSync();
// Malformed stamp file, not safe to skip.
_dirty = true;
return;
}
Map<String, Object?>? values;
try {
values = castStringKeyedMap(json.decode(content));
} on FormatException {
// The json is malformed in some way.
_dirty = true;
return;
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.
}
}
}
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;
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.inputs,
this.outputs,
this.dependencies,
this.buildKey,
this.missingDepfile,
) : previousInputs = <String>{},
previousOutputs = <String>{},
previousBuildKey = null,
_dirty = true;
Node.withStamp(
this.target,
this.inputs,
this.previousInputs,
this.outputs,
this.previousOutputs,
this.dependencies,
this.buildKey,
this.previousBuildKey,
this.missingDepfile,
) : _dirty = false;
/// The resolved input files.
///
/// These files may not yet exist if they are produced by previous steps.
......@@ -1034,6 +1084,11 @@ class Node {
/// These files may not yet exist if the target hasn't run yet.
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.
///
/// This requires an additional pass of source resolution after the target
......@@ -1047,10 +1102,15 @@ class Node {
final List<Node> dependencies;
/// 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.
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.
///
......@@ -1074,6 +1134,10 @@ class Node {
FileSystem fileSystem,
Logger logger,
) {
if (buildKey != previousBuildKey) {
_invalidate(InvalidatedReasonKind.buildKeyChanged);
_dirty = true;
}
final Set<String> currentOutputPaths = <String>{
for (final File file in outputs) file.path,
};
......@@ -1173,7 +1237,8 @@ class InvalidatedReason {
InvalidatedReasonKind.inputChanged => 'The following inputs have updated contents: ${data.join(',')}',
InvalidatedReasonKind.outputChanged => 'The following outputs have updated contents: ${data.join(',')}',
InvalidatedReasonKind.outputMissing => 'The following outputs were missing: ${data.join(',')}',
InvalidatedReasonKind.outputSetChanged => 'The following outputs were removed from the output set: ${data.join(',')}'
InvalidatedReasonKind.outputSetChanged => 'The following outputs were removed from the output set: ${data.join(',')}',
InvalidatedReasonKind.buildKeyChanged => 'The target build key changed.',
};
}
}
......@@ -1195,4 +1260,7 @@ enum InvalidatedReasonKind {
/// The set of expected output files changed.
outputSetChanged,
/// The build key changed
buildKeyChanged,
}
......@@ -138,24 +138,50 @@ class BuildWebCommand extends BuildSubCommand {
throwToolExit('"build web" is not currently supported. To enable, run "flutter config --enable-web".');
}
final WebCompilerConfig compilerConfig;
final List<WebCompilerConfig> compilerConfigs;
if (boolArg('wasm')) {
if (!featureFlags.isFlutterWebWasmEnabled) {
throwToolExit('Compiling to WebAssembly (wasm) is only available on the master channel.');
}
compilerConfig = WasmCompilerConfig(
omitTypeChecks: boolArg('omit-type-checks'),
wasmOpt: WasmOptLevel.values.byName(stringArg('wasm-opt')!),
if (stringArg(FlutterOptions.kWebRendererFlag) != argParser.defaultFor(FlutterOptions.kWebRendererFlag)) {
throwToolExit('"--${FlutterOptions.kWebRendererFlag}" cannot be combined with "--${FlutterOptions.kWebWasmFlag}"');
}
globals.logger.printBox(
title: 'Experimental feature',
'''
WebAssembly compilation is experimental.
$kWasmMoreInfo''',
);
compilerConfigs = <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 {
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'),
optimizationLevel: stringArg('dart2js-optimization') ?? JsCompilerConfig.kDart2jsDefaultOptimizationLevel,
dumpInfo: boolArg('dump-info'),
nativeNullAssertions: boolArg('native-null-assertions'),
noFrequencyBasedMinification: boolArg('no-frequency-based-minification'),
sourceMaps: boolArg('source-maps'),
);
renderer: webRenderer,
)];
}
final FlutterProject flutterProject = FlutterProject.current();
......@@ -205,7 +231,7 @@ class BuildWebCommand extends BuildSubCommand {
target,
buildInfo,
ServiceWorkerStrategy.fromCliName(stringArg('pwa-strategy')),
compilerConfig: compilerConfig,
compilerConfigs: compilerConfigs,
baseHref: baseHref,
outputDirectoryPath: outputDirectoryPath,
);
......
......@@ -29,6 +29,7 @@ import '../runner/flutter_command.dart';
import '../runner/flutter_command_runner.dart';
import '../tracing.dart';
import '../vmservice.dart';
import '../web/compile.dart';
import '../web/web_runner.dart';
import 'daemon.dart';
......@@ -241,6 +242,10 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
final Map<String, String> webHeaders = featureFlags.isWebEnabled
? extractWebHeaders()
: const <String, String>{};
final String? webRendererString = stringArg('web-renderer');
final WebRendererMode webRenderer = (webRendererString != null)
? WebRendererMode.values.byName(webRendererString)
: WebRendererMode.auto;
if (buildInfo.mode.isRelease) {
return DebuggingOptions.disabled(
......@@ -258,6 +263,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
webBrowserDebugPort: webBrowserDebugPort,
webBrowserFlags: webBrowserFlags,
webHeaders: webHeaders,
webRenderer: webRenderer,
enableImpeller: enableImpeller,
enableVulkanValidation: enableVulkanValidation,
uninstallFirst: uninstallFirst,
......@@ -307,6 +313,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
webEnableExpressionEvaluation: featureFlags.isWebEnabled && boolArg('web-enable-expression-evaluation'),
webLaunchUrl: featureFlags.isWebEnabled ? stringArg('web-launch-url') : null,
webHeaders: webHeaders,
webRenderer: webRenderer,
vmserviceOutFile: stringArg('vmservice-out-file'),
fastStart: argParser.options.containsKey('fast-start')
&& boolArg('fast-start')
......
......@@ -18,6 +18,7 @@ import 'devfs.dart';
import 'device_port_forwarder.dart';
import 'project.dart';
import 'vmservice.dart';
import 'web/compile.dart';
DeviceManager? get deviceManager => context.get<DeviceManager>();
......@@ -952,6 +953,7 @@ class DebuggingOptions {
this.webEnableExpressionEvaluation = false,
this.webHeaders = const <String, String>{},
this.webLaunchUrl,
this.webRenderer = WebRendererMode.auto,
this.vmserviceOutFile,
this.fastStart = false,
this.nullAssertions = false,
......@@ -981,6 +983,7 @@ class DebuggingOptions {
this.webBrowserFlags = const <String>[],
this.webLaunchUrl,
this.webHeaders = const <String, String>{},
this.webRenderer = WebRendererMode.auto,
this.cacheSkSL = false,
this.traceAllowlist,
this.enableImpeller = ImpellerStatus.platformDefault,
......@@ -1060,6 +1063,7 @@ class DebuggingOptions {
required this.webEnableExpressionEvaluation,
required this.webHeaders,
required this.webLaunchUrl,
required this.webRenderer,
required this.vmserviceOutFile,
required this.fastStart,
required this.nullAssertions,
......@@ -1144,6 +1148,9 @@ class DebuggingOptions {
/// Allow developers to add custom headers to web server
final Map<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.
final String? vmserviceOutFile;
final bool fastStart;
......@@ -1252,6 +1259,7 @@ class DebuggingOptions {
'webEnableExpressionEvaluation': webEnableExpressionEvaluation,
'webLaunchUrl': webLaunchUrl,
'webHeaders': webHeaders,
'webRenderer': webRenderer.name,
'vmserviceOutFile': vmserviceOutFile,
'fastStart': fastStart,
'nullAssertions': nullAssertions,
......@@ -1307,6 +1315,7 @@ class DebuggingOptions {
webEnableExpressionEvaluation: json['webEnableExpressionEvaluation']! as bool,
webHeaders: (json['webHeaders']! as Map<dynamic, dynamic>).cast<String, String>(),
webLaunchUrl: json['webLaunchUrl'] as String?,
webRenderer: WebRendererMode.values.byName(json['webRenderer']! as String),
vmserviceOutFile: json['vmserviceOutFile'] as String?,
fastStart: json['fastStart']! as bool,
nullAssertions: json['nullAssertions']! as bool,
......
......@@ -78,12 +78,14 @@ class WebDriverService extends DriverService {
buildInfo,
port: debuggingOptions.port,
hostname: debuggingOptions.hostname,
webRenderer: debuggingOptions.webRenderer,
)
: DebuggingOptions.enabled(
buildInfo,
port: debuggingOptions.port,
hostname: debuggingOptions.hostname,
disablePortPublication: debuggingOptions.disablePortPublication,
webRenderer: debuggingOptions.webRenderer,
),
stayResident: true,
flutterProject: FlutterProject.current(),
......
......@@ -62,6 +62,7 @@ class IndexHtml {
void applySubstitutions({
required String baseHref,
required String? serviceWorkerVersion,
String? buildConfig,
}) {
if (_content.contains(kBaseHrefPlaceholder)) {
_content = _content.replaceAll(kBaseHrefPlaceholder, baseHref);
......@@ -81,6 +82,12 @@ class IndexHtml {
"navigator.serviceWorker.register('flutter_service_worker.js?v=$serviceWorkerVersion')",
);
}
if (buildConfig != null) {
_content = _content.replaceFirst(
'{{flutter_build_config}}',
buildConfig,
);
}
}
}
......
......@@ -117,8 +117,9 @@ class WebAssetServer implements AssetReader {
this.internetAddress,
this._modules,
this._digests,
this._nullSafetyMode,
) : basePath = _getIndexHtml().getBaseHref();
this._nullSafetyMode, {
required this.webRenderer,
}) : basePath = _getIndexHtml().getBaseHref();
// Fallback to "application/octet-stream" on null which
// makes no claims as to the structure of the data.
......@@ -177,6 +178,7 @@ class WebAssetServer implements AssetReader {
ExpressionCompiler? expressionCompiler,
Map<String, String> extraHeaders,
NullSafetyMode nullSafetyMode, {
required WebRendererMode webRenderer,
bool testMode = false,
DwdsLauncher dwdsLauncher = Dwds.start,
}) async {
......@@ -225,6 +227,7 @@ class WebAssetServer implements AssetReader {
modules,
digests,
nullSafetyMode,
webRenderer: webRenderer,
);
if (testMode) {
return server;
......@@ -504,16 +507,29 @@ class WebAssetServer implements AssetReader {
}
/// Determines what rendering backed to use.
WebRendererMode webRenderer = WebRendererMode.html;
final WebRendererMode webRenderer;
shelf.Response _serveIndex() {
final IndexHtml indexHtml = _getIndexHtml();
final Map<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(
// Currently, we don't support --base-href for the "run" command.
baseHref: '/',
serviceWorkerVersion: null,
buildConfig: buildConfigString,
);
final Map<String, String> headers = <String, String>{
......@@ -663,6 +679,7 @@ class WebDevFS implements DevFS {
required this.nullAssertions,
required this.nativeNullAssertions,
required this.nullSafetyMode,
required this.webRenderer,
this.testMode = false,
}) : _port = port;
......@@ -686,6 +703,7 @@ class WebDevFS implements DevFS {
final NullSafetyMode nullSafetyMode;
final String? tlsCertPath;
final String? tlsCertKeyPath;
final WebRendererMode webRenderer;
late WebAssetServer webAssetServer;
......@@ -785,15 +803,11 @@ class WebDevFS implements DevFS {
expressionCompiler,
extraHeaders,
nullSafetyMode,
webRenderer: webRenderer,
testMode: testMode,
);
final int selectedPort = webAssetServer.selectedPort;
if (buildInfo.dartDefines.contains('FLUTTER_WEB_AUTO_DETECT=true')) {
webAssetServer.webRenderer = WebRendererMode.auto;
} else if (buildInfo.dartDefines.contains('FLUTTER_WEB_USE_SKIA=true')) {
webAssetServer.webRenderer = WebRendererMode.canvaskit;
}
String url = '$hostname:$selectedPort';
if (hostname == 'any') {
url ='localhost:$selectedPort';
......
......@@ -309,6 +309,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
nullAssertions: debuggingOptions.nullAssertions,
nullSafetyMode: debuggingOptions.buildInfo.nullSafetyMode,
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
webRenderer: debuggingOptions.webRenderer,
);
Uri url = await device!.devFS!.create();
if (debuggingOptions.tlsCertKeyPath != null && debuggingOptions.tlsCertPath != null) {
......@@ -339,7 +340,12 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
target,
debuggingOptions.buildInfo,
ServiceWorkerStrategy.none,
compilerConfig: JsCompilerConfig.run(nativeNullAssertions: debuggingOptions.nativeNullAssertions)
compilerConfigs: <WebCompilerConfig>[
JsCompilerConfig.run(
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
renderer: debuggingOptions.webRenderer,
)
]
);
}
await device!.device!.startApp(
......@@ -418,7 +424,12 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
target,
debuggingOptions.buildInfo,
ServiceWorkerStrategy.none,
compilerConfig: JsCompilerConfig.run(nativeNullAssertions: debuggingOptions.nativeNullAssertions),
compilerConfigs: <WebCompilerConfig>[
JsCompilerConfig.run(
nativeNullAssertions: debuggingOptions.nativeNullAssertions,
renderer: debuggingOptions.webRenderer,
)
],
);
} on ToolExit {
return OperationResult(1, 'Failed to recompile application.');
......
......@@ -137,7 +137,7 @@ class FlutterDevice {
}
final String platformDillPath = globals.fs.path.join(
getWebPlatformBinariesDirectory(globals.artifacts!, buildInfo.webRenderer).path,
globals.artifacts!.getHostArtifact(HostArtifact.webPlatformKernelFolder).path,
platformDillName,
);
......
......@@ -1263,13 +1263,7 @@ abstract class FlutterCommand extends Command<void> {
: null;
final Map<String, Object?> defineConfigJsonMap = extractDartDefineConfigJsonMap();
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);
}
final List<String> dartDefines = extractDartDefines(defineConfigJsonMap: defineConfigJsonMap);
if (argParser.options.containsKey(FlutterOptions.kWebResourcesCdnFlag)) {
final bool hasLocalWebSdk = argParser.options.containsKey('local-web-sdk') && stringArg('local-web-sdk') != null;
......@@ -1317,7 +1311,6 @@ abstract class FlutterCommand extends Command<void> {
dartDefines: dartDefines,
bundleSkSLPath: bundleSkSLPath,
dartExperiments: experiments,
webRenderer: webRenderer,
performanceMeasurementFile: performanceMeasurementFile,
packagesPath: packagesPath ?? globals.fs.path.absolute('.dart_tool', 'package_config.json'),
nullSafetyMode: nullSafetyMode,
......@@ -1555,19 +1548,6 @@ abstract class FlutterCommand extends Command<void> {
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() {
final Map<String, String> webHeaders = <String, String>{};
......
......@@ -69,7 +69,7 @@ class WebTestCompiler {
}
final String platformDillPath = _fileSystem.path.join(
getWebPlatformBinariesDirectory(_artifacts, buildInfo.webRenderer).path,
_artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder).path,
platformDillName
);
......
......@@ -25,7 +25,6 @@ import '../version.dart';
import 'compiler_config.dart';
import 'file_generators/flutter_service_worker_js.dart';
import 'migrations/scrub_generated_plugin_registrant.dart';
import 'web_constants.dart';
export 'compiler_config.dart';
......@@ -59,23 +58,14 @@ class WebBuilder {
String target,
BuildInfo buildInfo,
ServiceWorkerStrategy serviceWorkerStrategy, {
required WebCompilerConfig compilerConfig,
required List<WebCompilerConfig> compilerConfigs,
String? baseHref,
String? outputDirectoryPath,
}) async {
if (compilerConfig.isWasm) {
globals.logger.printBox(
title: 'Experimental feature',
'''
WebAssembly compilation is experimental.
$kWasmMoreInfo''',
);
}
final bool hasWebPlugins =
(await findPlugins(flutterProject)).any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey));
final Directory outputDirectory = outputDirectoryPath == null
? _fileSystem.directory(getWebBuildDirectory(compilerConfig.isWasm))
? _fileSystem.directory(getWebBuildDirectory())
: _fileSystem.directory(outputDirectoryPath);
outputDirectory.createSync(recursive: true);
......@@ -91,7 +81,7 @@ class WebBuilder {
final Stopwatch sw = Stopwatch()..start();
try {
final BuildResult result = await _buildSystem.build(
WebServiceWorker(_fileSystem, buildInfo.webRenderer, isWasm: compilerConfig.isWasm),
WebServiceWorker(_fileSystem, compilerConfigs),
Environment(
projectDir: _fileSystem.currentDirectory,
outputDir: outputDirectory,
......@@ -101,7 +91,6 @@ class WebBuilder {
kHasWebPlugins: hasWebPlugins.toString(),
if (baseHref != null) kBaseHref: baseHref,
kServiceWorkerStrategy: serviceWorkerStrategy.cliName,
...compilerConfig.toBuildSystemEnvironment(),
...buildInfo.toBuildSystemEnvironment(),
},
artifacts: globals.artifacts!,
......@@ -134,8 +123,7 @@ class WebBuilder {
}
final String buildSettingsString = _buildEventAnalyticsSettings(
config: compilerConfig,
buildInfo: buildInfo,
configs: compilerConfigs,
);
BuildEvent(
......@@ -151,14 +139,15 @@ class WebBuilder {
));
final Duration elapsedDuration = sw.elapsed;
final String variableName = compilerConfigs.length > 1 ? 'dual-compile' : 'dart2js';
_flutterUsage.sendTiming(
'build',
compilerConfig.isWasm ? 'dart2wasm' : 'dart2js',
variableName,
elapsedDuration,
);
_analytics.send(Event.timing(
workflow: 'build',
variableName: compilerConfig.isWasm ? 'dart2wasm' : 'dart2js',
variableName: variableName,
elapsedMilliseconds: elapsedDuration.inMilliseconds,
));
}
......@@ -245,13 +234,18 @@ const Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> kDartSdkJsMapArtif
};
String _buildEventAnalyticsSettings({
required WebCompilerConfig config,
required BuildInfo buildInfo,
required List<WebCompilerConfig> configs,
}) {
final Map<String, Object> values = <String, Object>{
...config.buildEventAnalyticsValues,
'web-renderer': buildInfo.webRenderer.cliName,
};
final Map<String, Object> values = <String, Object>{};
final List<String> renderers = <String>[];
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
.map((MapEntry<String, Object> e) => '${e.key}: ${e.value};')
......
......@@ -3,58 +3,48 @@
// found in the LICENSE file.
import '../base/utils.dart';
import '../convert.dart';
import 'compile.dart';
abstract class WebCompilerConfig {
const WebCompilerConfig();
enum CompileTarget {
js,
wasm,
}
/// Returns `true` if `this` represents configuration for the Wasm compiler.
///
/// Otherwise, `false`–represents the JavaScript compiler.
bool get isWasm;
sealed class WebCompilerConfig {
const WebCompilerConfig({required this.renderer});
Map<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>{
'wasm-compile': isWasm,
};
String get buildKey;
Map<String, Object> get buildEventAnalyticsValues => <String, Object>{};
}
/// Configuration for the Dart-to-Javascript compiler (dart2js).
class JsCompilerConfig extends WebCompilerConfig {
const JsCompilerConfig({
required this.csp,
required this.dumpInfo,
required this.nativeNullAssertions,
required this.optimizationLevel,
required this.noFrequencyBasedMinification,
required this.sourceMaps,
this.csp = false,
this.dumpInfo = false,
this.nativeNullAssertions = false,
this.optimizationLevel = kDart2jsDefaultOptimizationLevel,
this.noFrequencyBasedMinification = false,
this.sourceMaps = true,
super.renderer = WebRendererMode.auto,
});
/// Instantiates [JsCompilerConfig] suitable for the `flutter run` command.
const JsCompilerConfig.run({required bool nativeNullAssertions})
: this(
csp: false,
dumpInfo: false,
const JsCompilerConfig.run({
required bool nativeNullAssertions,
required WebRendererMode renderer,
}) : this(
nativeNullAssertions: nativeNullAssertions,
noFrequencyBasedMinification: false,
optimizationLevel: kDart2jsDefaultOptimizationLevel,
sourceMaps: true,
renderer: renderer,
);
/// Creates a new [JsCompilerConfig] from build system environment values.
///
/// Should correspond exactly with [toBuildSystemEnvironment].
factory JsCompilerConfig.fromBuildSystemEnvironment(
Map<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.
///
/// Maps to [kDart2jsOptimization].
......@@ -102,17 +92,7 @@ class JsCompilerConfig extends WebCompilerConfig {
final bool sourceMaps;
@override
bool get isWasm => false;
@override
Map<String, String> toBuildSystemEnvironment() => <String, String>{
kCspMode: csp.toString(),
kDart2jsDumpInfo: dumpInfo.toString(),
kNativeNullAssertions: nativeNullAssertions.toString(),
kDart2jsNoFrequencyBasedMinification: noFrequencyBasedMinification.toString(),
kDart2jsOptimization: optimizationLevel,
kSourceMapsEnabled: sourceMaps.toString(),
};
CompileTarget get compileTarget => CompileTarget.js;
/// Arguments to use in both phases: full JS compile and CFE-only.
List<String> toSharedCommandOptions() => <String>[
......@@ -130,25 +110,29 @@ class JsCompilerConfig extends WebCompilerConfig {
if (noFrequencyBasedMinification) '--no-frequency-based-minification',
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.
class WasmCompilerConfig extends WebCompilerConfig {
const WasmCompilerConfig({
required this.omitTypeChecks,
required this.wasmOpt,
this.omitTypeChecks = false,
this.wasmOpt = WasmOptLevel.defaultValue,
super.renderer = WebRendererMode.auto,
});
/// Creates a new [WasmCompilerConfig] from build system environment values.
///
/// Should correspond exactly with [toBuildSystemEnvironment].
factory WasmCompilerConfig.fromBuildSystemEnvironment(
Map<String, String> defines) =>
WasmCompilerConfig(
omitTypeChecks: defines[kOmitTypeChecks] == 'true',
wasmOpt: WasmOptLevel.values.byName(defines[kRunWasmOpt]!),
);
/// Build environment for [omitTypeChecks].
static const String kOmitTypeChecks = 'WasmOmitTypeChecks';
......@@ -162,25 +146,31 @@ class WasmCompilerConfig extends WebCompilerConfig {
final WasmOptLevel wasmOpt;
@override
bool get isWasm => true;
CompileTarget get compileTarget => CompileTarget.wasm;
bool get runWasmOpt => wasmOpt == WasmOptLevel.full || wasmOpt == WasmOptLevel.debug;
@override
Map<String, String> toBuildSystemEnvironment() => <String, String>{
kOmitTypeChecks: omitTypeChecks.toString(),
kRunWasmOpt: wasmOpt.name,
};
bool get runWasmOpt =>
wasmOpt == WasmOptLevel.full || wasmOpt == WasmOptLevel.debug;
List<String> toCommandOptions() => <String>[
if (omitTypeChecks) '--omit-type-checks',
];
if (omitTypeChecks) '--omit-type-checks',
];
@override
Map<String, Object> get buildEventAnalyticsValues => <String, Object>{
...super.buildEventAnalyticsValues,
...toBuildSystemEnvironment(),
};
...super.buildEventAnalyticsValues,
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 {
......@@ -195,8 +185,11 @@ enum WasmOptLevel implements CliEnum {
@override
String get helpText => switch (this) {
WasmOptLevel.none => 'wasm-opt is not run. Fastest build; bigger, slower output.',
WasmOptLevel.debug => 'Similar to `${WasmOptLevel.full.name}`, but member names are preserved. Debugging is easier, but size is a bit bigger.',
WasmOptLevel.full => 'wasm-opt is run. Build time is slower, but output is smaller and faster.',
};
WasmOptLevel.none =>
'wasm-opt is not run. Fastest build; bigger, slower output.',
WasmOptLevel.debug =>
'Similar to `${WasmOptLevel.full.name}`, but member names are preserved. Debugging is easier, but size is a bit bigger.',
WasmOptLevel.full =>
'wasm-opt is run. Build time is slower, but output is smaller and faster.',
};
}
......@@ -11,11 +11,13 @@ import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/web.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/commands/build_web.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/web/compile.dart';
import '../../src/common.dart';
import '../../src/context.dart';
......@@ -147,15 +149,9 @@ void main() {
expect(environment.defines, <String, String>{
'TargetFile': 'lib/main.dart',
'HasWebPlugins': 'true',
'cspMode': 'false',
'SourceMaps': 'false',
'NativeNullAssertions': 'true',
'ServiceWorkerStrategy': 'offline-first',
'Dart2jsDumpInfo': 'false',
'Dart2jsNoFrequencyBasedMinification': 'false',
'Dart2jsOptimization': 'O3',
'BuildMode': 'release',
'DartDefines': 'Zm9vPWE=,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==',
'DartDefines': 'Zm9vPWE=',
'DartObfuscation': 'false',
'TrackWidgetCreation': 'false',
'TreeShakeIcons': 'true',
......@@ -249,15 +245,8 @@ void main() {
expect(environment.defines, <String, String>{
'TargetFile': 'lib/main.dart',
'HasWebPlugins': 'true',
'cspMode': 'false',
'SourceMaps': 'false',
'NativeNullAssertions': 'true',
'ServiceWorkerStrategy': 'offline-first',
'Dart2jsDumpInfo': 'false',
'Dart2jsNoFrequencyBasedMinification': 'false',
'Dart2jsOptimization': 'O4',
'BuildMode': 'release',
'DartDefines': 'RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==',
'DartObfuscation': 'false',
'TrackWidgetCreation': 'false',
'TreeShakeIcons': 'true',
......@@ -288,15 +277,17 @@ void main() {
final CommandRunner<void> runner = createTestCommandRunner(buildCommand);
setupFileSystemForEndToEndTest(fileSystem);
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>{
Platform: () => fakePlatform,
FileSystem: () => fileSystem,
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
ProcessManager: () => processManager,
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)),
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) {
expect(target, isA<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 {
......
......@@ -31,7 +31,6 @@ import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:flutter_tools/src/web/compile.dart';
import 'package:test/fake.dart';
import 'package:unified_analytics/unified_analytics.dart' as analytics;
import 'package:vm_service/vm_service.dart';
......@@ -1086,47 +1085,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', () {
late FakeAnsiTerminal fakeTerminal;
......
......@@ -21,10 +21,10 @@ import '../../src/fake_process_manager.dart';
void main() {
late FileSystem fileSystem;
late Environment environment;
late Target fooTarget;
late Target barTarget;
late Target fizzTarget;
late Target sharedTarget;
late TestTarget fooTarget;
late TestTarget barTarget;
late TestTarget fizzTarget;
late TestTarget sharedTarget;
late int fooInvocations;
late int barInvocations;
late int shared;
......@@ -138,6 +138,23 @@ void main() {
json.decode(stampFile.readAsStringSync()));
expect(stampContents, containsPair('inputs', <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 {
......@@ -168,6 +185,19 @@ void main() {
expect(fooInvocations, 2);
});
testWithoutContext('Re-invoke build if build key is modified', () async {
final BuildSystem buildSystem = setUpBuildSystem(fileSystem);
fooTarget.buildKey = 'old';
await buildSystem.build(fooTarget, environment);
fooTarget.buildKey = 'new';
await buildSystem.build(fooTarget, environment);
expect(fooInvocations, 2);
});
testWithoutContext('does not re-invoke build if input timestamp changes', () async {
final BuildSystem buildSystem = setUpBuildSystem(fileSystem);
await buildSystem.build(fooTarget, environment);
......@@ -723,4 +753,7 @@ class TestTarget extends Target {
@override
List<Source> outputs = <Source>[];
@override
String? buildKey;
}
// 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/build_system/targets/web.dart';
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 = updateDartDefines(dartDefines, WebRendererMode.auto);
expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=true']);
});
test('canvaskit web-renderer with no dart-defines', () {
dartDefines = 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 = 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 = 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 = 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 = updateDartDefines(dartDefines, WebRendererMode.html);
expect(dartDefines, <String>['FLUTTER_WEB_AUTO_DETECT=false','FLUTTER_WEB_USE_SKIA=false']);
});
});
}
......@@ -43,16 +43,11 @@ void main() {
testUsingContext('WebBuilder sets environment on success', () async {
final TestBuildSystem buildSystem =
TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) {
final WebServiceWorker webServiceWorker = target as WebServiceWorker;
expect(webServiceWorker.isWasm, isTrue, reason: 'should be wasm');
expect(webServiceWorker.webRenderer, WebRendererMode.auto);
expect(target, isA<WebServiceWorker>());
expect(environment.defines, <String, String>{
'TargetFile': 'target',
'HasWebPlugins': 'false',
'ServiceWorkerStrategy': ServiceWorkerStrategy.offlineFirst.cliName,
'WasmOmitTypeChecks': 'false',
'RunWasmOpt': 'none',
'BuildMode': 'debug',
'DartObfuscation': 'false',
'TrackWidgetCreation': 'true',
......@@ -77,10 +72,16 @@ void main() {
'target',
BuildInfo.debug,
ServiceWorkerStrategy.offlineFirst,
compilerConfig: const WasmCompilerConfig(
omitTypeChecks: false,
wasmOpt: WasmOptLevel.none,
),
compilerConfigs: <WebCompilerConfig>[
const WasmCompilerConfig(
wasmOpt: WasmOptLevel.none,
renderer: WebRendererMode.skwasm,
),
const JsCompilerConfig.run(
nativeNullAssertions: true,
renderer: WebRendererMode.canvaskit,
),
],
);
expect(logger.statusText, contains('Compiling target for the Web...'));
......@@ -102,7 +103,7 @@ void main() {
label: 'web-compile',
parameters: CustomDimensions(
buildEventSettings:
'RunWasmOpt: none; WasmOmitTypeChecks: false; wasm-compile: true; web-renderer: auto;',
'RunWasmOpt: none; WasmOmitTypeChecks: false; web-renderer: skwasm,canvaskit; web-target: wasm,js;',
),
),
],
......@@ -115,7 +116,7 @@ void main() {
Event.flutterBuildInfo(
label: 'web-compile',
buildType: 'web',
settings: 'RunWasmOpt: none; WasmOmitTypeChecks: false; wasm-compile: true; web-renderer: auto;',
settings: 'RunWasmOpt: none; WasmOmitTypeChecks: false; web-renderer: skwasm,canvaskit; web-target: wasm,js;',
),
]),
);
......@@ -123,12 +124,12 @@ void main() {
// Sends timing event.
final TestTimingEvent timingEvent = testUsage.timings.single;
expect(timingEvent.category, 'build');
expect(timingEvent.variableName, 'dart2wasm');
expect(timingEvent.variableName, 'dual-compile');
expect(
analyticsTimingEventExists(
sentEvents: fakeAnalytics.sentEvents,
workflow: 'build',
variableName: 'dart2wasm',
variableName: 'dual-compile',
),
true,
);
......@@ -161,7 +162,9 @@ void main() {
'target',
BuildInfo.debug,
ServiceWorkerStrategy.offlineFirst,
compilerConfig: const JsCompilerConfig.run(nativeNullAssertions: true),
compilerConfigs: <WebCompilerConfig>[
const JsCompilerConfig.run(nativeNullAssertions: true, renderer: WebRendererMode.auto),
]
),
throwsToolExit(message: 'Failed to compile application for the Web.'));
......
......@@ -66,6 +66,7 @@ void main() {
<String, String>{},
<String, String>{},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
);
releaseAssetServer = ReleaseAssetServer(
globals.fs.file('main.dart').uri,
......@@ -291,6 +292,7 @@ void main() {
<String, String>{},
<String, String>{},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
);
expect(webAssetServer.basePath, 'foo/bar');
......@@ -310,6 +312,7 @@ void main() {
<String, String>{},
<String, String>{},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
);
// Defaults to "/" when there's no base element.
......@@ -331,6 +334,7 @@ void main() {
<String, String>{},
<String, String>{},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
),
throwsToolExit(),
);
......@@ -351,6 +355,7 @@ void main() {
<String, String>{},
<String, String>{},
NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
),
throwsToolExit(),
);
......@@ -684,6 +689,7 @@ void main() {
extraHeaders: const <String, String>{},
chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.unsound,
webRenderer: WebRendererMode.html,
);
webDevFS.requireJS.createSync(recursive: true);
webDevFS.flutterJs.createSync(recursive: true);
......@@ -745,13 +751,6 @@ void main() {
// New SDK should be visible..
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'BELLOW');
// Toggle CanvasKit
expect(webDevFS.webAssetServer.webRenderer, WebRendererMode.html);
webDevFS.webAssetServer.webRenderer = WebRendererMode.canvaskit;
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'OL');
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js.map'), 'CHUM');
// Generated entrypoint.
expect(await webDevFS.webAssetServer.dartSourceContents('web_entrypoint.dart'),
contains('GENERATED'));
......@@ -800,6 +799,7 @@ void main() {
extraHeaders: const <String, String>{},
chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.sound,
webRenderer: WebRendererMode.html,
);
webDevFS.requireJS.createSync(recursive: true);
webDevFS.flutterJs.createSync(recursive: true);
......@@ -861,11 +861,6 @@ void main() {
// New SDK should be visible..
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'BELLOW');
// Toggle CanvasKit
webDevFS.webAssetServer.webRenderer = WebRendererMode.canvaskit;
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'OL');
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js.map'), 'CHUM');
// Generated entrypoint.
expect(await webDevFS.webAssetServer.dartSourceContents('web_entrypoint.dart'),
contains('GENERATED'));
......@@ -913,6 +908,7 @@ void main() {
extraHeaders: const <String, String>{},
chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.sound,
webRenderer: WebRendererMode.canvaskit,
);
webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true);
......@@ -974,6 +970,7 @@ void main() {
nullAssertions: true,
nativeNullAssertions: true,
nullSafetyMode: NullSafetyMode.sound,
webRenderer: WebRendererMode.canvaskit,
);
webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true);
......@@ -1019,6 +1016,7 @@ void main() {
extraHeaders: const <String, String>{},
chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.sound,
webRenderer: WebRendererMode.canvaskit,
);
webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true);
......@@ -1065,6 +1063,7 @@ void main() {
extraHeaders: const <String, String>{},
chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.sound,
webRenderer: WebRendererMode.auto,
);
webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true);
......@@ -1112,6 +1111,7 @@ void main() {
extraHeaders: const <String, String>{},
chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
);
webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true);
......@@ -1148,7 +1148,9 @@ void main() {
null,
const <String, String>{},
NullSafetyMode.unsound,
testMode: true);
webRenderer: WebRendererMode.canvaskit,
testMode: true
);
expect(webAssetServer.defaultResponseHeaders['x-frame-options'], null);
await webAssetServer.dispose();
......@@ -1180,7 +1182,9 @@ void main() {
extraHeaderKey: extraHeaderValue,
},
NullSafetyMode.unsound,
testMode: true);
webRenderer: WebRendererMode.canvaskit,
testMode: true
);
expect(webAssetServer.defaultResponseHeaders[extraHeaderKey], <String>[extraHeaderValue]);
......@@ -1215,6 +1219,7 @@ void main() {
<String, String>{},
<String, String>{},
NullSafetyMode.sound,
webRenderer: WebRendererMode.canvaskit,
);
expect(await webAssetServer.metadataContents('foo/main_module.ddc_merged_metadata'), null);
......@@ -1257,6 +1262,7 @@ void main() {
extraHeaders: const <String, String>{},
chromiumLauncher: null,
nullSafetyMode: NullSafetyMode.unsound,
webRenderer: WebRendererMode.canvaskit,
);
webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true);
......
......@@ -51,7 +51,7 @@ void main() {
final Directory appBuildDir = fileSystem.directory(fileSystem.path.join(
exampleAppDir.path,
'build',
'web_wasm',
'web',
));
for (final String filename in const <String>[
'flutter.js',
......
......@@ -19,6 +19,7 @@ void main() async {
await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsCallback), name: 'flutter.js (callback)');
await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsPromisesFull), name: 'flutter.js (promises)');
await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsPromisesShort), name: 'flutter.js (promises, short)');
await _testProject(HotReloadProject(indexHtml: indexHtmlFlutterJsLoad), name: 'flutter.js (load)');
await _testProject(HotReloadProject(indexHtml: indexHtmlNoFlutterJs), name: 'No flutter.js');
}
......
......@@ -56,6 +56,27 @@ String indexHtmlFlutterJsPromisesFull = _generateFlutterJsIndexHtml('''
});
''');
/// index_with_flutterjs.html
String indexHtmlFlutterJsLoad = _generateFlutterJsIndexHtml('''
window.addEventListener('load', function(ev) {
_flutter.buildConfig = {
builds: [
{
"compileTarget": "dartdevc",
"renderer": "html",
"mainJsPath": "main.dart.js",
}
]
};
// Download main.dart.js
_flutter.loader.load({
serviceWorkerSettings: {
serviceWorkerVersion: serviceWorkerVersion,
},
});
});
''');
/// index_without_flutterjs.html
String indexHtmlNoFlutterJs = '''
<!DOCTYPE HTML>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment