feat(dart/transform): Create standalone transformers for phases

Create transformers that allow specifying transformer actions on
specific libraries.

* angular2/transform/codegen: Generates all necessary code.
* angular2/transform/reflection_rewriter: Replaces `bootstrap` calls in
  application entry points to remove transitive dart:mirrors import,
  resulting in smaller code size & faster execution.
* angular2/transform/deferred_rewriter: Rewrites deferred imports and
  `loadLibrary` calls to initialize Angular2 and preserve deferred
  operation.

Proper configuration of these three transformers can replace the single
angular2 transformer, resulting in significant performance gains for
builds of large angular2 apps.

Update angular2 itself to declare the codegen transformer, since it has
neither deferred imports nor application entry points.

Remove the undocumented & unused quick_transformer.
This commit is contained in:
Tim Blasi 2016-02-12 16:33:40 -08:00 committed by Vikram Subramanian
parent ae49085481
commit 15e16148f4
8 changed files with 222 additions and 109 deletions

View File

@ -27,7 +27,7 @@ dev_dependencies:
quiver: '^0.21.4'
test: '^0.12.6'
transformers:
- angular2
- angular2/transform/codegen
- $dart2js:
commandLineOptions:
- --show-package-warnings

View File

@ -6,18 +6,21 @@ import 'package:barback/barback.dart';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/options.dart';
import 'package:angular2/src/transform/common/zone.dart' as zone;
import 'rewriter.dart';
/// Transformer responsible for rewriting deferred library loads to enable
/// initializing the reflector in a deferred way to keep the code with the
/// deferred library.
/// Transformer which rewrites deferred imports and `loadLibrary` calls.
///
/// Deferred imports with associated initialization code are rewritten to
/// point to their associated generated code file.
/// `loadLibrary` calls for libraries with associated initialization methods
/// are rewritten to initialize after the library is loaded and before use.
class DeferredRewriter extends AggregateTransformer implements LazyTransformer {
final TransformerOptions options;
DeferredRewriter();
DeferredRewriter(this.options);
/// Ctor which tells pub that this can be run as a standalone transformer.
DeferredRewriter.asPlugin(BarbackSettings _);
@override
declareOutputs(DeclaringTransform transform) {
@ -41,45 +44,51 @@ class DeferredRewriter extends AggregateTransformer implements LazyTransformer {
@override
Future apply(AggregateTransform transform) async {
return zone.exec(() async {
final dartAsset = await _assetToProcess(transform);
if (dartAsset == null) return;
final dartAsset = await _assetToProcess(transform);
if (dartAsset == null) return null;
return applyImpl(transform, dartAsset.id);
}
/// Transforms the asset with id [id].
///
/// [transform] must be an [AggregateTransform] or [Transform].
Future applyImpl(dynamic transform, AssetId id) {
assert(transform is AggregateTransform || transform is Transform);
return zone.exec(() async {
var transformedCode = await rewriteDeferredLibraries(
new AssetReader.fromTransform(transform), dartAsset.id);
new AssetReader.fromTransform(transform), id);
if (transformedCode != null) {
transform
.addOutput(new Asset.fromString(dartAsset.id, transformedCode));
transform.addOutput(new Asset.fromString(id, transformedCode));
}
}, log: transform.logger);
}
/// Returns the asset we need to process or `null` if none exists.
///
/// Consumes the .dart.deferredCount asset if it is present.
Future<Asset> _assetToProcess(AggregateTransform transform) async {
// We only need to process .dart files that have an associated
// .dart.deferredCount file with a value != "0".
//
// The .dart.deferredCount file is generated by a previous phase for files
// which have deferred imports. An absent .dart.deferredCount asset is the
// treated the same as a .dart.deferredCount asset with value "0".
var dartAsset, deferredCountAsset;
await for (Asset a in transform.primaryInputs) {
if (a.id.path.endsWith(DEFERRED_EXTENSION)) {
deferredCountAsset = a;
} else if (a.id.path.endsWith('.dart')) {
dartAsset = a;
}
}
if (deferredCountAsset == null) return null;
// No longer necessary.
transform.consumePrimary(deferredCountAsset.id);
if ((await deferredCountAsset.readAsString()) == "0") return null;
return dartAsset;
}
}
// Visible for testing
/// Returns the asset we need to process or `null` if none exists.
///
/// Consumes the .dart.deferredCount asset if it is present.
Future<Asset> _assetToProcess(AggregateTransform transform) async {
// We only need to process .dart files that have an associated
// .dart.deferredCount file with a value != "0".
//
// The .dart.deferredCount file is generated by a previous phase for files
// which have deferred imports. An absent .dart.deferredCount asset is the
// treated the same as a .dart.deferredCount asset with value "0".
var dartAsset, deferredCountAsset;
await for (Asset a in transform.primaryInputs) {
if (a.id.path.endsWith(DEFERRED_EXTENSION)) {
deferredCountAsset = a;
} else if (a.id.path.endsWith('.dart')) {
dartAsset = a;
}
}
if (deferredCountAsset == null) return null;
// No longer necessary.
transform.consumePrimary(deferredCountAsset.id);
if ((await deferredCountAsset.readAsString()) == "0") return null;
return dartAsset;
}
Future<String> rewriteDeferredLibraries(AssetReader reader, AssetId id) =>
rewriteLibrary(id, reader);

View File

@ -1,68 +0,0 @@
library angular2.src.transform.quick_transformer;
import 'package:barback/barback.dart';
import 'package:dart_style/dart_style.dart';
import 'common/formatter.dart' as formatter;
import 'common/options.dart';
import 'common/options_reader.dart';
import 'deferred_rewriter/transformer.dart';
// import 'directive_metadata_linker/transformer.dart';
// import 'directive_processor/transformer.dart';
import 'inliner_for_test/transformer.dart';
import 'reflection_remover/transformer.dart';
// import 'stylesheet_compiler/transformer.dart';
// import 'template_compiler/transformer.dart';
export 'common/options.dart';
/// Replaces Angular 2 mirror use with generated code.
///
/// Typical users should not use this implementation and should instead use
/// package:angular2/transformer.dart.
///
/// This alternative excludes phases that generate new [Asset]s, including only
/// the phases that replace existing [Asset]s.
///
/// This can be used if all code that would be generated already exists as real
/// files, rather than needing to be generated by [Transformer]s.
class AngularTransformerGroup extends TransformerGroup {
AngularTransformerGroup._(phases, {bool formatCode: false}) : super(phases) {
if (formatCode) {
formatter.init(new DartFormatter());
}
}
factory AngularTransformerGroup(TransformerOptions options) {
var phases;
if (options.inlineViews) {
phases = [
[new InlinerForTest(options)]
];
} else {
// Only the phases that replace [Asset]s.
phases = [
[new ReflectionRemover(options)],
[new DeferredRewriter(options)],
];
/*
phases = [
[new DirectiveProcessor(options)],
[new DirectiveMetadataLinker()],
[new ReflectionRemover(options)],
[
new DeferredRewriter(options),
new StylesheetCompiler(),
new TemplateCompiler(options)
],
];
*/
}
return new AngularTransformerGroup._(phases,
formatCode: options.formatCode);
}
factory AngularTransformerGroup.asPlugin(BarbackSettings settings) {
return new AngularTransformerGroup(parseBarbackSettings(settings));
}
}

View File

@ -8,6 +8,7 @@ import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/mirror_mode.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/options.dart';
import 'package:angular2/src/transform/common/options_reader.dart';
import 'package:angular2/src/transform/common/zone.dart' as zone;
import 'remove_reflection_capabilities.dart';
@ -26,8 +27,13 @@ class ReflectionRemover extends Transformer implements LazyTransformer {
ReflectionRemover(this.options);
/// Ctor which tells pub that this can be run as a standalone transformer.
factory ReflectionRemover.asPlugin(BarbackSettings settings) =>
new ReflectionRemover(parseBarbackSettings(settings));
@override
bool isPrimary(AssetId id) => options.entryPointGlobs != null &&
bool isPrimary(AssetId id) =>
options.entryPointGlobs != null &&
options.entryPointGlobs.any((g) => g.matches(id.path));
@override

View File

@ -37,7 +37,7 @@ class AngularTransformerGroup extends TransformerGroup {
[new DirectiveMetadataLinker()],
[new ReflectionRemover(options)],
[
new DeferredRewriter(options),
new DeferredRewriter(),
new StylesheetCompiler(),
new TemplateCompiler(options)
],

View File

@ -0,0 +1,56 @@
library angular2.transform.codegen.dart;
import 'package:barback/barback.dart';
import 'package:dart_style/dart_style.dart';
import 'package:angular2/src/transform/common/eager_transformer_wrapper.dart';
import 'package:angular2/src/transform/common/formatter.dart' as formatter;
import 'package:angular2/src/transform/common/options.dart';
import 'package:angular2/src/transform/common/options_reader.dart';
import 'package:angular2/src/transform/directive_metadata_linker/transformer.dart';
import 'package:angular2/src/transform/directive_processor/transformer.dart';
import 'package:angular2/src/transform/inliner_for_test/transformer.dart';
import 'package:angular2/src/transform/stylesheet_compiler/transformer.dart';
import 'package:angular2/src/transform/template_compiler/transformer.dart';
export 'package:angular2/src/transform/common/options.dart';
/// Generates code to replace mirror use in Angular 2 apps.
///
/// This transformer can be used along with others as a faster alternative to
/// the single angular2 transformer.
///
/// See [the wiki][] for details.
///
/// [the wiki]: https://github.com/angular/angular/wiki/Angular-2-Dart-Transformer
class CodegenTransformer extends TransformerGroup {
CodegenTransformer._(phases, {bool formatCode: false}) : super(phases) {
if (formatCode) {
formatter.init(new DartFormatter());
}
}
factory CodegenTransformer(TransformerOptions options) {
var phases;
if (options.inlineViews) {
phases = [
[new InlinerForTest(options)]
];
} else {
phases = [
[new DirectiveProcessor(options)],
[new DirectiveMetadataLinker()],
[new StylesheetCompiler(), new TemplateCompiler(options),],
];
}
if (options.modeName == BarbackMode.RELEASE || !options.lazyTransformers) {
phases = phases
.map((phase) => phase.map((t) => new EagerTransformerWrapper(t)));
}
return new CodegenTransformer._(phases, formatCode: options.formatCode);
}
factory CodegenTransformer.asPlugin(BarbackSettings settings) {
return new CodegenTransformer(parseBarbackSettings(settings));
}
}

View File

@ -0,0 +1,45 @@
library angular2.transform.deferred_rewriter.dart;
import 'dart:async';
import 'package:barback/barback.dart';
import 'package:angular2/src/transform/deferred_rewriter/transformer.dart'
as base show DeferredRewriter;
// TODO(kegluneq): Make this a TransformerGroup and add an AggregateTransformer
// that counts the number of transformed files & primary inputs.
// If the number of primary inputs is >> transformed files, output an error
// telling the user to use $include or $exclude in their pubspec.
/// Rewrites `deferred` imports that need Angular 2 initialization.
///
/// This transformer can be used along with others as a faster alternative to
/// the single angular2 transformer.
///
/// See [the wiki][] for details.
///
/// [the wiki]: https://github.com/angular/angular/wiki/Angular-2-Dart-Transformer
class DeferredRewriter extends Transformer implements DeclaringTransformer {
final base.DeferredRewriter _impl;
/// Ctor which tells pub that this can be run as a standalone transformer.
DeferredRewriter.asPlugin(BarbackSettings _)
: _impl = new base.DeferredRewriter();
/// Signal that we process all .dart files.
///
/// Instead, use the standard, built-in $exclude and $include transformer
/// parameters to control which files this transformer runs on.
/// See [https://www.dartlang.org/tools/pub/assets-and-transformers.html] for
/// details.
@override
String get allowedExtensions => '.dart';
@override
declareOutputs(transform) => _impl.declareOutputs(transform);
@override
Future apply(transform) =>
_impl.applyImpl(transform, transform.primaryInput.id);
}

View File

@ -0,0 +1,65 @@
library angular2.transform.reflection_remover.dart;
import 'dart:async';
import 'package:barback/barback.dart';
import 'package:angular2/src/transform/common/options.dart';
import 'package:angular2/src/transform/common/options_reader.dart';
import 'package:angular2/src/transform/reflection_remover/transformer.dart'
as base show ReflectionRemover;
// TODO(kegluneq): Make this a TransformerGroup and add an AggregateTransformer
// that counts the number of transformed files & primary inputs.
// If the number of primary inputs is >> transformed files, output an error
// telling the user to use $include or $exclude in their pubspec.
/// Removes the transitive dart:mirrors import from Angular 2 entrypoints.
///
/// This transformer can be used along with others as a faster alternative to
/// the single angular2 transformer.
///
/// See [the wiki][] for details.
///
/// [the wiki]: https://github.com/angular/angular/wiki/Angular-2-Dart-Transformer
class ReflectionRemover extends Transformer implements DeclaringTransformer {
final base.ReflectionRemover _impl;
ReflectionRemover._(this._impl);
/// Ctor which tells pub that this can be run as a standalone transformer.
factory ReflectionRemover.asPlugin(BarbackSettings settings) {
final options = parseBarbackSettings(settings);
final entryPoints = options.entryPoints;
if (entryPoints != null && entryPoints.isNotEmpty) {
// TODO(kegluneq): Add a goo.gl link with more info.
throw new ArgumentError.value(
entryPoints.join(', '),
ENTRY_POINT_PARAM,
"Do not use '$ENTRY_POINT_PARAM' when specifying the Angular 2 "
"reflection_remover transformer. Instead, use pub's built-in "
r"$include and $exclude parameters to filter which files are "
"processed.");
}
return new ReflectionRemover._(new base.ReflectionRemover(options));
}
/// Signal that we process all .dart files.
///
/// The underlying ReflectionRemover implementation respects the entry_points
/// transformer parameter, but this is inefficient and can be expensive for
/// large numbers of files.
///
/// Instead, use the standard, built-in $exclude and $include transformer
/// parameters to control which files this transformer runs on.
/// See [https://www.dartlang.org/tools/pub/assets-and-transformers.html] for
/// details.
@override
String get allowedExtensions => '.dart';
@override
declareOutputs(transform) => _impl.declareOutputs(transform);
@override
Future apply(transform) => _impl.apply(transform);
}