Skip to content

Commit

Permalink
feat(compiler): add change detector generation
Browse files Browse the repository at this point in the history
Runtime and Codegen.

Part of angular#3605
  • Loading branch information
tbosch committed Sep 8, 2015
1 parent f3da37c commit daaf7d0
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 33 deletions.
102 changes: 102 additions & 0 deletions modules/angular2/src/compiler/change_detector_compiler.ts
@@ -0,0 +1,102 @@
import {TypeMetadata, SourceModule} from './api';
import {
ChangeDetectorJITGenerator
} from 'angular2/src/core/change_detection/change_detection_jit_generator';
import {
createPropertyRecords,
createEventRecords
} from 'angular2/src/core/change_detection/proto_change_detector';
import {AbstractChangeDetector} from 'angular2/src/core/change_detection/abstract_change_detector';
import {ChangeDetectionUtil} from 'angular2/src/core/change_detection/change_detection_util';

import {createChangeDetectorDefinitions} from './change_definition_factory';
import {isJsObject, CONST_EXPR} from 'angular2/src/core/facade/lang';

import {
ChangeDetectorGenConfig,
ChangeDetectorDefinition,
DynamicProtoChangeDetector,
ChangeDetectionStrategy
} from 'angular2/src/core/change_detection/change_detection';

import {TemplateAst} from './template_ast';
import {Codegen} from 'angular2/src/transform/template_compiler/change_detector_codegen';

var IS_DART = !isJsObject({});

const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
const UTIL = "ChangeDetectionUtil";

const JS_CHANGE_DETECTOR_IMPORTS = CONST_EXPR([
['angular2/src/core/change_detection/abstract_change_detector', 'acd'],
['angular2/src/core/change_detection/change_detection_util', 'cdu']
]);

const DART_CHANGE_DETECTOR_IMPORTS =
CONST_EXPR([['angular2/src/core/change_detection/pregen_proto_change_detector', '_gen']]);

export class ChangeDetectionCompiler {
constructor(private _genConfig: ChangeDetectorGenConfig) {}

compileComponentRuntime(componentType: TypeMetadata, strategy: ChangeDetectionStrategy,
parsedTemplate: TemplateAst[]): Function[] {
var changeDetectorDefinitions =
createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate);
if (IS_DART) {
return changeDetectorDefinitions.map(
definition => (dispatcher) =>
new DynamicProtoChangeDetector(definition).instantiate(dispatcher));
} else {
// TODO(tbosch): provide a flag in _genConfig whether to allow eval or fall back to dynamic
// change detection as well!
var source =
createJsChangeDetectorsSource(changeDetectorDefinitions, UTIL, ABSTRACT_CHANGE_DETECTOR);
source += '\n return CHANGE_DETECTORS';
return new Function(ABSTRACT_CHANGE_DETECTOR, UTIL, source)(AbstractChangeDetector,
ChangeDetectionUtil);
}
}

compileComponentCodeGen(componentType: TypeMetadata, strategy: ChangeDetectionStrategy,
parsedTemplate: TemplateAst[]): SourceModule {
var changeDetectorDefinitions =
createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate);
if (IS_DART) {
var factories = [];
var sourceParts = changeDetectorDefinitions.map(definition => {
var codegen = new Codegen();
var className = definition.id;
codegen.generate(componentType.typeName, className, definition);
factories.push(`(dispatcher) => new ${className}(dispatcher)`);
return codegen.toString();
});
sourceParts.push(`var CHANGE_DETECTORS = [ ${factories.join(',')} ];`);
return new SourceModule(componentType.typeUrl, sourceParts.join('\n'),
DART_CHANGE_DETECTOR_IMPORTS);
} else {
var source = createJsChangeDetectorsSource(changeDetectorDefinitions, `cdu.${UTIL}`,
`acd.${ABSTRACT_CHANGE_DETECTOR}`);
source += '\n exports.CHANGE_DETECTORS = CHANGE_DETECTORS';
return new SourceModule(componentType.typeUrl, source, JS_CHANGE_DETECTOR_IMPORTS);
}
}
}

function createJsChangeDetectorsSource(changeDetectorDefinitions: ChangeDetectorDefinition[],
changeDetectionUtilVarName: string,
abstractChangeDetectorVarName: string): string {
var factories = [];
var sourceParts = changeDetectorDefinitions.map(definition => {
var propertyBindingRecords = createPropertyRecords(definition);
var eventBindingRecords = createEventRecords(definition);
var propertyBindingTargets = definition.bindingRecords.map(b => b.target);
var generator = new ChangeDetectorJITGenerator(
definition.id, definition.strategy, propertyBindingRecords, propertyBindingTargets,
eventBindingRecords, definition.directiveRecords, definition.genConfig,
changeDetectionUtilVarName, abstractChangeDetectorVarName);
factories.push(`function(dispatcher) { return new ${generator.typeName}(dispatcher); }`);
return generator.generateSource();
});
sourceParts.push(`var CHANGE_DETECTORS = [ ${factories.join(',')} ];`);
return sourceParts.join('\n');
}
@@ -1,4 +1,10 @@
import {BaseException, Type, isBlank, isPresent} from 'angular2/src/core/facade/lang';
import {
BaseException,
Type,
isBlank,
isPresent,
StringWrapper
} from 'angular2/src/core/facade/lang';
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection';

