// 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> 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> 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), classes: [ for (String requiredClass in allRequiredTypes[requiredLibrary] ?? []) Class(name: requiredClass), ], procedures: [ for (var requiredMethod in requiredMethods[requiredLibrary] ?? []) Procedure(Name(requiredMethod), ProcedureKind.Method, FunctionNode(EmptyStatement())), ]), ]; 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 library = Library( Uri.file('/c.dart'), fileUri: Uri.file('/c.dart'), procedures: [ Procedure(Name('ArbitrarilyChosen'), ProcedureKind.Method, FunctionNode(Block([]))) ], ); 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 coreTypes = CoreTypes(testComponent); await javaScriptBundler.compile(ClassHierarchy(testComponent, coreTypes), coreTypes, {}, codeSink, manifestSink, sourcemapSink, metadataSink); 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')); }); 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([]))) ], ); 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 coreTypes = CoreTypes(testComponent); await javaScriptBundler.compile(ClassHierarchy(testComponent, coreTypes), coreTypes, {}, codeSink, manifestSink, sourcemapSink, metadataSink); 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([]))) ], ); 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 coreTypes = CoreTypes(testComponent); await javaScriptBundler.compile(ClassHierarchy(testComponent, coreTypes), coreTypes, {}, codeSink, manifestSink, sourcemapSink, metadataSink); 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 libraryA = Library( Uri.file('/a.dart'), fileUri: Uri.file('/a.dart'), dependencies: [ LibraryDependency.import(libraryB), ], procedures: [ Procedure(Name('ArbitrarilyChosen'), ProcedureKind.Method, FunctionNode(Block([]))) ], ); 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 coreTypes = CoreTypes(testComponent); javaScriptBundler.compile(ClassHierarchy(testComponent, coreTypes), coreTypes, {}, codeSink, manifestSink, sourcemapSink, metadataSink); 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 buffer = []; @override void add(List data) { buffer.addAll(data); } @override Future close() => Future.value(); @override void noSuchMethod(Invocation invocation) { throw UnsupportedError(invocation.memberName.toString()); } } Map> _combineMaps( Map> left, Map> right, ) { final result = Map>.from(left); for (String key in right.keys) { result[key] ??= []; result[key].addAll(right[key]); } return result; }