Skip to content

Commit c26d596

Browse files
jonahwilliamscommit-bot@chromium.org
authored andcommitted
[flutter] JavaScript support to frontend_server
Adds support to the frontend server for producing javascript when compiling with a target model of dartdevc. This uses the ProgramCompiler and associated types from the dev_compiler package to output the kernel AST into a "bundle" format. dev_compiler: * Exposes additional types needed for JavaScript compilation. * Allows overriding CoreTypes instead of forcing it to be created from Component. This is require to support compilation of the partial Components created by the incremental compiler. * provides extraIndexedLibraries in Target class so the CoreTypes produced by the incremental compiler have indexed them. frontend_server: * When the target model is dartdev, JavaScript is output instead of kernel. For each Component we compile, we first compute the strongly connected components and produce a List of new Components to interop with the dev compiler. The file uri is used as module name. Change-Id: I4cc117b20671ffd48dd43f9786961c016a02d056 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/121400 Reviewed-by: Alexander Aprelev <aam@google.com> Reviewed-by: Jake Macdonald <jakemac@google.com> Reviewed-by: Nicholas Shahan <nshahan@google.com> Commit-Queue: Jonah Williams <jonahwilliams@google.com>
1 parent b1b4f6b commit c26d596

File tree

10 files changed

+588
-22
lines changed

10 files changed

+588
-22
lines changed

pkg/dev_compiler/lib/dev_compiler.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
// The dev_compiler does not have a publishable public API, instead this is
66
// intended for other consumers within the Dart SDK.
77
export 'src/kernel/target.dart' show DevCompilerTarget;
8+
export 'src/kernel/compiler.dart' show ProgramCompiler;
9+
export 'src/kernel/command.dart' show jsProgramToCode;
10+
export 'src/compiler/shared_command.dart' show SharedCompilerOptions;
11+
export 'src/compiler/module_builder.dart' show ModuleFormat;

pkg/dev_compiler/lib/src/kernel/compiler.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,9 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
216216
final NullableInference _nullableInference;
217217

