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
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {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 {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/compiler/src/core';
|
||||||
import * as o from '@angular/compiler/src/output/output_ast';
|
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}`);
|
templateSource.expression, `Errors found in the template:\n${errors}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let wrapDirectivesAndPipesInClosure = false;
|
let declarationListEmitMode = DeclarationListEmitMode.Direct;
|
||||||
|
|
||||||
let directives: R3UsedDirectiveMetadata[] = [];
|
let directives: R3UsedDirectiveMetadata[] = [];
|
||||||
if (metaObj.has('directives')) {
|
if (metaObj.has('directives')) {
|
||||||
|
@ -76,7 +76,7 @@ export function toR3ComponentMeta<TExpression>(
|
||||||
const forwardRefType = extractForwardRef(type);
|
const forwardRefType = extractForwardRef(type);
|
||||||
if (forwardRefType !== null) {
|
if (forwardRefType !== null) {
|
||||||
typeExpr = forwardRefType;
|
typeExpr = forwardRefType;
|
||||||
wrapDirectivesAndPipesInClosure = true;
|
declarationListEmitMode = DeclarationListEmitMode.Closure;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -100,7 +100,7 @@ export function toR3ComponentMeta<TExpression>(
|
||||||
pipes = metaObj.getObject('pipes').toMap(pipe => {
|
pipes = metaObj.getObject('pipes').toMap(pipe => {
|
||||||
const forwardRefType = extractForwardRef(pipe);
|
const forwardRefType = extractForwardRef(pipe);
|
||||||
if (forwardRefType !== null) {
|
if (forwardRefType !== null) {
|
||||||
wrapDirectivesAndPipesInClosure = true;
|
declarationListEmitMode = DeclarationListEmitMode.Closure;
|
||||||
return forwardRefType;
|
return forwardRefType;
|
||||||
} else {
|
} else {
|
||||||
return pipe.getOpaque();
|
return pipe.getOpaque();
|
||||||
|
@ -115,7 +115,7 @@ export function toR3ComponentMeta<TExpression>(
|
||||||
nodes: template.nodes,
|
nodes: template.nodes,
|
||||||
ngContentSelectors: template.ngContentSelectors,
|
ngContentSelectors: template.ngContentSelectors,
|
||||||
},
|
},
|
||||||
wrapDirectivesAndPipesInClosure,
|
declarationListEmitMode,
|
||||||
styles: metaObj.has('styles') ? metaObj.getArray('styles').map(entry => entry.getString()) : [],
|
styles: metaObj.has('styles') ? metaObj.getArray('styles').map(entry => entry.getString()) : [],
|
||||||
encapsulation: metaObj.has('encapsulation') ?
|
encapsulation: metaObj.has('encapsulation') ?
|
||||||
parseEncapsulation(metaObj.getValue('encapsulation')) :
|
parseEncapsulation(metaObj.getValue('encapsulation')) :
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 * as ts from 'typescript';
|
||||||
|
|
||||||
import {CycleAnalyzer} from '../../cycles';
|
import {CycleAnalyzer} from '../../cycles';
|
||||||
|
@ -42,7 +42,7 @@ const EMPTY_ARRAY: any[] = [];
|
||||||
* be included here.
|
* be included here.
|
||||||
*/
|
*/
|
||||||
export type ComponentMetadataResolvedFields =
|
export type ComponentMetadataResolvedFields =
|
||||||
SubsetOfKeys<R3ComponentMetadata, 'directives'|'pipes'|'wrapDirectivesAndPipesInClosure'>;
|
SubsetOfKeys<R3ComponentMetadata, 'directives'|'pipes'|'declarationListEmitMode'>;
|
||||||
|
|
||||||
export interface ComponentAnalysisData {
|
export interface ComponentAnalysisData {
|
||||||
/**
|
/**
|
||||||
|
@ -451,7 +451,7 @@ export class ComponentDecoratorHandler implements
|
||||||
const data: ComponentResolutionData = {
|
const data: ComponentResolutionData = {
|
||||||
directives: EMPTY_ARRAY,
|
directives: EMPTY_ARRAY,
|
||||||
pipes: EMPTY_MAP,
|
pipes: EMPTY_MAP,
|
||||||
wrapDirectivesAndPipesInClosure: false,
|
declarationListEmitMode: DeclarationListEmitMode.Direct,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (scope !== null && (!scope.compilation.isPoisoned || this.usePoisonedData)) {
|
if (scope !== null && (!scope.compilation.isPoisoned || this.usePoisonedData)) {
|
||||||
|
@ -549,7 +549,9 @@ export class ComponentDecoratorHandler implements
|
||||||
|
|
||||||
data.directives = usedDirectives;
|
data.directives = usedDirectives;
|
||||||
data.pipes = new Map(usedPipes.map(pipe => [pipe.pipeName, pipe.expression]));
|
data.pipes = new Map(usedPipes.map(pipe => [pipe.pipeName, pipe.expression]));
|
||||||
data.wrapDirectivesAndPipesInClosure = wrapDirectivesAndPipesInClosure;
|
data.declarationListEmitMode = wrapDirectivesAndPipesInClosure ?
|
||||||
|
DeclarationListEmitMode.Closure :
|
||||||
|
DeclarationListEmitMode.Direct;
|
||||||
} else {
|
} else {
|
||||||
// Declaring the directiveDefs/pipeDefs arrays directly would require imports that would
|
// 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
|
// create a cycle. Instead, mark this component as requiring remote scoping, so that the
|
||||||
|
|
|
@ -42,6 +42,9 @@ export interface CompilerFacade {
|
||||||
declaration: R3DeclareDirectiveFacade): any;
|
declaration: R3DeclareDirectiveFacade): any;
|
||||||
compileComponent(
|
compileComponent(
|
||||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadataFacade): any;
|
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadataFacade): any;
|
||||||
|
compileComponentDeclaration(
|
||||||
|
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
|
||||||
|
declaration: R3DeclareComponentFacade): any;
|
||||||
compileFactory(
|
compileFactory(
|
||||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade): any;
|
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade): any;
|
||||||
|
|
||||||
|
@ -187,6 +190,24 @@ export interface R3DeclareDirectiveFacade {
|
||||||
usesOnChanges?: boolean;
|
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 {
|
export interface R3UsedDirectiveMetadata {
|
||||||
selector: string;
|
selector: string;
|
||||||
inputs: 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 {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 {Identifiers} from './identifiers';
|
||||||
import {compileInjectable} from './injectable_compiler_2';
|
import {compileInjectable} from './injectable_compiler_2';
|
||||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/interpolation_config';
|
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 {compileInjector, compileNgModule, R3InjectorMetadata, R3NgModuleMetadata} from './render3/r3_module_compiler';
|
||||||
import {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler';
|
import {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler';
|
||||||
import {R3Reference} from './render3/util';
|
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 {compileComponentFromMetadata, compileDirectiveFromMetadata, ParsedHostBindings, parseHostBindings, verifyHostBindings} from './render3/view/compiler';
|
||||||
import {makeBindingParser, parseTemplate} from './render3/view/template';
|
import {makeBindingParser, parseTemplate} from './render3/view/template';
|
||||||
import {ResourceLoader} from './resource_loader';
|
import {ResourceLoader} from './resource_loader';
|
||||||
|
@ -132,32 +132,21 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
||||||
compileComponent(
|
compileComponent(
|
||||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
|
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
|
||||||
facade: R3ComponentMetadataFacade): any {
|
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.
|
// Parse the template and check for errors.
|
||||||
const template = parseTemplate(
|
const {template, interpolation} = parseJitTemplate(
|
||||||
facade.template, sourceMapUrl,
|
facade.template, facade.name, sourceMapUrl, facade.preserveWhitespaces,
|
||||||
{preserveWhitespaces: facade.preserveWhitespaces, interpolationConfig});
|
facade.interpolation);
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile the component metadata, including template, into an expression.
|
// Compile the component metadata, including template, into an expression.
|
||||||
// TODO(alxhub): implement inputs, outputs, queries, etc.
|
const meta: R3ComponentMetadata = {
|
||||||
const metadata: R3ComponentMetadata = {
|
|
||||||
...facade as R3ComponentMetadataFacadeNoPropAndWhitespace,
|
...facade as R3ComponentMetadataFacadeNoPropAndWhitespace,
|
||||||
...convertDirectiveFacadeToMetadata(facade),
|
...convertDirectiveFacadeToMetadata(facade),
|
||||||
selector: facade.selector || this.elementSchemaRegistry.getDefaultComponentElementName(),
|
selector: facade.selector || this.elementSchemaRegistry.getDefaultComponentElementName(),
|
||||||
template,
|
template,
|
||||||
wrapDirectivesAndPipesInClosure: false,
|
declarationListEmitMode: DeclarationListEmitMode.Direct,
|
||||||
styles: [...facade.styles, ...template.styles],
|
styles: [...facade.styles, ...template.styles],
|
||||||
encapsulation: facade.encapsulation as any,
|
encapsulation: facade.encapsulation as any,
|
||||||
interpolation: interpolationConfig,
|
interpolation,
|
||||||
changeDetection: facade.changeDetection,
|
changeDetection: facade.changeDetection,
|
||||||
animations: facade.animations != null ? new WrappedNodeExpr(facade.animations) : null,
|
animations: facade.animations != null ? new WrappedNodeExpr(facade.animations) : null,
|
||||||
viewProviders: facade.viewProviders != null ? new WrappedNodeExpr(facade.viewProviders) :
|
viewProviders: facade.viewProviders != null ? new WrappedNodeExpr(facade.viewProviders) :
|
||||||
|
@ -165,11 +154,26 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
||||||
relativeContextFilePath: '',
|
relativeContextFilePath: '',
|
||||||
i18nUseExternalIds: true,
|
i18nUseExternalIds: true,
|
||||||
};
|
};
|
||||||
const res = compileComponentFromMetadata(
|
|
||||||
metadata, constantPool, makeBindingParser(interpolationConfig));
|
|
||||||
const jitExpressionSourceMap = `ng:///${facade.name}.js`;
|
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(
|
return this.jitExpression(
|
||||||
res.expression, angularCoreEnv, jitExpressionSourceMap, constantPool.statements);
|
res.expression, angularCoreEnv, sourceMapUrl, constantPool.statements);
|
||||||
}
|
}
|
||||||
|
|
||||||
compileFactory(
|
compileFactory(
|
||||||
|
@ -337,6 +341,74 @@ function convertOpaqueValuesToExpressions(obj: {[key: string]: OpaqueValue}):
|
||||||
return result;
|
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
|
// This seems to be needed to placate TS v3.0 only
|
||||||
type R3DirectiveMetadataFacadeNoPropAndWhitespace =
|
type R3DirectiveMetadataFacadeNoPropAndWhitespace =
|
||||||
Pick<R3DirectiveMetadataFacade, Exclude<keyof R3DirectiveMetadataFacade, 'propMetadata'>>;
|
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 {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
|
||||||
import * as o from '../../output/output_ast';
|
import * as o from '../../output/output_ast';
|
||||||
import {Identifiers as R3} from '../r3_identifiers';
|
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 {createComponentType} from '../view/compiler';
|
||||||
import {ParsedTemplate} from '../view/template';
|
import {ParsedTemplate} from '../view/template';
|
||||||
import {DefinitionMap} from '../view/util';
|
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.
|
* individual directives. If the component does not use any directives, then null is returned.
|
||||||
*/
|
*/
|
||||||
function compileUsedDirectiveMetadata(meta: R3ComponentMetadata): o.LiteralArrayExpr|null {
|
function compileUsedDirectiveMetadata(meta: R3ComponentMetadata): o.LiteralArrayExpr|null {
|
||||||
const wrapType =
|
const wrapType = meta.declarationListEmitMode !== DeclarationListEmitMode.Direct ?
|
||||||
meta.wrapDirectivesAndPipesInClosure ? generateForwardRef : (expr: o.Expression) => expr;
|
generateForwardRef :
|
||||||
|
(expr: o.Expression) => expr;
|
||||||
|
|
||||||
return toOptionalLiteralArray(meta.directives, directive => {
|
return toOptionalLiteralArray(meta.directives, directive => {
|
||||||
const dirMeta = new DefinitionMap<R3UsedDirectiveMetadata>();
|
const dirMeta = new DefinitionMap<R3UsedDirectiveMetadata>();
|
||||||
|
@ -115,8 +116,9 @@ function compileUsedPipeMetadata(meta: R3ComponentMetadata): o.LiteralMapExpr|nu
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapType =
|
const wrapType = meta.declarationListEmitMode !== DeclarationListEmitMode.Direct ?
|
||||||
meta.wrapDirectivesAndPipesInClosure ? generateForwardRef : (expr: o.Expression) => expr;
|
generateForwardRef :
|
||||||
|
(expr: o.Expression) => expr;
|
||||||
|
|
||||||
const entries = [];
|
const entries = [];
|
||||||
for (const [name, pipe] of meta.pipes) {
|
for (const [name, pipe] of meta.pipes) {
|
||||||
|
|
|
@ -230,6 +230,7 @@ export class Identifiers {
|
||||||
o.ExternalReference = {name: 'ɵɵtemplateRefExtractor', moduleName: CORE};
|
o.ExternalReference = {name: 'ɵɵtemplateRefExtractor', moduleName: CORE};
|
||||||
|
|
||||||
static forwardRef: o.ExternalReference = {name: 'forwardRef', 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 resolveWindow: o.ExternalReference = {name: 'ɵɵresolveWindow', moduleName: CORE};
|
||||||
static resolveDocument: o.ExternalReference = {name: 'ɵɵresolveDocument', moduleName: CORE};
|
static resolveDocument: o.ExternalReference = {name: 'ɵɵresolveDocument', moduleName: CORE};
|
||||||
|
|
|
@ -119,6 +119,50 @@ export interface R3DirectiveMetadata {
|
||||||
providers: o.Expression|null;
|
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.
|
* Information needed to compile a component for the render3 runtime.
|
||||||
*/
|
*/
|
||||||
|
@ -152,11 +196,9 @@ export interface R3ComponentMetadata extends R3DirectiveMetadata {
|
||||||
directives: R3UsedDirectiveMetadata[];
|
directives: R3UsedDirectiveMetadata[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to wrap the 'directives' and/or `pipes` array, if one is generated, in a closure.
|
* Specifies how the 'directives' and/or `pipes` array, if generated, need to be emitted.
|
||||||
*
|
|
||||||
* This is done when the directives or pipes contain forward references.
|
|
||||||
*/
|
*/
|
||||||
wrapDirectivesAndPipesInClosure: boolean;
|
declarationListEmitMode: DeclarationListEmitMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A collection of styling data that will be applied and scoped to the component.
|
* 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 {Render3ParseResult} from '../r3_template_transform';
|
||||||
import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util';
|
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 {MIN_STYLING_BINDING_SLOTS_REQUIRED, StylingBuilder, StylingInstructionCall} from './styling_builder';
|
||||||
import {BindingScope, makeBindingParser, prepareEventListenerParameters, renderFlagCheckIfStmt, resolveSanitizationFn, TemplateDefinitionBuilder, ValueConverter} from './template';
|
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';
|
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]`
|
// e.g. `directives: [MyDirective]`
|
||||||
if (directivesUsed.size) {
|
if (directivesUsed.size) {
|
||||||
let directivesExpr: o.Expression = o.literalArr(Array.from(directivesUsed));
|
const directivesList = o.literalArr(Array.from(directivesUsed));
|
||||||
if (meta.wrapDirectivesAndPipesInClosure) {
|
const directivesExpr = compileDeclarationList(directivesList, meta.declarationListEmitMode);
|
||||||
directivesExpr = o.fn([], [new o.ReturnStatement(directivesExpr)]);
|
|
||||||
}
|
|
||||||
definitionMap.set('directives', directivesExpr);
|
definitionMap.set('directives', directivesExpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// e.g. `pipes: [MyPipe]`
|
// e.g. `pipes: [MyPipe]`
|
||||||
if (pipesUsed.size) {
|
if (pipesUsed.size) {
|
||||||
let pipesExpr: o.Expression = o.literalArr(Array.from(pipesUsed));
|
const pipesList = o.literalArr(Array.from(pipesUsed));
|
||||||
if (meta.wrapDirectivesAndPipesInClosure) {
|
const pipesExpr = compileDeclarationList(pipesList, meta.declarationListEmitMode);
|
||||||
pipesExpr = o.fn([], [new o.ReturnStatement(pipesExpr)]);
|
|
||||||
}
|
|
||||||
definitionMap.set('pipes', pipesExpr);
|
definitionMap.set('pipes', pipesExpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +273,26 @@ export function createComponentType(meta: R3ComponentMetadata): o.Type {
|
||||||
return o.expressionType(o.importExpr(R3.ComponentDefWithMeta, typeParams));
|
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
|
* A wrapper around `compileDirective` which depends on render2 global analysis data as its input
|
||||||
* instead of the `R3DirectiveMetadata`.
|
* instead of the `R3DirectiveMetadata`.
|
||||||
|
@ -335,7 +351,7 @@ export function compileComponentFromRender2(
|
||||||
directives: [],
|
directives: [],
|
||||||
pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx),
|
pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx),
|
||||||
viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx),
|
viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx),
|
||||||
wrapDirectivesAndPipesInClosure: false,
|
declarationListEmitMode: DeclarationListEmitMode.Direct,
|
||||||
styles: (summary.template && summary.template.styles) || EMPTY_ARRAY,
|
styles: (summary.template && summary.template.styles) || EMPTY_ARRAY,
|
||||||
encapsulation:
|
encapsulation:
|
||||||
(summary.template && summary.template.encapsulation) || core.ViewEncapsulation.Emulated,
|
(summary.template && summary.template.encapsulation) || core.ViewEncapsulation.Emulated,
|
||||||
|
|
|
@ -108,6 +108,16 @@ const coreR3DeclareDirectiveFacade: core.R3DeclareDirectiveFacade =
|
||||||
const compilerR3DeclareDirectiveFacade: compiler.R3DeclareDirectiveFacade =
|
const compilerR3DeclareDirectiveFacade: compiler.R3DeclareDirectiveFacade =
|
||||||
null! as core.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 coreViewEncapsulation: core.ViewEncapsulation = null! as compiler.ViewEncapsulation;
|
||||||
const compilerViewEncapsulation: compiler.ViewEncapsulation = null! as core.ViewEncapsulation;
|
const compilerViewEncapsulation: compiler.ViewEncapsulation = null! as core.ViewEncapsulation;
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,9 @@ export interface CompilerFacade {
|
||||||
declaration: R3DeclareDirectiveFacade): any;
|
declaration: R3DeclareDirectiveFacade): any;
|
||||||
compileComponent(
|
compileComponent(
|
||||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadataFacade): any;
|
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadataFacade): any;
|
||||||
|
compileComponentDeclaration(
|
||||||
|
angularCoreEnv: CoreEnvironment, sourceMapUrl: string,
|
||||||
|
declaration: R3DeclareComponentFacade): any;
|
||||||
compileFactory(
|
compileFactory(
|
||||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade): any;
|
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3FactoryDefMetadataFacade): any;
|
||||||
|
|
||||||
|
@ -187,6 +190,24 @@ export interface R3DeclareDirectiveFacade {
|
||||||
usesOnChanges?: boolean;
|
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 {
|
export interface R3UsedDirectiveMetadata {
|
||||||
selector: string;
|
selector: string;
|
||||||
inputs: string[];
|
inputs: string[];
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {ɵɵinject, ɵɵinvalidFactoryDep} from '../../di/injector_compatibility';
|
||||||
import {ɵɵdefineInjectable, ɵɵdefineInjector} from '../../di/interface/defs';
|
import {ɵɵdefineInjectable, ɵɵdefineInjector} from '../../di/interface/defs';
|
||||||
import * as sanitization from '../../sanitization/sanitization';
|
import * as sanitization from '../../sanitization/sanitization';
|
||||||
|
@ -170,4 +170,5 @@ export const angularCoreEnv: {[name: string]: Function} =
|
||||||
'ɵɵtrustConstantResourceUrl': sanitization.ɵɵtrustConstantResourceUrl,
|
'ɵɵtrustConstantResourceUrl': sanitization.ɵɵtrustConstantResourceUrl,
|
||||||
|
|
||||||
'forwardRef': forwardRef,
|
'forwardRef': forwardRef,
|
||||||
|
'resolveForwardRef': resolveForwardRef,
|
||||||
}))();
|
}))();
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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';
|
import {angularCoreEnv} from './environment';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,6 +25,8 @@ export function ɵɵngDeclareDirective(decl: R3DeclareDirectiveFacade): unknown
|
||||||
*
|
*
|
||||||
* @codeGenApi
|
* @codeGenApi
|
||||||
*/
|
*/
|
||||||
export function ɵɵngDeclareComponent(decl: unknown): unknown {
|
export function ɵɵngDeclareComponent(decl: R3DeclareComponentFacade): unknown {
|
||||||
throw new Error('Not yet implemented');
|
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