Skip to content

Commit 54d89a9

Browse files
committed
fix: bail out of event hoisting when referencing store subscriptions
1 parent 521988c commit 54d89a9

File tree

4 files changed

+56
-15
lines changed

4 files changed

+56
-15
lines changed

.changeset/dry-ghosts-prove.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: bail out of event hoisting when referencing store subscriptions

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -166,25 +166,20 @@ function get_delegated_event(event_name, handler, context) {
166166
}
167167

168168
// If we can't find a function, bail-out
169-
if (target_function == null) {
170-
return non_hoistable;
171-
}
169+
if (target_function == null) return non_hoistable;
172170
// If the function is marked as non-hoistable, bail-out
173-
if (target_function.metadata.hoistable === 'impossible') {
174-
return non_hoistable;
175-
}
171+
if (target_function.metadata.hoistable === 'impossible') return non_hoistable;
176172
// If the function has more than one arg, then bail-out
177-
if (target_function.params.length > 1) {
178-
return non_hoistable;
179-
}
173+
if (target_function.params.length > 1) return non_hoistable;
180174

181175
const visited_references = new Set();
182176
const scope = target_function.metadata.scope;
183177
for (const [reference] of scope.references) {
184178
// Bail-out if the arguments keyword is used
185-
if (reference === 'arguments') {
186-
return non_hoistable;
187-
}
179+
if (reference === 'arguments') return non_hoistable;
180+
// Bail-out if references a store subscription
181+
if (scope.get(`$${reference}`)?.kind === 'store_sub') return non_hoistable;
182+
188183
const binding = scope.get(reference);
189184
const local_binding = context.state.scope.get(reference);
190185

@@ -203,9 +198,7 @@ function get_delegated_event(event_name, handler, context) {
203198
}
204199

205200
// If we reference the index within an each block, then bail-out.
206-
if (binding !== null && binding.initial?.type === 'EachBlock') {
207-
return non_hoistable;
208-
}
201+
if (binding !== null && binding.initial?.type === 'EachBlock') return non_hoistable;
209202

210203
if (
211204
binding !== null &&
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
test({ assert, target }) {
6+
const [b1, b2] = target.querySelectorAll('button');
7+
8+
b1.click();
9+
flushSync();
10+
11+
assert.htmlEqual(
12+
target.innerHTML,
13+
'<button>set new store</button><button>incr</button><pre>0</pre>'
14+
);
15+
16+
b2.click();
17+
b2.click();
18+
b2.click();
19+
flushSync();
20+
21+
assert.htmlEqual(
22+
target.innerHTML,
23+
'<button>set new store</button><button>incr</button><pre>3</pre>'
24+
);
25+
}
26+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script>
2+
import { writable } from 'svelte/store';
3+
4+
let store = $state();
5+
6+
function setStore() {
7+
store = writable(0, () => {
8+
console.log('start');
9+
return () => console.log('stop');
10+
});
11+
}
12+
</script>
13+
14+
<button onclick={setStore}>set new store</button>
15+
<button onclick={() => $store++}>incr</button>
16+
17+
<pre>{$store}</pre>

0 commit comments

Comments
 (0)