From d792db8652db338d67cf47c6d32ce4ef3a32707e Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 2 Aug 2023 12:38:02 -0700 Subject: [PATCH] refactor(compiler): Adds a new phase for parsing extracted styles (#51258) Adds a new phase that converts previously extracted ExtractedAttributeOps representing a style attribute into individual ExtractedAttributeOps representing each of the style properties set in the style attribute. PR Close #51258 --- .../template/pipeline/ir/src/expression.ts | 7 ++++ .../src/template/pipeline/src/emit.ts | 2 + .../src/phases/attribute_extraction.ts | 12 +----- .../src/phases/parse_extracted_styles.ts | 37 +++++++++++++++++++ 4 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 packages/compiler/src/template/pipeline/src/phases/parse_extracted_styles.ts diff --git a/packages/compiler/src/template/pipeline/ir/src/expression.ts b/packages/compiler/src/template/pipeline/ir/src/expression.ts index 4090887f861..d32a4c33a02 100644 --- a/packages/compiler/src/template/pipeline/ir/src/expression.ts +++ b/packages/compiler/src/template/pipeline/ir/src/expression.ts @@ -902,3 +902,10 @@ export function transformExpressionsInStatement( throw new Error(`Unhandled statement kind: ${stmt.constructor.name}`); } } + +/** + * Checks whether the given expression is a string literal. + */ +export function isStringLiteral(expr: o.Expression): expr is o.LiteralExpr&{value: string} { + return expr instanceof o.LiteralExpr && typeof expr.value === 'string'; +} diff --git a/packages/compiler/src/template/pipeline/src/emit.ts b/packages/compiler/src/template/pipeline/src/emit.ts index 14c12eaf820..8ec7cd77ed4 100644 --- a/packages/compiler/src/template/pipeline/src/emit.ts +++ b/packages/compiler/src/template/pipeline/src/emit.ts @@ -31,6 +31,7 @@ import {phaseNgContainer} from './phases/ng_container'; import {phaseNoListenersOnTemplates} from './phases/no_listeners_on_templates'; import {phaseNonbindable} from './phases/nonbindable'; import {phaseNullishCoalescing} from './phases/nullish_coalescing'; +import {phaseParseExtractedStyles} from './phases/parse_extracted_styles'; import {phasePipeCreation} from './phases/pipe_creation'; import {phasePipeVariadic} from './phases/pipe_variadic'; import {phasePropertyOrdering} from './phases/property_ordering'; @@ -58,6 +59,7 @@ export function transformTemplate(job: ComponentCompilationJob): void { phaseStyleBindingSpecialization(job); phaseBindingSpecialization(job); phaseAttributeExtraction(job); + phaseParseExtractedStyles(job); phaseRemoveEmptyBindings(job); phaseNoListenersOnTemplates(job); phasePipeCreation(job); diff --git a/packages/compiler/src/template/pipeline/src/phases/attribute_extraction.ts b/packages/compiler/src/template/pipeline/src/phases/attribute_extraction.ts index 919ead40364..6b455586b55 100644 --- a/packages/compiler/src/template/pipeline/src/phases/attribute_extraction.ts +++ b/packages/compiler/src/template/pipeline/src/phases/attribute_extraction.ts @@ -8,7 +8,6 @@ import {SecurityContext} from '../../../../core'; -import * as o from '../../../../output/output_ast'; import * as ir from '../../ir'; import {ComponentCompilationJob, ViewCompilationUnit} from '../compilation'; import {getElementsByXrefId} from '../util/elements'; @@ -70,13 +69,6 @@ function lookupElement( return el; } -/** - * Checks whether the given expression is a string literal. - */ -function isStringLiteral(expr: o.Expression): expr is o.LiteralExpr&{value: string} { - return expr instanceof o.LiteralExpr && typeof expr.value === 'string'; -} - /** * Extracts an attribute binding. */ @@ -94,13 +86,13 @@ function extractAttributeOp( // context, so we emulate that behavior in compaitiblity mode, otherwise we optimize more // aggressively. extractable = view.compatibility === ir.CompatibilityMode.TemplateDefinitionBuilder ? - isStringLiteral(op.expression) && op.securityContext === SecurityContext.NONE : + ir.isStringLiteral(op.expression) && op.securityContext === SecurityContext.NONE : op.expression.isConstant(); } else { // TemplateDefinitionBuilder only extracted string constants, so we emulate that behavior in // compaitiblity mode, otherwise we optimize more aggressively. extractable = view.compatibility === ir.CompatibilityMode.TemplateDefinitionBuilder ? - isStringLiteral(op.expression) : + ir.isStringLiteral(op.expression) : op.expression.isConstant(); } diff --git a/packages/compiler/src/template/pipeline/src/phases/parse_extracted_styles.ts b/packages/compiler/src/template/pipeline/src/phases/parse_extracted_styles.ts new file mode 100644 index 00000000000..8542adc1606 --- /dev/null +++ b/packages/compiler/src/template/pipeline/src/phases/parse_extracted_styles.ts @@ -0,0 +1,37 @@ +/** + * @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 * as o from '../../../../output/output_ast'; +import {parse as parseStyle} from '../../../../render3/view/style_parser'; +import * as ir from '../../ir'; +import {ComponentCompilationJob} from '../compilation'; + +/** + * Parses extracted style attributes into separate ExtractedAttributeOps per style property. + */ +export function phaseParseExtractedStyles(cpl: ComponentCompilationJob) { + for (const [_, view] of cpl.views) { + for (const op of view.create) { + if (op.kind === ir.OpKind.ExtractedAttribute && op.bindingKind === ir.BindingKind.Attribute) { + if (op.name === 'style') { + if (ir.isStringLiteral(op.expression!)) { + const parsedStyles = parseStyle(op.expression.value); + for (let i = 0; i < parsedStyles.length - 1; i += 2) { + ir.OpList.insertBefore( + ir.createExtractedAttributeOp( + op.target, ir.BindingKind.StyleProperty, parsedStyles[i], + o.literal(parsedStyles[i + 1])), + op); + } + ir.OpList.remove(op); + } + } + } + } + } +}