feat(dart/transform): Allow multiple transformer entry points

- Allow the user to specify multiple entry points to an app.
- Allow the Angular 2 transformer to run without explicit entry points to
generate necessary setters & getters on built-in directives like `For`
and `If`.

Closes #1246
This commit is contained in:
Tim Blasi 2015-04-09 17:49:11 -07:00
parent bba849909c
commit 2cab7c79c3
12 changed files with 120 additions and 74 deletions

View File

@ -1,35 +1,29 @@
library angular2.transform.common.options; library angular2.transform.common.options;
const ENTRY_POINT_PARAM = 'entry_point'; const ENTRY_POINT_PARAM = 'entry_points';
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_point'; const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_points';
/// Provides information necessary to transform an Angular2 app. /// Provides information necessary to transform an Angular2 app.
class TransformerOptions { class TransformerOptions {
/// The path to the file where the application's call to [bootstrap] is. /// The path to the files where the application's calls to `bootstrap` are.
// TODO(kegluneq): Allow multiple entry points. final List<String> entryPoints;
final String entryPoint;
/// The reflection entry point, that is, the path to the file where the /// The paths to the files where the application's [ReflectionCapabilities]
/// application's [ReflectionCapabilities] are set. /// are set.
final String reflectionEntryPoint; final List<String> reflectionEntryPoints;
/// The `BarbackMode#name` we are running in. /// The `BarbackMode#name` we are running in.
final String modeName; final String modeName;
TransformerOptions._internal( TransformerOptions._internal(
this.entryPoint, this.reflectionEntryPoint, this.modeName); this.entryPoints, this.reflectionEntryPoints, this.modeName);
factory TransformerOptions(String entryPoint, factory TransformerOptions(List<String> entryPoints,
{String reflectionEntryPoint, String modeName: 'release'}) { {List<String> reflectionEntryPoints, String modeName: 'release'}) {
if (entryPoint == null) { if (reflectionEntryPoints == null || reflectionEntryPoints.isEmpty) {
throw new ArgumentError.notNull(ENTRY_POINT_PARAM); reflectionEntryPoints = entryPoints;
} else if (entryPoint.isEmpty) {
throw new ArgumentError.value(entryPoint, 'entryPoint');
}
if (reflectionEntryPoint == null || entryPoint.isEmpty) {
reflectionEntryPoint = entryPoint;
} }
return new TransformerOptions._internal( return new TransformerOptions._internal(
entryPoint, reflectionEntryPoint, modeName); entryPoints, reflectionEntryPoints, modeName);
} }
} }

View File

@ -0,0 +1,36 @@
library angular2.transform.common.options;
import 'package:barback/barback.dart';
import 'options.dart';
TransformerOptions parseBarbackSettings(BarbackSettings settings) {
var config = settings.configuration;
var entryPoints = _readFileList(config, ENTRY_POINT_PARAM);
var reflectionEntryPoints =
_readFileList(config, REFLECTION_ENTRY_POINT_PARAM);
return new TransformerOptions(entryPoints,
reflectionEntryPoints: reflectionEntryPoints,
modeName: settings.mode.name);
}
/// Cribbed from the polymer project.
/// [https://github.com/dart-lang/polymer-dart]
List<String> _readFileList(Map config, String paramName) {
var value = config[paramName];
if (value == null) return null;
var files = [];
bool error = false;
if (value is List) {
files = value;
error = value.any((e) => e is! String);
} else if (value is String) {
files = [value];
error = false;
} else {
error = true;
}
if (error) {
print('Invalid value for "$paramName" in the Angular 2 transformer.');
}
return files;
}

View File

