Skip to content

Commit c065fb1

Browse files
author
Tim Blasi
committed
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 angular#1929
1 parent cda3510 commit c065fb1

15 files changed

Lines changed: 165 additions & 64 deletions

File tree

modules/angular2/src/transform/common/options.dart

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ library angular2.transform.common.options;
33
import 'annotation_matcher.dart';
44
import 'mirror_mode.dart';
55

6+
/// See `optimizationPhases` below for an explanation.
7+
const DEFAULT_OPTIMIZATION_PHASES = 5;
8+
9+
const CUSTOM_ANNOTATIONS_PARAM = 'custom_annotations';
610
const ENTRY_POINT_PARAM = 'entry_points';
11+
const OPTIMIZATION_PHASES_PARAM = 'optimization_phases';
712
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_points';
8-
const CUSTOM_ANNOTATIONS_PARAM = 'custom_annotations';
913

1014
/// Provides information necessary to transform an Angular2 app.
1115
class TransformerOptions {
@@ -28,20 +32,32 @@ class TransformerOptions {
2832
/// The [AnnotationMatcher] which is used to identify angular annotations.
2933
final AnnotationMatcher annotationMatcher;
3034

35+
/// The number of phases to spend optimizing output size.
36+
/// Each additional phase adds time to the transformation but may decrease
37+
/// final output size. There is a limit beyond which this will no longer
38+
/// decrease size, that is, setting this to 20 may not decrease size any
39+
/// more than setting it to 10, but you will still pay an additional
40+
/// penalty in transformation time.
41+
/// The "correct" number of phases varies with the structure of the app.
42+
final int optimizationPhases;
43+
3144
TransformerOptions._internal(this.entryPoints, this.reflectionEntryPoints,
3245
this.modeName, this.mirrorMode, this.initReflector,
33-
this.annotationMatcher);
46+
this.annotationMatcher, this.optimizationPhases);
3447

3548
factory TransformerOptions(List<String> entryPoints,
3649
{List<String> reflectionEntryPoints, String modeName: 'release',
3750
MirrorMode mirrorMode: MirrorMode.none, bool initReflector: true,
38-
List<AnnotationDescriptor> customAnnotationDescriptors: const []}) {
51+
List<AnnotationDescriptor> customAnnotationDescriptors: const [],
52+
int optimizationPhases: DEFAULT_OPTIMIZATION_PHASES}) {
3953
if (reflectionEntryPoints == null || reflectionEntryPoints.isEmpty) {
4054
reflectionEntryPoints = entryPoints;
4155
}
4256
var annotationMatcher = new AnnotationMatcher()
4357
..addAll(customAnnotationDescriptors);
58+
optimizationPhases = optimizationPhases.isNegative ? 0 : optimizationPhases;
4459
return new TransformerOptions._internal(entryPoints, reflectionEntryPoints,
45-
modeName, mirrorMode, initReflector, annotationMatcher);
60+
modeName, mirrorMode, initReflector, annotationMatcher,
61+
optimizationPhases);
4662
}
4763
}

modules/angular2/src/transform/common/options_reader.dart

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) {
2626
mirrorMode = MirrorMode.none;
2727
break;
2828
}
29+
var optimizationPhases = _readInt(config, OPTIMIZATION_PHASES_PARAM,
30+
defaultValue: DEFAULT_OPTIMIZATION_PHASES);
2931
return new TransformerOptions(entryPoints,
3032
reflectionEntryPoints: reflectionEntryPoints,
3133
modeName: settings.mode.name,
3234
mirrorMode: mirrorMode,
3335
initReflector: initReflector,
34-
customAnnotationDescriptors: _readCustomAnnotations(config));
36+
customAnnotationDescriptors: _readCustomAnnotations(config),
37+
optimizationPhases: optimizationPhases);
3538
}
3639

3740
/// Cribbed from the polymer project.
@@ -56,6 +59,18 @@ List<String> _readFileList(Map config, String paramName) {
5659
return files;
5760
}
5861

