From bcd22dea131621671f0fb04d3b8d6d098efdd062 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Wed, 18 Dec 2024 13:29:40 -0300 Subject: [PATCH] fix(ssr): missing bookends for slotted lwc:if not at the top-level (#5027) Co-authored-by: Nolan Lawson --- .../src/__tests__/utils/expected-failures.ts | 3 --- .../src/compile-template/ir-to-es.ts | 22 ++++++++++++++----- .../transformers/component/slotted-content.ts | 22 ++++++++++++++++++- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts b/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts index 37cc7e5ce8..25d32f3cd7 100644 --- a/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts +++ b/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts @@ -25,10 +25,7 @@ export const expectedFailures = new Set([ 'scoped-slots/mixed-with-light-dom-slots-outside/index.js', 'slot-forwarding/slots/mixed/index.js', 'slot-forwarding/slots/dangling/index.js', - 'slot-not-at-top-level/advanced/lwcIf/light/index.js', - 'slot-not-at-top-level/advanced/lwcIf/shadow/index.js', 'slot-not-at-top-level/lwcIf-with-adjacent-text-nodes/light/index.js', - 'slot-not-at-top-level/nested-elements/lwcIf/shadow/index.js', 'superclass/render-in-superclass/no-template-in-subclass/index.js', 'superclass/render-in-superclass/unused-default-in-subclass/index.js', 'superclass/render-in-superclass/unused-default-in-superclass/index.js', diff --git a/packages/@lwc/ssr-compiler/src/compile-template/ir-to-es.ts b/packages/@lwc/ssr-compiler/src/compile-template/ir-to-es.ts index 2fd1c7c04a..a938922933 100644 --- a/packages/@lwc/ssr-compiler/src/compile-template/ir-to-es.ts +++ b/packages/@lwc/ssr-compiler/src/compile-template/ir-to-es.ts @@ -66,14 +66,24 @@ const transformers: Transformers = { Lwc: LwcComponent, }; -export function irChildrenToEs(children: IrChildNode[], cxt: TransformerContext): EsStatement[] { - const result = children.flatMap((child, idx) => { - cxt.prevSibling = children[idx - 1]; - cxt.nextSibling = children[idx + 1]; - return irToEs(child, cxt); - }); +export function irChildrenToEs( + children: IrChildNode[], + cxt: TransformerContext, + cb?: (child: IrChildNode) => (() => void) | void +): EsStatement[] { + const result: EsStatement[] = []; + + for (let i = 0; i < children.length; i++) { + cxt.prevSibling = children[i - 1]; + cxt.nextSibling = children[i + 1]; + const cleanUp = cb?.(children[i]); + result.push(...irToEs(children[i], cxt)); + cleanUp?.(); + } + cxt.prevSibling = undefined; cxt.nextSibling = undefined; + return result; } diff --git a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/slotted-content.ts b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/slotted-content.ts index f39c3da3fe..62a47db98d 100644 --- a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/slotted-content.ts +++ b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/slotted-content.ts @@ -77,6 +77,23 @@ const bAddLightContent = esTemplate` }); `; +function getShadowSlottedContent(slottableChildren: IrChildNode[], cxt: TransformerContext) { + return optimizeAdjacentYieldStmts( + irChildrenToEs(slottableChildren, cxt, (child) => { + const { isSlotted } = cxt; + + if (child.type === 'ExternalComponent' || child.type === 'Element') { + cxt.isSlotted = false; + } + + // cleanup function + return () => { + cxt.isSlotted = isSlotted; + }; + }) + ); +} + // Light DOM slots are a bit complex because of needing to handle slots _not_ at the top level // At the non-top level, it matters what the ancestors are. These are relevant to slots: // - If (`if:true`, `if:false`) @@ -130,7 +147,10 @@ function getLightSlottedContent(rootNodes: IrChildNode[], cxt: TransformerContex leaf.attributes = leaf.attributes.filter((attr) => attr.name !== 'slot'); } }); + const { isSlotted: originalIsSlotted } = cxt; + cxt.isSlotted = ancestorIndices.length > 1 || clone.type === 'Slot'; const slotContent = irToEs(clone, cxt); + cxt.isSlotted = originalIsSlotted; results.push(b.expressionStatement(bAddLightContent(slotName, null, slotContent))); }; @@ -180,7 +200,7 @@ export function getSlottedContent( (child) => child.type === 'ScopedSlotFragment' ) as IrScopedSlotFragment[]; - const shadowSlotContent = optimizeAdjacentYieldStmts(irChildrenToEs(slottableChildren, cxt)); + const shadowSlotContent = getShadowSlottedContent(slottableChildren, cxt); const lightSlotContent = getLightSlottedContent(slottableChildren, cxt);