feat(dart/transform): Inline templateUrl values

Modify DirectiveProcessor to inline `templateUrl` values to avoid making
additional browser requests.

Closes #1035
This commit is contained in:
Tim Blasi
2015-05-08 17:29:21 -07:00
parent 655ed851f0
commit 97d24563f4
13 changed files with 294 additions and 24 deletions

View File

@ -0,0 +1,75 @@
library angular2.transform.common.async_string_writer;
import 'dart:async';
import 'package:analyzer/src/generated/java_core.dart';
/// [PrintWriter] implementation that allows asynchronous printing via
/// [asyncPrint] and [asyncToString]. See those methods for details.
class AsyncStringWriter extends PrintWriter {
/// All [Future]s we are currently waiting on.
final List<Future<String>> _toAwait = <Future<String>>[];
final List<StringBuffer> _bufs;
StringBuffer _curr;
int _asyncCount = 0;
AsyncStringWriter._(StringBuffer curr)
: _curr = curr,
_bufs = <StringBuffer>[curr];
AsyncStringWriter() : this._(new StringBuffer());
void print(x) {
_curr.write(x);
}
/// Adds the result of `futureText` to the writer at the current position
/// in the string being built. If using this method, you must use
/// [asyncToString] instead of [toString] to get the value of the writer or
/// your string may not appear as expected.
Future<String> asyncPrint(Future<String> futureText) {
_semaphoreIncrement();
var myBuf = new StringBuffer();
_bufs.add(myBuf);
_curr = new StringBuffer();
_bufs.add(_curr);
var toAwait = futureText.then((val) {
myBuf.write(val);
return val;
});
_toAwait.add(toAwait);
return toAwait.whenComplete(() {
_semaphoreDecrementAndCleanup();
_toAwait.remove(toAwait);
});
}
/// Waits for any values added via [asyncPrint] and returns the fully
/// built string.
Future<String> asyncToString() {
_semaphoreIncrement();
var bufLen = _bufs.length;
return Future.wait(_toAwait).then((_) {
return _bufs.sublist(0, bufLen).join('');
}).whenComplete(_semaphoreDecrementAndCleanup);
}
String toString() => _bufs.map((buf) => '$buf').join('(async gap)');
void _semaphoreIncrement() {
++_asyncCount;
}
void _semaphoreDecrementAndCleanup() {
assert(_asyncCount > 0);
--_asyncCount;
if (_asyncCount == 0) {
_curr = _bufs[0];
for (var i = 1; i < _bufs.length; ++i) {
_curr.write('${_bufs[i]}');
}
_bufs.removeRange(1, _bufs.length);
}
}
}

View File

