Skip to content

Commit 2be6d43

Browse files
authored
feat: defer tasks without creating effects (#11960)
In a handful of places, we're using effect(...) to defer work that only needs to happen once (like autofocusing an element). This is overkill. There is a much cheaper and simpler way that already exists in the codebase — queue_micro_task. It feels like we could probably use it in more places, but when I tried it causes some tests to fail, likely because of subtle timing issues that don't apply to the things changed in this PR.
1 parent 36a5143 commit 2be6d43

File tree

9 files changed

+29
-34
lines changed

9 files changed

+29
-34
lines changed

.changeset/late-bees-vanish.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+
feat: defer tasks without creating effects

packages/svelte/src/internal/client/dom/blocks/each.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,10 @@ import {
1717
} from '../hydration.js';
1818
import { clear_text_content, empty } from '../operations.js';
1919
import { remove } from '../reconciler.js';
20-
import { untrack } from '../../runtime.js';
2120
import {
2221
block,
2322
branch,
2423
destroy_effect,
25-
effect,
2624
run_out_transitions,
2725
pause_children,
2826
pause_effect,
@@ -31,6 +29,7 @@ import {
3129
import { source, mutable_source, set } from '../../reactivity/sources.js';
3230
import { is_array, is_frozen } from '../../utils.js';
3331
import { INERT, STATE_SYMBOL } from '../../constants.js';
32+
import { queue_micro_task } from '../task.js';
3433

3534
/**
3635
* The row of a keyed each block that is currently updating. We track this
@@ -423,12 +422,10 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
423422
}
424423

425424
if (is_animated) {
426-
effect(() => {
427-
untrack(() => {
428-
for (item of to_animate) {
429-
item.a?.apply();
430-
}
431-
});
425+
queue_micro_task(() => {
426+
for (item of to_animate) {
427+
item.a?.apply();
428+
}
432429
});
433430
}
434431
}

packages/svelte/src/internal/client/dom/elements/attributes.js

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ import {
99
} from '../../../../constants.js';
1010
import { create_event, delegate } from './events.js';
1111
import { add_form_reset_listener, autofocus } from './misc.js';
12-
import { effect, effect_root } from '../../reactivity/effects.js';
1312
import * as w from '../../warnings.js';
1413
import { LOADING_ATTR_SYMBOL } from '../../constants.js';
15-
import { queue_idle_task } from '../task.js';
14+
import { queue_idle_task, queue_micro_task } from '../task.js';
1615

1716
/**
1817
* The value/checked attribute in the template actually corresponds to the defaultValue property, so we need
@@ -262,21 +261,13 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
262261
// On the first run, ensure that events are added after bindings so
263262
// that their listeners fire after the binding listeners
264263
if (!prev) {
265-
// In edge cases it may happen that set_attributes is re-run before the
266-
// effect is executed. In that case the render effect which initiates this
267-
// re-run will destroy the inner effect and it will never run. But because
268-
// next and prev may have the same keys, the event would not get added again
269-
// and it would get lost. We prevent this by using a root effect.
270-
const destroy_root = effect_root(() => {
271-
effect(() => {
272-
if (!element.isConnected) return;
273-
for (const [key, value, evt] of events) {
274-
if (current[key] === value) {
275-
evt();
276-
}
264+
queue_micro_task(() => {
265+
if (!element.isConnected) return;
266+
for (const [key, value, evt] of events) {
267+
if (current[key] === value) {
268+
evt();
277269
}
278-
destroy_root();
279-
});
270+
}
280271
});
281272
}
282273

packages/svelte/src/internal/client/dom/elements/bindings/input.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { DEV } from 'esm-env';
2-
import { render_effect, effect, teardown } from '../../../reactivity/effects.js';
2+
import { render_effect, teardown } from '../../../reactivity/effects.js';
33
import { listen_to_event_and_reset_event } from './shared.js';
44
import * as e from '../../../errors.js';
55
import { get_proxied_value, is } from '../../../proxy.js';
6+
import { queue_micro_task } from '../../task.js';
67

78
/**
89
* @param {HTMLInputElement} input
@@ -111,7 +112,7 @@ export function bind_group(inputs, group_index, input, get_value, update) {
111112
}
112113
});
113114

114-
effect(() => {
115+
queue_micro_task(() => {
115116
// necessary to maintain binding group order in all insertion scenarios. TODO optimise
116117
binding_group.sort((a, b) => (a.compareDocumentPosition(b) === 4 ? -1 : 1));
117118
});

packages/svelte/src/internal/client/dom/elements/events.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { render_effect, teardown } from '../../reactivity/effects.js';
1+
import { teardown } from '../../reactivity/effects.js';
22
import { all_registered_events, root_event_handles } from '../../render.js';
33
import { define_property, is_array } from '../../utils.js';
44
import { hydrating } from '../hydration.js';

packages/svelte/src/internal/client/dom/elements/misc.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { hydrating } from '../hydration.js';
2-
import { effect } from '../../reactivity/effects.js';
32
import { clear_text_content } from '../operations.js';
3+
import { queue_micro_task } from '../task.js';
44

55
/**
66
* @param {HTMLElement} dom
@@ -12,7 +12,7 @@ export function autofocus(dom, value) {
1212
const body = document.body;
1313
dom.autofocus = true;
1414

15-
effect(() => {
15+
queue_micro_task(() => {
1616
if (document.activeElement === body) {
1717
dom.focus();
1818
}

packages/svelte/src/internal/client/dom/elements/transitions.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { is_function } from '../../utils.js';
88
import { current_each_item } from '../blocks/each.js';
99
import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js';
1010
import { BLOCK_EFFECT, EFFECT_RAN, EFFECT_TRANSPARENT } from '../../constants.js';
11+
import { queue_micro_task } from '../task.js';
1112

1213
/**
1314
* @param {Element} element
@@ -272,8 +273,8 @@ function animate(element, options, counterpart, t2, callback) {
272273
/** @type {import('#client').Animation} */
273274
var a;
274275

275-
effect(() => {
276-
var o = untrack(() => options({ direction: t2 === 1 ? 'in' : 'out' }));
276+
queue_micro_task(() => {
277+
var o = options({ direction: t2 === 1 ? 'in' : 'out' });
277278
a = animate(element, o, counterpart, t2, callback);
278279
});
279280

packages/svelte/src/internal/client/dom/template.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { empty } from './operations.js';
33
import { create_fragment_from_html } from './reconciler.js';
44
import { current_effect } from '../runtime.js';
55
import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js';
6-
import { effect } from '../reactivity/effects.js';
76
import { is_array } from '../utils.js';
7+
import { queue_micro_task } from './task.js';
88

99
/**
1010
* @template {import("#client").TemplateNode | import("#client").TemplateNode[]} T
@@ -196,7 +196,7 @@ function run_scripts(node) {
196196
// Don't do it in other circumstances or we could accidentally execute scripts
197197
// in an adjacent @html tag that was instantiated in the meantime.
198198
if (script === node) {
199-
effect(() => script.replaceWith(clone));
199+
queue_micro_task(() => script.replaceWith(clone));
200200
} else {
201201
script.replaceWith(clone);
202202
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ function infinite_loop_guard() {
561561
* @returns {void}
562562
*/
563563
function flush_queued_root_effects(root_effects) {
564-
const length = root_effects.length;
564+
var length = root_effects.length;
565565
if (length === 0) {
566566
return;
567567
}

0 commit comments

Comments
 (0)