feat(ivy): generate pipe references and definitions (#22034)
PR Close #22034
This commit is contained in:

committed by
Victor Berchet

parent
ee60bb5b36
commit
99909bbf2c
@ -20,6 +20,7 @@ import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
import {OutputEmitter} from '../output/abstract_emitter';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ParseError} from '../parse_util';
|
||||
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
|
||||
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
|
||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
@ -356,14 +357,22 @@ export class AotCompiler {
|
||||
error(
|
||||
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
|
||||
|
||||
const {template: parsedTemplate} =
|
||||
const {template: parsedTemplate, pipes: parsedPipes} =
|
||||
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
||||
compileIvyComponent(context, directiveMetadata, parsedTemplate, this._reflector);
|
||||
compileIvyComponent(
|
||||
context, directiveMetadata, parsedPipes, parsedTemplate, this._reflector);
|
||||
} else {
|
||||
compileIvyDirective(context, directiveMetadata, this._reflector);
|
||||
}
|
||||
});
|
||||
|
||||
pipes.forEach(pipeType => {
|
||||
const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType);
|
||||
if (pipeMetadata) {
|
||||
compileIvyPipe(context, pipeMetadata, this._reflector);
|
||||
}
|
||||
});
|
||||
|
||||
injectables.forEach(injectable => this._injectableCompiler.compile(injectable, context));
|
||||
|
||||
if (context.statements && context.statements.length > 0) {
|
||||
|
@ -11,7 +11,7 @@ import {OutputContext, error} from './util';
|
||||
|
||||
const CONSTANT_PREFIX = '_c';
|
||||
|
||||
export const enum DefinitionKind {Injector, Directive, Component}
|
||||
export const enum DefinitionKind {Injector, Directive, Component, Pipe}
|
||||
|
||||
/**
|
||||
* A node that is a place-holder that allows the node to be replaced when the actual
|
||||
@ -51,6 +51,7 @@ export class ConstantPool {
|
||||
private injectorDefinitions = new Map<any, FixupExpression>();
|
||||
private directiveDefinitions = new Map<any, FixupExpression>();
|
||||
private componentDefinitions = new Map<any, FixupExpression>();
|
||||
private pipeDefinitions = new Map<any, FixupExpression>();
|
||||
|
||||
private nextNameIndex = 0;
|
||||
|
||||
@ -75,18 +76,19 @@ export class ConstantPool {
|
||||
return fixup;
|
||||
}
|
||||
|
||||
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext): o.Expression {
|
||||
const declarations = kind == DefinitionKind.Component ?
|
||||
this.componentDefinitions :
|
||||
kind == DefinitionKind.Directive ? this.directiveDefinitions : this.injectorDefinitions;
|
||||
let fixup = declarations.get(type);
|
||||
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext, forceShared: boolean = false):
|
||||
o.Expression {
|
||||
const definitions = this.definitionsOf(kind);
|
||||
let fixup = definitions.get(type);
|
||||
let newValue = false;
|
||||
if (!fixup) {
|
||||
const property = kind == DefinitionKind.Component ?
|
||||
'ngComponentDef' :
|
||||
kind == DefinitionKind.Directive ? 'ngDirectiveDef' : 'ngInjectorDef';
|
||||
const property = this.propertyNameOf(kind);
|
||||
fixup = new FixupExpression(ctx.importExpr(type).prop(property));
|
||||
declarations.set(type, fixup);
|
||||
} else if (!fixup.shared) {
|
||||
definitions.set(type, fixup);
|
||||
newValue = true;
|
||||
}
|
||||
|
||||
if ((!newValue && !fixup.shared) || (newValue && forceShared)) {
|
||||
const name = this.freshName();
|
||||
this.statements.push(
|
||||
o.variable(name).set(fixup.resolved).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||
@ -104,6 +106,36 @@ export class ConstantPool {
|
||||
*/
|
||||
uniqueName(prefix: string): string { return `${prefix}${this.nextNameIndex++}`; }
|
||||
|
||||
private definitionsOf(kind: DefinitionKind): Map<any, FixupExpression> {
|
||||
switch (kind) {
|
||||
case DefinitionKind.Component:
|
||||
return this.componentDefinitions;
|
||||
case DefinitionKind.Directive:
|
||||
return this.directiveDefinitions;
|
||||
case DefinitionKind.Injector:
|
||||
return this.injectorDefinitions;
|
||||
case DefinitionKind.Pipe:
|
||||
return this.pipeDefinitions;
|
||||
}
|
||||
error(`Unknown definition kind ${kind}`);
|
||||
return this.componentDefinitions;
|
||||
}
|
||||
|
||||
public propertyNameOf(kind: DefinitionKind): string {
|
||||
switch (kind) {
|
||||
case DefinitionKind.Component:
|
||||
return 'ngComponentDef';
|
||||
case DefinitionKind.Directive:
|
||||
return 'ngDirectiveDef';
|
||||
case DefinitionKind.Injector:
|
||||
return 'ngInjectorDef';
|
||||
case DefinitionKind.Pipe:
|
||||
return 'ngPipeDef';
|
||||
}
|
||||
error(`Unknown definition kind ${kind}`);
|
||||
return '<unknown>';
|
||||
}
|
||||
|
||||
private freshName(): string { return this.uniqueName(CONSTANT_PREFIX); }
|
||||
|
||||
private keyOf(expression: o.Expression) {
|
||||
|
@ -435,6 +435,173 @@ export class AstTransformer implements AstVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
// A transformer that only creates new nodes if the transformer makes a change or
|
||||
// a change is made a child node.
|
||||
export class AstMemoryEfficientTransformer implements AstVisitor {
|
||||
visitImplicitReceiver(ast: ImplicitReceiver, context: any): AST { return ast; }
|
||||
|
||||
visitInterpolation(ast: Interpolation, context: any): Interpolation {
|
||||
const expressions = this.visitAll(ast.expressions);
|
||||
if (expressions !== ast.expressions)
|
||||
return new Interpolation(ast.span, ast.strings, expressions);
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): AST { return ast; }
|
||||
|
||||
visitPropertyRead(ast: PropertyRead, context: any): AST {
|
||||
const receiver = ast.receiver.visit(this);
|
||||
if (receiver !== ast.receiver) {
|
||||
return new PropertyRead(ast.span, receiver, ast.name);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitPropertyWrite(ast: PropertyWrite, context: any): AST {
|
||||
const receiver = ast.receiver.visit(this);
|
||||
const value = ast.value.visit(this);
|
||||
if (receiver !== ast.receiver || value !== ast.value) {
|
||||
return new PropertyWrite(ast.span, receiver, ast.name, value);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitSafePropertyRead(ast: SafePropertyRead, context: any): AST {
|
||||
const receiver = ast.receiver.visit(this);
|
||||
if (receiver !== ast.receiver) {
|
||||
return new SafePropertyRead(ast.span, receiver, ast.name);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitMethodCall(ast: MethodCall, context: any): AST {
|
||||
const receiver = ast.receiver.visit(this);
|
||||
if (receiver !== ast.receiver) {
|
||||
return new MethodCall(ast.span, receiver, ast.name, this.visitAll(ast.args));
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitSafeMethodCall(ast: SafeMethodCall, context: any): AST {
|
||||
const receiver = ast.receiver.visit(this);
|
||||
const args = this.visitAll(ast.args);
|
||||
if (receiver !== ast.receiver || args !== ast.args) {
|
||||
return new SafeMethodCall(ast.span, receiver, ast.name, args);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitFunctionCall(ast: FunctionCall, context: any): AST {
|
||||
const target = ast.target && ast.target.visit(this);
|
||||
const args = this.visitAll(ast.args);
|
||||
if (target !== ast.target || args !== ast.args) {
|
||||
return new FunctionCall(ast.span, target, args);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitLiteralArray(ast: LiteralArray, context: any): AST {
|
||||
const expressions = this.visitAll(ast.expressions);
|
||||
if (expressions !== ast.expressions) {
|
||||
return new LiteralArray(ast.span, expressions);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitLiteralMap(ast: LiteralMap, context: any): AST {
|
||||
const values = this.visitAll(ast.values);
|
||||
if (values !== ast.values) {
|
||||
return new LiteralMap(ast.span, ast.keys, values);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitBinary(ast: Binary, context: any): AST {
|
||||
const left = ast.left.visit(this);
|
||||
const right = ast.right.visit(this);
|
||||
if (left !== ast.left || right !== ast.right) {
|
||||
return new Binary(ast.span, ast.operation, left, right);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitPrefixNot(ast: PrefixNot, context: any): AST {
|
||||
const expression = ast.expression.visit(this);
|
||||
if (expression !== ast.expression) {
|
||||
return new PrefixNot(ast.span, expression);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitNonNullAssert(ast: NonNullAssert, context: any): AST {
|
||||
const expression = ast.expression.visit(this);
|
||||
if (expression !== ast.expression) {
|
||||
return new NonNullAssert(ast.span, expression);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitConditional(ast: Conditional, context: any): AST {
|
||||
const condition = ast.condition.visit(this);
|
||||
const trueExp = ast.trueExp.visit(this);
|
||||
const falseExp = ast.falseExp.visit(this);
|
||||
if (condition !== ast.condition || trueExp !== ast.trueExp || falseExp !== falseExp) {
|
||||
return new Conditional(ast.span, condition, trueExp, falseExp);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitPipe(ast: BindingPipe, context: any): AST {
|
||||
const exp = ast.exp.visit(this);
|
||||
const args = this.visitAll(ast.args);
|
||||
if (exp !== ast.exp || args !== ast.args) {
|
||||
return new BindingPipe(ast.span, exp, ast.name, args);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitKeyedRead(ast: KeyedRead, context: any): AST {
|
||||
const obj = ast.obj.visit(this);
|
||||
const key = ast.key.visit(this);
|
||||
if (obj !== ast.obj || key !== ast.key) {
|
||||
return new KeyedRead(ast.span, obj, key);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitKeyedWrite(ast: KeyedWrite, context: any): AST {
|
||||
const obj = ast.obj.visit(this);
|
||||
const key = ast.key.visit(this);
|
||||
const value = ast.value.visit(this);
|
||||
if (obj !== ast.obj || key !== ast.key || value !== ast.value) {
|
||||
return new KeyedWrite(ast.span, obj, key, value);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitAll(asts: any[]): any[] {
|
||||
const res = new Array(asts.length);
|
||||
let modified = false;
|
||||
for (let i = 0; i < asts.length; ++i) {
|
||||
const original = asts[i];
|
||||
const value = original.visit(this);
|
||||
res[i] = value;
|
||||
modified = modified || value !== original;
|
||||
}
|
||||
return modified ? res : asts;
|
||||
}
|
||||
|
||||
visitChain(ast: Chain, context: any): AST {
|
||||
const expressions = this.visitAll(ast.expressions);
|
||||
if (expressions !== ast.expressions) {
|
||||
return new Chain(ast.span, expressions);
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitQuote(ast: Quote, context: any): AST { return ast; }
|
||||
}
|
||||
|
||||
export function visitAstChildren(ast: AST, visitor: AstVisitor, context?: any) {
|
||||
function visit(ast: AST) {
|
||||
visitor.visit && visitor.visit(ast, context) || ast.visit(visitor, context);
|
||||
|
@ -21,6 +21,7 @@ export class Identifiers {
|
||||
/* Methods */
|
||||
static NEW_METHOD = 'n';
|
||||
static HOST_BINDING_METHOD = 'h';
|
||||
static TRANSFORM_METHOD = 'transform';
|
||||
|
||||
/* Instructions */
|
||||
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
|
||||
@ -63,8 +64,16 @@ export class Identifiers {
|
||||
static interpolation8: o.ExternalReference = {name: 'ɵi8', moduleName: CORE};
|
||||
static interpolationV: o.ExternalReference = {name: 'ɵiV', moduleName: CORE};
|
||||
|
||||
static pipeBind1: o.ExternalReference = {name: 'ɵpb1', moduleName: CORE};
|
||||
static pipeBind2: o.ExternalReference = {name: 'ɵpb2', moduleName: CORE};
|
||||
static pipeBind3: o.ExternalReference = {name: 'ɵpb3', moduleName: CORE};
|
||||
static pipeBind4: o.ExternalReference = {name: 'ɵpb4', moduleName: CORE};
|
||||
static pipeBindV: o.ExternalReference = {name: 'ɵpbV', moduleName: CORE};
|
||||
|
||||
static load: o.ExternalReference = {name: 'ɵld', moduleName: CORE};
|
||||
|
||||
static pipe: o.ExternalReference = {name: 'ɵPp', moduleName: CORE};
|
||||
|
||||
static projection: o.ExternalReference = {name: 'ɵP', moduleName: CORE};
|
||||
static projectionDef: o.ExternalReference = {name: 'ɵpD', moduleName: CORE};
|
||||
|
||||
@ -88,5 +97,7 @@ export class Identifiers {
|
||||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
|
||||
|
||||
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
|
||||
}
|
50
packages/compiler/src/render3/r3_pipe_compiler.ts
Normal file
50
packages/compiler/src/render3/r3_pipe_compiler.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
import {CompileDirectiveMetadata, CompilePipeMetadata, identifierName} from '../compile_metadata';
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {DefinitionKind} from '../constant_pool';
|
||||
import * as o from '../output/output_ast';
|
||||
import {OutputContext, error} from '../util';
|
||||
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
import {createFactory} from './r3_view_compiler';
|
||||
|
||||
export function compilePipe(
|
||||
outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector) {
|
||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||
|
||||
// e.g. 'type: MyPipe`
|
||||
definitionMapValues.push(
|
||||
{key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false});
|
||||
|
||||
// e.g. factory: function MyPipe_Factory() { return new MyPipe(); },
|
||||
const templateFactory = createFactory(pipe.type, outputCtx, reflector);
|
||||
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
|
||||
|
||||
// e.g. pure: true
|
||||
if (pipe.pure) {
|
||||
definitionMapValues.push({key: 'pure', value: o.literal(true), quoted: false});
|
||||
}
|
||||
|
||||
const className = identifierName(pipe.type) !;
|
||||
className || error(`Cannot resolve the name of ${pipe.type}`);
|
||||
|
||||
outputCtx.statements.push(new o.ClassStmt(
|
||||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[new o.ClassField(
|
||||
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe),
|
||||
/* type */ o.INFERRED_TYPE,
|
||||
/* modifiers */[o.StmtModifier.Static],
|
||||
/* initializer */ o.importExpr(R3.definePipe).callFn([o.literalMap(
|
||||
definitionMapValues)]))],
|
||||
/* getters */[],
|
||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||
/* methods */[]));
|
||||
}
|
@ -8,9 +8,9 @@
|
||||
|
||||
import {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||
import {BindingForm, BuiltinConverter, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||
import {ConstantPool, DefinitionKind} from '../constant_pool';
|
||||
import {AST} from '../expression_parser/ast';
|
||||
import {AST, AstMemoryEfficientTransformer, AstTransformer, BindingPipe, FunctionCall, ImplicitReceiver, LiteralPrimitive, MethodCall, ParseSpan, PropertyRead} from '../expression_parser/ast';
|
||||
import {Identifiers} from '../identifiers';
|
||||
import {LifecycleHooks} from '../lifecycle_reflector';
|
||||
import * as o from '../output/output_ast';
|
||||
@ -21,6 +21,7 @@ import {OutputContext, error} from '../util';
|
||||
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
|
||||
|
||||
/** Name of the context parameter passed into a template function */
|
||||
const CONTEXT_NAME = 'ctx';
|
||||
|
||||
@ -62,7 +63,7 @@ export function compileDirective(
|
||||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[new o.ClassField(
|
||||
/* name */ 'ngDirectiveDef',
|
||||
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive),
|
||||
/* type */ o.INFERRED_TYPE,
|
||||
/* modifiers */[o.StmtModifier.Static],
|
||||
/* initializer */ o.importExpr(R3.defineDirective).callFn([o.literalMap(
|
||||
@ -73,8 +74,8 @@ export function compileDirective(
|
||||
}
|
||||
|
||||
export function compileComponent(
|
||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
reflector: CompileReflector) {
|
||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, pipes: CompilePipeSummary[],
|
||||
template: TemplateAst[], reflector: CompileReflector) {
|
||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||
|
||||
// e.g. `type: MyApp`
|
||||
@ -112,10 +113,11 @@ export function compileComponent(
|
||||
// e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
|
||||
const templateTypeName = component.type.reference.name;
|
||||
const templateName = templateTypeName ? `${templateTypeName}_Template` : null;
|
||||
const pipeMap = new Map(pipes.map<[string, CompilePipeSummary]>(pipe => [pipe.name, pipe]));
|
||||
const templateFunctionExpression =
|
||||
new TemplateDefinitionBuilder(
|
||||
outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, ROOT_SCOPE.nestedScope(), 0,
|
||||
component.template !.ngContentSelectors, templateTypeName, templateName)
|
||||
component.template !.ngContentSelectors, templateTypeName, templateName, pipeMap)
|
||||
.buildTemplateFunction(template, []);
|
||||
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
|
||||
|
||||
@ -143,7 +145,7 @@ export function compileComponent(
|
||||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[new o.ClassField(
|
||||
/* name */ 'ngComponentDef',
|
||||
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Component),
|
||||
/* type */ o.INFERRED_TYPE,
|
||||
/* modifiers */[o.StmtModifier.Static],
|
||||
/* initializer */ o.importExpr(R3.defineComponent).callFn([o.literalMap(
|
||||
@ -199,6 +201,22 @@ function interpolate(args: o.Expression[]): o.Expression {
|
||||
return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]);
|
||||
}
|
||||
|
||||
function pipeBinding(args: o.Expression[]): o.ExternalReference {
|
||||
switch (args.length) {
|
||||
case 0:
|
||||
// The first parameter to pipeBind is always the value to be transformed followed
|
||||
// by arg.length arguments so the total number of arguments to pipeBind are
|
||||
// arg.length + 1.
|
||||
return R3.pipeBind1;
|
||||
case 1:
|
||||
return R3.pipeBind2;
|
||||
case 2:
|
||||
return R3.pipeBind3;
|
||||
default:
|
||||
return R3.pipeBindV;
|
||||
}
|
||||
}
|
||||
|
||||
class BindingScope {
|
||||
private map = new Map<string, o.Expression>();
|
||||
private referenceNameIndex = 0;
|
||||
@ -219,10 +237,10 @@ class BindingScope {
|
||||
return null;
|
||||
}
|
||||
|
||||
set(name: string, variableName: string): BindingScope {
|
||||
set(name: string, value: o.Expression): BindingScope {
|
||||
!this.map.has(name) ||
|
||||
error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
|
||||
this.map.set(name, o.variable(variableName));
|
||||
this.map.set(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -236,7 +254,7 @@ class BindingScope {
|
||||
}
|
||||
}
|
||||
|
||||
const ROOT_SCOPE = new BindingScope(null).set('$event', '$event');
|
||||
const ROOT_SCOPE = new BindingScope(null).set('$event', o.variable('$event'));
|
||||
|
||||
class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
private _dataIndex = 0;
|
||||
@ -251,6 +269,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
private _postfix: o.Statement[] = [];
|
||||
private _contentProjections: Map<NgContentAst, NgContentInfo>;
|
||||
private _projectionDefinitionIndex = 0;
|
||||
private _pipeConverter: PipeConverter;
|
||||
private unsupported = unsupported;
|
||||
private invalid = invalid;
|
||||
|
||||
@ -258,7 +277,23 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
private outputCtx: OutputContext, private constantPool: ConstantPool,
|
||||
private reflector: CompileReflector, private contextParameter: string,
|
||||
private bindingScope: BindingScope, private level = 0, private ngContentSelectors: string[],
|
||||
private contextName: string|null, private templateName: string|null) {}
|
||||
private contextName: string|null, private templateName: string|null,
|
||||
private pipes: Map<string, CompilePipeSummary>) {
|
||||
this._pipeConverter =
|
||||
new PipeConverter(() => this.allocateDataSlot(), (name, localName, slot, value) => {
|
||||
bindingScope.set(localName, value);
|
||||
const pipe = pipes.get(name) !;
|
||||
pipe || error(`Could not find pipe ${name}`);
|
||||
const pipeDefinition = constantPool.getDefinition(
|
||||
pipe.type.reference, DefinitionKind.Pipe, outputCtx, /* forceShared */ true);
|
||||
this._creationMode.push(
|
||||
o.importExpr(R3.pipe)
|
||||
.callFn([
|
||||
o.literal(slot), pipeDefinition, pipeDefinition.callMethod(R3.NEW_METHOD, [])
|
||||
])
|
||||
.toStmt());
|
||||
});
|
||||
}
|
||||
|
||||
buildTemplateFunction(asts: TemplateAst[], variables: VariableAst[]): o.FunctionExpr {
|
||||
// Create variable bindings
|
||||
@ -272,7 +307,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
]);
|
||||
|
||||
// Add the reference to the local scope.
|
||||
this.bindingScope.set(variableName, scopedName);
|
||||
this.bindingScope.set(variableName, o.variable(scopedName));
|
||||
|
||||
// Declare the local variable in binding mode
|
||||
this._bindingMode.push(declaration);
|
||||
@ -332,8 +367,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
o.INFERRED_TYPE, null, this.templateName);
|
||||
}
|
||||
|
||||
// LocalResolver
|
||||
getLocal(name: string): o.Expression|null { return this.bindingScope.get(name); }
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitNgContent(ast: NgContentAst) {
|
||||
const info = this._contentProjections.get(ast) !;
|
||||
info || error(`Expected ${ast.sourceSpan} to be included in content projection collection`);
|
||||
@ -361,6 +398,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
};
|
||||
}
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitElement(ast: ElementAst) {
|
||||
let bindingCount = 0;
|
||||
const elementIndex = this.allocateDataSlot();
|
||||
@ -410,7 +448,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
this._bindingMode.push(o.variable(variableName, o.INFERRED_TYPE)
|
||||
.set(o.importExpr(R3.load).callFn([o.literal(slot)]))
|
||||
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||
this.bindingScope.set(reference.name, variableName);
|
||||
this.bindingScope.set(reference.name, o.variable(variableName));
|
||||
return [reference.name, reference.originalValue];
|
||||
})).map(value => o.literal(value));
|
||||
parameters.push(
|
||||
@ -434,18 +472,14 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
if (input.isAnimation) {
|
||||
this.unsupported('animations');
|
||||
}
|
||||
// TODO(chuckj): Built-in transform?
|
||||
const convertedBinding = convertPropertyBinding(
|
||||
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||
this._bindingMode.push(...convertedBinding.stmts);
|
||||
const parameters =
|
||||
[o.literal(elementIndex), o.literal(input.name), convertedBinding.currValExpr];
|
||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||
const parameters = [o.literal(elementIndex), o.literal(input.name), convertedBinding];
|
||||
const instruction = BINDING_INSTRUCTION_MAP[input.type];
|
||||
if (instruction) {
|
||||
// TODO(chuckj): runtime: security context?
|
||||
this.instruction(
|
||||
this._bindingMode, input.sourceSpan, instruction, o.literal(elementIndex),
|
||||
o.literal(input.name), convertedBinding.currValExpr);
|
||||
o.literal(input.name), convertedBinding);
|
||||
} else {
|
||||
this.unsupported(`binding ${PropertyBindingType[input.type]}`);
|
||||
}
|
||||
@ -479,13 +513,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
|
||||
// Bindings
|
||||
for (const input of directive.inputs) {
|
||||
const convertedBinding = convertPropertyBinding(
|
||||
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||
this._bindingMode.push(...convertedBinding.stmts);
|
||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||
this.instruction(
|
||||
this._bindingMode, directive.sourceSpan, R3.elementProperty, o.literal(nodeIndex),
|
||||
o.literal(input.templateName),
|
||||
o.importExpr(R3.bind).callFn([convertedBinding.currValExpr]));
|
||||
o.literal(input.templateName), o.importExpr(R3.bind).callFn([convertedBinding]));
|
||||
}
|
||||
|
||||
// e.g. MyDirective.ngDirectiveDef.h(0, 0);
|
||||
@ -501,6 +532,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
}
|
||||
}
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst) {
|
||||
const templateIndex = this.allocateDataSlot();
|
||||
|
||||
@ -539,7 +571,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
const templateVisitor = new TemplateDefinitionBuilder(
|
||||
this.outputCtx, this.constantPool, this.reflector, templateContext,
|
||||
this.bindingScope.nestedScope(), this.level + 1, this.ngContentSelectors, contextName,
|
||||
templateName);
|
||||
templateName, this.pipes);
|
||||
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children, ast.variables);
|
||||
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
||||
}
|
||||
@ -551,6 +583,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
readonly visitElementProperty = invalid;
|
||||
readonly visitAttr = invalid;
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitBoundText(ast: BoundTextAst) {
|
||||
const nodeIndex = this.allocateDataSlot();
|
||||
|
||||
@ -563,6 +596,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
this.bind(o.variable(CONTEXT_NAME), ast.value, ast.sourceSpan));
|
||||
}
|
||||
|
||||
// TemplateAstVisitor
|
||||
visitText(ast: TextAst) {
|
||||
// Text is defined in creation mode only.
|
||||
this.instruction(
|
||||
@ -600,8 +634,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
}
|
||||
|
||||
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
|
||||
const pipesConvertedValue = value.visit(this._pipeConverter);
|
||||
const convertedPropertyBinding = convertPropertyBinding(
|
||||
this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolate);
|
||||
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
|
||||
interpolate);
|
||||
this._refreshMode.push(...convertedPropertyBinding.stmts);
|
||||
return convertedPropertyBinding.currValExpr;
|
||||
}
|
||||
@ -611,7 +647,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
}
|
||||
}
|
||||
|
||||
function createFactory(
|
||||
export function createFactory(
|
||||
type: CompileTypeMetadata, outputCtx: OutputContext,
|
||||
reflector: CompileReflector): o.FunctionExpr {
|
||||
let args: o.Expression[] = [];
|
||||
@ -652,6 +688,35 @@ function createFactory(
|
||||
o.INFERRED_TYPE, null, type.reference.name ? `${type.reference.name}_Factory` : null);
|
||||
}
|
||||
|
||||
class PipeConverter extends AstMemoryEfficientTransformer {
|
||||
private pipeSlots = new Map<string, number>();
|
||||
constructor(
|
||||
private allocateSlot: () => number,
|
||||
private definePipe:
|
||||
(name: string, localName: string, slot: number, value: o.Expression) => void) {
|
||||
super();
|
||||
}
|
||||
|
||||
// AstMemoryEfficientTransformer
|
||||
visitPipe(ast: BindingPipe, context: any): AST {
|
||||
// Allocate a slot to create the pipe
|
||||
let slot = this.pipeSlots.get(ast.name);
|
||||
if (slot == null) {
|
||||
slot = this.allocateSlot();
|
||||
this.pipeSlots.set(ast.name, slot);
|
||||
}
|
||||
const slotPseudoLocal = `PIPE:${slot}`;
|
||||
const target = new PropertyRead(ast.span, new ImplicitReceiver(ast.span), slotPseudoLocal);
|
||||
const bindingId = pipeBinding(ast.args);
|
||||
this.definePipe(ast.name, slotPseudoLocal, slot, o.importExpr(bindingId));
|
||||
const value = ast.exp.visit(this);
|
||||
const args = this.visitAll(ast.args);
|
||||
|
||||
return new FunctionCall(
|
||||
ast.span, target, [new LiteralPrimitive(ast.span, slot), value, ...args]);
|
||||
}
|
||||
}
|
||||
|
||||
function invalid<T>(arg: o.Expression | o.Statement | TemplateAst): never {
|
||||
throw new Error(
|
||||
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
|
||||
|
Reference in New Issue
Block a user