feat(dart/transform) Allow ctor stubs to be tree shaken
Change the method used to discover Directive annotated classes to ensure that the Dart code can be tree shaken. Closes #497 Closes $\x23736
This commit is contained in:
parent
0c4fbfc8e2
commit
fb49946160
|
@ -1,8 +1,11 @@
|
||||||
library angular2.src.transform;
|
library angular2.src.transform;
|
||||||
|
|
||||||
import 'dart:collection' show Queue;
|
import 'dart:collection' show Queue;
|
||||||
|
import 'package:analyzer/src/generated/ast.dart';
|
||||||
import 'package:analyzer/src/generated/element.dart';
|
import 'package:analyzer/src/generated/element.dart';
|
||||||
|
|
||||||
|
import 'resolvers.dart';
|
||||||
|
|
||||||
/// Provides a mechanism for checking an element for the provided
|
/// Provides a mechanism for checking an element for the provided
|
||||||
/// [_annotationClass] and reporting the resulting (element, annotation) pairs.
|
/// [_annotationClass] and reporting the resulting (element, annotation) pairs.
|
||||||
class AnnotationMatcher {
|
class AnnotationMatcher {
|
||||||
|
@ -11,18 +14,19 @@ class AnnotationMatcher {
|
||||||
/// All the annotations we have seen for each element
|
/// All the annotations we have seen for each element
|
||||||
final _seenAnnotations = new Map<Element, Set<ElementAnnotation>>();
|
final _seenAnnotations = new Map<Element, Set<ElementAnnotation>>();
|
||||||
|
|
||||||
/// The class we are searching for to populate [initQueue].
|
/// The classes we are searching for to populate [matchQueue].
|
||||||
final ClassElement _annotationClass;
|
final Set<ClassElement> _annotationClasses;
|
||||||
|
|
||||||
AnnotationMatcher(this._annotationClass);
|
AnnotationMatcher(this._annotationClasses);
|
||||||
|
|
||||||
/// Records all [_annotationClass] annotations and the [element]s they apply to.
|
/// Records all [_annotationClass] annotations and the [element]s they apply to.
|
||||||
/// Returns [true] if 1) [element] is annotated with [_annotationClass] and
|
/// Returns
|
||||||
/// 2) ([element], [_annotationClass]) has not been seen previously.
|
List<AnnotationMatch> processAnnotations(ClassElement element) {
|
||||||
bool processAnnotations(ClassElement element) {
|
// Finding the node corresponding to [element] can be very expensive.
|
||||||
var found = false;
|
ClassDeclaration cachedNode = null;
|
||||||
|
|
||||||
|
var result = <AnnotationMatch>[];
|
||||||
element.metadata.where((ElementAnnotation meta) {
|
element.metadata.where((ElementAnnotation meta) {
|
||||||
// Only process [_annotationClass]s.
|
|
||||||
// TODO(tjblasi): Make this recognize non-ConstructorElement annotations.
|
// TODO(tjblasi): Make this recognize non-ConstructorElement annotations.
|
||||||
return meta.element is ConstructorElement &&
|
return meta.element is ConstructorElement &&
|
||||||
_isAnnotationMatch(meta.element.returnType);
|
_isAnnotationMatch(meta.element.returnType);
|
||||||
|
@ -32,29 +36,36 @@ class AnnotationMatcher {
|
||||||
.putIfAbsent(element, () => new Set<ElementAnnotation>())
|
.putIfAbsent(element, () => new Set<ElementAnnotation>())
|
||||||
.contains(meta);
|
.contains(meta);
|
||||||
}).forEach((ElementAnnotation meta) {
|
}).forEach((ElementAnnotation meta) {
|
||||||
|
if (cachedNode == null) {
|
||||||
|
cachedNode = element.node;
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = new AnnotationMatch(cachedNode, meta);
|
||||||
_seenAnnotations[element].add(meta);
|
_seenAnnotations[element].add(meta);
|
||||||
matchQueue.addLast(new AnnotationMatch(element, meta));
|
matchQueue.addLast(match);
|
||||||
found = true;
|
result.add(match);
|
||||||
});
|
});
|
||||||
return found;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether [type], its superclass, or one of its interfaces matches [_annotationClass].
|
/// Whether [type], its superclass, or one of its interfaces matches [_annotationClass].
|
||||||
bool _isAnnotationMatch(InterfaceType type) {
|
bool _isAnnotationMatch(InterfaceType type) {
|
||||||
if (type == null || type.element == null) return false;
|
return _annotationClasses.any((el) => isAnnotationMatch(type, el));
|
||||||
if (type.element.type == _annotationClass.type) return true;
|
|
||||||
if (_isAnnotationMatch(type.superclass)) return true;
|
|
||||||
for (var interface in type.interfaces) {
|
|
||||||
if (_isAnnotationMatch(interface)) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Element/ElementAnnotation pair.
|
/// [ConstructorElement] / [ElementAnnotation] pair, where the Constructor
|
||||||
class AnnotationMatch {
|
class AnnotationMatch {
|
||||||
final Element element;
|
/// The resolved element corresponding to [node].
|
||||||
|
final ClassElement element;
|
||||||
|
|
||||||
|
/// The Ast node corresponding to the class definition.
|
||||||
|
final ClassDeclaration node;
|
||||||
|
|
||||||
|
/// The resolved element for the matched annotation.
|
||||||
final ElementAnnotation annotation;
|
final ElementAnnotation annotation;
|
||||||
|
|
||||||
AnnotationMatch(this.element, this.annotation);
|
AnnotationMatch(ClassDeclaration node, this.annotation)
|
||||||
|
: node = node,
|
||||||
|
element = node.element;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
void error(String errorString) {
|
void error(String errorString) {
|
||||||
if (_logger == null) throw new Error(errorString);
|
if (_logger == null) throw new CodegenError(errorString);
|
||||||
_logger.error(errorString);
|
_logger.error(errorString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,16 @@ class Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CodegenError extends Error {
|
||||||
|
final String message;
|
||||||
|
CodegenError(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Error generating Angular2 code: ${Error.safeToString(message)}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Object which [register]s [AnnotationMatch] objects for code generation.
|
/// Object which [register]s [AnnotationMatch] objects for code generation.
|
||||||
abstract class DirectiveRegistry {
|
abstract class DirectiveRegistry {
|
||||||
// Adds [entry] to the `registerType` calls which will be generated.
|
// Adds [entry] to the `registerType` calls which will be generated.
|
||||||
|
@ -107,6 +117,7 @@ _codegenImport(Context context, AssetId libraryId, AssetId entryPoint) {
|
||||||
class _DirectiveRegistryImpl implements DirectiveRegistry {
|
class _DirectiveRegistryImpl implements DirectiveRegistry {
|
||||||
final Context _context;
|
final Context _context;
|
||||||
final StringBuffer _buffer = new StringBuffer();
|
final StringBuffer _buffer = new StringBuffer();
|
||||||
|
final Set<ClassDeclaration> _seen = new Set();
|
||||||
|
|
||||||
_DirectiveRegistryImpl(this._context);
|
_DirectiveRegistryImpl(this._context);
|
||||||
|
|
||||||
|
@ -117,6 +128,9 @@ class _DirectiveRegistryImpl implements DirectiveRegistry {
|
||||||
|
|
||||||
// Adds [entry] to the `registerType` calls which will be generated.
|
// Adds [entry] to the `registerType` calls which will be generated.
|
||||||
void register(AnnotationMatch entry) {
|
void register(AnnotationMatch entry) {
|
||||||
|
if (_seen.contains(entry.node)) return;
|
||||||
|
_seen.add(entry.node);
|
||||||
|
|
||||||
var element = entry.element;
|
var element = entry.element;
|
||||||
var annotation = entry.annotation;
|
var annotation = entry.annotation;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import 'package:analyzer/src/generated/ast.dart';
|
||||||
|
import 'package:analyzer/src/generated/element.dart';
|
||||||
|
import 'package:code_transformers/resolver.dart';
|
||||||
|
|
||||||
|
import 'resolvers.dart';
|
||||||
|
|
||||||
|
/// Finds all calls to the Angular2 [bootstrap] method defined in [library].
|
||||||
|
/// This only searches the code defined in the file
|
||||||
|
// represented by [library], not `part`s, `import`s, `export`s, etc.
|
||||||
|
Set<BootstrapCallInfo> findBootstrapCalls(
|
||||||
|
Resolver resolver, LibraryElement library) {
|
||||||
|
var types = new Angular2Types(resolver);
|
||||||
|
if (types.bootstrapMethod == null) {
|
||||||
|
throw new ArgumentError(
|
||||||
|
'Could not find symbol for ${bootstrapMethodName}.');
|
||||||
|
}
|
||||||
|
var visitor = new _FindFunctionVisitor(types.bootstrapMethod);
|
||||||
|
|
||||||
|
// TODO(kegluneq): Determine how to get nodes without querying Element#node.
|
||||||
|
// Root of file defining that library (main part).
|
||||||
|
library.definingCompilationUnit.node.accept(visitor);
|
||||||
|
|
||||||
|
return new Set.from(visitor.functionCalls.map((MethodInvocation mi) {
|
||||||
|
var visitor = new _ParseBootstrapTypeVisitor(types);
|
||||||
|
if (mi.argumentList.arguments.isEmpty) {
|
||||||
|
throw new ArgumentError('No arguments provided to `bootstrap`.');
|
||||||
|
}
|
||||||
|
mi.argumentList.arguments[0].accept(visitor);
|
||||||
|
if (visitor.bootstrapType == null) {
|
||||||
|
throw new UnsupportedError(
|
||||||
|
'Failed to parse `bootstrap` call: ${mi.toSource()}');
|
||||||
|
}
|
||||||
|
return new BootstrapCallInfo(mi, visitor.bootstrapType);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about a single call to Angular2's [bootstrap] method.
|
||||||
|
class BootstrapCallInfo {
|
||||||
|
/// The [AstNode] representing the call to [bootstrap].
|
||||||
|
final MethodInvocation call;
|
||||||
|
|
||||||
|
/// The type, which should be annotated as a [Component], which is the root
|
||||||
|
/// of the Angular2 app.
|
||||||
|
final ClassElement bootstrapType;
|
||||||
|
|
||||||
|
BootstrapCallInfo(this.call, this.bootstrapType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visitor that finds the Angular2 bootstrap component given [bootstrap]'s
|
||||||
|
/// first argument.
|
||||||
|
///
|
||||||
|
/// This visitor does not recursively visit nodes in the Ast.
|
||||||
|
class _ParseBootstrapTypeVisitor extends SimpleAstVisitor<Object> {
|
||||||
|
ClassElement bootstrapType = null;
|
||||||
|
|
||||||
|
final Angular2Types _types;
|
||||||
|
|
||||||
|
_ParseBootstrapTypeVisitor(this._types);
|
||||||
|
|
||||||
|
// TODO(kegluneq): Allow non-SimpleIdentifier expressions.
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitSimpleIdentifier(SimpleIdentifier e) {
|
||||||
|
bootstrapType = (e.bestElement as ClassElement);
|
||||||
|
if (!_types.isComponent(bootstrapType)) {
|
||||||
|
throw new ArgumentError('Class passed to `${bootstrapMethodName}` must '
|
||||||
|
'be a @${_types.componentAnnotation.name}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively visits all nodes in an Ast structure, recording all encountered
|
||||||
|
/// calls to the provided [FunctionElement].
|
||||||
|
class _FindFunctionVisitor extends UnifyingAstVisitor<Object> {
|
||||||
|
final FunctionElement _target;
|
||||||
|
_FindFunctionVisitor(this._target);
|
||||||
|
|
||||||
|
final Set<MethodInvocation> functionCalls = new Set();
|
||||||
|
|
||||||
|
bool _isDesiredMethod(MethodInvocation node) {
|
||||||
|
return node.methodName.bestElement == _target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitMethodInvocation(MethodInvocation node) {
|
||||||
|
if (_isDesiredMethod(node)) {
|
||||||
|
functionCalls.add(node);
|
||||||
|
}
|
||||||
|
return super.visitMethodInvocation(node);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
library angular2.src.transform.logging;
|
||||||
|
|
||||||
|
import 'package:barback/barback.dart';
|
||||||
|
import 'package:code_transformers/messages/build_logger.dart';
|
||||||
|
|
||||||
|
BuildLogger _logger;
|
||||||
|
|
||||||
|
/// Prepares [logger] for use throughout the transformer.
|
||||||
|
void init(Transform t) {
|
||||||
|
if (_logger == null) {
|
||||||
|
_logger = new BuildLogger(t);
|
||||||
|
} else {
|
||||||
|
_logger.fine('Attempted to initialize logger multiple times.',
|
||||||
|
asset: t.primaryInput.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The logger the transformer should use for messaging.
|
||||||
|
BuildLogger get logger {
|
||||||
|
if (_logger == null) {
|
||||||
|
throw new StateError('Logger never initialized.');
|
||||||
|
}
|
||||||
|
return _logger;
|
||||||
|
}
|
|
@ -2,12 +2,23 @@ library angular2.src.transform;
|
||||||
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
/// Provides information necessary to transform an Angular2 app.
|
||||||
class TransformerOptions {
|
class TransformerOptions {
|
||||||
|
/// The file where the application's call to [bootstrap] is.
|
||||||
|
// TODO(kegluenq): Allow multiple bootstrap entry points.
|
||||||
|
final String bootstrapEntryPoint;
|
||||||
|
|
||||||
|
/// The Dart entry point, that is, where the initial call to [main] occurs.
|
||||||
final String entryPoint;
|
final String entryPoint;
|
||||||
|
|
||||||
|
/// The path where we should generate code.
|
||||||
final String newEntryPoint;
|
final String newEntryPoint;
|
||||||
|
|
||||||
|
/// The html file that includes [entryPoint].
|
||||||
final String htmlEntryPoint;
|
final String htmlEntryPoint;
|
||||||
|
|
||||||
TransformerOptions(this.entryPoint, this.newEntryPoint, this.htmlEntryPoint);
|
TransformerOptions(this.bootstrapEntryPoint, this.entryPoint,
|
||||||
|
this.newEntryPoint, this.htmlEntryPoint);
|
||||||
|
|
||||||
bool inSameTopLevelDir() {
|
bool inSameTopLevelDir() {
|
||||||
var expectedDir = path.split(htmlEntryPoint)[0];
|
var expectedDir = path.split(htmlEntryPoint)[0];
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
library angular2.src.transform;
|
library angular2.src.transform;
|
||||||
|
|
||||||
|
import 'package:barback/barback.dart';
|
||||||
|
import 'package:analyzer/src/generated/element.dart';
|
||||||
import 'package:code_transformers/resolver.dart';
|
import 'package:code_transformers/resolver.dart';
|
||||||
|
|
||||||
Resolvers createResolvers() {
|
Resolvers createResolvers() {
|
||||||
|
@ -43,3 +45,87 @@ Resolvers createResolvers() {
|
||||||
''',
|
''',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bootstrapMethodName = 'bootstrap';
|
||||||
|
|
||||||
|
/// Provides resolved [Elements] for well-known Angular2 symbols.
|
||||||
|
class Angular2Types {
|
||||||
|
static Map<Resolver, Angular2Types> _cache = {};
|
||||||
|
static final _annotationsLibAssetId =
|
||||||
|
new AssetId('angular2', 'lib/src/core/annotations/annotations.dart');
|
||||||
|
static final _applicationLibAssetId =
|
||||||
|
new AssetId('angular2', 'lib/src/core/application.dart');
|
||||||
|
static final _templateLibAssetId =
|
||||||
|
new AssetId('angular2', 'lib/src/core/annotations/template.dart');
|
||||||
|
|
||||||
|
final Resolver _resolver;
|
||||||
|
FunctionElement _bootstrapMethod;
|
||||||
|
|
||||||
|
Angular2Types._internal(this._resolver);
|
||||||
|
|
||||||
|
factory Angular2Types(Resolver resolver) {
|
||||||
|
return _cache.putIfAbsent(
|
||||||
|
resolver, () => new Angular2Types._internal(resolver));
|
||||||
|
}
|
||||||
|
|
||||||
|
LibraryElement get annotationsLib =>
|
||||||
|
_resolver.getLibrary(_annotationsLibAssetId);
|
||||||
|
|
||||||
|
ClassElement get directiveAnnotation =>
|
||||||
|
_getTypeSafe(annotationsLib, 'Directive');
|
||||||
|
|
||||||
|
ClassElement get componentAnnotation =>
|
||||||
|
_getTypeSafe(annotationsLib, 'Component');
|
||||||
|
|
||||||
|
ClassElement get decoratorAnnotation =>
|
||||||
|
_getTypeSafe(annotationsLib, 'Decorator');
|
||||||
|
|
||||||
|
LibraryElement get templateLib => _resolver.getLibrary(_templateLibAssetId);
|
||||||
|
|
||||||
|
ClassElement get templateAnnotation => _getTypeSafe(templateLib, 'Template');
|
||||||
|
|
||||||
|
LibraryElement get applicationLib =>
|
||||||
|
_resolver.getLibrary(_applicationLibAssetId);
|
||||||
|
|
||||||
|
FunctionElement get bootstrapMethod {
|
||||||
|
if (_bootstrapMethod == null) {
|
||||||
|
_bootstrapMethod = applicationLib.definingCompilationUnit.functions
|
||||||
|
.firstWhere((FunctionElement el) => el.name == bootstrapMethodName,
|
||||||
|
orElse: () => null);
|
||||||
|
}
|
||||||
|
return _bootstrapMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the type named [name] in library [lib]. Returns `null` if [lib] is
|
||||||
|
/// `null` or [name] cannot be found in [lib].
|
||||||
|
ClassElement _getTypeSafe(LibraryElement lib, String name) {
|
||||||
|
if (lib == null) return null;
|
||||||
|
return lib.getType(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether [clazz] is annotated as a [Component].
|
||||||
|
bool isComponent(ClassElement clazz) =>
|
||||||
|
hasAnnotation(clazz, componentAnnotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether [type], its superclass, or one of its interfaces matches [target].
|
||||||
|
bool isAnnotationMatch(InterfaceType type, ClassElement target) {
|
||||||
|
if (type == null || type.element == null) return false;
|
||||||
|
if (type.element.type == target.type) return true;
|
||||||
|
if (isAnnotationMatch(type.superclass, target)) return true;
|
||||||
|
for (var interface in type.interfaces) {
|
||||||
|
if (isAnnotationMatch(interface, target)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines whether [clazz] has at least one annotation that `is` a
|
||||||
|
/// [metaClazz].
|
||||||
|
bool hasAnnotation(ClassElement clazz, ClassElement metaClazz) {
|
||||||
|
if (clazz == null || metaClazz == null) return false;
|
||||||
|
return clazz.metadata.firstWhere((ElementAnnotation meta) {
|
||||||
|
// TODO(kegluneq): Make this recognize non-ConstructorElement annotations.
|
||||||
|
return meta.element is ConstructorElement &&
|
||||||
|
isAnnotationMatch(meta.element.returnType, metaClazz);
|
||||||
|
}, orElse: () => null) != null;
|
||||||
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
library angular2.src.transform;
|
library angular2.src.transform;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:analyzer/src/generated/ast.dart';
|
||||||
|
import 'package:analyzer/src/generated/element.dart';
|
||||||
import 'package:barback/barback.dart';
|
import 'package:barback/barback.dart';
|
||||||
import 'package:code_transformers/resolver.dart';
|
import 'package:code_transformers/resolver.dart';
|
||||||
|
|
||||||
import 'annotation_processor.dart';
|
import 'annotation_processor.dart';
|
||||||
import 'codegen.dart' as codegen;
|
import 'codegen.dart' as codegen;
|
||||||
|
import 'find_bootstrap.dart';
|
||||||
import 'html_transform.dart';
|
import 'html_transform.dart';
|
||||||
|
import 'logging.dart' as log;
|
||||||
import 'options.dart';
|
import 'options.dart';
|
||||||
import 'resolvers.dart';
|
import 'resolvers.dart';
|
||||||
import 'traversal.dart';
|
import 'traversal.dart';
|
||||||
|
@ -21,25 +25,29 @@ class AngularTransformer extends Transformer {
|
||||||
|
|
||||||
AngularTransformer(this.options) : _resolvers = createResolvers();
|
AngularTransformer(this.options) : _resolvers = createResolvers();
|
||||||
|
|
||||||
|
static const _bootstrapEntryPointParam = 'bootstrap_entry_point';
|
||||||
static const _entryPointParam = 'entry_point';
|
static const _entryPointParam = 'entry_point';
|
||||||
static const _newEntryPointParam = 'new_entry_point';
|
static const _newEntryPointParam = 'new_entry_point';
|
||||||
static const _htmlEntryPointParam = 'html_entry_point';
|
static const _htmlEntryPointParam = 'html_entry_point';
|
||||||
|
|
||||||
factory AngularTransformer.asPlugin(BarbackSettings settings) {
|
factory AngularTransformer.asPlugin(BarbackSettings settings) {
|
||||||
|
var bootstrapEntryPoint = settings.configuration[_bootstrapEntryPointParam];
|
||||||
var entryPoint = settings.configuration[_entryPointParam];
|
var entryPoint = settings.configuration[_entryPointParam];
|
||||||
var newEntryPoint = settings.configuration[_newEntryPointParam];
|
var newEntryPoint = settings.configuration[_newEntryPointParam];
|
||||||
if (newEntryPoint == null) {
|
if (newEntryPoint == null) {
|
||||||
newEntryPoint = entryPoint.replaceFirst('.dart', '.bootstrap.dart');
|
newEntryPoint = entryPoint.replaceFirst('.dart', '.bootstrap.dart');
|
||||||
}
|
}
|
||||||
var htmlEntryPoint = settings.configuration[_htmlEntryPointParam];
|
var htmlEntryPoint = settings.configuration[_htmlEntryPointParam];
|
||||||
return new AngularTransformer(
|
return new AngularTransformer(new TransformerOptions(
|
||||||
new TransformerOptions(entryPoint, newEntryPoint, htmlEntryPoint));
|
bootstrapEntryPoint, entryPoint, newEntryPoint, htmlEntryPoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isPrimary(AssetId id) =>
|
bool isPrimary(AssetId id) =>
|
||||||
options.entryPoint == id.path || options.htmlEntryPoint == id.path;
|
options.entryPoint == id.path || options.htmlEntryPoint == id.path;
|
||||||
|
|
||||||
Future apply(Transform transform) {
|
Future apply(Transform transform) {
|
||||||
|
log.init(transform);
|
||||||
|
|
||||||
if (transform.primaryInput.id.path == options.entryPoint) {
|
if (transform.primaryInput.id.path == options.entryPoint) {
|
||||||
return _buildBootstrapFile(transform);
|
return _buildBootstrapFile(transform);
|
||||||
} else if (transform.primaryInput.id.path == options.htmlEntryPoint) {
|
} else if (transform.primaryInput.id.path == options.htmlEntryPoint) {
|
||||||
|
@ -49,17 +57,24 @@ class AngularTransformer extends Transformer {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _buildBootstrapFile(Transform transform) {
|
Future _buildBootstrapFile(Transform transform) {
|
||||||
|
var bootstrapEntryPointId = new AssetId(
|
||||||
|
transform.primaryInput.id.package, options.bootstrapEntryPoint);
|
||||||
var newEntryPointId =
|
var newEntryPointId =
|
||||||
new AssetId(transform.primaryInput.id.package, options.newEntryPoint);
|
new AssetId(transform.primaryInput.id.package, options.newEntryPoint);
|
||||||
return transform.hasInput(newEntryPointId).then((exists) {
|
return transform.hasInput(newEntryPointId).then((exists) {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
transform.logger
|
log.logger
|
||||||
.error('New entry point file $newEntryPointId already exists.');
|
.error('New entry point file $newEntryPointId already exists.');
|
||||||
} else {
|
} else {
|
||||||
return _resolvers.get(transform).then((resolver) {
|
return _resolvers.get(transform).then((resolver) {
|
||||||
try {
|
try {
|
||||||
new _BootstrapFileBuilder(resolver, transform,
|
new _BootstrapFileBuilder(resolver, transform,
|
||||||
transform.primaryInput.id, newEntryPointId).run();
|
transform.primaryInput.id, bootstrapEntryPointId,
|
||||||
|
newEntryPointId).run();
|
||||||
|
} catch (err, stackTrace) {
|
||||||
|
log.logger.error('${err}: ${stackTrace}',
|
||||||
|
asset: bootstrapEntryPointId);
|
||||||
|
rethrow;
|
||||||
} finally {
|
} finally {
|
||||||
resolver.release();
|
resolver.release();
|
||||||
}
|
}
|
||||||
|
@ -72,28 +87,40 @@ class AngularTransformer extends Transformer {
|
||||||
class _BootstrapFileBuilder {
|
class _BootstrapFileBuilder {
|
||||||
final Resolver _resolver;
|
final Resolver _resolver;
|
||||||
final Transform _transform;
|
final Transform _transform;
|
||||||
|
final AssetId _bootstrapEntryPoint;
|
||||||
final AssetId _entryPoint;
|
final AssetId _entryPoint;
|
||||||
final AssetId _newEntryPoint;
|
final AssetId _newEntryPoint;
|
||||||
|
|
||||||
AnnotationMatcher _directiveInfo;
|
|
||||||
|
|
||||||
_BootstrapFileBuilder(Resolver resolver, Transform transform,
|
_BootstrapFileBuilder(Resolver resolver, Transform transform,
|
||||||
this._entryPoint, this._newEntryPoint)
|
this._entryPoint, this._bootstrapEntryPoint, this._newEntryPoint)
|
||||||
: _resolver = resolver,
|
: _resolver = resolver,
|
||||||
_transform = transform,
|
_transform = transform;
|
||||||
_directiveInfo = new AnnotationMatcher(resolver
|
|
||||||
.getLibrary(new AssetId(
|
|
||||||
'angular2', 'lib/src/core/annotations/annotations.dart'))
|
|
||||||
.getType('Directive'));
|
|
||||||
|
|
||||||
/// Adds the new entry point file to the transform. Should only be ran once.
|
/// Adds the new entry point file to the transform. Should only be ran once.
|
||||||
void run() {
|
void run() {
|
||||||
var entryLib = _resolver.getLibrary(_entryPoint);
|
var entryLib = _resolver.getLibrary(_entryPoint);
|
||||||
|
|
||||||
new ImportTraversal(_directiveInfo).traverse(entryLib);
|
Set<BootstrapCallInfo> bootstrapCalls = findBootstrapCalls(
|
||||||
|
_resolver, _resolver.getLibrary(_bootstrapEntryPoint));
|
||||||
|
|
||||||
|
log.logger.info('found ${bootstrapCalls.length} call(s) to `bootstrap`');
|
||||||
|
bootstrapCalls.forEach((BootstrapCallInfo info) {
|
||||||
|
log.logger.info('Arg1: ${info.bootstrapType}');
|
||||||
|
});
|
||||||
|
|
||||||
|
var types = new Angular2Types(_resolver);
|
||||||
|
// TODO(kegluneq): Also match [Inject].
|
||||||
|
var matcher = new AnnotationMatcher(new Set.from([
|
||||||
|
types.componentAnnotation,
|
||||||
|
types.decoratorAnnotation,
|
||||||
|
types.templateAnnotation
|
||||||
|
]));
|
||||||
|
|
||||||
|
var traversal = new AngularVisibleTraversal(types, matcher);
|
||||||
|
bootstrapCalls.forEach((call) => traversal.traverse(call.bootstrapType));
|
||||||
|
|
||||||
var context = new codegen.Context(logger: _transform.logger);
|
var context = new codegen.Context(logger: _transform.logger);
|
||||||
_directiveInfo.matchQueue
|
matcher.matchQueue
|
||||||
.forEach((entry) => context.directiveRegistry.register(entry));
|
.forEach((entry) => context.directiveRegistry.register(entry));
|
||||||
|
|
||||||
_transform.addOutput(new Asset.fromString(_newEntryPoint, codegen
|
_transform.addOutput(new Asset.fromString(_newEntryPoint, codegen
|
||||||
|
|
|
@ -1,102 +1,83 @@
|
||||||
library angular2.src.transform;
|
library angular2.src.transform;
|
||||||
|
|
||||||
|
import 'package:analyzer/src/generated/ast.dart';
|
||||||
import 'package:analyzer/src/generated/element.dart';
|
import 'package:analyzer/src/generated/element.dart';
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
|
|
||||||
import 'annotation_processor.dart';
|
import 'annotation_processor.dart';
|
||||||
|
import 'logging.dart';
|
||||||
|
import 'resolvers.dart';
|
||||||
|
|
||||||
class ImportTraversal {
|
/// Walks through an Angular2 application, finding all classes matching the
|
||||||
final AnnotationMatcher _annotationMatcher;
|
/// provided [annotationMatcher].
|
||||||
|
class AngularVisibleTraversal {
|
||||||
|
final Angular2Types _types;
|
||||||
|
final _ComponentParsingAstVisitor _visitor;
|
||||||
|
|
||||||
ImportTraversal(this._annotationMatcher);
|
AngularVisibleTraversal(this._types, AnnotationMatcher annotationMatcher)
|
||||||
|
: _visitor = new _ComponentParsingAstVisitor(annotationMatcher);
|
||||||
|
|
||||||
/// Reads Initializer annotations on this library and all its dependencies in
|
/// Walks an Angular2 application, starting with the class represented by
|
||||||
/// post-order.
|
/// [entryPoint], which must be annotated as an Angular2 [Component].
|
||||||
void traverse(LibraryElement library, [Set<LibraryElement> seen]) {
|
///
|
||||||
if (seen == null) seen = new Set<LibraryElement>();
|
/// We recursively check the entryPoint's annotations and constructor
|
||||||
seen.add(library);
|
/// arguments for types which match the provided [annotationMatcher].
|
||||||
|
void traverse(ClassElement entryPoint) {
|
||||||
// Visit all our dependencies.
|
if (!_types.isComponent(entryPoint)) {
|
||||||
for (var importedLibrary in _sortedLibraryImports(library)) {
|
throw new ArgumentError.value(entryPoint, 'entryPoint',
|
||||||
// Don't include anything from the sdk.
|
'Provided entryPoint must be annotated as a Component');
|
||||||
if (importedLibrary.isInSdk) continue;
|
|
||||||
if (seen.contains(importedLibrary)) continue;
|
|
||||||
traverse(importedLibrary, seen);
|
|
||||||
}
|
}
|
||||||
|
entryPoint.node.accept(_visitor);
|
||||||
for (var clazz in _classesOfLibrary(library, seen)) {
|
|
||||||
var superClass = clazz.supertype;
|
|
||||||
while (superClass != null) {
|
|
||||||
if (_annotationMatcher.processAnnotations(superClass.element) &&
|
|
||||||
superClass.element.library != clazz.library) {
|
|
||||||
_logger.warning(
|
|
||||||
'We have detected a cycle in your import graph when running '
|
|
||||||
'initializers on ${clazz.name}. This means the super class '
|
|
||||||
'${superClass.name} has a dependency on this library '
|
|
||||||
'(possibly transitive).');
|
|
||||||
}
|
|
||||||
superClass = superClass.superclass;
|
|
||||||
}
|
|
||||||
_annotationMatcher.processAnnotations(clazz);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves all classes that are visible if you were to import [lib]. This
|
class _ComponentParsingAstVisitor extends Object
|
||||||
/// includes exported classes from other libraries.
|
with RecursiveAstVisitor<Object> {
|
||||||
List<ClassElement> _classesOfLibrary(
|
final Set<ClassElement> _seen = new Set();
|
||||||
LibraryElement library, Set<LibraryElement> seen) {
|
final AnnotationMatcher _matcher;
|
||||||
var result = [];
|
|
||||||
result.addAll(library.units.expand((u) => u.types));
|
_ComponentParsingAstVisitor(this._matcher);
|
||||||
for (var export in library.exports) {
|
|
||||||
if (seen.contains(export.exportedLibrary)) continue;
|
@override
|
||||||
var exported = _classesOfLibrary(export.exportedLibrary, seen);
|
Object visitClassDeclaration(ClassDeclaration node) {
|
||||||
_filter(exported, export.combinators);
|
if (node.element != null) {
|
||||||
result.addAll(exported);
|
if (_seen.contains(node.element)) return null;
|
||||||
}
|
_seen.add(node.element);
|
||||||
result.sort((a, b) => a.name.compareTo(b.name));
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Filters [elements] that come from an export, according to its show/hide
|
// Process the class itself.
|
||||||
/// combinators. This modifies [elements] in place.
|
node.name.accept(this);
|
||||||
void _filter(List<Element> elements, List<NamespaceCombinator> combinators) {
|
|
||||||
for (var c in combinators) {
|
// Process metadata information, ignoring [FieldDeclaration]s and
|
||||||
if (c is ShowElementCombinator) {
|
// [MethodDeclaration]s (see below).
|
||||||
var show = c.shownNames.toSet();
|
node.metadata.forEach((Annotation meta) => meta.accept(this));
|
||||||
elements.retainWhere((e) => show.contains(e.displayName));
|
|
||||||
} else if (c is HideElementCombinator) {
|
// Process constructor parameters, fields & methods are ignored below.
|
||||||
var hide = c.hiddenNames.toSet();
|
node.members.forEach((m) => m.accept(this));
|
||||||
elements.removeWhere((e) => hide.contains(e.displayName));
|
|
||||||
}
|
return null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<LibraryElement> _sortedLibraryImports(LibraryElement library) =>
|
@override
|
||||||
(new List.from(library.imports)
|
Object visitFieldDeclaration(FieldDeclaration node) => null;
|
||||||
..sort((ImportElement a, ImportElement b) {
|
|
||||||
// dart: imports don't have a uri
|
@override
|
||||||
if (a.uri == null && b.uri != null) return -1;
|
Object visitMethodDeclaration(MethodDeclaration node) => null;
|
||||||
if (b.uri == null && a.uri != null) return 1;
|
|
||||||
if (a.uri == null && b.uri == null) {
|
@override
|
||||||
return a.importedLibrary.name.compareTo(b.importedLibrary.name);
|
Object visitAnnotation(Annotation node) {
|
||||||
|
// TODO(kegluneq): Visit only Angular2 annotations & subtypes.
|
||||||
|
return super.visitAnnotation(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
// package: imports next
|
@override
|
||||||
var aIsPackage = a.uri.startsWith('package:');
|
Object visitSimpleIdentifier(SimpleIdentifier node) {
|
||||||
var bIsPackage = b.uri.startsWith('package:');
|
if (node.bestElement != null) {
|
||||||
if (aIsPackage && !bIsPackage) {
|
if (node.bestElement is ClassElement) {
|
||||||
return -1;
|
var matches = _matcher.processAnnotations(node.bestElement);
|
||||||
} else if (bIsPackage && !aIsPackage) {
|
// If any of these types are matches, recurse on them.
|
||||||
return 1;
|
matches.forEach((match) => match.node.accept(this));
|
||||||
} else if (bIsPackage && aIsPackage) {
|
}
|
||||||
return a.uri.compareTo(b.uri);
|
}
|
||||||
|
return super.visitSimpleIdentifier(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
// And finally compare based on the relative uri if both are file paths.
|
|
||||||
var aUri = path.relative(a.source.uri.path,
|
|
||||||
from: path.dirname(library.source.uri.path));
|
|
||||||
var bUri = path.relative(b.source.uri.path,
|
|
||||||
from: path.dirname(library.source.uri.path));
|
|
||||||
return aUri.compareTo(bUri);
|
|
||||||
})).map((import) => import.importedLibrary);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||||
|
// for details. All rights reserved. Use of this source code is governed by a
|
||||||
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
library angular2.test.transform;
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
/// Mocked out version of [bootstrap], defined in application.dart. Importing
|
||||||
|
/// the actual file in tests causes issues with resolution due to its
|
||||||
|
/// transitive dependencies.
|
||||||
|
Future bootstrap(Type appComponentType, [List<Binding> bindings = null,
|
||||||
|
Function givenBootstrapErrorReporter = null]) {
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
library web_foo;
|
library web_foo;
|
||||||
|
|
||||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||||
|
import 'package:angular2/src/core/application.dart';
|
||||||
|
|
||||||
void main() {}
|
void main() {}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
library web_foo;
|
library web_foo;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/application.dart';
|
||||||
import 'bar.dart';
|
import 'bar.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
new Component('Things');
|
bootstrap(MyComponent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
library web_foo;
|
library web_foo;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/application.dart';
|
||||||
import 'bar.dart';
|
import 'bar.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
new MyComponent('Things');
|
bootstrap(MyComponent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
library web_foo;
|
library web_foo;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/application.dart';
|
||||||
import 'bar.dart';
|
import 'bar.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
new MyComponent();
|
bootstrap(MyComponent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var formatter = new DartFormatter();
|
var formatter = new DartFormatter();
|
||||||
var transform = new AngularTransformer(new TransformerOptions(
|
var transform = new AngularTransformer(new TransformerOptions('web/index.dart',
|
||||||
'web/index.dart', 'web/index.bootstrap.dart', 'web/index.html'));
|
'web/index.dart', 'web/index.bootstrap.dart', 'web/index.html'));
|
||||||
|
|
||||||
class TestConfig {
|
class TestConfig {
|
||||||
|
@ -40,7 +40,8 @@ void _runTests() {
|
||||||
'a|web/index.html': 'common.html',
|
'a|web/index.html': 'common.html',
|
||||||
'a|web/index.dart': 'html_entry_point_files/index.dart',
|
'a|web/index.dart': 'html_entry_point_files/index.dart',
|
||||||
'angular2|lib/src/core/annotations/annotations.dart':
|
'angular2|lib/src/core/annotations/annotations.dart':
|
||||||
'../../lib/src/core/annotations/annotations.dart'
|
'../../lib/src/core/annotations/annotations.dart',
|
||||||
|
'angular2|lib/src/core/application.dart': 'common.dart'
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
'a|web/index.html': 'html_entry_point_files/expected/index.html'
|
'a|web/index.html': 'html_entry_point_files/expected/index.html'
|
||||||
|
@ -51,7 +52,8 @@ void _runTests() {
|
||||||
'a|web/index.dart': 'simple_annotation_files/index.dart',
|
'a|web/index.dart': 'simple_annotation_files/index.dart',
|
||||||
'a|web/bar.dart': 'simple_annotation_files/bar.dart',
|
'a|web/bar.dart': 'simple_annotation_files/bar.dart',
|
||||||
'angular2|lib/src/core/annotations/annotations.dart':
|
'angular2|lib/src/core/annotations/annotations.dart':
|
||||||
'../../lib/src/core/annotations/annotations.dart'
|
'../../lib/src/core/annotations/annotations.dart',
|
||||||
|
'angular2|lib/src/core/application.dart': 'common.dart'
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
'a|web/index.bootstrap.dart':
|
'a|web/index.bootstrap.dart':
|
||||||
|
@ -64,7 +66,8 @@ void _runTests() {
|
||||||
'a|web/foo.dart': 'two_deps_files/foo.dart',
|
'a|web/foo.dart': 'two_deps_files/foo.dart',
|
||||||
'a|web/bar.dart': 'two_deps_files/bar.dart',
|
'a|web/bar.dart': 'two_deps_files/bar.dart',
|
||||||
'angular2|lib/src/core/annotations/annotations.dart':
|
'angular2|lib/src/core/annotations/annotations.dart':
|
||||||
'../../lib/src/core/annotations/annotations.dart'
|
'../../lib/src/core/annotations/annotations.dart',
|
||||||
|
'angular2|lib/src/core/application.dart': 'common.dart'
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
'a|web/index.bootstrap.dart':
|
'a|web/index.bootstrap.dart':
|
||||||
|
@ -77,7 +80,8 @@ void _runTests() {
|
||||||
'a|web/foo.dart': 'list_of_types_files/foo.dart',
|
'a|web/foo.dart': 'list_of_types_files/foo.dart',
|
||||||
'a|web/bar.dart': 'list_of_types_files/bar.dart',
|
'a|web/bar.dart': 'list_of_types_files/bar.dart',
|
||||||
'angular2|lib/src/core/annotations/annotations.dart':
|
'angular2|lib/src/core/annotations/annotations.dart':
|
||||||
'../../lib/src/core/annotations/annotations.dart'
|
'../../lib/src/core/annotations/annotations.dart',
|
||||||
|
'angular2|lib/src/core/application.dart': 'common.dart'
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
'a|web/index.bootstrap.dart':
|
'a|web/index.bootstrap.dart':
|
||||||
|
@ -89,12 +93,28 @@ void _runTests() {
|
||||||
'a|web/index.dart': 'synthetic_ctor_files/index.dart',
|
'a|web/index.dart': 'synthetic_ctor_files/index.dart',
|
||||||
'a|web/bar.dart': 'synthetic_ctor_files/bar.dart',
|
'a|web/bar.dart': 'synthetic_ctor_files/bar.dart',
|
||||||
'angular2|lib/src/core/annotations/annotations.dart':
|
'angular2|lib/src/core/annotations/annotations.dart':
|
||||||
'../../lib/src/core/annotations/annotations.dart'
|
'../../lib/src/core/annotations/annotations.dart',
|
||||||
|
'angular2|lib/src/core/application.dart': 'common.dart'
|
||||||
},
|
},
|
||||||
outputs: {
|
outputs: {
|
||||||
'a|web/index.bootstrap.dart':
|
'a|web/index.bootstrap.dart':
|
||||||
'synthetic_ctor_files/expected/index.bootstrap.dart'
|
'synthetic_ctor_files/expected/index.bootstrap.dart'
|
||||||
})
|
}),
|
||||||
|
new TestConfig('Component with two annotations',
|
||||||
|
inputs: {
|
||||||
|
'a|web/index.html': 'common.html',
|
||||||
|
'a|web/index.dart': 'two_annotations_files/index.dart',
|
||||||
|
'a|web/bar.dart': 'two_annotations_files/bar.dart',
|
||||||
|
'angular2|lib/src/core/annotations/annotations.dart':
|
||||||
|
'../../lib/src/core/annotations/annotations.dart',
|
||||||
|
'angular2|lib/src/core/annotations/template.dart':
|
||||||
|
'../../lib/src/core/annotations/template.dart',
|
||||||
|
'angular2|lib/src/core/application.dart': 'common.dart'
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
'a|web/index.bootstrap.dart':
|
||||||
|
'two_annotations_files/expected/index.bootstrap.dart'
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
var cache = {};
|
var cache = {};
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
library bar;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||||
|
import 'package:angular2/src/core/annotations/template.dart';
|
||||||
|
|
||||||
|
@Component(selector: '[soup]')
|
||||||
|
@Template(inline: 'Salad')
|
||||||
|
class MyComponent {
|
||||||
|
MyComponent();
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import 'package:angular2/src/reflection/reflection.dart' show reflector;
|
||||||
|
import 'bar.dart' as i0;
|
||||||
|
import 'package:angular2/src/core/annotations/annotations.dart' as i1;
|
||||||
|
import 'package:angular2/src/core/annotations/template.dart' as i2;
|
||||||
|
import 'index.dart' as i3;
|
||||||
|
|
||||||
|
main() {
|
||||||
|
reflector
|
||||||
|
..registerType(i0.MyComponent, {
|
||||||
|
"factory": () => new i0.MyComponent(),
|
||||||
|
"parameters": const [const []],
|
||||||
|
"annotations": const [
|
||||||
|
const i1.Component(selector: '[soup]'),
|
||||||
|
const i2.Template(inline: 'Salad')
|
||||||
|
]
|
||||||
|
});
|
||||||
|
i3.main();
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
library web_foo;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/application.dart';
|
||||||
|
import 'bar.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
bootstrap(MyComponent);
|
||||||
|
}
|
|
@ -10,9 +10,7 @@ main() {
|
||||||
"factory":
|
"factory":
|
||||||
(i1.MyContext c, String inValue) => new i0.MyComponent(c, inValue),
|
(i1.MyContext c, String inValue) => new i0.MyComponent(c, inValue),
|
||||||
"parameters": const [const [i1.MyContext, String]],
|
"parameters": const [const [i1.MyContext, String]],
|
||||||
"annotations": const [
|
"annotations": const [const i2.Component(selector: i1.preDefinedSelector)]
|
||||||
const i2.Component(selector: i1.preDefinedSelector)
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
i3.main();
|
i3.main();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
library web_foo;
|
library web_foo;
|
||||||
|
|
||||||
|
import 'package:angular2/src/core/application.dart';
|
||||||
import 'bar.dart';
|
import 'bar.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
new MyComponent('Things');
|
bootstrap(MyComponent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ dev_dependencies:
|
||||||
guinness: ">=0.1.16 <0.2.0"
|
guinness: ">=0.1.16 <0.2.0"
|
||||||
transformers:
|
transformers:
|
||||||
- angular2:
|
- angular2:
|
||||||
|
bootstrap_entry_point: web/src/hello_world/index_common.dart
|
||||||
entry_point: web/src/hello_world/index.dart
|
entry_point: web/src/hello_world/index.dart
|
||||||
html_entry_point: web/src/hello_world/index.html
|
html_entry_point: web/src/hello_world/index.html
|
||||||
- $dart2js:
|
- $dart2js:
|
||||||
|
|
Loading…
Reference in New Issue