mirror of https://github.com/sveltejs/svelte
chore: centralise branch management (#16977)
* WIP * WIP * WIP * WIP * WIP * fix hydration * simplify * all tests passing * key blocks * snippets * fix * tidy up * WIP await * tidy up * fix * neaten up * unused * tweak * elements * changeset * fix * preserve newer batches * add comment * add comment * no longer necessary apparently? * move legacy logic to key blockpull/16984/head
parent
9a488d6b25
commit
b8627e511d
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
chore: centralise branch management
|
||||||
@ -0,0 +1,185 @@
|
|||||||
|
/** @import { Effect, TemplateNode } from '#client' */
|
||||||
|
import { is_runes } from '../../context.js';
|
||||||
|
import { Batch, current_batch } from '../../reactivity/batch.js';
|
||||||
|
import {
|
||||||
|
branch,
|
||||||
|
destroy_effect,
|
||||||
|
move_effect,
|
||||||
|
pause_effect,
|
||||||
|
resume_effect
|
||||||
|
} from '../../reactivity/effects.js';
|
||||||
|
import { set_should_intro, should_intro } from '../../render.js';
|
||||||
|
import { hydrate_node, hydrating } from '../hydration.js';
|
||||||
|
import { create_text, should_defer_append } from '../operations.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{ effect: Effect, fragment: DocumentFragment }} Branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template Key
|
||||||
|
*/
|
||||||
|
export class BranchManager {
|
||||||
|
/** @type {TemplateNode} */
|
||||||
|
anchor;
|
||||||
|
|
||||||
|
/** @type {Map<Batch, Key>} */
|
||||||
|
#batches = new Map();
|
||||||
|
|
||||||
|
/** @type {Map<Key, Effect>} */
|
||||||
|
#onscreen = new Map();
|
||||||
|
|
||||||
|
/** @type {Map<Key, Branch>} */
|
||||||
|
#offscreen = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to pause (i.e. outro) on change, or destroy immediately.
|
||||||
|
* This is necessary for `<svelte:element>`
|
||||||
|
*/
|
||||||
|
#transition = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {TemplateNode} anchor
|
||||||
|
* @param {boolean} transition
|
||||||
|
*/
|
||||||
|
constructor(anchor, transition = true) {
|
||||||
|
this.anchor = anchor;
|
||||||
|
this.#transition = transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
#commit = () => {
|
||||||
|
var batch = /** @type {Batch} */ (current_batch);
|
||||||
|
|
||||||
|
// if this batch was made obsolete, bail
|
||||||
|
if (!this.#batches.has(batch)) return;
|
||||||
|
|
||||||
|
var key = /** @type {Key} */ (this.#batches.get(batch));
|
||||||
|
|
||||||
|
var onscreen = this.#onscreen.get(key);
|
||||||
|
|
||||||
|
if (onscreen) {
|
||||||
|
// effect is already in the DOM — abort any current outro
|
||||||
|
resume_effect(onscreen);
|
||||||
|
} else {
|
||||||
|
// effect is currently offscreen. put it in the DOM
|
||||||
|
var offscreen = this.#offscreen.get(key);
|
||||||
|
|
||||||
|
if (offscreen) {
|
||||||
|
this.#onscreen.set(key, offscreen.effect);
|
||||||
|
this.#offscreen.delete(key);
|
||||||
|
|
||||||
|
// remove the anchor...
|
||||||
|
/** @type {TemplateNode} */ (offscreen.fragment.lastChild).remove();
|
||||||
|
|
||||||
|
// ...and append the fragment
|
||||||
|
this.anchor.before(offscreen.fragment);
|
||||||
|
onscreen = offscreen.effect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [b, k] of this.#batches) {
|
||||||
|
this.#batches.delete(b);
|
||||||
|
|
||||||
|
if (b === batch) {
|
||||||
|
// keep values for newer batches
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offscreen = this.#offscreen.get(k);
|
||||||
|
|
||||||
|
if (offscreen) {
|
||||||
|
// for older batches, destroy offscreen effects
|
||||||
|
// as they will never be committed
|
||||||
|
destroy_effect(offscreen.effect);
|
||||||
|
this.#offscreen.delete(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// outro/destroy all onscreen effects...
|
||||||
|
for (const [k, effect] of this.#onscreen) {
|
||||||
|
// ...except the one that was just committed
|
||||||
|
if (k === key) continue;
|
||||||
|
|
||||||
|
const on_destroy = () => {
|
||||||
|
const keys = Array.from(this.#batches.values());
|
||||||
|
|
||||||
|
if (keys.includes(k)) {
|
||||||
|
// keep the effect offscreen, as another batch will need it
|
||||||
|
var fragment = document.createDocumentFragment();
|
||||||
|
move_effect(effect, fragment);
|
||||||
|
|
||||||
|
fragment.append(create_text()); // TODO can we avoid this?
|
||||||
|
|
||||||
|
this.#offscreen.set(k, { effect, fragment });
|
||||||
|
} else {
|
||||||
|
destroy_effect(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#onscreen.delete(k);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.#transition || !onscreen) {
|
||||||
|
pause_effect(effect, on_destroy, false);
|
||||||
|
} else {
|
||||||
|
on_destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {any} key
|
||||||
|
* @param {null | ((target: TemplateNode) => void)} fn
|
||||||
|
*/
|
||||||
|
ensure(key, fn) {
|
||||||
|
var batch = /** @type {Batch} */ (current_batch);
|
||||||
|
var defer = should_defer_append();
|
||||||
|
|
||||||
|
if (fn && !this.#onscreen.has(key) && !this.#offscreen.has(key)) {
|
||||||
|
if (defer) {
|
||||||
|
var fragment = document.createDocumentFragment();
|
||||||
|
var target = create_text();
|
||||||
|
|
||||||
|
fragment.append(target);
|
||||||
|
|
||||||
|
this.#offscreen.set(key, {
|
||||||
|
effect: branch(() => fn(target)),
|
||||||
|
fragment
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.#onscreen.set(
|
||||||
|
key,
|
||||||
|
branch(() => fn(this.anchor))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#batches.set(batch, key);
|
||||||
|
|
||||||
|
if (defer) {
|
||||||
|
for (const [k, effect] of this.#onscreen) {
|
||||||
|
if (k === key) {
|
||||||
|
batch.skipped_effects.delete(effect);
|
||||||
|
} else {
|
||||||
|
batch.skipped_effects.add(effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [k, branch] of this.#offscreen) {
|
||||||
|
if (k === key) {
|
||||||
|
batch.skipped_effects.delete(branch.effect);
|
||||||
|
} else {
|
||||||
|
batch.skipped_effects.add(branch.effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.add_callback(this.#commit);
|
||||||
|
} else {
|
||||||
|
if (hydrating) {
|
||||||
|
this.anchor = hydrate_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in new issue