import {AbstractChangeDetector} from './abstract_change_detector';
Expand Down Expand Up @@ -33,31 +39,51 @@ const CHANGES_LOCAL = "changes";
export class ChangeDetectorJITGenerator {
_logic: CodegenLogicUtil;
_names: CodegenNameUtil;
_typeName: string;
typeName: string;

constructor(private id: string, private changeDetectionStrategy: ChangeDetectionStrategy,
private records: ProtoRecord[], private propertyBindingTargets: BindingTarget[],
private eventBindings: EventBinding[], private directiveRecords: any[],
private genConfig: ChangeDetectorGenConfig) {
this._names =
new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords, UTIL);
this._logic = new CodegenLogicUtil(this._names, UTIL, changeDetectionStrategy);
this._typeName = sanitizeName(`ChangeDetector_${this.id}`);
private genConfig: ChangeDetectorGenConfig,
private changeDetectionUtilVarName: string = UTIL,
private abstractChangeDetectorVarName: string = ABSTRACT_CHANGE_DETECTOR) {
this._names = new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords,
this.changeDetectionUtilVarName);
this._logic =
new CodegenLogicUtil(this._names, this.changeDetectionUtilVarName, changeDetectionStrategy);
this.typeName = sanitizeName(`ChangeDetector_${this.id}`);
}

