diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/error.txt b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/error.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/expected.html
new file mode 100644
index 0000000000..63ea1759fe
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/expected.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Component content
+
+
+
+
+ Component content
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/index.js b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/index.js
new file mode 100644
index 0000000000..b202a15a0a
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/index.js
@@ -0,0 +1,4 @@
+export const tagName = 'x-dangling-container';
+export { default } from 'x/container';
+export * from 'x/container';
+export const features = [];
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/component/component.html b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/component/component.html
new file mode 100644
index 0000000000..17ad53e7e1
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/component/component.html
@@ -0,0 +1,3 @@
+
+ Component content
+
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/component/component.js b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/component/component.js
new file mode 100644
index 0000000000..0679d2bc10
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/component/component.js
@@ -0,0 +1,5 @@
+import { LightningElement } from 'lwc';
+
+export default class extends LightningElement {
+ static renderMode = 'light';
+}
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/container/container.html b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/container/container.html
new file mode 100644
index 0000000000..685193646f
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/container/container.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/container/container.js b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/container/container.js
new file mode 100644
index 0000000000..0679d2bc10
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/container/container.js
@@ -0,0 +1,5 @@
+import { LightningElement } from 'lwc';
+
+export default class extends LightningElement {
+ static renderMode = 'light';
+}
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/leaf/leaf.html b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/leaf/leaf.html
new file mode 100644
index 0000000000..dc9ac68111
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/leaf/leaf.html
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/leaf/leaf.js b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/leaf/leaf.js
new file mode 100644
index 0000000000..0679d2bc10
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/leaf/leaf.js
@@ -0,0 +1,5 @@
+import { LightningElement } from 'lwc';
+
+export default class extends LightningElement {
+ static renderMode = 'light';
+}
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/slot/slot.html b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/slot/slot.html
new file mode 100644
index 0000000000..963a3fceca
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/slot/slot.html
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/slot/slot.js b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/slot/slot.js
new file mode 100644
index 0000000000..0679d2bc10
--- /dev/null
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling-component/modules/x/slot/slot.js
@@ -0,0 +1,5 @@
+import { LightningElement } from 'lwc';
+
+export default class extends LightningElement {
+ static renderMode = 'light';
+}
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/expected.html
index aa23e8774b..adfb2329a5 100644
--- a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/expected.html
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/expected.html
@@ -2,14 +2,14 @@
-
- bottom content
+
+ top content
-
- top content
-
+
+ bottom content
+
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/modules/x/container/container.html b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/modules/x/container/container.html
index d6153f7e58..616c5495b8 100644
--- a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/modules/x/container/container.html
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/modules/x/container/container.html
@@ -1,6 +1,6 @@
top content
- bottom content
+ bottom content
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/modules/x/leaf/leaf.html b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/modules/x/leaf/leaf.html
index 039365a7a7..dc9ac68111 100644
--- a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/modules/x/leaf/leaf.html
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/modules/x/leaf/leaf.html
@@ -1,4 +1,4 @@
-
-
+
+
\ No newline at end of file
diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/modules/x/slot/slot.html b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/modules/x/slot/slot.html
index e641874967..963a3fceca 100644
--- a/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/modules/x/slot/slot.html
+++ b/packages/@lwc/engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/modules/x/slot/slot.html
@@ -1,6 +1,6 @@
-
-
+
+
\ No newline at end of file
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 b2f8518054..4d02bf9f22 100644
--- a/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts
+++ b/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts
@@ -13,8 +13,6 @@ export const expectedFailures = new Set([
'exports/component-as-default/index.js',
'known-boolean-attributes/default-def-html-attributes/static-on-component/index.js',
'render-dynamic-value/index.js',
- 'slot-forwarding/slots/mixed/index.js',
- 'slot-forwarding/slots/dangling/index.js',
'wire/errors/throws-on-computed-key/index.js',
'wire/errors/throws-when-colliding-prop-then-method/index.js',
]);
diff --git a/packages/@lwc/ssr-compiler/src/compile-template/index.ts b/packages/@lwc/ssr-compiler/src/compile-template/index.ts
index 037d51299f..041ef68f94 100644
--- a/packages/@lwc/ssr-compiler/src/compile-template/index.ts
+++ b/packages/@lwc/ssr-compiler/src/compile-template/index.ts
@@ -32,6 +32,9 @@ const bExportTemplate = esTemplate`
let textContentBuffer = '';
let didBufferTextContent = false;
+ // This will get overridden but requires initialization.
+ const slotAttributeValue = null;
+
// Establishes a contextual relationship between two components for ContextProviders.
// This variable will typically get overridden (shadowed) within slotted content.
const contextfulParent = instance;
diff --git a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts
index 305878fdd4..c02ffe8bf8 100644
--- a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts
+++ b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts
@@ -19,6 +19,13 @@ const bYieldFromChildGenerator = esTemplateWithYield`
{
const childProps = ${/* child props */ is.objectExpression};
const childAttrs = ${/* child attrs */ is.objectExpression};
+ /*
+ If 'slotAttributeValue' is set, it references a slot that does not exist, and the 'slot' attribute should be set in the DOM. This behavior aligns with engine-server and engine-dom.
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example case.
+ */
+ if (slotAttributeValue) {
+ childAttrs.slot = slotAttributeValue;
+ }
${
/*
Slotted content is inserted here.
diff --git a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/lwc-component.ts b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/lwc-component.ts
index b7076424c9..b6b7a518be 100644
--- a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/lwc-component.ts
+++ b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/lwc-component.ts
@@ -25,6 +25,13 @@ const bYieldFromDynamicComponentConstructorGenerator = esTemplateWithYield`
}
const childProps = ${/* child props */ is.objectExpression};
const childAttrs = ${/* child attrs */ is.objectExpression};
+ /*
+ If 'slotAttributeValue' is set, it references a slot that does not exist, and the 'slot' attribute should be set in the DOM. This behavior aligns with engine-server and engine-dom.
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example case.
+ */
+ if (slotAttributeValue) {
+ childAttrs.slot = slotAttributeValue;
+ }
${
/*
Slotted content is inserted here.
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 6fa1660203..8ad46d5d5e 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
@@ -78,7 +78,7 @@ const bGenerateSlottedContent = esTemplateWithYield`
const bAddSlottedContent = esTemplate`
addSlottedContent(${/* slot name */ is.expression} ?? "", async function* generateSlottedContent(contextfulParent, ${
/* scoped slot data variable */ isNullableOf(is.identifier)
- }) {
+ }, slotAttributeValue) {
// FIXME: make validation work again
${/* slot content */ false}
}, ${/* content map */ is.identifier});
diff --git a/packages/@lwc/ssr-compiler/src/compile-template/transformers/element.ts b/packages/@lwc/ssr-compiler/src/compile-template/transformers/element.ts
index 66341e2355..32c673fe7c 100644
--- a/packages/@lwc/ssr-compiler/src/compile-template/transformers/element.ts
+++ b/packages/@lwc/ssr-compiler/src/compile-template/transformers/element.ts
@@ -120,6 +120,16 @@ const bConditionallyYieldScopeTokenClass = esTemplateWithYield`
}
`;
+/*
+ If `slotAttributeValue` is set, it references a slot that does not exist, and the `slot` attribute should be set in the DOM. This behavior aligns with engine-server and engine-dom.
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example case.
+*/
+const bConditionallyYieldDanglingSlotName = esTemplateWithYield`
+ if (slotAttributeValue) {
+ yield \` slot="\${slotAttributeValue}"\`;
+ }
+`;
+
const bYieldSanitizedHtml = esTemplateWithYield`
yield sanitizeHtmlContent(${/* lwc:inner-html content */ is.expression})
`;
@@ -263,6 +273,7 @@ export const Element: Transformer = fu
return [
bYield(b.literal(`<${node.name}`)),
+ bConditionallyYieldDanglingSlotName(),
// If we haven't already prefixed the scope token to an existing class, add an explicit class here
...(hasClassAttribute ? [] : [bConditionallyYieldScopeTokenClass()]),
...yieldAttrsAndProps,
diff --git a/packages/@lwc/ssr-compiler/src/compile-template/transformers/slot.ts b/packages/@lwc/ssr-compiler/src/compile-template/transformers/slot.ts
index 3dcdea4adc..952619235d 100644
--- a/packages/@lwc/ssr-compiler/src/compile-template/transformers/slot.ts
+++ b/packages/@lwc/ssr-compiler/src/compile-template/transformers/slot.ts
@@ -30,7 +30,13 @@ const bConditionalSlot = esTemplateWithYield`
const scopedGenerators = scopedSlottedContent?.[slotName ?? ""];
const mismatchedSlots = isScopedSlot ? lightGenerators : scopedGenerators;
const generators = isScopedSlot ? scopedGenerators : lightGenerators;
-
+ /*
+ If a slotAttributeValue is present, it should be provided for assignment to any slotted content. This behavior aligns with v1 and engine-dom.
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/slots/dangling/ for example.
+ Note the slot mapping does not work for scoped slots, so the slot name is not rendered in this case.
+ See: engine-server/src/__tests__/fixtures/slot-forwarding/scoped-slots for example.
+ */
+ const danglingSlotName = !isScopedSlot ? ${/* slotAttributeValue */ is.expression} || slotAttributeValue : null;
// start bookend HTML comment for light DOM slot vfragment
if (!isSlotted) {
yield '';
@@ -43,7 +49,7 @@ const bConditionalSlot = esTemplateWithYield`
if (generators) {
for (let i = 0; i < generators.length; i++) {
- yield* generators[i](contextfulParent, ${/* scoped slot data */ isNullableOf(is.expression)});
+ yield* generators[i](contextfulParent, ${/* scoped slot data */ isNullableOf(is.expression)}, danglingSlotName);
// Scoped slotted data is separated by bookends. Final bookends are added outside of the loop below.
if (isScopedSlot && i < generators.length - 1) {
yield '';
@@ -90,5 +96,16 @@ export const Slot: Transformer = function Slot(node, ctx): EsStatement[]
const slotChildren = irChildrenToEs(node.children, ctx);
const isScopedSlot = b.literal(Boolean(slotBound));
const isSlotted = b.literal(Boolean(ctx.isSlotted));
- return [bConditionalSlot(isScopedSlot, isSlotted, slotName, slotBound, slotChildren, slotAst)];
+ const slotAttributeValue = bAttributeValue(node, 'slot');
+ return [
+ bConditionalSlot(
+ isScopedSlot,
+ isSlotted,
+ slotName,
+ slotAttributeValue,
+ slotBound,
+ slotChildren,
+ slotAst
+ ),
+ ];
};