Skip to content

Commit dd21d30

Browse files
committed
Tracker.autorun() can now be put under a $: directive, with caveats
1 parent 581cb5a commit dd21d30

File tree

4 files changed

+88
-11
lines changed

4 files changed

+88
-11
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,10 @@ not depend on any dynamic Svelte state, such as in this example:
110110
</script>
111111
```
112112

113-
However, it will not automatically detect changes to Svelte state, nor can I
114-
guarantee that it will work well with `$:`, so I highly recommend the use of
115-
`useTracker` instead.
113+
To make the autorun also respond to Svelte state changes, you need to put it
114+
under a `$:` block. This will work, but with some caveats: if the Tracker state
115+
is invalidated right after a change to the Svelte state, all `$:` blocks will be
116+
re-run. It is therefore better to use `useTracker` instead, as listed above.
116117

117118
### ReactiveVar
118119

autorun.js

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,89 @@
11
/**
22
* Makes Tracker.autorun() computations automatically stop when the component is
3-
* destroyed.
3+
* destroyed, or, if run from a reactive Svelte computation, when the update
4+
* function is run again.
45
*/
56

67
import { Tracker } from 'meteor/tracker';
7-
import { current_component } from 'svelte/internal';
8+
import { current_component, schedule_update, dirty_components } from 'svelte/internal';
9+
810

911
_autorun = Tracker.autorun;
10-
Tracker.autorun = function autorun() {
12+
Tracker.autorun = function autorun(f, options) {
13+
const component = current_component;
1114
const computation = _autorun.apply(this, arguments);
12-
if (current_component) {
13-
current_component.$$.on_destroy.push(computation.stop.bind(computation));
15+
if (component) {
16+
// We're inside a Svelte component. We have to stop the computation when
17+
// the component is destroyed.
18+
_autoStopComputation(computation, component);
1419
}
1520
return computation;
1621
};
22+
23+
function _autoStopComputation(computation, component) {
24+
const $$ = component.$$;
25+
$$.on_destroy.push(computation.stop.bind(computation));
26+
if (!$$.ctx) {
27+
// We're in initialization, so nothing else to do.
28+
return;
29+
}
30+
31+
// We are in a reactive Svelte update. That means that we'll need to stop the
32+
// computation the next time that it is run. But we don't know when that is,
33+
// because the next update may or may not hit this autorun again, depending on
34+
// the dirty flags.
35+
// So, we simply stop all computations the next time that the update is run,
36+
// but we keep listening for invalidations, so that if one of them becomes
37+
// invalid, we can force Svelte to re-run the updates to make it hit the
38+
// autorun again.
39+
40+
// But first, remember which dirty flags made this autorun trigger, so that we
41+
// can reuse these bits to force Svelte to re-hit the autorun.
42+
// This will unfortunately most of the time be all bits set, since the first
43+
// time it is called is usually during initialization. But if the autorun is
44+
// first enabled by a Svelte variable change, it will be a bit more efficient.
45+
computation._savedDirty = [...$$.dirty];
46+
47+
if ($$._stopComputations) {
48+
$$._stopComputations.push(computation);
49+
return;
50+
}
51+
52+
$$._stopComputations = [computation];
53+
54+
// Temporary hook around the update function so that it stops our computation
55+
// the next time it is called.
56+
const _update = $$.update;
57+
$$.update = () => {
58+
// Optimization: are we about to rerun everything? If so, don't bother with
59+
// onInvalidate, just stop the computations right here.
60+
if ($$.dirty.every(d => (d === 0x7fffffff))) {
61+
$$._stopComputations.forEach(comp => comp.stop());
62+
} else {
63+
// Otherwise, we are not sure whether all the autorun blocks will run
64+
// again, so we prevent the computations from continuing to run, but will
65+
// continue to watch it for changes. If there is a change, we require the
66+
// update to be run again.
67+
for (const comp of $$._stopComputations) {
68+
comp.stopped = true;
69+
comp.onInvalidate(() => {
70+
if ($$.dirty[0] === -1) {
71+
// We're the first to mark it dirty since the last update.
72+
dirty_components.push(component);
73+
schedule_update();
74+
$$.dirty.fill(0);
75+
}
76+
comp._savedDirty.forEach((mask, i) => {
77+
$$.dirty[i] |= mask & 0x7fffffff;
78+
});
79+
});
80+
}
81+
}
82+
83+
// Leave everything as it was, so that the overhead is removed if the
84+
// Tracker.autorun was under a condition that has now becomes false.
85+
delete $$._stopComputations;
86+
$$.update = _update;
87+
return _update();
88+
};
89+
}

package.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package.describe({
22
name: 'rdb:svelte-meteor-data',
3-
version: '0.1.0',
3+
version: '0.2.0',
44
summary: 'Reactively track Meteor data inside Svelte components',
55
git: 'https://github.com/rdb/svelte-meteor-data',
66
documentation: 'README.md'

use-tracker.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
* This function wraps a reactive Meteor computation as a Svelte store.
33
*/
44

5+
const nonreactive = Tracker.nonreactive;
6+
const autorun = Tracker.autorun;
7+
58
export default function useTracker(reactiveFn) {
69
return {
710
subscribe(set) {
8-
return Tracker.nonreactive(() => {
9-
const computation = Tracker.autorun(() => set(reactiveFn()));
11+
return nonreactive(() => {
12+
const computation = autorun(() => set(reactiveFn()));
1013
return computation.stop.bind(computation);
1114
});
1215
},

0 commit comments

Comments
 (0)