From dbdcfed2bd7015665890f0718598beb1c873506e Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Tue, 26 Jun 2018 10:43:06 -0700 Subject: [PATCH] feat(ivy): support pipe compilation from local metadata (#24703) This updates the r3_pipe_compiler to not depend on global analysis, and to produce ngPipeDef instructions in the same way that the other compilers do. It's a precursor to JIT and AOT implementations of @Pipe compilation. PR Close #24703 --- packages/compiler/src/aot/compiler.ts | 2 +- packages/compiler/src/compiler.ts | 1 + .../compiler/src/render3/r3_identifiers.ts | 2 + .../compiler/src/render3/r3_pipe_compiler.ts | 73 +++++++++++++------ .../compiler/test/render3/mock_compile.ts | 4 +- .../render3/r3_compiler_compliance_spec.ts | 2 +- packages/core/src/render3/definition.ts | 6 +- .../core/src/render3/interfaces/definition.ts | 11 ++- packages/core/src/render3/interfaces/view.ts | 4 +- packages/core/src/render3/pipe.ts | 10 +-- packages/core/test/render3/render_util.ts | 6 +- 11 files changed, 78 insertions(+), 43 deletions(-) diff --git a/packages/compiler/src/aot/compiler.ts b/packages/compiler/src/aot/compiler.ts index 45d80a6206..660c1eeb26 100644 --- a/packages/compiler/src/aot/compiler.ts +++ b/packages/compiler/src/aot/compiler.ts @@ -23,7 +23,7 @@ import {OutputEmitter} from '../output/abstract_emitter'; import * as o from '../output/output_ast'; import {ParseError} from '../parse_util'; import {compileNgModuleFromRender2 as compileR3Module} from '../render3/r3_module_compiler'; -import {compilePipe as compileR3Pipe} from '../render3/r3_pipe_compiler'; +import {compilePipeFromRender2 as compileR3Pipe} from '../render3/r3_pipe_compiler'; import {htmlAstToRender3Ast} from '../render3/r3_template_transform'; import {compileComponentFromRender2 as compileR3Component, compileDirectiveFromRender2 as compileR3Directive} from '../render3/view/compiler'; import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 22ca91c682..98398f2716 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -84,6 +84,7 @@ export * from './render3/view/api'; export {jitExpression} from './render3/r3_jit'; export {R3DependencyMetadata, R3FactoryMetadata, R3ResolvedDependencyType} from './render3/r3_factory'; export {compileInjector, compileNgModule, R3InjectorMetadata, R3NgModuleMetadata} from './render3/r3_module_compiler'; +export {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler'; export {makeBindingParser, parseTemplate} from './render3/view/template'; export {compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings} from './render3/view/compiler'; // This file only reexports content of the `src` folder. Keep it that way. \ No newline at end of file diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 78dd3ca0a7..f5acfbca01 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -131,6 +131,8 @@ export class Identifiers { static defineNgModule: o.ExternalReference = {name: 'ɵdefineNgModule', moduleName: CORE}; + static PipeDef: o.ExternalReference = {name: 'ɵPipeDef', moduleName: CORE}; + static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE}; static query: o.ExternalReference = {name: 'ɵQ', moduleName: CORE}; diff --git a/packages/compiler/src/render3/r3_pipe_compiler.ts b/packages/compiler/src/render3/r3_pipe_compiler.ts index a7456b4e7a..4f534054e2 100644 --- a/packages/compiler/src/render3/r3_pipe_compiler.ts +++ b/packages/compiler/src/render3/r3_pipe_compiler.ts @@ -12,55 +12,84 @@ import {DefinitionKind} from '../constant_pool'; import * as o from '../output/output_ast'; import {OutputContext, error} from '../util'; -import {compileFactoryFunction, dependenciesFromGlobalMetadata} from './r3_factory'; +import {R3DependencyMetadata, compileFactoryFunction, dependenciesFromGlobalMetadata} from './r3_factory'; import {Identifiers as R3} from './r3_identifiers'; +export interface R3PipeMetadata { + name: string; + type: o.Expression; + pipeName: string; + deps: R3DependencyMetadata[]; + pure: boolean; +} -/** - * Write a pipe definition to the output context. - */ -export function compilePipe( - outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector) { +export interface R3PipeDef { + expression: o.Expression; + type: o.Type; +} + +export function compilePipeFromMetadata(metadata: R3PipeMetadata) { const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = []; // e.g. `name: 'myPipe'` - definitionMapValues.push({key: 'name', value: o.literal(pipe.name), quoted: false}); + definitionMapValues.push({key: 'name', value: o.literal(metadata.pipeName), quoted: false}); // e.g. `type: MyPipe` - definitionMapValues.push( - {key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false}); + definitionMapValues.push({key: 'type', value: metadata.type, quoted: false}); - // e.g. `factory: function MyPipe_Factory() { return new MyPipe(); }` - const deps = dependenciesFromGlobalMetadata(pipe.type, outputCtx, reflector); const templateFactory = compileFactoryFunction({ - name: identifierName(pipe.type) !, - fnOrClass: outputCtx.importExpr(pipe.type.reference), deps, + name: metadata.name, + fnOrClass: metadata.type, + deps: metadata.deps, useNew: true, injectFn: R3.directiveInject, }); 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}); + definitionMapValues.push({key: 'pure', value: o.literal(metadata.pure), quoted: false}); + + const expression = o.importExpr(R3.definePipe).callFn([o.literalMap(definitionMapValues)]); + const type = new o.ExpressionType(o.importExpr(R3.PipeDef, [ + new o.ExpressionType(metadata.type), + new o.ExpressionType(new o.LiteralExpr(metadata.pipeName)), + ])); + return {expression, type}; +} + +/** + * Write a pipe definition to the output context. + */ +export function compilePipeFromRender2( + outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector) { + const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = []; + + const name = identifierName(pipe.type); + if (!name) { + return error(`Cannot resolve the name of ${pipe.type}`); } - const className = identifierName(pipe.type) !; - className || error(`Cannot resolve the name of ${pipe.type}`); + const metadata: R3PipeMetadata = { + name, + pipeName: pipe.name, + type: outputCtx.importExpr(pipe.type.reference), + deps: dependenciesFromGlobalMetadata(pipe.type, outputCtx, reflector), + pure: pipe.pure, + }; + + const res = compilePipeFromMetadata(metadata); const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe); - const definitionFunction = - o.importExpr(R3.definePipe).callFn([o.literalMap(definitionMapValues)]); outputCtx.statements.push(new o.ClassStmt( - /* name */ className, + /* name */ name, /* parent */ null, /* fields */[new o.ClassField( /* name */ definitionField, /* type */ o.INFERRED_TYPE, /* modifiers */[o.StmtModifier.Static], - /* initializer */ definitionFunction)], + /* initializer */ res.expression)], /* getters */[], /* constructorMethod */ new o.ClassMethod(null, [], []), /* methods */[])); -} \ No newline at end of file +} diff --git a/packages/compiler/test/render3/mock_compile.ts b/packages/compiler/test/render3/mock_compile.ts index d7ec3bdad0..441a53c942 100644 --- a/packages/compiler/test/render3/mock_compile.ts +++ b/packages/compiler/test/render3/mock_compile.ts @@ -15,7 +15,7 @@ import {ConstantPool} from '../../src/constant_pool'; import * as html from '../../src/ml_parser/ast'; import {removeWhitespaces} from '../../src/ml_parser/html_whitespaces'; import * as o from '../../src/output/output_ast'; -import {compilePipe} from '../../src/render3/r3_pipe_compiler'; +import {compilePipeFromRender2} from '../../src/render3/r3_pipe_compiler'; import {htmlAstToRender3Ast} from '../../src/render3/r3_template_transform'; import {compileComponentFromRender2, compileDirectiveFromRender2} from '../../src/render3/view/compiler'; import {BindingParser} from '../../src/template_parser/binding_parser'; @@ -320,7 +320,7 @@ export function compile( } else if (resolver.isPipe(pipeOrDirective)) { const metadata = resolver.getPipeMetadata(pipeOrDirective); if (metadata) { - compilePipe(outputCtx, metadata, reflector); + compilePipeFromRender2(outputCtx, metadata, reflector); } } } diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index a7d9befbe6..332071aec4 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -1072,7 +1072,7 @@ describe('compiler compliance', () => { it('should render pipes', () => { const MyPipeDefinition = ` static ngPipeDef = $r3$.ɵdefinePipe( - {name: 'myPipe', type: MyPipe, factory: function MyPipe_Factory() { return new MyPipe(); }}); + {name: 'myPipe', type: MyPipe, factory: function MyPipe_Factory() { return new MyPipe(); }, pure: false}); `; const MyPurePipeDefinition = ` diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 2cda88b570..bcdce3f532 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -16,7 +16,7 @@ import {Type} from '../type'; import {resolveRendererType2} from '../view/util'; import {diPublic} from './di'; -import {ComponentDefFeature, ComponentDefInternal, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition'; +import {ComponentDefFeature, ComponentDefInternal, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDefInternal, PipeType, PipeTypesOrFactory} from './interfaces/definition'; import {CssSelectorList, SelectorFlags} from './interfaces/projection'; @@ -255,7 +255,7 @@ export function extractDirectiveDef(type: DirectiveType& ComponentType return def; } -export function extractPipeDef(type: PipeType): PipeDef { +export function extractPipeDef(type: PipeType): PipeDefInternal { const def = type.ngPipeDef; if (ngDevMode && !def) { throw new Error(`'${type.name}' is not a 'PipeType'.`); @@ -483,7 +483,7 @@ export function definePipe(pipeDef: { /** Whether the pipe is pure. */ pure?: boolean }): never { - return (>{ + return (>{ name: pipeDef.name, factory: pipeDef.factory, pure: pipeDef.pure !== false, diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 8df6820645..232df396a9 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -226,13 +226,13 @@ export interface ComponentDef extends DirectiveDef { +export interface PipeDef { /** * Pipe name. * * Used to resolve pipe in templates. */ - name: string; + name: S; /** * Factory function used to create a new pipe instance. @@ -251,6 +251,8 @@ export interface PipeDef { onDestroy: (() => void)|null; } +export type PipeDefInternal = PipeDef; + export type DirectiveDefFeature = (directiveDef: DirectiveDef) => void; export type ComponentDefFeature = (componentDef: ComponentDef) => void; @@ -276,12 +278,13 @@ export type DirectiveTypeList = */ export type PipeDefListOrFactory = (() => PipeDefList) | PipeDefList; -export type PipeDefList = PipeDef[]; +export type PipeDefList = PipeDefInternal[]; export type PipeTypesOrFactory = (() => DirectiveTypeList) | DirectiveTypeList; export type PipeTypeList = - (PipeDef| Type/* Type as workaround for: Microsoft/TypeScript/issues/4881 */)[]; + (PipeDefInternal| + Type/* Type as workaround for: Microsoft/TypeScript/issues/4881 */)[]; // Note: This hack is necessary so we don't erroneously get a circular dependency diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 6a68e87f7e..1548b16dd8 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -10,7 +10,7 @@ import {Injector} from '../../di/injector'; import {Sanitizer} from '../../sanitization/security'; import {LContainer} from './container'; -import {ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefList, PipeDef, PipeDefList} from './definition'; +import {ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefList, PipeDefInternal, PipeDefList} from './definition'; import {LContainerNode, LElementNode, LViewNode, TNode} from './node'; import {LQueries} from './query'; import {Renderer3} from './renderer'; @@ -456,7 +456,7 @@ export type HookData = (number | (() => void))[]; * as its pipe instance in the data array. Any nodes that do not have static * data store a null value in tData to avoid a sparse array. */ -export type TData = (TNode | PipeDef| null)[]; +export type TData = (TNode | PipeDefInternal| null)[]; /** Type for TView.currentMatches */ export type CurrentMatchesList = [DirectiveDefInternal, (string | number | null)]; diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts index 2bd2ef7cfe..d956528ede 100644 --- a/packages/core/src/render3/pipe.ts +++ b/packages/core/src/render3/pipe.ts @@ -9,7 +9,7 @@ import {PipeTransform} from '../change_detection/pipe_transform'; import {getTView, load, store} from './instructions'; -import {PipeDef, PipeDefList} from './interfaces/definition'; +import {PipeDefInternal, PipeDefList} from './interfaces/definition'; import {HEADER_OFFSET} from './interfaces/view'; import {pureFunction1, pureFunction2, pureFunction3, pureFunction4, pureFunctionV} from './pure_function'; @@ -22,7 +22,7 @@ import {pureFunction1, pureFunction2, pureFunction3, pureFunction4, pureFunction */ export function pipe(index: number, pipeName: string): any { const tView = getTView(); - let pipeDef: PipeDef; + let pipeDef: PipeDefInternal; const adjustedIndex = index + HEADER_OFFSET; if (tView.firstTemplatePass) { @@ -33,7 +33,7 @@ export function pipe(index: number, pipeName: string): any { ])).push(adjustedIndex, pipeDef.onDestroy); } } else { - pipeDef = tView.data[adjustedIndex] as PipeDef; + pipeDef = tView.data[adjustedIndex] as PipeDefInternal; } const pipeInstance = pipeDef.factory(); @@ -49,7 +49,7 @@ export function pipe(index: number, pipeName: string): any { * @param registry Full list of available pipes * @returns Matching PipeDef */ -function getPipeDef(name: string, registry: PipeDefList | null): PipeDef { +function getPipeDef(name: string, registry: PipeDefList | null): PipeDefInternal { if (registry) { for (let i = 0; i < registry.length; i++) { const pipeDef = registry[i]; @@ -151,5 +151,5 @@ export function pipeBindV(index: number, slotOffset: number, values: any[]): any } function isPure(index: number): boolean { - return (>getTView().data[index + HEADER_OFFSET]).pure; + return (>getTView().data[index + HEADER_OFFSET]).pure; } diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index 7ec49e6f32..dedd1ae988 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -13,7 +13,7 @@ import {CreateComponentOptions} from '../../src/render3/component'; import {extractDirectiveDef, extractPipeDef} from '../../src/render3/definition'; import {ComponentTemplate, ComponentType, DirectiveDefInternal, DirectiveType, PublicFeature, RenderFlags, defineComponent, defineDirective, renderComponent as _renderComponent, tick} from '../../src/render3/index'; import {NG_HOST_SYMBOL, renderTemplate} from '../../src/render3/instructions'; -import {DirectiveDefList, DirectiveDefListOrFactory, DirectiveTypesOrFactory, PipeDef, PipeDefList, PipeDefListOrFactory, PipeTypesOrFactory} from '../../src/render3/interfaces/definition'; +import {DirectiveDefList, DirectiveDefListOrFactory, DirectiveTypesOrFactory, PipeDefInternal, PipeDefList, PipeDefListOrFactory, PipeTypesOrFactory} from '../../src/render3/interfaces/definition'; import {LElementNode} from '../../src/render3/interfaces/node'; import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer'; import {Sanitizer} from '../../src/sanitization/security'; @@ -183,10 +183,10 @@ function toDefs( mapFn: (type: Type) => DirectiveDefInternal): DirectiveDefList|null; function toDefs( types: PipeTypesOrFactory | undefined | null, - mapFn: (type: Type) => PipeDef): PipeDefList|null; + mapFn: (type: Type) => PipeDefInternal): PipeDefList|null; function toDefs( types: PipeTypesOrFactory | DirectiveTypesOrFactory | undefined | null, - mapFn: (type: Type) => PipeDef| DirectiveDefInternal): any { + mapFn: (type: Type) => PipeDefInternal| DirectiveDefInternal): any { if (!types) return null; if (typeof types == 'function') { types = types();