feat(compiler-cli): JIT compilation of component declarations (#40127)
The `ɵɵngDeclareComponent` calls are designed to be translated to fully AOT compiled code during a build transform, but in cases this is not done it is still possible to compile the declaration object in the browser using the JIT compiler. This commit adds a runtime implementation of `ɵɵngDeclareComponent` which invokes the JIT compiler using the declaration object, such that a compiled component definition is made available to the Ivy runtime. PR Close #40127
This commit is contained in:
parent
826b77b632
commit
d4327d51d1
|
@ -5,7 +5,7 @@
|
|||
* 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 {compileComponentFromMetadata, ConstantPool, DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig, makeBindingParser, parseTemplate, R3ComponentMetadata, R3DeclareComponentMetadata, R3PartialDeclaration, R3UsedDirectiveMetadata} from '@angular/compiler';
|
||||
import {compileComponentFromMetadata, ConstantPool, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig, makeBindingParser, parseTemplate, R3ComponentMetadata, R3DeclareComponentMetadata, R3PartialDeclaration, R3UsedDirectiveMetadata} from '@angular/compiler';
|
||||
import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/compiler/src/core';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
|
@ -63,7 +63,7 @@ export function toR3ComponentMeta<TExpression>(
|
|||
templateSource.expression, `Errors found in the template:\n${errors}`);
|
||||
}
|
||||
|
||||
let wrapDirectivesAndPipesInClosure = false;
|
||||
let declarationListEmitMode = DeclarationListEmitMode.Direct;
|
||||
|
||||
let directives: R3UsedDirectiveMetadata[] = [];
|
||||
if (metaObj.has('directives')) {
|
||||
|
@ -76,7 +76,7 @@ export function toR3ComponentMeta<TExpression>(
|
|||
const forwardRefType = extractForwardRef(type);
|
||||
if (forwardRefType !== null) {
|
||||
typeExpr = forwardRefType;
|
||||
wrapDirectivesAndPipesInClosure = true;
|
||||
declarationListEmitMode = DeclarationListEmitMode.Closure;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -100,7 +100,7 @@ export function toR3ComponentMeta<TExpression>(
|
|||
pipes = metaObj.getObject('pipes').toMap(pipe => {
|
||||
const forwardRefType = extractForwardRef(pipe);
|
||||
if (forwardRefType !== null) {
|
||||
wrapDirectivesAndPipesInClosure = true;
|
||||
declarationListEmitMode = DeclarationListEmitMode.Closure;
|
||||
return forwardRefType;
|
||||
} else {
|
||||
return pipe.getOpaque();
|
||||
|
@ -115,7 +115,7 @@ export function toR3ComponentMeta<TExpression>(
|
|||
nodes: template.nodes,
|
||||
ngContentSelectors: template.ngContentSelectors,
|
||||
},
|
||||
wrapDirectivesAndPipesInClosure,
|
||||
declarationListEmitMode,
|
||||
styles: metaObj.has('styles') ? metaObj.getArray('styles').map(entry => entry.getString()) : [],
|
||||
encapsulation: metaObj.has('encapsulation') ?
|
||||
parseEncapsulation(metaObj.getValue('encapsulation')) :
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {compileComponentFromMetadata, compileDeclareComponentFromMetadata, ConstantPool, CssSelector, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, Identifiers, InterpolationConfig, LexerRange, makeBindingParser, ParsedTemplate, ParseSourceFile, parseTemplate, R3ComponentDef, R3ComponentMetadata, R3FactoryTarget, R3TargetBinder, R3UsedDirectiveMetadata, SelectorMatcher, Statement, syntaxError, TmplAstNode, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {compileComponentFromMetadata, compileDeclareComponentFromMetadata, ConstantPool, CssSelector, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, Identifiers, InterpolationConfig, LexerRange, makeBindingParser, ParsedTemplate, ParseSourceFile, parseTemplate, R3ComponentDef, R3ComponentMetadata, R3FactoryTarget, R3TargetBinder, R3UsedDirectiveMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CycleAnalyzer} from '../../cycles';
|
||||
|
@ -42,7 +42,7 @@ const EMPTY_ARRAY: any[] = [];
|
|||
* be included here.
|
||||
*/
|
||||
export type ComponentMetadataResolvedFields =
|
||||
SubsetOfKeys<R3ComponentMetadata, 'directives'|'pipes'|'wrapDirectivesAndPipesInClosure'>;
|
||||
SubsetOfKeys<R3ComponentMetadata, 'directives'|'pipes'|'declarationListEmitMode'>;
|
||||
|
||||
export interface ComponentAnalysisData {
|
||||
/**
|
||||
|
@ -451,7 +451,7 @@ export class ComponentDecoratorHandler implements
|
|||
const data: ComponentResolutionData = {
|
||||
directives: EMPTY_ARRAY,
|
||||
pipes: EMPTY_MAP,
|
||||
wrapDirectivesAndPipesInClosure: false,
|
||||
declarationListEmitMode: DeclarationListEmitMode.Direct,
|
||||
};
|
||||
|
||||
if (scope !== null && (!scope.compilation.isPoisoned || this.usePoisonedData)) {
|
||||
|
@ -549,7 +549,9 @@ export class ComponentDecoratorHandler implements
|
|||
|
||||
data.directives = usedDirectives;
|
||||
data.pipes = new Map(usedPipes.map(pipe => [pipe.pipeName, pipe.expression]));
|
||||
data.wrapDirectivesAndPipesInClosure = wrapDirectivesAndPipesInClosure;
|
||||
data.declarationListEmitMode = wrapDirectivesAndPipesInClosure ?
|
||||
DeclarationListEmitMode.Closure :
|
||||
DeclarationListEmitMode.Direct;
|
||||
} else {
|
||||
// Declaring the directiveDefs/pipeDefs arrays directly would require imports that would
|
||||
// create a cycle. Instead, mark this component as requiring remote scoping, so that the
|
||||
|
|
|
@ -42,6 +42,9 @@ export interface CompilerFacade {
|
|||
declaration: R3DeclareDirectiveFacade): any;
|
||||
compileComponent(
|
||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadataFacade): any;
|
||||
compileComponentDeclaration(
|
||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
|
||||
declaration: R3DeclareComponentFacade): any;
|
||||
compileFactory(
|
||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade): any;
|
||||
|
||||
|
@ -187,6 +190,24 @@ export interface R3DeclareDirectiveFacade {
|
|||
usesOnChanges?: boolean;
|
||||
}
|
||||
|
||||
export interface R3DeclareComponentFacade extends R3DeclareDirectiveFacade {
|
||||
template: {source: string; isInline: boolean;};
|
||||
styles?: string[];
|
||||
directives?: {
|
||||
selector: string; type: OpaqueValue | (() => OpaqueValue);
|
||||
inputs?: string[];
|
||||
outputs?: string[];
|
||||
exportAs?: string[];
|
||||
}[];
|
||||
pipes?: {[pipeName: string]: OpaqueValue|(() => OpaqueValue)};
|
||||
viewProviders?: OpaqueValue;
|
||||
animations?: OpaqueValue;
|
||||
changeDetection?: ChangeDetectionStrategy;
|
||||
encapsulation?: ViewEncapsulation;
|
||||
interpolation?: [string, string];
|
||||
preserveWhitespaces?: boolean;
|
||||
}
|
||||
|
||||
export interface R3UsedDirectiveMetadata {
|
||||
selector: string;
|
||||
inputs: string[];
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
*/
|
||||
|
||||
|
||||
import {CompilerFacade, CoreEnvironment, ExportedCompilerFacade, OpaqueValue, R3ComponentMetadataFacade, R3DeclareDirectiveFacade, R3DeclareQueryMetadataFacade, R3DependencyMetadataFacade, R3DirectiveMetadataFacade, R3FactoryDefMetadataFacade, R3InjectableMetadataFacade, R3InjectorMetadataFacade, R3NgModuleMetadataFacade, R3PipeMetadataFacade, R3QueryMetadataFacade, StringMap, StringMapWithRename} from './compiler_facade_interface';
|
||||
import {CompilerFacade, CoreEnvironment, ExportedCompilerFacade, OpaqueValue, R3ComponentMetadataFacade, R3DeclareComponentFacade, R3DeclareDirectiveFacade, R3DeclareQueryMetadataFacade, R3DependencyMetadataFacade, R3DirectiveMetadataFacade, R3FactoryDefMetadataFacade, R3InjectableMetadataFacade, R3InjectorMetadataFacade, R3NgModuleMetadataFacade, R3PipeMetadataFacade, R3QueryMetadataFacade, StringMap, StringMapWithRename} from './compiler_facade_interface';
|
||||
import {ConstantPool} from './constant_pool';
|
||||
import {HostBinding, HostListener, Input, Output, Type} from './core';
|
||||
import {ChangeDetectionStrategy, HostBinding, HostListener, Input, Output, Type, ViewEncapsulation} from './core';
|
||||
import {Identifiers} from './identifiers';
|
||||
import {compileInjectable} from './injectable_compiler_2';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/interpolation_config';
|
||||
|
@ -21,7 +21,7 @@ import {R3JitReflector} from './render3/r3_jit';
|
|||
import {compileInjector, compileNgModule, R3InjectorMetadata, R3NgModuleMetadata} from './render3/r3_module_compiler';
|
||||
import {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler';
|
||||
import {R3Reference} from './render3/util';
|
||||
import {R3ComponentMetadata, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata} from './render3/view/api';
|
||||
import {DeclarationListEmitMode, R3ComponentMetadata, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata, R3UsedDirectiveMetadata} from './render3/view/api';
|
||||
import {compileComponentFromMetadata, compileDirectiveFromMetadata, ParsedHostBindings, parseHostBindings, verifyHostBindings} from './render3/view/compiler';
|
||||
import {makeBindingParser, parseTemplate} from './render3/view/template';
|
||||
import {ResourceLoader} from './resource_loader';
|
||||
|
@ -132,32 +132,21 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
|||
compileComponent(
|
||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
|
||||
facade: R3ComponentMetadataFacade): any {
|
||||
// The ConstantPool is a requirement of the JIT'er.
|
||||
const constantPool = new ConstantPool();
|
||||
|
||||
const interpolationConfig = facade.interpolation ?
|
||||
InterpolationConfig.fromArray(facade.interpolation) :
|
||||
DEFAULT_INTERPOLATION_CONFIG;
|
||||
// Parse the template and check for errors.
|
||||
const template = parseTemplate(
|
||||
facade.template, sourceMapUrl,
|
||||
{preserveWhitespaces: facade.preserveWhitespaces, interpolationConfig});
|
||||
if (template.errors !== null) {
|
||||
const errors = template.errors.map(err => err.toString()).join(', ');
|
||||
throw new Error(`Errors during JIT compilation of template for ${facade.name}: ${errors}`);
|
||||
}
|
||||
const {template, interpolation} = parseJitTemplate(
|
||||
facade.template, facade.name, sourceMapUrl, facade.preserveWhitespaces,
|
||||
facade.interpolation);
|
||||
|
||||
// Compile the component metadata, including template, into an expression.
|
||||
// TODO(alxhub): implement inputs, outputs, queries, etc.
|
||||
const metadata: R3ComponentMetadata = {
|
||||
const meta: R3ComponentMetadata = {
|
||||
...facade as R3ComponentMetadataFacadeNoPropAndWhitespace,
|
||||
...convertDirectiveFacadeToMetadata(facade),
|
||||
selector: facade.selector || this.elementSchemaRegistry.getDefaultComponentElementName(),
|
||||
template,
|
||||
wrapDirectivesAndPipesInClosure: false,
|
||||
declarationListEmitMode: DeclarationListEmitMode.Direct,
|
||||
styles: [...facade.styles, ...template.styles],
|
||||
encapsulation: facade.encapsulation as any,
|
||||
interpolation: interpolationConfig,
|
||||
interpolation,
|
||||
changeDetection: facade.changeDetection,
|
||||
animations: facade.animations != null ? new WrappedNodeExpr(facade.animations) : null,
|
||||
viewProviders: facade.viewProviders != null ? new WrappedNodeExpr(facade.viewProviders) :
|
||||
|
@ -165,11 +154,26 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
|||
relativeContextFilePath: '',
|
||||
i18nUseExternalIds: true,
|
||||
};
|
||||
const res = compileComponentFromMetadata(
|
||||
metadata, constantPool, makeBindingParser(interpolationConfig));
|
||||
const jitExpressionSourceMap = `ng:///${facade.name}.js`;
|
||||
return this.compileComponentFromMeta(angularCoreEnv, jitExpressionSourceMap, meta);
|
||||
}
|
||||
|
||||
compileComponentDeclaration(
|
||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
|
||||
declaration: R3DeclareComponentFacade): any {
|
||||
const typeSourceSpan =
|
||||
this.createParseSourceSpan('Component', declaration.type.name, sourceMapUrl);
|
||||
const meta = convertDeclareComponentFacadeToMetadata(declaration, typeSourceSpan, sourceMapUrl);
|
||||
return this.compileComponentFromMeta(angularCoreEnv, sourceMapUrl, meta);
|
||||
}
|
||||
|
||||
private compileComponentFromMeta(
|
||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadata): any {
|
||||
const constantPool = new ConstantPool();
|
||||
const bindingParser = makeBindingParser(meta.interpolation);
|
||||
const res = compileComponentFromMetadata(meta, constantPool, bindingParser);
|
||||
return this.jitExpression(
|
||||
res.expression, angularCoreEnv, jitExpressionSourceMap, constantPool.statements);
|
||||
res.expression, angularCoreEnv, sourceMapUrl, constantPool.statements);
|
||||
}
|
||||
|
||||
compileFactory(
|
||||
|
@ -337,6 +341,74 @@ function convertOpaqueValuesToExpressions(obj: {[key: string]: OpaqueValue}):
|
|||
return result;
|
||||
}
|
||||
|
||||
function convertDeclareComponentFacadeToMetadata(
|
||||
declaration: R3DeclareComponentFacade, typeSourceSpan: ParseSourceSpan,
|
||||
sourceMapUrl: string): R3ComponentMetadata {
|
||||
const {template, interpolation} = parseJitTemplate(
|
||||
declaration.template.source, declaration.type.name, sourceMapUrl,
|
||||
declaration.preserveWhitespaces ?? false, declaration.interpolation);
|
||||
|
||||
return {
|
||||
...convertDeclareDirectiveFacadeToMetadata(declaration, typeSourceSpan),
|
||||
template,
|
||||
styles: declaration.styles ?? [],
|
||||
directives: (declaration.directives ?? []).map(convertUsedDirectiveDeclarationToMetadata),
|
||||
pipes: convertUsedPipesToMetadata(declaration.pipes),
|
||||
viewProviders: declaration.viewProviders !== undefined ?
|
||||
new WrappedNodeExpr(declaration.viewProviders) :
|
||||
null,
|
||||
animations: declaration.animations !== undefined ? new WrappedNodeExpr(declaration.animations) :
|
||||
null,
|
||||
changeDetection: declaration.changeDetection ?? ChangeDetectionStrategy.Default,
|
||||
encapsulation: declaration.encapsulation ?? ViewEncapsulation.Emulated,
|
||||
interpolation,
|
||||
declarationListEmitMode: DeclarationListEmitMode.ClosureResolved,
|
||||
relativeContextFilePath: '',
|
||||
i18nUseExternalIds: true,
|
||||
};
|
||||
}
|
||||
|
||||
function convertUsedDirectiveDeclarationToMetadata(
|
||||
declaration: NonNullable<R3DeclareComponentFacade['directives']>[number]):
|
||||
R3UsedDirectiveMetadata {
|
||||
return {
|
||||
selector: declaration.selector,
|
||||
type: new WrappedNodeExpr(declaration.type),
|
||||
inputs: declaration.inputs ?? [],
|
||||
outputs: declaration.outputs ?? [],
|
||||
exportAs: declaration.exportAs ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function convertUsedPipesToMetadata(declaredPipes: R3DeclareComponentFacade['pipes']):
|
||||
Map<string, Expression> {
|
||||
const pipes = new Map<string, Expression>();
|
||||
if (declaredPipes === undefined) {
|
||||
return pipes;
|
||||
}
|
||||
|
||||
for (const pipeName of Object.keys(declaredPipes)) {
|
||||
const pipeType = declaredPipes[pipeName];
|
||||
pipes.set(pipeName, new WrappedNodeExpr(pipeType));
|
||||
}
|
||||
return pipes;
|
||||
}
|
||||
|
||||
function parseJitTemplate(
|
||||
template: string, typeName: string, sourceMapUrl: string, preserveWhitespaces: boolean,
|
||||
interpolation: [string, string]|undefined) {
|
||||
const interpolationConfig =
|
||||
interpolation ? InterpolationConfig.fromArray(interpolation) : DEFAULT_INTERPOLATION_CONFIG;
|
||||
// Parse the template and check for errors.
|
||||
const parsed = parseTemplate(
|
||||
template, sourceMapUrl, {preserveWhitespaces: preserveWhitespaces, interpolationConfig});
|
||||
if (parsed.errors !== null) {
|
||||
const errors = parsed.errors.map(err => err.toString()).join(', ');
|
||||
throw new Error(`Errors during JIT compilation of template for ${typeName}: ${errors}`);
|
||||
}
|
||||
return {template: parsed, interpolation: interpolationConfig};
|
||||
}
|
||||
|
||||
// This seems to be needed to placate TS v3.0 only
|
||||
type R3DirectiveMetadataFacadeNoPropAndWhitespace =
|
||||
Pick<R3DirectiveMetadataFacade, Exclude<keyof R3DirectiveMetadataFacade, 'propMetadata'>>;
|
||||
|
|
|
@ -9,7 +9,7 @@ import * as core from '../../core';
|
|||
import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
|
||||
import * as o from '../../output/output_ast';
|
||||
import {Identifiers as R3} from '../r3_identifiers';
|
||||
import {R3ComponentDef, R3ComponentMetadata, R3UsedDirectiveMetadata} from '../view/api';
|
||||
import {DeclarationListEmitMode, R3ComponentDef, R3ComponentMetadata, R3UsedDirectiveMetadata} from '../view/api';
|
||||
import {createComponentType} from '../view/compiler';
|
||||
import {ParsedTemplate} from '../view/template';
|
||||
import {DefinitionMap} from '../view/util';
|
||||
|
@ -91,8 +91,9 @@ function compileTemplateDefinition(template: ParsedTemplate): o.LiteralMapExpr {
|
|||
* individual directives. If the component does not use any directives, then null is returned.
|
||||
*/
|
||||
function compileUsedDirectiveMetadata(meta: R3ComponentMetadata): o.LiteralArrayExpr|null {
|
||||
const wrapType =
|
||||
meta.wrapDirectivesAndPipesInClosure ? generateForwardRef : (expr: o.Expression) => expr;
|
||||
const wrapType = meta.declarationListEmitMode !== DeclarationListEmitMode.Direct ?
|
||||
generateForwardRef :
|
||||
(expr: o.Expression) => expr;
|
||||
|
||||
return toOptionalLiteralArray(meta.directives, directive => {
|
||||
const dirMeta = new DefinitionMap<R3UsedDirectiveMetadata>();
|
||||
|
@ -115,8 +116,9 @@ function compileUsedPipeMetadata(meta: R3ComponentMetadata): o.LiteralMapExpr|nu
|
|||
return null;
|
||||
}
|
||||
|
||||
const wrapType =
|
||||
meta.wrapDirectivesAndPipesInClosure ? generateForwardRef : (expr: o.Expression) => expr;
|
||||
const wrapType = meta.declarationListEmitMode !== DeclarationListEmitMode.Direct ?
|
||||
generateForwardRef :
|
||||
(expr: o.Expression) => expr;
|
||||
|
||||
const entries = [];
|
||||
for (const [name, pipe] of meta.pipes) {
|
||||
|
|
|
@ -230,6 +230,7 @@ export class Identifiers {
|
|||
o.ExternalReference = {name: 'ɵɵtemplateRefExtractor', moduleName: CORE};
|
||||
|
||||
static forwardRef: o.ExternalReference = {name: 'forwardRef', moduleName: CORE};
|
||||
static resolveForwardRef: o.ExternalReference = {name: 'resolveForwardRef', moduleName: CORE};
|
||||
|
||||
static resolveWindow: o.ExternalReference = {name: 'ɵɵresolveWindow', moduleName: CORE};
|
||||
static resolveDocument: o.ExternalReference = {name: 'ɵɵresolveDocument', moduleName: CORE};
|
||||
|
|
|
@ -119,6 +119,50 @@ export interface R3DirectiveMetadata {
|
|||
providers: o.Expression|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies how a list of declaration type references should be emitted into the generated code.
|
||||
*/
|
||||
export const enum DeclarationListEmitMode {
|
||||
/**
|
||||
* The list of declarations is emitted into the generated code as is.
|
||||
*
|
||||
* ```
|
||||
* directives: [MyDir],
|
||||
* ```
|
||||
*/
|
||||
Direct,
|
||||
|
||||
/**
|
||||
* The list of declarations is emitted into the generated code wrapped inside a closure, which
|
||||
* is needed when at least one declaration is a forward reference.
|
||||
*
|
||||
* ```
|
||||
* directives: function () { return [MyDir, ForwardDir]; },
|
||||
* ```
|
||||
*/
|
||||
Closure,
|
||||
|
||||
/**
|
||||
* Similar to `Closure`, with the addition that the list of declarations can contain individual
|
||||
* items that are themselves forward references. This is relevant for JIT compilations, as
|
||||
* unwrapping the forwardRef cannot be done statically so must be deferred. This mode emits
|
||||
* the declaration list using a mapping transform through `resolveForwardRef` to ensure that
|
||||
* any forward references within the list are resolved when the outer closure is invoked.
|
||||
*
|
||||
* Consider the case where the runtime has captured two declarations in two distinct values:
|
||||
* ```
|
||||
* const dirA = MyDir;
|
||||
* const dirB = forwardRef(function() { return ForwardRef; });
|
||||
* ```
|
||||
*
|
||||
* This mode would emit the declarations captured in `dirA` and `dirB` as follows:
|
||||
* ```
|
||||
* directives: function () { return [dirA, dirB].map(ng.resolveForwardRef); },
|
||||
* ```
|
||||
*/
|
||||
ClosureResolved,
|
||||
}
|
||||
|
||||
/**
|
||||
* Information needed to compile a component for the render3 runtime.
|
||||
*/
|
||||
|
@ -152,11 +196,9 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata {
|
|||
directives: R3UsedDirectiveMetadata[];
|
||||
|
||||
/**
|
||||
* Whether to wrap the 'directives' and/or `pipes` array, if one is generated, in a closure.
|
||||
*
|
||||
* This is done when the directives or pipes contain forward references.
|
||||
* Specifies how the 'directives' and/or `pipes` array, if generated, need to be emitted.
|
||||
*/
|
||||
wrapDirectivesAndPipesInClosure: boolean;
|
||||
declarationListEmitMode: DeclarationListEmitMode;
|
||||
|
||||
/**
|
||||
* A collection of styling data that will be applied and scoped to the component.
|
||||
|
|
|
@ -27,7 +27,7 @@ import {Identifiers as R3} from '../r3_identifiers';
|
|||
import {Render3ParseResult} from '../r3_template_transform';
|
||||
import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util';
|
||||
|
||||
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata} from './api';
|
||||
import {DeclarationListEmitMode, R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata} from './api';
|
||||
import {MIN_STYLING_BINDING_SLOTS_REQUIRED, StylingBuilder, StylingInstructionCall} from './styling_builder';
|
||||
import {BindingScope, makeBindingParser, prepareEventListenerParameters, renderFlagCheckIfStmt, resolveSanitizationFn, TemplateDefinitionBuilder, ValueConverter} from './template';
|
||||
import {asLiteral, chainedInstruction, conditionallyCreateMapObjectLiteral, CONTEXT_NAME, DefinitionMap, getQueryPredicate, RENDER_FLAGS, TEMPORARY_NAME, temporaryAllocator} from './util';
|
||||
|
@ -213,19 +213,15 @@ export function compileComponentFromMetadata(
|
|||
|
||||
// e.g. `directives: [MyDirective]`
|
||||
if (directivesUsed.size) {
|
||||
let directivesExpr: o.Expression = o.literalArr(Array.from(directivesUsed));
|
||||
if (meta.wrapDirectivesAndPipesInClosure) {
|
||||
directivesExpr = o.fn([], [new o.ReturnStatement(directivesExpr)]);
|
||||
}
|
||||
const directivesList = o.literalArr(Array.from(directivesUsed));
|
||||
const directivesExpr = compileDeclarationList(directivesList, meta.declarationListEmitMode);
|
||||
definitionMap.set('directives', directivesExpr);
|
||||
}
|
||||
|
||||
// e.g. `pipes: [MyPipe]`
|
||||
if (pipesUsed.size) {
|
||||
let pipesExpr: o.Expression = o.literalArr(Array.from(pipesUsed));
|
||||
if (meta.wrapDirectivesAndPipesInClosure) {
|
||||
pipesExpr = o.fn([], [new o.ReturnStatement(pipesExpr)]);
|
||||
}
|
||||
const pipesList = o.literalArr(Array.from(pipesUsed));
|
||||
const pipesExpr = compileDeclarationList(pipesList, meta.declarationListEmitMode);
|
||||
definitionMap.set('pipes', pipesExpr);
|
||||
}
|
||||
|
||||
|
@ -277,6 +273,26 @@ export function createComponentType(meta: R3ComponentMetadata): o.Type {
|
|||
return o.expressionType(o.importExpr(R3.ComponentDefWithMeta, typeParams));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the array literal of declarations into an expression according to the provided emit
|
||||
* mode.
|
||||
*/
|
||||
function compileDeclarationList(
|
||||
list: o.LiteralArrayExpr, mode: DeclarationListEmitMode): o.Expression {
|
||||
switch (mode) {
|
||||
case DeclarationListEmitMode.Direct:
|
||||
// directives: [MyDir],
|
||||
return list;
|
||||
case DeclarationListEmitMode.Closure:
|
||||
// directives: function () { return [MyDir]; }
|
||||
return o.fn([], [new o.ReturnStatement(list)]);
|
||||
case DeclarationListEmitMode.ClosureResolved:
|
||||
// directives: function () { return [MyDir].map(ng.resolveForwardRef); }
|
||||
const resolvedList = list.callMethod('map', [o.importExpr(R3.resolveForwardRef)]);
|
||||
return o.fn([], [new o.ReturnStatement(resolvedList)]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around `compileDirective` which depends on render2 global analysis data as its input
|
||||
* instead of the `R3DirectiveMetadata`.
|
||||
|
@ -335,7 +351,7 @@ export function compileComponentFromRender2(
|
|||
directives: [],
|
||||
pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx),
|
||||
viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx),
|
||||
wrapDirectivesAndPipesInClosure: false,
|
||||
declarationListEmitMode: DeclarationListEmitMode.Direct,
|
||||
styles: (summary.template && summary.template.styles) || EMPTY_ARRAY,
|
||||
encapsulation:
|
||||
(summary.template && summary.template.encapsulation) || core.ViewEncapsulation.Emulated,
|
||||
|
|
|
@ -108,6 +108,16 @@ const coreR3DeclareDirectiveFacade: core.R3DeclareDirectiveFacade =
|
|||
const compilerR3DeclareDirectiveFacade: compiler.R3DeclareDirectiveFacade =
|
||||
null! as core.R3DeclareDirectiveFacade;
|
||||
|
||||
const coreR3DeclareComponentFacade: core.R3DeclareComponentFacade =
|
||||
null! as compiler.R3DeclareComponentFacade;
|
||||
const compilerR3DeclareComponentFacade: compiler.R3DeclareComponentFacade =
|
||||
null! as core.R3DeclareComponentFacade;
|
||||
|
||||
const coreR3UsedDirectiveMetadata: core.R3UsedDirectiveMetadata =
|
||||
null! as compiler.R3UsedDirectiveMetadata;
|
||||
const compilerR3UsedDirectiveMetadata: compiler.R3UsedDirectiveMetadata =
|
||||
null! as core.R3UsedDirectiveMetadata;
|
||||
|
||||
const coreViewEncapsulation: core.ViewEncapsulation = null! as compiler.ViewEncapsulation;
|
||||
const compilerViewEncapsulation: compiler.ViewEncapsulation = null! as core.ViewEncapsulation;
|
||||
|
||||
|
|
|
@ -42,6 +42,9 @@ export interface CompilerFacade {
|
|||
declaration: R3DeclareDirectiveFacade): any;
|
||||
compileComponent(
|
||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadataFacade): any;
|
||||
compileComponentDeclaration(
|
||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
|
||||
declaration: R3DeclareComponentFacade): any;
|
||||
compileFactory(
|
||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade): any;
|
||||
|
||||
|
@ -187,6 +190,24 @@ export interface R3DeclareDirectiveFacade {
|
|||
usesOnChanges?: boolean;
|
||||
}
|
||||
|
||||
export interface R3DeclareComponentFacade extends R3DeclareDirectiveFacade {
|
||||
template: {source: string; isInline: boolean;};
|
||||
styles?: string[];
|
||||
directives?: {
|
||||
selector: string; type: OpaqueValue | (() => OpaqueValue);
|
||||
inputs?: string[];
|
||||
outputs?: string[];
|
||||
exportAs?: string[];
|
||||
}[];
|
||||
pipes?: {[pipeName: string]: OpaqueValue|(() => OpaqueValue)};
|
||||
viewProviders?: OpaqueValue;
|
||||
animations?: OpaqueValue;
|
||||
changeDetection?: ChangeDetectionStrategy;
|
||||
encapsulation?: ViewEncapsulation;
|
||||
interpolation?: [string, string];
|
||||
preserveWhitespaces?: boolean;
|
||||
}
|
||||
|
||||
export interface R3UsedDirectiveMetadata {
|
||||
selector: string;
|
||||
inputs: string[];
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {forwardRef} from '../../di/forward_ref';
|
||||
import {forwardRef, resolveForwardRef} from '../../di/forward_ref';
|
||||
import {ɵɵinject, ɵɵinvalidFactoryDep} from '../../di/injector_compatibility';
|
||||
import {ɵɵdefineInjectable, ɵɵdefineInjector} from '../../di/interface/defs';
|
||||
import * as sanitization from '../../sanitization/sanitization';
|
||||
|
@ -170,4 +170,5 @@ export const angularCoreEnv: {[name: string]: Function} =
|
|||
'ɵɵtrustConstantResourceUrl': sanitization.ɵɵtrustConstantResourceUrl,
|
||||
|
||||
'forwardRef': forwardRef,
|
||||
'resolveForwardRef': resolveForwardRef,
|
||||
}))();
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {getCompilerFacade, R3DeclareDirectiveFacade} from '../../compiler/compiler_facade';
|
||||
import {getCompilerFacade, R3DeclareComponentFacade, R3DeclareDirectiveFacade} from '../../compiler/compiler_facade';
|
||||
import {angularCoreEnv} from './environment';
|
||||
|
||||
/**
|
||||
|
@ -25,6 +25,8 @@ export function ɵɵngDeclareDirective(decl: R3DeclareDirectiveFacade): unknown
|
|||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵngDeclareComponent(decl: unknown): unknown {
|
||||
throw new Error('Not yet implemented');
|
||||
export function ɵɵngDeclareComponent(decl: R3DeclareComponentFacade): unknown {
|
||||
const compiler = getCompilerFacade();
|
||||
return compiler.compileComponentDeclaration(
|
||||
angularCoreEnv, `ng:///${decl.type.name}/ɵcmp.js`, decl);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,539 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {ChangeDetectionStrategy, Directive, ElementRef, forwardRef, Pipe, Type, ViewEncapsulation, ɵɵngDeclareComponent} from '@angular/core';
|
||||
import {AttributeMarker, ComponentDef} from '../../../src/render3';
|
||||
import {functionContaining} from './matcher';
|
||||
|
||||
describe('component declaration jit compilation', () => {
|
||||
it('should compile a minimal component declaration', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate(`<div></div>`),
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
template: functionContaining([
|
||||
/element[^(]*\(0,'div'\)/,
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile a selector', () => {
|
||||
const def =
|
||||
ɵɵngDeclareComponent(
|
||||
{type: TestClass, template: createTemplate('<div></div>'), selector: '[dir], test'}) as
|
||||
ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
selectors: [['', 'dir', ''], ['test']],
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile inputs and outputs', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
inputs: {
|
||||
minifiedProperty: 'property',
|
||||
minifiedClassProperty: ['bindingName', 'classProperty'],
|
||||
},
|
||||
outputs: {
|
||||
minifiedEventName: 'eventBindingName',
|
||||
},
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
inputs: {
|
||||
'property': 'minifiedProperty',
|
||||
'bindingName': 'minifiedClassProperty',
|
||||
},
|
||||
declaredInputs: {
|
||||
'property': 'property',
|
||||
'bindingName': 'classProperty',
|
||||
},
|
||||
outputs: {
|
||||
'eventBindingName': 'minifiedEventName',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile exportAs', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
exportAs: ['a', 'b'],
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
exportAs: ['a', 'b'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile providers', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
providers: [
|
||||
{provide: 'token', useValue: 123},
|
||||
],
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
features: [jasmine.any(Function)],
|
||||
providersResolver: jasmine.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile view providers', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
viewProviders: [
|
||||
{provide: 'token', useValue: 123},
|
||||
],
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
features: [jasmine.any(Function)],
|
||||
providersResolver: jasmine.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile content queries', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
queries: [
|
||||
{
|
||||
propertyName: 'byRef',
|
||||
predicate: ['ref'],
|
||||
},
|
||||
{
|
||||
propertyName: 'byToken',
|
||||
predicate: String,
|
||||
descendants: true,
|
||||
static: true,
|
||||
first: true,
|
||||
read: ElementRef,
|
||||
}
|
||||
],
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
contentQueries: functionContaining([
|
||||
// "byRef" should use `contentQuery` with `false` for descendants flag without a read token,
|
||||
// and bind to the full query result.
|
||||
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
|
||||
/(?:contentQuery|anonymous)[^(]*\(dirIndex,_c0,false\)/,
|
||||
'(ctx.byRef = _t)',
|
||||
|
||||
// "byToken" should use `staticContentQuery` with `true` for descendants flag and
|
||||
// `ElementRef` as read token, and bind to the first result in the query result.
|
||||
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
|
||||
/(?:staticContentQuery|anonymous)[^(]*\(dirIndex,[^,]*String[^,]*,true,[^)]*ElementRef[^)]*\)/,
|
||||
'(ctx.byToken = _t.first)',
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile view queries', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
viewQueries: [
|
||||
{
|
||||
propertyName: 'byRef',
|
||||
predicate: ['ref'],
|
||||
},
|
||||
{
|
||||
propertyName: 'byToken',
|
||||
predicate: String,
|
||||
descendants: true,
|
||||
static: true,
|
||||
first: true,
|
||||
read: ElementRef,
|
||||
}
|
||||
],
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
viewQuery: functionContaining([
|
||||
// "byRef" should use `viewQuery` with `false` for descendants flag without a read token,
|
||||
// and bind to the full query result.
|
||||
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
|
||||
/(?:viewQuery|anonymous)[^(]*\(_c0,false\)/,
|
||||
'(ctx.byRef = _t)',
|
||||
|
||||
// "byToken" should use `staticViewQuery` with `true` for descendants flag and
|
||||
// `ElementRef` as read token, and bind to the first result in the query result.
|
||||
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
|
||||
/(?:staticViewQuery|anonymous)[^(]*\([^,]*String[^,]*,true,[^)]*ElementRef[^)]*\)/,
|
||||
'(ctx.byToken = _t.first)',
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile host bindings', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
host: {
|
||||
attributes: {
|
||||
'attr': 'value',
|
||||
},
|
||||
listeners: {
|
||||
'event': 'handleEvent($event)',
|
||||
},
|
||||
properties: {
|
||||
'foo': 'foo.prop',
|
||||
'attr.bar': 'bar.prop',
|
||||
},
|
||||
classAttribute: 'foo bar',
|
||||
styleAttribute: 'width: 100px;',
|
||||
},
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
hostAttrs: [
|
||||
'attr', 'value', AttributeMarker.Classes, 'foo', 'bar', AttributeMarker.Styles, 'width',
|
||||
'100px'
|
||||
],
|
||||
hostBindings: functionContaining([
|
||||
'return ctx.handleEvent($event)',
|
||||
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
|
||||
/(?:hostProperty|anonymous)[^(]*\('foo',ctx\.foo\.prop\)/,
|
||||
/(?:attribute|anonymous)[^(]*\('bar',ctx\.bar\.prop\)/,
|
||||
]),
|
||||
hostVars: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile components with inheritance', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
usesInheritance: true,
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
features: [functionContaining(['ɵɵInheritDefinitionFeature'])],
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile components with onChanges lifecycle hook', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
usesOnChanges: true,
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
features: [functionContaining(['ɵɵNgOnChangesFeature'])],
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile components with OnPush change detection strategy', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
onPush: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile components with styles', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
styles: ['div {}'],
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
styles: ['div[_ngcontent-%COMP%] {}'],
|
||||
encapsulation: ViewEncapsulation.Emulated,
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile components with view encapsulation', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
styles: ['div {}'],
|
||||
encapsulation: ViewEncapsulation.ShadowDom,
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
styles: ['div {}'],
|
||||
encapsulation: ViewEncapsulation.ShadowDom,
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile components with animations', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div></div>'),
|
||||
animations: [{type: 'trigger'}],
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
data: {
|
||||
animation: [{type: 'trigger'}],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should honor preserveWhitespaces', () => {
|
||||
const template = createTemplate('<div> Foo </div>');
|
||||
const whenTrue = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template,
|
||||
preserveWhitespaces: true,
|
||||
}) as ComponentDef<TestClass>;
|
||||
const whenOmitted = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template,
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(whenTrue, {
|
||||
template: functionContaining([
|
||||
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
|
||||
/(?:elementStart|anonymous)[^(]*\(0,'div'\)/,
|
||||
/(?:text|anonymous)[^(]*\(1,' Foo '\)/,
|
||||
]),
|
||||
});
|
||||
expectComponentDef(whenOmitted, {
|
||||
template: functionContaining([
|
||||
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
|
||||
/(?:elementStart|anonymous)[^(]*\(0,'div'\)/,
|
||||
/(?:text|anonymous)[^(]*\(1,' Foo '\)/,
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should honor custom interpolation config', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('{% foo %}'),
|
||||
interpolation: ['{%', '%}'],
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
template: functionContaining([
|
||||
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
|
||||
/(?:textInterpolate|anonymous)[^(]*\(ctx.foo\)/,
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile used directives', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div dir></div>'),
|
||||
directives: [{
|
||||
type: TestDir,
|
||||
selector: '[dir]',
|
||||
}],
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
directives: [TestDir],
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile forward declared directives', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div forward></div>'),
|
||||
directives: [{
|
||||
type: forwardRef(function() {
|
||||
return ForwardDir;
|
||||
}),
|
||||
selector: '[forward]',
|
||||
}],
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
@Directive({selector: '[forward]'})
|
||||
class ForwardDir {
|
||||
}
|
||||
|
||||
expectComponentDef(def, {
|
||||
directives: [ForwardDir],
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile mixed forward and direct declared directives', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('<div dir forward></div>'),
|
||||
directives: [
|
||||
{
|
||||
type: TestDir,
|
||||
selector: '[dir]',
|
||||
},
|
||||
{
|
||||
type: forwardRef(function() {
|
||||
return ForwardDir;
|
||||
}),
|
||||
selector: '[forward]',
|
||||
}
|
||||
],
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
@Directive({selector: '[forward]'})
|
||||
class ForwardDir {
|
||||
}
|
||||
|
||||
expectComponentDef(def, {
|
||||
directives: [TestDir, ForwardDir],
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile used pipes', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('{{ expr | test }}'),
|
||||
pipes: {
|
||||
'test': TestPipe,
|
||||
},
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
expectComponentDef(def, {
|
||||
pipes: [TestPipe],
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile forward declared pipes', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('{{ expr | forward }}'),
|
||||
pipes: {
|
||||
'forward': forwardRef(function() {
|
||||
return ForwardPipe;
|
||||
}),
|
||||
},
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
@Pipe({name: 'forward'})
|
||||
class ForwardPipe {
|
||||
}
|
||||
|
||||
expectComponentDef(def, {
|
||||
pipes: [ForwardPipe],
|
||||
});
|
||||
});
|
||||
|
||||
it('should compile mixed forward and direct declared pipes', () => {
|
||||
const def = ɵɵngDeclareComponent({
|
||||
type: TestClass,
|
||||
template: createTemplate('{{ expr | forward | test }}'),
|
||||
pipes: {
|
||||
'test': TestPipe,
|
||||
'forward': forwardRef(function() {
|
||||
return ForwardPipe;
|
||||
}),
|
||||
},
|
||||
}) as ComponentDef<TestClass>;
|
||||
|
||||
@Pipe({name: 'forward'})
|
||||
class ForwardPipe {
|
||||
}
|
||||
|
||||
expectComponentDef(def, {
|
||||
pipes: [TestPipe, ForwardPipe],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createTemplate(template: string) {
|
||||
return {source: template, isInline: true};
|
||||
}
|
||||
|
||||
type ComponentDefExpectations = jasmine.Expected<Pick<
|
||||
ComponentDef<unknown>,
|
||||
'selectors'|'template'|'inputs'|'declaredInputs'|'outputs'|'features'|'hostAttrs'|
|
||||
'hostBindings'|'hostVars'|'contentQueries'|'viewQuery'|'exportAs'|'providersResolver'|
|
||||
'encapsulation'|'onPush'|'styles'|'data'>>&{
|
||||
directives: Type<unknown>[]|null;
|
||||
pipes: Type<unknown>[]|null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that the provided component definition is according to the provided expectation.
|
||||
* Definition fields for which no expectation is present are verified to be initialized to their
|
||||
* default value.
|
||||
*/
|
||||
function expectComponentDef(
|
||||
actual: ComponentDef<unknown>, expected: Partial<ComponentDefExpectations>): void {
|
||||
const expectation: ComponentDefExpectations = {
|
||||
selectors: [],
|
||||
template: jasmine.any(Function),
|
||||
inputs: {},
|
||||
declaredInputs: {},
|
||||
outputs: {},
|
||||
features: null,
|
||||
hostAttrs: null,
|
||||
hostBindings: null,
|
||||
hostVars: 0,
|
||||
contentQueries: null,
|
||||
viewQuery: null,
|
||||
exportAs: null,
|
||||
providersResolver: null,
|
||||
// Although the default view encapsulation is `Emulated`, the default expected view
|
||||
// encapsulation is `None` as this is chosen when no styles are present.
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
onPush: false,
|
||||
styles: [],
|
||||
directives: null,
|
||||
pipes: null,
|
||||
data: {},
|
||||
...expected,
|
||||
};
|
||||
|
||||
expect(actual.type).toBe(TestClass);
|
||||
expect(actual.selectors).toEqual(expectation.selectors);
|
||||
expect(actual.template).toEqual(expectation.template);
|
||||
expect(actual.inputs).toEqual(expectation.inputs);
|
||||
expect(actual.declaredInputs).toEqual(expectation.declaredInputs);
|
||||
expect(actual.outputs).toEqual(expectation.outputs);
|
||||
expect(actual.features).toEqual(expectation.features);
|
||||
expect(actual.hostAttrs).toEqual(expectation.hostAttrs);
|
||||
expect(actual.hostBindings).toEqual(expectation.hostBindings);
|
||||
expect(actual.hostVars).toEqual(expectation.hostVars);
|
||||
expect(actual.contentQueries).toEqual(expectation.contentQueries);
|
||||
expect(actual.viewQuery).toEqual(expectation.viewQuery);
|
||||
expect(actual.exportAs).toEqual(expectation.exportAs);
|
||||
expect(actual.providersResolver).toEqual(expectation.providersResolver);
|
||||
expect(actual.encapsulation).toEqual(expectation.encapsulation);
|
||||
expect(actual.onPush).toEqual(expectation.onPush);
|
||||
expect(actual.styles).toEqual(expectation.styles);
|
||||
expect(actual.data).toEqual(expectation.data);
|
||||
|
||||
const directiveDefs =
|
||||
typeof actual.directiveDefs === 'function' ? actual.directiveDefs() : actual.directiveDefs;
|
||||
const directiveTypes = directiveDefs !== null ? directiveDefs.map(def => def.type) : null;
|
||||
expect(directiveTypes).toEqual(expectation.directives);
|
||||
|
||||
const pipeDefs = typeof actual.pipeDefs === 'function' ? actual.pipeDefs() : actual.pipeDefs;
|
||||
const pipeTypes = pipeDefs !== null ? pipeDefs.map(def => def.type) : null;
|
||||
expect(pipeTypes).toEqual(expectation.pipes);
|
||||
}
|
||||
|
||||
class TestClass {}
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
class TestDir {
|
||||
}
|
||||
|
||||
@Pipe({name: 'test'})
|
||||
class TestPipe {
|
||||
}
|
Loading…
Reference in New Issue