diff --git a/.changeset/odd-jobs-taste.md b/.changeset/odd-jobs-taste.md
new file mode 100644
index 000000000000..5e2b097815e9
--- /dev/null
+++ b/.changeset/odd-jobs-taste.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: wrap props in deriveds more conservatively in legacy mode
diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js
index 5a0adfc40c25..a62666e18072 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/index.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/index.js
@@ -1263,8 +1263,9 @@ const common_visitors = {
},
CallExpression(node, context) {
if (
- context.state.expression?.type === 'ExpressionTag' ||
- (context.state.expression?.type === 'SpreadAttribute' && !is_known_safe_call(node, context))
+ (context.state.expression?.type === 'ExpressionTag' ||
+ context.state.expression?.type === 'SpreadAttribute') &&
+ !is_known_safe_call(node, context)
) {
context.state.expression.metadata.contains_call_expression = true;
}
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 082a6c4dee41..a2a5084dfa1a 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
@@ -719,11 +719,15 @@ function serialize_inline_component(node, component_name, context) {
const should_wrap_in_derived =
Array.isArray(attribute.value) &&
attribute.value.some((n) => {
- return (
- n.type === 'ExpressionTag' &&
- n.expression.type !== 'Identifier' &&
- n.expression.type !== 'MemberExpression'
- );
+ if (n.type !== 'ExpressionTag') return false;
+ if (n.expression.type === 'Identifier' || n.expression.type === 'Literal') return false;
+
+ if (n.expression.type === 'MemberExpression' && context.state.analysis.runes) {
+ // in legacy mode, `foo={bar.baz}` is wrapped in derived to preserve old behaviour.
+ return false;
+ }
+
+ return true;
});
if (should_wrap_in_derived) {
diff --git a/packages/svelte/tests/runtime-legacy/samples/component-prop-derived/Child.svelte b/packages/svelte/tests/runtime-legacy/samples/component-prop-derived/Child.svelte
new file mode 100644
index 000000000000..3b4b540cc958
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/component-prop-derived/Child.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/packages/svelte/tests/runtime-legacy/samples/component-prop-derived/_config.js b/packages/svelte/tests/runtime-legacy/samples/component-prop-derived/_config.js
new file mode 100644
index 000000000000..b1bb9e5d3c73
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/component-prop-derived/_config.js
@@ -0,0 +1,13 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ test({ assert, logs, target }) {
+ assert.deepEqual(logs, ['x', 42]);
+
+ const btn = target.querySelector('button');
+ flushSync(() => btn?.click());
+
+ assert.deepEqual(logs, ['x', 42]);
+ }
+});
diff --git a/packages/svelte/tests/runtime-legacy/samples/component-prop-derived/main.svelte b/packages/svelte/tests/runtime-legacy/samples/component-prop-derived/main.svelte
new file mode 100644
index 000000000000..584555717328
--- /dev/null
+++ b/packages/svelte/tests/runtime-legacy/samples/component-prop-derived/main.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+