feat(dart/transform): Use the render Compiler and the DirectiveParser

Update the `TemplateCompile` step to use the full render `Compiler`.

Provide `DirectiveMetadata` for `ViewDefinition` objects and use it to
run the `DirectiveParser` step of the render compile pipeline.
This commit is contained in:
Tim Blasi
2015-05-05 10:31:21 -07:00
committed by Misko Hevery
parent 401c9efad7
commit 44f829dbc6
24 changed files with 576 additions and 93 deletions

View File

@ -83,7 +83,16 @@ class Html5LibDomAdapter implements DomAdapter {
throw 'not implemented';
}
String nodeName(node) {
throw 'not implemented';
switch (node.nodeType) {
case Node.ELEMENT_NODE:
return (node as Element).localName;
case Node.TEXT_NODE:
return '#text';
default:
throw 'not implemented for type ${node.nodeType}. '
'See http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1950641247'
' for node types definitions.';
}
}
String nodeValue(node) => node.data;
String type(node) {
@ -179,9 +188,8 @@ class Html5LibDomAdapter implements DomAdapter {
getElementsByTagName(element, String name) {
throw 'not implemented';
}
List classList(element) {
throw 'not implemented';
}
List classList(element) => element.classes.toList();
addClass(element, String classname) {
element.classes.add(classname);
}

View File

@ -9,6 +9,9 @@ import 'parser.dart';
DirectiveMetadata readDirectiveMetadata(RegisteredType t) {
var visitor = new _DirectiveMetadataVisitor();
t.annotations.accept(visitor);
if (visitor.meta != null) {
visitor.meta.id = '${t.typeName}';
}
return visitor.meta;
}

View File

@ -68,7 +68,8 @@ class NgDeps {
final List<ImportDirective> imports = [];
final List<ExportDirective> exports = [];
final List<RegisteredType> registeredTypes = [];
FunctionDeclaration setupMethod;
LibraryDirective lib = null;
FunctionDeclaration setupMethod = null;
NgDeps(this.code);
}
@ -76,6 +77,12 @@ class NgDeps {
class _ParseNgDepsVisitor extends Object with RecursiveAstVisitor<Object> {
NgDeps ngDeps = null;
@override
Object visitLibraryDirective(LibraryDirective node) {
ngDeps.lib = node;
return null;
}
@override
Object visitImportDirective(ImportDirective node) {
ngDeps.imports.add(node);

View File

@ -6,6 +6,7 @@ import 'package:angular2/src/render/api.dart';
import 'package:angular2/src/render/dom/compiler/compile_step.dart';
import 'package:angular2/src/render/dom/compiler/compile_step_factory.dart'
as base;
import 'package:angular2/src/render/dom/compiler/directive_parser.dart';
import 'package:angular2/src/render/dom/compiler/property_binding_parser.dart';
import 'package:angular2/src/render/dom/compiler/text_interpolation_parser.dart';
import 'package:angular2/src/render/dom/compiler/view_splitter.dart';
@ -20,6 +21,7 @@ class CompileStepFactory implements base.CompileStepFactory {
return [
new ViewSplitter(_parser),
new PropertyBindingParser(_parser),
new DirectiveParser(_parser, template.directives),
new TextInterpolationParser(_parser)
];
}

View File

@ -2,24 +2,22 @@ library angular2.transform.template_compiler.generator;
import 'dart:async';
import 'package:analyzer/analyzer.dart';
import 'package:angular2/src/change_detection/parser/lexer.dart' as ng;
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/compile_pipeline.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/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/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/parser.dart';
import 'package:angular2/src/transform/common/property_utils.dart' as prop;
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
@ -28,42 +26,40 @@ import 'xhr_impl.dart';
///
/// This method assumes a {@link DomAdapter} has been registered.
Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
var parser = new Parser(reader);
NgDeps ngDeps = await parser.parse(entryPoint);
var viewDefResults = await createViewDefinitions(reader, entryPoint);
var extractor = new _TemplateExtractor(new XhrImpl(reader, entryPoint));
var registrations = new StringBuffer();
for (var rType in ngDeps.registeredTypes) {
var values = await extractor.extractTemplates(rType);
for (var viewDef in viewDefResults.viewDefinitions.values) {
var values = await extractor.extractTemplates(viewDef);
if (values == null) continue;
var calls = _generateGetters('${rType.typeName}', values.getterNames);
var calls = _generateGetters(values.getterNames);
if (calls.isNotEmpty) {
registrations.write('..${REGISTER_GETTERS_METHOD_NAME}'
'({${calls.join(', ')}})');
}
calls = _generateSetters('${rType.typeName}', values.setterNames);
calls = _generateSetters(values.setterNames);
if (calls.isNotEmpty) {
registrations.write('..${REGISTER_SETTERS_METHOD_NAME}'
'({${calls.join(', ')}})');
}
calls = _generateMethods('${rType.typeName}', values.methodNames);
calls = _generateMethods(values.methodNames);
if (calls.isNotEmpty) {
registrations.write('..${REGISTER_METHODS_METHOD_NAME}'
'({${calls.join(', ')}})');
}
}
var code = ngDeps.code;
var code = viewDefResults.ngDeps.code;
if (registrations.length == 0) return code;
var codeInjectIdx = ngDeps.registeredTypes.last.registerMethod.end;
var codeInjectIdx =
viewDefResults.ngDeps.registeredTypes.last.registerMethod.end;
return '${code.substring(0, codeInjectIdx)}'
'${registrations}'
'${code.substring(codeInjectIdx)}';
}
Iterable<String> _generateGetters(
String typeName, Iterable<String> getterNames) {
// TODO(kegluneq): Include `typeName` where possible.
Iterable<String> _generateGetters(Iterable<String> getterNames) {
return getterNames.map((getterName) {
if (!prop.isValid(getterName)) {
// TODO(kegluenq): Eagerly throw here once #1295 is addressed.
@ -74,8 +70,7 @@ Iterable<String> _generateGetters(
});
}
Iterable<String> _generateSetters(
String typeName, Iterable<String> setterName) {
Iterable<String> _generateSetters(Iterable<String> setterName) {
return setterName.map((setterName) {
if (!prop.isValid(setterName)) {
// TODO(kegluenq): Eagerly throw here once #1295 is addressed.
@ -87,8 +82,7 @@ Iterable<String> _generateSetters(
});
}
Iterable<String> _generateMethods(
String typeName, Iterable<String> methodNames) {
Iterable<String> _generateMethods(Iterable<String> methodNames) {
return methodNames.map((methodName) {
if (!prop.isValid(methodName)) {
// TODO(kegluenq): Eagerly throw here once #1295 is addressed.
@ -104,85 +98,25 @@ Iterable<String> _generateMethods(
/// template code if necessary, and determines what values will be
/// reflectively accessed from that template.
class _TemplateExtractor {
final CompileStepFactory _factory;
final _TemplateExtractVisitor _visitor = new _TemplateExtractVisitor();
final TemplateLoader _loader;
final RenderCompiler _compiler;
_TemplateExtractor(XHR xhr)
: _loader = new TemplateLoader(xhr, new UrlResolver()),
_factory = new CompileStepFactory(new ng.Parser(new ng.Lexer()));
_TemplateExtractor(XHR xhr) : _compiler = new DomCompiler(
new CompileStepFactory(new ng.Parser(new ng.Lexer())),
new TemplateLoader(xhr, new UrlResolver()));
Future<RecordingReflectionCapabilities> extractTemplates(RegisteredType t) {
return _processTemplate(_processRegisteredType(t));
}
Future<RecordingReflectionCapabilities> _processTemplate(
Future<RecordingReflectionCapabilities> extractTemplates(
ViewDefinition viewDef) async {
// Check for "imperative views".
if (viewDef.template == null && viewDef.absUrl == null) return null;
var recordingCapabilities = new RecordingReflectionCapabilities();
var savedReflectionCapabilities = reflector.reflectionCapabilities;
var recordingCapabilities = new RecordingReflectionCapabilities();
reflector.reflectionCapabilities = recordingCapabilities;
// TODO(kegluneq): Rewrite url to inline `template` where possible.
// See [https://github.com/angular/angular/issues/1035].
var domNode = await _loader.load(viewDef);
new CompilePipeline(_factory.createSteps(viewDef, [])).process(
domNode, '$domNode');
await _compiler.compile(viewDef);
reflector.reflectionCapabilities = savedReflectionCapabilities;
return recordingCapabilities;
}
ViewDefinition _processRegisteredType(RegisteredType t) {
_visitor.reset();
t.annotations.accept(_visitor);
return _visitor.viewDef;
}
}
/// Visitor responsible for processing the `annotations` property of a
/// {@link RegisterType} object and pulling out template information.
class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
ViewDefinition viewDef = new ViewDefinition();
void reset() {
viewDef = new ViewDefinition();
}
@override
Object visitNamedExpression(NamedExpression node) {
// TODO(kegluneq): Remove this limitation.
if (node.name is! Label || node.name.label is! SimpleIdentifier) {
logger.error(
'Angular 2 currently only supports simple identifiers in directives.'
' Source: ${node}');
return null;
}
var keyString = '${node.name.label}';
if (keyString == 'template' || keyString == 'templateUrl') {
if (node.expression is! SimpleStringLiteral) {
logger.error(
'Angular 2 currently only supports string literals in directives.'
' Source: ${node}');
return null;
}
var valueString = stringLiteralToString(node.expression);
if (keyString == 'templateUrl') {
if (viewDef.absUrl != null) {
logger.error(
'Found multiple values for "templateUrl". Source: ${node}');
}
viewDef.absUrl = valueString;
} else {
if (viewDef.template != null) {
logger.error('Found multiple values for "template". Source: ${node}');
}
viewDef.template = valueString;
}
}
return null;
}
}

View File

@ -0,0 +1,232 @@
library angular2.transform.template_compiler.view_definition_creator;
import 'dart:async';
import 'dart:convert';
import 'package:analyzer/analyzer.dart';
import 'package:angular2/src/render/api.dart';
import 'package:angular2/src/render/dom/convert.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:angular2/src/transform/common/parser.dart';
import 'package:barback/barback.dart';
import 'package:code_transformers/assets.dart';
/// Creates [ViewDefinition] objects for all `View` `Directive`s defined in
/// `entryPoint`.
Future<ViewDefinitionResults> createViewDefinitions(
AssetReader reader, AssetId entryPoint) async {
return await new _ViewDefinitionCreator(reader, entryPoint).createViewDefs();
}
class ViewDefinitionResults {
final NgDeps ngDeps;
final Map<RegisteredType, ViewDefinition> viewDefinitions;
ViewDefinitionResults._(this.ngDeps, this.viewDefinitions);
}
String _getComponentId(AssetId assetId, String className) =>
'$assetId:$className';
/// Creates [ViewDefinition] objects for all `View` `Directive`s defined in
/// `entryPoint`.
class _ViewDefinitionCreator {
final AssetReader reader;
final AssetId entryPoint;
final Future<NgDeps> ngDepsFuture;
_ViewDefinitionCreator(AssetReader reader, AssetId entryPoint)
: this.reader = reader,
this.entryPoint = entryPoint,
ngDepsFuture = new Parser(reader).parse(entryPoint);
Future<ViewDefinitionResults> createViewDefs() async {
var ngDeps = await ngDepsFuture;
var retVal = <RegisteredType, ViewDefinition>{};
var visitor = new _TemplateExtractVisitor(await _createMetadataMap());
ngDeps.registeredTypes.forEach((rType) {
visitor.reset();
rType.annotations.accept(visitor);
if (visitor.viewDef != null) {
var typeName = '${rType.typeName}';
if (visitor._metadataMap.containsKey(typeName)) {
visitor.viewDef.componentId = visitor._metadataMap[typeName].id;
} else {
logger.warning('Missing component "$typeName" in metadata map',
asset: entryPoint);
visitor.viewDef.componentId = _getComponentId(entryPoint, typeName);
}
retVal[rType] = visitor.viewDef;
}
});
return new ViewDefinitionResults._(ngDeps, retVal);
}
/// Creates a map from [AssetId] to import prefix for `.dart` libraries
/// imported by `entryPoint`, excluding any `.ng_deps.dart` files it imports.
/// Unprefixed imports have `null` as their value. `entryPoint` is included
/// in the map with no prefix.
Future<Map<AssetId, String>> _createImportAssetToPrefixMap() async {
// TODO(kegluneq): Support `part` directives.
var ngDeps = await ngDepsFuture;
var importAssetToPrefix = <AssetId, String>{};
// Include the `.ng_meta.dart` file associated with `entryPoint`.
importAssetToPrefix[new AssetId(
entryPoint.package, toMetaExtension(entryPoint.path))] = null;
for (ImportDirective node in ngDeps.imports) {
var uri = stringLiteralToString(node.uri);
if (uri.endsWith('.dart') && !uri.endsWith(DEPS_EXTENSION)) {
var prefix = node.prefix != null && node.prefix.name != null
? '${node.prefix.name}'
: null;
importAssetToPrefix[
uriToAssetId(entryPoint, uri, logger, null /* span */)] = prefix;
}
}
return importAssetToPrefix;
}
/// Reads the `.ng_meta.json` files associated with all of `entryPoint`'s
/// imports and creates a map `Type` name, prefixed if appropriate to the
/// associated [DirectiveMetadata].
///
/// For example, if in `entryPoint` we have:
///
/// ```
/// import 'component.dart' as prefix;
/// ```
///
/// and in 'component.dart' we have:
///
/// ```
/// @Component(...)
/// class MyComponent {...}
/// ```
///
/// This method will look for `component.ng_meta.json`to contain the
/// serialized [DirectiveMetadata] `MyComponent` and any other `Directive`s
/// declared in `component.dart`. We use this information to build a map:
///
/// ```
/// {
/// "prefix.MyComponent": [DirectiveMetadata for MyComponent],
/// ...<any other entries>...
/// }
/// ```
Future<Map<String, DirectiveMetadata>> _createMetadataMap() async {
var importAssetToPrefix = await _createImportAssetToPrefixMap();
var retVal = <String, DirectiveMetadata>{};
for (var importAssetId in importAssetToPrefix.keys) {
var metaAssetId = new AssetId(
importAssetId.package, toMetaExtension(importAssetId.path));
if (await reader.hasInput(metaAssetId)) {
try {
var json = await reader.readAsString(metaAssetId);
var jsonMap = JSON.decode(json);
jsonMap.forEach((className, metaDataMap) {
var prefixStr = importAssetToPrefix[importAssetId];
var key = prefixStr != null ? '$prefixStr.$className' : className;
var value = directiveMetadataFromMap(metaDataMap)
..id = _getComponentId(importAssetId, className);
retVal[key] = value;
});
} catch (ex, stackTrace) {
logger.warning('Failed to decode: $ex, $stackTrace',
asset: metaAssetId);
}
}
}
return retVal;
}
}
/// Visitor responsible for processing the `annotations` property of a
/// {@link RegisterType} object and pulling out [ViewDefinition] information.
class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
ViewDefinition viewDef = null;
final Map<String, DirectiveMetadata> _metadataMap;
_TemplateExtractVisitor(this._metadataMap);
void reset() {
viewDef = null;
}
/// These correspond to the annotations themselves.
@override
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
if ('${node.constructorName.type}' == 'View') {
viewDef = new ViewDefinition(directives: <DirectiveMetadata>[]);
node.visitChildren(this);
}
return null;
}
/// These correspond to the annotation parameters.
@override
Object visitNamedExpression(NamedExpression node) {
// TODO(kegluneq): Remove this limitation.
if (node.name is! Label || node.name.label is! SimpleIdentifier) {
logger.error(
'Angular 2 currently only supports simple identifiers in directives.'
' Source: ${node}');
return null;
}
var keyString = '${node.name.label}';
if (keyString == 'directives') {
_readDirectives(node.expression);
}
if (keyString == 'template' || keyString == 'templateUrl') {
if (node.expression is! SimpleStringLiteral) {
logger.error(
'Angular 2 currently only supports string literals in directives.'
' Source: ${node}');
return null;
}
var valueString = stringLiteralToString(node.expression);
if (keyString == 'templateUrl') {
if (viewDef.absUrl != null) {
logger.error(
'Found multiple values for "templateUrl". Source: ${node}');
}
viewDef.absUrl = valueString;
} else {
// keyString == 'template'
if (viewDef.template != null) {
logger.error('Found multiple values for "template". Source: ${node}');
}
viewDef.template = valueString;
}
}
return null;
}
void _readDirectives(Expression node) {
if (node is! ListLiteral) {
logger.error(
'Angular 2 currently only supports list literals as values for'
' "directives". Source: $node');
return;
}
var directiveList = (node as ListLiteral);
for (var node in directiveList.elements) {
if (node is! SimpleIdentifier && node is! PrefixedIdentifier) {
logger.error(
'Angular 2 currently only supports simple and prefixed identifiers '
'as values for "directives". Source: $node');
return;
}
var name = '$node';
if (_metadataMap.containsKey(name)) {
viewDef.directives.add(_metadataMap[name]);
} else {
logger.warning('Could not find Directive entry for $name');
}
}
}
}