// TODO(tbosch): remove when the new compiler is in
generate(): Function {
var classDefinition = `
var ${this._typeName} = function ${this._typeName}(dispatcher) {
${ABSTRACT_CHANGE_DETECTOR}.call(
if (!StringWrapper.equals(this.changeDetectionUtilVarName, UTIL) ||
!StringWrapper.equals(this.abstractChangeDetectorVarName, ABSTRACT_CHANGE_DETECTOR)) {
throw new BaseException(
'Generating on the fly ChangeDetectors does not support using a different variable names for imports');
}
var factorySource = `
${this.generateSource()}
return function(dispatcher) {
return new ${this.typeName}(dispatcher);
}
`;
return new Function(ABSTRACT_CHANGE_DETECTOR, UTIL, factorySource)(AbstractChangeDetector,
ChangeDetectionUtil);
}

generateSource(): string {
return `
var ${this.typeName} = function ${this.typeName}(dispatcher) {
${this.abstractChangeDetectorVarName}.call(
this, ${JSON.stringify(this.id)}, dispatcher, ${this.records.length},
${this._typeName}.gen_propertyBindingTargets, ${this._typeName}.gen_directiveIndices,
${this.typeName}.gen_propertyBindingTargets, ${this.typeName}.gen_directiveIndices,
${codify(this.changeDetectionStrategy)});
this.dehydrateDirectives(false);
}
${this._typeName}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
${this.typeName}.prototype = Object.create(${this.abstractChangeDetectorVarName}.prototype);
${this._typeName}.prototype.detectChangesInRecordsInternal = function(throwOnChange) {
${this.typeName}.prototype.detectChangesInRecordsInternal = function(throwOnChange) {
${this._names.genInitLocals()}
var ${IS_CHANGED_LOCAL} = false;
var ${CHANGES_LOCAL} = null;
Expand All @@ -80,31 +106,25 @@ export class ChangeDetectorJITGenerator {
${this._genPropertyBindingTargets()}
${this._genDirectiveIndices()}
return function(dispatcher) {
return new ${this._typeName}(dispatcher);
}
`;
return new Function(ABSTRACT_CHANGE_DETECTOR, UTIL, classDefinition)(AbstractChangeDetector,
ChangeDetectionUtil);
}

_genPropertyBindingTargets(): string {
var targets = this._logic.genPropertyBindingTargets(this.propertyBindingTargets,
this.genConfig.genDebugInfo);
return `${this._typeName}.gen_propertyBindingTargets = ${targets};`;
return `${this.typeName}.gen_propertyBindingTargets = ${targets};`;
}

_genDirectiveIndices(): string {
var indices = this._logic.genDirectiveIndices(this.directiveRecords);
return `${this._typeName}.gen_directiveIndices = ${indices};`;
return `${this.typeName}.gen_directiveIndices = ${indices};`;
}

_maybeGenHandleEventInternal(): string {
if (this.eventBindings.length > 0) {
var handlers = this.eventBindings.map(eb => this._genEventBinding(eb)).join("\n");
return `
${this._typeName}.prototype.handleEventInternal = function(eventName, elIndex, locals) {
${this.typeName}.prototype.handleEventInternal = function(eventName, elIndex, locals) {
var ${this._names.getPreventDefaultAccesor()} = false;
${this._names.genInitEventLocals()}
${handlers}
Expand Down Expand Up @@ -156,7 +176,7 @@ export class ChangeDetectorJITGenerator {
}
var dehydrateFieldsCode = this._names.genDehydrateFields();
if (!destroyPipesCode && !dehydrateFieldsCode) return '';
return `${this._typeName}.prototype.dehydrateDirectives = function(destroyPipes) {
return `${this.typeName}.prototype.dehydrateDirectives = function(destroyPipes) {
${destroyPipesCode}
${dehydrateFieldsCode}
}`;
Expand All @@ -166,7 +186,7 @@ export class ChangeDetectorJITGenerator {
var hydrateDirectivesCode = this._logic.genHydrateDirectives(this.directiveRecords);
var hydrateDetectorsCode = this._logic.genHydrateDetectors(this.directiveRecords);
if (!hydrateDirectivesCode && !hydrateDetectorsCode) return '';
return `${this._typeName}.prototype.hydrateDirectives = function(directives) {
return `${this.typeName}.prototype.hydrateDirectives = function(directives) {
${hydrateDirectivesCode}
${hydrateDetectorsCode}
}`;
Expand All @@ -177,7 +197,7 @@ export class ChangeDetectorJITGenerator {
if (notifications.length > 0) {
var directiveNotifications = notifications.join("\n");
return `
${this._typeName}.prototype.afterContentLifecycleCallbacksInternal = function() {
${this.typeName}.prototype.afterContentLifecycleCallbacksInternal = function() {
${directiveNotifications}
}
`;
Expand All @@ -191,7 +211,7 @@ export class ChangeDetectorJITGenerator {
if (notifications.length > 0) {
var directiveNotifications = notifications.join("\n");
return `
${this._typeName}.prototype.afterViewLifecycleCallbacksInternal = function() {
${this.typeName}.prototype.afterViewLifecycleCallbacksInternal = function() {
${directiveNotifications}
}
`;
Expand Down Expand Up @@ -239,7 +259,7 @@ export class ChangeDetectorJITGenerator {
var pipeName = r.name;

var init = `
if (${pipe} === ${UTIL}.uninitialized) {
if (${pipe} === ${this.changeDetectionUtilVarName}.uninitialized) {
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeName}');
}
`;
Expand All @@ -251,7 +271,7 @@ export class ChangeDetectorJITGenerator {

var check = `
if (${oldValue} !== ${newValue}) {
${newValue} = ${UTIL}.unwrapValue(${newValue})
${newValue} = ${this.changeDetectionUtilVarName}.unwrapValue(${newValue})
${this._genChangeMarker(r)}
${this._genUpdateDirectiveOrElement(r)}
${this._genAddToChanges(r)}
Expand Down Expand Up @@ -342,7 +362,7 @@ export class ChangeDetectorJITGenerator {

_genCheckNoChanges(): string {
if (this.genConfig.genCheckNoChanges) {
return `${this._typeName}.prototype.checkNoChanges = function() { this.runDetectChanges(true); }`;
return `${this.typeName}.prototype.checkNoChanges = function() { this.runDetectChanges(true); }`;
} else {
return '';
}
Expand Down
@@ -0,0 +1,14 @@
import {
ChangeDetectorDefinition,
} from 'angular2/src/core/change_detection/change_detection';

// Note: This class is only here so that we can reference it from TypeScript code.
// The actual implementation lives under modules_dart.
// TODO(tbosch): Move the corresponding code into angular2/src/compiler once
// the new compiler is done.
export class Codegen {
generate(typeName: string, changeDetectorTypeName: string, def: ChangeDetectorDefinition): void {
throw "Not implemented in JS";
}
toString(): string { throw "Not implemented in JS"; }
}
Expand Up @@ -165,22 +165,22 @@ export function main() {
});
}

class TestContext {
export class TestContext {
eventLog: string[] = [];
someProp: string;
someProp2: string;

onEvent(value: string) { this.eventLog.push(value); }
}

class TestDirective {
export class TestDirective {
eventLog: string[] = [];
dirProp: string;

onEvent(value: string) { this.eventLog.push(value); }
}

class TestDispatcher implements ChangeDispatcher {
export class TestDispatcher implements ChangeDispatcher {
log: string[];

constructor(public directives: any[], public detectors: ProtoChangeDetector[]) { this.clear(); }
Expand All @@ -205,6 +205,6 @@ class TestDispatcher implements ChangeDispatcher {
_asString(value) { return (isBlank(value) ? 'null' : value.toString()); }
}

class TestPipes implements Pipes {
export class TestPipes implements Pipes {
get(type: string) { return null; }
}

0 comments on commit daaf7d0

Please sign in to comment.