From 9be0fdd753d116573c894f121c8cc5769d009d3a Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 14 May 2024 20:28:35 +0200 Subject: [PATCH] interop for passing slots but rendering them with render tags --- .../3-transform/client/visitors/template.js | 18 ++++++++- .../3-transform/server/transform-server.js | 39 ++++++++++++++----- .../src/internal/client/dom/blocks/snippet.js | 31 +++++++++++++++ packages/svelte/src/internal/client/index.js | 2 +- packages/svelte/src/internal/server/index.js | 20 ++++++++++ .../snippet-slot-interop-2/Child.svelte | 6 +++ .../samples/snippet-slot-interop-2/_config.js | 5 +++ .../snippet-slot-interop-2/main.svelte | 8 ++++ 8 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-slot-interop-2/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-slot-interop-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-slot-interop-2/main.svelte 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 fcf8d00d4f..4286e3ac19 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 @@ -1786,6 +1786,10 @@ export const template_visitors = { const raw_args = unwrap_optional(node.expression).arguments; const is_reactive = callee.type !== 'Identifier' || context.state.scope.get(callee.name)?.kind !== 'normal'; + const needs_backwards_compat = + callee.type === 'Identifier' && + raw_args.length < 2 && + context.state.scope.get(callee.name)?.kind === 'prop'; /** @type {import('estree').Expression[]} */ const args = [context.state.node]; @@ -1798,7 +1802,19 @@ export const template_visitors = { snippet_function = b.call('$.validate_snippet', snippet_function); } - if (is_reactive) { + if (needs_backwards_compat) { + context.state.init.push( + b.stmt( + b.call( + '$.render_snippet_or_slot', + b.thunk(snippet_function), + b.id('$$props'), + b.literal(callee.name), + ...args + ) + ) + ); + } else if (is_reactive) { context.state.init.push(b.stmt(b.call('$.snippet', b.thunk(snippet_function), ...args))); } else { context.state.init.push( 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 f5d7e2c3ee..7a9bec71e9 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 @@ -1303,6 +1303,10 @@ const template_visitors = { const callee = unwrap_optional(node.expression).callee; const raw_args = unwrap_optional(node.expression).arguments; + const needs_backwards_compat = + callee.type === 'Identifier' && + raw_args.length < 2 && + context.state.scope.get(callee.name)?.kind === 'prop'; const expression = /** @type {import('estree').Expression} */ (context.visit(callee)); const snippet_function = state.options.dev @@ -1313,17 +1317,34 @@ const template_visitors = { return /** @type {import('estree').Expression} */ (context.visit(arg)); }); - state.template.push( - t_statement( - b.stmt( - (node.expression.type === 'CallExpression' ? b.call : b.maybe_call)( - snippet_function, - b.id('$$payload'), - ...snippet_args + if (needs_backwards_compat) { + state.template.push( + t_statement( + b.stmt( + b.call( + '$.render_snippet_or_slot', + snippet_function, + b.id('$$props'), + b.literal(callee.name), + b.id('$$payload'), + ...snippet_args + ) ) ) - ) - ); + ); + } else { + state.template.push( + t_statement( + b.stmt( + (node.expression.type === 'CallExpression' ? b.call : b.maybe_call)( + snippet_function, + b.id('$$payload'), + ...snippet_args + ) + ) + ) + ); + } state.template.push(block_close); }, diff --git a/packages/svelte/src/internal/client/dom/blocks/snippet.js b/packages/svelte/src/internal/client/dom/blocks/snippet.js index 5bf13a45d8..7902df4211 100644 --- a/packages/svelte/src/internal/client/dom/blocks/snippet.js +++ b/packages/svelte/src/internal/client/dom/blocks/snippet.js @@ -57,3 +57,34 @@ export function wrap_snippet(fn) { } ); } + +/** + * Remove this once slots are gone + * @param {any} snippet_fn + * @param {Record} $$props + * @param {string} name + * @param {Element} node + * @param {any} slot_props + */ +export function render_snippet_or_slot(snippet_fn, $$props, name, node, slot_props) { + if ($$props.$$slots) { + const slot = $$props.$$slots[name === 'children' ? 'default' : name]; + if (typeof slot === 'function') { + let props = undefined; + if (slot_props) { + props = new Proxy( + {}, + { + get(_, key) { + return slot_props()?.[key]; + } + } + ); + } + slot(node, props); + return; + } + } + + snippet(snippet_fn, node, slot_props); +} diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index c54de90fe5..27161906e5 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -15,7 +15,7 @@ export { key_block as key } from './dom/blocks/key.js'; export { css_props } from './dom/blocks/css-props.js'; export { index, each } from './dom/blocks/each.js'; export { html } from './dom/blocks/html.js'; -export { snippet, wrap_snippet } from './dom/blocks/snippet.js'; +export { snippet, wrap_snippet, render_snippet_or_slot } from './dom/blocks/snippet.js'; export { component } from './dom/blocks/svelte-component.js'; export { element } from './dom/blocks/svelte-element.js'; export { head } from './dom/blocks/svelte-head.js'; diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 2d8632063d..888cd48f6e 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -563,6 +563,26 @@ export function sanitize_slots(props) { return sanitized; } +/** + * Remove this once slots are gone + * @param {any} snippet_fn + * @param {Record} $$props + * @param {string} name + * @param {Payload} payload + * @param {any} slot_props + */ +export function render_snippet_or_slot(snippet_fn, $$props, name, payload, slot_props) { + if ($$props.$$slots) { + const slot = $$props.$$slots[name === 'children' ? 'default' : name]; + if (typeof slot === 'function') { + slot(payload, slot_props); + return; + } + } + + snippet_fn?.(payload, slot_props); +} + /** * Legacy mode: If the prop has a fallback and is bound in the * parent component, propagate the fallback value upwards. diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-slot-interop-2/Child.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-slot-interop-2/Child.svelte new file mode 100644 index 0000000000..9f68823422 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-slot-interop-2/Child.svelte @@ -0,0 +1,6 @@ + + +

{@render children()}

+{@render named({foo: 'foo'})} diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-slot-interop-2/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-slot-interop-2/_config.js new file mode 100644 index 0000000000..fb0f428cf4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-slot-interop-2/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `

Default

Named foo

` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-slot-interop-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-slot-interop-2/main.svelte new file mode 100644 index 0000000000..7079d5a1b2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-slot-interop-2/main.svelte @@ -0,0 +1,8 @@ + + + + Default +

Named {foo}

+