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 #3605
Closes #4057
  • Loading branch information
tbosch committed Sep 10, 2015
1 parent 2daf2ee commit 12dd44f
Show file tree
Hide file tree
Showing 15 changed files with 374 additions and 121 deletions.
81 changes: 81 additions & 0 deletions modules/angular2/src/compiler/change_detector_compiler.ts
@@ -0,0 +1,81 @@
import {TypeMetadata, SourceModule} from './api';
import {
ChangeDetectorJITGenerator
} from 'angular2/src/core/change_detection/change_detection_jit_generator';

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);
return changeDetectorDefinitions.map(definition =>
this._createChangeDetectorFactory(definition));
}

private _createChangeDetectorFactory(definition: ChangeDetectorDefinition): Function {
if (IS_DART) {
var proto = new DynamicProtoChangeDetector(definition);
return (dispatcher) => proto.instantiate(dispatcher);
} else {
// TODO(tbosch): provide a flag in _genConfig whether to allow eval or fall back to dynamic
// change detection as well!
return new ChangeDetectorJITGenerator(definition, UTIL, ABSTRACT_CHANGE_DETECTOR).generate();
}
}

compileComponentCodeGen(componentType: TypeMetadata, strategy: ChangeDetectionStrategy,
parsedTemplate: TemplateAst[]): SourceModule {
var changeDetectorDefinitions =
createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate);
var imports = IS_DART ? DART_CHANGE_DETECTOR_IMPORTS : JS_CHANGE_DETECTOR_IMPORTS;
var factories = [];
var sourceParts = changeDetectorDefinitions.map(definition => {
var codegen: any;
// TODO(tbosch): move the 2 code generators to the same place, one with .dart and one with .ts
// suffix
// and have the same API for calling them!
if (IS_DART) {
codegen = new Codegen();
var className = definition.id;
codegen.generate(componentType.typeName, className, definition);
factories.push(`(dispatcher) => new ${className}(dispatcher)`);
return codegen.toString();
} else {
codegen = new ChangeDetectorJITGenerator(definition, `cdu.${UTIL}`,
`acd.${ABSTRACT_CHANGE_DETECTOR}`);
factories.push(`function(dispatcher) { return new ${codegen.typeName}(dispatcher); }`);
return codegen.generateSource();
}
});
sourceParts.push(`var CHANGE_DETECTORS = [ ${factories.join(',')} ];`);
return new SourceModule(componentType.typeUrl, sourceParts.join('\n'), imports);
}
}
10 changes: 1 addition & 9 deletions modules/angular2/src/compiler/style_compiler.ts
Expand Up @@ -76,7 +76,7 @@ export class StyleCompiler {
private _styleCodeGen(moduleName: string, plainStyles: string[], absUrls: string[], shim: boolean,
suffix: string): SourceModule {
var imports: string[][] = [];
var moduleSource = `${codeGenExportVar('STYLES')} (`;
var moduleSource = `var STYLES = (`;
moduleSource +=
`[${plainStyles.map( plainStyle => escapeString(this._shimIfNeeded(plainStyle, shim)) ).join(',')}]`;
for (var i = 0; i < absUrls.length; i++) {
Expand Down Expand Up @@ -109,14 +109,6 @@ function escapeString(input: string): string {
return `'${escapedInput}'`;
}

function codeGenExportVar(name: string): string {
if (IS_DART) {
return `var ${name} =`;
} else {
return `var ${name} = exports.${name} =`;
}
}

function codeGenConcatArray(expression: string): string {
return `${IS_DART ? '..addAll' : '.concat'}(${expression})`;
}
Expand Down
Expand Up @@ -103,7 +103,7 @@ export class PreGeneratedChangeDetection extends ChangeDetection {

this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false);
assertionsEnabled(), false, false);
}

static isSupported(): boolean { return PregenProtoChangeDetector.isSupported(); }
Expand Down Expand Up @@ -133,7 +133,7 @@ export class DynamicChangeDetection extends ChangeDetection {
super();
this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false);
assertionsEnabled(), false, false);
}

getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
Expand All @@ -157,7 +157,7 @@ export class JitChangeDetection extends ChangeDetection {
super();
this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false);
assertionsEnabled(), false, true);
}

static isSupported(): boolean { return JitProtoChangeDetector.isSupported(); }
Expand Down
Expand Up @@ -7,11 +7,16 @@ library change_detection.change_detection_jit_generator;
/// `PregenProtoChangeDetector`, and
/// `src/transform/template_compiler/change_detector_codegen.dart` for details.
class ChangeDetectorJITGenerator {
ChangeDetectorJITGenerator(typeName, strategy, records, directiveMementos) {}
String typeName;
ChangeDetectorJITGenerator(definition, changeDetectionUtilVarName, abstractChangeDetectorVarName) {}

generate() {
throw "Jit Change Detection is not supported in Dart";
}

generateSource() {
throw "Jit Change Detection is not supported in Dart";
}

static bool isSupported() => false;
}
@@ -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 All @@ -11,10 +17,9 @@ import {CodegenLogicUtil} from './codegen_logic_util';
import {codify} from './codegen_facade';
import {EventBinding} from './event_binding';
import {BindingTarget} from './binding_record';
import {ChangeDetectorGenConfig} from './interfaces';
import {ChangeDetectorGenConfig, ChangeDetectorDefinition} from './interfaces';
import {ChangeDetectionStrategy} from './constants';


