From 97d3ec2f89897f2f17126734016a6adb56c95e49 Mon Sep 17 00:00:00 2001 From: Daniel Imfeld Date: Mon, 5 Feb 2024 03:47:10 -1000 Subject: [PATCH] fix: allow `let:` directives on slot elements (#10391) fixes #10382 --------- Co-authored-by: Simon Holthausen --- .changeset/friendly-candles-relate.md | 5 +++++ packages/svelte/src/compiler/errors.js | 3 ++- .../svelte/src/compiler/phases/2-analyze/validation.js | 3 ++- .../phases/3-transform/client/visitors/template.js | 10 +++++++++- .../phases/3-transform/server/transform-server.js | 10 +++++++++- packages/svelte/src/compiler/phases/scope.js | 1 + .../samples/slot-let-forwarding/_config.js | 5 +++++ .../samples/slot-let-forwarding/inner.svelte | 3 +++ .../samples/slot-let-forwarding/main.svelte | 9 +++++++++ .../samples/slot-let-forwarding/outer.svelte | 9 +++++++++ 10 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 .changeset/friendly-candles-relate.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/inner.svelte create mode 100644 packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/main.svelte create mode 100644 packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/outer.svelte diff --git a/.changeset/friendly-candles-relate.md b/.changeset/friendly-candles-relate.md new file mode 100644 index 0000000000..d9bde31819 --- /dev/null +++ b/.changeset/friendly-candles-relate.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: allow `let:` directives on slot elements diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 5c73007a3e..fa0964636f 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -273,7 +273,8 @@ const attributes = { /** @satisfies {Errors} */ const slots = { - 'invalid-slot-element-attribute': () => ` can only receive attributes, not directives`, + 'invalid-slot-element-attribute': () => + ` can only receive attributes and (optionally) let directives`, 'invalid-slot-attribute': () => `slot attribute must be a static value`, /** @param {boolean} is_default */ 'invalid-slot-name': (is_default) => diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 8863b617b3..47a5fd74bc 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -494,6 +494,7 @@ const validation = { parent === undefined || (parent.type !== 'Component' && parent.type !== 'RegularElement' && + parent.type !== 'SlotElement' && parent.type !== 'SvelteElement' && parent.type !== 'SvelteComponent' && parent.type !== 'SvelteSelf' && @@ -609,7 +610,7 @@ const validation = { error(attribute, 'invalid-slot-name', true); } } - } else if (attribute.type !== 'SpreadAttribute') { + } else if (attribute.type !== 'SpreadAttribute' && attribute.type !== 'LetDirective') { error(attribute, 'invalid-slot-element-attribute'); } } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 5297a403ce..0351159783 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -2916,6 +2916,9 @@ export const template_visitors = { /** @type {import('estree').Expression[]} */ const spreads = []; + /** @type {import('estree').ExpressionStatement[]} */ + const lets = []; + let is_default = true; /** @type {import('estree').Expression} */ @@ -2931,16 +2934,21 @@ export const template_visitors = { if (attribute.name === 'name') { name = value; is_default = false; - } else { + } else if (attribute.name !== 'slot') { if (attribute.metadata.dynamic) { props.push(b.get(attribute.name, [b.return(value)])); } else { props.push(b.init(attribute.name, value)); } } + } else if (attribute.type === 'LetDirective') { + lets.push(/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute))); } } + // Let bindings first, they can be used on attributes + context.state.init.push(...lets); + const props_expression = spreads.length === 0 ? b.object(props) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index accb6103a7..330340bc7d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -1576,6 +1576,9 @@ const template_visitors = { /** @type {import('estree').Expression[]} */ const spreads = []; + /** @type {import('estree').ExpressionStatement[]} */ + const lets = []; + /** @type {import('estree').Expression} */ let expression = b.member_id('$$props.children'); @@ -1586,16 +1589,21 @@ const template_visitors = { const value = serialize_attribute_value(attribute.value, context); if (attribute.name === 'name') { expression = b.member(b.member_id('$$props.$$slots'), value, true, true); - } else { + } else if (attribute.name !== 'slot') { if (attribute.metadata.dynamic) { props.push(b.get(attribute.name, [b.return(value)])); } else { props.push(b.init(attribute.name, value)); } } + } else if (attribute.type === 'LetDirective') { + lets.push(/** @type {import('estree').ExpressionStatement} */ (context.visit(attribute))); } } + // Let bindings first, they can be used on attributes + context.state.init.push(...lets); + const props_expression = spreads.length === 0 ? b.object(props) diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 24083fec00..bf2adf724b 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -357,6 +357,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { }, SvelteFragment, + SlotElement: SvelteFragment, SvelteElement: SvelteFragment, RegularElement: SvelteFragment, diff --git a/packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/_config.js b/packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/_config.js new file mode 100644 index 0000000000..f48317749c --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `
5
` +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/inner.svelte b/packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/inner.svelte new file mode 100644 index 0000000000..c9b3506291 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/inner.svelte @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/main.svelte b/packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/main.svelte new file mode 100644 index 0000000000..366234f33b --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/main.svelte @@ -0,0 +1,9 @@ + + + +
+ {foo} +
+
diff --git a/packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/outer.svelte b/packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/outer.svelte new file mode 100644 index 0000000000..95f819eb15 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/slot-let-forwarding/outer.svelte @@ -0,0 +1,9 @@ + + + + + {foo} + +