perf(animations): reduce size of bundle by removing AST classes (#19539)

This CL refactors the animation AST code to make use of interfaces instead of classes. Given that interfaces are not persisted during runtime the removal of classes should nicely cut down on size for the animations-browser bundle.

-- before --
animations-browser.umd.js = 222kb
animations-browser.umd.min.js = 107kb

-- after --
animations-browser.umd.js = 213kb
animations-browser.umd.min.js = 102kb

PR Close #19539
This commit is contained in:
Matias Niemelä 2017-10-03 13:41:52 -07:00 committed by Chuck Jazdzewski
parent c4704c8abc
commit c3a52697f5
8 changed files with 254 additions and 239 deletions

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AnimationMetadata, AnimationOptions, ɵStyleData} from '@angular/animations'; import {AnimationMetadata, AnimationMetadataType, AnimationOptions, ɵStyleData} from '@angular/animations';
import {AnimationDriver} from '../render/animation_driver'; import {AnimationDriver} from '../render/animation_driver';
import {normalizeStyles} from '../util'; import {normalizeStyles} from '../util';
@ -17,7 +17,7 @@ import {AnimationTimelineInstruction} from './animation_timeline_instruction';
import {ElementInstructionMap} from './element_instruction_map'; import {ElementInstructionMap} from './element_instruction_map';
export class Animation { export class Animation {
private _animationAst: Ast; private _animationAst: Ast<AnimationMetadataType>;
constructor(private _driver: AnimationDriver, input: AnimationMetadata|AnimationMetadata[]) { constructor(private _driver: AnimationDriver, input: AnimationMetadata|AnimationMetadata[]) {
const errors: any[] = []; const errors: any[] = [];
const ast = buildAnimationAst(_driver, input, errors); const ast = buildAnimationAst(_driver, input, errors);

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AnimateTimings, AnimationOptions, ɵStyleData} from '@angular/animations'; import {AnimateTimings, AnimationMetadataType, AnimationOptions, ɵStyleData} from '@angular/animations';
const EMPTY_ANIMATION_OPTIONS: AnimationOptions = {}; const EMPTY_ANIMATION_OPTIONS: AnimationOptions = {};
@ -23,129 +23,90 @@ export interface AstVisitor {
visitAnimateRef(ast: AnimateRefAst, context: any): any; visitAnimateRef(ast: AnimateRefAst, context: any): any;
visitQuery(ast: QueryAst, context: any): any; visitQuery(ast: QueryAst, context: any): any;
visitStagger(ast: StaggerAst, context: any): any; visitStagger(ast: StaggerAst, context: any): any;
visitTiming(ast: TimingAst, context: any): any;
} }
export abstract class Ast { export interface Ast<T extends AnimationMetadataType> {
abstract visit(ast: AstVisitor, context: any): any; type: T;
public options: AnimationOptions = EMPTY_ANIMATION_OPTIONS; options: AnimationOptions|null;
get params(): {[name: string]: any}|null { return this.options['params'] || null; }
} }
export class TriggerAst extends Ast { export interface TriggerAst extends Ast<AnimationMetadataType.Trigger> {
public queryCount: number = 0; type: AnimationMetadataType.Trigger;
public depCount: number = 0; name: string;
states: StateAst[];
constructor(public name: string, public states: StateAst[], public transitions: TransitionAst[]) { transitions: TransitionAst[];
super(); queryCount: number;
depCount: number;
} }
visit(visitor: AstVisitor, context: any): any { return visitor.visitTrigger(this, context); } export interface StateAst extends Ast<AnimationMetadataType.State> {
type: AnimationMetadataType.State;
name: string;
style: StyleAst;
} }
export class StateAst extends Ast { export interface TransitionAst extends Ast<AnimationMetadataType.Transition> {
constructor(public name: string, public style: StyleAst) { super(); } matchers: ((fromState: string, toState: string) => boolean)[];
animation: Ast<AnimationMetadataType>;
visit(visitor: AstVisitor, context: any): any { return visitor.visitState(this, context); } queryCount: number;
depCount: number;
} }
export class TransitionAst extends Ast { export interface SequenceAst extends Ast<AnimationMetadataType.Sequence> {
public queryCount: number = 0; steps: Ast<AnimationMetadataType>[];
public depCount: number = 0;
constructor(
public matchers: ((fromState: string, toState: string) => boolean)[], public animation: Ast) {
super();
} }
visit(visitor: AstVisitor, context: any): any { return visitor.visitTransition(this, context); } export interface GroupAst extends Ast<AnimationMetadataType.Group> {
steps: Ast<AnimationMetadataType>[];
} }
export class SequenceAst extends Ast { export interface AnimateAst extends Ast<AnimationMetadataType.Animate> {
constructor(public steps: Ast[]) { super(); } timings: TimingAst;
style: StyleAst|KeyframesAst;
visit(visitor: AstVisitor, context: any): any { return visitor.visitSequence(this, context); }
} }
export class GroupAst extends Ast { export interface StyleAst extends Ast<AnimationMetadataType.Style> {
constructor(public steps: Ast[]) { super(); } styles: (ɵStyleData|string)[];
easing: string|null;
visit(visitor: AstVisitor, context: any): any { return visitor.visitGroup(this, context); } offset: number|null;
containsDynamicStyles: boolean;
isEmptyStep?: boolean;
} }
export class AnimateAst extends Ast { export interface KeyframesAst extends Ast<AnimationMetadataType.Keyframes> { styles: StyleAst[]; }
constructor(public timings: TimingAst, public style: StyleAst|KeyframesAst) { super(); }
visit(visitor: AstVisitor, context: any): any { return visitor.visitAnimate(this, context); } export interface ReferenceAst extends Ast<AnimationMetadataType.Reference> {
animation: Ast<AnimationMetadataType>;
} }
export class StyleAst extends Ast { export interface AnimateChildAst extends Ast<AnimationMetadataType.AnimateChild> {}
public isEmptyStep = false;
public containsDynamicStyles = false;
constructor( export interface AnimateRefAst extends Ast<AnimationMetadataType.AnimateRef> {
public styles: (ɵStyleData|string)[], public easing: string|null, animation: ReferenceAst;
public offset: number|null) {
super();
} }
visit(visitor: AstVisitor, context: any): any { return visitor.visitStyle(this, context); } export interface QueryAst extends Ast<AnimationMetadataType.Query> {
selector: string;
limit: number;
optional: boolean;
includeSelf: boolean;
animation: Ast<AnimationMetadataType>;
originalSelector: string;
} }
export class KeyframesAst extends Ast { export interface StaggerAst extends Ast<AnimationMetadataType.Stagger> {
constructor(public styles: StyleAst[]) { super(); } timings: AnimateTimings;
animation: Ast<AnimationMetadataType>;
visit(visitor: AstVisitor, context: any): any { return visitor.visitKeyframes(this, context); }
} }
export class ReferenceAst extends Ast { export interface TimingAst {
constructor(public animation: Ast) { super(); } duration: number;
delay: number;
visit(visitor: AstVisitor, context: any): any { return visitor.visitReference(this, context); } easing: string|null;
dynamic?: boolean;
} }
export class AnimateChildAst extends Ast { export interface DynamicTimingAst extends TimingAst {
constructor() { super(); } strValue: string;
dynamic: true;
visit(visitor: AstVisitor, context: any): any { return visitor.visitAnimateChild(this, context); }
}
export class AnimateRefAst extends Ast {
constructor(public animation: ReferenceAst) { super(); }
visit(visitor: AstVisitor, context: any): any { return visitor.visitAnimateRef(this, context); }
}
export class QueryAst extends Ast {
public originalSelector: string;
constructor(
public selector: string, public limit: number, public optional: boolean,
public includeSelf: boolean, public animation: Ast) {
super();
}
visit(visitor: AstVisitor, context: any): any { return visitor.visitQuery(this, context); }
}
export class StaggerAst extends Ast {
constructor(public timings: AnimateTimings, public animation: Ast) { super(); }
visit(visitor: AstVisitor, context: any): any { return visitor.visitStagger(this, context); }
}
export class TimingAst extends Ast {
constructor(
public duration: number, public delay: number = 0, public easing: string|null = null) {
super();
}
visit(visitor: AstVisitor, context: any): any { return visitor.visitTiming(this, context); }
}
export class DynamicTimingAst extends TimingAst {
constructor(public value: string) { super(0, 0, ''); }
visit(visitor: AstVisitor, context: any): any { return visitor.visitTiming(this, context); }
} }

View File

@ -9,10 +9,10 @@ import {AUTO_STYLE, AnimateTimings, AnimationAnimateChildMetadata, AnimationAnim
import {AnimationDriver} from '../render/animation_driver'; import {AnimationDriver} from '../render/animation_driver';
import {getOrSetAsInMap} from '../render/shared'; import {getOrSetAsInMap} from '../render/shared';
import {ENTER_SELECTOR, LEAVE_SELECTOR, NG_ANIMATING_SELECTOR, NG_TRIGGER_SELECTOR, SUBSTITUTION_EXPR_START, copyObj, extractStyleParams, iteratorToArray, normalizeAnimationEntry, resolveTiming, validateStyleParams} from '../util'; import {ENTER_SELECTOR, LEAVE_SELECTOR, NG_ANIMATING_SELECTOR, NG_TRIGGER_SELECTOR, SUBSTITUTION_EXPR_START, copyObj, extractStyleParams, iteratorToArray, normalizeAnimationEntry, resolveTiming, validateStyleParams, visitDslNode} from '../util';
import {AnimateAst, AnimateChildAst, AnimateRefAst, Ast, DynamicTimingAst, GroupAst, KeyframesAst, QueryAst, ReferenceAst, SequenceAst, StaggerAst, StateAst, StyleAst, TimingAst, TransitionAst, TriggerAst} from './animation_ast'; import {AnimateAst, AnimateChildAst, AnimateRefAst, Ast, DynamicTimingAst, GroupAst, KeyframesAst, QueryAst, ReferenceAst, SequenceAst, StaggerAst, StateAst, StyleAst, TimingAst, TransitionAst, TriggerAst} from './animation_ast';
import {AnimationDslVisitor, visitAnimationNode} from './animation_dsl_visitor'; import {AnimationDslVisitor} from './animation_dsl_visitor';
import {parseTransitionExpr} from './animation_transition_expr'; import {parseTransitionExpr} from './animation_transition_expr';
const SELF_TOKEN = ':self'; const SELF_TOKEN = ':self';
@ -56,7 +56,7 @@ const SELF_TOKEN_REGEX = new RegExp(`\s*${SELF_TOKEN}\s*,?`, 'g');
*/ */
export function buildAnimationAst( export function buildAnimationAst(
driver: AnimationDriver, metadata: AnimationMetadata | AnimationMetadata[], driver: AnimationDriver, metadata: AnimationMetadata | AnimationMetadata[],
errors: any[]): Ast { errors: any[]): Ast<AnimationMetadataType> {
return new AnimationAstBuilderVisitor(driver).build(metadata, errors); return new AnimationAstBuilderVisitor(driver).build(metadata, errors);
} }
@ -69,10 +69,12 @@ const ROOT_SELECTOR = '';
export class AnimationAstBuilderVisitor implements AnimationDslVisitor { export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
constructor(private _driver: AnimationDriver) {} constructor(private _driver: AnimationDriver) {}
build(metadata: AnimationMetadata|AnimationMetadata[], errors: any[]): Ast { build(metadata: AnimationMetadata|AnimationMetadata[], errors: any[]):
Ast<AnimationMetadataType> {
const context = new AnimationAstBuilderContext(errors); const context = new AnimationAstBuilderContext(errors);
this._resetContextStyleTimingState(context); this._resetContextStyleTimingState(context);
return visitAnimationNode(this, normalizeAnimationEntry(metadata), context) as Ast; return <Ast<AnimationMetadataType>>visitDslNode(
this, normalizeAnimationEntry(metadata), context);
} }
private _resetContextStyleTimingState(context: AnimationAstBuilderContext) { private _resetContextStyleTimingState(context: AnimationAstBuilderContext) {
@ -108,11 +110,12 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
'only state() and transition() definitions can sit inside of a trigger()'); 'only state() and transition() definitions can sit inside of a trigger()');
} }
}); });
const ast = new TriggerAst(metadata.name, states, transitions);
ast.options = normalizeAnimationOptions(metadata.options); return {
ast.queryCount = queryCount; type: AnimationMetadataType.Trigger,
ast.depCount = depCount; name: metadata.name, states, transitions, queryCount, depCount,
return ast; options: null
};
} }
visitState(metadata: AnimationStateMetadata, context: AnimationAstBuilderContext): StateAst { visitState(metadata: AnimationStateMetadata, context: AnimationAstBuilderContext): StateAst {
@ -140,31 +143,38 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
} }
} }
const stateAst = new StateAst(metadata.name, styleAst); return {
if (astParams) { type: AnimationMetadataType.State,
stateAst.options = {params: astParams}; name: metadata.name,
} style: styleAst,
return stateAst; options: astParams ? {params: astParams} : null
};
} }
visitTransition(metadata: AnimationTransitionMetadata, context: AnimationAstBuilderContext): visitTransition(metadata: AnimationTransitionMetadata, context: AnimationAstBuilderContext):
TransitionAst { TransitionAst {
context.queryCount = 0; context.queryCount = 0;
context.depCount = 0; context.depCount = 0;
const entry = visitAnimationNode(this, normalizeAnimationEntry(metadata.animation), context); const animation = visitDslNode(this, normalizeAnimationEntry(metadata.animation), context);
const matchers = parseTransitionExpr(metadata.expr, context.errors); const matchers = parseTransitionExpr(metadata.expr, context.errors);
const ast = new TransitionAst(matchers, entry);
ast.options = normalizeAnimationOptions(metadata.options); return {
ast.queryCount = context.queryCount; type: AnimationMetadataType.Transition,
ast.depCount = context.depCount; matchers,
return ast; animation,
queryCount: context.queryCount,
depCount: context.depCount,
options: normalizeAnimationOptions(metadata.options)
};
} }
visitSequence(metadata: AnimationSequenceMetadata, context: AnimationAstBuilderContext): visitSequence(metadata: AnimationSequenceMetadata, context: AnimationAstBuilderContext):
SequenceAst { SequenceAst {
const ast = new SequenceAst(metadata.steps.map(s => visitAnimationNode(this, s, context))); return {
ast.options = normalizeAnimationOptions(metadata.options); type: AnimationMetadataType.Sequence,
return ast; steps: metadata.steps.map(s => visitDslNode(this, s, context)),
options: normalizeAnimationOptions(metadata.options)
};
} }
visitGroup(metadata: AnimationGroupMetadata, context: AnimationAstBuilderContext): GroupAst { visitGroup(metadata: AnimationGroupMetadata, context: AnimationAstBuilderContext): GroupAst {
@ -172,15 +182,17 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
let furthestTime = 0; let furthestTime = 0;
const steps = metadata.steps.map(step => { const steps = metadata.steps.map(step => {
context.currentTime = currentTime; context.currentTime = currentTime;
const innerAst = visitAnimationNode(this, step, context); const innerAst = visitDslNode(this, step, context);
furthestTime = Math.max(furthestTime, context.currentTime); furthestTime = Math.max(furthestTime, context.currentTime);
return innerAst; return innerAst;
}); });
context.currentTime = furthestTime; context.currentTime = furthestTime;
const ast = new GroupAst(steps); return {
ast.options = normalizeAnimationOptions(metadata.options); type: AnimationMetadataType.Group,
return ast; steps,
options: normalizeAnimationOptions(metadata.options)
};
} }
visitAnimate(metadata: AnimationAnimateMetadata, context: AnimationAstBuilderContext): visitAnimate(metadata: AnimationAnimateMetadata, context: AnimationAstBuilderContext):
@ -188,10 +200,10 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
const timingAst = constructTimingAst(metadata.timings, context.errors); const timingAst = constructTimingAst(metadata.timings, context.errors);
context.currentAnimateTimings = timingAst; context.currentAnimateTimings = timingAst;
let styles: StyleAst|KeyframesAst; let styleAst: StyleAst|KeyframesAst;
let styleMetadata: AnimationMetadata = metadata.styles ? metadata.styles : style({}); let styleMetadata: AnimationMetadata = metadata.styles ? metadata.styles : style({});
if (styleMetadata.type == AnimationMetadataType.Keyframes) { if (styleMetadata.type == AnimationMetadataType.Keyframes) {
styles = this.visitKeyframes(styleMetadata as AnimationKeyframesSequenceMetadata, context); styleAst = this.visitKeyframes(styleMetadata as AnimationKeyframesSequenceMetadata, context);
} else { } else {
let styleMetadata = metadata.styles as AnimationStyleMetadata; let styleMetadata = metadata.styles as AnimationStyleMetadata;
let isEmpty = false; let isEmpty = false;
@ -204,13 +216,18 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
styleMetadata = style(newStyleData); styleMetadata = style(newStyleData);
} }
context.currentTime += timingAst.duration + timingAst.delay; context.currentTime += timingAst.duration + timingAst.delay;
const styleAst = this.visitStyle(styleMetadata, context); const _styleAst = this.visitStyle(styleMetadata, context);
styleAst.isEmptyStep = isEmpty; _styleAst.isEmptyStep = isEmpty;
styles = styleAst; styleAst = _styleAst;
} }
context.currentAnimateTimings = null; context.currentAnimateTimings = null;
return new AnimateAst(timingAst, styles); return {
type: AnimationMetadataType.Animate,
timings: timingAst,
style: styleAst,
options: null
};
} }
visitStyle(metadata: AnimationStyleMetadata, context: AnimationAstBuilderContext): StyleAst { visitStyle(metadata: AnimationStyleMetadata, context: AnimationAstBuilderContext): StyleAst {
@ -260,9 +277,13 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
} }
}); });
const ast = new StyleAst(styles, collectedEasing, metadata.offset); return {
ast.containsDynamicStyles = containsDynamicStyles; type: AnimationMetadataType.Style,
return ast; styles,
easing: collectedEasing,
offset: metadata.offset, containsDynamicStyles,
options: null
};
} }
private _validateStyleAst(ast: StyleAst, context: AnimationAstBuilderContext): void { private _validateStyleAst(ast: StyleAst, context: AnimationAstBuilderContext): void {
@ -313,9 +334,10 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
visitKeyframes(metadata: AnimationKeyframesSequenceMetadata, context: AnimationAstBuilderContext): visitKeyframes(metadata: AnimationKeyframesSequenceMetadata, context: AnimationAstBuilderContext):
KeyframesAst { KeyframesAst {
const ast: KeyframesAst = {type: AnimationMetadataType.Keyframes, styles: [], options: null};
if (!context.currentAnimateTimings) { if (!context.currentAnimateTimings) {
context.errors.push(`keyframes() must be placed inside of a call to animate()`); context.errors.push(`keyframes() must be placed inside of a call to animate()`);
return new KeyframesAst([]); return ast;
} }
const MAX_KEYFRAME_OFFSET = 1; const MAX_KEYFRAME_OFFSET = 1;
@ -369,33 +391,38 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
currentAnimateTimings.duration = durationUpToThisFrame; currentAnimateTimings.duration = durationUpToThisFrame;
this._validateStyleAst(kf, context); this._validateStyleAst(kf, context);
kf.offset = offset; kf.offset = offset;
ast.styles.push(kf);
}); });
return new KeyframesAst(keyframes); return ast;
} }
visitReference(metadata: AnimationReferenceMetadata, context: AnimationAstBuilderContext): visitReference(metadata: AnimationReferenceMetadata, context: AnimationAstBuilderContext):
ReferenceAst { ReferenceAst {
const entry = visitAnimationNode(this, normalizeAnimationEntry(metadata.animation), context); return {
const ast = new ReferenceAst(entry); type: AnimationMetadataType.Reference,
ast.options = normalizeAnimationOptions(metadata.options); animation: visitDslNode(this, normalizeAnimationEntry(metadata.animation), context),
return ast; options: normalizeAnimationOptions(metadata.options)
};
} }
visitAnimateChild(metadata: AnimationAnimateChildMetadata, context: AnimationAstBuilderContext): visitAnimateChild(metadata: AnimationAnimateChildMetadata, context: AnimationAstBuilderContext):
AnimateChildAst { AnimateChildAst {
context.depCount++; context.depCount++;
const ast = new AnimateChildAst(); return {
ast.options = normalizeAnimationOptions(metadata.options); type: AnimationMetadataType.AnimateChild,
return ast; options: normalizeAnimationOptions(metadata.options)
};
} }
visitAnimateRef(metadata: AnimationAnimateRefMetadata, context: AnimationAstBuilderContext): visitAnimateRef(metadata: AnimationAnimateRefMetadata, context: AnimationAstBuilderContext):
AnimateRefAst { AnimateRefAst {
const animation = this.visitReference(metadata.animation, context); return {
const ast = new AnimateRefAst(animation); type: AnimationMetadataType.AnimateRef,
ast.options = normalizeAnimationOptions(metadata.options); animation: this.visitReference(metadata.animation, context),
return ast; options: normalizeAnimationOptions(metadata.options)
};
} }
visitQuery(metadata: AnimationQueryMetadata, context: AnimationAstBuilderContext): QueryAst { visitQuery(metadata: AnimationQueryMetadata, context: AnimationAstBuilderContext): QueryAst {
@ -409,14 +436,18 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
parentSelector.length ? (parentSelector + ' ' + selector) : selector; parentSelector.length ? (parentSelector + ' ' + selector) : selector;
getOrSetAsInMap(context.collectedStyles, context.currentQuerySelector, {}); getOrSetAsInMap(context.collectedStyles, context.currentQuerySelector, {});
const entry = visitAnimationNode(this, normalizeAnimationEntry(metadata.animation), context); const animation = visitDslNode(this, normalizeAnimationEntry(metadata.animation), context);
context.currentQuery = null; context.currentQuery = null;
context.currentQuerySelector = parentSelector; context.currentQuerySelector = parentSelector;
const ast = new QueryAst(selector, options.limit || 0, !!options.optional, includeSelf, entry); return {
ast.originalSelector = metadata.selector; type: AnimationMetadataType.Query,
ast.options = normalizeAnimationOptions(metadata.options); selector,
return ast; limit: options.limit || 0,
optional: !!options.optional, includeSelf, animation,
originalSelector: metadata.selector,
options: normalizeAnimationOptions(metadata.options)
};
} }
visitStagger(metadata: AnimationStaggerMetadata, context: AnimationAstBuilderContext): visitStagger(metadata: AnimationStaggerMetadata, context: AnimationAstBuilderContext):
@ -427,9 +458,12 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
const timings = metadata.timings === 'full' ? const timings = metadata.timings === 'full' ?
{duration: 0, delay: 0, easing: 'full'} : {duration: 0, delay: 0, easing: 'full'} :
resolveTiming(metadata.timings, context.errors, true); resolveTiming(metadata.timings, context.errors, true);
const animation =
visitAnimationNode(this, normalizeAnimationEntry(metadata.animation), context); return {
return new StaggerAst(timings, animation); type: AnimationMetadataType.Stagger,
animation: visitDslNode(this, normalizeAnimationEntry(metadata.animation), context), timings,
options: null
};
} }
} }
@ -501,17 +535,20 @@ function constructTimingAst(value: string | number | AnimateTimings, errors: any
timings = value as AnimateTimings; timings = value as AnimateTimings;
} else if (typeof value == 'number') { } else if (typeof value == 'number') {
const duration = resolveTiming(value as number, errors).duration; const duration = resolveTiming(value as number, errors).duration;
return new TimingAst(value as number, 0, ''); return makeTimingAst(duration as number, 0, '');
} }
const strValue = value as string; const strValue = value as string;
const isDynamic = strValue.split(/\s+/).some(v => v.charAt(0) == '{' && v.charAt(1) == '{'); const isDynamic = strValue.split(/\s+/).some(v => v.charAt(0) == '{' && v.charAt(1) == '{');
if (isDynamic) { if (isDynamic) {
return new DynamicTimingAst(strValue); const ast = makeTimingAst(0, 0, '') as any;
ast.dynamic = true;
ast.strValue = strValue;
return ast as DynamicTimingAst;
} }
timings = timings || resolveTiming(strValue, errors); timings = timings || resolveTiming(strValue, errors);
return new TimingAst(timings.duration, timings.delay, timings.easing); return makeTimingAst(timings.duration, timings.delay, timings.easing);
} }
function normalizeAnimationOptions(options: AnimationOptions | null): AnimationOptions { function normalizeAnimationOptions(options: AnimationOptions | null): AnimationOptions {
@ -525,3 +562,7 @@ function normalizeAnimationOptions(options: AnimationOptions | null): AnimationO
} }
return options; return options;
} }
function makeTimingAst(duration: number, delay: number, easing: string | null): TimingAst {
return {duration, delay, easing};
}

