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:
parent
cda35101df
commit
c065fb1422
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue