fix: prevent infinite loop when writing to store using shorthand (#10477)

Fixes #10472

This PR ensures we untrack parts of the compiled output to a store write, such as that this no longer brings up an infinite updates error
pull/10487/head
Dominic Gannaway 2 years ago committed by GitHub
parent 87d4b12620
commit cc273f7d53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: prevent infinite loop when writing to store using shorthand

@ -418,15 +418,44 @@ export function serialize_set_binding(node, context, fallback, options) {
}
} else {
if (is_store) {
// If we are assigning to a store property, we need to ensure we don't
// capture the read for the store as part of the member expression to
// keep consistency with how store $ shorthand reads work in Svelte 4.
/**
*
* @param {import("estree").Expression | import("estree").Pattern} node
* @returns {import("estree").Expression}
*/
function visit_node(node) {
if (node.type === 'MemberExpression') {
return {
...node,
object: visit_node(/** @type {import("estree").Expression} */ (node.object)),
property: /** @type {import("estree").Expression} */ (visit(node.property))
};
}
if (node.type === 'Identifier') {
const binding = state.scope.get(node.name);
if (binding !== null && binding.kind === 'store_sub') {
return b.call(
'$.untrack',
b.thunk(/** @type {import('estree').Expression} */ (visit(node)))
);
}
}
return /** @type {import("estree").Expression} */ (visit(node));
}
return b.call(
'$.mutate_store',
serialize_get_binding(b.id(left_name), state),
b.assignment(
node.operator,
/** @type {import('estree').Pattern} */ (visit(node.left)),
/** @type {import("estree").Pattern}} */ (visit_node(node.left)),
value
),
b.call('$' + left_name)
b.call('$.untrack', b.id('$' + left_name))
);
} else if (!state.analysis.runes) {
if (binding.kind === 'prop') {

@ -0,0 +1,26 @@
import { flushSync } from '../../../../src/main/main-client';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
const btn = target.querySelector('button');
flushSync(() => {
btn?.click();
});
assert.htmlEqual(
target.innerHTML,
`<p>test_store:\n 4</p><p>counter:\n 4</p><button>+1</button>`
);
flushSync(() => {
btn?.click();
});
assert.htmlEqual(
target.innerHTML,
`<p>test_store:\n 5</p><p>counter:\n 5</p><button>+1</button>`
);
}
});

@ -0,0 +1,16 @@
<script>
import { writable } from 'svelte/store'
let test_store = writable({id:0});
let counter = $state(3);
$effect(() => {
$test_store.id = counter
});
</script>
<p>test_store: {$test_store.id}</p>
<p>counter: {counter}</p>
<button onclick={() => counter++}>+1</button>
Loading…
Cancel
Save