62+
int _readInt(Map config, String paramName, {int defaultValue: null}) {
63+
if (!config.containsKey(paramName)) return defaultValue;
64+
var value = config[paramName];
65+
if (value is String) {
66+
value = int.parse(value);
67+
}
68+
if (value is! int) {
69+
throw new ArgumentError.value(value, paramName, 'Expected an integer');
70+
}
71+
return value;
72+
}
73+
5974
/// Parse the [CUSTOM_ANNOTATIONS_PARAM] options out of the transformer into
6075
/// [AnnotationDescriptor]s.
6176
List<AnnotationDescriptor> _readCustomAnnotations(Map config) {

modules/angular2/src/transform/di_transformer.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ class DiTransformerGroup extends TransformerGroup {
2222
factory DiTransformerGroup(TransformerOptions options) {
2323
var phases = [
2424
[new ReflectionRemover(options)],
25-
[new DirectiveProcessor(null)],
26-
[new DirectiveLinker()]
25+
[new DirectiveProcessor(null)]
2726
];
27+
phases.addAll(new List.generate(
28+
options.optimizationPhases, (_) => [new EmptyNgDepsRemover()]));
29+
phases.add([new DirectiveLinker()]);
2830
return new DiTransformerGroup._(phases);
2931
}
3032

modules/angular2/src/transform/directive_linker/linker.dart

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,56 @@ import 'package:barback/barback.dart';
1111
import 'package:code_transformers/assets.dart';
1212
import 'package:path/path.dart' as path;
1313

14+
/// Checks the `.ng_deps.dart` file represented by `entryPoint` and
15+
/// determines whether it is necessary to the functioning of the Angular 2
16+
/// Dart app.
17+
///
18+
/// An `.ng_deps.dart` file is not necessary if:
19+
/// 1. It does not register any `@Injectable` types with the system.
20+
/// 2. It does not import any libraries whose `.ng_deps.dart` files register
21+
/// any `@Injectable` types with the system.
22+
///
23+
/// Since `@Directive` and `@Component` inherit from `@Injectable`, we know
24+
/// we will not miss processing any classes annotated with those tags.
25+
Future<bool> isNecessary(AssetReader reader, AssetId entryPoint) async {
26+
var parser = new Parser(reader);
27+
NgDeps ngDeps = await parser.parse(entryPoint);
28+
29+
if (ngDeps.registeredTypes.isNotEmpty) return true;
30+
31+
// We do not register any @Injectables, do we call any dependencies?
32+
var linkedDepsMap =
33+
await _processNgImports(reader, entryPoint, _getSortedDeps(ngDeps));
34+
return linkedDepsMap.isNotEmpty;
35+
}
36+
37+
/// Modifies the `.ng_deps.dart` file represented by `entryPoint` to call its
38+
/// dependencies associated `initReflector` methods.
39+
///
40+
/// For example, if entry_point.ng_deps.dart imports dependency.dart, this
41+
/// will check if dependency.ng_deps.dart exists. If it does, we add:
42+
///
43+
/// ```
44+
/// import 'dependency.ng_deps.dart' as i0;
45+
/// ...
46+
/// void setupReflection(reflector) {
47+
/// ...
48+
/// i0.initReflector(reflector);
49+
/// }
50+
/// ```
1451
Future<String> linkNgDeps(AssetReader reader, AssetId entryPoint) async {
1552
var parser = new Parser(reader);
1653
NgDeps ngDeps = await parser.parse(entryPoint);
54+
1755
if (ngDeps == null) return null;
1856

19-
var allDeps = <UriBasedDirective>[]
20-
..addAll(ngDeps.imports)
21-
..addAll(ngDeps.exports)
22-
..sort((a, b) => a.end.compareTo(b.end));
57+
var allDeps = _getSortedDeps(ngDeps);
2358
var linkedDepsMap = await _processNgImports(reader, entryPoint, allDeps);
2459

25-
if (linkedDepsMap.isEmpty) return ngDeps.code;
60+
if (linkedDepsMap.isEmpty) {
61+
// We are not calling `initReflector` on any other libraries.
62+
return ngDeps.code;
63+
}
2664

2765
var importBuf = new StringBuffer();
2866
var declarationBuf = new StringBuffer();
@@ -49,6 +87,15 @@ Future<String> linkNgDeps(AssetReader reader, AssetId entryPoint) async {
4987
'${code.substring(declarationSeamIdx)}';
5088
}
5189

90+
/// All `import`s and `export`s in `ngDeps` sorted by order of appearance in
91+
/// the file.
92+
List<UriBasedDirective> _getSortedDeps(NgDeps ngDeps) {
93+
return <UriBasedDirective>[]
94+
..addAll(ngDeps.imports)
95+
..addAll(ngDeps.exports)
96+
..sort((a, b) => a.end.compareTo(b.end));
97+
}
98+
5299
String _toDepsUri(String importUri) =>
53100
'${path.withoutExtension(importUri)}${DEPS_EXTENSION}';
54101

@@ -67,10 +114,8 @@ Future<Map<UriBasedDirective, String>> _processNgImports(AssetReader reader,
67114
.where(_isNotDartDirective)
68115
.map((UriBasedDirective directive) {
69116
var ngDepsUri = _toDepsUri(stringLiteralToString(directive.uri));
70-
var ngDepsAsset = uriToAssetId(entryPoint, ngDepsUri, logger,
71-
null /*
72-
span */
73-
);
117+
var spanArg = null;
118+
var ngDepsAsset = uriToAssetId(entryPoint, ngDepsUri, logger, spanArg);
74119
if (ngDepsAsset == entryPoint) return nullFuture;
75120
return reader.hasInput(ngDepsAsset).then((hasInput) {
76121
if (hasInput) {

modules/angular2/src/transform/directive_linker/transformer.dart

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ class DirectiveLinker extends Transformer {
2929
var assetId = transform.primaryInput.id;
3030
var assetPath = assetId.path;
3131
var transformedCode = await linkNgDeps(reader, assetId);
32-
var formattedCode = formatter.format(transformedCode, uri: assetPath);
33-
transform.addOutput(new Asset.fromString(assetId, formattedCode));
32+
if (transformedCode != null) {
33+
var formattedCode = formatter.format(transformedCode, uri: assetPath);
34+
transform.addOutput(new Asset.fromString(assetId, formattedCode));
35+
}
3436
} catch (ex, stackTrace) {
3537
log.logger.error('Linking ng directives failed.\n'
3638
'Exception: $ex\n'
@@ -39,3 +41,29 @@ class DirectiveLinker extends Transformer {
3941
return null;
4042
}
4143
}
44+
45+
/// Transformer responsible for removing unnecessary `.ng_deps.dart` files
46+
/// created by {@link DirectiveProcessor}.
47+
class EmptyNgDepsRemover extends Transformer {
48+
EmptyNgDepsRemover();
49+
50+
@override
51+
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
52+
53+
@override
54+
Future apply(Transform transform) async {
55+
log.init(transform);
56+
57+
try {
58+
var reader = new AssetReader.fromTransform(transform);
59+
if (!(await isNecessary(reader, transform.primaryInput.id))) {
60+
transform.consumePrimary();
61+
}
62+
} catch (ex, stackTrace) {
63+
log.logger.error('Removing unnecessary ng deps failed.\n'
64+
'Exception: $ex\n'
65+
'Stack Trace: $stackTrace');
66+
}
67+
return null;
68+
}
69+
}

modules/angular2/src/transform/directive_processor/rewriter.dart

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,26 @@ Future<String> createNgDeps(AssetReader reader, AssetId assetId,
3131
writer, assetId, new XhrImpl(reader, assetId), annotationMatcher);
3232
var code = await reader.readAsString(assetId);
3333
parseCompilationUnit(code, name: assetId.path).accept(visitor);
34+
35+
// If this library does not define an `@Injectable` and it does not import
36+
// any libaries that could, then we do not need to generate a `.ng_deps
37+
// .dart` file for it.
38+
if (!visitor._foundNgInjectable && !visitor._usesNonLangLibs) return null;
39+
3440
return await writer.asyncToString();
3541
}
3642

3743
/// Visitor responsible for processing [CompilationUnit] and creating an
3844
/// associated .ng_deps.dart file.
3945
class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
4046
final AsyncStringWriter writer;
41-
bool _foundNgDirectives = false;
42-
bool _wroteImport = false;
47+
/// Whether an Angular 2 `Injectable` has been found.
48+
bool _foundNgInjectable = false;
49+
/// Whether this library `imports` or `exports` any non-'dart:' libraries.
50+
bool _usesNonLangLibs = false;
51+
/// Whether we have written an import of base file
52+
/// (the file we are processing).
53+
bool _wroteBaseLibImport = false;
4354
final ToSourceVisitor _copyVisitor;
4455
final FactoryTransformVisitor _factoryVisitor;
4556
final ParameterTransformVisitor _paramsVisitor;
@@ -79,20 +90,27 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
7990
/// Write the import to the file the .ng_deps.dart file is based on if it
8091
/// has not yet been written.
8192
void _maybeWriteImport() {
82-
if (_wroteImport) return;
83-
_wroteImport = true;
93+
if (_wroteBaseLibImport) return;
94+
_wroteBaseLibImport = true;
8495
writer.print('''import '${path.basename(assetId.path)}';''');
8596
}
8697

98+
void _updateUsesNonLangLibs(UriBasedDirective directive) {
99+
_usesNonLangLibs = _usesNonLangLibs ||
100+
!stringLiteralToString(directive.uri).startsWith('dart:');
101+
}
102+
87103
@override
88104
Object visitImportDirective(ImportDirective node) {
89105
_maybeWriteImport();
106+
_updateUsesNonLangLibs(node);
90107
return node.accept(_copyVisitor);
91108
}
92109

93110
@override
94111
Object visitExportDirective(ExportDirective node) {
95112
_maybeWriteImport();
113+
_updateUsesNonLangLibs(node);
96114
return node.accept(_copyVisitor);
97115
}
98116

@@ -104,7 +122,7 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
104122
}
105123

106124
void _closeFunctionWrapper() {
107-
if (_foundNgDirectives) {
125+
if (_foundNgInjectable) {
108126
writer.print(';');
109127
}
110128
writer.print('}');
@@ -153,10 +171,10 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor<Object> {
153171

154172
var ctor = _getCtor(node);
155173

156-
if (!_foundNgDirectives) {
174+
if (!_foundNgInjectable) {
157175
// The receiver for cascaded calls.
158176
writer.print(REFLECTOR_VAR_NAME);
159-
_foundNgDirectives = true;
177+
_foundNgInjectable = true;
160178
}
161179
writer.print('..registerType(');
162180
node.name.accept(this);

modules/angular2/src/transform/transformer.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,15 @@ class AngularTransformerGroup extends TransformerGroup {
2424
factory AngularTransformerGroup(TransformerOptions options) {
2525
var phases = [
2626
[new ReflectionRemover(options)],
27-
[new DirectiveProcessor(options)],
27+
[new DirectiveProcessor(options)]
28+
];
29+
phases.addAll(new List.generate(
30+
options.optimizationPhases, (_) => [new EmptyNgDepsRemover()]));
31+
phases.addAll([
2832
[new DirectiveLinker(), new DirectiveMetadataExtractor()],
2933
[new BindGenerator(options)],
3034
[new TemplateCompiler(options)]
31-
];
35+
]);
3236
return new AngularTransformerGroup._(phases);
3337
}
3438

modules/angular2/test/transform/directive_processor/all_tests.dart

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,18 @@ void _testNgDeps(String name, String inputPath,
5050
reader.addAsset(assetId, await reader.readAsString(inputId));
5151
inputId = assetId;
5252
}
53-
var annotationMatcher = new AnnotationMatcher()..addAll(customDescriptors);
54-
var output = formatter
55-
.format(await createNgDeps(reader, inputId, annotationMatcher));
5653
var expectedPath = path.join(path.dirname(inputPath), 'expected',
5754
path.basename(inputPath).replaceFirst('.dart', '.ng_deps.dart'));
5855
var expectedId = _assetIdForPath(expectedPath);
59-
expect(output).toEqual(await reader.readAsString(expectedId));
56+
57+
var annotationMatcher = new AnnotationMatcher()..addAll(customDescriptors);
58+
var output = await createNgDeps(reader, inputId, annotationMatcher);
59+
if (output == null) {
60+
expect(await reader.hasInput(expectedId)).toBeFalse();
61+
} else {
62+
expect(formatter.format(output))
63+
.toEqual(await reader.readAsString(expectedId));
64+
}
6065
});
6166
}
6267

modules/angular2/test/transform/directive_processor/custom_metadata/expected/bad_soup.ng_deps.dart

Lines changed: 0 additions & 9 deletions
This file was deleted.

modules/angular2/test/transform/integration/list_of_types_files/expected/bar.ng_deps.dart

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ library bar.ng_deps.dart;
22

33
import 'bar.dart';
44
import 'package:angular2/src/core/annotations_impl/annotations.dart';
5-
import 'package:angular2/src/core/annotations_impl/annotations.ng_deps.dart'
6-
as i0;
75
import 'foo.dart';
8-
import 'foo.ng_deps.dart' as i1;
96

107
var _visited = false;
118
void initReflector(reflector) {
@@ -18,6 +15,4 @@ void initReflector(reflector) {
1815
'annotations':
1916
const [const Component(componentServices: const [MyContext])]
2017
});
21-
i0.initReflector(reflector);
22-
i1.initReflector(reflector);
2318
}

0 commit comments

Comments
 (0)