From 2c196030695f2409c3a399a8f52365044b7998bc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 20 Feb 2024 23:44:54 -0500 Subject: [PATCH] blockless if block --- .../src/internal/client/dom/blocks/if.js | 206 +++++------------- .../client/reactivity/computations.js | 18 ++ packages/svelte/src/internal/client/render.js | 5 +- 3 files changed, 76 insertions(+), 153 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index 9573b7cca1..164cd7ea60 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -1,4 +1,3 @@ -import { IF_BLOCK } from '../../block.js'; import { current_hydration_fragment, hydrate_block_anchor, @@ -6,35 +5,7 @@ import { set_current_hydration_fragment } from '../../hydration.js'; import { remove } from '../../reconciler.js'; -import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js'; -import { render_effect } from '../../reactivity/computations.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 - }; -} +import { pause_effect, render_effect, resume_effect } from '../../reactivity/computations.js'; /** * @param {Comment} anchor_node @@ -44,138 +15,69 @@ function create_if_block() { * @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} */ - let consequent_dom = null; - /** @type {null | import('../../types.js').TemplateNode | Array} */ - let alternate_dom = null; - let has_mounted = false; - /** - * @type {import('../../types.js').EffectSignal | null} - */ - let current_branch_effect = null; + /** @type {import('../../types.js').EffectSignal | null} */ + let consequent_effect; + + /** @type {import('../../types.js').EffectSignal | null} */ + let alternate_effect; + + /** @type {boolean | null} */ + let condition = null; + + let mounted = false; + + render_effect(() => { + if (condition === (condition = condition_fn())) return; - 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; + if (hydrating) { + const comment_text = /** @type {Comment} */ (current_hydration_fragment?.[0])?.data; + if ( + !comment_text || + (comment_text === 'ssr:if:true' && !condition) || + (comment_text === 'ssr:if:false' && condition) + ) { + // 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(); } - }, - 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 (condition) { + if (consequent_effect) { + resume_effect(consequent_effect); + } else { + consequent_effect = render_effect(() => consequent_fn(anchor_node)); } - 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; + + if (alternate_effect) { + pause_effect(alternate_effect, () => { + alternate_effect = null; + }); } - 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; + } else { + if (alternate_effect) { + resume_effect(alternate_effect); + } else { + alternate_effect = alternate_fn && render_effect(() => alternate_fn(anchor_node)); } - 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; + + if (consequent_effect) { + pause_effect(consequent_effect, () => { + consequent_effect = null; + }); } - 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; + + mounted = true; } diff --git a/packages/svelte/src/internal/client/reactivity/computations.js b/packages/svelte/src/internal/client/reactivity/computations.js index e36afc1284..8c129eeb3f 100644 --- a/packages/svelte/src/internal/client/reactivity/computations.js +++ b/packages/svelte/src/internal/client/reactivity/computations.js @@ -18,6 +18,7 @@ import { schedule_effect } from '../runtime.js'; import { default_equals, safe_equal } from './equality.js'; +import { remove } from '../reconciler.js'; /** * @template V @@ -254,3 +255,20 @@ export function derived_safe_equal(fn) { signal.e = safe_equal; return signal; } + +/** + * @param {import('../types.js').ComputationSignal} effect + * @param {() => void} done + */ +export function pause_effect(effect, done) { + // TODO pause children + remove(effect.dom); + done(); // TODO defer until transitions have completed +} + +/** + * @param {import('../types.js').ComputationSignal} signal + */ +export function resume_effect(signal) { + // TODO +} diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 139af02404..535e8deeee 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -41,7 +41,8 @@ import { push, current_component_context, pop, - deep_read + deep_read, + current_effect } from './runtime.js'; import { render_effect, effect, managed_effect } from './reactivity/computations.js'; import { @@ -254,6 +255,7 @@ export function comment(anchor) { */ function close_template(dom, is_fragment, anchor) { const block = /** @type {import('./types.js').Block} */ (current_block); + const effect = current_effect; /** @type {import('./types.js').TemplateNode | Array} */ const current = is_fragment @@ -265,6 +267,7 @@ function close_template(dom, is_fragment, anchor) { insert(current, null, anchor); } block.d = current; + effect.dom = current; } /**