fix: preserve component function context

We need to pass the SFC component function to the snippet dev time function or else nested snippets result in the wrong context being set

fixes #12040
binding-validation-fix
Simon Holthausen 2 weeks ago
parent a62dce3e83
commit ef5eeea39b

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: preserve component function context for nested components

@ -886,7 +886,12 @@ function serialize_inline_component(node, component_name, context) {
if (slot_name === 'default' && !has_children_prop) {
push_prop(
b.init('children', context.state.options.dev ? b.call('$.wrap_snippet', slot_fn) : slot_fn)
b.init(
'children',
context.state.options.dev
? b.call('$.wrap_snippet', slot_fn, b.id(context.state.analysis.name))
: slot_fn
)
);
// We additionally add the default slot as a boolean, so that the slot render function on the other
// side knows it should get the content to render from $$props.children
@ -2699,7 +2704,7 @@ export const template_visitors = {
let snippet = b.arrow(args, body);
if (context.state.options.dev) {
snippet = b.call('$.wrap_snippet', snippet);
snippet = b.call('$.wrap_snippet', snippet, b.id(context.state.analysis.name));
}
const declaration = b.var(node.expression, snippet);

@ -39,15 +39,13 @@ export function snippet(get_snippet, node, ...args) {
* In development, wrap the snippet function so that it passes validation, and so that the
* correct component context is set for ownership checks
* @param {(node: import('#client').TemplateNode, ...args: any[]) => import('#client').Dom} fn
* @returns
* @param {any} component
*/
export function wrap_snippet(fn) {
let component = /** @type {import('#client').ComponentContext} */ (current_component_context);
export function wrap_snippet(fn, component) {
return add_snippet_symbol(
(/** @type {import('#client').TemplateNode} */ node, /** @type {any[]} */ ...args) => {
var previous_component_function = dev_current_component_function;
set_dev_current_component_function(component.function);
set_dev_current_component_function(component);
try {
return fn(node, ...args);

@ -0,0 +1,5 @@
<script>
let { children } = $props();
</script>
{@render children()}

@ -0,0 +1,5 @@
<script>
let { children } = $props();
</script>
{@render children()}

@ -0,0 +1,5 @@
<script lang="ts">
let { count = $bindable() } = $props();
</script>
<button onclick={() => count.value++}>{count.value}</button>

@ -0,0 +1,23 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
// Tests that nested snippets preserve correct component function context so we don't get false positive warnings
export default test({
html: `<button>0</button>`,
compileOptions: {
dev: true
},
test({ assert, target, warnings }) {
const button = target.querySelector('button');
button?.click();
flushSync();
assert.htmlEqual(target.innerHTML, `<button>1</button>`);
assert.deepEqual(warnings, []);
},
warnings: []
});

@ -0,0 +1,13 @@
<script>
import Component1 from './Component1.svelte';
import Component2 from './Component2.svelte';
import Component3 from './Component3.svelte';
let count = $state({ value: 0 });
</script>
<Component1>
<Component2>
<Component3 bind:count></Component3>
</Component2>
</Component1>
Loading…
Cancel
Save