chore: move DOM-related effect properties to `effect.nodes` (#17293)

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* changeset

* unused
pull/17295/head
Rich Harris 4 days ago committed by GitHub
parent 8dd8e3ec0b
commit 1f3f1826e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: move DOM-related effect properties to `effect.nodes`

@ -110,13 +110,13 @@ export function log_effect_tree(effect, depth = 0) {
console.groupEnd();
}
if (effect.nodes_start && effect.nodes_end) {
if (effect.nodes) {
// eslint-disable-next-line no-console
console.log(effect.nodes_start);
console.log(effect.nodes.start);
if (effect.nodes_start !== effect.nodes_end) {
if (effect.nodes.start !== effect.nodes.end) {
// eslint-disable-next-line no-console
console.log(effect.nodes_end);
console.log(effect.nodes.end);
}
}

@ -1,4 +1,4 @@
/** @import { EachItem, EachState, Effect, MaybeSource, Source, TemplateNode, TransitionManager, Value } from '#client' */
/** @import { EachItem, EachState, Effect, EffectNodes, MaybeSource, Source, TemplateNode, TransitionManager, Value } from '#client' */
/** @import { Batch } from '../../reactivity/batch.js'; */
import {
EACH_INDEX_REACTIVE,
@ -43,18 +43,6 @@ import { DEV } from 'esm-env';
import { derived_safe_equal } from '../../reactivity/deriveds.js';
import { current_batch } from '../../reactivity/batch.js';
/**
* The row of a keyed each block that is currently updating. We track this
* so that `animate:` directives have something to attach themselves to
* @type {EachItem | null}
*/
export let current_each_item = null;
/** @param {EachItem | null} item */
export function set_current_each_item(item) {
current_each_item = item;
}
/**
* @param {any} _
* @param {number} i
@ -395,7 +383,7 @@ function reconcile(state, array, anchor, flags, get_key) {
// offscreen == coming in now, no animation in that case,
// else this would happen https://github.com/sveltejs/svelte/issues/17181
if (item.o) {
item.a?.measure();
item.e.nodes?.a?.measure();
(to_animate ??= new Set()).add(item);
}
}
@ -430,7 +418,7 @@ function reconcile(state, array, anchor, flags, get_key) {
if ((item.e.f & INERT) !== 0) {
resume_effect(item.e);
if (is_animated) {
item.a?.unfix();
item.e.nodes?.a?.unfix();
(to_animate ??= new Set()).delete(item);
}
}
@ -527,11 +515,11 @@ function reconcile(state, array, anchor, flags, get_key) {
if (is_animated) {
for (i = 0; i < destroy_length; i += 1) {
to_destroy[i].a?.measure();
to_destroy[i].e.nodes?.a?.measure();
}
for (i = 0; i < destroy_length; i += 1) {
to_destroy[i].a?.fix();
to_destroy[i].e.nodes?.a?.fix();
}
}
@ -555,7 +543,7 @@ function reconcile(state, array, anchor, flags, get_key) {
queue_micro_task(() => {
if (to_animate === undefined) return;
for (item of to_animate) {
item.a?.apply();
item.e.nodes?.a?.apply();
}
});
}
@ -574,7 +562,6 @@ function reconcile(state, array, anchor, flags, get_key) {
* @returns {EachItem}
*/
function create_item(anchor, prev, value, key, index, render_fn, flags, get_collection) {
var previous_each_item = current_each_item;
var reactive = (flags & EACH_ITEM_REACTIVE) !== 0;
var mutable = (flags & EACH_ITEM_IMMUTABLE) === 0;
@ -596,7 +583,6 @@ function create_item(anchor, prev, value, key, index, render_fn, flags, get_coll
i,
v,
k: key,
a: null,
// @ts-expect-error
e: null,
o: false,
@ -604,27 +590,21 @@ function create_item(anchor, prev, value, key, index, render_fn, flags, get_coll
next: null
};
current_each_item = item;
try {
if (anchor === null) {
var fragment = document.createDocumentFragment();
fragment.append((anchor = create_text()));
}
item.e = branch(() => render_fn(/** @type {Node} */ (anchor), v, i, get_collection));
if (anchor === null) {
var fragment = document.createDocumentFragment();
fragment.append((anchor = create_text()));
}
if (prev !== null) {
// we only need to set `prev.next = item`, because
// `item.prev = prev` was set on initialization.
// the effects themselves are already linked
prev.next = item;
}
item.e = branch(() => render_fn(/** @type {Node} */ (anchor), v, i, get_collection));
return item;
} finally {
current_each_item = previous_each_item;
if (prev !== null) {
// we only need to set `prev.next = item`, because
// `item.prev = prev` was set on initialization.
// the effects themselves are already linked
prev.next = item;
}
return item;
}
/**
@ -633,10 +613,12 @@ function create_item(anchor, prev, value, key, index, render_fn, flags, get_coll
* @param {Text | Element | Comment} anchor
*/
function move(item, next, anchor) {
var end = item.next ? /** @type {TemplateNode} */ (item.next.e.nodes_start) : anchor;
if (!item.e.nodes) return;
var end = item.next ? /** @type {EffectNodes} */ (item.next.e.nodes).start : anchor;
var dest = next ? /** @type {TemplateNode} */ (next.e.nodes_start) : anchor;
var node = /** @type {TemplateNode} */ (item.e.nodes_start);
var dest = next ? /** @type {EffectNodes} */ (next.e.nodes).start : anchor;
var node = /** @type {TemplateNode} */ (item.e.nodes.start);
while (node !== null && node !== end) {
var next_node = /** @type {TemplateNode} */ (get_next_sibling(node));

@ -54,9 +54,9 @@ export function html(node, get_value, svg = false, mathml = false, skip_warning
return;
}
if (effect.nodes_start !== null) {
remove_effect_dom(effect.nodes_start, /** @type {TemplateNode} */ (effect.nodes_end));
effect.nodes_start = effect.nodes_end = null;
if (effect.nodes !== null) {
remove_effect_dom(effect.nodes.start, /** @type {TemplateNode} */ (effect.nodes.end));
effect.nodes = null;
}
if (value === '') return;

@ -1,4 +1,4 @@
/** @import { Effect, TemplateNode } from '#client' */
/** @import { Effect, EffectNodes, TemplateNode } from '#client' */
import { FILENAME, NAMESPACE_SVG } from '../../../../constants.js';
import {
hydrate_next,
@ -10,7 +10,6 @@ import {
import { create_text, get_first_child } from '../operations.js';
import { block, teardown } from '../../reactivity/effects.js';
import { set_should_intro } from '../../render.js';
import { current_each_item, set_current_each_item } from './each.js';
import { active_effect } from '../../runtime.js';
import { component_context, dev_stack } from '../../context.js';
import { DEV } from 'esm-env';
@ -18,6 +17,7 @@ import { EFFECT_TRANSPARENT, ELEMENT_NODE } from '#client/constants';
import { assign_nodes } from '../template.js';
import { is_raw_text_element } from '../../../../utils.js';
import { BranchManager } from './branches.js';
import { set_animation_effect_override } from '../elements/transitions.js';
/**
* @param {Comment | Element} node
@ -48,11 +48,10 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
var anchor = /** @type {TemplateNode} */ (hydrating ? hydrate_node : node);
/**
* The keyed `{#each ...}` item block, if any, that this element is inside.
* We track this so we can set it when changing the element, allowing any
* `animate:` directive to bind itself to the correct block
*/
var each_item_block = current_each_item;
var parent_effect = /** @type {Effect} */ (active_effect);
var branches = new BranchManager(anchor, false);
@ -67,10 +66,6 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
}
branches.ensure(next_tag, (anchor) => {
// See explanation of `each_item_block` above
var previous_each_item = current_each_item;
set_current_each_item(each_item_block);
if (next_tag) {
element = hydrating
? /** @type {Element} */ (element)
@ -112,21 +107,23 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
}
}
set_animation_effect_override(parent_effect);
// `child_anchor` is undefined if this is a void element, but we still
// need to call `render_fn` in order to run actions etc. If the element
// contains children, it's a user error (which is warned on elsewhere)
// and the DOM will be silently discarded
render_fn(element, child_anchor);
set_animation_effect_override(null);
}
// we do this after calling `render_fn` so that child effects don't override `nodes.end`
/** @type {Effect} */ (active_effect).nodes_end = element;
/** @type {Effect & { nodes: EffectNodes }} */ (active_effect).nodes.end = element;
anchor.before(element);
}
set_current_each_item(previous_each_item);
if (hydrating) {
set_hydrate_node(anchor);
}

@ -1,10 +1,9 @@
/** @import { AnimateFn, Animation, AnimationConfig, EachItem, Effect, TransitionFn, TransitionManager } from '#client' */
/** @import { AnimateFn, Animation, AnimationConfig, EachItem, Effect, EffectNodes, TransitionFn, TransitionManager } from '#client' */
import { noop, is_function } from '../../../shared/utils.js';
import { effect } from '../../reactivity/effects.js';
import { active_effect, untrack } from '../../runtime.js';
import { loop } from '../../loop.js';
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 '#client/constants';
import { queue_micro_task } from '../task.js';
@ -66,6 +65,14 @@ function css_to_keyframe(css) {
/** @param {number} t */
const linear = (t) => t;
/** @type {Effect | null} */
let animation_effect_override = null;
/** @param {Effect | null} v */
export function set_animation_effect_override(v) {
animation_effect_override = v;
}
/**
* Called inside keyed `{#each ...}` blocks (as `$.animation(...)`). This creates an animation manager
* and attaches it to the block, so that moves can be animated following reconciliation.
@ -75,7 +82,8 @@ const linear = (t) => t;
* @param {(() => P) | null} get_params
*/
export function animation(element, get_fn, get_params) {
var item = /** @type {EachItem} */ (current_each_item);
var effect = animation_effect_override ?? /** @type {Effect} */ (active_effect);
var nodes = /** @type {EffectNodes} */ (effect.nodes);
/** @type {DOMRect} */
var from;
@ -89,7 +97,7 @@ export function animation(element, get_fn, get_params) {
/** @type {null | { position: string, width: string, height: string, transform: string }} */
var original_styles = null;
item.a ??= {
nodes.a ??= {
element,
measure() {
from = this.element.getBoundingClientRect();
@ -161,7 +169,7 @@ export function animation(element, get_fn, get_params) {
// when an animation manager already exists, if the tag changes. in that case, we need to
// swap out the element rather than creating a new manager, in case it happened at the same
// moment as a reconciliation
item.a.element = element;
nodes.a.element = element;
}
/**
@ -265,9 +273,9 @@ export function transition(flags, element, get_fn, get_params) {
}
};
var e = /** @type {Effect} */ (active_effect);
var e = /** @type {Effect & { nodes: EffectNodes }} */ (active_effect);
(e.transitions ??= []).push(transition);
(e.nodes.t ??= []).push(transition);
// if this is a local transition, we only want to run it if the parent (branch) effect's
// parent (block) effect is where the state change happened. we can determine that by

@ -1,4 +1,4 @@
/** @import { Effect, TemplateNode } from '#client' */
/** @import { Effect, EffectNodes, TemplateNode } from '#client' */
/** @import { TemplateStructure } from './types' */
import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
import {
@ -28,9 +28,8 @@ import { COMMENT_NODE, DOCUMENT_FRAGMENT_NODE, EFFECT_RAN, TEXT_NODE } from '#cl
*/
export function assign_nodes(start, end) {
var effect = /** @type {Effect} */ (active_effect);
if (effect.nodes_start === null) {
effect.nodes_start = start;
effect.nodes_end = end;
if (effect.nodes === null) {
effect.nodes = { start, end, a: null, t: null };
}
}
@ -270,7 +269,8 @@ function run_scripts(node) {
/** @type {HTMLElement} */ (node).tagName === 'SCRIPT'
? [/** @type {HTMLScriptElement} */ (node)]
: node.querySelectorAll('script');
const effect = /** @type {Effect} */ (active_effect);
const effect = /** @type {Effect & { nodes: EffectNodes }} */ (active_effect);
for (const script of scripts) {
const clone = document.createElement('script');
@ -282,10 +282,10 @@ function run_scripts(node) {
// The script has changed - if it's at the edges, the effect now points at dead nodes
if (is_fragment ? node.firstChild === script : node === script) {
effect.nodes_start = clone;
effect.nodes.start = clone;
}
if (is_fragment ? node.lastChild === script : node === script) {
effect.nodes_end = clone;
effect.nodes.end = clone;
}
script.replaceWith(clone);
@ -344,13 +344,15 @@ export function comment() {
*/
export function append(anchor, dom) {
if (hydrating) {
var effect = /** @type {Effect} */ (active_effect);
var effect = /** @type {Effect & { nodes: EffectNodes }} */ (active_effect);
// When hydrating and outer component and an inner component is async, i.e. blocked on a promise,
// then by the time the inner resolves we have already advanced to the end of the hydrated nodes
// of the parent component. Check for defined for that reason to avoid rewinding the parent's end marker.
if ((effect.f & EFFECT_RAN) === 0 || effect.nodes_end === null) {
effect.nodes_end = hydrate_node;
if ((effect.f & EFFECT_RAN) === 0 || effect.nodes.end === null) {
effect.nodes.end = hydrate_node;
}
hydrate_next();
return;
}

@ -26,7 +26,6 @@ import {
} from './deriveds.js';
import { aborted } from './effects.js';
import { hydrate_next, hydrating, set_hydrate_node, skip_nodes } from '../dom/hydration.js';
import { current_each_item, set_current_each_item } from '../dom/blocks/each.js';
/**
* @param {Array<Promise<void>>} blockers
@ -90,11 +89,7 @@ export function flatten(blockers, sync, async, fn) {
* @param {(values: Value[]) => any} fn
*/
export function run_after_blockers(blockers, fn) {
var each_item = current_each_item; // TODO should this be part of capture?
flatten(blockers, [], [], (v) => {
set_current_each_item(each_item);
fn(v);
});
flatten(blockers, [], [], fn);
}
/**

@ -708,7 +708,7 @@ function flush_queued_effects(effects) {
// don't know if we need to keep them until they are executed. Doing the check
// here (rather than in `update_effect`) allows us to skip the work for
// immediate effects.
if (effect.deps === null && effect.first === null && effect.nodes_start === null) {
if (effect.deps === null && effect.first === null && effect.nodes === null) {
// if there's no teardown or abort controller we completely unlink
// the effect from the graph
if (effect.teardown === null && effect.ac === null) {

@ -101,8 +101,7 @@ function create_effect(type, fn, sync) {
var effect = {
ctx: component_context,
deps: null,
nodes_start: null,
nodes_end: null,
nodes: null,
f: type | DIRTY | CONNECTED,
first: null,
fn,
@ -112,7 +111,6 @@ function create_effect(type, fn, sync) {
b: parent && parent.b,
prev: null,
teardown: null,
transitions: null,
wv: 0,
ac: null
};
@ -143,7 +141,7 @@ function create_effect(type, fn, sync) {
sync &&
e.deps === null &&
e.teardown === null &&
e.nodes_start === null &&
e.nodes === null &&
e.first === e.last && // either `null`, or a singular child
(e.f & EFFECT_PRESERVED) === 0
) {
@ -497,10 +495,10 @@ export function destroy_effect(effect, remove_dom = true) {
if (
(remove_dom || (effect.f & HEAD_EFFECT) !== 0) &&
effect.nodes_start !== null &&
effect.nodes_end !== null
effect.nodes !== null &&
effect.nodes.end !== null
) {
remove_effect_dom(effect.nodes_start, /** @type {TemplateNode} */ (effect.nodes_end));
remove_effect_dom(effect.nodes.start, /** @type {TemplateNode} */ (effect.nodes.end));
removed = true;
}
@ -508,7 +506,7 @@ export function destroy_effect(effect, remove_dom = true) {
remove_reactions(effect, 0);
set_signal_status(effect, DESTROYED);
var transitions = effect.transitions;
var transitions = effect.nodes && effect.nodes.t;
if (transitions !== null) {
for (const transition of transitions) {
@ -537,8 +535,7 @@ export function destroy_effect(effect, remove_dom = true) {
effect.ctx =
effect.deps =
effect.fn =
effect.nodes_start =
effect.nodes_end =
effect.nodes =
effect.ac =
null;
}
@ -624,8 +621,10 @@ export function pause_children(effect, transitions, local) {
if ((effect.f & INERT) !== 0) return;
effect.f ^= INERT;
if (effect.transitions !== null) {
for (const transition of effect.transitions) {
var t = effect.nodes && effect.nodes.t;
if (t !== null) {
for (const transition of t) {
if (transition.is_global || local) {
transitions.push(transition);
}
@ -688,8 +687,10 @@ function resume_children(effect, local) {
child = sibling;
}
if (effect.transitions !== null) {
for (const transition of effect.transitions) {
var t = effect.nodes && effect.nodes.t;
if (t !== null) {
for (const transition of t) {
if (transition.is_global || local) {
transition.in();
}
@ -706,8 +707,11 @@ export function aborted(effect = /** @type {Effect} */ (active_effect)) {
* @param {DocumentFragment} fragment
*/
export function move_effect(effect, fragment) {
var node = effect.nodes_start;
var end = effect.nodes_end;
if (!effect.nodes) return;
/** @type {TemplateNode | null} */
var node = effect.nodes.start;
var end = effect.nodes.end;
while (node !== null) {
/** @type {TemplateNode | null} */

@ -1,4 +1,5 @@
import type {
AnimationManager,
ComponentContext,
DevStackEntry,
Equals,
@ -60,6 +61,15 @@ export interface Derived<V = unknown> extends Value<V>, Reaction {
parent: Effect | Derived | null;
}
export interface EffectNodes {
start: TemplateNode;
end: TemplateNode | null;
/** $.animation */
a: AnimationManager | null;
/** $.transition */
t: TransitionManager[] | null;
}
export interface Effect extends Reaction {
/**
* Branch effects store their start/end nodes so that they can be
@ -67,14 +77,11 @@ export interface Effect extends Reaction {
* block is reconciled. In the case of a single text/element node,
* `start` and `end` will be the same.
*/
nodes_start: null | TemplateNode;
nodes_end: null | TemplateNode;
nodes: null | EffectNodes;
/** The effect function */
fn: null | (() => void | (() => void));
/** The teardown function returned from the effect function */
teardown: null | (() => void);
/** Transition managers created with `$.transition` */
transitions: null | TransitionManager[];
/** Next sibling child effect created inside the parent signal */
prev: null | Effect;
/** Next sibling child effect created inside the parent signal */

@ -1,4 +1,4 @@
/** @import { ComponentContext, Effect, TemplateNode } from '#client' */
/** @import { ComponentContext, Effect, EffectNodes, TemplateNode } from '#client' */
/** @import { Component, ComponentType, SvelteComponent, MountOptions } from '../../index.js' */
import { DEV } from 'esm-env';
import {
@ -228,7 +228,7 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
should_intro = true;
if (hydrating) {
/** @type {Effect} */ (active_effect).nodes_end = hydrate_node;
/** @type {Effect & { nodes: EffectNodes }} */ (active_effect).nodes.end = hydrate_node;
if (
hydrate_node === null ||

@ -84,8 +84,6 @@ export type EachState = {
};
export type EachItem = {
/** animation manager */
a: AnimationManager | null;
/** effect */
e: Effect;
/** item */

Loading…
Cancel
Save