feat(ivy): add an IVY local the compiler which avoids analyzeModule (#23441)
closes #23289 Based on a spike by @chukjaz PR Close #23441
This commit is contained in:
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileInjectableMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileShallowModuleMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileInjectableMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileShallowModuleMetadata, CompileStylesheetMetadata, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {ConstantPool} from '../constant_pool';
|
||||
import {ViewEncapsulation} from '../core';
|
||||
@ -14,7 +14,9 @@ import {MessageBundle} from '../i18n/message_bundle';
|
||||
import {Identifiers, createTokenForExternalReference} from '../identifiers';
|
||||
import {InjectableCompiler} from '../injectable_compiler';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import * as html from '../ml_parser/ast';
|
||||
import {HtmlParser} from '../ml_parser/html_parser';
|
||||
import {removeWhitespaces} from '../ml_parser/html_whitespaces';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
import {OutputEmitter} from '../output/abstract_emitter';
|
||||
@ -22,7 +24,9 @@ import * as o from '../output/output_ast';
|
||||
import {ParseError} from '../parse_util';
|
||||
import {compileNgModule as compileIvyModule} from '../render3/r3_module_compiler';
|
||||
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
|
||||
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
|
||||
import {HtmlToTemplateTransform} from '../render3/r3_template_transform';
|
||||
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler_local';
|
||||
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
import {BindingParser} from '../template_parser/binding_parser';
|
||||
@ -39,15 +43,11 @@ import {LazyRoute, listLazyRoutes, parseLazyRoute} from './lazy_routes';
|
||||
import {PartialModule} from './partial_module';
|
||||
import {StaticReflector} from './static_reflector';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||
import {StaticSymbolResolver} from './static_symbol_resolver';
|
||||
import {createForJitStub, serializeSummaries} from './summary_serializer';
|
||||
import {ngfactoryFilePath, normalizeGenFileSuffix, splitTypescriptSuffix, summaryFileName, summaryForJitFileName, summaryForJitName} from './util';
|
||||
import {ngfactoryFilePath, normalizeGenFileSuffix, splitTypescriptSuffix, summaryFileName, summaryForJitFileName} from './util';
|
||||
|
||||
enum StubEmitFlags {
|
||||
Basic = 1 << 0,
|
||||
TypeCheck = 1 << 1,
|
||||
All = TypeCheck | Basic
|
||||
}
|
||||
const enum StubEmitFlags { Basic = 1 << 0, TypeCheck = 1 << 1, All = TypeCheck | Basic }
|
||||
|
||||
export class AotCompiler {
|
||||
private _templateAstCache =
|
||||
@ -369,11 +369,12 @@ export class AotCompiler {
|
||||
fileName: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
|
||||
injectables: CompileInjectableMetadata[], context: OutputContext): void {
|
||||
const classes: o.ClassStmt[] = [];
|
||||
const errors: ParseError[] = [];
|
||||
|
||||
const schemaRegistry = new DomElementSchemaRegistry();
|
||||
const hostBindingParser = new BindingParser(
|
||||
this._templateParser.expressionParser, DEFAULT_INTERPOLATION_CONFIG, null !, [], errors);
|
||||
this._templateParser.expressionParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, [],
|
||||
errors);
|
||||
|
||||
// Process all components and directives
|
||||
directives.forEach(directiveType => {
|
||||
@ -384,11 +385,40 @@ export class AotCompiler {
|
||||
error(
|
||||
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
|
||||
|
||||
const {template: parsedTemplate, pipes: parsedPipes} =
|
||||
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
||||
let htmlAst = directiveMetadata.template !.htmlAst !;
|
||||
const preserveWhitespaces = directiveMetadata !.template !.preserveWhitespaces;
|
||||
|
||||
if (!preserveWhitespaces) {
|
||||
htmlAst = removeWhitespaces(htmlAst);
|
||||
}
|
||||
const transform = new HtmlToTemplateTransform(hostBindingParser);
|
||||
const nodes = html.visitAll(transform, htmlAst.rootNodes, null);
|
||||
const hasNgContent = transform.hasNgContent;
|
||||
const ngContentSelectors = transform.ngContentSelectors;
|
||||
|
||||
// Map of StaticType by directive selectors
|
||||
const directiveTypeBySel = new Map<string, any>();
|
||||
|
||||
const directives = module.transitiveModule.directives.map(
|
||||
dir => this._metadataResolver.getDirectiveSummary(dir.reference));
|
||||
|
||||
directives.forEach(directive => {
|
||||
if (directive.selector) {
|
||||
directiveTypeBySel.set(directive.selector, directive.type.reference);
|
||||
}
|
||||
});
|
||||
|
||||
// Map of StaticType by pipe names
|
||||
const pipeTypeByName = new Map<string, any>();
|
||||
|
||||
const pipes = module.transitiveModule.pipes.map(
|
||||
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
||||
|
||||
pipes.forEach(pipe => { pipeTypeByName.set(pipe.name, pipe.type.reference); });
|
||||
|
||||
compileIvyComponent(
|
||||
context, directiveMetadata, parsedPipes, parsedTemplate, this.reflector,
|
||||
hostBindingParser);
|
||||
context, directiveMetadata, nodes, hasNgContent, ngContentSelectors, this.reflector,
|
||||
hostBindingParser, directiveTypeBySel, pipeTypeByName);
|
||||
} else {
|
||||
compileIvyDirective(context, directiveMetadata, this.reflector, hostBindingParser);
|
||||
}
|
||||
@ -891,16 +921,13 @@ export function analyzeFileForInjectables(
|
||||
if (!symbolMeta || symbolMeta.__symbolic === 'error') {
|
||||
return;
|
||||
}
|
||||
let isNgSymbol = false;
|
||||
if (symbolMeta.__symbolic === 'class') {
|
||||
if (metadataResolver.isInjectable(symbol)) {
|
||||
isNgSymbol = true;
|
||||
const injectable = metadataResolver.getInjectableMetadata(symbol, null, false);
|
||||
if (injectable) {
|
||||
injectables.push(injectable);
|
||||
}
|
||||
} else if (metadataResolver.isNgModule(symbol)) {
|
||||
isNgSymbol = true;
|
||||
const module = metadataResolver.getShallowModuleMetadata(symbol);
|
||||
if (module) {
|
||||
shallowModules.push(module);
|
||||
|
241
packages/compiler/src/render3/r3_ast.ts
Normal file
241
packages/compiler/src/render3/r3_ast.ts
Normal file
@ -0,0 +1,241 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {SecurityContext} from '../core';
|
||||
import {AST} from '../expression_parser/ast';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
|
||||
export interface Node {
|
||||
sourceSpan: ParseSourceSpan;
|
||||
visit<Result>(visitor: Visitor<Result>): Result;
|
||||
}
|
||||
|
||||
export class Text implements Node {
|
||||
constructor(public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitText(this); }
|
||||
}
|
||||
|
||||
export class BoundText implements Node {
|
||||
constructor(public value: AST, public sourceSpan: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundText(this); }
|
||||
}
|
||||
|
||||
export class TextAttribute implements Node {
|
||||
constructor(
|
||||
public name: string, public value: string, public sourceSpan: ParseSourceSpan,
|
||||
public valueSpan?: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitAttribute(this); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration of types of property bindings.
|
||||
*/
|
||||
export enum PropertyBindingType {
|
||||
|
||||
/**
|
||||
* A normal binding to a property (e.g. `[property]="expression"`).
|
||||
*/
|
||||
Property,
|
||||
|
||||
/**
|
||||
* A binding to an element attribute (e.g. `[attr.name]="expression"`).
|
||||
*/
|
||||
Attribute,
|
||||
|
||||
/**
|
||||
* A binding to a CSS class (e.g. `[class.name]="condition"`).
|
||||
*/
|
||||
Class,
|
||||
|
||||
/**
|
||||
* A binding to a style rule (e.g. `[style.rule]="expression"`).
|
||||
*/
|
||||
Style,
|
||||
|
||||
/**
|
||||
* A binding to an animation reference (e.g. `[animate.key]="expression"`).
|
||||
*/
|
||||
Animation
|
||||
}
|
||||
|
||||
export class BoundAttribute implements Node {
|
||||
constructor(
|
||||
public name: string, public type: PropertyBindingType,
|
||||
public securityContext: SecurityContext, public value: AST, public unit: string|null,
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundAttribute(this); }
|
||||
}
|
||||
|
||||
export class BoundEvent implements Node {
|
||||
constructor(
|
||||
public name: string, public handler: AST, public target: string|null,
|
||||
public phase: string|null, public sourceSpan: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundEvent(this); }
|
||||
}
|
||||
|
||||
export class Element implements Node {
|
||||
constructor(
|
||||
public name: string, public attributes: TextAttribute[], public inputs: BoundAttribute[],
|
||||
public outputs: BoundEvent[], public children: Node[], public references: Reference[],
|
||||
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan|null,
|
||||
public endSourceSpan: ParseSourceSpan|null) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitElement(this); }
|
||||
}
|
||||
|
||||
export class Template implements Node {
|
||||
constructor(
|
||||
public attributes: TextAttribute[], public inputs: BoundAttribute[], public children: Node[],
|
||||
public references: Reference[], public variables: Variable[],
|
||||
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan|null,
|
||||
public endSourceSpan: ParseSourceSpan|null) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitTemplate(this); }
|
||||
}
|
||||
|
||||
export class Content implements Node {
|
||||
constructor(
|
||||
public selectorIndex: number, public attributes: TextAttribute[],
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitContent(this); }
|
||||
}
|
||||
|
||||
export class Variable implements Node {
|
||||
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitVariable(this); }
|
||||
}
|
||||
|
||||
export class Reference implements Node {
|
||||
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitReference(this); }
|
||||
}
|
||||
|
||||
export interface Visitor<Result = any> {
|
||||
// Returning a truthy value from `visit()` will prevent `visitAll()` from the call to the typed
|
||||
// method and result returned will become the result included in `visitAll()`s result array.
|
||||
visit?(node: Node): Result;
|
||||
|
||||
visitElement(element: Element): Result;
|
||||
visitTemplate(template: Template): Result;
|
||||
visitContent(content: Content): Result;
|
||||
visitVariable(variable: Variable): Result;
|
||||
visitReference(reference: Reference): Result;
|
||||
visitAttribute(attribute: TextAttribute): Result;
|
||||
visitBoundAttribute(attribute: BoundAttribute): Result;
|
||||
visitBoundEvent(attribute: BoundEvent): Result;
|
||||
visitText(text: Text): Result;
|
||||
visitBoundText(text: BoundText): Result;
|
||||
}
|
||||
|
||||
export class NullVisitor implements Visitor<void> {
|
||||
visitElement(element: Element): void {}
|
||||
visitTemplate(template: Template): void {}
|
||||
visitContent(content: Content): void {}
|
||||
visitVariable(variable: Variable): void {}
|
||||
visitReference(reference: Reference): void {}
|
||||
visitAttribute(attribute: TextAttribute): void {}
|
||||
visitBoundAttribute(attribute: BoundAttribute): void {}
|
||||
visitBoundEvent(attribute: BoundEvent): void {}
|
||||
visitText(text: Text): void {}
|
||||
visitBoundText(text: BoundText): void {}
|
||||
}
|
||||
|
||||
export class RecursiveVisitor implements Visitor<void> {
|
||||
visitElement(element: Element): void {
|
||||
visitAll(this, element.attributes);
|
||||
visitAll(this, element.children);
|
||||
visitAll(this, element.references);
|
||||
}
|
||||
visitTemplate(template: Template): void {
|
||||
visitAll(this, template.attributes);
|
||||
visitAll(this, template.children);
|
||||
visitAll(this, template.references);
|
||||
visitAll(this, template.variables);
|
||||
}
|
||||
visitContent(content: Content): void {}
|
||||
visitVariable(variable: Variable): void {}
|
||||
visitReference(reference: Reference): void {}
|
||||
visitAttribute(attribute: TextAttribute): void {}
|
||||
visitBoundAttribute(attribute: BoundAttribute): void {}
|
||||
visitBoundEvent(attribute: BoundEvent): void {}
|
||||
visitText(text: Text): void {}
|
||||
visitBoundText(text: BoundText): void {}
|
||||
}
|
||||
|
||||
export class TransformVisitor implements Visitor<Node> {
|
||||
visitElement(element: Element): Node {
|
||||
const newAttributes = transformAll(this, element.attributes);
|
||||
const newInputs = transformAll(this, element.inputs);
|
||||
const newOutputs = transformAll(this, element.outputs);
|
||||
const newChildren = transformAll(this, element.children);
|
||||
const newReferences = transformAll(this, element.references);
|
||||
if (newAttributes != element.attributes || newInputs != element.inputs ||
|
||||
newOutputs != element.outputs || newChildren != element.children ||
|
||||
newReferences != element.references) {
|
||||
return new Element(
|
||||
element.name, newAttributes, newInputs, newOutputs, newChildren, newReferences,
|
||||
element.sourceSpan, element.startSourceSpan, element.endSourceSpan);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
visitTemplate(template: Template): Node {
|
||||
const newAttributes = transformAll(this, template.attributes);
|
||||
const newInputs = transformAll(this, template.inputs);
|
||||
const newChildren = transformAll(this, template.children);
|
||||
const newReferences = transformAll(this, template.references);
|
||||
const newVariables = transformAll(this, template.variables);
|
||||
if (newAttributes != template.attributes || newInputs != template.inputs ||
|
||||
newChildren != template.children || newVariables != template.variables ||
|
||||
newReferences != template.references) {
|
||||
return new Template(
|
||||
newAttributes, newInputs, newChildren, newReferences, newVariables, template.sourceSpan,
|
||||
template.startSourceSpan, template.endSourceSpan);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
visitContent(content: Content): Node { return content; }
|
||||
|
||||
visitVariable(variable: Variable): Node { return variable; }
|
||||
visitReference(reference: Reference): Node { return reference; }
|
||||
visitAttribute(attribute: TextAttribute): Node { return attribute; }
|
||||
visitBoundAttribute(attribute: BoundAttribute): Node { return attribute; }
|
||||
visitBoundEvent(attribute: BoundEvent): Node { return attribute; }
|
||||
visitText(text: Text): Node { return text; }
|
||||
visitBoundText(text: BoundText): Node { return text; }
|
||||
}
|
||||
|
||||
export function visitAll<Result>(visitor: Visitor<Result>, nodes: Node[]): Result[] {
|
||||
const result: Result[] = [];
|
||||
if (visitor.visit) {
|
||||
for (const node of nodes) {
|
||||
const newNode = visitor.visit(node) || node.visit(visitor);
|
||||
}
|
||||
} else {
|
||||
for (const node of nodes) {
|
||||
const newNode = node.visit(visitor);
|
||||
if (newNode) {
|
||||
result.push(newNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function transformAll<Result extends Node>(
|
||||
visitor: Visitor<Node>, nodes: Result[]): Result[] {
|
||||
const result: Result[] = [];
|
||||
let changed = false;
|
||||
for (const node of nodes) {
|
||||
const newNode = node.visit(visitor);
|
||||
if (newNode) {
|
||||
result.push(newNode as Result);
|
||||
}
|
||||
changed = changed || newNode != node;
|
||||
}
|
||||
return changed ? result : nodes;
|
||||
}
|
@ -31,14 +31,8 @@ export class Identifiers {
|
||||
|
||||
static containerCreate: o.ExternalReference = {name: 'ɵC', moduleName: CORE};
|
||||
|
||||
static containerEnd: o.ExternalReference = {name: 'ɵc', moduleName: CORE};
|
||||
|
||||
static directiveCreate: o.ExternalReference = {name: 'ɵD', moduleName: CORE};
|
||||
|
||||
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};
|
||||
|
||||
static directiveInput: o.ExternalReference = {name: 'ɵi', moduleName: CORE};
|
||||
|
||||
static textCreateBound: o.ExternalReference = {name: 'ɵt', moduleName: CORE};
|
||||
|
||||
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};
|
||||
@ -77,10 +71,6 @@ export class Identifiers {
|
||||
static projection: o.ExternalReference = {name: 'ɵP', moduleName: CORE};
|
||||
static projectionDef: o.ExternalReference = {name: 'ɵpD', moduleName: CORE};
|
||||
|
||||
static refreshComponent: o.ExternalReference = {name: 'ɵr', moduleName: CORE};
|
||||
|
||||
static directiveLifeCycle: o.ExternalReference = {name: 'ɵl', moduleName: CORE};
|
||||
|
||||
static injectAttribute: o.ExternalReference = {name: 'ɵinjectAttribute', moduleName: CORE};
|
||||
|
||||
static injectElementRef: o.ExternalReference = {name: 'ɵinjectElementRef', moduleName: CORE};
|
||||
|
@ -15,18 +15,18 @@ import {OutputContext} from '../util';
|
||||
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
|
||||
const EMPTY_ARRAY = o.literalArr([]);
|
||||
|
||||
function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression {
|
||||
if (Array.isArray(meta)) {
|
||||
return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx)));
|
||||
} else if (meta instanceof StaticSymbol) {
|
||||
return ctx.importExpr(meta);
|
||||
} else if (meta == null) {
|
||||
return o.literal(meta);
|
||||
} else {
|
||||
throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`);
|
||||
}
|
||||
if (meta instanceof StaticSymbol) {
|
||||
return ctx.importExpr(meta);
|
||||
}
|
||||
if (meta == null) {
|
||||
return o.literal(meta);
|
||||
}
|
||||
|
||||
throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`);
|
||||
}
|
||||
|
||||
export function compileNgModule(
|
||||
|
@ -6,8 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticReflector} from '../aot/static_reflector';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeSummary, CompileTypeMetadata, identifierName} from '../compile_metadata';
|
||||
import {CompileNgModuleMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import * as o from '../output/output_ast';
|
||||
import {OutputContext} from '../util';
|
||||
|
@ -6,15 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileDirectiveMetadata, CompilePipeMetadata, identifierName} from '../compile_metadata';
|
||||
import {CompilePipeMetadata, identifierName} from '../compile_metadata';
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {DefinitionKind} from '../constant_pool';
|
||||
import * as o from '../output/output_ast';
|
||||
import {OutputContext, error} from '../util';
|
||||
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
import {BUILD_OPTIMIZER_COLOCATE} from './r3_types';
|
||||
import {createFactory} from './r3_view_compiler';
|
||||
import {createFactory} from './r3_view_compiler_local';
|
||||
|
||||
/**
|
||||
* Write a pipe definition to the output context.
|
||||
@ -30,11 +29,11 @@ export function compilePipe(
|
||||
definitionMapValues.push(
|
||||
{key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false});
|
||||
|
||||
// e.g. factory: function MyPipe_Factory() { return new MyPipe(); },
|
||||
// e.g. `factory: function MyPipe_Factory() { return new MyPipe(); }`
|
||||
const templateFactory = createFactory(pipe.type, outputCtx, reflector, []);
|
||||
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
|
||||
|
||||
// e.g. pure: true
|
||||
// e.g. `pure: true`
|
||||
if (pipe.pure) {
|
||||
definitionMapValues.push({key: 'pure', value: o.literal(true), quoted: false});
|
||||
}
|
||||
|
366
packages/compiler/src/render3/r3_template_transform.ts
Normal file
366
packages/compiler/src/render3/r3_template_transform.ts
Normal file
@ -0,0 +1,366 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as html from '../ml_parser/ast';
|
||||
import {replaceNgsp} from '../ml_parser/html_whitespaces';
|
||||
import {isNgTemplate} from '../ml_parser/tags';
|
||||
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
||||
import {isStyleUrlResolvable} from '../style_url_resolver';
|
||||
import {BindingParser, BoundProperty} from '../template_parser/binding_parser';
|
||||
// TODO(chuckj): Refactor binding parser to not have a dependency on template_ast.
|
||||
import {BoundEventAst, VariableAst} from '../template_parser/template_ast';
|
||||
import {PreparsedElementType, preparseElement} from '../template_parser/template_preparser';
|
||||
|
||||
import * as t from './r3_ast';
|
||||
|
||||
const BIND_NAME_REGEXP =
|
||||
/^(?:(?:(?:(bind-)|(let-)|(ref-|#)|(on-)|(bindon-)|(@))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/;
|
||||
|
||||
// Group 1 = "bind-"
|
||||
const KW_BIND_IDX = 1;
|
||||
// Group 2 = "let-"
|
||||
const KW_LET_IDX = 2;
|
||||
// Group 3 = "ref-/#"
|
||||
const KW_REF_IDX = 3;
|
||||
// Group 4 = "on-"
|
||||
const KW_ON_IDX = 4;
|
||||
// Group 5 = "bindon-"
|
||||
const KW_BINDON_IDX = 5;
|
||||
// Group 6 = "@"
|
||||
const KW_AT_IDX = 6;
|
||||
// Group 7 = the identifier after "bind-", "let-", "ref-/#", "on-", "bindon-" or "@"
|
||||
const IDENT_KW_IDX = 7;
|
||||
// Group 8 = identifier inside [()]
|
||||
const IDENT_BANANA_BOX_IDX = 8;
|
||||
// Group 9 = identifier inside []
|
||||
const IDENT_PROPERTY_IDX = 9;
|
||||
// Group 10 = identifier inside ()
|
||||
const IDENT_EVENT_IDX = 10;
|
||||
|
||||
const TEMPLATE_ATTR_PREFIX = '*';
|
||||
const CLASS_ATTR = 'class';
|
||||
// Default selector used by `<ng-content>` if none specified
|
||||
const DEFAULT_CONTENT_SELECTOR = '*';
|
||||
|
||||
export class HtmlToTemplateTransform implements html.Visitor {
|
||||
errors: ParseError[];
|
||||
|
||||
// Selectors for the `ng-content` tags. Only non `*` selectors are recorded here
|
||||
ngContentSelectors: string[] = [];
|
||||
// Any `<ng-content>` in the template ?
|
||||
hasNgContent = false;
|
||||
|
||||
constructor(private bindingParser: BindingParser) {}
|
||||
|
||||
// HTML visitor
|
||||
visitElement(element: html.Element): t.Node|null {
|
||||
const preparsedElement = preparseElement(element);
|
||||
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
||||
preparsedElement.type === PreparsedElementType.STYLE) {
|
||||
// Skipping <script> for security reasons
|
||||
// Skipping <style> as we already processed them
|
||||
// in the StyleCompiler
|
||||
return null;
|
||||
}
|
||||
if (preparsedElement.type === PreparsedElementType.STYLESHEET &&
|
||||
isStyleUrlResolvable(preparsedElement.hrefAttr)) {
|
||||
// Skipping stylesheets with either relative urls or package scheme as we already processed
|
||||
// them in the StyleCompiler
|
||||
return null;
|
||||
}
|
||||
|
||||
// Whether the element is a `<ng-template>`
|
||||
const isTemplateElement = isNgTemplate(element.name);
|
||||
|
||||
const matchableAttributes: [string, string][] = [];
|
||||
const boundProperties: BoundProperty[] = [];
|
||||
const boundEvents: t.BoundEvent[] = [];
|
||||
const variables: t.Variable[] = [];
|
||||
const references: t.Reference[] = [];
|
||||
const attributes: t.TextAttribute[] = [];
|
||||
|
||||
const templateMatchableAttributes: [string, string][] = [];
|
||||
let inlineTemplateSourceSpan: ParseSourceSpan;
|
||||
const templateBoundProperties: BoundProperty[] = [];
|
||||
const templateVariables: t.Variable[] = [];
|
||||
|
||||
// Whether the element has any *-attribute
|
||||
let elementHasInlineTemplate = false;
|
||||
|
||||
for (const attribute of element.attrs) {
|
||||
let hasBinding = false;
|
||||
const normalizedName = normalizeAttributeName(attribute.name);
|
||||
|
||||
// `*attr` defines template bindings
|
||||
let isTemplateBinding = false;
|
||||
|
||||
if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||
if (elementHasInlineTemplate) {
|
||||
this.reportError(
|
||||
`Can't have multiple template bindings on one element. Use only one attribute prefixed with *`,
|
||||
attribute.sourceSpan);
|
||||
}
|
||||
isTemplateBinding = true;
|
||||
elementHasInlineTemplate = true;
|
||||
const templateBindingsSource = attribute.value;
|
||||
const prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
|
||||
|
||||
const oldVariables: VariableAst[] = [];
|
||||
|
||||
inlineTemplateSourceSpan = attribute.valueSpan || attribute.sourceSpan;
|
||||
|
||||
this.bindingParser.parseInlineTemplateBinding(
|
||||
prefixToken !, templateBindingsSource !, attribute.sourceSpan,
|
||||
templateMatchableAttributes, templateBoundProperties, oldVariables);
|
||||
|
||||
templateVariables.push(
|
||||
...oldVariables.map(v => new t.Variable(v.name, v.value, v.sourceSpan)));
|
||||
} else {
|
||||
// Check for variables, events, property bindings, interpolation
|
||||
hasBinding = this.parseAttribute(
|
||||
isTemplateElement, attribute, matchableAttributes, boundProperties, boundEvents,
|
||||
variables, references);
|
||||
}
|
||||
|
||||
if (!hasBinding && !isTemplateBinding) {
|
||||
// don't include the bindings as attributes as well in the AST
|
||||
attributes.push(this.visitAttribute(attribute) as t.TextAttribute);
|
||||
matchableAttributes.push([attribute.name, attribute.value]);
|
||||
}
|
||||
}
|
||||
|
||||
const children: t.Node[] =
|
||||
html.visitAll(preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children);
|
||||
|
||||
let parsedElement: t.Node|undefined;
|
||||
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
|
||||
// `<ng-content>`
|
||||
this.hasNgContent = true;
|
||||
|
||||
if (element.children && !element.children.every(isEmptyTextNode)) {
|
||||
this.reportError(`<ng-content> element cannot have content.`, element.sourceSpan);
|
||||
}
|
||||
|
||||
const selector = preparsedElement.selectAttr;
|
||||
|
||||
let attributes: t.TextAttribute[] = element.attrs.map(attribute => {
|
||||
return new t.TextAttribute(
|
||||
attribute.name, attribute.value, attribute.sourceSpan, attribute.valueSpan);
|
||||
});
|
||||
|
||||
const selectorIndex =
|
||||
selector === DEFAULT_CONTENT_SELECTOR ? 0 : this.ngContentSelectors.push(selector);
|
||||
parsedElement = new t.Content(selectorIndex, attributes, element.sourceSpan);
|
||||
} else if (isTemplateElement) {
|
||||
// `<ng-template>`
|
||||
const boundAttributes = this.createBoundAttributes(element.name, boundProperties);
|
||||
parsedElement = new t.Template(
|
||||
attributes, boundAttributes, children, references, variables, element.sourceSpan,
|
||||
element.startSourceSpan, element.endSourceSpan);
|
||||
} else {
|
||||
const boundAttributes = this.createBoundAttributes(element.name, boundProperties);
|
||||
|
||||
parsedElement = new t.Element(
|
||||
element.name, attributes, boundAttributes, boundEvents, children, references,
|
||||
element.sourceSpan, element.startSourceSpan, element.endSourceSpan);
|
||||
}
|
||||
|
||||
if (elementHasInlineTemplate) {
|
||||
const attributes: t.TextAttribute[] = [];
|
||||
|
||||
templateMatchableAttributes.forEach(
|
||||
([name, value]) =>
|
||||
attributes.push(new t.TextAttribute(name, value, inlineTemplateSourceSpan)));
|
||||
|
||||
const boundAttributes = this.createBoundAttributes('ng-template', templateBoundProperties);
|
||||
parsedElement = new t.Template(
|
||||
attributes, boundAttributes, [parsedElement], [], templateVariables, element.sourceSpan,
|
||||
element.startSourceSpan, element.endSourceSpan);
|
||||
}
|
||||
return parsedElement;
|
||||
}
|
||||
|
||||
visitAttribute(attribute: html.Attribute): t.Node {
|
||||
return new t.TextAttribute(
|
||||
attribute.name, attribute.value, attribute.sourceSpan, attribute.valueSpan);
|
||||
}
|
||||
|
||||
visitText(text: html.Text): t.Node {
|
||||
const valueNoNgsp = replaceNgsp(text.value);
|
||||
const expr = this.bindingParser.parseInterpolation(valueNoNgsp, text.sourceSpan);
|
||||
return expr ? new t.BoundText(expr, text.sourceSpan) : new t.Text(valueNoNgsp, text.sourceSpan);
|
||||
}
|
||||
|
||||
visitComment(comment: html.Comment): null { return null; }
|
||||
|
||||
visitExpansion(expansion: html.Expansion): null { return null; }
|
||||
|
||||
visitExpansionCase(expansionCase: html.ExpansionCase): null { return null; }
|
||||
|
||||
private createBoundAttributes(elementName: string, boundProperties: BoundProperty[]):
|
||||
t.BoundAttribute[] {
|
||||
const literalProperties = boundProperties.filter(prop => !prop.isLiteral);
|
||||
|
||||
return literalProperties.map(property => {
|
||||
// TODO(vicb): get ride of the boundProperty (from TemplateAst)
|
||||
const boundProp = this.bindingParser.createElementPropertyAst(elementName, property);
|
||||
return new t.BoundAttribute(
|
||||
boundProp.name, boundProp.type as any as t.PropertyBindingType, boundProp.securityContext,
|
||||
boundProp.value, boundProp.unit, boundProp.sourceSpan);
|
||||
});
|
||||
}
|
||||
|
||||
private parseAttribute(
|
||||
isTemplateElement: boolean, attribute: html.Attribute, matchableAttributes: string[][],
|
||||
boundProperties: BoundProperty[], boundEvents: t.BoundEvent[], variables: t.Variable[],
|
||||
references: t.Reference[]) {
|
||||
const name = normalizeAttributeName(attribute.name);
|
||||
const value = attribute.value;
|
||||
const srcSpan = attribute.sourceSpan;
|
||||
|
||||
const bindParts = name.match(BIND_NAME_REGEXP);
|
||||
let hasBinding = false;
|
||||
|
||||
if (bindParts) {
|
||||
hasBinding = true;
|
||||
if (bindParts[KW_BIND_IDX] != null) {
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, boundProperties);
|
||||
|
||||
} else if (bindParts[KW_LET_IDX]) {
|
||||
if (isTemplateElement) {
|
||||
const identifier = bindParts[IDENT_KW_IDX];
|
||||
this.parseVariable(identifier, value, srcSpan, variables);
|
||||
} else {
|
||||
this.reportError(`"let-" is only supported on ng-template elements.`, srcSpan);
|
||||
}
|
||||
|
||||
} else if (bindParts[KW_REF_IDX]) {
|
||||
const identifier = bindParts[IDENT_KW_IDX];
|
||||
this.parseReference(identifier, value, srcSpan, references);
|
||||
|
||||
} else if (bindParts[KW_ON_IDX]) {
|
||||
const events: BoundEventAst[] = [];
|
||||
this.bindingParser.parseEvent(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, events);
|
||||
addEvents(events, boundEvents);
|
||||
} else if (bindParts[KW_BINDON_IDX]) {
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, boundProperties);
|
||||
this.parseAssignmentEvent(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, boundEvents);
|
||||
} else if (bindParts[KW_AT_IDX]) {
|
||||
this.bindingParser.parseLiteralAttr(
|
||||
name, value, srcSpan, matchableAttributes, boundProperties);
|
||||
|
||||
} else if (bindParts[IDENT_BANANA_BOX_IDX]) {
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, matchableAttributes,
|
||||
boundProperties);
|
||||
this.parseAssignmentEvent(
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, matchableAttributes, boundEvents);
|
||||
|
||||
} else if (bindParts[IDENT_PROPERTY_IDX]) {
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_PROPERTY_IDX], value, false, srcSpan, matchableAttributes,
|
||||
boundProperties);
|
||||
|
||||
} else if (bindParts[IDENT_EVENT_IDX]) {
|
||||
const events: BoundEventAst[] = [];
|
||||
this.bindingParser.parseEvent(
|
||||
bindParts[IDENT_EVENT_IDX], value, srcSpan, matchableAttributes, events);
|
||||
addEvents(events, boundEvents);
|
||||
}
|
||||
} else {
|
||||
hasBinding = this.bindingParser.parsePropertyInterpolation(
|
||||
name, value, srcSpan, matchableAttributes, boundProperties);
|
||||
}
|
||||
|
||||
return hasBinding;
|
||||
}
|
||||
|
||||
|
||||
private parseVariable(
|
||||
identifier: string, value: string, sourceSpan: ParseSourceSpan, variables: t.Variable[]) {
|
||||
if (identifier.indexOf('-') > -1) {
|
||||
this.reportError(`"-" is not allowed in variable names`, sourceSpan);
|
||||
}
|
||||
variables.push(new t.Variable(identifier, value, sourceSpan));
|
||||
}
|
||||
|
||||
private parseReference(
|
||||
identifier: string, value: string, sourceSpan: ParseSourceSpan, references: t.Reference[]) {
|
||||
if (identifier.indexOf('-') > -1) {
|
||||
this.reportError(`"-" is not allowed in reference names`, sourceSpan);
|
||||
}
|
||||
|
||||
references.push(new t.Reference(identifier, value, sourceSpan));
|
||||
}
|
||||
|
||||
private parseAssignmentEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], boundEvents: t.BoundEvent[]) {
|
||||
const events: BoundEventAst[] = [];
|
||||
this.bindingParser.parseEvent(
|
||||
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, events);
|
||||
addEvents(events, boundEvents);
|
||||
}
|
||||
|
||||
private reportError(
|
||||
message: string, sourceSpan: ParseSourceSpan,
|
||||
level: ParseErrorLevel = ParseErrorLevel.ERROR) {
|
||||
this.errors.push(new ParseError(sourceSpan, message, level));
|
||||
}
|
||||
}
|
||||
|
||||
class NonBindableVisitor implements html.Visitor {
|
||||
visitElement(ast: html.Element): t.Element|null {
|
||||
const preparsedElement = preparseElement(ast);
|
||||
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
||||
preparsedElement.type === PreparsedElementType.STYLE ||
|
||||
preparsedElement.type === PreparsedElementType.STYLESHEET) {
|
||||
// Skipping <script> for security reasons
|
||||
// Skipping <style> and stylesheets as we already processed them
|
||||
// in the StyleCompiler
|
||||
return null;
|
||||
}
|
||||
|
||||
const children: t.Node[] = html.visitAll(this, ast.children, null);
|
||||
return new t.Element(
|
||||
ast.name, html.visitAll(this, ast.attrs) as t.TextAttribute[],
|
||||
/* inputs */[], /* outputs */[], children, /* references */[], ast.sourceSpan,
|
||||
ast.startSourceSpan, ast.endSourceSpan);
|
||||
}
|
||||
|
||||
visitComment(comment: html.Comment): any { return null; }
|
||||
|
||||
visitAttribute(attribute: html.Attribute): t.TextAttribute {
|
||||
return new t.TextAttribute(attribute.name, attribute.value, attribute.sourceSpan);
|
||||
}
|
||||
|
||||
visitText(text: html.Text): t.Text { return new t.Text(text.value, text.sourceSpan); }
|
||||
|
||||
visitExpansion(expansion: html.Expansion): any { return null; }
|
||||
|
||||
visitExpansionCase(expansionCase: html.ExpansionCase): any { return null; }
|
||||
}
|
||||
|
||||
const NON_BINDABLE_VISITOR = new NonBindableVisitor();
|
||||
|
||||
function normalizeAttributeName(attrName: string): string {
|
||||
return /^data-/i.test(attrName) ? attrName.substring(5) : attrName;
|
||||
}
|
||||
|
||||
function addEvents(events: BoundEventAst[], boundEvents: t.BoundEvent[]) {
|
||||
boundEvents.push(
|
||||
...events.map(e => new t.BoundEvent(e.name, e.handler, e.target, e.phase, e.sourceSpan)));
|
||||
}
|
||||
|
||||
function isEmptyTextNode(node: html.Node): boolean {
|
||||
return node instanceof html.Text && node.value.trim().length == 0;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -51,14 +51,20 @@ export class BoundProperty {
|
||||
* Parses bindings in templates and in the directive host area.
|
||||
*/
|
||||
export class BindingParser {
|
||||
pipesByName: Map<string, CompilePipeSummary> = new Map();
|
||||
pipesByName: Map<string, CompilePipeSummary>|null = null;
|
||||
private _usedPipes: Map<string, CompilePipeSummary> = new Map();
|
||||
|
||||
constructor(
|
||||
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
|
||||
private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeSummary[],
|
||||
private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeSummary[]|null,
|
||||
private _targetErrors: ParseError[]) {
|
||||
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
|
||||
// When the `pipes` parameter is `null`, do not check for used pipes
|
||||
// This is used in IVY when we might not know the available pipes at compile time
|
||||
if (pipes) {
|
||||
const pipesByName: Map<string, CompilePipeSummary> = new Map();
|
||||
pipes.forEach(pipe => pipesByName.set(pipe.name, pipe));
|
||||
this.pipesByName = pipesByName;
|
||||
}
|
||||
}
|
||||
|
||||
getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); }
|
||||
@ -390,11 +396,11 @@ export class BindingParser {
|
||||
|
||||
// Make sure all the used pipes are known in `this.pipesByName`
|
||||
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan): void {
|
||||
if (ast) {
|
||||
if (ast && this.pipesByName) {
|
||||
const collector = new PipeCollector();
|
||||
ast.visit(collector);
|
||||
collector.pipes.forEach((ast, pipeName) => {
|
||||
const pipeMeta = this.pipesByName.get(pipeName);
|
||||
const pipeMeta = this.pipesByName !.get(pipeName);
|
||||
if (!pipeMeta) {
|
||||
this._reportError(
|
||||
`The pipe '${pipeName}' could not be found`,
|
||||
|
Reference in New Issue
Block a user