feat(dart/transform): Remove unnecessary .ng_deps.dart files

Removes `.ng_deps.dart` files which

1. Do not register any `@Injectable` classes
2. Do not call `initReflector` on any other `.ng_deps.dart` files.

Closes #1929
This commit is contained in:
Tim Blasi 2015-05-22 14:15:18 -07:00
parent cda35101df
commit c065fb1422
15 changed files with 166 additions and 65 deletions

View File

@ -3,9 +3,13 @@ library angular2.transform.common.options;
import 'annotation_matcher.dart';
import 'mirror_mode.dart';
const ENTRY_POINT_PARAM = 'entry_points';
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_points';
/// See `optimizationPhases` below for an explanation.
const DEFAULT_OPTIMIZATION_PHASES = 5;
const CUSTOM_ANNOTATIONS_PARAM = 'custom_annotations';
const ENTRY_POINT_PARAM = 'entry_points';
const OPTIMIZATION_PHASES_PARAM = 'optimization_phases';
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_points';
/// Provides information necessary to transform an Angular2 app.
class TransformerOptions {
@ -28,20 +32,32 @@ class TransformerOptions {
/// The [AnnotationMatcher] which is used to identify angular annotations.
final AnnotationMatcher annotationMatcher;
/// The number of phases to spend optimizing output size.
/// Each additional phase adds time to the transformation but may decrease
/// final output size. There is a limit beyond which this will no longer
/// decrease size, that is, setting this to 20 may not decrease size any
/// more than setting it to 10, but you will still pay an additional
/// penalty in transformation time.
/// The "correct" number of phases varies with the structure of the app.
final int optimizationPhases;
TransformerOptions._internal(this.entryPoints, this.reflectionEntryPoints,
this.modeName, this.mirrorMode, this.initReflector,
this.annotationMatcher);
this.annotationMatcher, this.optimizationPhases);
factory TransformerOptions(List<String> entryPoints,
{List<String> reflectionEntryPoints, String modeName: 'release',
MirrorMode mirrorMode: MirrorMode.none, bool initReflector: true,
List<AnnotationDescriptor> customAnnotationDescriptors: const []}) {
List<AnnotationDescriptor> customAnnotationDescriptors: const [],
int optimizationPhases: DEFAULT_OPTIMIZATION_PHASES}) {
if (reflectionEntryPoints == null || reflectionEntryPoints.isEmpty) {
reflectionEntryPoints = entryPoints;
}
var annotationMatcher = new AnnotationMatcher()
..addAll(customAnnotationDescriptors);
optimizationPhases = optimizationPhases.isNegative ? 0 : optimizationPhases;
return new TransformerOptions._internal(entryPoints, reflectionEntryPoints,
modeName, mirrorMode, initReflector, annotationMatcher);
modeName, mirrorMode, initReflector, annotationMatcher,
optimizationPhases);
}
}

View File