import {createPropertyRecords, createEventRecords} from './proto_change_detector';

/**
* The code generator takes a list of proto records and creates a function/class
Expand All @@ -25,39 +30,65 @@ import {ChangeDetectionStrategy} from './constants';
* `angular2.transform.template_compiler.change_detector_codegen` library. If you make updates
* here, please make equivalent changes there.
*/
const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
const UTIL = "ChangeDetectionUtil";
const IS_CHANGED_LOCAL = "isChanged";
const CHANGES_LOCAL = "changes";

export class ChangeDetectorJITGenerator {
_logic: CodegenLogicUtil;
_names: CodegenNameUtil;
_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 _logic: CodegenLogicUtil;
private _names: CodegenNameUtil;
private id: string;
private changeDetectionStrategy: ChangeDetectionStrategy;
private records: ProtoRecord[];
private propertyBindingTargets: BindingTarget[];
private eventBindings: EventBinding[];
private directiveRecords: any[];
private genConfig: ChangeDetectorGenConfig;
typeName: string;

constructor(definition: ChangeDetectorDefinition, private changeDetectionUtilVarName: string,
private abstractChangeDetectorVarName: string) {
var propertyBindingRecords = createPropertyRecords(definition);
var eventBindingRecords = createEventRecords(definition);
var propertyBindingTargets = definition.bindingRecords.map(b => b.target);
this.id = definition.id;
this.changeDetectionStrategy = definition.strategy;
this.genConfig = definition.genConfig;

this.records = propertyBindingRecords;
this.propertyBindingTargets = propertyBindingTargets;
this.eventBindings = eventBindingRecords;
this.directiveRecords = definition.directiveRecords;
this._names = new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords,
this.changeDetectionUtilVarName);
this._logic = new CodegenLogicUtil(this._names, this.changeDetectionUtilVarName,
this.changeDetectionStrategy);
this.typeName = sanitizeName(`ChangeDetector_${this.id}`);
}

generate(): Function {
var classDefinition = `
var ${this._typeName} = function ${this._typeName}(dispatcher) {
${ABSTRACT_CHANGE_DETECTOR}.call(
var factorySource = `
${this.generateSource()}
return function(dispatcher) {
return new ${this.typeName}(dispatcher);
}
`;
return new Function(this.abstractChangeDetectorVarName, this.changeDetectionUtilVarName,
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 +111,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 +181,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 +191,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 +202,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 +216,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 +264,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 +276,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 +367,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
2 changes: 1 addition & 1 deletion modules/angular2/src/core/change_detection/interfaces.ts
Expand Up @@ -77,7 +77,7 @@ export interface ProtoChangeDetector { instantiate(dispatcher: ChangeDispatcher)

export class ChangeDetectorGenConfig {
constructor(public genCheckNoChanges: boolean, public genDebugInfo: boolean,
public logBindingUpdate: boolean) {}
public logBindingUpdate: boolean, public useJit: boolean) {}
}

export class ChangeDetectorDefinition {
Expand Down
Expand Up @@ -4,9 +4,6 @@ import {isPresent} from 'angular2/src/core/facade/lang';
import {ProtoChangeDetector, ChangeDetector, ChangeDetectorDefinition} from './interfaces';
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';

import {coalesce} from './coalesce';
import {createPropertyRecords, createEventRecords} from './proto_change_detector';

export class JitProtoChangeDetector implements ProtoChangeDetector {
_factory: Function;

Expand All @@ -19,13 +16,6 @@ export class JitProtoChangeDetector implements ProtoChangeDetector {
instantiate(dispatcher: any): ChangeDetector { return this._factory(dispatcher); }

_createFactory(definition: ChangeDetectorDefinition) {
var propertyBindingRecords = createPropertyRecords(definition);
var eventBindingRecords = createEventRecords(definition);
var propertyBindingTargets = this.definition.bindingRecords.map(b => b.target);

return new ChangeDetectorJITGenerator(
definition.id, definition.strategy, propertyBindingRecords, propertyBindingTargets,
eventBindingRecords, this.definition.directiveRecords, this.definition.genConfig)
.generate();
return new ChangeDetectorJITGenerator(definition, 'util', 'AbstractChangeDetector').generate();
}
}

0 comments on commit 12dd44f

Please sign in to comment.