@ -8,19 +8,29 @@ import 'directive_processor/transformer.dart';
import 'bind_generator/transformer.dart'; import 'bind_generator/transformer.dart';
import 'reflection_remover/transformer.dart'; import 'reflection_remover/transformer.dart';
import 'common/formatter.dart' as formatter; import 'common/formatter.dart' as formatter;
import 'common/names.dart';
import 'common/options.dart'; import 'common/options.dart';
import 'common/options_reader.dart';
export 'common/options.dart'; export 'common/options.dart';
/// Removes the mirror-based initialization logic and replaces it with static /// Removes the mirror-based initialization logic and replaces it with static
/// logic. /// logic.
class DiTransformerGroup extends TransformerGroup { class DiTransformerGroup extends TransformerGroup {
DiTransformerGroup() DiTransformerGroup._(phases) : super(phases) {
: super([[new DirectiveProcessor(null)], [new DirectiveLinker()]]) {
formatter.init(new DartFormatter()); formatter.init(new DartFormatter());
} }
factory DiTransformerGroup(TransformerOptions options) {
var phases = [
[new ReflectionRemover(options)],
[new DirectiveProcessor(null)],
[new DirectiveLinker()]
];
return new DiTransformerGroup._(phases);
}
factory DiTransformerGroup.asPlugin(BarbackSettings settings) { factory DiTransformerGroup.asPlugin(BarbackSettings settings) {
return new DiTransformerGroup(); return new DiTransformerGroup(parseBarbackSettings(settings));
} }
} }

View File

@ -12,14 +12,14 @@ class Codegen {
/// The prefix used to import our generated file. /// The prefix used to import our generated file.
final String prefix; final String prefix;
/// The import uri /// The import uris
final String importUri; final Iterable<String> importUris;
Codegen(String reflectionEntryPointPath, String newEntryPointPath, Codegen(String reflectionEntryPointPath, Iterable<String> newEntryPointPaths,
{String prefix}) {String prefix})
: this.prefix = prefix == null ? _PREFIX_BASE : prefix, : this.prefix = prefix == null ? _PREFIX_BASE : prefix,
importUri = path.relative(newEntryPointPath, importUris = newEntryPointPaths.map((p) =>
from: path.dirname(reflectionEntryPointPath)) { path.relative(p, from: path.dirname(reflectionEntryPointPath))) {
if (this.prefix.isEmpty) throw new ArgumentError.value('(empty)', 'prefix'); if (this.prefix.isEmpty) throw new ArgumentError.value('(empty)', 'prefix');
} }
@ -43,7 +43,10 @@ class Codegen {
/// The code generated here should follow the example of code generated for /// The code generated here should follow the example of code generated for
/// an [ImportDirective] node. /// an [ImportDirective] node.
String codegenImport() { String codegenImport() {
return 'import \'${importUri}\' as ${prefix};'; var count = 0;
return importUris
.map((importUri) => 'import \'${importUri}\' as ${prefix}${count++};')
.join('');
} }
/// Generates code to call the method which sets up Angular2 reflection /// Generates code to call the method which sets up Angular2 reflection
@ -63,7 +66,11 @@ class Codegen {
reflectorExpression = 'reflector'; reflectorExpression = 'reflector';
} }
return '${prefix}.${SETUP_METHOD_NAME}(${reflectorExpression});'; var count = 0;
return importUris
.map((_) =>
'${prefix}${count++}.${SETUP_METHOD_NAME}(${reflectorExpression});')
.join('');
} }
} }

View File

@ -1,21 +1,26 @@
library angular2.transform.reflection_remover.remove_reflection_capabilities; library angular2.transform.reflection_remover.remove_reflection_capabilities;
import 'dart:async';
import 'package:analyzer/analyzer.dart'; import 'package:analyzer/analyzer.dart';
import 'package:barback/barback.dart';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'codegen.dart'; import 'codegen.dart';
import 'rewriter.dart'; import 'rewriter.dart';
/// Finds the call to the Angular2 [ReflectionCapabilities] constructor /// Finds the call to the Angular2 `ReflectionCapabilities` constructor
/// in [code] and replaces it with a call to `setupReflection` in /// in [reflectionEntryPoint] and replaces it with a call to
/// [newEntryPoint]. /// `setupReflection` in [newEntryPoint].
/// ///
/// [reflectionEntryPointPath] is the path where [code] is defined and is /// This only searches the code in [reflectionEntryPoint], not `part`s,
/// used to display parsing errors. /// `import`s, `export`s, etc.
/// Future<String> removeReflectionCapabilities(AssetReader reader,
/// This only searches [code] not `part`s, `import`s, `export`s, etc. AssetId reflectionEntryPoint, Iterable<AssetId> newEntryPoints) async {
String removeReflectionCapabilities( var code = await reader.readAsString(reflectionEntryPoint);
String code, String reflectionEntryPointPath, String newEntryPointPath) { var reflectionEntryPointPath = reflectionEntryPoint.path;
var codegen = new Codegen(reflectionEntryPointPath, newEntryPointPath); var newEntryPointPaths = newEntryPoints.map((id) => id.path);
var codegen = new Codegen(reflectionEntryPointPath, newEntryPointPaths);
return new Rewriter(code, codegen) return new Rewriter(code, codegen)
.rewrite(parseCompilationUnit(code, name: reflectionEntryPointPath)); .rewrite(parseCompilationUnit(code, name: reflectionEntryPointPath));
} }