@ -26,12 +26,15 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) {
mirrorMode = MirrorMode.none;
break;
}
var optimizationPhases = _readInt(config, OPTIMIZATION_PHASES_PARAM,
defaultValue: DEFAULT_OPTIMIZATION_PHASES);
return new TransformerOptions(entryPoints,
reflectionEntryPoints: reflectionEntryPoints,
modeName: settings.mode.name,
mirrorMode: mirrorMode,
initReflector: initReflector,
customAnnotationDescriptors: _readCustomAnnotations(config));
customAnnotationDescriptors: _readCustomAnnotations(config),
optimizationPhases: optimizationPhases);
}
/// Cribbed from the polymer project.
@ -56,6 +59,18 @@ List<String> _readFileList(Map config, String paramName) {
return files;
}
int _readInt(Map config, String paramName, {int defaultValue: null}) {
if (!config.containsKey(paramName)) return defaultValue;
var value = config[paramName];
if (value is String) {
value = int.parse(value);
}
if (value is! int) {
throw new ArgumentError.value(value, paramName, 'Expected an integer');
}
return value;
}
/// Parse the [CUSTOM_ANNOTATIONS_PARAM] options out of the transformer into
/// [AnnotationDescriptor]s.
List<AnnotationDescriptor> _readCustomAnnotations(Map config) {

View File

@ -22,9 +22,11 @@ class DiTransformerGroup extends TransformerGroup {
factory DiTransformerGroup(TransformerOptions options) {
var phases = [
[new ReflectionRemover(options)],
[new DirectiveProcessor(null)],
[new DirectiveLinker()]
[new DirectiveProcessor(null)]
];
phases.addAll(new List.generate(
options.optimizationPhases, (_) => [new EmptyNgDepsRemover()]));
phases.add([new DirectiveLinker()]);
return new DiTransformerGroup._(phases);
}

View File

@ -11,18 +11,56 @@ import 'package:barback/barback.dart';
import 'package:code_transformers/assets.dart';
import 'package:path/path.dart' as path;
/// Checks the `.ng_deps.dart` file represented by `entryPoint` and
/// determines whether it is necessary to the functioning of the Angular 2
/// Dart app.
///
/// An `.ng_deps.dart` file is not necessary if:
/// 1. It does not register any `@Injectable` types with the system.
/// 2. It does not import any libraries whose `.ng_deps.dart` files register
/// any `@Injectable` types with the system.
///
/// Since `@Directive` and `@Component` inherit from `@Injectable`, we know
/// we will not miss processing any classes annotated with those tags.
Future<bool> isNecessary(AssetReader reader, AssetId entryPoint) async {
var parser = new Parser(reader);
NgDeps ngDeps = await parser.parse(entryPoint);
if (ngDeps.registeredTypes.isNotEmpty) return true;
// We do not register any @Injectables, do we call any dependencies?
var linkedDepsMap =
await _processNgImports(reader, entryPoint, _getSortedDeps(ngDeps));
return linkedDepsMap.isNotEmpty;
}
/// Modifies the `.ng_deps.dart` file represented by `entryPoint` to call its
/// dependencies associated `initReflector` methods.
///
/// For example, if entry_point.ng_deps.dart imports dependency.dart, this
/// will check if dependency.ng_deps.dart exists. If it does, we add:
///
/// ```
/// import 'dependency.ng_deps.dart' as i0;
/// ...
/// void setupReflection(reflector) {
/// ...
/// i0.initReflector(reflector);
/// }
/// ```
Future<String> linkNgDeps(AssetReader reader, AssetId entryPoint) async {
var parser = new Parser(reader);
NgDeps ngDeps = await parser.parse(entryPoint);
if (ngDeps == null) return null;
var allDeps = <UriBasedDirective>[]
..addAll(ngDeps.imports)
..addAll(ngDeps.exports)
..sort((a, b) => a.end.compareTo(b.end));
var allDeps = _getSortedDeps(ngDeps);
var linkedDepsMap = await _processNgImports(reader, entryPoint, allDeps);
if (linkedDepsMap.isEmpty) return ngDeps.code;
if (linkedDepsMap.isEmpty) {
// We are not calling `initReflector` on any other libraries.
return ngDeps.code;
}
var importBuf = new StringBuffer();
var declarationBuf = new StringBuffer();
@ -49,6 +87,15 @@ Future<String> linkNgDeps(AssetReader reader, AssetId entryPoint) async {
'${code.substring(declarationSeamIdx)}';
}
/// All `import`s and `export`s in `ngDeps` sorted by order of appearance in
/// the file.
List<UriBasedDirective> _getSortedDeps(NgDeps ngDeps) {
return <UriBasedDirective>[]
..addAll(ngDeps.imports)
..addAll(ngDeps.exports)
..sort((a, b) => a.end.compareTo(b.end));
}
String _toDepsUri(String importUri) =>
'${path.withoutExtension(importUri)}${DEPS_EXTENSION}';
@ -67,10 +114,8 @@ Future<Map<UriBasedDirective, String>> _processNgImports(AssetReader reader,
.where(_isNotDartDirective)
.map((UriBasedDirective directive) {
var ngDepsUri = _toDepsUri(stringLiteralToString(directive.uri));
var ngDepsAsset = uriToAssetId(entryPoint, ngDepsUri, logger,
null /*
span */
);
var spanArg = null;
var ngDepsAsset = uriToAssetId(entryPoint, ngDepsUri, logger, spanArg);
if (ngDepsAsset == entryPoint) return nullFuture;
return reader.hasInput(ngDepsAsset).then((hasInput) {
if (hasInput) {

View File

@ -29,8 +29,10 @@ class DirectiveLinker extends Transformer {
var assetId = transform.primaryInput.id;
var assetPath = assetId.path;
var transformedCode = await linkNgDeps(reader, assetId);
var formattedCode = formatter.format(transformedCode, uri: assetPath);
transform.addOutput(new Asset.fromString(assetId, formattedCode));
if (transformedCode != null) {
var formattedCode = formatter.format(transformedCode, uri: assetPath);
transform.addOutput(new Asset.fromString(assetId, formattedCode));
}
} catch (ex, stackTrace) {
log.logger.error('Linking ng directives failed.\n'
'Exception: $ex\n'
@ -39,3 +41,29 @@ class DirectiveLinker extends Transformer {
return null;
}
}
/// Transformer responsible for removing unnecessary `.ng_deps.dart` files
/// created by {@link DirectiveProcessor}.
class EmptyNgDepsRemover extends Transformer {
EmptyNgDepsRemover();
@override
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
@override
Future apply(Transform transform) async {
log.init(transform);
try {
var reader = new AssetReader.fromTransform(transform);
if (!(await isNecessary(reader, transform.primaryInput.id))) {
transform.consumePrimary();
}
} catch (ex, stackTrace) {
log.logger.error('Removing unnecessary ng deps failed.\n'
'Exception: $ex\n'
'Stack Trace: $stackTrace');
}
return null;
}
}

View File

@ -31,6 +31,12 @@ Future<String> createNgDeps(AssetReader reader, AssetId assetId,
writer, assetId, new XhrImpl(reader, assetId), annotationMatcher);
var code = await reader.readAsString(assetId);
parseCompilationUnit(code, name: assetId.path).accept(visitor);
// If this library does not define an `@Injectable` and it does not import
// any libaries that could, then we do not need to generate a `.ng_deps
// .dart` file for it.
if (!visitor._foundNgInjectable && !visitor._usesNonLangLibs) return null;
return await writer.asyncToString();
}
@ -38,8 +44,13 @@ Future<String> createNgDeps(AssetReader reader, AssetId assetId,
/// associated .ng_deps.dart file.
class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
final AsyncStringWriter writer;
bool _foundNgDirectives = false;
bool _wroteImport = false;
/// Whether an Angular 2 `Injectable` has been found.
bool _foundNgInjectable = false;
/// Whether this library `imports` or `exports` any non-'dart:' libraries.
bool _usesNonLangLibs = false;
/// Whether we have written an import of base file
/// (the file we are processing).
bool _wroteBaseLibImport = false;
final ToSourceVisitor _copyVisitor;
final FactoryTransformVisitor _factoryVisitor;
final ParameterTransformVisitor _paramsVisitor;
@ -79,20 +90,27 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
/// Write the import to the file the .ng_deps.dart file is based on if it
/// has not yet been written.
void _maybeWriteImport() {
if (_wroteImport) return;
_wroteImport = true;
if (_wroteBaseLibImport) return;
_wroteBaseLibImport = true;
writer.print('''import '${path.basename(assetId.path)}';''');
}
void _updateUsesNonLangLibs(UriBasedDirective directive) {
_usesNonLangLibs = _usesNonLangLibs ||
!stringLiteralToString(directive.uri).startsWith('dart:');
}
@override
Object visitImportDirective(ImportDirective node) {
_maybeWriteImport();
_updateUsesNonLangLibs(node);
return node.accept(_copyVisitor);
}
@override
Object visitExportDirective(ExportDirective node) {
_maybeWriteImport();
_updateUsesNonLangLibs(node);
return node.accept(_copyVisitor);
}
@ -104,7 +122,7 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
}
void _closeFunctionWrapper() {
if (_foundNgDirectives) {
if (_foundNgInjectable) {
writer.print(';');
}
writer.print('}');
@ -153,10 +171,10 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
var ctor = _getCtor(node);
if (!_foundNgDirectives) {
if (!_foundNgInjectable) {
// The receiver for cascaded calls.
writer.print(REFLECTOR_VAR_NAME);
_foundNgDirectives = true;
_foundNgInjectable = true;
}
writer.print('..registerType(');
node.name.accept(this);

View File

@ -24,11 +24,15 @@ class AngularTransformerGroup extends TransformerGroup {
factory AngularTransformerGroup(TransformerOptions options) {
var phases = [
[new ReflectionRemover(options)],
[new DirectiveProcessor(options)],
[new DirectiveProcessor(options)]
];
phases.addAll(new List.generate(
options.optimizationPhases, (_) => [new EmptyNgDepsRemover()]));
phases.addAll([
[new DirectiveLinker(), new DirectiveMetadataExtractor()],
[new BindGenerator(options)],
[new TemplateCompiler(options)]
];
]);
return new AngularTransformerGroup._(phases);
}

View File

@ -50,13 +50,18 @@ void _testNgDeps(String name, String inputPath,
reader.addAsset(assetId, await reader.readAsString(inputId));
inputId = assetId;
}
var annotationMatcher = new AnnotationMatcher()..addAll(customDescriptors);
var output = formatter
.format(await createNgDeps(reader, inputId, annotationMatcher));
var expectedPath = path.join(path.dirname(inputPath), 'expected',
path.basename(inputPath).replaceFirst('.dart', '.ng_deps.dart'));
var expectedId = _assetIdForPath(expectedPath);
expect(output).toEqual(await reader.readAsString(expectedId));
var annotationMatcher = new AnnotationMatcher()..addAll(customDescriptors);
var output = await createNgDeps(reader, inputId, annotationMatcher);
if (output == null) {
expect(await reader.hasInput(expectedId)).toBeFalse();
} else {
expect(formatter.format(output))
.toEqual(await reader.readAsString(expectedId));
}
});
}

View File

@ -1,9 +0,0 @@
library dinner.bad_soup.ng_deps.dart;
import 'bad_soup.dart';
var _visited = false;
void initReflector(reflector) {
if (_visited) return;
_visited = true;
}

View File

@ -2,10 +2,7 @@ library bar.ng_deps.dart;
import 'bar.dart';
import 'package:angular2/src/core/annotations_impl/annotations.dart';
import 'package:angular2/src/core/annotations_impl/annotations.ng_deps.dart'
as i0;
import 'foo.dart';
import 'foo.ng_deps.dart' as i1;
var _visited = false;
void initReflector(reflector) {
@ -18,6 +15,4 @@ void initReflector(reflector) {
'annotations':
const [const Component(componentServices: const [MyContext])]
});
i0.initReflector(reflector);
i1.initReflector(reflector);
}

View File

@ -2,8 +2,6 @@ library bar.ng_deps.dart;
import 'bar.dart';
import 'package:angular2/src/core/annotations_impl/annotations.dart';
import 'package:angular2/src/core/annotations_impl/annotations.ng_deps.dart'
as i0;
var _visited = false;
void initReflector(reflector) {
@ -15,5 +13,4 @@ void initReflector(reflector) {
'parameters': const [],
'annotations': const [const Component(selector: '[soup]')]
});
i0.initReflector(reflector);
}

View File

@ -2,16 +2,14 @@ library web_foo.ng_deps.dart;
import 'index.dart';
import 'package:angular2/src/core/application.dart';
import 'package:angular2/src/core/application.ng_deps.dart' as i0;
import 'package:angular2/src/reflection/reflection.dart';
import 'index.ng_deps.dart' as ngStaticInit0;
import 'bar.dart';
import 'bar.ng_deps.dart' as i1;
import 'bar.ng_deps.dart' as i0;
var _visited = false;
void initReflector(reflector) {
if (_visited) return;
_visited = true;
i0.initReflector(reflector);
i1.initReflector(reflector);
}

View File

@ -2,8 +2,6 @@ library bar.ng_deps.dart;
import 'bar.dart';
import 'package:angular2/src/core/annotations_impl/annotations.dart';
import 'package:angular2/src/core/annotations_impl/annotations.ng_deps.dart'
as i0;
var _visited = false;
void initReflector(reflector) {
@ -15,5 +13,4 @@ void initReflector(reflector) {
'parameters': const [],
'annotations': const [const Component(selector: '[soup]')]
});
i0.initReflector(reflector);
}

View File

@ -2,10 +2,7 @@ library bar.ng_deps.dart;
import 'bar.dart';
import 'package:angular2/src/core/annotations_impl/annotations.dart';
import 'package:angular2/src/core/annotations_impl/annotations.ng_deps.dart'
as i0;
import 'package:angular2/src/core/annotations_impl/view.dart';
import 'package:angular2/src/core/annotations_impl/view.ng_deps.dart' as i1;
var _visited = false;
void initReflector(reflector) {
@ -20,6 +17,4 @@ void initReflector(reflector) {
const View(template: 'Salad')
]
});
i0.initReflector(reflector);
i1.initReflector(reflector);
}

View File

@ -2,10 +2,7 @@ library bar.ng_deps.dart;
import 'bar.dart';
import 'package:angular2/src/core/annotations_impl/annotations.dart';
import 'package:angular2/src/core/annotations_impl/annotations.ng_deps.dart'
as i0;
import 'foo.dart' as prefix;
import 'foo.ng_deps.dart' as i1;
var _visited = false;
void initReflector(reflector) {
@ -18,6 +15,4 @@ void initReflector(reflector) {
'parameters': const [const [prefix.MyContext], const [String]],
'annotations': const [const Component(selector: 'soup')]
});
i0.initReflector(reflector);
i1.initReflector(reflector);
}