Skip to content

Commit df0b094

Browse files
committed
chore: apply each block controlled teardown optimization
remove deopt remove deopt
1 parent 17281c3 commit df0b094

File tree

2 files changed

+50
-33
lines changed

2 files changed

+50
-33
lines changed

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

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ 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';
@@ -39,6 +41,38 @@ export function set_current_each_item(item) {
3941
current_each_item = item;
4042
}
4143

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

147181
if (!hydrating) {
148-
// TODO add 'empty controlled block' optimisation here
149182
reconcile_fn(array, state, anchor, render_fn, flags, keys);
150183
}
151184

@@ -244,7 +277,9 @@ function reconcile_indexed_array(array, state, anchor, render_fn, flags) {
244277
effects.push(a_items[i].e);
245278
}
246279

247-
pause_effects(effects, () => {
280+
var controlled_anchor = (flags & EACH_IS_CONTROLLED) !== 0 && b === 0 ? anchor : null;
281+
282+
pause_effects(effects, controlled_anchor, () => {
248283
state.items.length = b;
249284
});
250285
}
@@ -274,6 +309,7 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
274309

275310
var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
276311
var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0;
312+
var is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
277313
var start = 0;
278314
var item;
279315

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

386428
// working from the back, insert new or moved items
@@ -421,9 +463,9 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
421463
});
422464
}
423465

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, () => {
466+
var controlled_anchor = is_controlled && b === 0 ? anchor : null;
467+
468+
pause_effects(to_destroy, controlled_anchor, () => {
427469
state.items = b_items;
428470
});
429471
}

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)