218218
factory ProgramCompiler(Component component, ClassHierarchy hierarchy,
219-
SharedCompilerOptions options) {
220-
var coreTypes = CoreTypes(component);
219+
SharedCompilerOptions options,
220+
{CoreTypes coreTypes}) {
221+
coreTypes ??= CoreTypes(component);
221222
var types = TypeSchemaEnvironment(coreTypes, hierarchy);
222223
var constants = DevCompilerConstants();
223224
var nativeTypes = NativeTypeSet(coreTypes, constants);

pkg/dev_compiler/lib/src/kernel/target.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,23 @@ class DevCompilerTarget extends Target {
5959
'dart:web_sql'
6060
];
6161

62+
// The libraries required to be indexed via CoreTypes.
63+
@override
64+
List<String> get extraIndexedLibraries => const [
65+
'dart:async',
66+
'dart:collection',
67+
'dart:html',
68+
'dart:indexed_db',
69+
'dart:math',
70+
'dart:svg',
71+
'dart:web_audio',
72+
'dart:web_gl',
73+
'dart:web_sql',
74+
'dart:_interceptors',
75+
'dart:_js_helper',
76+
'dart:_native_typed_data',
77+
];
78+
6279
@override
6380
bool mayDefineRestrictedType(Uri uri) =>
6481
uri.scheme == 'dart' &&

pkg/frontend_server/lib/frontend_server.dart

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,9 @@ import 'package:vm/kernel_front_end.dart'
5050
setVMEnvironmentDefines,
5151
sortComponent,
5252
writeDepfile;
53-
import 'package:vm/target/dart_runner.dart' show DartRunnerTarget;
54-
import 'package:vm/target/flutter.dart' show FlutterTarget;
55-
import 'package:vm/target/flutter_runner.dart' show FlutterRunnerTarget;
56-
import 'package:vm/target/vm.dart' show VmTarget;
53+
54+
import 'src/javascript_bundle.dart';
55+
import 'src/strong_components.dart';
5756

5857
ArgParser argParser = ArgParser(allowTrailingOptions: true)
5958
..addFlag('train',
@@ -273,14 +272,7 @@ class FrontendCompiler implements CompilerInterface {
273272
this.unsafePackageSerialization,
274273
this.incrementalSerialization: true}) {
275274
_outputStream ??= stdout;
276-
printerFactory ??= BinaryPrinterFactory();
277-
// Initialize supported kernel targets.
278-
targets['dart_runner'] = (TargetFlags flags) => DartRunnerTarget(flags);
279-
targets['flutter'] = (TargetFlags flags) => FlutterTarget(flags);
280-
targets['flutter_runner'] =
281-
(TargetFlags flags) => FlutterRunnerTarget(flags);
282-
targets['vm'] = (TargetFlags flags) => VmTarget(flags);
283-
targets['dartdevc'] = (TargetFlags flags) => DevCompilerTarget(flags);
275+
printerFactory ??= new BinaryPrinterFactory();
284276
}
285277

286278
StringSink _outputStream;
@@ -382,6 +374,8 @@ class FrontendCompiler implements CompilerInterface {
382374
aot: options['aot'],
383375
)..parseCommandLineFlags(options['bytecode-options']);
384376

377+
// Initialize additional supported kernel targets.
378+
targets['dartdevc'] = (TargetFlags flags) => DevCompilerTarget(flags);
385379
compilerOptions.target = createFrontEndTarget(
386380
options['target'],
387381
trackWidgetCreation: options['track-widget-creation'],
@@ -444,13 +438,15 @@ class FrontendCompiler implements CompilerInterface {
444438
useProtobufTreeShaker: options['protobuf-tree-shaker']));
445439
}
446440
if (results.component != null) {
447-
if (transformer != null) {
448-
transformer.transform(results.component);
449-
}
441+
transformer?.transform(results.component);
450442

451-
await writeDillFile(results, _kernelBinaryFilename,
452-
filterExternal: importDill != null,
453-
incrementalSerializer: incrementalSerializer);
443+
if (_compilerOptions.target.name == 'dartdevc') {
444+
await writeJavascriptBundle(results, _kernelBinaryFilename);
445+
} else {
446+
await writeDillFile(results, _kernelBinaryFilename,
447+
filterExternal: importDill != null,
448+
incrementalSerializer: incrementalSerializer);
449+
}
454450

455451
_outputStream.writeln(boundaryKey);
456452
await _outputDependenciesDelta(results.compiledSources);
@@ -514,6 +510,25 @@ class FrontendCompiler implements CompilerInterface {
514510
previouslyReportedDependencies = uris;
515511
}
516512

513+
/// Write a JavaScript bundle containg the provided component.
514+
Future<void> writeJavascriptBundle(
515+
KernelCompilationResults results, String filename) async {
516+
final Component component = results.component;
517+
// Compute strongly connected components.
518+
final strongComponents = StrongComponents(component, _mainSource);
519+
strongComponents.computeModules();
520+
521+
// Create JavaScript bundler.
522+
final File sourceFile = File('$filename.sources');
523+
final File manifestFile = File('$filename.json');
524+
if (!sourceFile.parent.existsSync()) {
525+
sourceFile.parent.createSync(recursive: true);
526+
}
527+
final bundler = JavaScriptBundler(component, strongComponents);
528+
bundler.compile(results.classHierarchy, results.coreTypes,
529+
sourceFile.openWrite(), manifestFile.openWrite());
530+
}
531+
517532
writeDillFile(KernelCompilationResults results, String filename,
518533
{bool filterExternal: false,
519534
IncrementalSerializer incrementalSerializer}) async {
@@ -707,8 +722,12 @@ class FrontendCompiler implements CompilerInterface {
707722
_generator.getCoreTypes(),
708723
deltaProgram.uriToSource.keys);
709724

710-
await writeDillFile(results, _kernelBinaryFilename,
711-
incrementalSerializer: _generator.incrementalSerializer);
725+
if (_compilerOptions.target.name == 'dartdevc') {
726+
await writeJavascriptBundle(results, _kernelBinaryFilename);
727+
} else {
728+
await writeDillFile(results, _kernelBinaryFilename,
729+
incrementalSerializer: _generator.incrementalSerializer);
730+
}
712731

713732
_outputStream.writeln(boundaryKey);
714733
await _outputDependenciesDelta(results.compiledSources);
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:dev_compiler/dev_compiler.dart';
5+
import 'package:kernel/ast.dart';
6+
import 'package:kernel/class_hierarchy.dart';
7+
import 'package:kernel/core_types.dart';
8+
9+
import 'strong_components.dart';
10+
11+
/// Produce a special bundle format for compiled JavaScript.
12+
///
13+
/// The bundle format consists of two files: One containing all produced
14+
/// JavaScript modules concatenated together, and a second containing the byte
15+
/// offsets by module name for each JavaScript module in JSON format.
16+
///
17+
/// Ths format is analgous to the dill and .incremental.dill in that during
18+
/// an incremental build, a different file is written for each which contains
19+
/// only the updated libraries.
20+
class JavaScriptBundler {
21+
JavaScriptBundler(this._originalComponent, this._strongComponents) {
22+
_summaries = <Component>[];
23+
_summaryUris = <Uri>[];
24+
_moduleImportForSummary = <Uri, String>{};
25+
_uriToComponent = <Uri, Component>{};
26+
for (Uri uri in _strongComponents.modules.keys) {
27+
final List<Library> libraries = _strongComponents.modules[uri].toList();
28+
final Component summaryComponent = Component(
29+
libraries: libraries,
30+
nameRoot: _originalComponent.root,
31+
uriToSource: _originalComponent.uriToSource,
32+
);
33+
_summaries.add(summaryComponent);
34+
_summaryUris.add(uri);
35+
_moduleImportForSummary[uri] = uri.toFilePath();
36+
_uriToComponent[uri] = summaryComponent;
37+
}
38+
}
39+
40+
final StrongComponents _strongComponents;
41+
final Component _originalComponent;
42+
43+
List<Component> _summaries;
44+
List<Uri> _summaryUris;
45+
Map<Uri, String> _moduleImportForSummary;
46+
Map<Uri, Component> _uriToComponent;
47+
48+
/// Compile each component into a single JavaScript module.
49+
Future<void> compile(ClassHierarchy classHierarchy, CoreTypes coreTypes,
50+
IOSink codeSink, IOSink manifestSink) async {
51+
var offset = 0;
52+
final _manifest = <String, List<int>>{};
53+
final Set<Uri> visited = <Uri>{};
54+
for (Library library in _originalComponent.libraries) {
55+
if (library.isExternal || library.importUri.scheme == 'dart') {
56+
continue;
57+
}
58+
final Uri moduleUri = _strongComponents.moduleAssignment[library.fileUri];
59+
if (visited.contains(moduleUri)) {
60+
continue;
61+
}
62+
visited.add(moduleUri);
63+
64+
final summaryComponent = _uriToComponent[moduleUri];
65+
final compiler = ProgramCompiler(
66+
_originalComponent,
67+
classHierarchy,
68+
SharedCompilerOptions(sourceMap: true, summarizeApi: false),
69+
coreTypes: coreTypes,
70+
);
71+
final jsModule = compiler.emitModule(
72+
summaryComponent, _summaries, _summaryUris, _moduleImportForSummary);
73+
final moduleUrl = moduleUri.toString();
74+
final code = jsProgramToCode(jsModule, ModuleFormat.amd,
75+
inlineSourceMap: true,
76+
buildSourceMap: true,
77+
jsUrl: '$moduleUrl.js',
78+
mapUrl: moduleUrl);
79+
final bytes = utf8.encode(code.code);
80+
codeSink.add(bytes);
81+
_manifest[_moduleImportForSummary[moduleUri]] = <int>[
82+
offset,
83+
offset += bytes.length
84+
];
85+
}
86+
manifestSink.add(utf8.encode(json.encode(_manifest)));
87+
await Future.wait([codeSink.close(), manifestSink.close()]);
88+
}
89+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:kernel/ast.dart';
6+
import 'package:kernel/util/graph.dart';
7+
8+
/// Compute the strongly connected components for JavaScript compilation.
9+
///
10+
/// Implements a Path-based strong component algorithm.
11+
/// See https://en.wikipedia.org/wiki/Path-based_strong_component_algorithm
12+
///
13+
/// The file URI for each library is used as an identifier for the module
14+
/// name. [moduleAssignment] will be populated with a mapping of library URI to
15+
/// module name, while [modules] will be populated with a mapping of module
16+
/// name to library set.
17+
///
18+
/// JavaScript import semantics do not permit circular imports in the same
19+
/// manner that Dart does. When compiling a set of libraries with circular
20+
/// imports, these must be combined into a single JavaScript module.
21+
///
22+
/// On incremental updates, we completely recompute the strongly connected
23+
/// components, but only for the partial component produced.
24+
class StrongComponents {
25+
StrongComponents(
26+
this.component,
27+
this.entrypointFileUri,
28+
);
29+
30+
/// The Component that is being compiled.
31+
///
32+
/// On incremental compiles, this will only contain the invalidated
33+
/// lbraries.
34+
final Component component;
35+
36+
/// The file URI containing main for this application.
37+
final Uri entrypointFileUri;
38+
39+
/// The set of libraries for each module URI.
40+
///
41+
/// This is populated after calling [computeModules] once.
42+
final Map<Uri, List<Library>> modules = <Uri, List<Library>>{};
43+
44+
/// The module URI for each library file URI.
45+
///
46+
/// This is populated after calling [computeModules] once.
47+
final Map<Uri, Uri> moduleAssignment = <Uri, Uri>{};
48+
49+
/// Compute the strongly connected components for the current program.
50+
void computeModules() {
51+
assert(modules.isEmpty);
52+
if (component.libraries.isEmpty) {
53+
return;
54+
}
55+
final Library entrypoint = component.libraries
56+
.firstWhere((Library library) => library.fileUri == entrypointFileUri);
57+
final List<List<Library>> results =
58+
computeStrongComponents(_LibraryGraph(entrypoint));
59+
for (List<Library> component in results) {
60+
assert(component.length > 0);
61+
final Uri moduleUri = component.first.fileUri;
62+
modules[moduleUri] = component;
63+
for (Library componentLibrary in component) {
64+
moduleAssignment[componentLibrary.fileUri] = moduleUri;
65+
}
66+
}
67+
}
68+
}
69+
70+
class _LibraryGraph implements Graph<Library> {
71+
_LibraryGraph(this.library);
72+
73+
final Library library;
74+
75+
@override
76+
Iterable<Library> neighborsOf(Library vertex) {
77+
return <Library>[
78+
for (LibraryDependency dependency in vertex.dependencies)
79+
if (!dependency.targetLibrary.isExternal &&
80+
dependency.targetLibrary.importUri.scheme != 'dart')
81+
dependency.targetLibrary
82+
];
83+
}
84+
85+
@override
86+
Iterable<Library> get vertices => <Library>[library];
87+
}

pkg/frontend_server/pubspec.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ dependencies:
1313
front_end: ^0.1.6
1414
kernel: ^0.3.6
1515
args: ^1.4.4
16+
17+
dev_dependencies:
18+
test: any

0 commit comments

Comments
 (0)