View File

@ -5,54 +5,20 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AnimationAnimateChildMetadata, AnimationAnimateMetadata, AnimationAnimateRefMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationMetadataType, AnimationQueryMetadata, AnimationReferenceMetadata, AnimationSequenceMetadata, AnimationStaggerMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, AnimationTriggerMetadata} from '@angular/animations'; import {AnimationAnimateChildMetadata, AnimationAnimateMetadata, AnimationAnimateRefMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationQueryMetadata, AnimationReferenceMetadata, AnimationSequenceMetadata, AnimationStaggerMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, AnimationTriggerMetadata} from '@angular/animations';
export interface AnimationDslVisitor { export interface AnimationDslVisitor {
visitTrigger(ast: AnimationTriggerMetadata, context: any): any; visitTrigger(node: AnimationTriggerMetadata, context: any): any;
visitState(ast: AnimationStateMetadata, context: any): any; visitState(node: AnimationStateMetadata, context: any): any;
visitTransition(ast: AnimationTransitionMetadata, context: any): any; visitTransition(node: AnimationTransitionMetadata, context: any): any;
visitSequence(ast: AnimationSequenceMetadata, context: any): any; visitSequence(node: AnimationSequenceMetadata, context: any): any;
visitGroup(ast: AnimationGroupMetadata, context: any): any; visitGroup(node: AnimationGroupMetadata, context: any): any;
visitAnimate(ast: AnimationAnimateMetadata, context: any): any; visitAnimate(node: AnimationAnimateMetadata, context: any): any;
visitStyle(ast: AnimationStyleMetadata, context: any): any; visitStyle(node: AnimationStyleMetadata, context: any): any;
visitKeyframes(ast: AnimationKeyframesSequenceMetadata, context: any): any; visitKeyframes(node: AnimationKeyframesSequenceMetadata, context: any): any;
visitReference(ast: AnimationReferenceMetadata, context: any): any; visitReference(node: AnimationReferenceMetadata, context: any): any;
visitAnimateChild(ast: AnimationAnimateChildMetadata, context: any): any; visitAnimateChild(node: AnimationAnimateChildMetadata, context: any): any;
visitAnimateRef(ast: AnimationAnimateRefMetadata, context: any): any; visitAnimateRef(node: AnimationAnimateRefMetadata, context: any): any;
visitQuery(ast: AnimationQueryMetadata, context: any): any; visitQuery(node: AnimationQueryMetadata, context: any): any;
visitStagger(ast: AnimationStaggerMetadata, context: any): any; visitStagger(node: AnimationStaggerMetadata, context: any): any;
}
export function visitAnimationNode(
visitor: AnimationDslVisitor, node: AnimationMetadata, context: any) {
switch (node.type) {
case AnimationMetadataType.Trigger:
return visitor.visitTrigger(node as AnimationTriggerMetadata, context);
case AnimationMetadataType.State:
return visitor.visitState(node as AnimationStateMetadata, context);
case AnimationMetadataType.Transition:
return visitor.visitTransition(node as AnimationTransitionMetadata, context);
case AnimationMetadataType.Sequence:
return visitor.visitSequence(node as AnimationSequenceMetadata, context);
case AnimationMetadataType.Group:
return visitor.visitGroup(node as AnimationGroupMetadata, context);
case AnimationMetadataType.Animate:
return visitor.visitAnimate(node as AnimationAnimateMetadata, context);
case AnimationMetadataType.Keyframes:
return visitor.visitKeyframes(node as AnimationKeyframesSequenceMetadata, context);
case AnimationMetadataType.Style:
return visitor.visitStyle(node as AnimationStyleMetadata, context);
case AnimationMetadataType.Reference:
return visitor.visitReference(node as AnimationReferenceMetadata, context);
case AnimationMetadataType.AnimateChild:
return visitor.visitAnimateChild(node as AnimationAnimateChildMetadata, context);
case AnimationMetadataType.AnimateRef:
return visitor.visitAnimateRef(node as AnimationAnimateRefMetadata, context);
case AnimationMetadataType.Query:
return visitor.visitQuery(node as AnimationQueryMetadata, context);
case AnimationMetadataType.Stagger:
return visitor.visitStagger(node as AnimationStaggerMetadata, context);
default:
throw new Error(`Unable to resolve animation metadata node #${node.type}`);
}
} }

