Skip to content

Commit f2179c9

Browse files
authored
chore: better store subscriptions (#12277)
* use existing teardown function * set up stores and unsubscriptions in one go * we never set last_value, this is gibberish * simplify * tidy up * inline * more
1 parent 15873ab commit f2179c9

File tree

7 files changed

+41
-68
lines changed

7 files changed

+41
-68
lines changed

.changeset/thirty-flies-push.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+
chore: tidy up store logic

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

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,19 +154,12 @@ export function client_component(source, analysis, options) {
154154
}
155155
if (binding.kind === 'store_sub') {
156156
if (store_setup.length === 0) {
157-
store_setup.push(
158-
b.const('$$subscriptions', b.object([])),
159-
b.stmt(b.call('$.unsubscribe_on_destroy', b.id('$$subscriptions')))
160-
);
157+
store_setup.push(b.const('$$stores', b.call('$.setup_stores')));
161158
}
159+
162160
// We're creating an arrow function that gets the store value which minifies better for two or more references
163161
const store_reference = serialize_get_binding(b.id(name.slice(1)), instance_state);
164-
const store_get = b.call(
165-
'$.store_get',
166-
store_reference,
167-
b.literal(name),
168-
b.id('$$subscriptions')
169-
);
162+
const store_get = b.call('$.store_get', store_reference, b.literal(name), b.id('$$stores'));
170163
store_setup.push(
171164
b.const(
172165
binding.node,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ export function serialize_set_binding(node, context, fallback, prefix, options)
345345
}
346346

347347
if (state.scope.get(`$${left.name}`)?.kind === 'store_sub') {
348-
return b.call('$.store_unsub', call, b.literal(`$${left.name}`), b.id('$$subscriptions'));
348+
return b.call('$.store_unsub', call, b.literal(`$${left.name}`), b.id('$$stores'));
349349
} else {
350350
return call;
351351
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2438,7 +2438,7 @@ export const template_visitors = {
24382438
b.thunk(b.sequence(indirect_dependencies))
24392439
);
24402440
const invalidate_store = store_to_invalidate
2441-
? b.call('$.invalidate_store', b.id('$$subscriptions'), b.literal(store_to_invalidate))
2441+
? b.call('$.invalidate_store', b.id('$$stores'), b.literal(store_to_invalidate))
24422442
: undefined;
24432443

24442444
const sequence = [];

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ export {
111111
export {
112112
invalidate_store,
113113
mutate_store,
114+
setup_stores,
114115
store_get,
115116
store_set,
116117
store_unsub,
117-
unsubscribe_on_destroy,
118118
update_pre_store,
119119
update_store
120120
} from './reactivity/store.js';

packages/svelte/src/internal/client/reactivity/store.js

Lines changed: 29 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
/** @import { StoreReferencesContainer, Source } from '#client' */
1+
/** @import { StoreReferencesContainer } from '#client' */
22
/** @import { Store } from '#shared' */
33
import { subscribe_to_store } from '../../../store/utils.js';
44
import { noop } from '../../shared/utils.js';
55
import { UNINITIALIZED } from '../../../constants.js';
6-
import { get, untrack } from '../runtime.js';
7-
import { effect } from './effects.js';
6+
import { get } from '../runtime.js';
7+
import { teardown } from './effects.js';
88
import { mutable_source, set } from './sources.js';
99

1010
/**
@@ -18,30 +18,25 @@ import { mutable_source, set } from './sources.js';
1818
* @returns {V}
1919
*/
2020
export function store_get(store, store_name, stores) {
21-
/** @type {StoreReferencesContainer[''] | undefined} */
22-
let entry = stores[store_name];
23-
const is_new = entry === undefined;
24-
25-
if (is_new) {
26-
entry = {
27-
store: null,
28-
last_value: null,
29-
value: mutable_source(UNINITIALIZED),
30-
unsubscribe: noop
31-
};
32-
stores[store_name] = entry;
33-
}
21+
const entry = (stores[store_name] ??= {
22+
store: null,
23+
source: mutable_source(UNINITIALIZED),
24+
unsubscribe: noop
25+
});
3426

35-
if (is_new || entry.store !== store) {
27+
if (entry.store !== store) {
3628
entry.unsubscribe();
3729
entry.store = store ?? null;
38-
entry.unsubscribe = connect_store_to_signal(store, entry.value);
30+
31+
if (store == null) {
32+
set(entry.source, undefined);
33+
entry.unsubscribe = noop;
34+
} else {
35+
entry.unsubscribe = subscribe_to_store(store, (v) => set(entry.source, v));
36+
}
3937
}
4038

41-
const value = get(entry.value);
42-
// This could happen if the store was cleaned up because the component was destroyed and there's a leak on the user side.
43-
// In that case we don't want to fail with a cryptic Symbol error, but rather return the last value we got.
44-
return value === UNINITIALIZED ? entry.last_value : value;
39+
return get(entry.source);
4540
}
4641

4742
/**
@@ -65,20 +60,6 @@ export function store_unsub(store, store_name, stores) {
6560
return store;
6661
}
6762

68-
/**
69-
* @template V
70-
* @param {Store<V> | null | undefined} store
71-
* @param {Source<V>} source
72-
*/
73-
function connect_store_to_signal(store, source) {
74-
if (store == null) {
75-
set(source, undefined);
76-
return noop;
77-
}
78-
79-
return subscribe_to_store(store, (v) => set(source, v));
80-
}
81-
8263
/**
8364
* Sets the new value of a store and returns that value.
8465
* @template V
@@ -96,24 +77,28 @@ export function store_set(store, value) {
9677
* @param {string} store_name
9778
*/
9879
export function invalidate_store(stores, store_name) {
99-
const store = stores[store_name];
100-
if (store.store) {
101-
store_set(store.store, store.value.v);
80+
var entry = stores[store_name];
81+
if (entry.store !== null) {
82+
store_set(entry.store, entry.source.v);
10283
}
10384
}
10485

10586
/**
10687
* Unsubscribes from all auto-subscribed stores on destroy
107-
* @param {StoreReferencesContainer} stores
88+
* @returns {StoreReferencesContainer}
10889
*/
109-
export function unsubscribe_on_destroy(stores) {
110-
on_destroy(() => {
111-
let store_name;
112-
for (store_name in stores) {
90+
export function setup_stores() {
91+
/** @type {StoreReferencesContainer} */
92+
const stores = {};
93+
94+
teardown(() => {
95+
for (var store_name in stores) {
11396
const ref = stores[store_name];
11497
ref.unsubscribe();
11598
}
11699
});
100+
101+
return stores;
117102
}
118103

119104
/**
@@ -150,12 +135,3 @@ export function update_pre_store(store, store_value, d = 1) {
150135
store.set(value);
151136
return value;
152137
}
153-
154-
/**
155-
* Schedules a callback to run immediately before the component is unmounted.
156-
* @param {() => any} fn
157-
* @returns {void}
158-
*/
159-
function on_destroy(fn) {
160-
effect(() => () => untrack(fn));
161-
}

packages/svelte/src/internal/client/types.d.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,8 @@ export type StoreReferencesContainer = Record<
148148
string,
149149
{
150150
store: Store<any> | null;
151-
last_value: any;
152151
unsubscribe: Function;
153-
value: Source<any>;
152+
source: Source<any>;
154153
}
155154
>;
156155

0 commit comments

Comments
 (0)