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.
pull/11985/head
Rich Harris 1 year ago committed by GitHub
parent 36a5143758
commit 2be6d43ea3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: defer tasks without creating effects

@ -17,12 +17,10 @@ import {
} from '../hydration.js';
import { clear_text_content, empty } from '../operations.js';
import { remove } from '../reconciler.js';
import { untrack } from '../../runtime.js';
import {
block,
branch,
destroy_effect,
effect,
run_out_transitions,
pause_children,
pause_effect,
@ -31,6 +29,7 @@ import {
import { source, mutable_source, set } from '../../reactivity/sources.js';
import { is_array, is_frozen } from '../../utils.js';
import { INERT, STATE_SYMBOL } from '../../constants.js';
import { queue_micro_task } from '../task.js';
/**
* The row of a keyed each block that is currently updating. We track this
@ -423,13 +422,11 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
}
if (is_animated) {
effect(() => {
untrack(() => {
queue_micro_task(() => {
for (item of to_animate) {
item.a?.apply();
}
});
});
}
}

@ -9,10 +9,9 @@ import {
} from '../../../../constants.js';
import { create_event, delegate } from './events.js';
import { add_form_reset_listener, autofocus } from './misc.js';
import { effect, effect_root } from '../../reactivity/effects.js';
import * as w from '../../warnings.js';
import { LOADING_ATTR_SYMBOL } from '../../constants.js';
import { queue_idle_task } from '../task.js';
import { queue_idle_task, queue_micro_task } from '../task.js';
/**
* 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
// On the first run, ensure that events are added after bindings so
// that their listeners fire after the binding listeners
if (!prev) {
// In edge cases it may happen that set_attributes is re-run before the
// effect is executed. In that case the render effect which initiates this
// re-run will destroy the inner effect and it will never run. But because
// next and prev may have the same keys, the event would not get added again
// and it would get lost. We prevent this by using a root effect.
const destroy_root = effect_root(() => {
effect(() => {
queue_micro_task(() => {
if (!element.isConnected) return;
for (const [key, value, evt] of events) {
if (current[key] === value) {
evt();
}
}
destroy_root();
});
});
}

@ -1,8 +1,9 @@
import { DEV } from 'esm-env';
import { render_effect, effect, teardown } from '../../../reactivity/effects.js';
import { render_effect, teardown } from '../../../reactivity/effects.js';
import { listen_to_event_and_reset_event } from './shared.js';
import * as e from '../../../errors.js';
import { get_proxied_value, is } from '../../../proxy.js';
import { queue_micro_task } from '../../task.js';
/**
* @param {HTMLInputElement} input
@ -111,7 +112,7 @@ export function bind_group(inputs, group_index, input, get_value, update) {
}
});
effect(() => {
queue_micro_task(() => {
// necessary to maintain binding group order in all insertion scenarios. TODO optimise
binding_group.sort((a, b) => (a.compareDocumentPosition(b) === 4 ? -1 : 1));
});

@ -1,4 +1,4 @@
import { render_effect, teardown } from '../../reactivity/effects.js';
import { teardown } from '../../reactivity/effects.js';
import { all_registered_events, root_event_handles } from '../../render.js';
import { define_property, is_array } from '../../utils.js';
import { hydrating } from '../hydration.js';

@ -1,6 +1,6 @@
import { hydrating } from '../hydration.js';
import { effect } from '../../reactivity/effects.js';
import { clear_text_content } from '../operations.js';
import { queue_micro_task } from '../task.js';
/**
* @param {HTMLElement} dom
@ -12,7 +12,7 @@ export function autofocus(dom, value) {
const body = document.body;
dom.autofocus = true;
effect(() => {
queue_micro_task(() => {
if (document.activeElement === body) {
dom.focus();
}

@ -8,6 +8,7 @@ import { is_function } from '../../utils.js';
import { current_each_item } from '../blocks/each.js';
import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js';
import { BLOCK_EFFECT, EFFECT_RAN, EFFECT_TRANSPARENT } from '../../constants.js';
import { queue_micro_task } from '../task.js';
/**
* @param {Element} element
@ -272,8 +273,8 @@ function animate(element, options, counterpart, t2, callback) {
/** @type {import('#client').Animation} */
var a;
effect(() => {
var o = untrack(() => options({ direction: t2 === 1 ? 'in' : 'out' }));
queue_micro_task(() => {
var o = options({ direction: t2 === 1 ? 'in' : 'out' });
a = animate(element, o, counterpart, t2, callback);
});

@ -3,8 +3,8 @@ import { empty } from './operations.js';
import { create_fragment_from_html } from './reconciler.js';
import { current_effect } from '../runtime.js';
import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js';
import { effect } from '../reactivity/effects.js';
import { is_array } from '../utils.js';
import { queue_micro_task } from './task.js';
/**
* @template {import("#client").TemplateNode | import("#client").TemplateNode[]} T
@ -196,7 +196,7 @@ function run_scripts(node) {
// Don't do it in other circumstances or we could accidentally execute scripts
// in an adjacent @html tag that was instantiated in the meantime.
if (script === node) {
effect(() => script.replaceWith(clone));
queue_micro_task(() => script.replaceWith(clone));
} else {
script.replaceWith(clone);
}

@ -561,7 +561,7 @@ function infinite_loop_guard() {
* @returns {void}
*/
function flush_queued_root_effects(root_effects) {
const length = root_effects.length;
var length = root_effects.length;
if (length === 0) {
return;
}

Loading…
Cancel
Save