chore: move block logic into separate modules (#10542)

* move if block logic

* key and await

* move each

* move more stuff

* dedupe

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/10545/head
Rich Harris 10 months ago committed by GitHub
parent 71db9edcdc
commit 41e7dab755
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -32,48 +32,6 @@ export function create_root_block(intro) {
};
}
/** @returns {import('./types.js').IfBlock} */
export function create_if_block() {
return {
// alternate transitions
a: null,
// alternate effect
ae: null,
// consequent transitions
c: null,
// consequent effect
ce: null,
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('./types.js').Block} */ (current_block),
// transition
r: null,
// type
t: IF_BLOCK,
// value
v: false
};
}
/** @returns {import('./types.js').KeyBlock} */
export function create_key_block() {
return {
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('./types.js').Block} */ (current_block),
// transition
r: null,
// type
t: KEY_BLOCK
};
}
/** @returns {import('./types.js').HeadBlock} */
export function create_head_block() {
return {
@ -122,82 +80,6 @@ export function create_dynamic_component_block() {
};
}
/** @returns {import('./types.js').AwaitBlock} */
export function create_await_block() {
return {
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('./types.js').Block} */ (current_block),
// pending
n: true,
// transition
r: null,
// type
t: AWAIT_BLOCK
};
}
/**
* @param {number} flags
* @param {Element | Comment} anchor
* @returns {import('./types.js').EachBlock}
*/
export function create_each_block(flags, anchor) {
return {
// anchor
a: anchor,
// dom
d: null,
// flags
f: flags,
// items
v: [],
// effect
e: null,
p: /** @type {import('./types.js').Block} */ (current_block),
// transition
r: null,
// transitions
s: [],
// type
t: EACH_BLOCK
};
}
/**
* @param {any | import('./types.js').Signal<any>} item
* @param {number | import('./types.js').Signal<number>} index
* @param {null | unknown} key
* @returns {import('./types.js').EachItemBlock}
*/
export function create_each_item_block(item, index, key) {
return {
// animate transition
a: null,
// dom
d: null,
// effect
e: null,
// index
i: index,
// key
k: key,
// item
v: item,
// parent
p: /** @type {import('./types.js').EachBlock} */ (current_block),
// transition
r: null,
// transitions
s: null,
// type
t: EACH_ITEM_BLOCK
};
}
/** @returns {import('./types.js').SnippetBlock} */
export function create_snippet_block() {
return {

@ -0,0 +1,201 @@
import { is_promise } from '../../../common.js';
import { AWAIT_BLOCK } from '../../block.js';
import { hydrate_block_anchor } from '../../hydration.js';
import { remove } from '../../reconciler.js';
import {
UNINITIALIZED,
current_block,
destroy_signal,
execute_effect,
flushSync,
push_destroy_fn,
render_effect
} from '../../runtime.js';
import { trigger_transitions } from '../../transitions.js';
/** @returns {import('../../types.js').AwaitBlock} */
export function create_await_block() {
return {
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('../../types.js').Block} */ (current_block),
// pending
n: true,
// transition
r: null,
// type
t: AWAIT_BLOCK
};
}
/**
* @template V
* @param {Comment} anchor_node
* @param {(() => Promise<V>)} input
* @param {null | ((anchor: Node) => void)} pending_fn
* @param {null | ((anchor: Node, value: V) => void)} then_fn
* @param {null | ((anchor: Node, error: unknown) => void)} catch_fn
* @returns {void}
*/
export function await_block(anchor_node, input, pending_fn, then_fn, catch_fn) {
const block = create_await_block();
/** @type {null | import('../../types.js').Render} */
let current_render = null;
hydrate_block_anchor(anchor_node);
/** @type {{}} */
let latest_token;
/** @type {typeof UNINITIALIZED | V} */
let resolved_value = UNINITIALIZED;
/** @type {unknown} */
let error = UNINITIALIZED;
let pending = false;
block.r =
/**
* @param {import('../../types.js').Transition} transition
* @returns {void}
*/
(transition) => {
const render = /** @type {import('../../types.js').Render} */ (current_render);
const transitions = render.s;
transitions.add(transition);
transition.f(() => {
transitions.delete(transition);
if (transitions.size === 0) {
// If the current render has changed since, then we can remove the old render
// effect as it's stale.
if (current_render !== render && render.e !== null) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
destroy_signal(render.e);
render.e = null;
}
}
});
};
const create_render_effect = () => {
/** @type {import('../../types.js').Render} */
const render = {
d: null,
e: null,
s: new Set(),
p: current_render
};
const effect = render_effect(
() => {
if (error === UNINITIALIZED) {
if (resolved_value === UNINITIALIZED) {
// pending = true
block.n = true;
if (pending_fn !== null) {
pending_fn(anchor_node);
}
} else if (then_fn !== null) {
// pending = false
block.n = false;
then_fn(anchor_node, resolved_value);
}
} else if (catch_fn !== null) {
// pending = false
block.n = false;
catch_fn(anchor_node, error);
}
render.d = block.d;
block.d = null;
},
block,
true,
true
);
render.e = effect;
current_render = render;
};
const render = () => {
const render = current_render;
if (render === null) {
create_render_effect();
return;
}
const transitions = render.s;
if (transitions.size === 0) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
if (render.e) {
execute_effect(render.e);
} else {
create_render_effect();
}
} else {
create_render_effect();
trigger_transitions(transitions, 'out');
}
};
const await_effect = render_effect(
() => {
const token = {};
latest_token = token;
const promise = input();
if (is_promise(promise)) {
promise.then(
/** @param {V} v */
(v) => {
if (latest_token === token) {
// Ensure UI is in sync before resolving value.
flushSync();
resolved_value = v;
pending = false;
render();
}
},
/** @param {unknown} _error */
(_error) => {
error = _error;
pending = false;
render();
}
);
if (resolved_value !== UNINITIALIZED || error !== UNINITIALIZED) {
error = UNINITIALIZED;
resolved_value = UNINITIALIZED;
}
if (!pending) {
pending = true;
render();
}
} else {
error = UNINITIALIZED;
resolved_value = promise;
pending = false;
render();
}
},
block,
false
);
push_destroy_fn(await_effect, () => {
let render = current_render;
latest_token = {};
while (render !== null) {
const dom = render.d;
if (dom !== null) {
remove(dom);
}
const effect = render.e;
if (effect !== null) {
destroy_signal(effect);
}
render = render.p;
}
});
block.e = await_effect;
}

