Skip to content

Commit 39e2560

Browse files
committed
better code generation for slot props in SSR
1 parent beea5c3 commit 39e2560

File tree

5 files changed

+60
-32
lines changed

5 files changed

+60
-32
lines changed

packages/svelte/src/compiler/phases/3-transform/server/visitors/LetDirective.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import * as b from '../../../../utils/builders.js';
77
* @param {ComponentContext} context
88
*/
99
export function LetDirective(node, context) {
10+
return b.empty;
11+
1012
if (node.expression === null || node.expression.type === 'Identifier') {
1113
const name = node.expression === null ? node.name : node.expression.name;
1214
return b.const(name, b.member(b.id('$$slotProps'), b.id(node.name)));

packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
/** @import { BlockStatement, Expression, ExpressionStatement, Property, Statement } from 'estree' */
2-
/** @import { Attribute, Component, SvelteComponent, SvelteSelf, TemplateNode, Text } from '#compiler' */
1+
/** @import { BlockStatement, Expression, Pattern, Property, Statement } from 'estree' */
2+
/** @import { Attribute, Component, LetDirective, SvelteComponent, SvelteSelf, TemplateNode, Text } from '#compiler' */
33
/** @import { ComponentContext } from '../../types.js' */
44
import { empty_comment, serialize_attribute_value } from './utils.js';
55
import * as b from '../../../../../utils/builders.js';
@@ -17,8 +17,8 @@ export function serialize_inline_component(node, expression, context) {
1717
/** @type {Property[]} */
1818
const custom_css_props = [];
1919

20-
/** @type {ExpressionStatement[]} */
21-
const lets = [];
20+
/** @type {Record<string, LetDirective[]>} */
21+
const lets = { default: [] };
2222

2323
/** @type {Record<string, TemplateNode[]>} */
2424
const children = {};
@@ -27,7 +27,9 @@ export function serialize_inline_component(node, expression, context) {
2727
* If this component has a slot property, it is a named slot within another component. In this case
2828
* the slot scope applies to the component itself, too, and not just its children.
2929
*/
30-
let slot_scope_applies_to_itself = false;
30+
const slot_scope_applies_to_itself = node.attributes.some(
31+
(node) => node.type === 'Attribute' && node.name === 'slot'
32+
);
3133

3234
/**
3335
* Components may have a children prop and also have child nodes. In this case, we assume
@@ -50,7 +52,9 @@ export function serialize_inline_component(node, expression, context) {
5052
}
5153
for (const attribute of node.attributes) {
5254
if (attribute.type === 'LetDirective') {
53-
lets.push(/** @type {ExpressionStatement} */ (context.visit(attribute)));
55+
if (!slot_scope_applies_to_itself) {
56+
lets.default.push(attribute);
57+
}
5458
} else if (attribute.type === 'SpreadAttribute') {
5559
props_and_spreads.push(/** @type {Expression} */ (context.visit(attribute)));
5660
} else if (attribute.type === 'Attribute') {
@@ -60,10 +64,6 @@ export function serialize_inline_component(node, expression, context) {
6064
continue;
6165
}
6266

63-
if (attribute.name === 'slot') {
64-
slot_scope_applies_to_itself = true;
65-
}
66-
6767
if (attribute.name === 'children') {
6868
has_children_prop = true;
6969
}
@@ -90,10 +90,6 @@ export function serialize_inline_component(node, expression, context) {
9090
}
9191
}
9292

93-
if (slot_scope_applies_to_itself) {
94-
context.state.init.push(...lets);
95-
}
96-
9793
/** @type {Statement[]} */
9894
const snippet_declarations = [];
9995

@@ -115,13 +111,20 @@ export function serialize_inline_component(node, expression, context) {
115111

116112
let slot_name = 'default';
117113
if (is_element_node(child)) {
118-
const attribute = /** @type {Attribute | undefined} */ (
114+
const slot = /** @type {Attribute | undefined} */ (
119115
child.attributes.find(
120116
(attribute) => attribute.type === 'Attribute' && attribute.name === 'slot'
121117
)
122118
);
123-
if (attribute !== undefined) {
124-
slot_name = /** @type {Text[]} */ (attribute.value)[0].data;
119+
120+
if (slot !== undefined) {
121+
slot_name = /** @type {Text[]} */ (slot.value)[0].data;
122+
123+
lets[slot_name] = child.attributes.filter((attribute) => attribute.type === 'LetDirective');
124+
} else if (child.type === 'SvelteFragment') {
125+
lets.default.push(
126+
...child.attributes.filter((attribute) => attribute.type === 'LetDirective')
127+
);
125128
}
126129
}
127130

@@ -152,16 +155,40 @@ export function serialize_inline_component(node, expression, context) {
152155

153156
if (block.body.length === 0) continue;
154157

155-
const slot_fn = b.arrow(
156-
[b.id('$$payload'), b.id('$$slotProps')],
157-
b.block([
158-
...(slot_name === 'default' && !slot_scope_applies_to_itself ? lets : []),
159-
...block.body
160-
])
161-
);
158+
/** @type {Pattern[]} */
159+
const params = [b.id('$$payload')];
160+
161+
if (lets[slot_name].length > 0) {
162+
const pattern = b.object_pattern(
163+
lets[slot_name].map((node) => {
164+
if (node.expression === null) {
165+
return b.init(node.name, b.id(node.name));
166+
}
167+
168+
if (node.expression.type === 'ObjectExpression') {
169+
// @ts-expect-error TODO it should be an `ObjectPattern`, not an `ObjectExpression`
170+
return b.init(node.name, b.object_pattern(node.expression.properties));
171+
}
172+
173+
if (node.expression.type === 'ArrayExpression') {
174+
// @ts-expect-error TODO ditto
175+
return b.init(node.name, b.array_pattern(node.expression.elements));
176+
}
177+
178+
return b.init(node.name, node.expression);
179+
})
180+
);
181+
182+
params.push(pattern);
183+
}
184+
185+
const slot_fn = b.arrow(params, b.block(block.body));
162186

163187
if (slot_name === 'default' && !has_children_prop) {
164-
if (lets.length === 0 && children.default.every((node) => node.type !== 'SvelteFragment')) {
188+
if (
189+
lets.default.length === 0 &&
190+
children.default.every((node) => node.type !== 'SvelteFragment')
191+
) {
165192
// create `children` prop...
166193
push_prop(b.prop('init', b.id('children'), slot_fn));
167194

packages/svelte/src/compiler/utils/builders.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,11 @@ export function object(properties) {
320320
}
321321

322322
/**
323-
* @param {Array<ESTree.RestElement | ESTree.AssignmentProperty>} properties
323+
* @param {Array<ESTree.RestElement | ESTree.AssignmentProperty | ESTree.Property>} properties
324324
* @returns {ESTree.ObjectPattern}
325325
*/
326326
export function object_pattern(properties) {
327+
// @ts-expect-error the types appear to be wrong
327328
return { type: 'ObjectPattern', properties };
328329
}
329330

packages/svelte/src/internal/server/index.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -428,12 +428,10 @@ export async function value_or_fallback_async(value, fallback) {
428428
* @returns {void}
429429
*/
430430
export function slot(payload, slot_fn, slot_props, fallback_fn) {
431-
if (slot_fn === undefined) {
432-
if (fallback_fn !== null) {
433-
fallback_fn();
434-
}
435-
} else {
431+
if (slot_fn !== undefined) {
436432
slot_fn(payload, slot_props);
433+
} else {
434+
fallback_fn?.();
437435
}
438436
}
439437

packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default function Function_prop_no_getter($$payload) {
1313
onmousedown: () => count += 1,
1414
onmouseup,
1515
onmouseenter: () => count = plusOne(count),
16-
children: ($$payload, $$slotProps) => {
16+
children: ($$payload) => {
1717
$$payload.out += `<!---->clicks: ${$.escape(count)}`;
1818
},
1919
$$slots: { default: true }

0 commit comments

Comments
 (0)