feat(dart/transform): Use the best available Change Detectors
Enable pregenerated (for Dart) and JIT (for Js) change detectors when possible. Previously we would always use `DynamicChangeDetector`s, but these cause megamorphic calls and are therefore much slower. Closes #502
This commit is contained in:
@ -88,20 +88,20 @@ class _DirectiveMetadataVisitor extends Object
|
||||
}
|
||||
|
||||
DirectiveMetadata get meta => DirectiveMetadata.create(
|
||||
type: _type,
|
||||
selector: _selector,
|
||||
compileChildren: _compileChildren,
|
||||
properties: _properties,
|
||||
host: _host,
|
||||
readAttributes: _readAttributes,
|
||||
exportAs: _exportAs,
|
||||
callOnDestroy: _callOnDestroy,
|
||||
callOnChange: _callOnChange,
|
||||
callOnCheck: _callOnCheck,
|
||||
callOnInit: _callOnInit,
|
||||
callOnAllChangesDone: _callOnAllChangesDone,
|
||||
changeDetection: _changeDetection,
|
||||
events: _events);
|
||||
type: _type,
|
||||
selector: _selector,
|
||||
compileChildren: _compileChildren,
|
||||
properties: _properties,
|
||||
host: _host,
|
||||
readAttributes: _readAttributes,
|
||||
exportAs: _exportAs,
|
||||
callOnDestroy: _callOnDestroy,
|
||||
callOnChange: _callOnChange,
|
||||
callOnCheck: _callOnCheck,
|
||||
callOnInit: _callOnInit,
|
||||
callOnAllChangesDone: _callOnAllChangesDone,
|
||||
changeDetection: _changeDetection,
|
||||
events: _events);
|
||||
|
||||
@override
|
||||
Object visitAnnotation(Annotation node) {
|
||||
|
@ -8,6 +8,9 @@ const DEFAULT_OPTIMIZATION_PHASES = 5;
|
||||
|
||||
const CUSTOM_ANNOTATIONS_PARAM = 'custom_annotations';
|
||||
const ENTRY_POINT_PARAM = 'entry_points';
|
||||
const GENERATE_CHANGE_DETECTORS_PARAM = 'generate_change_detectors';
|
||||
const INIT_REFLECTOR_PARAM = 'init_reflector';
|
||||
const MIRROR_MODE_PARAM = 'mirror_mode';
|
||||
const OPTIMIZATION_PHASES_PARAM = 'optimization_phases';
|
||||
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_points';
|
||||
|
||||
@ -32,6 +35,9 @@ class TransformerOptions {
|
||||
/// The [AnnotationMatcher] which is used to identify angular annotations.
|
||||
final AnnotationMatcher annotationMatcher;
|
||||
|
||||
/// Whether to create change detector classes for discovered `@View`s.
|
||||
final bool generateChangeDetectors;
|
||||
|
||||
/// 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
|
||||
@ -43,13 +49,15 @@ class TransformerOptions {
|
||||
|
||||
TransformerOptions._internal(this.entryPoints, this.reflectionEntryPoints,
|
||||
this.modeName, this.mirrorMode, this.initReflector,
|
||||
this.annotationMatcher, this.optimizationPhases);
|
||||
this.annotationMatcher, this.optimizationPhases,
|
||||
this.generateChangeDetectors);
|
||||
|
||||
factory TransformerOptions(List<String> entryPoints,
|
||||
{List<String> reflectionEntryPoints, String modeName: 'release',
|
||||
MirrorMode mirrorMode: MirrorMode.none, bool initReflector: true,
|
||||
List<AnnotationDescriptor> customAnnotationDescriptors: const [],
|
||||
int optimizationPhases: DEFAULT_OPTIMIZATION_PHASES}) {
|
||||
int optimizationPhases: DEFAULT_OPTIMIZATION_PHASES,
|
||||
bool generateChangeDetectors: true}) {
|
||||
if (reflectionEntryPoints == null || reflectionEntryPoints.isEmpty) {
|
||||
reflectionEntryPoints = entryPoints;
|
||||
}
|
||||
@ -58,6 +66,6 @@ class TransformerOptions {
|
||||
optimizationPhases = optimizationPhases.isNegative ? 0 : optimizationPhases;
|
||||
return new TransformerOptions._internal(entryPoints, reflectionEntryPoints,
|
||||
modeName, mirrorMode, initReflector, annotationMatcher,
|
||||
optimizationPhases);
|
||||
optimizationPhases, generateChangeDetectors);
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,12 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) {
|
||||
var entryPoints = _readFileList(config, ENTRY_POINT_PARAM);
|
||||
var reflectionEntryPoints =
|
||||
_readFileList(config, REFLECTION_ENTRY_POINT_PARAM);
|
||||
var initReflector = !config.containsKey('init_reflector') ||
|
||||
config['init_reflector'] != false;
|
||||
var initReflector =
|
||||
_readBool(config, INIT_REFLECTOR_PARAM, defaultValue: true);
|
||||
var generateChangeDetectors =
|
||||
_readBool(config, GENERATE_CHANGE_DETECTORS_PARAM, defaultValue: true);
|
||||
String mirrorModeVal =
|
||||
config.containsKey('mirror_mode') ? config['mirror_mode'] : '';
|
||||
config.containsKey(MIRROR_MODE_PARAM) ? config[MIRROR_MODE_PARAM] : '';
|
||||
var mirrorMode = MirrorMode.none;
|
||||
switch (mirrorModeVal) {
|
||||
case 'debug':
|
||||
@ -34,7 +36,14 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) {
|
||||
mirrorMode: mirrorMode,
|
||||
initReflector: initReflector,
|
||||
customAnnotationDescriptors: _readCustomAnnotations(config),
|
||||
optimizationPhases: optimizationPhases);
|
||||
optimizationPhases: optimizationPhases,
|
||||
generateChangeDetectors: generateChangeDetectors);
|
||||
}
|
||||
|
||||
bool _readBool(Map config, String paramName, {bool defaultValue}) {
|
||||
return config.containsKey(paramName)
|
||||
? config[paramName] != false
|
||||
: defaultValue;
|
||||
}
|
||||
|
||||
/// Cribbed from the polymer project.
|
||||
|
@ -14,15 +14,31 @@ import 'package:angular2/src/facade/lang.dart' show BaseException;
|
||||
/// This code should be kept in sync with the `ChangeDetectorJITGenerator`
|
||||
/// class. If you make updates here, please make equivalent changes there.
|
||||
class Codegen {
|
||||
/// Stores the generated class definitions.
|
||||
final StringBuffer _buf = new StringBuffer();
|
||||
/// Stores all generated initialization code.
|
||||
final StringBuffer _initBuf = new StringBuffer();
|
||||
/// The names of already generated classes.
|
||||
final Set<String> _names = new Set<String>();
|
||||
|
||||
/// Generates a change detector class with name `changeDetectorTypeName`
|
||||
/// which is used to detect changes in Objects of type `typeName`.
|
||||
/// Generates a change detector class with name `changeDetectorTypeName`,
|
||||
/// which must not conflict with other generated classes in the same
|
||||
/// `.ng_deps.dart` file. The change detector is used to detect changes in
|
||||
/// Objects of type `typeName`.
|
||||
void generate(String typeName, String changeDetectorTypeName,
|
||||
ChangeDetectorDefinition def) {
|
||||
new _CodegenState(typeName, changeDetectorTypeName, def)._writeToBuf(_buf);
|
||||
if (_names.contains(changeDetectorTypeName)) {
|
||||
throw new BaseException(
|
||||
'Change detector named "${changeDetectorTypeName}" for ${typeName} '
|
||||
'conflicts with an earlier generated change detector class.');
|
||||
}
|
||||
_names.add(changeDetectorTypeName);
|
||||
new _CodegenState(typeName, changeDetectorTypeName, def)
|
||||
.._writeToBuf(_buf)
|
||||
.._writeInitToBuf(_initBuf);
|
||||
}
|
||||
|
||||
/// Gets all imports necessary for the generated code.
|
||||
String get imports {
|
||||
return _buf.isEmpty
|
||||
? ''
|
||||
@ -31,13 +47,27 @@ class Codegen {
|
||||
|
||||
bool get isEmpty => _buf.isEmpty;
|
||||
|
||||
/// Gets the initilization code that registers the generated classes with
|
||||
/// the Angular 2 change detection system.
|
||||
String get initialize => '$_initBuf';
|
||||
|
||||
@override
|
||||
String toString() => '$_buf';
|
||||
}
|
||||
|
||||
/// The state needed to generate a change detector for a single `Component`.
|
||||
class _CodegenState {
|
||||
/// The `id` of the `ChangeDetectorDefinition` we are generating this class
|
||||
/// for.
|
||||
final String _changeDetectorDefId;
|
||||
|
||||
/// The name of the `Type` this change detector is generated for. For example,
|
||||
/// this is `MyComponent` if the generated class will detect changes in
|
||||
/// `MyComponent` objects.
|
||||
final String _contextTypeName;
|
||||
|
||||
/// The name of the generated change detector class. This is an implementation
|
||||
/// detail and should not be visible to users.
|
||||
final String _changeDetectorTypeName;
|
||||
final String _changeDetectionMode;
|
||||
final List<ProtoRecord> _records;
|
||||
@ -47,9 +77,9 @@ class _CodegenState {
|
||||
final List<String> _fieldNames;
|
||||
final List<String> _pipeNames;
|
||||
|
||||
_CodegenState._(this._contextTypeName, this._changeDetectorTypeName,
|
||||
String changeDetectionStrategy, this._records, this._directiveRecords,
|
||||
List<String> localNames)
|
||||
_CodegenState._(this._changeDetectorDefId, this._contextTypeName,
|
||||
this._changeDetectorTypeName, String changeDetectionStrategy,
|
||||
this._records, this._directiveRecords, List<String> localNames)
|
||||
: this._localNames = localNames,
|
||||
_changeNames = _getChangeNames(localNames),
|
||||
_fieldNames = _getFieldNames(localNames),
|
||||
@ -63,8 +93,8 @@ class _CodegenState {
|
||||
def.bindingRecords
|
||||
.forEach((rec) => protoRecords.add(rec, def.variableNames));
|
||||
var records = coalesce(protoRecords.records);
|
||||
return new _CodegenState._(typeName, changeDetectorTypeName, def.strategy,
|
||||
records, def.directiveRecords, _getLocalNames(records));
|
||||
return new _CodegenState._(def.id, typeName, changeDetectorTypeName,
|
||||
def.strategy, records, def.directiveRecords, _getLocalNames(records));
|
||||
}
|
||||
|
||||
/// Generates sanitized names for use as local variables.
|
||||
@ -167,6 +197,13 @@ class _CodegenState {
|
||||
''');
|
||||
}
|
||||
|
||||
void _writeInitToBuf(StringBuffer buf) {
|
||||
buf.write('''
|
||||
$_GEN_PREFIX.preGeneratedProtoDetectors['$_changeDetectorDefId'] =
|
||||
$_changeDetectorTypeName.newProtoChangeDetector;
|
||||
''');
|
||||
}
|
||||
|
||||
List<String> _genGetDirectiveFieldNames() {
|
||||
return _directiveRecords
|
||||
.map((d) => _genGetDirective(d.directiveIndex))
|
||||
|
@ -64,11 +64,14 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint,
|
||||
viewDefResults.ngDeps.lib != null ? viewDefResults.ngDeps.lib.end : 0;
|
||||
var codeInjectIdx =
|
||||
viewDefResults.ngDeps.registeredTypes.last.registerMethod.end;
|
||||
var initInjectIdx = viewDefResults.ngDeps.setupMethod.end - 1;
|
||||
return '${code.substring(0, importInjectIdx)}'
|
||||
'${changeDetectorClasses.imports}'
|
||||
'${code.substring(importInjectIdx, codeInjectIdx)}'
|
||||
'${registrations}'
|
||||
'${code.substring(codeInjectIdx)}'
|
||||
'${code.substring(codeInjectIdx, initInjectIdx)}'
|
||||
'${changeDetectorClasses.initialize}'
|
||||
'${code.substring(initInjectIdx)}'
|
||||
'$changeDetectorClasses';
|
||||
}
|
||||
|
||||
@ -100,8 +103,8 @@ class _TemplateExtractor {
|
||||
var pipeline =
|
||||
new CompilePipeline(_factory.createSteps(viewDef, subtaskPromises));
|
||||
|
||||
var compileElements = pipeline.process(
|
||||
templateEl, ViewType.COMPONENT, viewDef.componentId);
|
||||
var compileElements =
|
||||
pipeline.process(templateEl, ViewType.COMPONENT, viewDef.componentId);
|
||||
var protoViewDto = compileElements[0].inheritedProtoView
|
||||
.build(new PropertySetterFactory());
|
||||
|
||||
|
@ -34,8 +34,8 @@ class TemplateCompiler extends Transformer {
|
||||
Html5LibDomAdapter.makeCurrent();
|
||||
var id = transform.primaryInput.id;
|
||||
var reader = new AssetReader.fromTransform(transform);
|
||||
var transformedCode =
|
||||
formatter.format(await processTemplates(reader, id));
|
||||
var transformedCode = formatter.format(await processTemplates(reader, id,
|
||||
generateChangeDetectors: options.generateChangeDetectors));
|
||||
transform.addOutput(new Asset.fromString(id, transformedCode));
|
||||
} catch (ex, stackTrace) {
|
||||
log.logger.error('Parsing ng templates failed.\n'
|
||||
|
@ -33,8 +33,7 @@ class ViewDefinitionEntry {
|
||||
ViewDefinitionEntry._(this.hostMetadata, this.viewDef);
|
||||
}
|
||||
|
||||
String _getComponentId(AssetId assetId, String className) =>
|
||||
'$assetId:$className';
|
||||
String _getComponentId(AssetId assetId, String className) => '$className';
|
||||
|
||||
// TODO(kegluenq): Improve this test.
|
||||
bool _isViewAnnotation(InstanceCreationExpression node) =>
|
||||
|
Reference in New Issue
Block a user