Files
Angular-Docs-Cn/modules/angular2/src/transform/deferred_rewriter/rewriter.dart
T
Ted Sander 5cc84ed4bb feat(transformers): implement initializing deferred libraries
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.
2015-07-17 13:07:31 -07:00

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;
}
}