blob: d927f825dde449f157c39cd02d178451a8343503 [file] [log] [blame]
// 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 'dart:convert';
import 'dart:io';
import 'package:frontend_server/src/javascript_bundle.dart';
import 'package:frontend_server/src/strong_components.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, List<String>> additionalRequiredClasses = {
'dart:core': ['Comparable'],
'dart:async': [
'StreamIterator',
'_AsyncStarImpl',
],
'dart:_interceptors': [
'JSBool',
'JSNumber',
'JSArray',
'JSString',
],
'dart:_native_typed_data': [],
'dart:collection': [
'ListMixin',
'MapMixin',
'LinkedHashSet',
'_HashSet',
'_IdentityHashSet',
],
'dart:math': ['Rectangle'],
'dart:html': [],
'dart:indexed_db': [],
'dart:svg': [],
'dart:web_audio': [],
'dart:web_gl': [],
'dart:web_sql': [],
'dart:_js_helper': [
'PrivateSymbol',
'LinkedMap',
'IdentityMap',
'SyncIterable',
],
};
/// Additional indexed top level methods required by the dev_compiler.
final Map<String, List<String>> requiredMethods = {
'dart:_runtime': ['assertInterop'],
};
void main() {
final allRequiredTypes =
_combineMaps(CoreTypes.requiredClasses, additionalRequiredClasses);
final allRequiredLibraries = {
...allRequiredTypes.keys,
...requiredMethods.keys
};
final testCoreLibraries = [
for (String requiredLibrary in allRequiredLibraries)
Library(Uri.parse(requiredLibrary),
fileUri: Uri.parse(requiredLibrary),
classes: [
for (String requiredClass
in allRequiredTypes[requiredLibrary] ?? [])
Class(name: requiredClass, fileUri: Uri.parse(requiredLibrary)),
],
procedures: [
for (var requiredMethod in requiredMethods[requiredLibrary] ?? [])
Procedure(Name(requiredMethod), ProcedureKind.Method,
FunctionNode(EmptyStatement()),
fileUri: Uri.parse(requiredLibrary)),
]),
];
final packageConfig = PackageConfig.parseJson({
'configVersion': 2,
'packages': [
{
'name': 'a',
'rootUri': 'file:///pkg/a',
'packagesUri': '',
}
],
}, Uri.base);
final multiRootScheme = 'org-dartlang-app';
test('compiles JavaScript code', () async {
final uri = Uri.file('/c.dart');
final library = Library(
uri,
fileUri: uri,
procedures: [
Procedure(Name('ArbitrarilyChosen'), ProcedureKind.Method,
FunctionNode(Block([])),
fileUri: uri)
],
);
final testComponent = Component(libraries: [library, ...testCoreLibraries]);
final strongComponents =
StrongComponents(testComponent, {}, Uri.file('/c.dart'));
strongComponents.computeModules();
final javaScriptBundler = JavaScriptBundler(
testComponent, strongComponents, multiRootScheme, packageConfig);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();
final sourcemapSink = _MemorySink();
final metadataSink = _MemorySink();
final symbolsSink = _MemorySink();
final coreTypes = CoreTypes(testComponent);
final compilers = await javaScriptBundler.compile(
ClassHierarchy(testComponent, coreTypes),
coreTypes,
{},
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final Map 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([urlForComponentUri(library.importUri)]));
});
test('converts package: uris into /packages/ uris', () async {
var importUri = Uri.parse('package:a/a.dart');
var fileUri = await packageConfig.resolve(importUri);
final library = Library(
importUri,
fileUri: fileUri,
procedures: [
Procedure(Name('ArbitrarilyChosen'), ProcedureKind.Method,
FunctionNode(Block([])),
fileUri: fileUri)
],
);
final testComponent = Component(libraries: [library, ...testCoreLibraries]);
final strongComponents = StrongComponents(testComponent, {}, fileUri);
strongComponents.computeModules();
final javaScriptBundler = JavaScriptBundler(
testComponent, strongComponents, multiRootScheme, packageConfig);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();
final sourcemapSink = _MemorySink();
final metadataSink = _MemorySink();
final symbolsSink = _MemorySink();
final coreTypes = CoreTypes(testComponent);
await javaScriptBundler.compile(
ClassHierarchy(testComponent, coreTypes),
coreTypes,
{},
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final Map manifest = json.decode(utf8.decode(manifestSink.buffer));
final String code = utf8.decode(codeSink.buffer);
expect(manifest, {
'/packages/a/a.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=a.dart.lib.js.map'));
});
test('multi-root uris create modules relative to the root', () async {
var importUri = Uri.parse('$multiRootScheme:/web/main.dart');
var fileUri = importUri;
final library = Library(
importUri,
fileUri: fileUri,
procedures: [
Procedure(Name('ArbitrarilyChosen'), ProcedureKind.Method,
FunctionNode(Block([])),
fileUri: fileUri)
],
);
final testComponent = Component(libraries: [library, ...testCoreLibraries]);
final strongComponents = StrongComponents(testComponent, {}, fileUri);
strongComponents.computeModules();
final javaScriptBundler = JavaScriptBundler(
testComponent, strongComponents, multiRootScheme, packageConfig);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();
final sourcemapSink = _MemorySink();
final metadataSink = _MemorySink();
final symbolsSink = _MemorySink();
final coreTypes = CoreTypes(testComponent);
await javaScriptBundler.compile(
ClassHierarchy(testComponent, coreTypes),
coreTypes,
{},
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', () {
// Create three libraries A, B, C where A is the entrypoint and B & C
// circularly import each other.
final libraryC = Library(Uri.file('/c.dart'), fileUri: Uri.file('/c.dart'));
final libraryB = Library(Uri.file('/b.dart'), fileUri: Uri.file('/b.dart'));
libraryC.dependencies.add(LibraryDependency.import(libraryB));
libraryB.dependencies.add(LibraryDependency.import(libraryC));
final uriA = Uri.file('/a.dart');
final libraryA = Library(
uriA,
fileUri: uriA,
dependencies: [
LibraryDependency.import(libraryB),
],
procedures: [
Procedure(Name('ArbitrarilyChosen'), ProcedureKind.Method,
FunctionNode(Block([])),
fileUri: uriA)
],
);
final testComponent = Component(
libraries: [libraryA, libraryB, libraryC, ...testCoreLibraries]);
final strongComponents =
StrongComponents(testComponent, {}, Uri.file('/a.dart'));
strongComponents.computeModules();
final javaScriptBundler = JavaScriptBundler(
testComponent, strongComponents, multiRootScheme, packageConfig);
final manifestSink = _MemorySink();
final codeSink = _MemorySink();
final sourcemapSink = _MemorySink();
final metadataSink = _MemorySink();
final symbolsSink = _MemorySink();
final coreTypes = CoreTypes(testComponent);
javaScriptBundler.compile(
ClassHierarchy(testComponent, coreTypes),
coreTypes,
{},
codeSink,
manifestSink,
sourcemapSink,
metadataSink,
symbolsSink,
);
final code = utf8.decode(codeSink.buffer);
final manifest = json.decode(utf8.decode(manifestSink.buffer));
// There should only be two modules since C and B should be combined.
final moduleHeader = r"define(['dart_sdk'], (function load__";
expect(moduleHeader.allMatches(code), hasLength(2));
// Expected module headers.
final aModuleHeader =
r"define(['dart_sdk'], (function load__a_dart(dart_sdk) {";
expect(code, contains(aModuleHeader));
final 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 offsets = manifest['/a.dart.lib.js']['sourcemap'];
final 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');
});
}
class _MemorySink implements IOSink {
final List<int> buffer = <int>[];
@override
void add(List<int> data) {
buffer.addAll(data);
}
@override
Future<void> close() => Future.value();
@override
void noSuchMethod(Invocation invocation) {
throw UnsupportedError(invocation.memberName.toString());
}
}
Map<String, List<String>> _combineMaps(
Map<String, List<String>> left,
Map<String, List<String>> right,
) {
final result = Map<String, List<String>>.from(left);
for (String key in right.keys) {
result[key] ??= [];
result[key].addAll(right[key]);
}
return result;
}