mirror of https://github.com/sveltejs/svelte
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
parent
71db9edcdc
commit
41e7dab755
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in new issue