44 * SPDX-License-Identifier: MIT
55 * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
66 */
7- import { esTemplateWithYield } from '../estemplate' ;
8- import type { IfStatement as EsIfStatement } from 'estree' ;
7+ import { builders as b } from 'estree-toolkit/dist/builders' ;
8+ import { is } from 'estree-toolkit' ;
9+ import { esTemplate , esTemplateWithYield } from '../estemplate' ;
10+ import { isLiteral } from './shared' ;
11+ import { expressionIrToEs } from './expression' ;
12+ import type {
13+ CallExpression as EsCallExpression ,
14+ Expression as EsExpression ,
15+ ExpressionStatement as EsExpressionStatement ,
16+ } from 'estree' ;
917import type { TransformerContext } from './types' ;
10- import type { Node as IrNode } from '@lwc/template-compiler' ;
18+ import type { Node as IrNode , Text as IrText , Comment as IrComment } from '@lwc/template-compiler' ;
19+
20+ const bNormalizeTextContent = esTemplate `
21+ normalizeTextContent(${ /* string value */ is . expression } );
22+ ` < EsCallExpression > ;
23+
24+ const bYieldTextContent = esTemplateWithYield `
25+ yield renderTextContent(${ /* text concatenation, possibly as binary expression */ is . expression } );
26+ ` < EsExpressionStatement > ;
1127
1228/**
1329 * True if this is one of a series of text content nodes and/or comment node that are adjacent to one another as
1430 * siblings. (Comment nodes are ignored when preserve-comments is turned off.) This allows for adjacent text
1531 * node concatenation.
1632 */
17- const isConcatenatedNode = ( node : IrNode , cxt : TransformerContext ) => {
33+ const isConcatenatedNode = ( node : IrNode , cxt : TransformerContext ) : node is IrText | IrComment => {
1834 switch ( node . type ) {
1935 case 'Text' :
2036 return true ;
@@ -26,23 +42,62 @@ const isConcatenatedNode = (node: IrNode, cxt: TransformerContext) => {
2642} ;
2743
2844export const isLastConcatenatedNode = ( cxt : TransformerContext ) => {
29- const { nextSibling } = cxt ;
45+ const siblings = cxt . siblings ! ;
46+ const currentNodeIndex = cxt . currentNodeIndex ! ;
47+
48+ const nextSibling = siblings [ currentNodeIndex + 1 ] ;
3049 if ( ! nextSibling ) {
3150 // we are the last sibling
3251 return true ;
3352 }
3453 return ! isConcatenatedNode ( nextSibling , cxt ) ;
3554} ;
3655
37- export const bYieldTextContent = esTemplateWithYield `
38- if (didBufferTextContent) {
39- // We are at the end of a series of text nodes - flush to a concatenated string
40- // We only render the ZWJ if there were actually any dynamic text nodes rendered
41- // The ZWJ is just so hydration can compare the SSR'd dynamic text content against
42- // the CSR'd text content.
43- yield textContentBuffer === '' ? '\u200D' : htmlEscape(textContentBuffer);
44- // Reset
45- textContentBuffer = '';
46- didBufferTextContent = false;
56+ function generateExpressionFromTextNode ( node : IrText , cxt : TransformerContext ) {
57+ return isLiteral ( node . value ) ? b . literal ( node . value . value ) : expressionIrToEs ( node . value , cxt ) ;
58+ }
59+
60+ export function generateConcatenatedTextNodesExpressions ( cxt : TransformerContext ) {
61+ const siblings = cxt . siblings ! ;
62+ const currentNodeIndex = cxt . currentNodeIndex ! ;
63+
64+ const textNodes = [ ] ;
65+
66+ for ( let i = currentNodeIndex ; i >= 0 ; i -- ) {
67+ const sibling = siblings [ i ] ;
68+ if ( isConcatenatedNode ( sibling , cxt ) ) {
69+ if ( sibling . type === 'Text' ) {
70+ textNodes . unshift ( sibling ) ;
71+ }
72+ } else {
73+ // If we reach a non-Text/Comment node, we are done. These should not be concatenated
74+ // with sibling Text nodes separated by e.g. an Element:
75+ // {a}{b}<div></div>{c}{d}
76+ // In the above, {a} and {b} are concatenated, and {c} and {d} are concatenated,
77+ // but the `<div>` separates the two groups.
78+ break ;
79+ }
4780 }
48- ` < EsIfStatement > ;
81+
82+ if ( ! textNodes . length ) {
83+ // Render nothing. This can occur if we hit a comment in non-preserveComments mode with no adjacent text nodes
84+ return [ ] ;
85+ }
86+
87+ cxt . import ( [ 'normalizeTextContent' , 'renderTextContent' ] ) ;
88+
89+ // Generate a binary expression to concatenate the text together. E.g.:
90+ // renderTextContent(
91+ // normalizeTextContent(a) +
92+ // normalizeTextContent(b) +
93+ // normalizeTextContent(c)
94+ // )
95+ const concatenatedExpression = textNodes
96+ . map (
97+ ( node ) =>
98+ bNormalizeTextContent ( generateExpressionFromTextNode ( node , cxt ) ) as EsExpression
99+ )
100+ . reduce ( ( accumulator , expression ) => b . binaryExpression ( '+' , accumulator , expression ) ) ;
101+
102+ return [ bYieldTextContent ( concatenatedExpression ) ] ;
103+ }
0 commit comments