Unverified Commit c91b6571 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Codegen an entrypoint for flutter web applications (#33956)

parent 4e5cf5ef
......@@ -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),
));
}
// 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<void> main() async {
await ui.webOnlyInitializePlatform(); // ignore: undefined_function
runApp(const GalleryApp());
}
......@@ -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<FlutterCommandResult> 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;
}
}
......@@ -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<T> runInContext<T>(
UserMessages: () => UserMessages(),
VisualStudio: () => VisualStudio(),
VisualStudioValidator: () => const VisualStudioValidator(),
WebCompiler: () => const WebCompiler(),
WebWorkflow: () => const WebWorkflow(),
WindowsWorkflow: () => const WindowsWorkflow(),
Xcode: () => Xcode(),
......
......@@ -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<void> ensureReadyForPlatformSpecificTooling() async {
/// Generate index.html in build/web. Eventually we could support
/// a custom html under the web sub directory.
......
......@@ -66,6 +66,7 @@ class WebAssetServer {
/// An HTTP server which provides JavaScript and web assets to the browser.
Future<void> _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')) {
......
......@@ -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<WebCompiler>();
import '../project.dart';
/// The [WebCompilationProxy] instance.
WebCompilationProxy get webCompilationProxy =>
context.get<WebCompilationProxy>();
/// A wrapper around dart tools for web compilation.
class WebCompiler {
const WebCompiler();
WebCompilationProxy get webCompilationProxy => context.get<WebCompilationProxy>();
/// 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<int> 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);
}
final String outputPath = fs.path.join(outputDir.path, 'main.dart.js');
if (!processManager.canRun(engineDartPath)) {
throwToolExit('Unable to find Dart binary at $engineDartPath');
}
Future<void> 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: <String>[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 Dart to JavaScript.
final List<String> command = <String>[
engineDartPath,
dart2jsPath,
target,
'-o',
'$outputPath',
'-O4',
'--libraries-spec=$librariesPath',
];
if (minify) {
command.add('-m');
// 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'));
}
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<void> 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<bool> initialize({
@required Directory projectDirectory,
@required List<String> targets,
String testOutputDir,
bool release,
}) async {
throw UnimplementedError();
}
......
......@@ -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);
......
// 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: <Type, Generator>{
ProcessManager: () => mockProcessManager,
});
});
test('invokes dart2js with correct arguments', () => testBed.run(() async {
await webCompiler.compileDart2js(target: 'lib/main.dart');
verify(mockProcessManager.start(<String>[
'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 {}
......@@ -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: <Type, Generator>{
ChromeLauncher: () => mockChromeLauncher,
WebCompiler: () => mockWebCompiler,
Platform: () => mockPlatform,
});
testUsingContext('Invokes version command on non-Windows platforms', () async{
when(mockPlatform.isWindows).thenReturn(false);
when(mockPlatform.environment).thenReturn(<String, String>{
......@@ -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 {
......
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