diff --git a/packages/compiler/BUILD.bazel b/packages/compiler/BUILD.bazel index 7ae31dcb847..c732c8dd5ca 100644 --- a/packages/compiler/BUILD.bazel +++ b/packages/compiler/BUILD.bazel @@ -1,3 +1,4 @@ +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") load("//tools:defaults.bzl", "ng_package", "ts_library", "tsec_test") package(default_visibility = ["//visibility:public"]) @@ -10,6 +11,9 @@ ts_library( "src/**/*.ts", ], ), + deps = [ + "//packages/compiler/src/template/pipeline/switch", + ], ) tsec_test( @@ -49,3 +53,19 @@ filegroup( "src/**/*.ts", ]), ) + +# Pass this flag on the commandline during a build to enable the prototype +# template pipeline: +# +# yarn bazel build //some:target --//packages/compiler:use_template_pipeline +bool_flag( + name = "use_template_pipeline", + build_setting_default = False, +) + +config_setting( + name = "template_pipeline_enabled", + flag_values = { + ":use_template_pipeline": "true", + }, +) diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 44d49708966..daaa0660633 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -15,6 +15,9 @@ import {ParseError, ParseSourceSpan, sanitizeIdentifier} from '../../parse_util' import {isIframeSecuritySensitiveAttr} from '../../schema/dom_security_schema'; import {CssSelector} from '../../selector'; import {ShadowCss} from '../../shadow_css'; +import {emitTemplateFn, transformTemplate} from '../../template/pipeline/src/emit'; +import {ingest} from '../../template/pipeline/src/ingest'; +import {USE_TEMPLATE_PIPELINE} from '../../template/pipeline/switch'; import {BindingParser} from '../../template_parser/binding_parser'; import {error} from '../../util'; import {BoundEvent} from '../r3_ast'; @@ -173,42 +176,67 @@ export function compileComponentFromMetadata( const changeDetection = meta.changeDetection; - const template = meta.template; - const templateBuilder = new TemplateDefinitionBuilder( - constantPool, BindingScope.createRootScope(), 0, templateTypeName, null, null, templateName, - R3.namespaceHTML, meta.relativeContextFilePath, meta.i18nUseExternalIds); + // Template compilation is currently conditional as we're in the process of rewriting it. + if (!USE_TEMPLATE_PIPELINE) { + // This is the main path currently used in compilation, which compiles the template with the + // legacy `TemplateDefinitionBuilder`. - const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []); + const template = meta.template; + const templateBuilder = new TemplateDefinitionBuilder( + constantPool, BindingScope.createRootScope(), 0, templateTypeName, null, null, templateName, + R3.namespaceHTML, meta.relativeContextFilePath, meta.i18nUseExternalIds); - // We need to provide this so that dynamically generated components know what - // projected content blocks to pass through to the component when it is instantiated. - const ngContentSelectors = templateBuilder.getNgContentSelectors(); - if (ngContentSelectors) { - definitionMap.set('ngContentSelectors', ngContentSelectors); - } + const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []); - // e.g. `decls: 2` - definitionMap.set('decls', o.literal(templateBuilder.getConstCount())); - - // e.g. `vars: 2` - definitionMap.set('vars', o.literal(templateBuilder.getVarCount())); - - // Generate `consts` section of ComponentDef: - // - either as an array: - // `consts: [['one', 'two'], ['three', 'four']]` - // - or as a factory function in case additional statements are present (to support i18n): - // `consts: function() { var i18n_0; if (ngI18nClosureMode) {...} else {...} return [i18n_0]; }` - const {constExpressions, prepareStatements} = templateBuilder.getConsts(); - if (constExpressions.length > 0) { - let constsExpr: o.LiteralArrayExpr|o.FunctionExpr = o.literalArr(constExpressions); - // Prepare statements are present - turn `consts` into a function. - if (prepareStatements.length > 0) { - constsExpr = o.fn([], [...prepareStatements, new o.ReturnStatement(constsExpr)]); + // We need to provide this so that dynamically generated components know what + // projected content blocks to pass through to the component when it is + // instantiated. + const ngContentSelectors = templateBuilder.getNgContentSelectors(); + if (ngContentSelectors) { + definitionMap.set('ngContentSelectors', ngContentSelectors); } - definitionMap.set('consts', constsExpr); - } - definitionMap.set('template', templateFunctionExpression); + // e.g. `decls: 2` + // definitionMap.set('decls', o.literal(tpl.root.decls!)); + definitionMap.set('decls', o.literal(templateBuilder.getConstCount())); + + // e.g. `vars: 2` + // definitionMap.set('vars', o.literal(tpl.root.vars!)); + definitionMap.set('vars', o.literal(templateBuilder.getVarCount())); + + // Generate `consts` section of ComponentDef: + // - either as an array: + // `consts: [['one', 'two'], ['three', 'four']]` + // - or as a factory function in case additional statements are present (to support i18n): + // `consts: function() { var i18n_0; if (ngI18nClosureMode) {...} else {...} return [i18n_0]; + // }` + const {constExpressions, prepareStatements} = templateBuilder.getConsts(); + if (constExpressions.length > 0) { + let constsExpr: o.LiteralArrayExpr|o.FunctionExpr = o.literalArr(constExpressions); + // Prepare statements are present - turn `consts` into a function. + if (prepareStatements.length > 0) { + constsExpr = o.fn([], [...prepareStatements, new o.ReturnStatement(constsExpr)]); + } + definitionMap.set('consts', constsExpr); + } + + definitionMap.set('template', templateFunctionExpression); + } else { + // This path compiles the template using the prototype template pipeline. First the template is + // ingested into IR: + const tpl = ingest(meta.name, meta.template.nodes); + + // Then the IR is transformed to prepare it for cod egeneration. + transformTemplate(tpl); + + // Finally we emit the template function: + const templateFn = emitTemplateFn(tpl, constantPool); + + definitionMap.set('template', templateFn); + definitionMap.set('decls', o.literal(tpl.root.decls as number)); + definitionMap.set('vars', o.literal(tpl.root.vars as number)); + definitionMap.set('consts', o.literalArr(tpl.consts)); + } if (meta.declarations.length > 0) { definitionMap.set( diff --git a/packages/compiler/src/template/pipeline/ir/src/expression.ts b/packages/compiler/src/template/pipeline/ir/src/expression.ts index e1d441dc958..64db65d8497 100644 --- a/packages/compiler/src/template/pipeline/ir/src/expression.ts +++ b/packages/compiler/src/template/pipeline/ir/src/expression.ts @@ -56,7 +56,7 @@ export abstract class ExpressionBase extends o.Expression { * Logical expression representing a lexical read of a variable name. */ export class LexicalReadExpr extends ExpressionBase { - readonly kind = ExpressionKind.LexicalRead; + override readonly kind = ExpressionKind.LexicalRead; constructor(readonly name: string) { super(); @@ -79,7 +79,7 @@ export class LexicalReadExpr extends ExpressionBase { * Runtime operation to retrieve the value of a local reference. */ export class ReferenceExpr extends ExpressionBase implements UsesSlotIndexExprTrait { - readonly kind = ExpressionKind.Reference; + override readonly kind = ExpressionKind.Reference; readonly[UsesSlotIndex] = true; @@ -106,7 +106,7 @@ export class ReferenceExpr extends ExpressionBase implements UsesSlotIndexExprTr * A reference to the current view context (usually the `ctx` variable in a template function). */ export class ContextExpr extends ExpressionBase { - readonly kind = ExpressionKind.Context; + override readonly kind = ExpressionKind.Context; constructor(readonly view: XrefId) { super(); @@ -129,7 +129,7 @@ export class ContextExpr extends ExpressionBase { * Runtime operation to navigate to the next view context in the view hierarchy. */ export class NextContextExpr extends ExpressionBase { - readonly kind = ExpressionKind.NextContext; + override readonly kind = ExpressionKind.NextContext; constructor() { super(); @@ -155,7 +155,7 @@ export class NextContextExpr extends ExpressionBase { * operation. */ export class GetCurrentViewExpr extends ExpressionBase { - readonly kind = ExpressionKind.GetCurrentView; + override readonly kind = ExpressionKind.GetCurrentView; constructor() { super(); @@ -178,7 +178,7 @@ export class GetCurrentViewExpr extends ExpressionBase { * Runtime operation to restore a snapshotted view. */ export class RestoreViewExpr extends ExpressionBase { - readonly kind = ExpressionKind.RestoreView; + override readonly kind = ExpressionKind.RestoreView; constructor(public view: XrefId|o.Expression) { super(); @@ -217,7 +217,7 @@ export class RestoreViewExpr extends ExpressionBase { * Runtime operation to reset the current view context after `RestoreView`. */ export class ResetViewExpr extends ExpressionBase { - readonly kind = ExpressionKind.ResetView; + override readonly kind = ExpressionKind.ResetView; constructor(public expr: o.Expression) { super(); @@ -244,19 +244,19 @@ export class ResetViewExpr extends ExpressionBase { * Read of a variable declared as an `ir.VariableOp` and referenced through its `ir.XrefId`. */ export class ReadVariableExpr extends ExpressionBase { - readonly kind = ExpressionKind.ReadVariable; + override readonly kind = ExpressionKind.ReadVariable; name: string|null = null; constructor(readonly xref: XrefId) { super(); } - visitExpression(): void {} + override visitExpression(): void {} - isEquivalent(other: o.Expression): boolean { + override isEquivalent(other: o.Expression): boolean { return other instanceof ReadVariableExpr && other.xref === this.xref; } - isConstant(): boolean { + override isConstant(): boolean { return false; } diff --git a/packages/compiler/src/template/pipeline/switch/BUILD.bazel b/packages/compiler/src/template/pipeline/switch/BUILD.bazel new file mode 100644 index 00000000000..4b8a82ea88d --- /dev/null +++ b/packages/compiler/src/template/pipeline/switch/BUILD.bazel @@ -0,0 +1,21 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +# Generate contents for `index.ts` depending on the value of the template +# pipeline build flag. +genrule( + name = "gen_index_ts", + outs = ["index.ts"], + cmd = "echo 'export const USE_TEMPLATE_PIPELINE = " + select({ + "//packages/compiler:template_pipeline_enabled": "true", + "//conditions:default": "false", + }) + ";' > $@", +) + +ts_library( + name = "switch", + srcs = [ + ":gen_index_ts", + ], +) diff --git a/packages/compiler/src/template/pipeline/switch/README.md b/packages/compiler/src/template/pipeline/switch/README.md new file mode 100644 index 00000000000..90b227b5c33 --- /dev/null +++ b/packages/compiler/src/template/pipeline/switch/README.md @@ -0,0 +1,7 @@ +The small `ts_library` defined in this directory defines a constant which determines whether the template pipeline should be used. This constant depends on the defined Bazel config flag `//packages/compiler:use_template_pipeline`. In other words: + +``` +yarn bazel build //some:target --//packages/compiler:use_template_pipeline +``` + +will enable the prototype template pipeline for this build. diff --git a/packages/compiler/src/template/pipeline/switch/index.ts b/packages/compiler/src/template/pipeline/switch/index.ts new file mode 100644 index 00000000000..fe5f2c7e531 --- /dev/null +++ b/packages/compiler/src/template/pipeline/switch/index.ts @@ -0,0 +1,15 @@ +/** + * @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 + */ + +// During 3P builds, this file is replaced with a `genrule()` that conditionally sets the +// `USE_TEMPLATE_PIPELINE` constant instead. In 1P builds, this file is read directly. + +/** + * Whether the prototype template pipeline should be enabled. + */ +export const USE_TEMPLATE_PIPELINE: boolean = false;