From 7912257bba6c3024bee0ed5e944f5691374add5c Mon Sep 17 00:00:00 2001 From: Andrew Jakubowicz Date: Tue, 1 Aug 2023 14:07:04 -0700 Subject: [PATCH 1/2] add raw text element handling to the compiler --- .../compiler/src/lib/template-transform.ts | 34 +++++++++++++++++-- .../test_files/raw_text_element.golden.js | 32 +++++++++++++++++ .../compiler/test_files/raw_text_element.js | 33 ++++++++++++++++++ 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 packages/labs/compiler/test_files/raw_text_element.golden.js create mode 100644 packages/labs/compiler/test_files/raw_text_element.js diff --git a/packages/labs/compiler/src/lib/template-transform.ts b/packages/labs/compiler/src/lib/template-transform.ts index 431271690c..8dc374c4ea 100644 --- a/packages/labs/compiler/src/lib/template-transform.ts +++ b/packages/labs/compiler/src/lib/template-transform.ts @@ -22,6 +22,7 @@ import { isElementNode, isTextNode, traverse, + getTextContent, } from '@parse5/tools'; import {getTypeChecker} from './type-checker.js'; const {getTemplateHtml, markerMatch, marker, boundAttributeSuffix} = @@ -64,6 +65,14 @@ interface TemplateInfo { variableName: ts.Identifier; } +/** + * Matches the raw text elements. + * + * Comments are not parsed within raw text elements, so we need to search their + * text content for marker strings. + */ +const rawTextElement = /^(?:script|style|textarea|title)$/i; + /** * CompiledTemplatePass provides a `ts.TransformerFactory` that transforms * `html` tagged templates into a `CompiledTemplateResult`. @@ -120,7 +129,8 @@ class CompiledTemplatePass { >(); /** * Map an `html` tagged template expression to the template info for each - * template to compile. + * template to compile. If a template is not in this map, it will not be + * rewritten into a `CompiledTemplateResult`. */ private readonly expressionToTemplate = new Map< ts.TaggedTemplateExpression, @@ -268,8 +278,9 @@ class CompiledTemplatePass { // Start at -1 to account for an extra root document fragment. let nodeIndex = -1; let attrNameIndex = 0; + let shouldCompile = true; traverse(ast, { - 'pre:node': (node) => { + 'pre:node': (node): boolean | void => { if (isElementNode(node)) { const attributesToRemove = new Set(); if (node.attrs.length > 0) { @@ -314,6 +325,18 @@ class CompiledTemplatePass { (attr) => !attributesToRemove.has(attr) ); } + if (rawTextElement.test(node.tagName)) { + // Raw text elements treat their contents as raw text. E.g., + // `` will render a textarea element + // with the comment as visible contents. Currently the compiled + // template runtime doesn't handle raw text elements with bindings, + // so we do not optimize these templates. + const hasMarkers = getTextContent(node).includes(marker); + if (hasMarkers) { + shouldCompile = false; + return false; + } + } } else if (isCommentNode(node)) { if (node.data === markerMatch) { parts.push({ @@ -329,6 +352,10 @@ class CompiledTemplatePass { }, }); + if (!shouldCompile) { + return {shouldCompile: false}; + } + // Add required unique ctor identifiers for parts added. const f = this.context.factory; for (const part of parts) { @@ -401,7 +428,8 @@ class CompiledTemplatePass { )!) { const result = this.litHtmlPrepareRenderPhase(template.node.template); if (!result.shouldCompile) { - throw new Error(`Unhandled TODO: Will be implemented in followup.`); + this.expressionToTemplate.delete(template.node); + continue; } const {parts: partData, preparedHtml} = result; const parts = createTemplateParts({ diff --git a/packages/labs/compiler/test_files/raw_text_element.golden.js b/packages/labs/compiler/test_files/raw_text_element.golden.js new file mode 100644 index 0000000000..d44a4ccb71 --- /dev/null +++ b/packages/labs/compiler/test_files/raw_text_element.golden.js @@ -0,0 +1,32 @@ +import { html } from 'lit-html'; +const b_1 = i => i; +const lit_template_1 = { h: b_1 ``, parts: [] }; +// TODO: In the future we can figure out a way to correctly compile raw text +// elements. The complexity is that they depend on creating adjacent Text nodes +// which cannot be represented in the prepared HTML. Thus a binding in a raw +// text node results in the template not being optimized. +const scriptTemplateNoBinding = { ["_$litType$"]: lit_template_1, values: [] }; +const lit_template_2 = { h: b_1 ``, parts: [] }; +const styleTemplateNoBinding = { ["_$litType$"]: lit_template_2, values: [] }; +const lit_template_3 = { h: b_1 ``, parts: [] }; +const textareaTemplateNoBinding = { ["_$litType$"]: lit_template_3, values: [] }; +const lit_template_4 = { h: b_1 `Codestin Search App`, parts: [] }; +const titleTemplateNoBinding = { ["_$litType$"]: lit_template_4, values: [] }; +const scriptTemplateOneBinding = html ``; +const styleTemplateOneBinding = html ``; +const textareaTemplateOneBinding = html ``; +const titleTemplateOneBinding = html `Codestin Search App`; +const scriptTemplateShouldNotBeCompiled = html ``; +const styleTemplateShouldNotBeCompiled = html ``; +const textareaTemplateShouldNotBeCompiled = html ``; +const titleTemplateShouldNotBeCompiled = html `Codestin Search App`; diff --git a/packages/labs/compiler/test_files/raw_text_element.js b/packages/labs/compiler/test_files/raw_text_element.js new file mode 100644 index 0000000000..013522fced --- /dev/null +++ b/packages/labs/compiler/test_files/raw_text_element.js @@ -0,0 +1,33 @@ +import {html} from 'lit-html'; +// TODO: In the future we can figure out a way to correctly compile raw text +// elements. The complexity is that they depend on creating adjacent Text nodes +// which cannot be represented in the prepared HTML. Thus a binding in a raw +// text node results in the template not being optimized. +const scriptTemplateNoBinding = html``; +const styleTemplateNoBinding = html``; +const textareaTemplateNoBinding = html``; +const titleTemplateNoBinding = html`Codestin Search App`; + +const scriptTemplateOneBinding = html``; +const styleTemplateOneBinding = html``; +const textareaTemplateOneBinding = html``; +const titleTemplateOneBinding = html`Codestin Search App`; + +const scriptTemplateShouldNotBeCompiled = html``; +const styleTemplateShouldNotBeCompiled = html``; +const textareaTemplateShouldNotBeCompiled = html``; +const titleTemplateShouldNotBeCompiled = html`Codestin Search App`; From 8f1d6f9d4aed8e201f8d99f8ffb3a6f4e4eebf5f Mon Sep 17 00:00:00 2001 From: Andrew Jakubowicz Date: Tue, 1 Aug 2023 14:08:01 -0700 Subject: [PATCH 2/2] add changeset --- .changeset/stupid-knives-punch.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changeset/stupid-knives-punch.md diff --git a/.changeset/stupid-knives-punch.md b/.changeset/stupid-knives-punch.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/stupid-knives-punch.md @@ -0,0 +1,2 @@ +--- +---