@ -5,18 +5,20 @@ import {
EACH_IS_STRICT_EQUALS,
EACH_ITEM_REACTIVE,
EACH_KEYED
} from '../../constants.js';
import { create_each_block, create_each_item_block } from './block.js';
} from '../../../../constants.js';
import { noop } from '../../../common.js';
import { EACH_BLOCK, EACH_ITEM_BLOCK } from '../../block.js';
import {
current_hydration_fragment,
get_hydration_fragment,
hydrate_block_anchor,
hydrating,
set_current_hydration_fragment
} from './hydration.js';
import { clear_text_content, empty, map_get, map_set } from './operations.js';
import { insert, remove } from './reconciler.js';
} from '../../hydration.js';
import { clear_text_content, empty, map_get, map_set } from '../../operations.js';
import { insert, remove } from '../../reconciler.js';
import {
current_block,
destroy_signal,
execute_effect,
mutable_source,
@ -24,15 +26,71 @@ import {
render_effect,
set_signal_value,
source
} from './runtime.js';
import { trigger_transitions } from './transitions.js';
import { is_array } from './utils.js';
} from '../../runtime.js';
import { trigger_transitions } from '../../transitions.js';
import { is_array } from '../../utils.js';
const NEW_BLOCK = -1;
const MOVED_BLOCK = 99999999;
const LIS_BLOCK = -2;
function no_op() {}
/**
* @param {number} flags
* @param {Element | Comment} anchor
* @returns {import('../../types.js').EachBlock}
*/
export function create_each_block(flags, anchor) {
return {
// anchor
a: anchor,
// dom
d: null,
// flags
f: flags,
// items
v: [],
// effect
e: null,
p: /** @type {import('../../types.js').Block} */ (current_block),
// transition
r: null,
// transitions
s: [],
// type
t: EACH_BLOCK
};
}
/**
* @param {any | import('../../types.js').Signal<any>} item
* @param {number | import('../../types.js').Signal<number>} index
* @param {null | unknown} key
* @returns {import('../../types.js').EachItemBlock}
*/
export function create_each_item_block(item, index, key) {
return {
// animate transition
a: null,
// dom
d: null,
// effect
e: null,
// index
i: index,
// key
k: key,
// item
v: item,
// parent
p: /** @type {import('../../types.js').EachBlock} */ (current_block),
// transition
r: null,
// transitions
s: null,
// type
t: EACH_ITEM_BLOCK
};
}
/**
* @template V
@ -40,7 +98,7 @@ function no_op() {}
* @param {() => V[]} collection
* @param {number} flags
* @param {null | ((item: V) => string)} key_fn
* @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal<number>) => void} render_fn
* @param {(anchor: null, item: V, index: import('../../types.js').MaybeSignal<number>) => void} render_fn
* @param {null | ((anchor: Node) => void)} fallback_fn
* @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn
* @returns {void}
@ -49,7 +107,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
const block = create_each_block(flags, anchor_node);
/** @type {null | import('./types.js').Render} */
/** @type {null | import('../../types.js').Render} */
let current_fallback = null;
hydrate_block_anchor(anchor_node, is_controlled);
@ -59,7 +117,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
/** @type {Array<string> | null} */
let keys = null;
/** @type {null | import('./types.js').EffectSignal} */
/** @type {null | import('../../types.js').EffectSignal} */
let render = null;
/**
@ -69,9 +127,9 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
let mismatch = false;
block.r =
/** @param {import('./types.js').Transition} transition */
/** @param {import('../../types.js').Transition} transition */
(transition) => {
const fallback = /** @type {import('./types.js').Render} */ (current_fallback);
const fallback = /** @type {import('../../types.js').Render} */ (current_fallback);
const transitions = fallback.s;
transitions.add(transition);
transition.f(() => {
@ -90,7 +148,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
};
const create_fallback_effect = () => {
/** @type {import('./types.js').Render} */
/** @type {import('../../types.js').Render} */
const fallback = {
d: null,
e: null,
@ -131,7 +189,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
current_fallback = fallback;
};
/** @param {import('./types.js').EachBlock} block */
/** @param {import('../../types.js').EachBlock} block */
const render_each = (block) => {
const flags = block.f;
const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
@ -152,7 +210,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
if (key_fn !== null) {
keys = array.map(key_fn);
} else if ((flags & EACH_KEYED) === 0) {
array.map(no_op);
array.map(noop);
}
const length = array.length;
@ -168,7 +226,9 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
mismatch = true;
} else if (is_each_else_comment) {
// Remove the each_else comment node or else it will confuse the subsequent hydration algorithm
/** @type {import('./types.js').TemplateNode[]} */ (current_hydration_fragment).shift();
/** @type {import('../../types.js').TemplateNode[]} */ (
current_hydration_fragment
).shift();
}
}
@ -226,7 +286,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
}
// Clear the array
reconcile_fn([], block, anchor_node, is_controlled, render_fn, flags, false, keys);
destroy_signal(/** @type {import('./types.js').EffectSignal} */ (render));
destroy_signal(/** @type {import('../../types.js').EffectSignal} */ (render));
});
block.e = each;
@ -238,7 +298,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
* @param {() => V[]} collection
* @param {number} flags
* @param {null | ((item: V) => string)} key_fn
* @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal<number>) => void} render_fn
* @param {(anchor: null, item: V, index: import('../../types.js').MaybeSignal<number>) => void} render_fn
* @param {null | ((anchor: Node) => void)} fallback_fn
* @returns {void}
*/
@ -251,7 +311,7 @@ export function each_keyed(anchor_node, collection, flags, key_fn, render_fn, fa
* @param {Element | Comment} anchor_node
* @param {() => V[]} collection
* @param {number} flags
* @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal<number>) => void} render_fn
* @param {(anchor: null, item: V, index: import('../../types.js').MaybeSignal<number>) => void} render_fn
* @param {null | ((anchor: Node) => void)} fallback_fn
* @returns {void}
*/
@ -262,10 +322,10 @@ export function each_indexed(anchor_node, collection, flags, render_fn, fallback
/**
* @template V
* @param {Array<V>} array
* @param {import('./types.js').EachBlock} each_block
* @param {import('../../types.js').EachBlock} each_block
* @param {Element | Comment | Text} dom
* @param {boolean} is_controlled
* @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn
* @param {(anchor: null, item: V, index: number | import('../../types.js').Signal<number>) => void} render_fn
* @param {number} flags
* @param {boolean} apply_transitions
* @returns {void}
@ -290,7 +350,7 @@ function reconcile_indexed_array(
var length = Math.max(a, b);
var index = 0;
/** @type {Array<import('./types.js').EachItemBlock>} */
/** @type {Array<import('../../types.js').EachItemBlock>} */
var b_blocks;
var block;
@ -315,7 +375,7 @@ function reconcile_indexed_array(
b_blocks = Array(b);
if (hydrating) {
// Hydrate block
var hydration_list = /** @type {import('./types.js').TemplateNode[]} */ (
var hydration_list = /** @type {import('../../types.js').TemplateNode[]} */ (
current_hydration_fragment
);
var hydrating_node = hydration_list[0];
@ -333,7 +393,7 @@ function reconcile_indexed_array(
block = each_item_block(item, null, index, render_fn, flags);
b_blocks[index] = block;
hydrating_node = /** @type {import('./types.js').TemplateNode} */ (
hydrating_node = /** @type {import('../../types.js').TemplateNode} */ (
/** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling
);
}
@ -376,10 +436,10 @@ function reconcile_indexed_array(
* https://github.com/localvoid/ivi/blob/9f1bd0918f487da5b131941228604763c5d8ef56/packages/ivi/src/client/core.ts#L968
* @template V
* @param {Array<V>} array
* @param {import('./types.js').EachBlock} each_block
* @param {import('../../types.js').EachBlock} each_block
* @param {Element | Comment | Text} dom
* @param {boolean} is_controlled
* @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn
* @param {(anchor: null, item: V, index: number | import('../../types.js').Signal<number>) => void} render_fn
* @param {number} flags
* @param {boolean} apply_transitions
* @param {Array<string> | null} keys
@ -405,7 +465,7 @@ function reconcile_tracked_array(
/** @type {number} */
var b = array.length;
/** @type {Array<import('./types.js').EachItemBlock>} */
/** @type {Array<import('../../types.js').EachItemBlock>} */
var b_blocks;
var block;
@ -435,7 +495,7 @@ function reconcile_tracked_array(
if (hydrating) {
// Hydrate block
var fragment;
var hydration_list = /** @type {import('./types.js').TemplateNode[]} */ (
var hydration_list = /** @type {import('../../types.js').TemplateNode[]} */ (
current_hydration_fragment
);
var hydrating_node = hydration_list[0];
@ -457,7 +517,7 @@ function reconcile_tracked_array(
// Get the <!--ssr:..--> tag of the next item in the list
// The fragment array can be empty if each block has no content
hydrating_node = /** @type {import('./types.js').TemplateNode} */ (
hydrating_node = /** @type {import('../../types.js').TemplateNode} */ (
/** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling
);
}
@ -610,8 +670,8 @@ function reconcile_tracked_array(
/**
* The server could have rendered more list items than the client specifies.
* In that case, we need to remove the remaining server-rendered nodes.
* @param {import('./types.js').TemplateNode[]} hydration_list
* @param {import('./types.js').TemplateNode | null} next_node
* @param {import('../../types.js').TemplateNode[]} hydration_list
* @param {import('../../types.js').TemplateNode | null} next_node
*/
function remove_excess_hydration_nodes(hydration_list, next_node) {
if (next_node === null) return;
@ -695,14 +755,14 @@ function mark_lis(a) {
}
/**
* @param {import('./types.js').Block} block
* @param {import('../../types.js').Block} block
* @param {Element | Comment | Text} dom
* @param {boolean} is_controlled
* @param {null | Text | Element | Comment} sibling
* @returns {Text | Element | Comment}
*/
function insert_each_item_block(block, dom, is_controlled, sibling) {
var current = /** @type {import('./types.js').TemplateNode} */ (block.d);
var current = /** @type {import('../../types.js').TemplateNode} */ (block.d);
if (sibling === null) {
if (is_controlled) {
@ -716,7 +776,7 @@ function insert_each_item_block(block, dom, is_controlled, sibling) {
}
/**
* @param {import('./types.js').Block} block
* @param {import('../../types.js').Block} block
* @returns {Text | Element | Comment}
*/
function get_first_child(block) {
@ -730,7 +790,7 @@ function get_first_child(block) {
}
/**
* @param {Array<import('./types.js').EachItemBlock>} active_transitions
* @param {Array<import('../../types.js').EachItemBlock>} active_transitions
* @returns {void}
*/
function destroy_active_transition_blocks(active_transitions) {
@ -755,7 +815,7 @@ function destroy_active_transition_blocks(active_transitions) {
}
/**
* @param {import('./types.js').Block} block
* @param {import('../../types.js').Block} block
* @returns {Text | Element | Comment}
*/
export function get_first_element(block) {
@ -774,7 +834,7 @@ export function get_first_element(block) {
}
/**
* @param {import('./types.js').EachItemBlock} block
* @param {import('../../types.js').EachItemBlock} block
* @param {any} item
* @param {number} index
* @param {number} type
@ -793,15 +853,15 @@ function update_each_item_block(block, item, index, type) {
each_animation(block, transitions);
}
if (index_is_reactive) {
set_signal_value(/** @type {import('./types.js').Signal<number>} */ (block.i), index);
set_signal_value(/** @type {import('../../types.js').Signal<number>} */ (block.i), index);
} else {
block.i = index;
}
}
/**
* @param {import('./types.js').EachItemBlock} block
* @param {null | Array<import('./types.js').Block>} transition_block
* @param {import('../../types.js').EachItemBlock} block
* @param {null | Array<import('../../types.js').Block>} transition_block
* @param {boolean} apply_transitions
* @param {any} controlled
* @returns {void}
@ -835,7 +895,7 @@ export function destroy_each_item_block(
if (!controlled && dom !== null) {
remove(dom);
}
destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e));
destroy_signal(/** @type {import('../../types.js').EffectSignal} */ (block.e));
}
/**
@ -843,9 +903,9 @@ export function destroy_each_item_block(
* @param {V} item
* @param {unknown} key
* @param {number} index
* @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn
* @param {(anchor: null, item: V, index: number | import('../../types.js').Signal<number>) => void} render_fn
* @param {number} flags
* @returns {import('./types.js').EachItemBlock}
* @returns {import('../../types.js').EachItemBlock}
*/
function each_item_block(item, key, index, render_fn, flags) {
const each_item_not_reactive = (flags & EACH_ITEM_REACTIVE) === 0;
@ -860,7 +920,7 @@ function each_item_block(item, key, index, render_fn, flags) {
const block = create_each_item_block(item_value, index_value, key);
const effect = render_effect(
/** @param {import('./types.js').EachItemBlock} block */
/** @param {import('../../types.js').EachItemBlock} block */
(block) => {
render_fn(null, block.v, block.i);
},

@ -0,0 +1,186 @@
import { IF_BLOCK } from '../../block.js';
import {
current_hydration_fragment,
hydrate_block_anchor,
hydrating,
set_current_hydration_fragment
} from '../../hydration.js';
import { remove } from '../../reconciler.js';
import {
current_block,
destroy_signal,
execute_effect,
push_destroy_fn,
render_effect
} from '../../runtime.js';
import { trigger_transitions } from '../../transitions.js';
/** @returns {import('../../types.js').IfBlock} */
function create_if_block() {
return {
// alternate transitions
a: null,
// alternate effect
ae: null,
// consequent transitions
c: null,
// consequent effect
ce: null,
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('../../types.js').Block} */ (current_block),
// transition
r: null,
// type
t: IF_BLOCK,
// value
v: false
};
}
/**
* @param {Comment} anchor_node
* @param {() => boolean} condition_fn
* @param {(anchor: Node) => void} consequent_fn
* @param {null | ((anchor: Node) => void)} alternate_fn
* @returns {void}
*/
export function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
const block = create_if_block();
hydrate_block_anchor(anchor_node);
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false;
/** @type {null | import('../../types.js').TemplateNode | Array<import('../../types.js').TemplateNode>} */
let consequent_dom = null;
/** @type {null | import('../../types.js').TemplateNode | Array<import('../../types.js').TemplateNode>} */
let alternate_dom = null;
let has_mounted = false;
/**
* @type {import('../../types.js').EffectSignal | null}
*/
let current_branch_effect = null;
const if_effect = render_effect(
() => {
const result = !!condition_fn();
if (block.v !== result || !has_mounted) {
block.v = result;
if (has_mounted) {
const consequent_transitions = block.c;
const alternate_transitions = block.a;
if (result) {
if (alternate_transitions === null || alternate_transitions.size === 0) {
execute_effect(alternate_effect);
} else {
trigger_transitions(alternate_transitions, 'out');
}
if (consequent_transitions === null || consequent_transitions.size === 0) {
execute_effect(consequent_effect);
} else {
trigger_transitions(consequent_transitions, 'in');
}
} else {
if (consequent_transitions === null || consequent_transitions.size === 0) {
execute_effect(consequent_effect);
} else {
trigger_transitions(consequent_transitions, 'out');
}
if (alternate_transitions === null || alternate_transitions.size === 0) {
execute_effect(alternate_effect);
} else {
trigger_transitions(alternate_transitions, 'in');
}
}
} else if (hydrating) {
const comment_text = /** @type {Comment} */ (current_hydration_fragment?.[0])?.data;
if (
!comment_text ||
(comment_text === 'ssr:if:true' && !result) ||
(comment_text === 'ssr:if:false' && result)
) {
// Hydration mismatch: remove everything inside the anchor and start fresh.
// This could happen using when `{#if browser} .. {/if}` in SvelteKit.
remove(current_hydration_fragment);
set_current_hydration_fragment(null);
mismatch = true;
} else {
// Remove the ssr:if comment node or else it will confuse the subsequent hydration algorithm
current_hydration_fragment.shift();
}
}
has_mounted = true;
}
},
block,
false
);
// Managed effect
const consequent_effect = render_effect(
(
/** @type {any} */ _,
/** @type {import('../../types.js').EffectSignal | null} */ consequent_effect
) => {
const result = block.v;
if (!result && consequent_dom !== null) {
remove(consequent_dom);
consequent_dom = null;
}
if (result && current_branch_effect !== consequent_effect) {
consequent_fn(anchor_node);
if (mismatch && current_branch_effect === null) {
// Set fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
current_branch_effect = consequent_effect;
consequent_dom = block.d;
}
block.d = null;
},
block,
true
);
block.ce = consequent_effect;
// Managed effect
const alternate_effect = render_effect(
(
/** @type {any} */ _,
/** @type {import('../../types.js').EffectSignal | null} */ alternate_effect
) => {
const result = block.v;
if (result && alternate_dom !== null) {
remove(alternate_dom);
alternate_dom = null;
}
if (!result && current_branch_effect !== alternate_effect) {
if (alternate_fn !== null) {
alternate_fn(anchor_node);
}
if (mismatch && current_branch_effect === null) {
// Set fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
current_branch_effect = alternate_effect;
alternate_dom = block.d;
}
block.d = null;
},
block,
true
);
block.ae = alternate_effect;
push_destroy_fn(if_effect, () => {
if (consequent_dom !== null) {
remove(consequent_dom);
}
if (alternate_dom !== null) {
remove(alternate_dom);
}
destroy_signal(consequent_effect);
destroy_signal(alternate_effect);
});
block.e = if_effect;
}

@ -0,0 +1,146 @@
import { KEY_BLOCK } from '../../block.js';
import { hydrate_block_anchor } from '../../hydration.js';
import { remove } from '../../reconciler.js';
import {
UNINITIALIZED,
current_block,
destroy_signal,
execute_effect,
push_destroy_fn,
render_effect,
safe_not_equal
} from '../../runtime.js';
import { trigger_transitions } from '../../transitions.js';
/** @returns {import('../../types.js').KeyBlock} */
function create_key_block() {
return {
// dom
d: null,
// effect
e: null,
// parent
p: /** @type {import('../../types.js').Block} */ (current_block),
// transition
r: null,
// type
t: KEY_BLOCK
};
}
/**
* @template V
* @param {Comment} anchor_node
* @param {() => V} key
* @param {(anchor: Node) => void} render_fn
* @returns {void}
*/
export function key_block(anchor_node, key, render_fn) {
const block = create_key_block();
/** @type {null | import('../../types.js').Render} */
let current_render = null;
hydrate_block_anchor(anchor_node);
/** @type {V | typeof UNINITIALIZED} */
let key_value = UNINITIALIZED;
let mounted = false;
block.r =
/**
* @param {import('../../types.js').Transition} transition
* @returns {void}
*/
(transition) => {
const render = /** @type {import('../../types.js').Render} */ (current_render);
const transitions = render.s;
transitions.add(transition);
transition.f(() => {
transitions.delete(transition);
if (transitions.size === 0) {
// If the current render has changed since, then we can remove the old render
// effect as it's stale.
if (current_render !== render && render.e !== null) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
destroy_signal(render.e);
render.e = null;
}
}
});
};
const create_render_effect = () => {
/** @type {import('../../types.js').Render} */
const render = {
d: null,
e: null,
s: new Set(),
p: current_render
};
const effect = render_effect(
() => {
render_fn(anchor_node);
render.d = block.d;
block.d = null;
},
block,
true,
true
);
render.e = effect;
current_render = render;
};
const render = () => {
const render = current_render;
if (render === null) {
create_render_effect();
return;
}
const transitions = render.s;
if (transitions.size === 0) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
if (render.e) {
execute_effect(render.e);
} else {
create_render_effect();
}
} else {
trigger_transitions(transitions, 'out');
create_render_effect();
}
};
const key_effect = render_effect(
() => {
const prev_key_value = key_value;
key_value = key();
if (mounted && safe_not_equal(prev_key_value, key_value)) {
render();
}
},
block,
false
);
// To ensure topological ordering of the key effect to the render effect,
// we trigger the effect after.
render();
mounted = true;
push_destroy_fn(key_effect, () => {
let render = current_render;
while (render !== null) {
const dom = render.d;
if (dom !== null) {
remove(dom);
}
const effect = render.e;
if (effect !== null) {
destroy_signal(effect);
}
render = render.p;
}
});
block.e = key_effect;
}

@ -12,9 +12,6 @@ import {
} from './operations.js';
import {
create_root_block,
create_if_block,
create_key_block,
create_await_block,
create_dynamic_element_block,
create_head_block,
create_dynamic_component_block,
@ -39,12 +36,9 @@ import {
is_signal,
push_destroy_fn,
execute_effect,
UNINITIALIZED,
untrack,
effect,
flushSync,
flush_sync,
safe_not_equal,
current_block,
managed_effect,
push,
@ -1453,151 +1447,6 @@ export function slot(anchor_node, slot_fn, slot_props, fallback_fn) {
}
}
/**
* @param {Comment} anchor_node
* @param {() => boolean} condition_fn
* @param {(anchor: Node) => void} consequent_fn
* @param {null | ((anchor: Node) => void)} alternate_fn
* @returns {void}
*/
function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
const block = create_if_block();
hydrate_block_anchor(anchor_node);
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false;
/** @type {null | import('./types.js').TemplateNode | Array<import('./types.js').TemplateNode>} */
let consequent_dom = null;
/** @type {null | import('./types.js').TemplateNode | Array<import('./types.js').TemplateNode>} */
let alternate_dom = null;
let has_mounted = false;
/**
* @type {import("./types.js").EffectSignal | null}
*/
let current_branch_effect = null;
const if_effect = render_effect(
() => {
const result = !!condition_fn();
if (block.v !== result || !has_mounted) {
block.v = result;
if (has_mounted) {
const consequent_transitions = block.c;
const alternate_transitions = block.a;
if (result) {
if (alternate_transitions === null || alternate_transitions.size === 0) {
execute_effect(alternate_effect);
} else {
trigger_transitions(alternate_transitions, 'out');
}
if (consequent_transitions === null || consequent_transitions.size === 0) {
execute_effect(consequent_effect);
} else {
trigger_transitions(consequent_transitions, 'in');
}
} else {
if (consequent_transitions === null || consequent_transitions.size === 0) {
execute_effect(consequent_effect);
} else {
trigger_transitions(consequent_transitions, 'out');
}
if (alternate_transitions === null || alternate_transitions.size === 0) {
execute_effect(alternate_effect);
} else {
trigger_transitions(alternate_transitions, 'in');
}
}
} else if (hydrating) {
const comment_text = /** @type {Comment} */ (current_hydration_fragment?.[0])?.data;
if (
!comment_text ||
(comment_text === 'ssr:if:true' && !result) ||
(comment_text === 'ssr:if:false' && result)
) {
// Hydration mismatch: remove everything inside the anchor and start fresh.
// This could happen using when `{#if browser} .. {/if}` in SvelteKit.
remove(current_hydration_fragment);
set_current_hydration_fragment(null);
mismatch = true;
} else {
// Remove the ssr:if comment node or else it will confuse the subsequent hydration algorithm
current_hydration_fragment.shift();
}
}
has_mounted = true;
}
},
block,
false
);
// Managed effect
const consequent_effect = render_effect(
(
/** @type {any} */ _,
/** @type {import("./types.js").EffectSignal | null} */ consequent_effect
) => {
const result = block.v;
if (!result && consequent_dom !== null) {
remove(consequent_dom);
consequent_dom = null;
}
if (result && current_branch_effect !== consequent_effect) {
consequent_fn(anchor_node);
if (mismatch && current_branch_effect === null) {
// Set fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
current_branch_effect = consequent_effect;
consequent_dom = block.d;
}
block.d = null;
},
block,
true
);
block.ce = consequent_effect;
// Managed effect
const alternate_effect = render_effect(
(
/** @type {any} */ _,
/** @type {import("./types.js").EffectSignal | null} */ alternate_effect
) => {
const result = block.v;
if (result && alternate_dom !== null) {
remove(alternate_dom);
alternate_dom = null;
}
if (!result && current_branch_effect !== alternate_effect) {
if (alternate_fn !== null) {
alternate_fn(anchor_node);
}
if (mismatch && current_branch_effect === null) {
// Set fragment so that Svelte continues to operate in hydration mode
set_current_hydration_fragment([]);
}
current_branch_effect = alternate_effect;
alternate_dom = block.d;
}
block.d = null;
},
block,
true
);
block.ae = alternate_effect;
push_destroy_fn(if_effect, () => {
if (consequent_dom !== null) {
remove(consequent_dom);
}
if (alternate_dom !== null) {
remove(alternate_dom);
}
destroy_signal(consequent_effect);
destroy_signal(alternate_effect);
});
block.e = if_effect;
}
export { if_block as if };
/**
* @param {(anchor: Node | null) => void} render_fn
* @returns {void}
@ -1876,293 +1725,6 @@ export function component(anchor_node, component_fn, render_fn) {
block.e = component_effect;
}
/**
* @template V
* @param {Comment} anchor_node
* @param {(() => Promise<V>)} input
* @param {null | ((anchor: Node) => void)} pending_fn
* @param {null | ((anchor: Node, value: V) => void)} then_fn
* @param {null | ((anchor: Node, error: unknown) => void)} catch_fn
* @returns {void}
*/
function await_block(anchor_node, input, pending_fn, then_fn, catch_fn) {
const block = create_await_block();
/** @type {null | import('./types.js').Render} */
let current_render = null;
hydrate_block_anchor(anchor_node);
/** @type {{}} */
let latest_token;
/** @type {typeof UNINITIALIZED | V} */
let resolved_value = UNINITIALIZED;
/** @type {unknown} */
let error = UNINITIALIZED;
let pending = false;
block.r =
/**
* @param {import('./types.js').Transition} transition
* @returns {void}
*/
(transition) => {
const render = /** @type {import('./types.js').Render} */ (current_render);
const transitions = render.s;
transitions.add(transition);
transition.f(() => {
transitions.delete(transition);
if (transitions.size === 0) {
// If the current render has changed since, then we can remove the old render
// effect as it's stale.
if (current_render !== render && render.e !== null) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
destroy_signal(render.e);
render.e = null;
}
}
});
};
const create_render_effect = () => {
/** @type {import('./types.js').Render} */
const render = {
d: null,
e: null,
s: new Set(),
p: current_render
};
const effect = render_effect(
() => {
if (error === UNINITIALIZED) {
if (resolved_value === UNINITIALIZED) {
// pending = true
block.n = true;
if (pending_fn !== null) {
pending_fn(anchor_node);
}
} else if (then_fn !== null) {
// pending = false
block.n = false;
then_fn(anchor_node, resolved_value);
}
} else if (catch_fn !== null) {
// pending = false
block.n = false;
catch_fn(anchor_node, error);
}
render.d = block.d;
block.d = null;
},
block,
true,
true
);
render.e = effect;
current_render = render;
};
const render = () => {
const render = current_render;
if (render === null) {
create_render_effect();
return;
}
const transitions = render.s;
if (transitions.size === 0) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
if (render.e) {
execute_effect(render.e);
} else {
create_render_effect();
}
} else {
create_render_effect();
trigger_transitions(transitions, 'out');
}
};
const await_effect = render_effect(
() => {
const token = {};
latest_token = token;
const promise = input();
if (is_promise(promise)) {
promise.then(
/** @param {V} v */
(v) => {
if (latest_token === token) {
// Ensure UI is in sync before resolving value.
flushSync();
resolved_value = v;
pending = false;
render();
}
},
/** @param {unknown} _error */
(_error) => {
error = _error;
pending = false;
render();
}
);
if (resolved_value !== UNINITIALIZED || error !== UNINITIALIZED) {
error = UNINITIALIZED;
resolved_value = UNINITIALIZED;
}
if (!pending) {
pending = true;
render();
}
} else {
error = UNINITIALIZED;
resolved_value = promise;
pending = false;
render();
}
},
block,
false
);
push_destroy_fn(await_effect, () => {
let render = current_render;
latest_token = {};
while (render !== null) {
const dom = render.d;
if (dom !== null) {
remove(dom);
}
const effect = render.e;
if (effect !== null) {
destroy_signal(effect);
}
render = render.p;
}
});
block.e = await_effect;
}
export { await_block as await };
/**
* @template V
* @param {Comment} anchor_node
* @param {() => V} key
* @param {(anchor: Node) => void} render_fn
* @returns {void}
*/
export function key(anchor_node, key, render_fn) {
const block = create_key_block();
/** @type {null | import('./types.js').Render} */
let current_render = null;
hydrate_block_anchor(anchor_node);
/** @type {V | typeof UNINITIALIZED} */
let key_value = UNINITIALIZED;
let mounted = false;
block.r =
/**
* @param {import('./types.js').Transition} transition
* @returns {void}
*/
(transition) => {
const render = /** @type {import('./types.js').Render} */ (current_render);
const transitions = render.s;
transitions.add(transition);
transition.f(() => {
transitions.delete(transition);
if (transitions.size === 0) {
// If the current render has changed since, then we can remove the old render
// effect as it's stale.
if (current_render !== render && render.e !== null) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
destroy_signal(render.e);
render.e = null;
}
}
});
};
const create_render_effect = () => {
/** @type {import('./types.js').Render} */
const render = {
d: null,
e: null,
s: new Set(),
p: current_render
};
const effect = render_effect(
() => {
render_fn(anchor_node);
render.d = block.d;
block.d = null;
},
block,
true,
true
);
render.e = effect;
current_render = render;
};
const render = () => {
const render = current_render;
if (render === null) {
create_render_effect();
return;
}
const transitions = render.s;
if (transitions.size === 0) {
if (render.d !== null) {
remove(render.d);
render.d = null;
}
if (render.e) {
execute_effect(render.e);
} else {
create_render_effect();
}
} else {
trigger_transitions(transitions, 'out');
create_render_effect();
}
};
const key_effect = render_effect(
() => {
const prev_key_value = key_value;
key_value = key();
if (mounted && safe_not_equal(prev_key_value, key_value)) {
render();
}
},
block,
false
);
// To ensure topological ordering of the key effect to the render effect,
// we trigger the effect after.
render();
mounted = true;
push_destroy_fn(key_effect, () => {
let render = current_render;
while (render !== null) {
const dom = render.d;
if (dom !== null) {
remove(dom);
}
const effect = render.e;
if (effect !== null) {
destroy_signal(effect);
}
render = render.p;
}
});
block.e = key_effect;
}
/**
* @param {Element | Text | Comment} anchor
* @param {boolean} is_html

@ -9,7 +9,7 @@ import {
KEY_BLOCK,
ROOT_BLOCK
} from './block.js';
import { destroy_each_item_block, get_first_element } from './each.js';
import { destroy_each_item_block, get_first_element } from './dom/blocks/each.js';
import { append_child, empty } from './operations.js';
import {
current_block,

@ -40,7 +40,10 @@ export {
freeze,
init
} from './client/runtime.js';
export * from './client/each.js';
export { await_block as await } from './client/dom/blocks/await.js';
export { if_block as if } from './client/dom/blocks/if.js';
export { key_block as key } from './client/dom/blocks/key.js';
export * from './client/dom/blocks/each.js';
export * from './client/render.js';
export * from './client/validate.js';
export { raf } from './client/timing.js';

Loading…
Cancel
Save