Skip to content

Commit 28c301a

Browse files
committed
chore: apply each block controlled teardown optimization
1 parent 17281c3 commit 28c301a

File tree

2 files changed

+51
-33
lines changed

2 files changed

+51
-33
lines changed

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

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ import { untrack } from '../../runtime.js';
1515
import {
1616
block,
1717
branch,
18+
destroy_effect,
1819
effect,
20+
out,
21+
pause_children,
1922
pause_effect,
20-
pause_effects,
2123
resume_effect
2224
} from '../../reactivity/effects.js';
2325
import { source, mutable_source, set } from '../../reactivity/sources.js';
2426
import { is_array, is_frozen, map_get, map_set } from '../../utils.js';
2527
import { STATE_SYMBOL } from '../../constants.js';
28+
import { noop } from '../../../shared/utils.js';
2629

2730
var NEW_ITEM = -1;
2831
var LIS_ITEM = -2;
@@ -39,6 +42,38 @@ export function set_current_each_item(item) {
3942
current_each_item = item;
4043
}
4144

45+
/**
46+
* Pause multiple effects simultaneously, and coordinate their
47+
* subsequent destruction. Used in each blocks
48+
* @param {import('#client').Effect[]} effects
49+
* @param {null | Node} controlled_anchor
50+
* @param {() => void} callback
51+
*/
52+
function pause_effects(effects, controlled_anchor, callback = noop) {
53+
/** @type {import('#client').TransitionManager[]} */
54+
var transitions = [];
55+
var length = effects.length;
56+
57+
for (var i = 0; i < length; i++) {
58+
pause_children(effects[i], transitions, true);
59+
}
60+
61+
// If we have a controlled anchor, it means that the each block is inside a single
62+
// DOM element, so we can apply a fast-path for clearing the contents of the element.
63+
if (effects.length > 0 && transitions.length === 0 && controlled_anchor !== null) {
64+
const each_element = /** @type {Element} */ (controlled_anchor.parentNode);
65+
each_element.textContent = '';
66+
each_element.append(controlled_anchor);
67+
}
68+
69+
out(transitions, () => {
70+
for (var i = 0; i < length; i++) {
71+
destroy_effect(effects[i]);
72+
}
73+
callback();
74+
});
75+
}
76+
4277
/**
4378
* @template V
4479
* @param {Element | Comment} anchor The next sibling node, or the parent node if this is a 'controlled' block
@@ -145,7 +180,6 @@ function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, re
145180
}
146181

147182
if (!hydrating) {
148-
// TODO add 'empty controlled block' optimisation here
149183
reconcile_fn(array, state, anchor, render_fn, flags, keys);
150184
}
151185

@@ -244,7 +278,9 @@ function reconcile_indexed_array(array, state, anchor, render_fn, flags) {
244278
effects.push(a_items[i].e);
245279
}
246280

247-
pause_effects(effects, () => {
281+
var controlled_anchor = (flags & EACH_IS_CONTROLLED) !== 0 && b === 0 ? anchor : null;
282+
283+
pause_effects(effects, controlled_anchor, () => {
248284
state.items.length = b;
249285
});
250286
}
@@ -274,6 +310,7 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
274310

275311
var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
276312
var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0;
313+
var is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
277314
var start = 0;
278315
var item;
279316

@@ -381,6 +418,12 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
381418
// I fully understand this part)
382419
if (moved) {
383420
mark_lis(sources);
421+
} else if (is_controlled && to_destroy.length === a_items.length) {
422+
// We can optimize the case where we change all items of the each block to an entirely new set of items.
423+
// In this case we can first clear the DOM fast, along with all their effect, then we can continue
424+
// with the normal logic that appends them.
425+
pause_effects(to_destroy, anchor);
426+
to_destroy = [];
384427
}
385428

386429
// working from the back, insert new or moved items
@@ -421,9 +464,9 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
421464
});
422465
}
423466

424-
// TODO: would be good to avoid this closure in the case where we have no
425-
// transitions at all. It would make it far more JIT friendly in the hot cases.
426-
pause_effects(to_destroy, () => {
467+
var controlled_anchor = is_controlled && b === 0 ? anchor : null;
468+
469+
pause_effects(to_destroy, controlled_anchor, () => {
427470
state.items = b_items;
428471
});
429472
}

packages/svelte/src/internal/client/reactivity/effects.js

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -309,36 +309,11 @@ export function pause_effect(effect, callback = noop) {
309309
});
310310
}
311311

312-
/**
313-
* Pause multiple effects simultaneously, and coordinate their
314-
* subsequent destruction. Used in each blocks
315-
* @param {import('#client').Effect[]} effects
316-
* @param {() => void} callback
317-
*/
318-
export function pause_effects(effects, callback = noop) {
319-
/** @type {import('#client').TransitionManager[]} */
320-
var transitions = [];
321-
var length = effects.length;
322-
323-
for (var i = 0; i < length; i++) {
324-
pause_children(effects[i], transitions, true);
325-
}
326-
327-
// TODO: would be good to avoid this closure in the case where we have no
328-
// transitions at all. It would make it far more JIT friendly in the hot cases.
329-
out(transitions, () => {
330-
for (var i = 0; i < length; i++) {
331-
destroy_effect(effects[i]);
332-
}
333-
callback();
334-
});
335-
}
336-
337312
/**
338313
* @param {import('#client').TransitionManager[]} transitions
339314
* @param {() => void} fn
340315
*/
341-
function out(transitions, fn) {
316+
export function out(transitions, fn) {
342317
var remaining = transitions.length;
343318
if (remaining > 0) {
344319
var check = () => --remaining || fn();
@@ -355,7 +330,7 @@ function out(transitions, fn) {
355330
* @param {import('#client').TransitionManager[]} transitions
356331
* @param {boolean} local
357332
*/
358-
function pause_children(effect, transitions, local) {
333+
export function pause_children(effect, transitions, local) {
359334
if ((effect.f & INERT) !== 0) return;
360335
effect.f ^= INERT;
361336

0 commit comments

Comments
 (0)