diff --git a/packages/svelte/src/internal/client/block.js b/packages/svelte/src/internal/client/block.js index 5db5d748bf..d3d8aadb11 100644 --- a/packages/svelte/src/internal/client/block.js +++ b/packages/svelte/src/internal/client/block.js @@ -32,22 +32,6 @@ export function create_root_block(intro) { }; } -/** @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 { @@ -96,24 +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 diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js new file mode 100644 index 0000000000..39946f63f2 --- /dev/null +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -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)} 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; +} diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index 50b0f0e040..75cfd7faed 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -48,7 +48,7 @@ function create_if_block() { * @param {null | ((anchor: Node) => void)} alternate_fn * @returns {void} */ -export default function (anchor_node, condition_fn, consequent_fn, alternate_fn) { +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 */ diff --git a/packages/svelte/src/internal/client/dom/blocks/key.js b/packages/svelte/src/internal/client/dom/blocks/key.js new file mode 100644 index 0000000000..fda8828475 --- /dev/null +++ b/packages/svelte/src/internal/client/dom/blocks/key.js @@ -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; +} diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index f334fb0312..b500efb1bb 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -12,8 +12,6 @@ import { } from './operations.js'; import { create_root_block, - create_key_block, - create_await_block, create_dynamic_element_block, create_head_block, create_dynamic_component_block, @@ -38,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, @@ -1730,293 +1725,6 @@ export function component(anchor_node, component_fn, render_fn) { block.e = component_effect; } -/** - * @template V - * @param {Comment} anchor_node - * @param {(() => Promise)} 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 diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index 4c4bbb9519..ec0a2cba4f 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -40,7 +40,9 @@ export { freeze, init } from './client/runtime.js'; -export { default as if } from './client/dom/blocks/if.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/each.js'; export * from './client/render.js'; export * from './client/validate.js';