View File

@ -5,10 +5,10 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AUTO_STYLE, AnimateChildOptions, AnimateTimings, AnimationOptions, AnimationQueryOptions, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations'; import {AUTO_STYLE, AnimateChildOptions, AnimateTimings, AnimationMetadataType, AnimationOptions, AnimationQueryOptions, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations';
import {AnimationDriver} from '../render/animation_driver'; import {AnimationDriver} from '../render/animation_driver';
import {copyObj, copyStyles, interpolateParams, iteratorToArray, resolveTiming, resolveTimingValue} from '../util'; import {copyObj, copyStyles, interpolateParams, iteratorToArray, resolveTiming, resolveTimingValue, visitDslNode} from '../util';
import {AnimateAst, AnimateChildAst, AnimateRefAst, Ast, AstVisitor, DynamicTimingAst, GroupAst, KeyframesAst, QueryAst, ReferenceAst, SequenceAst, StaggerAst, StateAst, StyleAst, TimingAst, TransitionAst, TriggerAst} from './animation_ast'; import {AnimateAst, AnimateChildAst, AnimateRefAst, Ast, AstVisitor, DynamicTimingAst, GroupAst, KeyframesAst, QueryAst, ReferenceAst, SequenceAst, StaggerAst, StateAst, StyleAst, TimingAst, TransitionAst, TriggerAst} from './animation_ast';
import {AnimationTimelineInstruction, createTimelineInstruction} from './animation_timeline_instruction'; import {AnimationTimelineInstruction, createTimelineInstruction} from './animation_timeline_instruction';
@ -101,8 +101,8 @@ const ONE_FRAME_IN_MILLISECONDS = 1;
* the `AnimationValidatorVisitor` code. * the `AnimationValidatorVisitor` code.
*/ */
export function buildAnimationTimelines( export function buildAnimationTimelines(
driver: AnimationDriver, rootElement: any, ast: Ast, startingStyles: ɵStyleData = {}, driver: AnimationDriver, rootElement: any, ast: Ast<AnimationMetadataType>,
finalStyles: ɵStyleData = {}, options: AnimationOptions, startingStyles: ɵStyleData = {}, finalStyles: ɵStyleData = {}, options: AnimationOptions,
subInstructions?: ElementInstructionMap, errors: any[] = []): AnimationTimelineInstruction[] { subInstructions?: ElementInstructionMap, errors: any[] = []): AnimationTimelineInstruction[] {
return new AnimationTimelineBuilderVisitor().buildKeyframes( return new AnimationTimelineBuilderVisitor().buildKeyframes(
driver, rootElement, ast, startingStyles, finalStyles, options, subInstructions, errors); driver, rootElement, ast, startingStyles, finalStyles, options, subInstructions, errors);
@ -110,15 +110,15 @@ export function buildAnimationTimelines(
export class AnimationTimelineBuilderVisitor implements AstVisitor { export class AnimationTimelineBuilderVisitor implements AstVisitor {
buildKeyframes( buildKeyframes(
driver: AnimationDriver, rootElement: any, ast: Ast, startingStyles: ɵStyleData, driver: AnimationDriver, rootElement: any, ast: Ast<AnimationMetadataType>,
finalStyles: ɵStyleData, options: AnimationOptions, subInstructions?: ElementInstructionMap, startingStyles: ɵStyleData, finalStyles: ɵStyleData, options: AnimationOptions,
errors: any[] = []): AnimationTimelineInstruction[] { subInstructions?: ElementInstructionMap, errors: any[] = []): AnimationTimelineInstruction[] {
subInstructions = subInstructions || new ElementInstructionMap(); subInstructions = subInstructions || new ElementInstructionMap();
const context = new AnimationTimelineContext(driver, rootElement, subInstructions, errors, []); const context = new AnimationTimelineContext(driver, rootElement, subInstructions, errors, []);
context.options = options; context.options = options;
context.currentTimeline.setStyles([startingStyles], null, context.errors, options); context.currentTimeline.setStyles([startingStyles], null, context.errors, options);
ast.visit(this, context); visitDslNode(this, ast, context);
// this checks to see if an actual animation happened // this checks to see if an actual animation happened
const timelines = context.timelines.filter(timeline => timeline.containsAnimation()); const timelines = context.timelines.filter(timeline => timeline.containsAnimation());
@ -193,7 +193,7 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
visitReference(ast: ReferenceAst, context: AnimationTimelineContext) { visitReference(ast: ReferenceAst, context: AnimationTimelineContext) {
context.updateOptions(ast.options, true); context.updateOptions(ast.options, true);
ast.animation.visit(this, context); visitDslNode(this, ast.animation, context);
context.previousNode = ast; context.previousNode = ast;
} }
@ -207,7 +207,7 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
ctx.transformIntoNewTimeline(); ctx.transformIntoNewTimeline();
if (options.delay != null) { if (options.delay != null) {
if (ctx.previousNode instanceof StyleAst) { if (ctx.previousNode.type == AnimationMetadataType.Style) {
ctx.currentTimeline.snapshotCurrentStyles(); ctx.currentTimeline.snapshotCurrentStyles();
ctx.previousNode = DEFAULT_NOOP_PREVIOUS_NODE; ctx.previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
} }
@ -218,7 +218,7 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
} }
if (ast.steps.length) { if (ast.steps.length) {
ast.steps.forEach(s => s.visit(this, ctx)); ast.steps.forEach(s => visitDslNode(this, s, ctx));
// this is here just incase the inner steps only contain or end with a style() call // this is here just incase the inner steps only contain or end with a style() call
ctx.currentTimeline.applyStylesToKeyframe(); ctx.currentTimeline.applyStylesToKeyframe();
@ -245,7 +245,7 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
innerContext.delayNextStep(delay); innerContext.delayNextStep(delay);
} }
s.visit(this, innerContext); visitDslNode(this, s, innerContext);
furthestTime = Math.max(furthestTime, innerContext.currentTimeline.currentTime); furthestTime = Math.max(furthestTime, innerContext.currentTimeline.currentTime);
innerTimelines.push(innerContext.currentTimeline); innerTimelines.push(innerContext.currentTimeline);
}); });
@ -259,19 +259,19 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
context.previousNode = ast; context.previousNode = ast;
} }
visitTiming(ast: TimingAst, context: AnimationTimelineContext): AnimateTimings { private _visitTiming(ast: TimingAst, context: AnimationTimelineContext): AnimateTimings {
if (ast instanceof DynamicTimingAst) { if ((ast as DynamicTimingAst).dynamic) {
const strValue = context.params ? const strValue = (ast as DynamicTimingAst).strValue;
interpolateParams(ast.value, context.params, context.errors) : const timingValue =
ast.value.toString(); context.params ? interpolateParams(strValue, context.params, context.errors) : strValue;
return resolveTiming(strValue, context.errors); return resolveTiming(timingValue, context.errors);
} else { } else {
return {duration: ast.duration, delay: ast.delay, easing: ast.easing}; return {duration: ast.duration, delay: ast.delay, easing: ast.easing};
} }
} }
visitAnimate(ast: AnimateAst, context: AnimationTimelineContext) { visitAnimate(ast: AnimateAst, context: AnimationTimelineContext) {
const timings = context.currentAnimateTimings = this.visitTiming(ast.timings, context); const timings = context.currentAnimateTimings = this._visitTiming(ast.timings, context);
const timeline = context.currentTimeline; const timeline = context.currentTimeline;
if (timings.delay) { if (timings.delay) {
context.incrementTime(timings.delay); context.incrementTime(timings.delay);
@ -279,7 +279,7 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
} }
const style = ast.style; const style = ast.style;
if (style instanceof KeyframesAst) { if (style.type == AnimationMetadataType.Keyframes) {
this.visitKeyframes(style, context); this.visitKeyframes(style, context);
} else { } else {
context.incrementTime(timings.duration); context.incrementTime(timings.duration);
@ -343,7 +343,7 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
const options = (ast.options || {}) as AnimationQueryOptions; const options = (ast.options || {}) as AnimationQueryOptions;
const delay = options.delay ? resolveTimingValue(options.delay) : 0; const delay = options.delay ? resolveTimingValue(options.delay) : 0;
if (delay && (context.previousNode instanceof StyleAst || if (delay && (context.previousNode.type === AnimationMetadataType.Style ||
(startTime == 0 && context.currentTimeline.getCurrentStyleProperties().length))) { (startTime == 0 && context.currentTimeline.getCurrentStyleProperties().length))) {
context.currentTimeline.snapshotCurrentStyles(); context.currentTimeline.snapshotCurrentStyles();
context.previousNode = DEFAULT_NOOP_PREVIOUS_NODE; context.previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
@ -368,7 +368,7 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
sameElementTimeline = innerContext.currentTimeline; sameElementTimeline = innerContext.currentTimeline;
} }
ast.animation.visit(this, innerContext); visitDslNode(this, ast.animation, innerContext);
// this is here just incase the inner steps only contain or end // this is here just incase the inner steps only contain or end
// with a style() call (which is here to signal that this is a preparatory // with a style() call (which is here to signal that this is a preparatory
@ -415,7 +415,7 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
} }
const startingTime = timeline.currentTime; const startingTime = timeline.currentTime;
ast.animation.visit(this, context); visitDslNode(this, ast.animation, context);
context.previousNode = ast; context.previousNode = ast;
// time = duration + delay // time = duration + delay
@ -431,12 +431,12 @@ export declare type StyleAtTime = {
time: number; value: string | number; time: number; value: string | number;
}; };
const DEFAULT_NOOP_PREVIOUS_NODE = <Ast>{}; const DEFAULT_NOOP_PREVIOUS_NODE = <Ast<AnimationMetadataType>>{};
export class AnimationTimelineContext { export class AnimationTimelineContext {
public parentContext: AnimationTimelineContext|null = null; public parentContext: AnimationTimelineContext|null = null;
public currentTimeline: TimelineBuilder; public currentTimeline: TimelineBuilder;
public currentAnimateTimings: AnimateTimings|null = null; public currentAnimateTimings: AnimateTimings|null = null;
public previousNode: Ast = DEFAULT_NOOP_PREVIOUS_NODE; public previousNode: Ast<AnimationMetadataType> = DEFAULT_NOOP_PREVIOUS_NODE;
public subContextCount = 0; public subContextCount = 0;
public options: AnimationOptions = {}; public options: AnimationOptions = {};
public currentQueryIndex: number = 0; public currentQueryIndex: number = 0;
@ -489,7 +489,7 @@ export class AnimationTimelineContext {
const oldParams = this.options.params; const oldParams = this.options.params;
if (oldParams) { if (oldParams) {
const params: {[name: string]: any} = options['params'] = {}; const params: {[name: string]: any} = options['params'] = {};
Object.keys(this.options.params).forEach(name => { params[name] = oldParams[name]; }); Object.keys(oldParams).forEach(name => { params[name] = oldParams[name]; });
} }
} }
return options; return options;

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ɵStyleData} from '@angular/animations'; import {AnimationMetadataType, ɵStyleData} from '@angular/animations';
import {copyStyles, interpolateParams} from '../util'; import {copyStyles, interpolateParams} from '../util';
@ -13,6 +13,7 @@ import {SequenceAst, StyleAst, TransitionAst, TriggerAst} from './animation_ast'
import {AnimationStateStyles, AnimationTransitionFactory} from './animation_transition_factory'; import {AnimationStateStyles, AnimationTransitionFactory} from './animation_transition_factory';
/** /**
* @experimental Animation support is experimental. * @experimental Animation support is experimental.
*/ */
@ -60,8 +61,15 @@ function createFallbackTransition(
triggerName: string, triggerName: string,
states: {[stateName: string]: AnimationStateStyles}): AnimationTransitionFactory { states: {[stateName: string]: AnimationStateStyles}): AnimationTransitionFactory {
const matchers = [(fromState: any, toState: any) => true]; const matchers = [(fromState: any, toState: any) => true];
const animation = new SequenceAst([]); const animation: SequenceAst = {type: AnimationMetadataType.Sequence, steps: [], options: null};
const transition = new TransitionAst(matchers, animation); const transition: TransitionAst = {
type: AnimationMetadataType.Transition,
animation,
matchers,
options: null,
queryCount: 0,
depCount: 0
};
return new AnimationTransitionFactory(triggerName, transition, states); return new AnimationTransitionFactory(triggerName, transition, states);
} }

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AUTO_STYLE, AnimationMetadata, AnimationOptions, AnimationPlayer, ɵStyleData} from '@angular/animations'; import {AUTO_STYLE, AnimationMetadata, AnimationMetadataType, AnimationOptions, AnimationPlayer, ɵStyleData} from '@angular/animations';
import {Ast} from '../dsl/animation_ast'; import {Ast} from '../dsl/animation_ast';
import {buildAnimationAst} from '../dsl/animation_ast_builder'; import {buildAnimationAst} from '../dsl/animation_ast_builder';
@ -20,7 +20,7 @@ import {getOrSetAsInMap, listenOnPlayer, makeAnimationEvent, normalizeKeyframes,
const EMPTY_INSTRUCTION_MAP = new ElementInstructionMap(); const EMPTY_INSTRUCTION_MAP = new ElementInstructionMap();
export class TimelineAnimationEngine { export class TimelineAnimationEngine {
private _animations: {[id: string]: Ast} = {}; private _animations: {[id: string]: Ast<AnimationMetadataType>} = {};
private _playersById: {[id: string]: AnimationPlayer} = {}; private _playersById: {[id: string]: AnimationPlayer} = {};
public players: AnimationPlayer[] = []; public players: AnimationPlayer[] = [];

View File

@ -5,7 +5,9 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AnimateTimings, AnimationMetadata, AnimationOptions, sequence, ɵStyleData} from '@angular/animations'; import {AnimateTimings, AnimationMetadata, AnimationMetadataType, AnimationOptions, sequence, ɵStyleData} from '@angular/animations';
import {Ast as AnimationAst, AstVisitor as AnimationAstVisitor} from './dsl/animation_ast';
import {AnimationDslVisitor} from './dsl/animation_dsl_visitor';
export const ONE_SECOND = 1000; export const ONE_SECOND = 1000;
@ -232,3 +234,40 @@ export function dashCaseToCamelCase(input: string): string {
export function allowPreviousPlayerStylesMerge(duration: number, delay: number) { export function allowPreviousPlayerStylesMerge(duration: number, delay: number) {
return duration === 0 || delay === 0; return duration === 0 || delay === 0;
} }
export function visitDslNode(
visitor: AnimationDslVisitor, node: AnimationMetadata, context: any): any;
export function visitDslNode(
visitor: AnimationAstVisitor, node: AnimationAst<AnimationMetadataType>, context: any): any;
export function visitDslNode(visitor: any, node: any, context: any): any {
switch (node.type) {
case AnimationMetadataType.Trigger:
return visitor.visitTrigger(node, context);
case AnimationMetadataType.State:
return visitor.visitState(node, context);
case AnimationMetadataType.Transition:
return visitor.visitTransition(node, context);
case AnimationMetadataType.Sequence:
return visitor.visitSequence(node, context);
case AnimationMetadataType.Group:
return visitor.visitGroup(node, context);
case AnimationMetadataType.Animate:
return visitor.visitAnimate(node, context);
case AnimationMetadataType.Keyframes:
return visitor.visitKeyframes(node, context);
case AnimationMetadataType.Style:
return visitor.visitStyle(node, context);
case AnimationMetadataType.Reference:
return visitor.visitReference(node, context);
case AnimationMetadataType.AnimateChild:
return visitor.visitAnimateChild(node, context);
case AnimationMetadataType.AnimateRef:
return visitor.visitAnimateRef(node, context);
case AnimationMetadataType.Query:
return visitor.visitQuery(node, context);
case AnimationMetadataType.Stagger:
return visitor.visitStagger(node, context);
default:
throw new Error(`Unable to resolve animation metadata node #${node.type}`);
}
}