Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: refactor task microtask dispatching + boundary scheduling #15046

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eleven-weeks-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

chore: refactor task microtask dispatching + boundary scheduling
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/** @import { Context } from '../types' */
import * as e from '../../../errors.js';

const valid = ['onerror', 'failed'];
const valid = ['onerror', 'failed', 'pending'];

/**
* @param {AST.SvelteBoundary} node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ export function SvelteBoundary(node, context) {

// Capture the `failed` implicit snippet prop
for (const child of node.fragment.nodes) {
if (child.type === 'SnippetBlock' && child.expression.name === 'failed') {
if (
child.type === 'SnippetBlock' &&
(child.expression.name === 'failed' || child.expression.name === 'pending')
) {
// we need to delay the visit of the snippets in case they access a ConstTag that is declared
// after the snippets so that the visitor for the const tag can be updated
snippets_visits.push(() => {
Expand Down
4 changes: 2 additions & 2 deletions packages/svelte/src/internal/client/dom/blocks/await.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
set_dev_current_component_function
} from '../../runtime.js';
import { hydrate_next, hydrate_node, hydrating } from '../hydration.js';
import { queue_micro_task } from '../task.js';
import { queue_post_micro_task } from '../task.js';
import { UNINITIALIZED } from '../../../../constants.js';

const PENDING = 0;
Expand Down Expand Up @@ -148,7 +148,7 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {
} else {
// Wait a microtask before checking if we should show the pending state as
// the promise might have resolved by the next microtask.
queue_micro_task(() => {
queue_post_micro_task(() => {
if (!resolved) update(PENDING, true);
});
}
Expand Down
155 changes: 132 additions & 23 deletions packages/svelte/src/internal/client/dom/blocks/boundary.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
/** @import { Effect, TemplateNode, } from '#client' */

