blob: f28cec60111355c1332d2036acad9e1511428d31 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. 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:convert';
import 'dart:io';
import 'package:dev_compiler/dev_compiler.dart' show Compiler;
import 'package:frontend_server/src/javascript_bundle.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:package_config/package_config.dart';
import 'package:test/test.dart';
/// Additional indexed types required by the dev_compiler's NativeTypeSet.
final Map<String, Map<String, List<String>>> additionalRequiredClasses = {
'dart:core': {'Comparable': []},
'dart:async': {
'StreamIterator': ['get:current', 'moveNext', 'cancel', ''],
'_SyncStarIterator': ['_current', '_datum', '_yieldStar'],
'_IterationMarker': ['yieldSingle', 'yieldStar'],
},
'dart:_interceptors': {
'JSBool': [],
'JSNumber': [],
'JSArray': [],
'JSString': [],
'LegacyJavaScriptObject': [],
},
'dart:_native_typed_data': {},
'dart:collection': {
'ListBase': [],
'MapBase': [],
'LinkedHashSet': [],
'_HashSet': [],
'_IdentityHashSet': [],
},
'dart:math': {'Rectangle': []},
'dart:html': {},
'dart:_rti': {'Rti': [], '_Universe': []},
'dart:indexed_db': {},
'dart:svg': {},
'dart:web_audio': {},
'dart:web_gl': {},
'dart:_js_helper': {
'PrivateSymbol': [],
'LinkedMap': [],
'IdentityMap': [],
'LinkedSet': [],
'IdentitySet': [],
'SyncIterable': [],
},
};
/// Additional indexed top level methods required by the dev_compiler.
final Map<String, List<String>> requiredTopLevels = {
'dart:_runtime': ['assertInterop'],
'dart:async': [
'_asyncAwait',
'_asyncReturn',
'_asyncRethrow',
'_asyncStarHelper',
'_asyncStartSync',
'_makeAsyncAwaitCompleter',
'_makeSyncStarIterable',
'_makeAsyncStarStreamController',
'_makeSyncStarIterable',
'_streamOfController',
'_wrapJsFunctionForAsync',
],
};
void main() {
Map<String, List<String>> allRequiredTypes = _combineMaps(
CoreTypes.requiredClasses,
additionalRequiredClasses
.map((k, v) => new MapEntry(k, v.keys.toList())));
List<String> allRequiredLibraries = [
...allRequiredTypes.keys,
...requiredTopLevels.keys,
];
List<Library> testCoreLibraries = [];
for (String requiredLibrary in allRequiredLibraries) {
Library library = new Library(Uri.parse(requiredLibrary),
fileUri: Uri.parse(requiredLibrary));
for (String requiredClass in allRequiredTypes[requiredLibrary] ?? []) {
library.addClass(new Class(
name: requiredClass,
fileUri: Uri.parse(requiredLibrary),
procedures: [
for (String requiredMember
in (additionalRequiredClasses[requiredLibrary]
?[requiredClass] ??
[]))
new Procedure(new Name(requiredMember, library),
ProcedureKind.Method, new FunctionNode(new EmptyStatement()),
fileUri: Uri.parse(requiredLibrary))
]));
}
for (String requiredMember in requiredTopLevels[requiredLibrary] ?? []) {
library.addProcedure(new Procedure(new Name(requiredMember, library),
ProcedureKind.Method, new FunctionNode(new EmptyStatement()),
fileUri: Uri.parse(requiredLibrary)));
}
testCoreLibraries.add(library);
}
final PackageConfig packageConfig = PackageConfig.parseJson({
'configVersion': 2,
'packages': [
{
'name': 'a',
'rootUri': 'file:///pkg/a',
'packageUri': 'lib/',
}
],
}, Uri.base);
final String multiRootScheme = 'org-dartlang-app';
for (final bool debuggerNames in [true, false]) {
group('Debugger module names: $debuggerNames |', () {
test('Creates component uris for file paths', () async {
final Uri fileUri = new Uri.file('/c.dart');
final IncrementalJavaScriptBundler javaScriptBundler =
new IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
useDebuggerModuleNames: debuggerNames,
);
final String componentUrl =
javaScriptBundler.urlForComponentUri(fileUri, packageConfig);
final String libraryBundleName =
javaScriptBundler.makeLibraryBundleName(componentUrl);
expect(componentUrl, '/c.dart');
expect(libraryBundleName, 'c.dart');
});
test('Creates component uris for package paths', () async {
final Uri packageUri = Uri.parse('package:a/a.dart');
final IncrementalJavaScriptBundler javaScriptBundler =
new IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
useDebuggerModuleNames: debuggerNames,
);
final String componentUrl =
javaScriptBundler.urlForComponentUri(packageUri, packageConfig);
final String libraryBundleName =
javaScriptBundler.makeLibraryBundleName(componentUrl);
expect(componentUrl,
debuggerNames ? 'packages/a/lib/a.dart' : '/packages/a/a.dart');
expect(libraryBundleName,
debuggerNames ? 'packages/a/lib/a.dart' : 'packages/a/a.dart');
});
test('compiles JavaScript code', () async {
final Uri uri = new Uri.file('/c.dart');
final Library library = new Library(
uri,
fileUri: uri,
procedures: [
new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method,
new FunctionNode(new Block([])),
fileUri: uri)
],
);
final Component testComponent =
new Component(libraries: [library, ...testCoreLibraries]);
final IncrementalJavaScriptBundler javaScriptBundler =
new IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
useDebuggerModuleNames: debuggerNames,
);
await javaScriptBundler.initialize(testComponent, uri, packageConfig);
final _MemorySink manifestSink = new _MemorySink();
final _MemorySink codeSink = new _MemorySink();
final _MemorySink sourcemapSink = new _MemorySink();
final _MemorySink metadataSink = new _MemorySink();
final _MemorySink symbolsSink = new _MemorySink();
final CoreTypes coreTypes = new CoreTypes(testComponent);
final Map<String, Compiler> compilers = await javaScriptBundler.compile(
new ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final Map<String, dynamic> manifest =
json.decode(utf8.decode(manifestSink.buffer));
final String code = utf8.decode(codeSink.buffer);
expect(manifest, {
'/c.dart.lib.js': {
'code': [0, codeSink.buffer.length],
'sourcemap': [0, sourcemapSink.buffer.length],
},
});
expect(code, contains('ArbitrarilyChosen'));
// Verify source map url is correct.
expect(code, contains('sourceMappingURL=c.dart.lib.js.map'));
// Verify program compilers are created.
expect(compilers.keys, equals([library.importUri.toString()]));
});
test('converts package: uris into /packages/ uris', () async {
Uri importUri = Uri.parse('package:a/a.dart');
Uri fileUri = packageConfig.resolve(importUri)!;
final Library library = new Library(
importUri,
fileUri: fileUri,
procedures: [
new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method,
new FunctionNode(new Block([])),
fileUri: fileUri)
],
);
final Component testComponent =
new Component(libraries: [library, ...testCoreLibraries]);
final IncrementalJavaScriptBundler javaScriptBundler =
new IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
useDebuggerModuleNames: debuggerNames,
);
await javaScriptBundler.initialize(
testComponent, importUri, packageConfig);
final _MemorySink manifestSink = new _MemorySink();
final _MemorySink codeSink = new _MemorySink();
final _MemorySink sourcemapSink = new _MemorySink();
final _MemorySink metadataSink = new _MemorySink();
final _MemorySink symbolsSink = new _MemorySink();
final CoreTypes coreTypes = new CoreTypes(testComponent);
await javaScriptBundler.compile(
new ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final Map<String, dynamic> manifest =
json.decode(utf8.decode(manifestSink.buffer));
final String code = utf8.decode(codeSink.buffer);
final String componentUrl = javaScriptBundler.urlForComponentUri(
library.importUri, packageConfig);
expect(manifest, {
'$componentUrl.lib.js': {
'code': [0, codeSink.buffer.length],
'sourcemap': [0, sourcemapSink.buffer.length],
},
});
expect(code, contains('ArbitrarilyChosen'));
// Verify source map url is correct.
expect(code, contains('sourceMappingURL=a.dart.lib.js.map'));
});
test('multi-root uris create library bundles relative to the root',
() async {
Uri importUri = Uri.parse('$multiRootScheme:/web/main.dart');
Uri fileUri = importUri;
final Library library = new Library(
importUri,
fileUri: fileUri,
procedures: [
new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method,
new FunctionNode(new Block([])),
fileUri: fileUri)
],
);
final Component testComponent =
new Component(libraries: [library, ...testCoreLibraries]);
final IncrementalJavaScriptBundler javaScriptBundler =
new IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
useDebuggerModuleNames: debuggerNames,
);
await javaScriptBundler.initialize(
testComponent, importUri, packageConfig);
final _MemorySink manifestSink = new _MemorySink();
final _MemorySink codeSink = new _MemorySink();
final _MemorySink sourcemapSink = new _MemorySink();
final _MemorySink metadataSink = new _MemorySink();
final _MemorySink symbolsSink = new _MemorySink();
final CoreTypes coreTypes = new CoreTypes(testComponent);
await javaScriptBundler.compile(
new ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final Map manifest = json.decode(utf8.decode(manifestSink.buffer));
final String code = utf8.decode(codeSink.buffer);
expect(manifest, {
'${importUri.path}.lib.js': {
'code': [0, codeSink.buffer.length],
'sourcemap': [0, sourcemapSink.buffer.length],
},
});
expect(code, contains('ArbitrarilyChosen'));
// Verify source map url is correct.
expect(code, contains('sourceMappingURL=main.dart.lib.js.map'));
});
});
}
test('can combine strongly connected components', () async {
// Create three libraries A, B, C where A is the entrypoint and B & C
// circularly import each other.
final Library libraryC =
new Library(new Uri.file('/c.dart'), fileUri: new Uri.file('/c.dart'));
final Library libraryB =
new Library(new Uri.file('/b.dart'), fileUri: new Uri.file('/b.dart'));
libraryC.dependencies.add(new LibraryDependency.import(libraryB));
libraryB.dependencies.add(new LibraryDependency.import(libraryC));
final Uri uriA = new Uri.file('/a.dart');
final Library libraryA = new Library(
uriA,
fileUri: uriA,
dependencies: [
new LibraryDependency.import(libraryB),
],
procedures: [
new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method,
new FunctionNode(new Block([])),
fileUri: uriA)
],
);
final Component testComponent = new Component(
libraries: [libraryA, libraryB, libraryC, ...testCoreLibraries]);
final IncrementalJavaScriptBundler javaScriptBundler =
new IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
);
await javaScriptBundler.initialize(testComponent, uriA, packageConfig);
final _MemorySink manifestSink = new _MemorySink();
final _MemorySink codeSink = new _MemorySink();
final _MemorySink sourcemapSink = new _MemorySink();
final _MemorySink metadataSink = new _MemorySink();
final _MemorySink symbolsSink = new _MemorySink();
final CoreTypes coreTypes = new CoreTypes(testComponent);
await javaScriptBundler.compile(
new ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final String code = utf8.decode(codeSink.buffer);
final Map<String, dynamic> manifest =
json.decode(utf8.decode(manifestSink.buffer));
// There should only be two modules since C and B should be combined.
final String moduleHeader = r"define(['dart_sdk'], (function load__";
expect(moduleHeader.allMatches(code), hasLength(2));
// Expected module headers.
final String aModuleHeader =
r"define(['dart_sdk'], (function load__a_dart(dart_sdk) {";
expect(code, contains(aModuleHeader));
final String cModuleHeader =
r"define(['dart_sdk'], (function load__c_dart(dart_sdk) {";
expect(code, contains(cModuleHeader));
// Verify source map url is correct.
expect(code, contains('sourceMappingURL=a.dart.lib.js.map'));
final List<dynamic> offsets = manifest['/a.dart.lib.js']['sourcemap'];
final Map<String, dynamic> sourcemapModuleA = json.decode(
utf8.decode(sourcemapSink.buffer.sublist(offsets.first, offsets.last)));
// Verify source maps are pointing at correct source files.
expect(sourcemapModuleA['file'], 'a.dart.lib.js');
});
test('can invalidate changes to strongly connected components', () async {
final Uri uriA = new Uri.file('/a.dart');
final Uri uriB = new Uri.file('/b.dart');
final Uri uriC = new Uri.file('/c.dart');
// Create three libraries A, B, C where A is the entrypoint and B & C
// circularly import each other.
final Library libraryC = new Library(uriC, fileUri: uriC);
final Library libraryB = new Library(uriB, fileUri: uriB);
libraryC.dependencies.add(new LibraryDependency.import(libraryB));
libraryB.dependencies.add(new LibraryDependency.import(libraryC));
final Library libraryA = new Library(
uriA,
fileUri: uriA,
dependencies: [
new LibraryDependency.import(libraryB),
],
procedures: [
new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method,
new FunctionNode(new Block([])),
fileUri: uriA)
],
);
final Component testComponent = new Component(
libraries: [libraryA, libraryB, libraryC, ...testCoreLibraries]);
final IncrementalJavaScriptBundler javaScriptBundler =
new IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
);
await javaScriptBundler.initialize(testComponent, uriA, packageConfig);
// Now change A and B so that they no longer import each other.
final Library libraryC2 = new Library(uriC, fileUri: uriC);
final Library libraryB2 = new Library(uriB, fileUri: uriB);
libraryB2.dependencies.add(new LibraryDependency.import(libraryC2));
final Library libraryA2 = new Library(
uriA,
fileUri: uriA,
dependencies: [
new LibraryDependency.import(libraryB2),
],
procedures: [
new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method,
new FunctionNode(new Block([])),
fileUri: uriA)
],
);
final Component partialComponent =
new Component(libraries: [libraryA2, libraryB2, libraryC2]);
await javaScriptBundler.invalidate(
partialComponent, testComponent, uriA, packageConfig,
recompileRestart: false);
final _MemorySink manifestSink = new _MemorySink();
final _MemorySink codeSink = new _MemorySink();
final _MemorySink sourcemapSink = new _MemorySink();
final _MemorySink metadataSink = new _MemorySink();
final _MemorySink symbolsSink = new _MemorySink();
final CoreTypes coreTypes = new CoreTypes(testComponent);
await javaScriptBundler.compile(
new ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final String code = utf8.decode(codeSink.buffer);
final Map<String, dynamic> manifest =
json.decode(utf8.decode(manifestSink.buffer));
// There should be 3 modules since A, B, C are now compiled separately
final String moduleHeader = r"define(['dart_sdk'], (function load__";
expect(moduleHeader.allMatches(code), hasLength(3));
// Expected module headers.
final String aModuleHeader =
r"define(['dart_sdk'], (function load__a_dart(dart_sdk) {";
expect(code, contains(aModuleHeader));
final String bModuleHeader =
r"define(['dart_sdk'], (function load__b_dart(dart_sdk) {";
expect(code, contains(bModuleHeader));
final String cModuleHeader =
r"define(['dart_sdk'], (function load__c_dart(dart_sdk) {";
expect(code, contains(cModuleHeader));
// Verify source map url is correct.
expect(code, contains('sourceMappingURL=a.dart.lib.js.map'));
final List<dynamic> offsets = manifest['/a.dart.lib.js']['sourcemap'];
final Map<String, dynamic> sourcemapModuleA = json.decode(
utf8.decode(sourcemapSink.buffer.sublist(offsets.first, offsets.last)));
// Verify source maps are pointing at correct source files.
expect(sourcemapModuleA['file'], 'a.dart.lib.js');
});
test('can compile using the advanced invalidation', () async {
final Uri uriC = new Uri.file('/c.dart');
// Given 3 libraries A -> B -> C
final Library libraryC = new Library(
uriC,
fileUri: uriC,
procedures: [
new Procedure(new Name('CheckForContents'), ProcedureKind.Method,
new FunctionNode(new Block([])),
fileUri: uriC)
],
);
final Uri uriB = new Uri.file('/b.dart');
final Library libraryB = new Library(uriB, fileUri: uriB);
libraryB.dependencies.add(new LibraryDependency.import(libraryC));
final Uri uriA = new Uri.file('/a.dart');
final Library libraryA = new Library(
uriA,
fileUri: uriA,
dependencies: [
new LibraryDependency.import(libraryB),
],
procedures: [
new Procedure(new Name('ArbitrarilyChosen'), ProcedureKind.Method,
new FunctionNode(new Block([])),
fileUri: uriA)
],
);
final Component testComponent = new Component(
libraries: [libraryA, libraryB, libraryC, ...testCoreLibraries]);
final IncrementalJavaScriptBundler javaScriptBundler =
new IncrementalJavaScriptBundler(
null,
{},
multiRootScheme,
);
await javaScriptBundler.initialize(testComponent, uriA, packageConfig);
// Create a new component that only contains C.
final Library libraryC2 = new Library(
uriC,
fileUri: uriC,
procedures: [
new Procedure(new Name('AlternativeContents'), ProcedureKind.Method,
new FunctionNode(new Block([])),
fileUri: uriC)
],
);
final Component partialComponent = new Component(libraries: [libraryC2]);
await javaScriptBundler.invalidate(
partialComponent, testComponent, uriA, packageConfig,
recompileRestart: false);
final _MemorySink manifestSink = new _MemorySink();
final _MemorySink codeSink = new _MemorySink();
final _MemorySink sourcemapSink = new _MemorySink();
final _MemorySink metadataSink = new _MemorySink();
final _MemorySink symbolsSink = new _MemorySink();
final CoreTypes coreTypes = new CoreTypes(testComponent);
await javaScriptBundler.compile(
new ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final String code = utf8.decode(codeSink.buffer);
// There should be only a single module.
final String moduleHeader = r"define(['dart_sdk'], (function load__";
expect(moduleHeader.allMatches(code), hasLength(1));
// C source code should be updated.
expect(code, contains('AlternativeContents'));
});
}
class _MemorySink implements IOSink {
final List<int> buffer = <int>[];
@override
void add(List<int> data) {
buffer.addAll(data);
}
@override
Future<void> close() => new Future.value();
@override
void noSuchMethod(Invocation invocation) {
throw new UnsupportedError(invocation.memberName.toString());
}
}
Map<String, List<String>> _combineMaps(
Map<String, List<String>> left,
Map<String, List<String>> right,
) {
final Map<String, List<String>> result =
new Map<String, List<String>>.of(left);
for (String key in right.keys) {
(result[key] ??= []).addAll(right[key]!);
}
return result;
}