Implement deferred libraries to work with dependency injection and other angular codegen. This is done by not initializing the library in the parent ng_deps file when it is declared as deferred, rewriting the import and, chaining a future that initializes the library in any files that are using deferred libraries which need angular codegen.
143 lines
5.1 KiB
Dart
143 lines
5.1 KiB
Dart
library angular2.transform.deferred_rewriter.rewriter;
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:analyzer/analyzer.dart';
|
|
import 'package:analyzer/src/generated/ast.dart';
|
|
import 'package:angular2/src/transform/common/asset_reader.dart';
|
|
import 'package:angular2/src/transform/common/logging.dart';
|
|
import 'package:angular2/src/transform/common/names.dart';
|
|
import 'package:barback/barback.dart';
|
|
import 'package:code_transformers/assets.dart';
|
|
import 'package:quiver/iterables.dart' as it;
|
|
|
|
class Rewriter {
|
|
final AssetId _entryPoint;
|
|
final AssetReader _reader;
|
|
|
|
Rewriter(this._entryPoint, this._reader);
|
|
|
|
/// Rewrites the provided code by finding all the deferred library imports
|
|
/// and loadLibrary invocations. Then it removes any libraries that don't
|
|
/// require angular codegen. For the remaining libraries it rewrites the
|
|
/// import to the corresponding ng_dep file, and chains a future which
|
|
/// first initializes the library.
|
|
///
|
|
/// To the extent possible, this method does not change line numbers or
|
|
/// offsets in the provided code to facilitate debugging via source maps.
|
|
Future<String> rewrite() async {
|
|
var code = await _reader.readAsString(_entryPoint);
|
|
var node = parseCompilationUnit(code);
|
|
if (node == null) return null;
|
|
|
|
var visitor = new _FindDeferredLibraries(_reader, _entryPoint);
|
|
node.accept(visitor);
|
|
// Look to see if we found any deferred libraries
|
|
if (!visitor.hasDeferredLibrariesToRewrite()) return null;
|
|
// Remove any libraries that don't need angular codegen.
|
|
await visitor.cull();
|
|
// Check again if there are any deferred libraries.
|
|
if (!visitor.hasDeferredLibrariesToRewrite()) return null;
|
|
|
|
var compare = (AstNode a, AstNode b) => a.offset - b.offset;
|
|
visitor.deferredImports.sort(compare);
|
|
visitor.loadLibraryInvocations.sort(compare);
|
|
|
|
var buf = new StringBuffer();
|
|
var idx = visitor.deferredImports.fold(0,
|
|
(int lastIdx, ImportDirective node) {
|
|
buf.write(code.substring(lastIdx, node.offset));
|
|
|
|
var import = code.substring(node.offset, node.end);
|
|
buf.write(import.replaceFirst('.dart', DEPS_EXTENSION));
|
|
return node.end;
|
|
});
|
|
|
|
idx = visitor.loadLibraryInvocations.fold(idx,
|
|
(int lastIdx, MethodInvocation node) {
|
|
buf.write(code.substring(lastIdx, node.offset));
|
|
var value = node.realTarget as SimpleIdentifier;
|
|
var prefix = value.name;
|
|
// Chain a future that initializes the reflector.
|
|
buf.write('$prefix.loadLibrary().then((_) {$prefix.initReflector();})');
|
|
return node.end;
|
|
});
|
|
if (idx < code.length) buf.write(code.substring(idx));
|
|
return '$buf';
|
|
}
|
|
}
|
|
|
|
/// Visitor responsible for finding the deferred libraries that need angular
|
|
/// codegen. Those are the libraries that are loaded deferred and have a
|
|
/// corresponding ng_deps file.
|
|
class _FindDeferredLibraries extends Object with RecursiveAstVisitor<Object> {
|
|
var deferredImports = new List<ImportDirective>();
|
|
var loadLibraryInvocations = new List<MethodInvocation>();
|
|
final AssetReader _reader;
|
|
final AssetId _entryPoint;
|
|
|
|
_FindDeferredLibraries(this._reader, this._entryPoint);
|
|
|
|
@override
|
|
Object visitImportDirective(ImportDirective node) {
|
|
if (node.deferredKeyword != null) {
|
|
deferredImports.add(node);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
Object visitMethodInvocation(MethodInvocation node) {
|
|
if (node.methodName.name == 'loadLibrary') {
|
|
loadLibraryInvocations.add(node);
|
|
}
|
|
return super.visitMethodInvocation(node);
|
|
}
|
|
|
|
bool hasDeferredLibrariesToRewrite() {
|
|
if (deferredImports.isEmpty) {
|
|
logger.fine('There are no deferred library imports.');
|
|
return false;
|
|
}
|
|
if (loadLibraryInvocations.isEmpty) {
|
|
logger.fine(
|
|
'There are no loadLibrary invocations that need to be rewritten.');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Remove all deferredImports that do not have a ng_dep file
|
|
// then remove all loadLibrary invocations that are not in the set of
|
|
// prefixes that are left.
|
|
Future cull() async {
|
|
// Determine whether a deferred import has ng_deps.
|
|
var hasInputs = await Future.wait(deferredImports
|
|
.map((import) => stringLiteralToString(import.uri))
|
|
.map((uri) => toDepsExtension(uri))
|
|
.map((depsUri) => uriToAssetId(_entryPoint, depsUri, logger, null,
|
|
errorOnAbsolute: false))
|
|
.map((asset) => _reader.hasInput(asset)));
|
|
|
|
// Filter out any deferred imports that do not have ng_deps.
|
|
deferredImports = it
|
|
.zip([deferredImports, hasInputs])
|
|
.where((importHasInput) => importHasInput[1])
|
|
.map((importHasInput) => importHasInput[0])
|
|
.toList();
|
|
|
|
// Find the set of prefixes which have ng_deps.
|
|
var prefixes =
|
|
new Set.from(deferredImports.map((import) => import.prefix.name));
|
|
|
|
// Filters out any load library invocations where the prefix is not a known
|
|
// library with ng_deps.
|
|
loadLibraryInvocations = loadLibraryInvocations.where((library) {
|
|
var value = library.realTarget as SimpleIdentifier;
|
|
return prefixes.contains(value.name);
|
|
}).toList();
|
|
|
|
return;
|
|
}
|
|
}
|