@ -1,10 +1,15 @@
library angular2.transform.directive_processor.rewriter;
import 'dart:async';
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:angular2/src/services/xhr.dart' show XHR;
import 'package:angular2/src/transform/common/annotation_matcher.dart';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/async_string_writer.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/xhr_impl.dart';
import 'package:barback/barback.dart' show AssetId;
import 'package:path/path.dart' as path;
@ -17,20 +22,22 @@ import 'visitors.dart';
/// If no Angular 2 `Directive`s are found in `code`, returns the empty
/// string unless `forceGenerate` is true, in which case an empty ngDeps
/// file is created.
String createNgDeps(
String code, AssetId assetId, AnnotationMatcher annotationMatcher) {
Future<String> createNgDeps(AssetReader reader, AssetId assetId,
AnnotationMatcher annotationMatcher) async {
// TODO(kegluneq): Shortcut if we can determine that there are no
// [Directive]s present, taking into account `export`s.
var writer = new PrintStringWriter();
var visitor = new CreateNgDepsVisitor(writer, assetId, annotationMatcher);
parseCompilationUnit(code, name: assetId.toString()).accept(visitor);
return '$writer';
var writer = new AsyncStringWriter();
var visitor = new CreateNgDepsVisitor(
writer, assetId, new XhrImpl(reader, assetId), annotationMatcher);
var code = await reader.readAsString(assetId);
parseCompilationUnit(code, name: assetId.path).accept(visitor);
return await writer.asyncToString();
}
/// Visitor responsible for processing [CompilationUnit] and creating an
/// associated .ng_deps.dart file.
class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
final PrintWriter writer;
final AsyncStringWriter writer;
bool _foundNgDirectives = false;
bool _wroteImport = false;
final ToSourceVisitor _copyVisitor;
@ -42,12 +49,13 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
/// The assetId for the file which we are parsing.
final AssetId assetId;
CreateNgDepsVisitor(PrintWriter writer, this.assetId, this._annotationMatcher)
CreateNgDepsVisitor(
AsyncStringWriter writer, this.assetId, XHR xhr, this._annotationMatcher)
: writer = writer,
_copyVisitor = new ToSourceVisitor(writer),
_factoryVisitor = new FactoryTransformVisitor(writer),
_paramsVisitor = new ParameterTransformVisitor(writer),
_metaVisitor = new AnnotationsTransformVisitor(writer);
_metaVisitor = new AnnotationsTransformVisitor(writer, xhr);
void _visitNodeListWithSeparator(NodeList<AstNode> list, String separator) {
if (list == null) return;

View File

@ -2,6 +2,7 @@ library angular2.transform.directive_processor.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/options.dart';
@ -32,9 +33,9 @@ class DirectiveProcessor extends Transformer {
try {
var asset = transform.primaryInput;
var assetCode = await asset.readAsString();
var reader = new AssetReader.fromTransform(transform);
var ngDepsSrc =
createNgDeps(assetCode, asset.id, options.annotationMatcher);
await createNgDeps(reader, asset.id, options.annotationMatcher);
if (ngDepsSrc != null && ngDepsSrc.isNotEmpty) {
var ngDepsAssetId =
transform.primaryInput.id.changeExtension(DEPS_EXTENSION);

View File

@ -2,6 +2,8 @@ library angular2.transform.directive_processor.visitors;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:angular2/src/services/xhr.dart' show XHR;
import 'package:angular2/src/transform/common/async_string_writer.dart';
import 'package:angular2/src/transform/common/logging.dart';
/// `ToSourceVisitor` designed to accept {@link ConstructorDeclaration} nodes.
@ -200,11 +202,17 @@ class FactoryTransformVisitor extends _CtorTransformVisitor {
}
}
// TODO(kegluenq): Use pull #1772 to detect when available.
bool _isViewAnnotation(Annotation node) => '${node.name}' == 'View';
/// ToSourceVisitor designed to print a `ClassDeclaration` node as a
/// 'annotations' value for Angular2's `registerType` calls.
class AnnotationsTransformVisitor extends ToSourceVisitor {
final PrintWriter writer;
AnnotationsTransformVisitor(PrintWriter writer)
final AsyncStringWriter writer;
final XHR _xhr;
bool _processingView = false;
AnnotationsTransformVisitor(AsyncStringWriter writer, this._xhr)
: this.writer = writer,
super(writer);
@ -226,6 +234,7 @@ class AnnotationsTransformVisitor extends ToSourceVisitor {
Object visitAnnotation(Annotation node) {
writer.print('const ');
if (node.name != null) {
_processingView = _isViewAnnotation(node);
node.name.accept(this);
}
if (node.constructorName != null) {
@ -237,4 +246,25 @@ class AnnotationsTransformVisitor extends ToSourceVisitor {
}
return null;
}
/// These correspond to the annotation parameters.
@override
Object visitNamedExpression(NamedExpression node) {
// TODO(kegluneq): Remove this limitation.
if (!_processingView ||
node.name is! Label ||
node.name.label is! SimpleIdentifier) {
return super.visitNamedExpression(node);
}
var keyString = '${node.name.label}';
if (keyString == 'templateUrl' && node.expression is SimpleStringLiteral) {
var url = stringLiteralToString(node.expression);
writer.print("template: r'''");
writer.asyncPrint(_xhr.get(url));
writer.print("'''");
return null;
} else {
return super.visitNamedExpression(node);
}
}
}

View File

@ -7,18 +7,18 @@ import 'package:angular2/src/change_detection/parser/parser.dart' as ng;
import 'package:angular2/src/render/api.dart';
import 'package:angular2/src/render/dom/compiler/compiler.dart';
import 'package:angular2/src/render/dom/compiler/template_loader.dart';
import "package:angular2/src/services/xhr.dart" show XHR;
import 'package:angular2/src/services/xhr.dart' show XHR;
import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/services/url_resolver.dart';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/property_utils.dart' as prop;
import 'package:angular2/src/transform/common/xhr_impl.dart';
import 'package:barback/barback.dart';
import 'compile_step_factory.dart';
import 'recording_reflection_capabilities.dart';
import 'view_definition_creator.dart';
import 'xhr_impl.dart';
/// Reads the `.ng_deps.dart` file represented by `entryPoint` and parses any
/// Angular 2 `View` annotations it declares to generate `getter`s,