Skip to content

Commit c2f75dc

Browse files
authored
fix: use coarse-grained updates for derived expressions in legacy mode (#11652)
fixes #11648 Also deduplicates the code a bit
1 parent 54083fb commit c2f75dc

File tree

5 files changed

+47
-17
lines changed

5 files changed

+47
-17
lines changed

.changeset/fresh-beds-wash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: use coarse-grained updates for derived expressions passed to props in legacy mode

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,15 @@ function collect_parent_each_blocks(context) {
632632
);
633633
}
634634

635+
/**
636+
* Svelte legacy mode should use safe equals in most places, runes mode shouldn't
637+
* @param {import('../types.js').ComponentClientTransformState} state
638+
* @param {import('estree').Expression} arg
639+
*/
640+
function create_derived(state, arg) {
641+
return b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', arg);
642+
}
643+
635644
/**
636645
* @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node
637646
* @param {string} component_name
@@ -745,7 +754,7 @@ function serialize_inline_component(node, component_name, context) {
745754

746755
if (should_wrap_in_derived) {
747756
const id = b.id(context.state.scope.generate(attribute.name));
748-
context.state.init.push(b.var(id, b.call('$.derived', b.thunk(value))));
757+
context.state.init.push(b.var(id, create_derived(context.state, b.thunk(value))));
749758
arg = b.call('$.get', id);
750759
}
751760

@@ -1649,9 +1658,8 @@ function serialize_template_literal(values, visit, state) {
16491658
state.init.push(
16501659
b.const(
16511660
id,
1652-
b.call(
1653-
// In runes mode, we want things to be fine-grained - but not in legacy mode
1654-
state.analysis.runes ? '$.derived' : '$.derived_safe_equal',
1661+
create_derived(
1662+
state,
16551663
b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression)))
16561664
)
16571665
)
@@ -1701,9 +1709,8 @@ export const template_visitors = {
17011709
state.init.push(
17021710
b.const(
17031711
declaration.id,
1704-
b.call(
1705-
// In runes mode, we want things to be fine-grained - but not in legacy mode
1706-
state.analysis.runes ? '$.derived' : '$.derived_safe_equal',
1712+
create_derived(
1713+
state,
17071714
b.thunk(/** @type {import('estree').Expression} */ (visit(declaration.init)))
17081715
)
17091716
)
@@ -1738,10 +1745,7 @@ export const template_visitors = {
17381745
])
17391746
);
17401747

1741-
state.init.push(
1742-
// In runes mode, we want things to be fine-grained - but not in legacy mode
1743-
b.const(tmp, b.call(state.analysis.runes ? '$.derived' : '$.derived_safe_equal', fn))
1744-
);
1748+
state.init.push(b.const(tmp, create_derived(state, fn)));
17451749

17461750
// we need to eagerly evaluate the expression in order to hit any
17471751
// 'Cannot access x before initialization' errors
@@ -2995,12 +2999,7 @@ export const template_visitors = {
29952999
const name = node.expression === null ? node.name : node.expression.name;
29963000
return b.const(
29973001
name,
2998-
b.call(
2999-
// in legacy mode, sources can be mutated but they're not fine-grained.
3000-
// Using the safe-equal derived version ensures the slot is still updated
3001-
state.analysis.runes ? '$.derived' : '$.derived_safe_equal',
3002-
b.thunk(b.member(b.id('$$slotProps'), b.id(node.name)))
3003-
)
3002+
create_derived(state, b.thunk(b.member(b.id('$$slotProps'), b.id(node.name))))
30043003
);
30053004
}
30063005
},
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
export let x;
3+
</script>
4+
5+
child: {x.y}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
html: `child: 0 parent: 0 <button>inc x</button>`,
5+
6+
async test({ assert, target }) {
7+
await target.querySelector('button')?.click();
8+
assert.htmlEqual(target.innerHTML, `child: 1 parent: 1 <button>inc x</button>`);
9+
}
10+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
import Child from "./Child.svelte";
3+
4+
let x;
5+
$: if (!x) x = {y:0};
6+
</script>
7+
8+
<Child x={x ?? {}} />
9+
10+
parent: {x.y}
11+
<button on:click={() => x.y++}>inc x</button>

0 commit comments

Comments
 (0)