View File

@ -1,6 +1,7 @@
library angular2.transform.reflection_remover.transformer; library angular2.transform.reflection_remover.transformer;
import 'dart:async'; 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/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart'; import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/options.dart'; import 'package:angular2/src/transform/common/options.dart';
@ -23,21 +24,24 @@ class ReflectionRemover extends Transformer {
ReflectionRemover(this.options); ReflectionRemover(this.options);
@override @override
bool isPrimary(AssetId id) => options.reflectionEntryPoint == id.path; bool isPrimary(AssetId id) => options.reflectionEntryPoints != null &&
options.reflectionEntryPoints.contains(id.path);
@override @override
Future apply(Transform transform) async { Future apply(Transform transform) async {
log.init(transform); log.init(transform);
try { try {
var newEntryPoint = new AssetId( var newEntryPoints = options.entryPoints.map((entryPoint) {
transform.primaryInput.id.package, options.entryPoint) return new AssetId(transform.primaryInput.id.package, entryPoint)
.changeExtension(DEPS_EXTENSION); .changeExtension(DEPS_EXTENSION);
});
var reader = new AssetReader.fromTransform(transform);
var assetCode = await transform.primaryInput.readAsString(); var transformedCode = await removeReflectionCapabilities(
transform.addOutput(new Asset.fromString(transform.primaryInput.id, reader, transform.primaryInput.id, newEntryPoints);
removeReflectionCapabilities( transform.addOutput(
assetCode, transform.primaryInput.id.path, newEntryPoint.path))); new Asset.fromString(transform.primaryInput.id, transformedCode));
} catch (ex, stackTrace) { } catch (ex, stackTrace) {
log.logger.error('Removing reflection failed.\n' log.logger.error('Removing reflection failed.\n'
'Exception: $ex\n' 'Exception: $ex\n'

View File

@ -11,6 +11,7 @@ import 'template_compiler/transformer.dart';
import 'common/formatter.dart' as formatter; import 'common/formatter.dart' as formatter;
import 'common/names.dart'; import 'common/names.dart';
import 'common/options.dart'; import 'common/options.dart';
import 'common/options_reader.dart';
export 'common/options.dart'; export 'common/options.dart';
@ -21,25 +22,17 @@ class AngularTransformerGroup extends TransformerGroup {
} }
factory AngularTransformerGroup(TransformerOptions options) { factory AngularTransformerGroup(TransformerOptions options) {
var phases = [[new DirectiveProcessor(options)], [new DirectiveLinker()]]; var phases = [
if (options.modeName == TRANSFORM_MODE) { [new ReflectionRemover(options)],
phases.addAll([ [new DirectiveProcessor(options)],
[new BindGenerator(options)], [new DirectiveLinker()],
[new TemplateCompiler(options)], [new BindGenerator(options)],
[new ReflectionRemover(options)] [new TemplateCompiler(options)]
]); ];
}
return new AngularTransformerGroup._(phases); return new AngularTransformerGroup._(phases);
} }
factory AngularTransformerGroup.asPlugin(BarbackSettings settings) { factory AngularTransformerGroup.asPlugin(BarbackSettings settings) {
return new AngularTransformerGroup(_parseOptions(settings)); return new AngularTransformerGroup(parseBarbackSettings(settings));
} }
} }
TransformerOptions _parseOptions(BarbackSettings settings) {
var config = settings.configuration;
return new TransformerOptions(config[ENTRY_POINT_PARAM],
reflectionEntryPoint: config[REFLECTION_ENTRY_POINT_PARAM],
modeName: settings.mode.name);
}

View File

@ -11,8 +11,8 @@ import '../common/read_file.dart';
var formatter = new DartFormatter(); var formatter = new DartFormatter();
var transform = new AngularTransformerGroup(new TransformerOptions( var transform = new AngularTransformerGroup(new TransformerOptions(
'web/index.dart', ['web/index.dart'],
reflectionEntryPoint: 'web/index.dart', modeName: TRANSFORM_MODE)); reflectionEntryPoints: ['web/index.dart'], modeName: TRANSFORM_MODE));
class IntegrationTestConfig { class IntegrationTestConfig {
final String name; final String name;

View File

@ -3,12 +3,10 @@ library web_foo.ng_deps.dart;
import 'index.dart'; import 'index.dart';
import 'package:angular2/src/core/application.dart'; import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart'; import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/reflection/reflection_capabilities.dart'; import 'index.ng_deps.dart' as ngStaticInit0;
import 'bar.dart'; import 'bar.dart';
import 'bar.ng_deps.dart' as i0; import 'bar.ng_deps.dart' as i0;
import 'package:angular2/src/core/application.ng_deps.dart' as i1; import 'package:angular2/src/core/application.ng_deps.dart' as i1;
import 'package:angular2/src/reflection/reflection_capabilities.ng_deps.dart'
as i2;
bool _visited = false; bool _visited = false;
void initReflector(reflector) { void initReflector(reflector) {
@ -16,5 +14,4 @@ void initReflector(reflector) {
_visited = true; _visited = true;
i0.initReflector(reflector); i0.initReflector(reflector);
i1.initReflector(reflector); i1.initReflector(reflector);
i2.initReflector(reflector);
} }

View File

@ -9,7 +9,7 @@ import 'reflection_remover_files/expected/index.dart' as expected;
import '../common/read_file.dart'; import '../common/read_file.dart';
void allTests() { void allTests() {
var codegen = new Codegen('web/index.dart', 'web/index.ng_deps.dart'); var codegen = new Codegen('web/index.dart', ['web/index.ng_deps.dart']);
it('should remove uses of mirrors & insert calls to generated code.', () { it('should remove uses of mirrors & insert calls to generated code.', () {
var code = var code =

View File

@ -13,10 +13,10 @@ library web_foo;
import 'package:angular2/src/core/application.dart'; import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/reflection/reflection.dart'; import 'package:angular2/src/reflection/reflection.dart';
/*import 'package:angular2/src/reflection/reflection_capabilities.dart';*/import 'index.ng_deps.dart' as ngStaticInit; /*import 'package:angular2/src/reflection/reflection_capabilities.dart';*/import 'index.ng_deps.dart' as ngStaticInit0;
void main() { void main() {
/*reflector.reflectionCapabilities = new ReflectionCapabilities();*/ngStaticInit.initReflector(reflector); /*reflector.reflectionCapabilities = new ReflectionCapabilities();*/ngStaticInit0.initReflector(reflector);
bootstrap(MyComponent); bootstrap(MyComponent);
} }
"""; """;

View File

@ -13,8 +13,8 @@ dev_dependencies:
path: ../benchpress path: ../benchpress
transformers: transformers:
- angular2: - angular2:
entry_point: web/src/hello_world/index_common.dart entry_points: web/src/hello_world/index_common.dart
reflection_entry_point: web/src/hello_world/index.dart reflection_entry_points: web/src/hello_world/index.dart
- $dart2js: - $dart2js:
minify: true minify: true
commandLineOptions: [--trust-type-annotations, --trust-primitives, --dump-info] commandLineOptions: [--trust-type-annotations, --trust-primitives, --dump-info]