import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '../../constants.js';
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js';
import {
block,
branch,
destroy_effect,
pause_effect,
resume_effect
} from '../../reactivity/effects.js';
import {
active_effect,
active_reaction,
Expand All @@ -20,7 +26,11 @@ import {
remove_nodes,
set_hydrate_node
} from '../hydration.js';
import { queue_micro_task } from '../task.js';
import { get_next_sibling } from '../operations.js';
import { queue_boundary_micro_task } from '../task.js';

const ASYNC_INCREMENT = Symbol();
const ASYNC_DECREMENT = Symbol();

/**
* @param {Effect} boundary
Expand Down Expand Up @@ -49,6 +59,7 @@ function with_boundary(boundary, fn) {
* @param {{
* onerror?: (error: unknown, reset: () => void) => void,
* failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void
* pending?: (anchor: Node) => void
* }} props
* @param {((anchor: Node) => void)} boundary_fn
* @returns {void}
Expand All @@ -58,14 +69,106 @@ export function boundary(node, props, boundary_fn) {

/** @type {Effect} */
var boundary_effect;
/** @type {Effect | null} */
var async_effect = null;
/** @type {DocumentFragment | null} */
var async_fragment = null;
var async_count = 0;

block(() => {
var boundary = /** @type {Effect} */ (active_effect);
var hydrate_open = hydrate_node;
var is_creating_fallback = false;

// We re-use the effect's fn property to avoid allocation of an additional field
boundary.fn = (/** @type {unknown}} */ error) => {
const render_snippet = (/** @type { () => void } */ snippet_fn) => {
with_boundary(boundary, () => {
is_creating_fallback = true;

try {
boundary_effect = branch(() => {
snippet_fn();
});
} catch (error) {
handle_error(error, boundary, null, boundary.ctx);
}

reset_is_throwing_error();
is_creating_fallback = false;
});
};

// @ts-ignore We re-use the effect's fn property to avoid allocation of an additional field
boundary.fn = (/** @type {unknown} */ input) => {
let pending = props.pending;

if (input === ASYNC_INCREMENT) {
if (!pending) {
return false;
}

if (async_count++ === 0) {
queue_boundary_micro_task(() => {
if (async_effect || !boundary_effect) {
return;
}

var effect = boundary_effect;
async_effect = boundary_effect;

pause_effect(
async_effect,
() => {
/** @type {TemplateNode | null} */
var node = effect.nodes_start;
var end = effect.nodes_end;
async_fragment = document.createDocumentFragment();

while (node !== null) {
/** @type {TemplateNode | null} */
var sibling =
node === end ? null : /** @type {TemplateNode} */ (get_next_sibling(node));

node.remove();
async_fragment.append(node);
node = sibling;
}
},
false
);

render_snippet(() => {
pending(anchor);
});
});
}

return true;
}

if (input === ASYNC_DECREMENT) {
if (!pending) {
return false;
}

if (--async_count === 0) {
queue_boundary_micro_task(() => {
if (!async_effect) {
return;
}
if (boundary_effect) {
destroy_effect(boundary_effect);
}
boundary_effect = async_effect;
async_effect = null;
anchor.before(/** @type {DocumentFragment} */ (async_fragment));
resume_effect(boundary_effect);
});
}

return true;
}

var error = input;
var onerror = props.onerror;
let failed = props.failed;

Expand Down Expand Up @@ -96,25 +199,13 @@ export function boundary(node, props, boundary_fn) {
}

if (failed) {
// Render the `failed` snippet in a microtask
queue_micro_task(() => {
with_boundary(boundary, () => {
is_creating_fallback = true;

try {
boundary_effect = branch(() => {
failed(
anchor,
() => error,
() => reset
);
});
} catch (error) {
handle_error(error, boundary, null, boundary.ctx);
}

reset_is_throwing_error();
is_creating_fallback = false;
queue_boundary_micro_task(() => {
render_snippet(() => {
failed(
anchor,
() => error,
() => reset
);
});
});
}
Expand All @@ -132,3 +223,21 @@ export function boundary(node, props, boundary_fn) {
anchor = hydrate_node;
}
}

/**
* @param {Effect | null} effect
* @param {typeof ASYNC_INCREMENT | typeof ASYNC_DECREMENT} trigger
*/
export function trigger_async_boundary(effect, trigger) {
var current = effect;

while (current !== null) {
if ((current.f & BOUNDARY_EFFECT) !== 0) {
// @ts-ignore
if (current.fn(trigger)) {
return;
}
}
current = current.parent;
}
}
4 changes: 2 additions & 2 deletions packages/svelte/src/internal/client/dom/blocks/each.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
import { source, mutable_source, internal_set } from '../../reactivity/sources.js';
import { array_from, is_array } from '../../../shared/utils.js';
import { INERT } from '../../constants.js';
import { queue_micro_task } from '../task.js';
import { queue_post_micro_task } from '../task.js';
import { active_effect, active_reaction, get } from '../../runtime.js';
import { DEV } from 'esm-env';
import { derived_safe_equal } from '../../reactivity/deriveds.js';
Expand Down Expand Up @@ -470,7 +470,7 @@ function reconcile(array, state, anchor, render_fn, flags, is_inert, get_key, ge
}

if (is_animated) {
queue_micro_task(() => {
queue_post_micro_task(() => {
if (to_animate === undefined) return;
for (item of to_animate) {
item.a?.apply();
Expand Down
6 changes: 3 additions & 3 deletions packages/svelte/src/internal/client/dom/css.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { DEV } from 'esm-env';
import { queue_micro_task } from './task.js';
import { queue_post_micro_task } from './task.js';
import { register_style } from '../dev/css.js';

/**
* @param {Node} anchor
* @param {{ hash: string, code: string }} css
*/
export function append_styles(anchor, css) {
// Use `queue_micro_task` to ensure `anchor` is in the DOM, otherwise getRootNode() will yield wrong results
queue_micro_task(() => {
// Use `queue_post_micro_task` to ensure `anchor` is in the DOM, otherwise getRootNode() will yield wrong results
queue_post_micro_task(() => {
var root = anchor.getRootNode();

var target = /** @type {ShadowRoot} */ (root).host
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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 { is } from '../../../proxy.js';
import { queue_micro_task } from '../../task.js';
import { queue_post_micro_task } from '../../task.js';
import { hydrating } from '../../hydration.js';
import { is_runes, untrack } from '../../../runtime.js';

Expand Down Expand Up @@ -158,14 +158,14 @@ export function bind_group(inputs, group_index, input, get, set = get) {
if (!pending.has(binding_group)) {
pending.add(binding_group);

queue_micro_task(() => {
queue_post_micro_task(() => {
// necessary to maintain binding group order in all insertion scenarios
binding_group.sort((a, b) => (a.compareDocumentPosition(b) === 4 ? -1 : 1));
pending.delete(binding_group);
});
}

queue_micro_task(() => {
queue_post_micro_task(() => {
if (hydration_mismatch) {
var value;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { STATE_SYMBOL } from '../../../constants.js';
import { effect, render_effect } from '../../../reactivity/effects.js';
import { untrack } from '../../../runtime.js';
import { queue_micro_task } from '../../task.js';
import { queue_post_micro_task } from '../../task.js';

/**
* @param {any} bound_value
Expand Down Expand Up @@ -49,7 +49,7 @@ export function bind_this(element_or_component = {}, update, get_value, get_part

return () => {
// We cannot use effects in the teardown phase, we we use a microtask instead.
queue_micro_task(() => {
queue_post_micro_task(() => {
if (parts && is_bound_this(get_value(...parts), element_or_component)) {
update(null, ...parts);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/svelte/src/internal/client/dom/elements/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { teardown } from '../../reactivity/effects.js';
import { define_property, is_array } from '../../../shared/utils.js';
import { hydrating } from '../hydration.js';
import { queue_micro_task } from '../task.js';
import { queue_post_micro_task } from '../task.js';
import { FILENAME } from '../../../../constants.js';
import * as w from '../../warnings.js';
import {
Expand Down Expand Up @@ -77,7 +77,7 @@ export function create_event(event_name, dom, handler, options) {
event_name.startsWith('touch') ||
event_name === 'wheel'
) {
queue_micro_task(() => {
queue_post_micro_task(() => {
dom.addEventListener(event_name, target_handler, options);
});
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/svelte/src/internal/client/dom/elements/misc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { hydrating } from '../hydration.js';
import { clear_text_content, get_first_child } from '../operations.js';
import { queue_micro_task } from '../task.js';
import { queue_post_micro_task } from '../task.js';

/**
* @param {HTMLElement} dom
Expand All @@ -12,7 +12,7 @@ export function autofocus(dom, value) {
const body = document.body;
dom.autofocus = true;

queue_micro_task(() => {
queue_post_micro_task(() => {
if (document.activeElement === body) {
dom.focus();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { should_intro } from '../../render.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';
import { queue_post_micro_task } from '../task.js';

/**
* @param {Element} element
Expand Down Expand Up @@ -326,7 +326,7 @@ function animate(element, options, counterpart, t2, on_finish) {
var a;
var aborted = false;

queue_micro_task(() => {
queue_post_micro_task(() => {
if (aborted) return;
var o = options({ direction: is_intro ? 'in' : 'out' });
a = animate(element, o, counterpart, t2, on_finish);
Expand Down
Loading
Loading