Skip to content

Commit 9f2c5d8

Browse files
Support flutter build web --wasm (#117075)
* Work in progress. * Some fixes to the command line. * Bootstrapping works. * Change kickoff order to maximize concurrency. * Fix analyzer errors and formatting issues. * Fix doc comment. * Added unit tests for some of the web targets. * Format issue. * Add an integration test that builds an app to wasm. * Add a todo for depfiles. * Formatting. * Apparently the license header needs to say 2014. * `file://` URIs confuse dart2wasm on Windows. Just use absolute paths. * Update unit tests to match new path passing. * Have a distinct build directory for wasm, and fixes for some upstream changes.
1 parent 70f391d commit 9f2c5d8

File tree

9 files changed

+322
-61
lines changed

9 files changed

+322
-61
lines changed

packages/flutter_tools/lib/src/artifacts.dart

+21-4
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@ enum Artifact {
3434
engineDartSdkPath,
3535
/// The dart binary used to execute any of the required snapshots.
3636
engineDartBinary,
37+
/// The dart binary for running aot snapshots
38+
engineDartAotRuntime,
3739
/// The snapshot of frontend_server compiler.
3840
frontendServerSnapshotForEngineDartSdk,
3941
/// The dart snapshot of the dart2js compiler.
4042
dart2jsSnapshot,
43+
/// The dart snapshot of the dart2wasm compiler.
44+
dart2wasmSnapshot,
4145

4246
/// The root of the Linux desktop sources.
4347
linuxDesktopPath,
@@ -168,8 +172,12 @@ String? _artifactToFileName(Artifact artifact, Platform hostPlatform, [ BuildMod
168172
return 'dart-sdk';
169173
case Artifact.engineDartBinary:
170174
return 'dart$exe';
175+
case Artifact.engineDartAotRuntime:
176+
return 'dartaotruntime$exe';
171177
case Artifact.dart2jsSnapshot:
172178
return 'dart2js.dart.snapshot';
179+
case Artifact.dart2wasmSnapshot:
180+
return 'dart2wasm_product.snapshot';
173181
case Artifact.frontendServerSnapshotForEngineDartSdk:
174182
return 'frontend_server.dart.snapshot';
175183
case Artifact.linuxDesktopPath:
@@ -488,7 +496,9 @@ class CachedArtifacts implements Artifacts {
488496
return _fileSystem.path.join(engineDir, hostPlatform, _artifactToFileName(artifact, _platform));
489497
case Artifact.engineDartSdkPath:
490498
case Artifact.engineDartBinary:
499+
case Artifact.engineDartAotRuntime:
491500
case Artifact.dart2jsSnapshot:
501+
case Artifact.dart2wasmSnapshot:
492502
case Artifact.frontendServerSnapshotForEngineDartSdk:
493503
case Artifact.constFinder:
494504
case Artifact.flutterFramework:
@@ -525,7 +535,9 @@ class CachedArtifacts implements Artifacts {
525535
return _getIosEngineArtifactPath(engineDir, environmentType, _fileSystem, _platform);
526536
case Artifact.engineDartSdkPath:
527537
case Artifact.engineDartBinary:
538+
case Artifact.engineDartAotRuntime:
528539
case Artifact.dart2jsSnapshot:
540+
case Artifact.dart2wasmSnapshot:
529541
case Artifact.frontendServerSnapshotForEngineDartSdk:
530542
case Artifact.constFinder:
531543
case Artifact.flutterMacOSFramework:
@@ -580,7 +592,9 @@ class CachedArtifacts implements Artifacts {
580592
case Artifact.fontSubset:
581593
case Artifact.engineDartSdkPath:
582594
case Artifact.engineDartBinary:
595+
case Artifact.engineDartAotRuntime:
583596
case Artifact.dart2jsSnapshot:
597+
case Artifact.dart2wasmSnapshot:
584598
case Artifact.frontendServerSnapshotForEngineDartSdk:
585599
case Artifact.icuData:
586600
case Artifact.isolateSnapshotData:
@@ -613,6 +627,7 @@ class CachedArtifacts implements Artifacts {
613627
// android_arm in profile mode because it is available on all supported host platforms.
614628
return _getAndroidArtifactPath(artifact, TargetPlatform.android_arm, BuildMode.profile);
615629
case Artifact.dart2jsSnapshot:
630+
case Artifact.dart2wasmSnapshot:
616631
case Artifact.frontendServerSnapshotForEngineDartSdk:
617632
return _fileSystem.path.join(
618633
_dartSdkPath(_cache), 'bin', 'snapshots',
@@ -634,6 +649,7 @@ class CachedArtifacts implements Artifacts {
634649
case Artifact.engineDartSdkPath:
635650
return _dartSdkPath(_cache);
636651
case Artifact.engineDartBinary:
652+
case Artifact.engineDartAotRuntime:
637653
return _fileSystem.path.join(_dartSdkPath(_cache), 'bin', _artifactToFileName(artifact, _platform));
638654
case Artifact.flutterMacOSFramework:
639655
case Artifact.linuxDesktopPath:
@@ -925,13 +941,12 @@ class CachedLocalEngineArtifacts implements Artifacts {
925941
case Artifact.engineDartSdkPath:
926942
return _getDartSdkPath();
927943
case Artifact.engineDartBinary:
944+
case Artifact.engineDartAotRuntime:
928945
return _fileSystem.path.join(_getDartSdkPath(), 'bin', artifactFileName);
929946
case Artifact.dart2jsSnapshot:
930-
return _fileSystem.path.join(_getDartSdkPath(), 'bin', 'snapshots', artifactFileName);
947+
case Artifact.dart2wasmSnapshot:
931948
case Artifact.frontendServerSnapshotForEngineDartSdk:
932-
return _fileSystem.path.join(
933-
_getDartSdkPath(), 'bin', 'snapshots', artifactFileName,
934-
);
949+
return _fileSystem.path.join(_getDartSdkPath(), 'bin', 'snapshots', artifactFileName);
935950
}
936951
}
937952

@@ -1048,10 +1063,12 @@ class CachedLocalWebSdkArtifacts implements Artifacts {
10481063
case Artifact.engineDartSdkPath:
10491064
return _getDartSdkPath();
10501065
case Artifact.engineDartBinary:
1066+
case Artifact.engineDartAotRuntime:
10511067
return _fileSystem.path.join(
10521068
_getDartSdkPath(), 'bin',
10531069
_artifactToFileName(artifact, _platform, mode));
10541070
case Artifact.dart2jsSnapshot:
1071+
case Artifact.dart2wasmSnapshot:
10551072
case Artifact.frontendServerSnapshotForEngineDartSdk:
10561073
return _fileSystem.path.join(
10571074
_getDartSdkPath(), 'bin', 'snapshots',

packages/flutter_tools/lib/src/build_info.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -914,8 +914,8 @@ String getMacOSBuildDirectory() {
914914
}
915915

916916
/// Returns the web build output directory.
917-
String getWebBuildDirectory() {
918-
return globals.fs.path.join(getBuildDirectory(), 'web');
917+
String getWebBuildDirectory([bool isWasm = false]) {
918+
return globals.fs.path.join(getBuildDirectory(), isWasm ? 'web_wasm' : 'web');
919919
}
920920

921921
/// Returns the Linux build output directory.

packages/flutter_tools/lib/src/build_system/targets/web.dart

+127-30
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import '../../web/compile.dart';
2222
import '../../web/file_generators/flutter_js.dart' as flutter_js;
2323
import '../../web/file_generators/flutter_service_worker_js.dart';
2424
import '../../web/file_generators/main_dart.dart' as main_dart;
25+
import '../../web/file_generators/wasm_bootstrap.dart' as wasm_bootstrap;
2526
import '../build_system.dart';
2627
import '../depfile.dart';
2728
import '../exceptions.dart';
@@ -141,13 +142,11 @@ class WebEntrypointTarget extends Target {
141142
}
142143

143144
/// Compiles a web entry point with dart2js.
144-
class Dart2JSTarget extends Target {
145-
const Dart2JSTarget(this.webRenderer);
145+
abstract class Dart2WebTarget extends Target {
146+
const Dart2WebTarget(this.webRenderer);
146147

147148
final WebRendererMode webRenderer;
148-
149-
@override
150-
String get name => 'dart2js';
149+
Source get compilerSnapshot;
151150

152151
@override
153152
List<Target> get dependencies => const <Target>[
@@ -156,22 +155,17 @@ class Dart2JSTarget extends Target {
156155
];
157156

158157
@override
159-
List<Source> get inputs => const <Source>[
160-
Source.hostArtifact(HostArtifact.flutterWebSdk),
161-
Source.artifact(Artifact.dart2jsSnapshot),
162-
Source.artifact(Artifact.engineDartBinary),
163-
Source.pattern('{BUILD_DIR}/main.dart'),
164-
Source.pattern('{PROJECT_DIR}/.dart_tool/package_config_subset'),
158+
List<Source> get inputs => <Source>[
159+
const Source.hostArtifact(HostArtifact.flutterWebSdk),
160+
compilerSnapshot,
161+
const Source.artifact(Artifact.engineDartBinary),
162+
const Source.pattern('{BUILD_DIR}/main.dart'),
163+
const Source.pattern('{PROJECT_DIR}/.dart_tool/package_config_subset'),
165164
];
166165

167166
@override
168167
List<Source> get outputs => const <Source>[];
169168

170-
@override
171-
List<String> get depfiles => const <String>[
172-
'dart2js.d',
173-
];
174-
175169
String _collectOutput(ProcessResult result) {
176170
final String stdout = result.stdout is List<int>
177171
? utf8.decode(result.stdout as List<int>)
@@ -181,6 +175,21 @@ class Dart2JSTarget extends Target {
181175
: result.stderr as String;
182176
return stdout + stderr;
183177
}
178+
}
179+
180+
class Dart2JSTarget extends Dart2WebTarget {
181+
Dart2JSTarget(super.webRenderer);
182+
183+
@override
184+
String get name => 'dart2js';
185+
186+
@override
187+
Source get compilerSnapshot => const Source.artifact(Artifact.dart2jsSnapshot);
188+
189+
@override
190+
List<String> get depfiles => const <String>[
191+
'dart2js.d',
192+
];
184193

185194
@override
186195
Future<void> build(Environment environment) async {
@@ -270,29 +279,94 @@ class Dart2JSTarget extends Target {
270279
}
271280
}
272281

273-
/// Unpacks the dart2js compilation and resources to a given output directory.
282+
class Dart2WasmTarget extends Dart2WebTarget {
283+
Dart2WasmTarget(super.webRenderer);
284+
285+
@override
286+
Future<void> build(Environment environment) async {
287+
final String? buildModeEnvironment = environment.defines[kBuildMode];
288+
if (buildModeEnvironment == null) {
289+
throw MissingDefineException(kBuildMode, name);
290+
}
291+
final BuildMode buildMode = getBuildModeForName(buildModeEnvironment);
292+
final Artifacts artifacts = globals.artifacts!;
293+
final File outputWasmFile = environment.buildDir.childFile('main.dart.wasm');
294+
final String dartSdkPath = artifacts.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript);
295+
final String dartSdkRoot = environment.fileSystem.directory(dartSdkPath).parent.path;
296+
297+
final List<String> compilationArgs = <String>[
298+
artifacts.getArtifactPath(Artifact.engineDartAotRuntime, platform: TargetPlatform.web_javascript),
299+
'--disable-dart-dev',
300+
artifacts.getArtifactPath(Artifact.dart2wasmSnapshot, platform: TargetPlatform.web_javascript),
301+
if (buildMode == BuildMode.profile)
302+
'-Ddart.vm.profile=true'
303+
else
304+
'-Ddart.vm.product=true',
305+
...decodeCommaSeparated(environment.defines, kExtraFrontEndOptions),
306+
for (final String dartDefine in decodeDartDefines(environment.defines, kDartDefines))
307+
'-D$dartDefine',
308+
'--packages=.dart_tool/package_config.json',
309+
'--dart-sdk=$dartSdkPath',
310+
'--multi-root-scheme',
311+
'org-dartlang-sdk',
312+
'--multi-root',
313+
artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path,
314+
'--multi-root',
315+
dartSdkRoot,
316+
'--libraries-spec',
317+
artifacts.getHostArtifact(HostArtifact.flutterWebLibrariesJson).path,
318+
319+
environment.buildDir.childFile('main.dart').path, // dartfile
320+
outputWasmFile.path,
321+
];
322+
final ProcessResult compileResult = await globals.processManager.run(compilationArgs);
323+
if (compileResult.exitCode != 0) {
324+
throw Exception(_collectOutput(compileResult));
325+
}
326+
}
327+
328+
@override
329+
Source get compilerSnapshot => const Source.artifact(Artifact.dart2wasmSnapshot);
330+
331+
@override
332+
String get name => 'dart2wasm';
333+
334+
@override
335+
List<Source> get outputs => const <Source>[
336+
Source.pattern('{OUTPUT_DIR}/main.dart.wasm'),
337+
];
338+
339+
// TODO(jacksongardner): override `depfiles` once dart2wasm begins producing
340+
// them: https://github.com/dart-lang/sdk/issues/50747
341+
}
342+
343+
/// Unpacks the dart2js or dart2wasm compilation and resources to a given
344+
/// output directory.
274345
class WebReleaseBundle extends Target {
275-
const WebReleaseBundle(this.webRenderer);
346+
const WebReleaseBundle(this.webRenderer, this.isWasm);
276347

277348
final WebRendererMode webRenderer;
349+
final bool isWasm;
350+
351+
String get outputFileName => isWasm ? 'main.dart.wasm' : 'main.dart.js';
278352

279353
@override
280354
String get name => 'web_release_bundle';
281355

282356
@override
283357
List<Target> get dependencies => <Target>[
284-
Dart2JSTarget(webRenderer),
358+
if (isWasm) Dart2WasmTarget(webRenderer) else Dart2JSTarget(webRenderer),
285359
];
286360

287361
@override
288-
List<Source> get inputs => const <Source>[
289-
Source.pattern('{BUILD_DIR}/main.dart.js'),
290-
Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
362+
List<Source> get inputs => <Source>[
363+
Source.pattern('{BUILD_DIR}/$outputFileName'),
364+
const Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
291365
];
292366

293367
@override
294-
List<Source> get outputs => const <Source>[
295-
Source.pattern('{OUTPUT_DIR}/main.dart.js'),
368+
List<Source> get outputs => <Source>[
369+
Source.pattern('{OUTPUT_DIR}/$outputFileName'),
296370
];
297371

298372
@override
@@ -306,7 +380,7 @@ class WebReleaseBundle extends Target {
306380
Future<void> build(Environment environment) async {
307381
for (final File outputFile in environment.buildDir.listSync(recursive: true).whereType<File>()) {
308382
final String basename = globals.fs.path.basename(outputFile.path);
309-
if (!basename.contains('main.dart.js')) {
383+
if (!basename.contains(outputFileName)) {
310384
continue;
311385
}
312386
// Do not copy the deps file.
@@ -318,6 +392,12 @@ class WebReleaseBundle extends Target {
318392
);
319393
}
320394

395+
if (isWasm) {
396+
// TODO(jacksongardner): Enable icon tree shaking once dart2wasm can do a two-phase compile.
397+
// https://github.com/flutter/flutter/issues/117248
398+
environment.defines[kIconTreeShakerFlag] = 'false';
399+
}
400+
321401
createVersionFile(environment, environment.defines);
322402
final Directory outputDirectory = environment.outputDir.childDirectory('assets');
323403
outputDirectory.createSync(recursive: true);
@@ -413,10 +493,11 @@ class WebReleaseBundle extends Target {
413493
/// These assets can be cached forever and are only invalidated when the
414494
/// Flutter SDK is upgraded to a new version.
415495
class WebBuiltInAssets extends Target {
416-
const WebBuiltInAssets(this.fileSystem, this.cache);
496+
const WebBuiltInAssets(this.fileSystem, this.cache, this.isWasm);
417497

418498
final FileSystem fileSystem;
419499
final Cache cache;
500+
final bool isWasm;
420501

421502
@override
422503
String get name => 'web_static_assets';
@@ -451,6 +532,21 @@ class WebBuiltInAssets extends Target {
451532
file.copySync(targetPath);
452533
}
453534

535+
if (isWasm) {
536+
final String dartSdkPath =
537+
globals.artifacts!.getArtifactPath(Artifact.engineDartSdkPath);
538+
final File dart2wasmRuntime = fileSystem.directory(dartSdkPath)
539+
.childDirectory('bin')
540+
.childFile('dart2wasm_runtime.mjs');
541+
final String targetPath = fileSystem.path.join(
542+
environment.outputDir.path,
543+
'dart2wasm_runtime.mjs');
544+
dart2wasmRuntime.copySync(targetPath);
545+
546+
final File bootstrapFile = environment.outputDir.childFile('main.dart.js');
547+
bootstrapFile.writeAsStringSync(wasm_bootstrap.generateWasmBootstrapFile());
548+
}
549+
454550
// Write the flutter.js file
455551
final File flutterJsFile = environment.outputDir.childFile('flutter.js');
456552
flutterJsFile.writeAsStringSync(flutter_js.generateFlutterJsFile());
@@ -459,20 +555,21 @@ class WebBuiltInAssets extends Target {
459555

460556
/// Generate a service worker for a web target.
461557
class WebServiceWorker extends Target {
462-
const WebServiceWorker(this.fileSystem, this.cache, this.webRenderer);
558+
const WebServiceWorker(this.fileSystem, this.cache, this.webRenderer, this.isWasm);
463559

464560
final FileSystem fileSystem;
465561
final Cache cache;
466562
final WebRendererMode webRenderer;
563+
final bool isWasm;
467564

468565
@override
469566
String get name => 'web_service_worker';
470567

471568
@override
472569
List<Target> get dependencies => <Target>[
473-
Dart2JSTarget(webRenderer),
474-
WebReleaseBundle(webRenderer),
475-
WebBuiltInAssets(fileSystem, cache),
570+
if (isWasm) Dart2WasmTarget(webRenderer) else Dart2JSTarget(webRenderer),
571+
WebReleaseBundle(webRenderer, isWasm),
572+
WebBuiltInAssets(fileSystem, cache, isWasm),
476573
];
477574

478575
@override

packages/flutter_tools/lib/src/commands/build_web.dart

+5
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ class BuildWebCommand extends BuildSubCommand {
4242
'to view and debug the original source code of a compiled and minified Dart '
4343
'application.'
4444
);
45+
argParser.addFlag(
46+
'wasm',
47+
help: 'Compile to WebAssembly rather than Javascript (experimental).'
48+
);
4549

4650
argParser.addOption('pwa-strategy',
4751
defaultsTo: kOfflineFirst,
@@ -140,6 +144,7 @@ class BuildWebCommand extends BuildSubCommand {
140144
stringArgDeprecated('pwa-strategy')!,
141145
boolArgDeprecated('source-maps'),
142146
boolArgDeprecated('native-null-assertions'),
147+
boolArgDeprecated('wasm'),
143148
baseHref: baseHref,
144149
dart2jsOptimization: stringArgDeprecated('dart2js-optimization'),
145150
outputDirectoryPath: outputDirectoryPath,

0 commit comments

Comments
 (0)