From c91b6571165e282330897a3a28858324f2024f9e Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Thu, 6 Jun 2019 21:05:55 -0700 Subject: [PATCH] Codegen an entrypoint for flutter web applications (#33956) --- dev/integration_tests/web/lib/main.dart | 4 +- examples/flutter_gallery/lib/main_web.dart | 16 -- .../web_compilation_delegate.dart | 270 ++++++++++++++++-- .../lib/src/commands/build_web.dart | 32 +-- .../flutter_tools/lib/src/context_runner.dart | 2 - packages/flutter_tools/lib/src/project.dart | 3 + .../lib/src/web/asset_server.dart | 7 +- .../flutter_tools/lib/src/web/compile.dart | 117 ++++---- .../flutter_tools/lib/src/web/web_device.dart | 31 +- .../flutter_tools/test/web/compile_test.dart | 50 ---- .../flutter_tools/test/web/devices_test.dart | 23 -- 11 files changed, 322 insertions(+), 233 deletions(-) delete mode 100644 examples/flutter_gallery/lib/main_web.dart delete mode 100644 packages/flutter_tools/test/web/compile_test.dart diff --git a/dev/integration_tests/web/lib/main.dart b/dev/integration_tests/web/lib/main.dart index 49c544cb7c..02c91ad297 100644 --- a/dev/integration_tests/web/lib/main.dart +++ b/dev/integration_tests/web/lib/main.dart @@ -6,8 +6,6 @@ import 'package:flutter/widgets.dart'; void main() { runApp(Center( - // Can remove when https://github.com/dart-lang/sdk/issues/35801 is fixed. - // ignore: prefer_const_constructors - child: Text('Hello, World', textDirection: TextDirection.ltr), + child: const Text('Hello, World', textDirection: TextDirection.ltr), )); } diff --git a/examples/flutter_gallery/lib/main_web.dart b/examples/flutter_gallery/lib/main_web.dart deleted file mode 100644 index f53fa2c5bb..0000000000 --- a/examples/flutter_gallery/lib/main_web.dart +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Thanks for checking out Flutter! -// Like what you see? Tweet us @FlutterDev - -import 'dart:ui' as ui; - -import 'package:flutter/material.dart'; -import 'gallery/app.dart'; - -Future main() async { - await ui.webOnlyInitializePlatform(); // ignore: undefined_function - runApp(const GalleryApp()); -} diff --git a/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart b/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart index 0ff1a9c116..d829deae78 100644 --- a/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart +++ b/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart @@ -3,13 +3,19 @@ // found in the LICENSE file. // ignore_for_file: implementation_imports +import 'dart:async'; +import 'dart:io' as io; // ignore: dart_io_import + import 'package:build/build.dart'; import 'package:build_config/build_config.dart'; import 'package:build_modules/build_modules.dart'; import 'package:build_modules/builders.dart'; import 'package:build_modules/src/module_builder.dart'; import 'package:build_modules/src/platform.dart'; +import 'package:build_modules/src/workers.dart'; import 'package:build_runner_core/build_runner_core.dart' as core; +import 'package:build_runner_core/src/asset_graph/graph.dart'; +import 'package:build_runner_core/src/asset_graph/node.dart'; import 'package:build_runner_core/src/generate/build_impl.dart'; import 'package:build_runner_core/src/generate/options.dart'; import 'package:build_test/builder.dart'; @@ -17,9 +23,11 @@ import 'package:build_test/src/debug_test_builder.dart'; import 'package:build_web_compilers/build_web_compilers.dart'; import 'package:build_web_compilers/builders.dart'; import 'package:build_web_compilers/src/dev_compiler_bootstrap.dart'; +import 'package:crypto/crypto.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; +import 'package:scratch_space/scratch_space.dart'; import 'package:test_core/backend.dart'; import 'package:watcher/watcher.dart'; @@ -28,6 +36,7 @@ import '../base/file_system.dart'; import '../base/logger.dart'; import '../base/platform.dart'; import '../compile.dart'; +import '../convert.dart'; import '../dart/package_map.dart'; import '../globals.dart'; import '../web/compile.dart'; @@ -83,6 +92,22 @@ final List builders = [ ], ), ), + core.apply( + 'flutter_tools|shell', + [ + (BuilderOptions options) => FlutterWebShellBuilder( + options.config['targets'] ?? ['lib/main.dart'] + ), + ], + core.toRoot(), + hideOutput: true, + defaultGenerateFor: const InputSet( + include: [ + 'lib/**', + 'web/**', + ], + ), + ), core.apply( 'flutter_tools|module_library', [moduleLibraryBuilder], @@ -127,7 +152,9 @@ final List builders = [ 'flutter_tools|entrypoint', [ (BuilderOptions options) => FlutterWebEntrypointBuilder( - options.config['targets'] ?? ['lib/main.dart']), + options.config['targets'] ?? ['lib/main.dart'], + options.config['release'], + ), ], core.toRoot(), hideOutput: true, @@ -152,11 +179,15 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { PackageUriMapper _packageUriMapper; @override - Future initialize({ + Future initialize({ @required Directory projectDirectory, @required List targets, String testOutputDir, + bool release = false, }) async { + // Create the .dart_tool directory if it doesn't exist. + projectDirectory.childDirectory('.dart_tool').createSync(); + // Override the generated output directory so this does not conflict with // other build_runner output. core.overrideGeneratedOutputDirectory('flutter_web'); @@ -195,19 +226,36 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { }; final Status status = logger.startProgress('Compiling ${targets.first} for the Web...', timeout: null); + core.BuildResult result; try { - _builder = await BuildImpl.create( - buildOptions, + result = await _runBuilder( buildEnvironment, - builders, - >{ - 'flutter_tools|entrypoint': { - 'targets': targets, - } - }, - isReleaseBuild: false, + buildOptions, + targets, + release, + buildDirs, ); - await _builder.run(const {}, buildDirs: buildDirs); + return result.status == core.BuildStatus.success; + } on core.BuildConfigChangedException { + await _cleanAssets(projectDirectory); + result = await _runBuilder( + buildEnvironment, + buildOptions, + targets, + release, + buildDirs, + ); + return result.status == core.BuildStatus.success; + } on core.BuildScriptChangedException { + await _cleanAssets(projectDirectory); + result = await _runBuilder( + buildEnvironment, + buildOptions, + targets, + release, + buildDirs, + ); + return result.status == core.BuildStatus.success; } finally { status.stop(); } @@ -219,9 +267,8 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { logger.startProgress('Recompiling sources...', timeout: null); final Map updates = {}; for (Uri input in inputs) { - updates[AssetId.resolve( - _packageUriMapper.map(input.toFilePath()).toString())] = - ChangeType.MODIFY; + final AssetId assetId = AssetId.resolve(_packageUriMapper.map(input.toFilePath()).toString()); + updates[assetId] = ChangeType.MODIFY; } core.BuildResult result; try { @@ -231,13 +278,81 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { } return result.status == core.BuildStatus.success; } + + + Future _runBuilder(core.BuildEnvironment buildEnvironment, BuildOptions buildOptions, List targets, bool release, Set buildDirs) async { + _builder = await BuildImpl.create( + buildOptions, + buildEnvironment, + builders, + >{ + 'flutter_tools|entrypoint': { + 'targets': targets, + 'release': release, + }, + 'flutter_tools|shell': { + 'targets': targets, + } + }, + isReleaseBuild: false, + ); + return _builder.run( + const {}, + buildDirs: buildDirs, + ); + } + + Future _cleanAssets(Directory projectDirectory) async { + final File assetGraphFile = fs.file(core.assetGraphPath); + AssetGraph assetGraph; + try { + assetGraph = AssetGraph.deserialize(await assetGraphFile.readAsBytes()); + } catch (_) { + printTrace('Failed to clean up asset graph.'); + } + final core.PackageGraph packageGraph = core.PackageGraph.forThisPackage(); + await _cleanUpSourceOutputs(assetGraph, packageGraph); + final Directory cacheDirectory = fs.directory(fs.path.join( + projectDirectory.path, + '.dart_tool', + 'build', + 'flutter_web', + )); + if (assetGraphFile.existsSync()) { + assetGraphFile.deleteSync(); + } + if (cacheDirectory.existsSync()) { + cacheDirectory.deleteSync(recursive: true); + } + } + + Future _cleanUpSourceOutputs(AssetGraph assetGraph, core.PackageGraph packageGraph) async { + final core.FileBasedAssetWriter writer = core.FileBasedAssetWriter(packageGraph); + if (assetGraph?.outputs == null) { + return; + } + for (AssetId id in assetGraph.outputs) { + if (id.package != packageGraph.root.name) { + continue; + } + final GeneratedAssetNode node = assetGraph.get(id); + if (node.wasOutput) { + // Note that this does a file.exists check in the root package and + // only tries to delete the file if it exists. This way we only + // actually delete to_source outputs, without reading in the build + // actions. + await writer.delete(id); + } + } + } } /// A ddc-only entrypoint builder that respects the Flutter target flag. class FlutterWebEntrypointBuilder implements Builder { - const FlutterWebEntrypointBuilder(this.targets); + const FlutterWebEntrypointBuilder(this.targets, this.release); final List targets; + final bool release; @override Map> get buildExtensions => const >{ @@ -254,7 +369,7 @@ class FlutterWebEntrypointBuilder implements Builder { Future build(BuildStep buildStep) async { bool matches = false; for (String target in targets) { - if (buildStep.inputId.path.contains(target)) { + if (buildStep.inputId.path.contains(fs.path.setExtension(target, '_web_entrypoint.dart'))) { matches = true; break; } @@ -263,10 +378,15 @@ class FlutterWebEntrypointBuilder implements Builder { return; } log.info('building for target ${buildStep.inputId.path}'); - await bootstrapDdc(buildStep, platform: flutterWebPlatform); + if (release) { + await bootstrapDart2Js(buildStep); + } else { + await bootstrapDdc(buildStep, platform: flutterWebPlatform); + } } } +/// Bootstraps the test entrypoint. class FlutterWebTestBootstrapBuilder implements Builder { const FlutterWebTestBootstrapBuilder(); @@ -372,3 +492,117 @@ void setStackTraceMapper(StackTraceMapper mapper) { } } +/// A shell builder which generates the web specific entrypoint. +class FlutterWebShellBuilder implements Builder { + const FlutterWebShellBuilder(this.targets); + + final List targets; + + @override + FutureOr build(BuildStep buildStep) async { + bool matches = false; + for (String target in targets) { + if (buildStep.inputId.path.contains(target)) { + matches = true; + break; + } + } + if (!matches) { + return; + } + final AssetId outputId = buildStep.inputId.changeExtension('_web_entrypoint.dart'); + await buildStep.writeAsString(outputId, ''' +import 'dart:ui' as ui; +import "${path.url.basename(buildStep.inputId.path)}" as entrypoint; + +Future main() async { + await ui.webOnlyInitializePlatform(); + entrypoint.main(); +} + +'''); + } + + @override + Map> get buildExtensions => const >{ + '.dart': ['_web_entrypoint.dart'], + }; +} + +Future bootstrapDart2Js(BuildStep buildStep) async { + final AssetId dartEntrypointId = buildStep.inputId; + final AssetId moduleId = dartEntrypointId.changeExtension(moduleExtension(flutterWebPlatform)); + final Module module = Module.fromJson(json.decode(await buildStep.readAsString(moduleId))); + + final List allDeps = await module.computeTransitiveDependencies(buildStep, throwIfUnsupported: false)..add(module); + final ScratchSpace scratchSpace = await buildStep.fetchResource(scratchSpaceResource); + final Iterable allSrcs = allDeps.expand((Module module) => module.sources); + await scratchSpace.ensureAssets(allSrcs, buildStep); + + final String packageFile = await _createPackageFile(allSrcs, buildStep, scratchSpace); + final String dartPath = dartEntrypointId.path.startsWith('lib/') + ? 'package:${dartEntrypointId.package}/' + '${dartEntrypointId.path.substring('lib/'.length)}' + : dartEntrypointId.path; + final String jsOutputPath = + '${fs.path.withoutExtension(dartPath.replaceFirst('package:', 'packages/'))}' + '$jsEntrypointExtension'; + final String flutterWebSdkPath = artifacts.getArtifactPath(Artifact.flutterWebSdk); + final String librariesPath = fs.path.join(flutterWebSdkPath, 'libraries.json'); + final List args = [ + '--libraries-spec="$librariesPath"', + '-m', + '-o4', + '-o', + '$jsOutputPath', + '--packages="$packageFile"', + dartPath, + ]; + final Dart2JsBatchWorkerPool dart2js = await buildStep.fetchResource(dart2JsWorkerResource); + final Dart2JsResult result = await dart2js.compile(args); + final AssetId jsOutputId = dartEntrypointId.changeExtension(jsEntrypointExtension); + final io.File jsOutputFile = scratchSpace.fileFor(jsOutputId); + if (result.succeeded && jsOutputFile.existsSync()) { + log.info(result.output); + // Explicitly write out the original js file and sourcemap. + await scratchSpace.copyOutput(jsOutputId, buildStep); + final AssetId jsSourceMapId = + dartEntrypointId.changeExtension(jsEntrypointSourceMapExtension); + await _copyIfExists(jsSourceMapId, scratchSpace, buildStep); + } else { + log.severe(result.output); + } +} + +Future _copyIfExists( + AssetId id, ScratchSpace scratchSpace, AssetWriter writer) async { + final io.File file = scratchSpace.fileFor(id); + if (file.existsSync()) { + await scratchSpace.copyOutput(id, writer); + } +} + +/// Creates a `.packages` file unique to this entrypoint at the root of the +/// scratch space and returns it's filename. +/// +/// Since mulitple invocations of Dart2Js will share a scratch space and we only +/// know the set of packages involved the current entrypoint we can't construct +/// a `.packages` file that will work for all invocations of Dart2Js so a unique +/// file is created for every entrypoint that is run. +/// +/// The filename is based off the MD5 hash of the asset path so that files are +/// unique regarless of situations like `web/foo/bar.dart` vs +/// `web/foo-bar.dart`. +Future _createPackageFile(Iterable inputSources, BuildStep buildStep, ScratchSpace scratchSpace) async { + final Uri inputUri = buildStep.inputId.uri; + final String packageFileName = + '.package-${md5.convert(inputUri.toString().codeUnits)}'; + final io.File packagesFile = + scratchSpace.fileFor(AssetId(buildStep.inputId.package, packageFileName)); + final Set packageNames = inputSources.map((AssetId s) => s.package).toSet(); + final String packagesFileContent = + packageNames.map((String name) => '$name:packages/$name/').join('\n'); + await packagesFile + .writeAsString('# Generated for $inputUri\n$packagesFileContent'); + return packageFileName; +} diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart index 7ed5191f91..32cea06fdf 100644 --- a/packages/flutter_tools/lib/src/commands/build_web.dart +++ b/packages/flutter_tools/lib/src/commands/build_web.dart @@ -4,10 +4,8 @@ import 'dart:async'; -import '../base/common.dart'; -import '../base/logger.dart'; import '../build_info.dart'; -import '../globals.dart'; +import '../project.dart'; import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult; import '../web/compile.dart'; @@ -41,34 +39,10 @@ class BuildWebCommand extends BuildSubCommand { @override Future runCommand() async { + final FlutterProject flutterProject = FlutterProject.current(); final String target = argResults['target']; - final Status status = logger - .startProgress('Compiling $target for the Web...', timeout: null); final BuildInfo buildInfo = getBuildInfo(); - int result; - switch (buildInfo.mode) { - case BuildMode.release: - result = await webCompiler.compileDart2js(target: target); - break; - case BuildMode.profile: - result = await webCompiler.compileDart2js(target: target, minify: false); - break; - case BuildMode.debug: - throwToolExit( - 'Debug mode is not supported as a build target. Instead use ' - '"flutter run -d web".'); - break; - case BuildMode.dynamicProfile: - case BuildMode.dynamicRelease: - throwToolExit( - 'Build mode ${buildInfo.mode} is not supported with JavaScript ' - 'compilation'); - break; - } - status.stop(); - if (result == 1) { - throwToolExit('Failed to compile $target to JavaScript.'); - } + await buildWeb(flutterProject, target, buildInfo); return null; } } diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index 51bb7e7a0d..8aec5280ba 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -44,7 +44,6 @@ import 'run_hot.dart'; import 'usage.dart'; import 'version.dart'; import 'web/chrome.dart'; -import 'web/compile.dart'; import 'web/workflow.dart'; import 'windows/visual_studio.dart'; import 'windows/visual_studio_validator.dart'; @@ -104,7 +103,6 @@ Future runInContext( UserMessages: () => UserMessages(), VisualStudio: () => VisualStudio(), VisualStudioValidator: () => const VisualStudioValidator(), - WebCompiler: () => const WebCompiler(), WebWorkflow: () => const WebWorkflow(), WindowsWorkflow: () => const WindowsWorkflow(), Xcode: () => Xcode(), diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 6b514467ec..03efe52f69 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -583,6 +583,9 @@ class WebProject { return parent.directory.childDirectory('web').existsSync(); } + /// The html file used to host the flutter web application. + File get indexFile => parent.directory.childDirectory('web').childFile('index.html'); + Future ensureReadyForPlatformSpecificTooling() async { /// Generate index.html in build/web. Eventually we could support /// a custom html under the web sub directory. diff --git a/packages/flutter_tools/lib/src/web/asset_server.dart b/packages/flutter_tools/lib/src/web/asset_server.dart index 730692c000..ebe41bed83 100644 --- a/packages/flutter_tools/lib/src/web/asset_server.dart +++ b/packages/flutter_tools/lib/src/web/asset_server.dart @@ -66,6 +66,7 @@ class WebAssetServer { /// An HTTP server which provides JavaScript and web assets to the browser. Future _onRequest(HttpRequest request) async { + final String targetName = '${fs.path.basenameWithoutExtension(target)}_web_entrypoint'; if (request.method != 'GET') { request.response.statusCode = HttpStatus.forbidden; await request.response.close(); @@ -103,17 +104,17 @@ class WebAssetServer { 'flutter_web', flutterProject.manifest.appName, 'lib', - '${fs.path.basename(target)}.js', + '$targetName.dart.js', )); await _completeRequest(request, file, 'text/javascript'); - } else if (uri.path.endsWith('${fs.path.basename(target)}.bootstrap.js')) { + } else if (uri.path.endsWith('$targetName.dart.bootstrap.js')) { final File file = fs.file(fs.path.join( flutterProject.dartTool.path, 'build', 'flutter_web', flutterProject.manifest.appName, 'lib', - '${fs.path.basename(target)}.bootstrap.js', + '$targetName.dart.bootstrap.js', )); await _completeRequest(request, file, 'text/javascript'); } else if (uri.path.contains('dart_sdk')) { diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index 732769a128..90b7ea3869 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -4,80 +4,56 @@ import 'package:meta/meta.dart'; -import '../artifacts.dart'; +import '../asset.dart'; import '../base/common.dart'; import '../base/context.dart'; import '../base/file_system.dart'; -import '../base/io.dart'; -import '../base/process_manager.dart'; +import '../base/logger.dart'; import '../build_info.dart'; -import '../convert.dart'; +import '../bundle.dart'; import '../globals.dart'; - -/// The [WebCompiler] instance. -WebCompiler get webCompiler => context.get(); +import '../project.dart'; /// The [WebCompilationProxy] instance. -WebCompilationProxy get webCompilationProxy => - context.get(); +WebCompilationProxy get webCompilationProxy => context.get(); -/// A wrapper around dart tools for web compilation. -class WebCompiler { - const WebCompiler(); +Future buildWeb(FlutterProject flutterProject, String target, BuildInfo buildInfo) async { + final Status status = logger.startProgress('Compiling $target for the Web...', timeout: null); + final Directory outputDir = fs.directory(getWebBuildDirectory()) + ..createSync(recursive: true); + bool result; + try { + result = await webCompilationProxy.initialize( + projectDirectory: FlutterProject.current().directory, + targets: [target], + release: buildInfo.isRelease, + ); + if (result) { + // Places assets adjacent to the web stuff. + final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); + await assetBundle.build(); + await writeBundle(fs.directory(fs.path.join(outputDir.path, 'assets')), assetBundle.entries); - /// Compile `target` using dart2js. - /// - /// `minify` controls whether minifaction of the source is enabled. Defaults to `true`. - /// `enabledAssertions` controls whether assertions are enabled. Defaults to `false`. - Future compileDart2js({ - @required String target, - bool minify = true, - bool enabledAssertions = false, - }) async { - final String engineDartPath = - artifacts.getArtifactPath(Artifact.engineDartBinary); - final String dart2jsPath = - artifacts.getArtifactPath(Artifact.dart2jsSnapshot); - final String flutterWebSdkPath = - artifacts.getArtifactPath(Artifact.flutterWebSdk); - final String librariesPath = - fs.path.join(flutterWebSdkPath, 'libraries.json'); - final Directory outputDir = fs.directory(getWebBuildDirectory()); - if (!outputDir.existsSync()) { - outputDir.createSync(recursive: true); + // Copy results to output directory. + final String outputPath = fs.path.join( + flutterProject.dartTool.path, + 'build', + 'flutter_web', + flutterProject.manifest.appName, + '${fs.path.withoutExtension(target)}_web_entrypoint.dart.js' + ); + fs.file(outputPath).copySync(fs.path.join(outputDir.path, 'main.dart.js')); + fs.file('$outputPath.map').copySync(fs.path.join(outputDir.path, 'main.dart.js.map')); + flutterProject.web.indexFile.copySync(fs.path.join(outputDir.path, 'index.html')); } - final String outputPath = fs.path.join(outputDir.path, 'main.dart.js'); - if (!processManager.canRun(engineDartPath)) { - throwToolExit('Unable to find Dart binary at $engineDartPath'); - } - - /// Compile Dart to JavaScript. - final List command = [ - engineDartPath, - dart2jsPath, - target, - '-o', - '$outputPath', - '-O4', - '--libraries-spec=$librariesPath', - ]; - if (minify) { - command.add('-m'); - } - if (enabledAssertions) { - command.add('--enable-asserts'); - } - printTrace(command.join(' ')); - final Process result = await processManager.start(command); - result.stdout - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen(printStatus); - result.stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen(printError); - return result.exitCode; + } catch (err) { + printError(err.toString()); + result = false; + } finally { + status.stop(); + } + if (result == false) { + throwToolExit('Failed to compile $target for the Web.'); } } @@ -87,12 +63,19 @@ class WebCompiler { class WebCompilationProxy { const WebCompilationProxy(); - /// Initialize the web compiler output to `outputDirectory` from a project spawned at - /// `projectDirectory`. - Future initialize({ + /// Initialize the web compiler from the `projectDirectory`. + /// + /// Returns whether or not the build was successful. + /// + /// `release` controls whether we build the bundle for dartdevc or only + /// the entrypoints for dart2js to later take over. + /// + /// `targets` controls the specific compiler targets. + Future initialize({ @required Directory projectDirectory, @required List targets, String testOutputDir, + bool release, }) async { throw UnimplementedError(); } diff --git a/packages/flutter_tools/lib/src/web/web_device.dart b/packages/flutter_tools/lib/src/web/web_device.dart index b824343439..86a10e8183 100644 --- a/packages/flutter_tools/lib/src/web/web_device.dart +++ b/packages/flutter_tools/lib/src/web/web_device.dart @@ -5,15 +5,11 @@ import 'package:meta/meta.dart'; import '../application_package.dart'; -import '../asset.dart'; -import '../base/common.dart'; import '../base/file_system.dart'; import '../base/io.dart'; -import '../base/logger.dart'; import '../base/platform.dart'; import '../base/process_manager.dart'; import '../build_info.dart'; -import '../bundle.dart'; import '../device.dart'; import '../globals.dart'; import '../project.dart'; @@ -22,15 +18,15 @@ import '../web/workflow.dart'; import 'chrome.dart'; class WebApplicationPackage extends ApplicationPackage { - WebApplicationPackage(this._flutterProject) : super(id: _flutterProject.manifest.appName); + WebApplicationPackage(this.flutterProject) : super(id: flutterProject.manifest.appName); - final FlutterProject _flutterProject; + final FlutterProject flutterProject; @override - String get name => _flutterProject.manifest.appName; + String get name => flutterProject.manifest.appName; /// The location of the web source assets. - Directory get webSourcePath => _flutterProject.directory.childDirectory('web'); + Directory get webSourcePath => flutterProject.directory.childDirectory('web'); } class WebDevice extends Device { @@ -121,20 +117,11 @@ class WebDevice extends Device { bool usesTerminalUi = true, bool ipv6 = false, }) async { - final Status status = logger.startProgress('Compiling ${package.name} to JavaScript...', timeout: null); - final int result = await webCompiler.compileDart2js(target: mainPath, minify: false, enabledAssertions: true); - status.stop(); - if (result != 0) { - printError('Failed to compile ${package.name} to JavaScript'); - return LaunchResult.failed(); - } - final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); - final int build = await assetBundle.build(); - if (build != 0) { - throwToolExit('Error: Failed to build asset bundle'); - } - await writeBundle(fs.directory(getAssetBuildDirectory()), assetBundle.entries); - + await buildWeb( + package.flutterProject, + fs.path.relative(mainPath, from: package.flutterProject.directory.path), + debuggingOptions.buildInfo, + ); _package = package; _server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); _server.listen(_basicAssetServer); diff --git a/packages/flutter_tools/test/web/compile_test.dart b/packages/flutter_tools/test/web/compile_test.dart deleted file mode 100644 index 425c79d754..0000000000 --- a/packages/flutter_tools/test/web/compile_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2019 The Chromium 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/artifacts.dart'; -import 'package:flutter_tools/src/globals.dart'; -import 'package:flutter_tools/src/web/compile.dart'; -import 'package:mockito/mockito.dart'; -import 'package:process/process.dart'; - -import '../src/common.dart'; -import '../src/mocks.dart'; -import '../src/testbed.dart'; - -void main() { - group(WebCompiler, () { - MockProcessManager mockProcessManager; - Testbed testBed; - - setUp(() { - mockProcessManager = MockProcessManager(); - testBed = Testbed(setup: () async { - final String engineDartPath = artifacts.getArtifactPath(Artifact.engineDartBinary); - when(mockProcessManager.start(any)).thenAnswer((Invocation invocation) async => FakeProcess()); - when(mockProcessManager.canRun(engineDartPath)).thenReturn(true); - - }, overrides: { - ProcessManager: () => mockProcessManager, - }); - }); - - test('invokes dart2js with correct arguments', () => testBed.run(() async { - await webCompiler.compileDart2js(target: 'lib/main.dart'); - - verify(mockProcessManager.start([ - 'bin/cache/dart-sdk/bin/dart', - 'bin/cache/dart-sdk/bin/snapshots/dart2js.dart.snapshot', - 'lib/main.dart', - '-o', - 'build/web/main.dart.js', - '-O4', - '--libraries-spec=bin/cache/flutter_web_sdk/libraries.json', - '-m', - ])).called(1); - - })); - }); -} - -class MockProcessManager extends Mock implements ProcessManager {} diff --git a/packages/flutter_tools/test/web/devices_test.dart b/packages/flutter_tools/test/web/devices_test.dart index 62f94e17a8..554104840c 100644 --- a/packages/flutter_tools/test/web/devices_test.dart +++ b/packages/flutter_tools/test/web/devices_test.dart @@ -2,12 +2,9 @@ // 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/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/platform.dart'; -import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/web/chrome.dart'; -import 'package:flutter_tools/src/web/compile.dart'; import 'package:flutter_tools/src/web/web_device.dart'; import 'package:mockito/mockito.dart'; import 'package:process/process.dart'; @@ -17,37 +14,18 @@ import '../src/context.dart'; void main() { group(WebDevice, () { - MockWebCompiler mockWebCompiler; MockChromeLauncher mockChromeLauncher; MockPlatform mockPlatform; - FlutterProject flutterProject; MockProcessManager mockProcessManager; setUp(() async { mockProcessManager = MockProcessManager(); mockChromeLauncher = MockChromeLauncher(); mockPlatform = MockPlatform(); - mockWebCompiler = MockWebCompiler(); - flutterProject = FlutterProject.fromPath(fs.path.join(getFlutterRoot(), 'dev', 'integration_tests', 'web')); - when(mockWebCompiler.compileDart2js( - target: anyNamed('target'), - minify: anyNamed('minify'), - enabledAssertions: anyNamed('enabledAssertions'), - )).thenAnswer((Invocation invocation) async => 0); when(mockChromeLauncher.launch(any)).thenAnswer((Invocation invocation) async { return null; }); }); - - testUsingContext('can build and connect to chrome', () async { - final WebDevice device = WebDevice(); - await device.startApp(WebApplicationPackage(flutterProject)); - }, overrides: { - ChromeLauncher: () => mockChromeLauncher, - WebCompiler: () => mockWebCompiler, - Platform: () => mockPlatform, - }); - testUsingContext('Invokes version command on non-Windows platforms', () async{ when(mockPlatform.isWindows).thenReturn(false); when(mockPlatform.environment).thenReturn({ @@ -86,7 +64,6 @@ void main() { } class MockChromeLauncher extends Mock implements ChromeLauncher {} -class MockWebCompiler extends Mock implements WebCompiler {} class MockPlatform extends Mock implements Platform {} class MockProcessManager extends Mock implements ProcessManager {} class MockProcessResult extends Mock implements ProcessResult {