feat(core): speed up view creation via code gen for view factories.

BREAKING CHANGE:
- Platform pipes can only contain types and arrays of types,
  but no bindings any more.
- When using transformers, platform pipes need to be specified explicitly
  in the pubspec.yaml via the new config option
  `platform_pipes`.
- `Compiler.compileInHost` now returns a `HostViewFactoryRef`
- Component view is not yet created when component constructor is called.
  -> use `onInit` lifecycle callback to access the view of a component
- `ViewRef#setLocal` has been moved to new type `EmbeddedViewRef`
- `internalView` is gone, use `EmbeddedViewRef.rootNodes` to access
  the root nodes of an embedded view
- `renderer.setElementProperty`, `..setElementStyle`, `..setElementAttribute` now
  take a native element instead of an ElementRef
- `Renderer` interface now operates on plain native nodes,
  instead of `RenderElementRef`s or `RenderViewRef`s

Closes #5993
This commit is contained in:
Tobias Bosch
2015-12-02 10:35:51 -08:00
parent a08f50badd
commit 7ae23adaff
191 changed files with 6476 additions and 10232 deletions

View File

@ -8,7 +8,9 @@ import {Pipes} from './pipes';
import {
ChangeDetectionError,
ExpressionChangedAfterItHasBeenCheckedException,
DehydratedException
DehydratedException,
EventEvaluationErrorContext,
EventEvaluationError
} from './exceptions';
import {BindingTarget} from './binding_record';
import {Locals} from './parser/locals';
@ -43,9 +45,12 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
subscriptions: any[];
streams: any[];
constructor(public id: string, public dispatcher: ChangeDispatcher,
public numberOfPropertyProtoRecords: number, public bindingTargets: BindingTarget[],
public directiveIndices: DirectiveIndex[], public strategy: ChangeDetectionStrategy) {
dispatcher: ChangeDispatcher;
constructor(public id: string, public numberOfPropertyProtoRecords: number,
public bindingTargets: BindingTarget[], public directiveIndices: DirectiveIndex[],
public strategy: ChangeDetectionStrategy) {
this.ref = new ChangeDetectorRef_(this);
}
@ -65,10 +70,24 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
remove(): void { this.parent.removeContentChild(this); }
handleEvent(eventName: string, elIndex: number, locals: Locals): boolean {
var res = this.handleEventInternal(eventName, elIndex, locals);
this.markPathToRootAsCheckOnce();
return res;
handleEvent(eventName: string, elIndex: number, event: any): boolean {
if (!this.hydrated()) {
return true;
}
try {
var locals = new Map<string, any>();
locals.set('$event', event);
var res = !this.handleEventInternal(eventName, elIndex, new Locals(this.locals, locals));
this.markPathToRootAsCheckOnce();
return res;
} catch (e) {
var c = this.dispatcher.getDebugContext(null, elIndex, null);
var context = isPresent(c) ?
new EventEvaluationErrorContext(c.element, c.componentElement, c.context,
c.locals, c.injector) :
null;
throw new EventEvaluationError(eventName, e, e.stack, context);
}
}
handleEventInternal(eventName: string, elIndex: number, locals: Locals): boolean { return false; }
@ -133,7 +152,8 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
// This method is not intended to be overridden. Subclasses should instead provide an
// implementation of `hydrateDirectives`.
hydrate(context: T, locals: Locals, directives: any, pipes: Pipes): void {
hydrate(context: T, locals: Locals, dispatcher: ChangeDispatcher, pipes: Pipes): void {
this.dispatcher = dispatcher;
this.mode = ChangeDetectionUtil.changeDetectionMode(this.strategy);
this.context = context;
@ -143,12 +163,12 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
this.locals = locals;
this.pipes = pipes;
this.hydrateDirectives(directives);
this.hydrateDirectives(dispatcher);
this.state = ChangeDetectorState.NeverChecked;
}
// Subclasses should override this method to hydrate any directives.
hydrateDirectives(directives: any): void {}
hydrateDirectives(dispatcher: ChangeDispatcher): void {}
// This method is not intended to be overridden. Subclasses should instead provide an
// implementation of `dehydrateDirectives`.
@ -160,6 +180,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
this._unsubsribeFromObservables();
}
this.dispatcher = null;
this.context = null;
this.locals = null;
this.pipes = null;
@ -171,6 +192,19 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
hydrated(): boolean { return isPresent(this.context); }
destroyRecursive(): void {
this.dispatcher.notifyOnDestroy();
this.dehydrate();
var children = this.contentChildren;
for (var i = 0; i < children.length; i++) {
children[i].destroyRecursive();
}
children = this.viewChildren;
for (var i = 0; i < children.length; i++) {
children[i].destroyRecursive();
}
}
afterContentLifecycleCallbacks(): void {
this.dispatcher.notifyAfterContentChecked();
this.afterContentLifecycleCallbacksInternal();
@ -298,7 +332,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
private _throwError(exception: any, stack: any): void {
var error;
try {
var c = this.dispatcher.getDebugContext(this._currentBinding().elementIndex, null);
var c = this.dispatcher.getDebugContext(null, this._currentBinding().elementIndex, null);
var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals,
c.injector, this._currentBinding().debug) :
null;

View File

@ -66,8 +66,8 @@ export class ChangeDetectorJITGenerator {
generate(): Function {
var factorySource = `
${this.generateSource()}
return function(dispatcher) {
return new ${this.typeName}(dispatcher);
return function() {
return new ${this.typeName}();
}
`;
return new Function(this.abstractChangeDetectorVarName, this.changeDetectionUtilVarName,
@ -77,9 +77,9 @@ export class ChangeDetectorJITGenerator {
generateSource(): string {
return `
var ${this.typeName} = function ${this.typeName}(dispatcher) {
var ${this.typeName} = function ${this.typeName}() {
${this.abstractChangeDetectorVarName}.call(
this, ${JSON.stringify(this.id)}, dispatcher, ${this.records.length},
this, ${JSON.stringify(this.id)}, ${this.records.length},
${this.typeName}.gen_propertyBindingTargets, ${this.typeName}.gen_directiveIndices,
${codify(this.changeDetectionStrategy)});
this.dehydrateDirectives(false);
@ -199,13 +199,14 @@ export class ChangeDetectorJITGenerator {
/** @internal */
_maybeGenDehydrateDirectives(): string {
var destroyPipesCode = this._names.genPipeOnDestroy();
if (destroyPipesCode) {
destroyPipesCode = `if (destroyPipes) { ${destroyPipesCode} }`;
}
var destroyDirectivesCode = this._logic.genDirectivesOnDestroy(this.directiveRecords);
var dehydrateFieldsCode = this._names.genDehydrateFields();
if (!destroyPipesCode && !dehydrateFieldsCode) return '';
if (!destroyPipesCode && !destroyDirectivesCode && !dehydrateFieldsCode) return '';
return `${this.typeName}.prototype.dehydrateDirectives = function(destroyPipes) {
${destroyPipesCode}
if (destroyPipes) {
${destroyPipesCode}
${destroyDirectivesCode}
}
${dehydrateFieldsCode}
}`;
}

View File

@ -205,4 +205,4 @@ export class ChangeDetectorRef_ extends ChangeDetectorRef {
this._cd.mode = ChangeDetectionStrategy.CheckAlways;
this.markForCheck();
}
}
}

View File

@ -155,17 +155,49 @@ export class CodegenLogicUtil {
var res = [];
for (var i = 0; i < directiveRecords.length; ++i) {
var r = directiveRecords[i];
res.push(`${this._names.getDirectiveName(r.directiveIndex)} = ${this._genReadDirective(i)};`);
var dirVarName = this._names.getDirectiveName(r.directiveIndex);
res.push(`${dirVarName} = ${this._genReadDirective(i)};`);
if (isPresent(r.outputs)) {
r.outputs.forEach(output => {
var eventHandlerExpr = this._genEventHandler(r.directiveIndex.elementIndex, output[1]);
if (IS_DART) {
res.push(`${dirVarName}.${output[0]}.listen(${eventHandlerExpr});`);
} else {
res.push(`${dirVarName}.${output[0]}.subscribe({next: ${eventHandlerExpr}});`);
}
});
}
}
return res.join("\n");
}
genDirectivesOnDestroy(directiveRecords: DirectiveRecord[]): string {
var res = [];
for (var i = 0; i < directiveRecords.length; ++i) {
var r = directiveRecords[i];
if (r.callOnDestroy) {
var dirVarName = this._names.getDirectiveName(r.directiveIndex);
res.push(`${dirVarName}.ngOnDestroy();`);
}
}
return res.join("\n");
}
private _genEventHandler(boundElementIndex: number, eventName: string): string {
if (IS_DART) {
return `(event) => this.handleEvent('${eventName}', ${boundElementIndex}, event)`;
} else {
return `(function(event) { return this.handleEvent('${eventName}', ${boundElementIndex}, event); }).bind(this)`;
}
}
private _genReadDirective(index: number) {
var directiveExpr = `this.getDirectiveFor(directives, ${index})`;
// This is an experimental feature. Works only in Dart.
if (this._changeDetection === ChangeDetectionStrategy.OnPushObserve) {
return `this.observeDirective(this.getDirectiveFor(directives, ${index}), ${index})`;
return `this.observeDirective(${directiveExpr}, ${index})`;
} else {
return `this.getDirectiveFor(directives, ${index})`;
return directiveExpr;
}
}

View File

@ -16,10 +16,14 @@ export class DirectiveRecord {
callOnChanges: boolean;
callDoCheck: boolean;
callOnInit: boolean;
callOnDestroy: boolean;
changeDetection: ChangeDetectionStrategy;
// array of [emitter property name, eventName]
outputs: string[][];
constructor({directiveIndex, callAfterContentInit, callAfterContentChecked, callAfterViewInit,
callAfterViewChecked, callOnChanges, callDoCheck, callOnInit, changeDetection}: {
callAfterViewChecked, callOnChanges, callDoCheck, callOnInit, callOnDestroy,
changeDetection, outputs}: {
directiveIndex?: DirectiveIndex,
callAfterContentInit?: boolean,
callAfterContentChecked?: boolean,
@ -28,7 +32,9 @@ export class DirectiveRecord {
callOnChanges?: boolean,
callDoCheck?: boolean,
callOnInit?: boolean,
changeDetection?: ChangeDetectionStrategy
callOnDestroy?: boolean,
changeDetection?: ChangeDetectionStrategy,
outputs?: string[][]
} = {}) {
this.directiveIndex = directiveIndex;
this.callAfterContentInit = normalizeBool(callAfterContentInit);
@ -38,7 +44,9 @@ export class DirectiveRecord {
this.callAfterViewChecked = normalizeBool(callAfterViewChecked);
this.callDoCheck = normalizeBool(callDoCheck);
this.callOnInit = normalizeBool(callOnInit);
this.callOnDestroy = normalizeBool(callOnDestroy);
this.changeDetection = changeDetection;
this.outputs = outputs;
}
isDefaultChangeDetection(): boolean {

View File

@ -11,21 +11,21 @@ import {ChangeDispatcher, ChangeDetectorGenConfig} from './interfaces';
import {ChangeDetectionUtil, SimpleChange} from './change_detection_util';
import {ChangeDetectionStrategy, ChangeDetectorState} from './constants';
import {ProtoRecord, RecordType} from './proto_record';
import {reflector} from 'angular2/src/core/reflection/reflection';
import {ObservableWrapper} from 'angular2/src/facade/async';
export class DynamicChangeDetector extends AbstractChangeDetector<any> {
values: any[];
changes: any[];
localPipes: any[];
prevContexts: any[];
directives: any = null;
constructor(id: string, dispatcher: ChangeDispatcher, numberOfPropertyProtoRecords: number,
constructor(id: string, numberOfPropertyProtoRecords: number,
propertyBindingTargets: BindingTarget[], directiveIndices: DirectiveIndex[],
strategy: ChangeDetectionStrategy, private _records: ProtoRecord[],
private _eventBindings: EventBinding[], private _directiveRecords: DirectiveRecord[],
private _genConfig: ChangeDetectorGenConfig) {
super(id, dispatcher, numberOfPropertyProtoRecords, propertyBindingTargets, directiveIndices,
strategy);
super(id, numberOfPropertyProtoRecords, propertyBindingTargets, directiveIndices, strategy);
var len = _records.length + 1;
this.values = ListWrapper.createFixedSize(len);
this.localPipes = ListWrapper.createFixedSize(len);
@ -104,24 +104,41 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
return this._eventBindings.filter(eb => eb.eventName == eventName && eb.elIndex === elIndex);
}
hydrateDirectives(directives: any): void {
hydrateDirectives(dispatcher: ChangeDispatcher): void {
this.values[0] = this.context;
this.directives = directives;
this.dispatcher = dispatcher;
if (this.strategy === ChangeDetectionStrategy.OnPushObserve) {
for (var i = 0; i < this.directiveIndices.length; ++i) {
var index = this.directiveIndices[i];
super.observeDirective(directives.getDirectiveFor(index), i);
super.observeDirective(this._getDirectiveFor(index), i);
}
}
for (var i = 0; i < this._directiveRecords.length; ++i) {
var r = this._directiveRecords[i];
if (isPresent(r.outputs)) {
r.outputs.forEach(output => {
var eventHandler =
<any>this._createEventHandler(r.directiveIndex.elementIndex, output[1]);
var directive = this._getDirectiveFor(r.directiveIndex);
var getter = reflector.getter(output[0]);
ObservableWrapper.subscribe(getter(directive), eventHandler);
});
}
}
}
private _createEventHandler(boundElementIndex: number, eventName: string): Function {
return (event) => this.handleEvent(eventName, boundElementIndex, event);
}
dehydrateDirectives(destroyPipes: boolean) {
if (destroyPipes) {
this._destroyPipes();
this._destroyDirectives();
}
this.values[0] = null;
this.directives = null;
ListWrapper.fill(this.values, ChangeDetectionUtil.uninitialized, 1);
ListWrapper.fill(this.changes, false);
ListWrapper.fill(this.localPipes, null);
@ -137,6 +154,16 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
}
}
/** @internal */
_destroyDirectives() {
for (var i = 0; i < this._directiveRecords.length; ++i) {
var record = this._directiveRecords[i];
if (record.callOnDestroy) {
this._getDirectiveFor(record.directiveIndex).ngOnDestroy();
}
}
}
checkNoChanges(): void { this.runDetectChanges(true); }
detectChangesInRecordsInternal(throwOnChange: boolean) {
@ -241,12 +268,14 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
}
/** @internal */
private _getDirectiveFor(directiveIndex) {
return this.directives.getDirectiveFor(directiveIndex);
private _getDirectiveFor(directiveIndex: DirectiveIndex) {
return this.dispatcher.getDirectiveFor(directiveIndex);
}
/** @internal */
private _getDetectorFor(directiveIndex) { return this.directives.getDetectorFor(directiveIndex); }
private _getDetectorFor(directiveIndex: DirectiveIndex) {
return this.dispatcher.getDetectorFor(directiveIndex);
}
/** @internal */
private _check(proto: ProtoRecord, throwOnChange: boolean, values: any[],

View File

@ -93,3 +93,20 @@ export class ChangeDetectionError extends WrappedException {
export class DehydratedException extends BaseException {
constructor() { super('Attempt to detect changes on a dehydrated detector.'); }
}
/**
* Wraps an exception thrown by an event handler.
*/
export class EventEvaluationError extends WrappedException {
constructor(eventName: string, originalException: any, originalStack: any, context: any) {
super(`Error during evaluation of "${eventName}"`, originalException, originalStack, context);
}
}
/**
* Error context included when an event handler throws an exception.
*/
export class EventEvaluationErrorContext {
constructor(public element: any, public componentElement: any, public context: any,
public locals: any, public injector: any) {}
}

View File

@ -1,6 +1,6 @@
import {Locals} from './parser/locals';
import {BindingTarget, BindingRecord} from './binding_record';
import {DirectiveIndex, DirectiveRecord} from './directive_record';
import {DirectiveRecord, DirectiveIndex} from './directive_record';
import {ChangeDetectionStrategy} from './constants';
import {ChangeDetectorRef} from './change_detector_ref';
@ -10,11 +10,14 @@ export class DebugContext {
}
export interface ChangeDispatcher {
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext;
getDebugContext(appElement: any, elementIndex: number, directiveIndex: number): DebugContext;
notifyOnBinding(bindingTarget: BindingTarget, value: any): void;
logBindingUpdate(bindingTarget: BindingTarget, value: any): void;
notifyAfterContentChecked(): void;
notifyAfterViewChecked(): void;
notifyOnDestroy(): void;
getDetectorFor(directiveIndex: DirectiveIndex): ChangeDetector;
getDirectiveFor(directiveIndex: DirectiveIndex): any;
}
export interface ChangeDetector {
@ -27,16 +30,18 @@ export interface ChangeDetector {
removeContentChild(cd: ChangeDetector): void;
removeViewChild(cd: ChangeDetector): void;
remove(): void;
hydrate(context: any, locals: Locals, directives: any, pipes: any): void;
hydrate(context: any, locals: Locals, dispatcher: ChangeDispatcher, pipes: any): void;
dehydrate(): void;
markPathToRootAsCheckOnce(): void;
handleEvent(eventName: string, elIndex: number, locals: Locals);
handleEvent(eventName: string, elIndex: number, event: any);
detectChanges(): void;
checkNoChanges(): void;
destroyRecursive(): void;
markAsCheckOnce(): void;
}
export interface ProtoChangeDetector { instantiate(dispatcher: ChangeDispatcher): ChangeDetector; }
export interface ProtoChangeDetector { instantiate(): ChangeDetector; }
export class ChangeDetectorGenConfig {
constructor(public genDebugInfo: boolean, public logBindingUpdate: boolean,

View File

@ -3,11 +3,11 @@ library change_detection.jit_proto_change_detector;
import 'interfaces.dart' show ChangeDetector, ProtoChangeDetector;
class JitProtoChangeDetector implements ProtoChangeDetector {
JitProtoChangeDetector(definition) : super();
JitProtoChangeDetector(definition);
static bool isSupported() => false;
ChangeDetector instantiate(dispatcher) {
ChangeDetector instantiate() {
throw "Jit Change Detection not supported in Dart";
}
}

View File

@ -14,7 +14,7 @@ export class JitProtoChangeDetector implements ProtoChangeDetector {
static isSupported(): boolean { return true; }
instantiate(dispatcher: any): ChangeDetector { return this._factory(dispatcher); }
instantiate(): ChangeDetector { return this._factory(); }
/** @internal */
_createFactory(definition: ChangeDetectorDefinition) {

View File

@ -41,5 +41,5 @@ export class Locals {
}
}
clearValues(): void { MapWrapper.clearValues(this.current); }
clearLocalValues(): void { MapWrapper.clearValues(this.current); }
}

View File

@ -1,8 +1,5 @@
library angular2.src.change_detection.pregen_proto_change_detector;
import 'package:angular2/src/core/change_detection/interfaces.dart';
import 'package:angular2/src/facade/lang.dart' show looseIdentical;
export 'dart:core' show List;
export 'package:angular2/src/core/change_detection/abstract_change_detector.dart'
show AbstractChangeDetector;
@ -20,34 +17,3 @@ export 'package:angular2/src/core/change_detection/proto_record.dart'
export 'package:angular2/src/core/change_detection/change_detection_util.dart'
show ChangeDetectionUtil;
export 'package:angular2/src/facade/lang.dart' show assertionsEnabled, looseIdentical;
typedef ProtoChangeDetector PregenProtoChangeDetectorFactory(
ChangeDetectorDefinition definition);
typedef ChangeDetector InstantiateMethod(dynamic dispatcher);
/// Implementation of [ProtoChangeDetector] for use by pre-generated change
/// detectors in Angular 2 Dart.
/// Classes generated by the `TemplateCompiler` use this. The `export`s above
/// allow the generated code to `import` a single library and get all
/// dependencies.
class PregenProtoChangeDetector extends ProtoChangeDetector {
/// The [ChangeDetectorDefinition#id]. Strictly informational.
final String id;
/// Closure used to generate an actual [ChangeDetector].
final InstantiateMethod _instantiateMethod;
/// Internal ctor.
PregenProtoChangeDetector._(this.id, this._instantiateMethod);
static bool isSupported() => true;
factory PregenProtoChangeDetector(
InstantiateMethod instantiateMethod, ChangeDetectorDefinition def) {
return new PregenProtoChangeDetector._(def.id, instantiateMethod);
}
@override
instantiate(dynamic dispatcher) => _instantiateMethod(dispatcher);
}

View File

@ -1,14 +1 @@
import {BaseException} from 'angular2/src/facade/exceptions';
import {ProtoChangeDetector, ChangeDetector} from './interfaces';
import {coalesce} from './coalesce';
export {Function as PregenProtoChangeDetectorFactory};
export class PregenProtoChangeDetector implements ProtoChangeDetector {
static isSupported(): boolean { return false; }
instantiate(dispatcher: any): ChangeDetector {
throw new BaseException('Pregen change detection not supported in Js');
}
}
// empty file as we only need the dart version

View File

@ -54,12 +54,11 @@ export class DynamicProtoChangeDetector implements ProtoChangeDetector {
this._directiveIndices = this._definition.directiveRecords.map(d => d.directiveIndex);
}
instantiate(dispatcher: any): ChangeDetector {
instantiate(): ChangeDetector {
return new DynamicChangeDetector(
this._definition.id, dispatcher, this._propertyBindingRecords.length,
this._propertyBindingTargets, this._directiveIndices, this._definition.strategy,
this._propertyBindingRecords, this._eventBindingRecords, this._definition.directiveRecords,
this._definition.genConfig);
this._definition.id, this._propertyBindingRecords.length, this._propertyBindingTargets,
this._directiveIndices, this._definition.strategy, this._propertyBindingRecords,
this._eventBindingRecords, this._definition.directiveRecords, this._definition.genConfig);
}
}