chore: remove blocks (#10880)

* attach DOM to effects

* null out effect.dom

* remove some block.d references

* another

* drive-by fix

* better comment

* unused arg

* another

* another

* another

* more

* finish renaming stuff

* more

* remove item.d

* remove block.d

* remove effect.block

* remove current_block

* delete delete delete

* rename

* remove some stuff we dont need

* simplify
pull/10882/head
Rich Harris 1 year ago committed by GitHub
parent c47c5713e2
commit b6c7956b26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -9,8 +9,7 @@ import {
set_current_reaction
} from '../../runtime.js';
import { destroy_effect, pause_effect, render_effect } from '../../reactivity/effects.js';
import { DESTROYED, INERT } from '../../constants.js';
import { create_block } from './utils.js';
import { INERT } from '../../constants.js';
/**
* @template V
@ -22,8 +21,6 @@ import { create_block } from './utils.js';
* @returns {void}
*/
export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
const block = create_block();
const component_context = current_component_context;
hydrate_block_anchor(anchor);
@ -48,7 +45,7 @@ export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
set_current_effect(branch);
set_current_reaction(branch); // TODO do we need both?
set_current_component_context(component_context);
var effect = render_effect(() => fn(anchor, value), {}, true);
var effect = render_effect(() => fn(anchor, value), true);
set_current_component_context(null);
set_current_reaction(null);
set_current_effect(null);
@ -60,18 +57,6 @@ export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
return effect;
}
/** @param {import('#client').Effect} effect */
function pause(effect) {
if ((effect.f & DESTROYED) !== 0) return;
const block = effect.block;
pause_effect(effect, () => {
// TODO make this unnecessary
const dom = block?.d;
if (dom) remove(dom);
});
}
const branch = render_effect(() => {
if (input === (input = get_input())) return;
@ -80,20 +65,20 @@ export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
if (pending_fn) {
if (pending_effect && (pending_effect.f & INERT) === 0) {
if (pending_effect.block?.d) remove(pending_effect.block.d);
if (pending_effect.dom) remove(pending_effect.dom);
destroy_effect(pending_effect);
}
pending_effect = render_effect(() => pending_fn(anchor), {}, true);
pending_effect = render_effect(() => pending_fn(anchor), true);
}
if (then_effect) pause(then_effect);
if (catch_effect) pause(catch_effect);
if (then_effect) pause_effect(then_effect);
if (catch_effect) pause_effect(catch_effect);
promise.then(
(value) => {
if (promise !== input) return;
if (pending_effect) pause(pending_effect);
if (pending_effect) pause_effect(pending_effect);
if (then_fn) {
then_effect = create_effect(then_fn, value);
@ -101,7 +86,7 @@ export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
},
(error) => {
if (promise !== input) return;
if (pending_effect) pause(pending_effect);
if (pending_effect) pause_effect(pending_effect);
if (catch_fn) {
catch_effect = create_effect(catch_fn, error);
@ -109,24 +94,24 @@ export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) {
}
);
} else {
if (pending_effect) pause(pending_effect);
if (catch_effect) pause(catch_effect);
if (pending_effect) pause_effect(pending_effect);
if (catch_effect) pause_effect(catch_effect);
if (then_fn) {
if (then_effect) {
if (then_effect.block?.d) remove(then_effect.block.d);
if (then_effect.dom) remove(then_effect.dom);
destroy_effect(then_effect);
}
then_effect = render_effect(() => then_fn(anchor, input), {}, true);
then_effect = render_effect(() => then_fn(anchor, input), true);
}
}
}, block);
});
branch.ondestroy = () => {
// TODO this sucks, tidy it up
if (pending_effect?.block?.d) remove(pending_effect.block.d);
if (then_effect?.block?.d) remove(then_effect.block.d);
if (catch_effect?.block?.d) remove(catch_effect.block.d);
if (pending_effect?.dom) remove(pending_effect.dom);
if (then_effect?.dom) remove(then_effect.dom);
if (catch_effect?.dom) remove(catch_effect.dom);
};
}

