From 9f2c5d8e21cccb0704b0d1970de3b43438971e97 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 19 Dec 2022 12:09:02 -0800 Subject: [PATCH] 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. --- packages/flutter_tools/lib/src/artifacts.dart | 25 ++- .../flutter_tools/lib/src/build_info.dart | 4 +- .../lib/src/build_system/targets/web.dart | 157 ++++++++++++++---- .../lib/src/commands/build_web.dart | 5 + .../lib/src/isolated/resident_web_runner.dart | 2 + .../flutter_tools/lib/src/web/compile.dart | 7 +- .../web/file_generators/wasm_bootstrap.dart | 31 ++++ .../build_system/targets/web_test.dart | 92 +++++++--- .../flutter_build_wasm_test.dart | 60 +++++++ 9 files changed, 322 insertions(+), 61 deletions(-) create mode 100644 packages/flutter_tools/lib/src/web/file_generators/wasm_bootstrap.dart create mode 100644 packages/flutter_tools/test/integration.shard/flutter_build_wasm_test.dart diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index c279fcebc6b5..a6e8a8294576 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart @@ -34,10 +34,14 @@ enum Artifact { engineDartSdkPath, /// The dart binary used to execute any of the required snapshots. engineDartBinary, + /// The dart binary for running aot snapshots + engineDartAotRuntime, /// The snapshot of frontend_server compiler. frontendServerSnapshotForEngineDartSdk, /// The dart snapshot of the dart2js compiler. dart2jsSnapshot, + /// The dart snapshot of the dart2wasm compiler. + dart2wasmSnapshot, /// The root of the Linux desktop sources. linuxDesktopPath, @@ -168,8 +172,12 @@ String? _artifactToFileName(Artifact artifact, Platform hostPlatform, [ BuildMod return 'dart-sdk'; case Artifact.engineDartBinary: return 'dart$exe'; + case Artifact.engineDartAotRuntime: + return 'dartaotruntime$exe'; case Artifact.dart2jsSnapshot: return 'dart2js.dart.snapshot'; + case Artifact.dart2wasmSnapshot: + return 'dart2wasm_product.snapshot'; case Artifact.frontendServerSnapshotForEngineDartSdk: return 'frontend_server.dart.snapshot'; case Artifact.linuxDesktopPath: @@ -488,7 +496,9 @@ class CachedArtifacts implements Artifacts { return _fileSystem.path.join(engineDir, hostPlatform, _artifactToFileName(artifact, _platform)); case Artifact.engineDartSdkPath: case Artifact.engineDartBinary: + case Artifact.engineDartAotRuntime: case Artifact.dart2jsSnapshot: + case Artifact.dart2wasmSnapshot: case Artifact.frontendServerSnapshotForEngineDartSdk: case Artifact.constFinder: case Artifact.flutterFramework: @@ -525,7 +535,9 @@ class CachedArtifacts implements Artifacts { return _getIosEngineArtifactPath(engineDir, environmentType, _fileSystem, _platform); case Artifact.engineDartSdkPath: case Artifact.engineDartBinary: + case Artifact.engineDartAotRuntime: case Artifact.dart2jsSnapshot: + case Artifact.dart2wasmSnapshot: case Artifact.frontendServerSnapshotForEngineDartSdk: case Artifact.constFinder: case Artifact.flutterMacOSFramework: @@ -580,7 +592,9 @@ class CachedArtifacts implements Artifacts { case Artifact.fontSubset: case Artifact.engineDartSdkPath: case Artifact.engineDartBinary: + case Artifact.engineDartAotRuntime: case Artifact.dart2jsSnapshot: + case Artifact.dart2wasmSnapshot: case Artifact.frontendServerSnapshotForEngineDartSdk: case Artifact.icuData: case Artifact.isolateSnapshotData: @@ -613,6 +627,7 @@ class CachedArtifacts implements Artifacts { // android_arm in profile mode because it is available on all supported host platforms. return _getAndroidArtifactPath(artifact, TargetPlatform.android_arm, BuildMode.profile); case Artifact.dart2jsSnapshot: + case Artifact.dart2wasmSnapshot: case Artifact.frontendServerSnapshotForEngineDartSdk: return _fileSystem.path.join( _dartSdkPath(_cache), 'bin', 'snapshots', @@ -634,6 +649,7 @@ class CachedArtifacts implements Artifacts { case Artifact.engineDartSdkPath: return _dartSdkPath(_cache); case Artifact.engineDartBinary: + case Artifact.engineDartAotRuntime: return _fileSystem.path.join(_dartSdkPath(_cache), 'bin', _artifactToFileName(artifact, _platform)); case Artifact.flutterMacOSFramework: case Artifact.linuxDesktopPath: @@ -925,13 +941,12 @@ class CachedLocalEngineArtifacts implements Artifacts { case Artifact.engineDartSdkPath: return _getDartSdkPath(); case Artifact.engineDartBinary: + case Artifact.engineDartAotRuntime: return _fileSystem.path.join(_getDartSdkPath(), 'bin', artifactFileName); case Artifact.dart2jsSnapshot: - return _fileSystem.path.join(_getDartSdkPath(), 'bin', 'snapshots', artifactFileName); + case Artifact.dart2wasmSnapshot: case Artifact.frontendServerSnapshotForEngineDartSdk: - return _fileSystem.path.join( - _getDartSdkPath(), 'bin', 'snapshots', artifactFileName, - ); + return _fileSystem.path.join(_getDartSdkPath(), 'bin', 'snapshots', artifactFileName); } } @@ -1048,10 +1063,12 @@ class CachedLocalWebSdkArtifacts implements Artifacts { case Artifact.engineDartSdkPath: return _getDartSdkPath(); case Artifact.engineDartBinary: + case Artifact.engineDartAotRuntime: return _fileSystem.path.join( _getDartSdkPath(), 'bin', _artifactToFileName(artifact, _platform, mode)); case Artifact.dart2jsSnapshot: + case Artifact.dart2wasmSnapshot: case Artifact.frontendServerSnapshotForEngineDartSdk: return _fileSystem.path.join( _getDartSdkPath(), 'bin', 'snapshots', diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index 02d1d3e4f74a..5fe7fbaaa554 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -914,8 +914,8 @@ String getMacOSBuildDirectory() { } /// Returns the web build output directory. -String getWebBuildDirectory() { - return globals.fs.path.join(getBuildDirectory(), 'web'); +String getWebBuildDirectory([bool isWasm = false]) { + return globals.fs.path.join(getBuildDirectory(), isWasm ? 'web_wasm' : 'web'); } /// Returns the Linux build output directory. diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart index 8911d5c359ad..354fb8898c91 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -22,6 +22,7 @@ import '../../web/compile.dart'; import '../../web/file_generators/flutter_js.dart' as flutter_js; import '../../web/file_generators/flutter_service_worker_js.dart'; import '../../web/file_generators/main_dart.dart' as main_dart; +import '../../web/file_generators/wasm_bootstrap.dart' as wasm_bootstrap; import '../build_system.dart'; import '../depfile.dart'; import '../exceptions.dart'; @@ -141,13 +142,11 @@ class WebEntrypointTarget extends Target { } /// Compiles a web entry point with dart2js. -class Dart2JSTarget extends Target { - const Dart2JSTarget(this.webRenderer); +abstract class Dart2WebTarget extends Target { + const Dart2WebTarget(this.webRenderer); final WebRendererMode webRenderer; - - @override - String get name => 'dart2js'; + Source get compilerSnapshot; @override List get dependencies => const [ @@ -156,22 +155,17 @@ class Dart2JSTarget extends Target { ]; @override - List get inputs => const [ - Source.hostArtifact(HostArtifact.flutterWebSdk), - Source.artifact(Artifact.dart2jsSnapshot), - Source.artifact(Artifact.engineDartBinary), - Source.pattern('{BUILD_DIR}/main.dart'), - Source.pattern('{PROJECT_DIR}/.dart_tool/package_config_subset'), + List get inputs => [ + const Source.hostArtifact(HostArtifact.flutterWebSdk), + compilerSnapshot, + const Source.artifact(Artifact.engineDartBinary), + const Source.pattern('{BUILD_DIR}/main.dart'), + const Source.pattern('{PROJECT_DIR}/.dart_tool/package_config_subset'), ]; @override List get outputs => const []; - @override - List get depfiles => const [ - 'dart2js.d', - ]; - String _collectOutput(ProcessResult result) { final String stdout = result.stdout is List ? utf8.decode(result.stdout as List) @@ -181,6 +175,21 @@ class Dart2JSTarget extends Target { : result.stderr as String; return stdout + stderr; } +} + +class Dart2JSTarget extends Dart2WebTarget { + Dart2JSTarget(super.webRenderer); + + @override + String get name => 'dart2js'; + + @override + Source get compilerSnapshot => const Source.artifact(Artifact.dart2jsSnapshot); + + @override + List get depfiles => const [ + 'dart2js.d', + ]; @override Future build(Environment environment) async { @@ -270,29 +279,94 @@ class Dart2JSTarget extends Target { } } -/// Unpacks the dart2js compilation and resources to a given output directory. +class Dart2WasmTarget extends Dart2WebTarget { + Dart2WasmTarget(super.webRenderer); + + @override + Future build(Environment environment) async { + final String? buildModeEnvironment = environment.defines[kBuildMode]; + if (buildModeEnvironment == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = getBuildModeForName(buildModeEnvironment); + final Artifacts artifacts = globals.artifacts!; + final File outputWasmFile = environment.buildDir.childFile('main.dart.wasm'); + final String dartSdkPath = artifacts.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript); + final String dartSdkRoot = environment.fileSystem.directory(dartSdkPath).parent.path; + + final List compilationArgs = [ + artifacts.getArtifactPath(Artifact.engineDartAotRuntime, platform: TargetPlatform.web_javascript), + '--disable-dart-dev', + artifacts.getArtifactPath(Artifact.dart2wasmSnapshot, platform: TargetPlatform.web_javascript), + if (buildMode == BuildMode.profile) + '-Ddart.vm.profile=true' + else + '-Ddart.vm.product=true', + ...decodeCommaSeparated(environment.defines, kExtraFrontEndOptions), + for (final String dartDefine in decodeDartDefines(environment.defines, kDartDefines)) + '-D$dartDefine', + '--packages=.dart_tool/package_config.json', + '--dart-sdk=$dartSdkPath', + '--multi-root-scheme', + 'org-dartlang-sdk', + '--multi-root', + artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path, + '--multi-root', + dartSdkRoot, + '--libraries-spec', + artifacts.getHostArtifact(HostArtifact.flutterWebLibrariesJson).path, + + environment.buildDir.childFile('main.dart').path, // dartfile + outputWasmFile.path, + ]; + final ProcessResult compileResult = await globals.processManager.run(compilationArgs); + if (compileResult.exitCode != 0) { + throw Exception(_collectOutput(compileResult)); + } + } + + @override + Source get compilerSnapshot => const Source.artifact(Artifact.dart2wasmSnapshot); + + @override + String get name => 'dart2wasm'; + + @override + List get outputs => const [ + Source.pattern('{OUTPUT_DIR}/main.dart.wasm'), + ]; + + // TODO(jacksongardner): override `depfiles` once dart2wasm begins producing + // them: https://github.com/dart-lang/sdk/issues/50747 +} + +/// Unpacks the dart2js or dart2wasm compilation and resources to a given +/// output directory. class WebReleaseBundle extends Target { - const WebReleaseBundle(this.webRenderer); + const WebReleaseBundle(this.webRenderer, this.isWasm); final WebRendererMode webRenderer; + final bool isWasm; + + String get outputFileName => isWasm ? 'main.dart.wasm' : 'main.dart.js'; @override String get name => 'web_release_bundle'; @override List get dependencies => [ - Dart2JSTarget(webRenderer), + if (isWasm) Dart2WasmTarget(webRenderer) else Dart2JSTarget(webRenderer), ]; @override - List get inputs => const [ - Source.pattern('{BUILD_DIR}/main.dart.js'), - Source.pattern('{PROJECT_DIR}/pubspec.yaml'), + List get inputs => [ + Source.pattern('{BUILD_DIR}/$outputFileName'), + const Source.pattern('{PROJECT_DIR}/pubspec.yaml'), ]; @override - List get outputs => const [ - Source.pattern('{OUTPUT_DIR}/main.dart.js'), + List get outputs => [ + Source.pattern('{OUTPUT_DIR}/$outputFileName'), ]; @override @@ -306,7 +380,7 @@ class WebReleaseBundle extends Target { Future build(Environment environment) async { for (final File outputFile in environment.buildDir.listSync(recursive: true).whereType()) { final String basename = globals.fs.path.basename(outputFile.path); - if (!basename.contains('main.dart.js')) { + if (!basename.contains(outputFileName)) { continue; } // Do not copy the deps file. @@ -318,6 +392,12 @@ class WebReleaseBundle extends Target { ); } + if (isWasm) { + // TODO(jacksongardner): Enable icon tree shaking once dart2wasm can do a two-phase compile. + // https://github.com/flutter/flutter/issues/117248 + environment.defines[kIconTreeShakerFlag] = 'false'; + } + createVersionFile(environment, environment.defines); final Directory outputDirectory = environment.outputDir.childDirectory('assets'); outputDirectory.createSync(recursive: true); @@ -413,10 +493,11 @@ class WebReleaseBundle extends Target { /// These assets can be cached forever and are only invalidated when the /// Flutter SDK is upgraded to a new version. class WebBuiltInAssets extends Target { - const WebBuiltInAssets(this.fileSystem, this.cache); + const WebBuiltInAssets(this.fileSystem, this.cache, this.isWasm); final FileSystem fileSystem; final Cache cache; + final bool isWasm; @override String get name => 'web_static_assets'; @@ -451,6 +532,21 @@ class WebBuiltInAssets extends Target { file.copySync(targetPath); } + if (isWasm) { + final String dartSdkPath = + globals.artifacts!.getArtifactPath(Artifact.engineDartSdkPath); + final File dart2wasmRuntime = fileSystem.directory(dartSdkPath) + .childDirectory('bin') + .childFile('dart2wasm_runtime.mjs'); + final String targetPath = fileSystem.path.join( + environment.outputDir.path, + 'dart2wasm_runtime.mjs'); + dart2wasmRuntime.copySync(targetPath); + + final File bootstrapFile = environment.outputDir.childFile('main.dart.js'); + bootstrapFile.writeAsStringSync(wasm_bootstrap.generateWasmBootstrapFile()); + } + // Write the flutter.js file final File flutterJsFile = environment.outputDir.childFile('flutter.js'); flutterJsFile.writeAsStringSync(flutter_js.generateFlutterJsFile()); @@ -459,20 +555,21 @@ class WebBuiltInAssets extends Target { /// Generate a service worker for a web target. class WebServiceWorker extends Target { - const WebServiceWorker(this.fileSystem, this.cache, this.webRenderer); + const WebServiceWorker(this.fileSystem, this.cache, this.webRenderer, this.isWasm); final FileSystem fileSystem; final Cache cache; final WebRendererMode webRenderer; + final bool isWasm; @override String get name => 'web_service_worker'; @override List get dependencies => [ - Dart2JSTarget(webRenderer), - WebReleaseBundle(webRenderer), - WebBuiltInAssets(fileSystem, cache), + if (isWasm) Dart2WasmTarget(webRenderer) else Dart2JSTarget(webRenderer), + WebReleaseBundle(webRenderer, isWasm), + WebBuiltInAssets(fileSystem, cache, isWasm), ]; @override diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart index 6b56fdc520d3..8c288b1aa0b6 100644 --- a/packages/flutter_tools/lib/src/commands/build_web.dart +++ b/packages/flutter_tools/lib/src/commands/build_web.dart @@ -42,6 +42,10 @@ class BuildWebCommand extends BuildSubCommand { 'to view and debug the original source code of a compiled and minified Dart ' 'application.' ); + argParser.addFlag( + 'wasm', + help: 'Compile to WebAssembly rather than Javascript (experimental).' + ); argParser.addOption('pwa-strategy', defaultsTo: kOfflineFirst, @@ -140,6 +144,7 @@ class BuildWebCommand extends BuildSubCommand { stringArgDeprecated('pwa-strategy')!, boolArgDeprecated('source-maps'), boolArgDeprecated('native-null-assertions'), + boolArgDeprecated('wasm'), baseHref: baseHref, dart2jsOptimization: stringArgDeprecated('dart2js-optimization'), outputDirectoryPath: outputDirectoryPath, diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index 55a9a7bb2c22..e363165bc54e 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -298,6 +298,7 @@ class ResidentWebRunner extends ResidentRunner { kNoneWorker, true, debuggingOptions.nativeNullAssertions, + false, ); } await device!.device!.startApp( @@ -370,6 +371,7 @@ class ResidentWebRunner extends ResidentRunner { kNoneWorker, true, debuggingOptions.nativeNullAssertions, + false, baseHref: kBaseHref, ); } on ToolExit { diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index c24f7f70e539..b6763554f776 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -25,7 +25,8 @@ Future buildWeb( bool csp, String serviceWorkerStrategy, bool sourceMaps, - bool nativeNullAssertions, { + bool nativeNullAssertions, + bool isWasm, { String? dart2jsOptimization, String? baseHref, bool dumpInfo = false, @@ -35,7 +36,7 @@ Future buildWeb( final bool hasWebPlugins = (await findPlugins(flutterProject)) .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey)); final Directory outputDirectory = outputDirectoryPath == null - ? globals.fs.directory(getWebBuildDirectory()) + ? globals.fs.directory(getWebBuildDirectory(isWasm)) : globals.fs.directory(outputDirectoryPath); outputDirectory.createSync(recursive: true); @@ -51,7 +52,7 @@ Future buildWeb( final Stopwatch sw = Stopwatch()..start(); try { final BuildResult result = await globals.buildSystem.build( - WebServiceWorker(globals.fs, globals.cache, buildInfo.webRenderer), + WebServiceWorker(globals.fs, globals.cache, buildInfo.webRenderer, isWasm), Environment( projectDir: globals.fs.currentDirectory, outputDir: outputDirectory, diff --git a/packages/flutter_tools/lib/src/web/file_generators/wasm_bootstrap.dart b/packages/flutter_tools/lib/src/web/file_generators/wasm_bootstrap.dart new file mode 100644 index 000000000000..ff2aa89c4ee6 --- /dev/null +++ b/packages/flutter_tools/lib/src/web/file_generators/wasm_bootstrap.dart @@ -0,0 +1,31 @@ +// 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. + +String generateWasmBootstrapFile() { + return r''' +// 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. + +(async function () { + let dart2wasm_runtime; + let moduleInstance; + try { + const dartModulePromise = WebAssembly.compileStreaming(fetch("main.dart.wasm")); + dart2wasm_runtime = await import('./dart2wasm_runtime.mjs'); + moduleInstance = await dart2wasm_runtime.instantiate(dartModulePromise, {}); + } catch (exception) { + console.error(`Failed to fetch and instantiate wasm module: ${exception}`); + } + + if (moduleInstance) { + try { + await dart2wasm_runtime.invoke(moduleInstance); + } catch (exception) { + console.error(`Exception while invoking test: ${exception}`); + } + } +})(); +'''; +} diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart index dd51ae09bc43..fcc804a678ec 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart @@ -100,7 +100,7 @@ void main() { webResources.childFile('index.html') .createSync(recursive: true); environment.buildDir.childFile('main.dart.js').createSync(); - await const WebReleaseBundle(WebRendererMode.autoDetect).build(environment); + await const WebReleaseBundle(WebRendererMode.autoDetect, false).build(environment); expect(environment.outputDir.childFile('version.json'), exists); })); @@ -112,7 +112,7 @@ void main() { final Directory webResources = environment.projectDir.childDirectory('web'); webResources.childFile('index.html').createSync(recursive: true); environment.buildDir.childFile('main.dart.js').createSync(); - await const WebReleaseBundle(WebRendererMode.autoDetect).build(environment); + await const WebReleaseBundle(WebRendererMode.autoDetect, false).build(environment); final String versionFile = environment.outputDir .childFile('version.json') @@ -130,7 +130,7 @@ void main() { '''); environment.buildDir.childFile('main.dart.js').createSync(); - await const WebReleaseBundle(WebRendererMode.autoDetect).build(environment); + await const WebReleaseBundle(WebRendererMode.autoDetect, false).build(environment); expect(environment.outputDir.childFile('index.html').readAsStringSync(), contains('/basehreftest/')); })); @@ -143,7 +143,7 @@ void main() { '''); environment.buildDir.childFile('main.dart.js').createSync(); - await const WebReleaseBundle(WebRendererMode.autoDetect).build(environment); + await const WebReleaseBundle(WebRendererMode.autoDetect, false).build(environment); expect(environment.outputDir.childFile('index.html').readAsStringSync(), contains('/basehreftest/')); })); @@ -165,7 +165,7 @@ void main() { .writeAsStringSync('A'); environment.buildDir.childFile('main.dart.js').createSync(); - await const WebReleaseBundle(WebRendererMode.autoDetect).build(environment); + await const WebReleaseBundle(WebRendererMode.autoDetect, false).build(environment); expect(environment.outputDir.childFile('foo.txt') .readAsStringSync(), 'A'); @@ -177,7 +177,7 @@ void main() { // Update to arbitrary resource file triggers rebuild. webResources.childFile('foo.txt').writeAsStringSync('B'); - await const WebReleaseBundle(WebRendererMode.autoDetect).build(environment); + await const WebReleaseBundle(WebRendererMode.autoDetect, false).build(environment); expect(environment.outputDir.childFile('foo.txt') .readAsStringSync(), 'B'); @@ -358,7 +358,7 @@ void main() { ] )); - await const Dart2JSTarget(WebRendererMode.autoDetect).build(environment); + await Dart2JSTarget(WebRendererMode.autoDetect).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -396,7 +396,7 @@ void main() { ] )); - await const Dart2JSTarget(WebRendererMode.autoDetect).build(environment); + await Dart2JSTarget(WebRendererMode.autoDetect).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -430,7 +430,7 @@ void main() { ] )); - await const Dart2JSTarget(WebRendererMode.autoDetect).build(environment); + await Dart2JSTarget(WebRendererMode.autoDetect).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -463,7 +463,7 @@ void main() { ] )); - await const Dart2JSTarget(WebRendererMode.autoDetect).build(environment); + await Dart2JSTarget(WebRendererMode.autoDetect).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -499,7 +499,7 @@ void main() { ] )); - await const Dart2JSTarget(WebRendererMode.autoDetect).build(environment); + await Dart2JSTarget(WebRendererMode.autoDetect).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -533,7 +533,7 @@ void main() { ] )); - await const Dart2JSTarget(WebRendererMode.autoDetect).build(environment); + await Dart2JSTarget(WebRendererMode.autoDetect).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -556,7 +556,7 @@ void main() { .writeAsStringSync('file:///a.dart'); }, )); - await const Dart2JSTarget(WebRendererMode.autoDetect).build(environment); + await Dart2JSTarget(WebRendererMode.autoDetect).build(environment); expect(environment.buildDir.childFile('dart2js.d'), exists); final Depfile depfile = depfileService.parse(environment.buildDir.childFile('dart2js.d')); @@ -601,7 +601,7 @@ void main() { ] )); - await const Dart2JSTarget(WebRendererMode.autoDetect).build(environment); + await Dart2JSTarget(WebRendererMode.autoDetect).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -633,7 +633,7 @@ void main() { ] )); - await const Dart2JSTarget(WebRendererMode.autoDetect).build(environment); + await Dart2JSTarget(WebRendererMode.autoDetect).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -673,7 +673,7 @@ void main() { ] )); - await const Dart2JSTarget(WebRendererMode.autoDetect).build(environment); + await Dart2JSTarget(WebRendererMode.autoDetect).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -709,7 +709,7 @@ void main() { ] )); - await const Dart2JSTarget(WebRendererMode.canvaskit).build(environment); + await Dart2JSTarget(WebRendererMode.canvaskit).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -745,7 +745,40 @@ void main() { ] )); - await const Dart2JSTarget(WebRendererMode.canvaskit).build(environment); + await Dart2JSTarget(WebRendererMode.canvaskit).build(environment); + }, overrides: { + ProcessManager: () => processManager, + })); + + test('Dart2WasmTarget invokes dart2wasm with dart defines', () => testbed.run(() async { + environment.defines[kBuildMode] = 'profile'; + environment.defines[kDartDefines] = encodeDartDefines(['FOO=bar', 'BAZ=qux']); + + processManager.addCommand(FakeCommand( + command: [ + 'bin/cache/dart-sdk/bin/dartaotruntime', + '--disable-dart-dev', + 'bin/cache/dart-sdk/bin/snapshots/dart2wasm_product.snapshot', + '-Ddart.vm.profile=true', + '-DFOO=bar', + '-DBAZ=qux', + '--packages=.dart_tool/package_config.json', + '--dart-sdk=bin/cache/dart-sdk', + '--multi-root-scheme', + 'org-dartlang-sdk', + '--multi-root', + 'bin/cache/flutter_web_sdk', + '--multi-root', + 'bin/cache', + '--libraries-spec', + 'bin/cache/flutter_web_sdk/libraries.json', + + environment.buildDir.childFile('main.dart').absolute.path, + environment.buildDir.childFile('main.dart.wasm').absolute.path, + ]) + ); + + await Dart2WasmTarget(WebRendererMode.canvaskit).build(environment); }, overrides: { ProcessManager: () => processManager, })); @@ -772,7 +805,7 @@ void main() { environment.outputDir.childDirectory('a').childFile('a.txt') ..createSync(recursive: true) ..writeAsStringSync('A'); - await WebServiceWorker(globals.fs, globals.cache, WebRendererMode.autoDetect).build(environment); + await WebServiceWorker(globals.fs, globals.cache, WebRendererMode.autoDetect, false).build(environment); expect(environment.outputDir.childFile('flutter_service_worker.js'), exists); // Contains file hash. @@ -791,7 +824,7 @@ void main() { environment.outputDir .childFile('index.html') .createSync(recursive: true); - await WebServiceWorker(globals.fs, globals.cache, WebRendererMode.autoDetect).build(environment); + await WebServiceWorker(globals.fs, globals.cache, WebRendererMode.autoDetect, false).build(environment); expect(environment.outputDir.childFile('flutter_service_worker.js'), exists); // Contains file hash for both `/` and index.html. @@ -809,7 +842,7 @@ void main() { environment.outputDir .childFile('main.dart.js.map') .createSync(recursive: true); - await WebServiceWorker(globals.fs, globals.cache, WebRendererMode.autoDetect).build(environment); + await WebServiceWorker(globals.fs, globals.cache, WebRendererMode.autoDetect, false).build(environment); // No caching of source maps. expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(), @@ -824,10 +857,25 @@ void main() { ..createSync(recursive: true) ..writeAsStringSync('OL'); - await WebBuiltInAssets(globals.fs, globals.cache).build(environment); + await WebBuiltInAssets(globals.fs, globals.cache, false).build(environment); // No caching of source maps. expect(environment.outputDir.childFile('flutter.js').readAsStringSync(), equals(flutter_js.generateFlutterJsFile())); })); + + test('wasm build copies and generates specific files', () => testbed.run(() async { + globals.fs.file('bin/cache/dart-sdk/bin/dart2wasm_runtime.mjs') + .createSync(recursive: true); + globals.fs.file('bin/cache/flutter_web_sdk/canvaskit/canvaskit.wasm') + .createSync(recursive: true); + + await WebBuiltInAssets(globals.fs, globals.cache, true).build(environment); + + expect(environment.outputDir.childFile('dart2wasm_runtime.mjs').existsSync(), true); + expect(environment.outputDir.childFile('main.dart.js').existsSync(), true); + expect(environment.outputDir.childDirectory('canvaskit') + .childFile('canvaskit.wasm') + .existsSync(), true); + })); } diff --git a/packages/flutter_tools/test/integration.shard/flutter_build_wasm_test.dart b/packages/flutter_tools/test/integration.shard/flutter_build_wasm_test.dart new file mode 100644 index 000000000000..cb911a3f1f47 --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/flutter_build_wasm_test.dart @@ -0,0 +1,60 @@ +// 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 'dart:io'; + +import 'package:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; + +import '../src/common.dart'; +import 'test_utils.dart'; + +void main() { + late Directory tempDir; + late String flutterBin; + late Directory exampleAppDir; + + setUp(() async { + tempDir = createResolvedTempDirectorySync('flutter_web_wasm_test.'); + flutterBin = fileSystem.path.join( + getFlutterRoot(), + 'bin', + 'flutter', + ); + exampleAppDir = tempDir.childDirectory('test_app'); + + processManager.runSync([ + flutterBin, + 'create', + '--platforms=web', + 'test_app', + ], workingDirectory: tempDir.path); + }); + + test('building web with --wasm produces expected files', () async { + final ProcessResult result = processManager.runSync([ + flutterBin, + 'build', + 'web', + '--wasm', + ], workingDirectory: exampleAppDir.path); + expect(result.exitCode, 0); + + final Directory appBuildDir = fileSystem.directory(fileSystem.path.join( + exampleAppDir.path, + 'build', + 'web_wasm' + )); + for (final String filename in const [ + 'dart2wasm_runtime.mjs', + 'flutter.js', + 'flutter_service_worker.js', + 'index.html', + 'main.dart.wasm', + 'main.dart.js', + ]) { + expect(appBuildDir.childFile(filename), exists); + } + }); +}