diff --git a/packages/compiler/src/aot/compiler.ts b/packages/compiler/src/aot/compiler.ts index 4bfc4004b6..775b8d5d8b 100644 --- a/packages/compiler/src/aot/compiler.ts +++ b/packages/compiler/src/aot/compiler.ts @@ -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, 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(); + + 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(); + + 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); diff --git a/packages/compiler/src/render3/r3_ast.ts b/packages/compiler/src/render3/r3_ast.ts new file mode 100644 index 0000000000..c35a78dd53 --- /dev/null +++ b/packages/compiler/src/render3/r3_ast.ts @@ -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(visitor: Visitor): Result; +} + +export class Text implements Node { + constructor(public value: string, public sourceSpan: ParseSourceSpan) {} + visit(visitor: Visitor): Result { return visitor.visitText(this); } +} + +export class BoundText implements Node { + constructor(public value: AST, public sourceSpan: ParseSourceSpan) {} + visit(visitor: Visitor): Result { return visitor.visitBoundText(this); } +} + +export class TextAttribute implements Node { + constructor( + public name: string, public value: string, public sourceSpan: ParseSourceSpan, + public valueSpan?: ParseSourceSpan) {} + visit(visitor: Visitor): 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(visitor: Visitor): 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(visitor: Visitor): 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(visitor: Visitor): 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(visitor: Visitor): Result { return visitor.visitTemplate(this); } +} + +export class Content implements Node { + constructor( + public selectorIndex: number, public attributes: TextAttribute[], + public sourceSpan: ParseSourceSpan) {} + visit(visitor: Visitor): Result { return visitor.visitContent(this); } +} + +export class Variable implements Node { + constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {} + visit(visitor: Visitor): Result { return visitor.visitVariable(this); } +} + +export class Reference implements Node { + constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {} + visit(visitor: Visitor): Result { return visitor.visitReference(this); } +} + +export interface Visitor { + // 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 { + 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 { + 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 { + 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(visitor: Visitor, 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( + visitor: Visitor, 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; +} \ No newline at end of file diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index dff47eb6f9..c18542fbe2 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -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}; diff --git a/packages/compiler/src/render3/r3_module_compiler.ts b/packages/compiler/src/render3/r3_module_compiler.ts index 547ed95fcc..f0d2c93a83 100644 --- a/packages/compiler/src/render3/r3_module_compiler.ts +++ b/packages/compiler/src/render3/r3_module_compiler.ts @@ -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( diff --git a/packages/compiler/src/render3/r3_module_factory_compiler.ts b/packages/compiler/src/render3/r3_module_factory_compiler.ts index 36aae89480..a6e64a7455 100644 --- a/packages/compiler/src/render3/r3_module_factory_compiler.ts +++ b/packages/compiler/src/render3/r3_module_factory_compiler.ts @@ -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'; diff --git a/packages/compiler/src/render3/r3_pipe_compiler.ts b/packages/compiler/src/render3/r3_pipe_compiler.ts index 6f60723caa..f1a849c33c 100644 --- a/packages/compiler/src/render3/r3_pipe_compiler.ts +++ b/packages/compiler/src/render3/r3_pipe_compiler.ts @@ -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}); } diff --git a/packages/compiler/src/render3/r3_template_transform.ts b/packages/compiler/src/render3/r3_template_transform.ts new file mode 100644 index 0000000000..7fce7119c6 --- /dev/null +++ b/packages/compiler/src/render3/r3_template_transform.ts @@ -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 `` 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 `` 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