@ -19,6 +19,7 @@ import { untrack } from '../../runtime.js';
import {
destroy_effect,
pause_effect,
pause_effects,
render_effect,
resume_effect,
user_effect
@ -26,21 +27,20 @@ import {
import { source, mutable_source, set } from '../../reactivity/sources.js';
import { is_array, is_frozen, map_get, map_set } from '../../utils.js';
import { STATE_SYMBOL } from '../../constants.js';
import { create_block } from './utils.js';
var NEW_BLOCK = -1;
var LIS_BLOCK = -2;
var NEW_ITEM = -1;
var LIS_ITEM = -2;
/**
* 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 {import('#client').EachItem | null}
*/
export let current_each_item_block = null;
export let current_each_item = null;
/** @param {import('#client').EachItem | null} block */
export function set_current_each_item_block(block) {
current_each_item_block = block;
/** @param {import('#client').EachItem | null} item */
export function set_current_each_item(item) {
current_each_item = item;
}
/**
@ -55,8 +55,6 @@ export function set_current_each_item_block(block) {
* @returns {void}
*/
function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, reconcile_fn) {
var block = create_block();
/** @type {import('#client').EachState} */
var state = { flags, items: [] };
@ -71,132 +69,122 @@ function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, re
/** @type {import('#client').Effect | null} */
var fallback = null;
var effect = render_effect(
() => {
var collection = get_collection();
var effect = render_effect(() => {
var collection = get_collection();
var array = is_array(collection)
? collection
: collection == null
? []
: Array.from(collection);
var array = is_array(collection)
? collection
: collection == null
? []
: Array.from(collection);
var keys = get_key === null ? array : array.map(get_key);
var keys = get_key === null ? array : array.map(get_key);
var length = array.length;
var length = array.length;
// If we are working with an array that isn't proxied or frozen, then remove strict equality and ensure the items
// are treated as reactive, so they get wrapped in a signal.
var flags = state.flags;
if ((flags & EACH_IS_STRICT_EQUALS) !== 0 && !is_frozen(array) && !(STATE_SYMBOL in array)) {
flags ^= EACH_IS_STRICT_EQUALS;
// If we are working with an array that isn't proxied or frozen, then remove strict equality and ensure the items
// are treated as reactive, so they get wrapped in a signal.
var flags = state.flags;
if ((flags & EACH_IS_STRICT_EQUALS) !== 0 && !is_frozen(array) && !(STATE_SYMBOL in array)) {
flags ^= EACH_IS_STRICT_EQUALS;
// Additionally if we're in an keyed each block, we'll need ensure the items are all wrapped in signals.
if ((flags & EACH_KEYED) !== 0 && (flags & EACH_ITEM_REACTIVE) === 0) {
flags ^= EACH_ITEM_REACTIVE;
}
// Additionally if we're in an keyed each block, we'll need ensure the items are all wrapped in signals.
if ((flags & EACH_KEYED) !== 0 && (flags & EACH_ITEM_REACTIVE) === 0) {
flags ^= EACH_ITEM_REACTIVE;
}
}
/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false;
if (hydrating) {
var is_else =
/** @type {Comment} */ (current_hydration_fragment?.[0])?.data === 'ssr:each_else';
/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false;
if (hydrating) {
var is_else =
/** @type {Comment} */ (current_hydration_fragment?.[0])?.data === 'ssr:each_else';
if (is_else !== (length === 0)) {
// hydration mismatch — remove the server-rendered DOM and start over
remove(current_hydration_fragment);
set_current_hydration_fragment(null);
mismatch = true;
} else if (is_else) {
// Remove the each_else comment node or else it will confuse the subsequent hydration algorithm
/** @type {import('#client').TemplateNode[]} */ (current_hydration_fragment).shift();
}
}
if (is_else !== (length === 0)) {
// hydration mismatch — remove the server-rendered DOM and start over
remove(current_hydration_fragment);
set_current_hydration_fragment(null);
// this is separate to the previous block because `hydrating` might change
if (hydrating) {
var b_items = [];
// Hydrate block
var hydration_list = /** @type {import('#client').TemplateNode[]} */ (
current_hydration_fragment
);
var hydrating_node = hydration_list[0];
for (var i = 0; i < length; i++) {
var fragment = get_hydration_fragment(hydrating_node);
set_current_hydration_fragment(fragment);
if (!fragment) {
// If fragment is null, then that means that the server rendered less items than what
// the client code specifies -> break out and continue with client-side node creation
mismatch = true;
} else if (is_else) {
// Remove the each_else comment node or else it will confuse the subsequent hydration algorithm
/** @type {import('#client').TemplateNode[]} */ (current_hydration_fragment).shift();
break;
}
}
// this is separate to the previous block because `hydrating` might change
if (hydrating) {
var b_blocks = [];
b_items[i] = create_item(array[i], keys?.[i], i, render_fn, flags);
// Hydrate block
var hydration_list = /** @type {import('#client').TemplateNode[]} */ (
current_hydration_fragment
// TODO helperise this
hydrating_node = /** @type {import('#client').TemplateNode} */ (
/** @type {Node} */ (
/** @type {Node} */ (fragment[fragment.length - 1] || hydrating_node).nextSibling
).nextSibling
);
var hydrating_node = hydration_list[0];
for (var i = 0; i < length; i++) {
var fragment = get_hydration_fragment(hydrating_node);
set_current_hydration_fragment(fragment);
if (!fragment) {
// If fragment is null, then that means that the server rendered less items than what
// the client code specifies -> break out and continue with client-side node creation
mismatch = true;
break;
}
b_blocks[i] = create_item(array[i], keys?.[i], i, render_fn, flags);
// TODO helperise this
hydrating_node = /** @type {import('#client').TemplateNode} */ (
/** @type {Node} */ (
/** @type {Node} */ (fragment[fragment.length - 1] || hydrating_node).nextSibling
).nextSibling
);
}
}
remove_excess_hydration_nodes(hydration_list, hydrating_node);
remove_excess_hydration_nodes(hydration_list, hydrating_node);
state.items = b_blocks;
}
state.items = b_items;
}
if (!hydrating) {
// TODO add 'empty controlled block' optimisation here
reconcile_fn(array, state, anchor, render_fn, flags, keys);
}
if (!hydrating) {
// TODO add 'empty controlled block' optimisation here
reconcile_fn(array, state, anchor, render_fn, flags, keys);
}
if (fallback_fn !== null) {
if (length === 0) {
if (fallback) {
resume_effect(fallback);
} else {
fallback = render_effect(
() => {
fallback_fn(anchor);
var dom = block.d; // TODO would be nice if this was just returned from the managed effect function...
return () => {
if (dom !== null) {
remove(dom);
dom = null;
}
};
},
block,
true
);
}
} else if (fallback !== null) {
pause_effect(fallback, () => {
fallback = null;
});
if (fallback_fn !== null) {
if (length === 0) {
if (fallback) {
resume_effect(fallback);
} else {
fallback = render_effect(() => {
var dom = fallback_fn(anchor);
return () => {
if (dom !== undefined) {
remove(dom);
}
};
}, true);
}
} else if (fallback !== null) {
pause_effect(fallback, () => {
fallback = null;
});
}
}
if (mismatch) {
// Set a fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
},
block,
false
);
if (mismatch) {
// Set a fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
});
effect.ondestroy = () => {
for (var item of state.items) {
if (item.d !== null) {
if (item.e.dom !== null) {
remove(item.e.dom);
destroy_effect(item.e);
remove(item.d);
}
}
@ -274,26 +262,14 @@ function reconcile_indexed_array(array, state, anchor, render_fn, flags) {
state.items = b_items;
} else if (a > b) {
// remove items
var remaining = a - b;
var clear = () => {
for (var i = b; i < a; i += 1) {
var block = a_items[i];
if (block.d) remove(block.d);
}
var effects = [];
for (i = b; i < a; i += 1) {
effects.push(a_items[i].e);
}
pause_effects(effects, () => {
state.items.length = b;
};
var check = () => {
if (--remaining === 0) {
clear();
}
};
for (; i < a; i += 1) {
pause_effect(a_items[i].e, check);
}
});
}
}
@ -311,42 +287,42 @@ function reconcile_indexed_array(array, state, anchor, render_fn, flags) {
* @returns {void}
*/
function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
var a_blocks = state.items;
var a_items = state.items;
var a = a_blocks.length;
var a = a_items.length;
var b = array.length;
/** @type {Array<import('#client').EachItem>} */
var b_blocks = Array(b);
var b_items = Array(b);
var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0;
var start = 0;
var block;
var item;
/** @type {Array<import('#client').EachItem>} */
/** @type {import('#client').Effect[]} */
var to_destroy = [];
// Step 1 — trim common suffix
while (a > 0 && b > 0 && a_blocks[a - 1].k === keys[b - 1]) {
block = b_blocks[--b] = a_blocks[--a];
anchor = get_first_child(block);
while (a > 0 && b > 0 && a_items[a - 1].k === keys[b - 1]) {
item = b_items[--b] = a_items[--a];
anchor = get_first_child(item);
resume_effect(block.e);
resume_effect(item.e);
if (should_update) {
update_item(block, array[b], b, flags);
update_item(item, array[b], b, flags);
}
}
// Step 2 — trim common prefix
while (start < a && start < b && a_blocks[start].k === keys[start]) {
block = b_blocks[start] = a_blocks[start];
while (start < a && start < b && a_items[start].k === keys[start]) {
item = b_items[start] = a_items[start];
resume_effect(block.e);
resume_effect(item.e);
if (should_update) {
update_item(block, array[start], start, flags);
update_item(item, array[start], start, flags);
}
start += 1;
@ -356,14 +332,14 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
if (start === a) {
// add only
while (start < b) {
block = create_item(array[start], keys[start], start, render_fn, flags);
b_blocks[start++] = block;
insert_item(block, anchor);
item = create_item(array[start], keys[start], start, render_fn, flags);
b_items[start++] = item;
insert_item(item, anchor);
}
} else if (start === b) {
// remove only
while (start < a) {
to_destroy.push(a_blocks[start++]);
to_destroy.push(a_items[start++].e);
}
} else {
// reconcile
@ -372,78 +348,78 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
var indexes = new Map();
var i;
var index;
var last_block;
var last_item;
var last_sibling;
// store the indexes of each block in the new world
// store the indexes of each item in the new world
for (i = start; i < b; i += 1) {
sources[i - start] = NEW_BLOCK;
sources[i - start] = NEW_ITEM;
map_set(indexes, keys[i], i);
}
/** @type {Array<import('#client').EachItem>} */
var to_animate = [];
if (is_animated) {
// for all blocks that were in both the old and the new list,
// for all items that were in both the old and the new list,
// measure them and store them in `to_animate` so we can
// apply animations once the DOM has been updated
for (i = 0; i < a_blocks.length; i += 1) {
block = a_blocks[i];
if (indexes.has(block.k)) {
block.a?.measure();
to_animate.push(block);
for (i = 0; i < a_items.length; i += 1) {
item = a_items[i];
if (indexes.has(item.k)) {
item.a?.measure();
to_animate.push(item);
}
}
}
// populate the `sources` array for each old block with
// populate the `sources` array for each old item with
// its new index, so that we can calculate moves
for (i = start; i < a; i += 1) {
block = a_blocks[i];
index = map_get(indexes, block.k);
item = a_items[i];
index = map_get(indexes, item.k);
resume_effect(block.e);
resume_effect(item.e);
if (index === undefined) {
to_destroy.push(block);
to_destroy.push(item.e);
} else {
moved = true;
sources[index - start] = i;
b_blocks[index] = block;
b_items[index] = item;
if (is_animated) {
to_animate.push(block);
to_animate.push(item);
}
}
}
// if we need to move blocks (as opposed to just adding/removing),
// if we need to move items (as opposed to just adding/removing),
// figure out how to do so efficiently (I would be lying if I said
// I fully understand this part)
if (moved) {
mark_lis(sources);
}
// working from the back, insert new or moved blocks
// working from the back, insert new or moved items
while (b-- > start) {
index = sources[b - start];
var insert = index === NEW_BLOCK;
var insert = index === NEW_ITEM;
if (insert) {
block = create_item(array[b], keys[b], b, render_fn, flags);
item = create_item(array[b], keys[b], b, render_fn, flags);
} else {
block = b_blocks[b];
item = b_items[b];
if (should_update) {
update_item(block, array[b], b, flags);
update_item(item, array[b], b, flags);
}
}
if (insert || (moved && index !== LIS_BLOCK)) {
last_sibling = last_block === undefined ? anchor : get_first_child(last_block);
anchor = insert_item(block, last_sibling);
if (insert || (moved && index !== LIS_ITEM)) {
last_sibling = last_item === undefined ? anchor : get_first_child(last_item);
anchor = insert_item(item, last_sibling);
}
last_block = b_blocks[b] = block;
last_item = b_items[b] = item;
}
if (to_animate.length > 0) {
@ -453,36 +429,17 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
// - https://svelte.dev/repl/6e891305e9644a7ca7065fa95c79d2d2?version=4.2.9
user_effect(() => {
untrack(() => {
for (block of to_animate) {
block.a?.apply();
for (item of to_animate) {
item.a?.apply();
}
});
});
}
}
var remaining = to_destroy.length;
if (remaining > 0) {
var clear = () => {
for (block of to_destroy) {
if (block.d) remove(block.d);
}
state.items = b_blocks;
};
var check = () => {
if (--remaining === 0) {
clear();
}
};
for (block of to_destroy) {
pause_effect(block.e, check);
}
} else {
state.items = b_blocks;
}
pause_effects(to_destroy, () => {
state.items = b_items;
});
}
/**
@ -524,7 +481,7 @@ function mark_lis(a) {
var hi;
// Skip -1 values at the start of the input array `a`.
for (; a[i] === NEW_BLOCK; ++i) {
for (; a[i] === NEW_ITEM; ++i) {
/**/
}
@ -533,7 +490,7 @@ function mark_lis(a) {
for (; i < length; ++i) {
k = a[i];
if (k !== NEW_BLOCK) {
if (k !== NEW_ITEM) {
// Ignore -1 values.
j = index[index_length];
@ -567,27 +524,27 @@ function mark_lis(a) {
j = index[index_length];
while (index_length-- >= 0) {
a[j] = LIS_BLOCK;
a[j] = LIS_ITEM;
j = parent[j];
}
}
/**
* @param {import('#client').EachItem} block
* @param {Text | Element | Comment} sibling
* @param {import('#client').EachItem} item
* @param {Text | Element | Comment} anchor
* @returns {Text | Element | Comment}
*/
function insert_item(block, sibling) {
var current = /** @type {import('#client').TemplateNode} */ (block.d);
return insert(current, sibling);
function insert_item(item, anchor) {
var current = /** @type {import('#client').Dom} */ (item.e.dom);
return insert(current, anchor);
}
/**
* @param {import('#client').EachItem} block
* @param {import('#client').EachItem} item
* @returns {Text | Element | Comment}
*/
function get_first_child(block) {
var current = block.d;
function get_first_child(item) {
var current = item.e.dom;
if (is_array(current)) {
return /** @type {Text | Element | Comment} */ (current[0]);
@ -597,48 +554,40 @@ function get_first_child(block) {
}
/**
* @param {import('#client').EachItem} block
* @param {any} item
* @param {import('#client').EachItem} item
* @param {any} value
* @param {number} index
* @param {number} type
* @returns {void}
*/
function update_item(block, item, index, type) {
function update_item(item, value, index, type) {
if ((type & EACH_ITEM_REACTIVE) !== 0) {
set(block.v, item);
set(item.v, value);
}
if ((type & EACH_INDEX_REACTIVE) !== 0) {
set(/** @type {import('#client').Value<number>} */ (block.i), index);
set(/** @type {import('#client').Value<number>} */ (item.i), index);
} else {
block.i = index;
item.i = index;
}
}
/**
* @template V
* @param {V} item
* @param {V} value
* @param {unknown} key
* @param {number} index
* @param {(anchor: null, item: V, index: number | import('#client').Value<number>) => void} render_fn
* @param {number} flags
* @returns {import('#client').EachItem}
*/
function create_item(item, key, index, render_fn, flags) {
function create_item(value, key, index, render_fn, flags) {
var each_item_not_reactive = (flags & EACH_ITEM_REACTIVE) === 0;
var item_value = each_item_not_reactive
? item
: (flags & EACH_IS_STRICT_EQUALS) !== 0
? source(item)
: mutable_source(item);
/** @type {import('#client').EachItem} */
var block = {
var item = {
a: null,
// dom
d: null,
// effect
// @ts-expect-error
e: null,
// index
@ -646,24 +595,30 @@ function create_item(item, key, index, render_fn, flags) {
// key
k: key,
// item
v: item_value
v: each_item_not_reactive
? value
: (flags & EACH_IS_STRICT_EQUALS) !== 0
? source(value)
: mutable_source(value)
};
var previous_each_item_block = current_each_item_block;
var previous_each_item = current_each_item;
try {
current_each_item_block = block;
current_each_item = item;
item.e = render_effect(() => {
var dom = render_fn(null, item.v, item.i);
block.e = render_effect(
() => {
render_fn(null, block.v, block.i);
},
block,
true
);
return () => {
if (dom !== undefined) {
remove(dom);
}
};
}, true);
return block;
return item;
} finally {
current_each_item_block = previous_each_item_block;
current_each_item = previous_each_item;
}
}

@ -12,7 +12,6 @@ import {
render_effect,
resume_effect
} from '../../reactivity/effects.js';
import { create_block } from './utils.js';
/**
* @param {Comment} anchor
@ -23,16 +22,8 @@ import { create_block } from './utils.js';
* @returns {void}
*/
export function if_block(anchor, get_condition, consequent_fn, alternate_fn, elseif = false) {
const block = create_block();
hydrate_block_anchor(anchor);
/** @type {undefined | import('#client').Dom} */
let consequent_dom;
/** @type {undefined | import('#client').Dom} */
let alternate_dom;
/** @type {import('#client').Effect | null} */
let consequent_effect = null;
@ -71,56 +62,24 @@ export function if_block(anchor, get_condition, consequent_fn, alternate_fn, els
if (consequent_effect) {
resume_effect(consequent_effect);
} else {
consequent_effect = render_effect(
() => {
consequent_dom = consequent_fn(anchor);
return () => {
// TODO make this unnecessary by linking the dom to the effect,
// and removing automatically on teardown
if (consequent_dom !== undefined) {
remove(consequent_dom);
consequent_dom = undefined;
}
};
},
block,
true
);
consequent_effect = render_effect(() => consequent_fn(anchor), true);
}
if (alternate_effect) {
pause_effect(alternate_effect, () => {
alternate_effect = null;
if (alternate_dom) remove(alternate_dom);
});
}
} else {
if (alternate_effect) {
resume_effect(alternate_effect);
} else if (alternate_fn) {
alternate_effect = render_effect(
() => {
alternate_dom = alternate_fn(anchor);
return () => {
// TODO make this unnecessary by linking the dom to the effect,
// and removing automatically on teardown
if (alternate_dom !== undefined) {
remove(alternate_dom);
alternate_dom = undefined;
}
};
},
block,
true
);
alternate_effect = render_effect(() => alternate_fn(anchor), true);
}
if (consequent_effect) {
pause_effect(consequent_effect, () => {
consequent_effect = null;
if (consequent_dom) remove(consequent_dom);
});
}
}
@ -129,23 +88,14 @@ export function if_block(anchor, get_condition, consequent_fn, alternate_fn, els
// Set fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
}, block);
});
if (elseif) {
if_effect.f |= IS_ELSEIF;
}
if_effect.ondestroy = () => {
// TODO make this unnecessary by linking the dom to the effect,
// and removing automatically on teardown
if (consequent_dom !== undefined) {
remove(consequent_dom);
}
if (alternate_dom !== undefined) {
remove(alternate_dom);
}
// TODO why is this not automatic? this should be children of `if_effect`
if (consequent_effect) {
destroy_effect(consequent_effect);
}

@ -3,18 +3,15 @@ import { hydrate_block_anchor } from '../hydration.js';
import { remove } from '../reconciler.js';
import { pause_effect, render_effect } from '../../reactivity/effects.js';
import { safe_not_equal } from '../../reactivity/equality.js';
import { create_block } from './utils.js';
/**
* @template V
* @param {Comment} anchor
* @param {() => V} get_key
* @param {(anchor: Node) => void} render_fn
* @param {(anchor: Node) => import('#client').Dom | void} render_fn
* @returns {void}
*/
export function key_block(anchor, get_key, render_fn) {
const block = create_block();
hydrate_block_anchor(anchor);
/** @type {V | typeof UNINITIALIZED} */
@ -30,47 +27,36 @@ export function key_block(anchor, get_key, render_fn) {
*/
let effects = new Set();
const key_effect = render_effect(
() => {
if (safe_not_equal(key, (key = get_key()))) {
if (effect) {
var e = effect;
pause_effect(e, () => {
effects.delete(e);
});
}
effect = render_effect(
() => {
render_fn(anchor);
const dom = block.d;
return () => {
if (dom !== null) {
remove(dom);
}
};
},
block,
true,
true
);
// @ts-expect-error TODO tidy up
effect.d = block.d;
effects.add(effect);
const key_effect = render_effect(() => {
if (safe_not_equal(key, (key = get_key()))) {
if (effect) {
var e = effect;
pause_effect(e, () => {
effects.delete(e);
});
}
},
block,
false
);
effect = render_effect(
() => {
const dom = render_fn(anchor);
return () => {
if (dom !== undefined) {
remove(dom);
}
};
},
true,
true
);
effects.add(effect);
}
});
key_effect.ondestroy = () => {
for (const e of effects) {
// @ts-expect-error TODO tidy up. ondestroy should be totally unnecessary
if (e.d) remove(e.d);
if (e.dom) remove(e.dom);
}
};
}

@ -1,28 +1,31 @@
import { render_effect } from '../../reactivity/effects.js';
import { remove } from '../reconciler.js';
import { untrack } from '../../runtime.js';
import { create_block } from './utils.js';
/**
* @param {() => Function | null | undefined} get_snippet
* @param {Node} node
* @template {(node: import('#client').TemplateNode, ...args: any[]) => import('#client').Dom} SnippetFn
* @param {() => SnippetFn | null | undefined} get_snippet
* @param {import('#client').TemplateNode} node
* @param {(() => any)[]} args
* @returns {void}
*/
export function snippet(get_snippet, node, ...args) {
const block = create_block();
/** @type {SnippetFn | null | undefined} */
var snippet_fn;
render_effect(() => {
// Only rerender when the snippet function itself changes,
// not when an eagerly-read prop inside the snippet function changes
const snippet = get_snippet();
if (snippet) {
untrack(() => snippet(node, ...args));
if (snippet_fn === (snippet_fn = get_snippet())) return;
if (snippet_fn) {
// Untrack so we only rerender when the snippet function itself changes,
// not when an eagerly-read prop inside the snippet function changes
var dom = untrack(() => /** @type {SnippetFn} */ (snippet_fn)(node, ...args));
}
return () => {
if (block.d !== null) {
remove(block.d);
if (dom !== undefined) {
remove(dom);
}
};
}, block);
});
}

@ -1,7 +1,7 @@
import { hydrate_block_anchor } from '../hydration.js';
import { pause_effect, render_effect } from '../../reactivity/effects.js';
import { remove } from '../reconciler.js';
import { create_block } from './utils.js';
import { current_effect } from '../../runtime.js';
// TODO this is very similar to `key`, can we deduplicate?
@ -10,12 +10,10 @@ import { create_block } from './utils.js';
* @template {(props: P) => void} C
* @param {Comment} anchor
* @param {() => C} get_component
* @param {(component: C) => void} render_fn
* @param {(component: C) => import('#client').Dom | void} render_fn
* @returns {void}
*/
export function component(anchor, get_component, render_fn) {
const block = create_block();
hydrate_block_anchor(anchor);
/** @type {C} */
@ -31,49 +29,36 @@ export function component(anchor, get_component, render_fn) {
*/
let effects = new Set();
const component_effect = render_effect(
() => {
if (component === (component = get_component())) return;
if (effect) {
var e = effect;
pause_effect(e, () => {
effects.delete(e);
});
}
const component_effect = render_effect(() => {
if (component === (component = get_component())) return;
if (component) {
effect = render_effect(
() => {
render_fn(component);
if (effect) {
var e = effect;
pause_effect(e, () => {
effects.delete(e);
});
}
const dom = block.d;
if (component) {
effect = render_effect(() => {
render_fn(component);
return () => {
if (dom !== null) {
remove(dom);
}
};
},
block,
true,
true
);
// `render_fn` doesn't return anything, and we can't reference `effect`
// yet, so we reference it indirectly as `current_effect`
const dom = /** @type {import('#client').Effect} */ (current_effect).dom;
// @ts-expect-error TODO tidy up
effect.d = block.d;
return () => {
if (dom !== null) remove(dom);
};
}, true);
effects.add(effect);
}
},
block,
false
);
effects.add(effect);
}
});
component_effect.ondestroy = () => {
for (const e of effects) {
// @ts-expect-error TODO tidy up. ondestroy should be totally unnecessary
if (e.d) remove(e.d);
if (e.dom) remove(e.dom);
}
};
}

@ -10,18 +10,18 @@ import {
import { remove } from '../reconciler.js';
import { is_array } from '../../utils.js';
import { set_should_intro } from '../../render.js';
import { current_each_item_block, set_current_each_item_block } from './each.js';
import { create_block } from './utils.js';
import { current_block } from '../../runtime.js';
import { current_each_item, set_current_each_item } from './each.js';
import { current_effect } from '../../runtime.js';
/**
* @param {import('#client').Block} block
* @param {import('#client').Effect} effect
* @param {Element} from
* @param {Element} to
* @returns {void}
*/
function swap_block_dom(block, from, to) {
const dom = block.d;
function swap_block_dom(effect, from, to) {
const dom = effect.dom;
if (is_array(dom)) {
for (let i = 0; i < dom.length; i++) {
if (dom[i] === from) {
@ -30,7 +30,7 @@ function swap_block_dom(block, from, to) {
}
}
} else if (dom === from) {
block.d = to;
effect.dom = to;
}
}
@ -42,8 +42,7 @@ function swap_block_dom(block, from, to) {
* @returns {void}
*/
export function element(anchor, get_tag, is_svg, render_fn) {
const parent_block = /** @type {import('#client').Block} */ (current_block);
const block = create_block();
const parent_effect = /** @type {import('#client').Effect} */ (current_effect);
hydrate_block_anchor(anchor);
@ -64,15 +63,15 @@ export function element(anchor, get_tag, is_svg, render_fn) {
* We track this so we can set it when changing the element, allowing any
* `animate:` directive to bind itself to the correct block
*/
let each_item_block = current_each_item_block;
let each_item_block = current_each_item;
const wrapper = render_effect(() => {
const next_tag = get_tag() || null;
if (next_tag === tag) return;
// See explanation of `each_item_block` above
var previous_each_item_block = current_each_item_block;
set_current_each_item_block(each_item_block);
var previous_each_item = current_each_item;
set_current_each_item(each_item_block);
// We try our best infering the namespace in case it's not possible to determine statically,
// but on the first render on the client (without hydration) the parent will be undefined,
@ -103,51 +102,46 @@ export function element(anchor, get_tag, is_svg, render_fn) {
}
if (next_tag && next_tag !== current_tag) {
effect = render_effect(
() => {
const prev_element = element;
element = hydrating
? /** @type {Element} */ (current_hydration_fragment[0])
: ns
? document.createElementNS(ns, next_tag)
: document.createElement(next_tag);
if (render_fn) {
let anchor;
if (hydrating) {
// Use the existing ssr comment as the anchor so that the inner open and close
// methods can pick up the existing nodes correctly
anchor = /** @type {Comment} */ (element.firstChild);
} else {
anchor = empty();
element.appendChild(anchor);
}
render_fn(element, anchor);
effect = render_effect(() => {
const prev_element = element;
element = hydrating
? /** @type {Element} */ (current_hydration_fragment[0])
: ns
? document.createElementNS(ns, next_tag)
: document.createElement(next_tag);
if (render_fn) {
let anchor;
if (hydrating) {
// Use the existing ssr comment as the anchor so that the inner open and close
// methods can pick up the existing nodes correctly
anchor = /** @type {Comment} */ (element.firstChild);
} else {
anchor = empty();
element.appendChild(anchor);
}
render_fn(element, anchor);
}
anchor.before(element);
anchor.before(element);
if (prev_element) {
swap_block_dom(parent_block, prev_element, element);
prev_element.remove();
}
},
block,
true
);
if (prev_element) {
swap_block_dom(parent_effect, prev_element, element);
prev_element.remove();
}
}, true);
}
tag = next_tag;
if (tag) current_tag = tag;
set_should_intro(true);
set_current_each_item_block(previous_each_item_block);
}, block);
set_current_each_item(previous_each_item);
});
wrapper.ondestroy = () => {
if (element !== null) {
remove(element);
block.d = null;
element = null;
}

@ -7,15 +7,12 @@ import {
import { empty } from '../operations.js';
import { render_effect } from '../../reactivity/effects.js';
import { remove } from '../reconciler.js';
import { create_block } from './utils.js';
/**
* @param {(anchor: Node | null) => void} render_fn
* @param {(anchor: Node | null) => import('#client').Dom | void} render_fn
* @returns {void}
*/
export function head(render_fn) {
const block = create_block();
// The head function may be called after the first hydration pass and ssr comment nodes may still be present,
// therefore we need to skip that when we detect that we're not in hydration mode.
let hydration_fragment = null;
@ -29,28 +26,27 @@ export function head(render_fn) {
}
try {
const head_effect = render_effect(
() => {
const current = block.d;
if (current !== null) {
remove(current);
block.d = null;
}
let anchor = null;
if (!hydrating) {
anchor = empty();
document.head.appendChild(anchor);
}
render_fn(anchor);
},
block,
false
);
/** @type {import('#client').Dom | null} */
var dom = null;
const head_effect = render_effect(() => {
if (dom !== null) {
remove(dom);
head_effect.dom = dom = null;
}
let anchor = null;
if (!hydrating) {
anchor = empty();
document.head.appendChild(anchor);
}
dom = render_fn(anchor) ?? null;
});
head_effect.ondestroy = () => {
const current = block.d;
if (current !== null) {
remove(current);
if (dom !== null) {
remove(dom);
}
};
} finally {

@ -1,7 +0,0 @@
/** @returns {import('#client').Block} */
export function create_block() {
return {
// dom
d: null
};
}

@ -1,6 +1,5 @@
import { hydrating } from '../hydration.js';
import { render_effect } from '../../reactivity/effects.js';
import { current_block } from '../../runtime.js';
/**
* @param {HTMLElement} dom
@ -17,7 +16,6 @@ export function autofocus(dom, value) {
dom.focus();
}
},
current_block,
true,
false
);

@ -5,7 +5,7 @@ import { raf } from '../../timing.js';
import { loop } from '../../loop.js';
import { should_intro } from '../../render.js';
import { is_function } from '../../utils.js';
import { current_each_item_block } from '../blocks/each.js';
import { current_each_item } from '../blocks/each.js';
import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js';
import { EFFECT_RAN } from '../../constants.js';
@ -77,7 +77,7 @@ const linear = (t) => t;
* @param {(() => P) | null} get_params
*/
export function animation(element, get_fn, get_params) {
var block = /** @type {import('#client').EachItem} */ (current_each_item_block);
var item = /** @type {import('#client').EachItem} */ (current_each_item);
/** @type {DOMRect} */
var from;
@ -88,7 +88,7 @@ export function animation(element, get_fn, get_params) {
/** @type {import('#client').Animation | undefined} */
var animation;
block.a ??= {
item.a ??= {
element,
measure() {
from = this.element.getBoundingClientRect();
@ -118,7 +118,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
block.a.element = element;
item.a.element = element;
}
/**

@ -5,7 +5,7 @@ import {
create_fragment_with_script_from_html,
insert
} from './reconciler.js';
import { current_block } from '../runtime.js';
import { current_effect } from '../runtime.js';
import { is_array } from '../utils.js';
/**
@ -192,7 +192,7 @@ function close_template(dom, is_fragment, anchor) {
insert(current, anchor);
}
/** @type {import('#client').Block} */ (current_block).d = current;
/** @type {import('#client').Effect} */ (current_effect).dom = current;
return current;
}

@ -1,7 +1,6 @@
import { DEV } from 'esm-env';
import {
check_dirtiness,
current_block,
current_component_context,
current_effect,
current_reaction,
@ -25,20 +24,20 @@ import {
} from '../constants.js';
import { set } from './sources.js';
import { noop } from '../../common.js';
import { remove } from '../dom/reconciler.js';
/**
* @param {import('./types.js').EffectType} type
* @param {(() => void | (() => void))} fn
* @param {boolean} sync
* @param {null | import('#client').Block} block
* @param {boolean} init
* @returns {import('#client').Effect}
*/
function create_effect(type, fn, sync, block = current_block, init = true) {
function create_effect(type, fn, sync, init = true) {
/** @type {import('#client').Effect} */
const signal = {
parent: current_effect,
block,
dom: null,
deps: null,
f: type | DIRTY,
l: 0,
@ -99,7 +98,7 @@ export function user_effect(fn) {
current_component_context !== null &&
!current_component_context.m;
const effect = create_effect(EFFECT, fn, false, current_block, !defer);
const effect = create_effect(EFFECT, fn, false, !defer);
if (defer) {
const context = /** @type {import('#client').ComponentContext} */ (current_component_context);
@ -115,7 +114,7 @@ export function user_effect(fn) {
* @returns {() => void}
*/
export function user_root_effect(fn) {
const effect = render_effect(fn, current_block, true);
const effect = render_effect(fn, true);
return () => {
destroy_effect(effect);
};
@ -216,17 +215,15 @@ export function invalidate_effect(fn) {
/**
* @param {(() => void)} fn
* @param {any} block
* @param {any} managed
* @param {any} sync
* @param {boolean} managed
* @param {boolean} sync
* @returns {import('#client').Effect}
*/
export function render_effect(fn, block = current_block, managed = false, sync = true) {
export function render_effect(fn, managed = false, sync = true) {
let flags = RENDER_EFFECT;
if (managed) {
flags |= MANAGED;
}
return create_effect(flags, /** @type {any} */ (fn), sync, block);
if (managed) flags |= MANAGED;
return create_effect(flags, /** @type {any} */ (fn), sync);
}
/**
@ -245,6 +242,11 @@ export function destroy_effect(effect) {
}
effect.teardown?.();
if (effect.dom !== null) {
remove(effect.dom);
}
effect.ondestroy?.();
// @ts-expect-error
@ -253,7 +255,7 @@ export function destroy_effect(effect) {
effect.teardown =
effect.ondestroy =
effect.ctx =
effect.block =
effect.dom =
effect.deps =
null;
}
@ -269,26 +271,51 @@ export function destroy_effect(effect) {
*/
export function pause_effect(effect, callback = noop) {
/** @type {import('#client').TransitionManager[]} */
const transitions = [];
var transitions = [];
pause_children(effect, transitions, true);
let remaining = transitions.length;
out(transitions, () => {
destroy_effect(effect);
callback();
});
}
if (remaining > 0) {
const check = () => {
if (!--remaining) {
destroy_effect(effect);
callback();
}
};
/**
* Pause multiple effects simultaneously, and coordinate their
* subsequent destruction. Used in each blocks
* @param {import('#client').Effect[]} effects
* @param {() => void} callback
*/
export function pause_effects(effects, callback = noop) {
/** @type {import('#client').TransitionManager[]} */
var transitions = [];
for (var effect of effects) {
pause_children(effect, transitions, true);
}
out(transitions, () => {
for (var effect of effects) {
destroy_effect(effect);
}
callback();
});
}
for (const transition of transitions) {
/**
* @param {import('#client').TransitionManager[]} transitions
* @param {() => void} fn
*/
function out(transitions, fn) {
var remaining = transitions.length;
if (remaining > 0) {
var check = () => --remaining || fn();
for (var transition of transitions) {
transition.out(check);
}
} else {
destroy_effect(effect);
callback();
fn();
}
}

@ -1,4 +1,4 @@
import type { Block, ComponentContext, Equals, TransitionManager } from '#client';
import type { Block, ComponentContext, Dom, Equals, TransitionManager } from '#client';
import type { EFFECT, PRE_EFFECT, RENDER_EFFECT } from '../constants';
export type EffectType = typeof EFFECT | typeof PRE_EFFECT | typeof RENDER_EFFECT;
@ -37,8 +37,7 @@ export interface Derived<V = unknown> extends Value<V>, Reaction {
export interface Effect extends Reaction {
parent: Effect | null;
/** The block associated with this effect */
block: null | Block;
dom: Dom | null;
/** The associated component context */
ctx: null | ComponentContext;
/** Stuff to do when the effect is destroyed */

@ -211,40 +211,30 @@ function _mount(Component, options) {
should_intro = options.intro ?? false;
/** @type {import('#client').Block} */
const block = {
// dom
d: null
};
/** @type {Exports} */
// @ts-expect-error will be defined because the render effect runs synchronously
let component = undefined;
const effect = render_effect(
() => {
if (options.context) {
push({});
/** @type {import('../client/types.js').ComponentContext} */ (current_component_context).c =
options.context;
}
if (!options.props) {
options.props = /** @type {Props} */ ({});
}
if (options.events) {
// We can't spread the object or else we'd lose the state proxy stuff, if it is one
/** @type {any} */ (options.props).$$events = options.events;
}
component =
// @ts-expect-error the public typings are not what the actual function looks like
Component(options.anchor, options.props) || {};
if (options.context) {
pop();
}
},
block,
true
);
const effect = render_effect(() => {
if (options.context) {
push({});
/** @type {import('../client/types.js').ComponentContext} */ (current_component_context).c =
options.context;
}
if (!options.props) {
options.props = /** @type {Props} */ ({});
}
if (options.events) {
// We can't spread the object or else we'd lose the state proxy stuff, if it is one
/** @type {any} */ (options.props).$$events = options.events;
}
component =
// @ts-expect-error the public typings are not what the actual function looks like
Component(options.anchor, options.props) || {};
if (options.context) {
pop();
}
}, true);
const bound_event_listener = handle_event_propagation.bind(null, container);
const bound_document_event_listener = handle_event_propagation.bind(null, document);
@ -291,7 +281,7 @@ function _mount(Component, options) {
container.removeEventListener(event_name, bound_event_listener);
}
root_event_handles.delete(event_handle);
const dom = block.d;
const dom = effect.dom;
if (dom !== null) {
remove(dom);
}

@ -107,11 +107,7 @@ export let inspect_fn = null;
/** @type {Array<import('./types.js').ValueDebug>} */
let inspect_captured_signals = [];
// Handle rendering tree blocks and anchors
/** @type {null | import('./types.js').Block} */
export let current_block = null;
// Handling runtime component context
/** @type {import('./types.js').ComponentContext | null} */
export let current_component_context = null;
@ -387,13 +383,11 @@ export function execute_effect(signal) {
const previous_effect = current_effect;
const previous_component_context = current_component_context;
const previous_block = current_block;
const component_context = signal.ctx;
current_effect = signal;
current_component_context = component_context;
current_block = signal.block;
try {
destroy_children(signal);
@ -403,7 +397,6 @@ export function execute_effect(signal) {
} finally {
current_effect = previous_effect;
current_component_context = previous_component_context;
current_block = previous_block;
}
if ((signal.f & PRE_EFFECT) !== 0 && current_queued_pre_and_render_effects.length > 0) {
@ -514,7 +507,6 @@ export function schedule_effect(signal, sync) {
if (!should_append) {
const target_level = signal.l;
const target_block = signal.block;
const is_pre_effect = (flags & PRE_EFFECT) !== 0;
let target_signal;
let target_signal_level;
@ -530,7 +522,7 @@ export function schedule_effect(signal, sync) {
is_target_pre_effect = (target_signal.f & PRE_EFFECT) !== 0;
if (
target_signal_level < target_level ||
target_signal.block !== target_block ||
target_signal !== signal ||
(is_target_pre_effect && !is_pre_effect)
) {
i++;

@ -51,10 +51,7 @@ export type TemplateNode = Text | Element | Comment;
export type Dom = TemplateNode | TemplateNode[];
export interface Block {
/** dom */
d: null | Dom;
}
export interface Block {}
export type EachState = {
/** flags */
@ -66,8 +63,6 @@ export type EachState = {
export type EachItem = {
/** animation manager */
a: AnimationManager | null;
/** dom */
d: null | Dom;
/** effect */
e: Effect;
/** item */

@ -26,7 +26,6 @@ function run_test(runes: boolean, fn: (runes: boolean) => () => void) {
() => {
execute = fn(runes);
},
null,
true,
true
);

Loading…
Cancel
Save