From 8a4dfb483b88f3e1384fee786c719d2a0719cf2e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 12 Mar 2024 11:04:11 -0400 Subject: [PATCH] chore: move some code around (#10767) * move code * move code * unused import * move code * unused import * unused import * typo * move snippet code, remove blocks.js * move CSS props code * move html code * remove unused import --------- Co-authored-by: Rich Harris --- .../3-transform/client/visitors/template.js | 2 +- packages/svelte/src/internal/client/block.js | 93 ---- .../internal/client/dom/blocks/css-props.js | 65 +++ .../src/internal/client/dom/blocks/html.js | 29 ++ .../src/internal/client/dom/blocks/snippet.js | 40 ++ .../client/dom/blocks/svelte-component.js | 147 +++++++ .../client/dom/blocks/svelte-element.js | 139 ++++++ .../internal/client/dom/blocks/svelte-head.js | 76 ++++ packages/svelte/src/internal/client/render.js | 415 +----------------- packages/svelte/src/internal/index.js | 6 + 10 files changed, 524 insertions(+), 488 deletions(-) delete mode 100644 packages/svelte/src/internal/client/block.js create mode 100644 packages/svelte/src/internal/client/dom/blocks/css-props.js create mode 100644 packages/svelte/src/internal/client/dom/blocks/html.js create mode 100644 packages/svelte/src/internal/client/dom/blocks/snippet.js create mode 100644 packages/svelte/src/internal/client/dom/blocks/svelte-component.js create mode 100644 packages/svelte/src/internal/client/dom/blocks/svelte-element.js create mode 100644 packages/svelte/src/internal/client/dom/blocks/svelte-head.js diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 1550f2499f..86246742a4 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -1895,7 +1895,7 @@ export const template_visitors = { if (is_reactive) { context.state.after_update.push( - b.stmt(b.call('$.snippet_effect', b.thunk(snippet_function), ...args)) + b.stmt(b.call('$.snippet', b.thunk(snippet_function), ...args)) ); } else { context.state.after_update.push( diff --git a/packages/svelte/src/internal/client/block.js b/packages/svelte/src/internal/client/block.js deleted file mode 100644 index e71cdc7ec7..0000000000 --- a/packages/svelte/src/internal/client/block.js +++ /dev/null @@ -1,93 +0,0 @@ -import { - ROOT_BLOCK, - HEAD_BLOCK, - DYNAMIC_ELEMENT_BLOCK, - DYNAMIC_COMPONENT_BLOCK, - SNIPPET_BLOCK -} from './constants.js'; -import { current_block } from './runtime.js'; - -/** - * @param {boolean} intro - * @returns {import('./types.js').RootBlock} - */ -export function create_root_block(intro) { - return { - // dom - d: null, - // effect - e: null, - // intro - i: intro, - // parent - p: null, - // transition - r: null, - // type - t: ROOT_BLOCK - }; -} - -/** @returns {import('./types.js').HeadBlock} */ -export function create_head_block() { - return { - // dom - d: null, - // effect - e: null, - // parent - p: /** @type {import('./types.js').Block} */ (current_block), - // transition - r: null, - // type - t: HEAD_BLOCK - }; -} - -/** @returns {import('./types.js').DynamicElementBlock} */ -export function create_dynamic_element_block() { - return { - // dom - d: null, - // effect - e: null, - // parent - p: /** @type {import('./types.js').Block} */ (current_block), - // transition - r: null, - // type - t: DYNAMIC_ELEMENT_BLOCK - }; -} - -/** @returns {import('./types.js').DynamicComponentBlock} */ -export function create_dynamic_component_block() { - return { - // dom - d: null, - // effect - e: null, - // parent - p: /** @type {import('./types.js').Block} */ (current_block), - // transition - r: null, - // type - t: DYNAMIC_COMPONENT_BLOCK - }; -} - -/** @returns {import('./types.js').SnippetBlock} */ -export function create_snippet_block() { - return { - // dom - d: null, - // parent - p: /** @type {import('./types.js').Block} */ (current_block), - // effect - e: null, - // transition - r: null, - // type - t: SNIPPET_BLOCK - }; -} diff --git a/packages/svelte/src/internal/client/dom/blocks/css-props.js b/packages/svelte/src/internal/client/dom/blocks/css-props.js new file mode 100644 index 0000000000..6829cdf0e6 --- /dev/null +++ b/packages/svelte/src/internal/client/dom/blocks/css-props.js @@ -0,0 +1,65 @@ +import { namespace_svg } from '../../../../constants.js'; +import { current_hydration_fragment, hydrate_block_anchor, hydrating } from '../../hydration.js'; +import { empty } from '../../operations.js'; +import { render_effect } from '../../reactivity/effects.js'; +import { insert, remove } from '../../reconciler.js'; + +/** + * @param {Element | Text | Comment} anchor + * @param {boolean} is_html + * @param {() => Record} props + * @param {(anchor: Element | Text | Comment) => any} component + * @returns {void} + */ +export function css_props(anchor, is_html, props, component) { + hydrate_block_anchor(anchor); + + /** @type {HTMLElement | SVGElement} */ + let tag; + + /** @type {Text | Comment} */ + let component_anchor; + + if (hydrating) { + // Hydration: css props element is surrounded by a ssr comment ... + tag = /** @type {HTMLElement | SVGElement} */ (current_hydration_fragment[0]); + // ... and the child(ren) of the css props element is also surround by a ssr comment + component_anchor = /** @type {Comment} */ (tag.firstChild); + } else { + if (is_html) { + tag = document.createElement('div'); + tag.style.display = 'contents'; + } else { + tag = document.createElementNS(namespace_svg, 'g'); + } + + insert(tag, null, anchor); + component_anchor = empty(); + tag.appendChild(component_anchor); + } + + component(component_anchor); + + /** @type {Record} */ + let current_props = {}; + + const effect = render_effect(() => { + const next_props = props(); + + for (const key in current_props) { + if (!(key in next_props)) { + tag.style.removeProperty(key); + } + } + + for (const key in next_props) { + tag.style.setProperty(key, next_props[key]); + } + + current_props = next_props; + }); + + effect.ondestroy = () => { + remove(tag); + }; +} diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js new file mode 100644 index 0000000000..1a02ad476f --- /dev/null +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -0,0 +1,29 @@ +import { render_effect } from '../../reactivity/effects.js'; +import { reconcile_html, remove } from '../../reconciler.js'; + +/** + * @param {Element | Text | Comment} dom + * @param {() => string} get_value + * @param {boolean} svg + * @returns {void} + */ +export function html(dom, get_value, svg) { + /** @type {import('#client').TemplateNode | import('#client').TemplateNode[]} */ + let html_dom; + + /** @type {string} */ + let value; + + const effect = render_effect(() => { + if (value !== (value = get_value())) { + if (html_dom) remove(html_dom); + html_dom = reconcile_html(dom, value, svg); + } + }); + + effect.ondestroy = () => { + if (html_dom) { + remove(html_dom); + } + }; +} diff --git a/packages/svelte/src/internal/client/dom/blocks/snippet.js b/packages/svelte/src/internal/client/dom/blocks/snippet.js new file mode 100644 index 0000000000..be7411d88f --- /dev/null +++ b/packages/svelte/src/internal/client/dom/blocks/snippet.js @@ -0,0 +1,40 @@ +import { SNIPPET_BLOCK } from '../../constants.js'; +import { render_effect } from '../../reactivity/effects.js'; +import { remove } from '../../reconciler.js'; +import { current_block, untrack } from '../../runtime.js'; + +/** + * @param {() => Function | null | undefined} get_snippet + * @param {Node} node + * @param {(() => any)[]} args + * @returns {void} + */ +export function snippet(get_snippet, node, ...args) { + /** @type {import('#client').SnippetBlock} */ + const block = { + // dom + d: null, + // parent + p: /** @type {import('#client').Block} */ (current_block), + // effect + e: null, + // transition + r: null, + // type + t: SNIPPET_BLOCK + }; + + 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)); + } + return () => { + if (block.d !== null) { + remove(block.d); + } + }; + }, block); +} diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-component.js b/packages/svelte/src/internal/client/dom/blocks/svelte-component.js new file mode 100644 index 0000000000..aee3a07130 --- /dev/null +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-component.js @@ -0,0 +1,147 @@ +import { DYNAMIC_COMPONENT_BLOCK } from '../../constants.js'; +import { hydrate_block_anchor } from '../../hydration.js'; +import { destroy_effect, render_effect } from '../../reactivity/effects.js'; +import { remove } from '../../reconciler.js'; +import { current_block, execute_effect } from '../../runtime.js'; +import { trigger_transitions } from '../../transitions.js'; + +/** + * @template P + * @param {Comment} anchor_node + * @param {() => (props: P) => void} component_fn + * @param {(component: (props: P) => void) => void} render_fn + * @returns {void} + */ +export function component(anchor_node, component_fn, render_fn) { + /** @type {import('#client').DynamicComponentBlock} */ + const block = { + // dom + d: null, + // effect + e: null, + // parent + p: /** @type {import('#client').Block} */ (current_block), + // transition + r: null, + // type + t: DYNAMIC_COMPONENT_BLOCK + }; + + /** @type {null | import('#client').Render} */ + let current_render = null; + hydrate_block_anchor(anchor_node); + + /** @type {null | ((props: P) => void)} */ + let component = null; + + block.r = + /** + * @param {import('#client').Transition} transition + * @returns {void} + */ + (transition) => { + const render = /** @type {import('#client').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_effect(render.e); + render.e = null; + } + } + }); + }; + + const create_render_effect = () => { + /** @type {import('#client').Render} */ + const render = { + d: null, + e: null, + s: new Set(), + p: current_render + }; + + // Managed effect + render.e = render_effect( + () => { + const current = block.d; + if (current !== null) { + remove(current); + block.d = null; + } + if (component) { + render_fn(component); + } + render.d = block.d; + block.d = null; + }, + block, + true + ); + + 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 component_effect = render_effect( + () => { + const next_component = component_fn(); + if (component !== next_component) { + component = next_component; + render(); + } + }, + block, + false + ); + + component_effect.ondestroy = () => { + let render = current_render; + while (render !== null) { + const dom = render.d; + if (dom !== null) { + remove(dom); + } + const effect = render.e; + if (effect !== null) { + destroy_effect(effect); + } + render = render.p; + } + }; + + block.e = component_effect; +} diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js new file mode 100644 index 0000000000..b4608cca8f --- /dev/null +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -0,0 +1,139 @@ +import { namespace_svg } from '../../../../constants.js'; +import { DYNAMIC_ELEMENT_BLOCK } from '../../constants.js'; +import { current_hydration_fragment, hydrate_block_anchor, hydrating } from '../../hydration.js'; +import { empty } from '../../operations.js'; +import { destroy_effect, render_effect } from '../../reactivity/effects.js'; +import { insert, remove } from '../../reconciler.js'; +import { current_block, execute_effect } from '../../runtime.js'; +import { is_array } from '../../utils.js'; + +/** + * @param {import('#client').Block} block + * @param {Element} from + * @param {Element} to + * @returns {void} + */ +function swap_block_dom(block, from, to) { + const dom = block.d; + if (is_array(dom)) { + for (let i = 0; i < dom.length; i++) { + if (dom[i] === from) { + dom[i] = to; + break; + } + } + } else if (dom === from) { + block.d = to; + } +} + +/** + * @param {Comment} anchor_node + * @param {() => string} tag_fn + * @param {boolean | null} is_svg `null` == not statically known + * @param {undefined | ((element: Element, anchor: Node) => void)} render_fn + * @returns {void} + */ +export function element(anchor_node, tag_fn, is_svg, render_fn) { + /** @type {import('#client').DynamicElementBlock} */ + const block = { + // dom + d: null, + // effect + e: null, + // parent + p: /** @type {import('#client').Block} */ (current_block), + // transition + r: null, + // type + t: DYNAMIC_ELEMENT_BLOCK + }; + + hydrate_block_anchor(anchor_node); + let has_mounted = false; + + /** @type {string} */ + let tag; + + /** @type {null | Element} */ + let element = null; + + const element_effect = render_effect( + () => { + tag = tag_fn(); + if (has_mounted) { + execute_effect(render_effect_signal); + } + has_mounted = true; + }, + block, + false + ); + + // Managed effect + const render_effect_signal = render_effect( + () => { + // 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, + // since the anchor is not attached to its parent / the dom yet. + const ns = + is_svg || tag === 'svg' + ? namespace_svg + : is_svg === false || anchor_node.parentElement?.tagName === 'foreignObject' + ? null + : anchor_node.parentElement?.namespaceURI ?? null; + + const next_element = tag + ? hydrating + ? /** @type {Element} */ (current_hydration_fragment[0]) + : ns + ? document.createElementNS(ns, tag) + : document.createElement(tag) + : null; + + const prev_element = element; + if (prev_element !== null) { + block.d = null; + } + + element = next_element; + if (element !== null && render_fn !== undefined) { + 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); + } + + const has_prev_element = prev_element !== null; + if (has_prev_element) { + remove(prev_element); + } + if (element !== null) { + insert(element, null, anchor_node); + if (has_prev_element) { + const parent_block = block.p; + swap_block_dom(parent_block, prev_element, element); + } + } + }, + block, + true + ); + + element_effect.ondestroy = () => { + if (element !== null) { + remove(element); + block.d = null; + element = null; + } + destroy_effect(render_effect_signal); + }; + + block.e = element_effect; +} diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-head.js b/packages/svelte/src/internal/client/dom/blocks/svelte-head.js new file mode 100644 index 0000000000..08b1c52471 --- /dev/null +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-head.js @@ -0,0 +1,76 @@ +import { HEAD_BLOCK } from '../../constants.js'; +import { + current_hydration_fragment, + get_hydration_fragment, + hydrating, + set_current_hydration_fragment +} from '../../hydration.js'; +import { empty } from '../../operations.js'; +import { render_effect } from '../../reactivity/effects.js'; +import { remove } from '../../reconciler.js'; +import { current_block } from '../../runtime.js'; + +/** + * @param {(anchor: Node | null) => void} render_fn + * @returns {void} + */ +export function head(render_fn) { + /** @type {import('#client').HeadBlock} */ + const block = { + // dom + d: null, + // effect + e: null, + // parent + p: /** @type {import('#client').Block} */ (current_block), + // transition + r: null, + // type + t: HEAD_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; + let previous_hydration_fragment = null; + + let is_hydrating = hydrating; + if (is_hydrating) { + hydration_fragment = get_hydration_fragment(document.head.firstChild); + previous_hydration_fragment = current_hydration_fragment; + set_current_hydration_fragment(hydration_fragment); + } + + 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 + ); + + head_effect.ondestroy = () => { + const current = block.d; + if (current !== null) { + remove(current); + } + }; + + block.e = head_effect; + } finally { + if (is_hydrating) { + set_current_hydration_fragment(previous_hydration_fragment); + } + } +} diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index f21d9729b6..4e1593e1a8 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -10,13 +10,6 @@ import { map_set, set_class_name } from './operations.js'; -import { - create_root_block, - create_dynamic_element_block, - create_head_block, - create_dynamic_component_block, - create_snippet_block -} from './block.js'; import { PassiveDelegatedEvents, DelegatedEvents, @@ -31,11 +24,9 @@ import { create_fragment_from_html, create_fragment_with_script_from_html, insert, - reconcile_html, remove } from './reconciler.js'; import { - execute_effect, untrack, flush_sync, current_block, @@ -73,13 +64,13 @@ import { object_assign } from './utils.js'; import { run } from '../common.js'; -import { bind_transition, trigger_transitions } from './transitions.js'; +import { bind_transition } from './transitions.js'; import { mutable_source, source, set } from './reactivity/sources.js'; import { safe_equals, safe_not_equal } from './reactivity/equality.js'; -import { STATE_SYMBOL } from './constants.js'; +import { ROOT_BLOCK, STATE_SYMBOL } from './constants.js'; /** @type {Set} */ -const all_registerd_events = new Set(); +const all_registered_events = new Set(); /** @type {Set<(events: Array) => void>} */ const root_event_handles = new Set(); @@ -1398,7 +1389,7 @@ export function bind_this(element_or_component, update, get_value, get_parts) { */ export function delegate(events) { for (let i = 0; i < events.length; i++) { - all_registerd_events.add(events[i]); + all_registered_events.add(events[i]); } for (const fn of root_event_handles) { fn(events); @@ -1517,336 +1508,6 @@ export function slot(anchor_node, slot_fn, slot_props, fallback_fn) { } } -/** - * @param {(anchor: Node | null) => void} render_fn - * @returns {void} - */ -export function head(render_fn) { - const block = create_head_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; - let previous_hydration_fragment = null; - let is_hydrating = hydrating; - if (is_hydrating) { - hydration_fragment = get_hydration_fragment(document.head.firstChild); - previous_hydration_fragment = current_hydration_fragment; - set_current_hydration_fragment(hydration_fragment); - } - - 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 - ); - head_effect.ondestroy = () => { - const current = block.d; - if (current !== null) { - remove(current); - } - }; - block.e = head_effect; - } finally { - if (is_hydrating) { - set_current_hydration_fragment(previous_hydration_fragment); - } - } -} - -/** - * @param {import('./types.js').Block} block - * @param {Element} from - * @param {Element} to - * @returns {void} - */ -function swap_block_dom(block, from, to) { - const dom = block.d; - if (is_array(dom)) { - for (let i = 0; i < dom.length; i++) { - if (dom[i] === from) { - dom[i] = to; - break; - } - } - } else if (dom === from) { - block.d = to; - } -} - -/** - * @param {Comment} anchor_node - * @param {() => string} tag_fn - * @param {boolean | null} is_svg `null` == not statically known - * @param {undefined | ((element: Element, anchor: Node) => void)} render_fn - * @returns {void} - */ -export function element(anchor_node, tag_fn, is_svg, render_fn) { - const block = create_dynamic_element_block(); - hydrate_block_anchor(anchor_node); - let has_mounted = false; - - /** @type {string} */ - let tag; - - /** @type {null | Element} */ - let element = null; - const element_effect = render_effect( - () => { - tag = tag_fn(); - if (has_mounted) { - execute_effect(render_effect_signal); - } - has_mounted = true; - }, - block, - false - ); - // Managed effect - const render_effect_signal = render_effect( - () => { - // 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, - // since the anchor is not attached to its parent / the dom yet. - const ns = - is_svg || tag === 'svg' - ? namespace_svg - : is_svg === false || anchor_node.parentElement?.tagName === 'foreignObject' - ? null - : anchor_node.parentElement?.namespaceURI ?? null; - const next_element = tag - ? hydrating - ? /** @type {Element} */ (current_hydration_fragment[0]) - : ns - ? document.createElementNS(ns, tag) - : document.createElement(tag) - : null; - const prev_element = element; - if (prev_element !== null) { - block.d = null; - } - element = next_element; - if (element !== null && render_fn !== undefined) { - 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); - } - const has_prev_element = prev_element !== null; - if (has_prev_element) { - remove(prev_element); - } - if (element !== null) { - insert(element, null, anchor_node); - if (has_prev_element) { - const parent_block = block.p; - swap_block_dom(parent_block, prev_element, element); - } - } - }, - block, - true - ); - element_effect.ondestroy = () => { - if (element !== null) { - remove(element); - block.d = null; - element = null; - } - destroy_effect(render_effect_signal); - }; - block.e = element_effect; -} - -/** - * @template P - * @param {Comment} anchor_node - * @param {() => (props: P) => void} component_fn - * @param {(component: (props: P) => void) => void} render_fn - * @returns {void} - */ -export function component(anchor_node, component_fn, render_fn) { - const block = create_dynamic_component_block(); - - /** @type {null | import('./types.js').Render} */ - let current_render = null; - hydrate_block_anchor(anchor_node); - - /** @type {null | ((props: P) => void)} */ - let component = null; - 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_effect(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 - }; - // Managed effect - const effect = render_effect( - () => { - const current = block.d; - if (current !== null) { - remove(current); - block.d = null; - } - if (component) { - render_fn(component); - } - render.d = block.d; - block.d = null; - }, - block, - 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 component_effect = render_effect( - () => { - const next_component = component_fn(); - if (component !== next_component) { - component = next_component; - render(); - } - }, - block, - false - ); - component_effect.ondestroy = () => { - let render = current_render; - while (render !== null) { - const dom = render.d; - if (dom !== null) { - remove(dom); - } - const effect = render.e; - if (effect !== null) { - destroy_effect(effect); - } - render = render.p; - } - }; - block.e = component_effect; -} - -/** - * @param {Element | Text | Comment} anchor - * @param {boolean} is_html - * @param {() => Record} props - * @param {(anchor: Element | Text | Comment) => any} component - * @returns {void} - */ -export function css_props(anchor, is_html, props, component) { - hydrate_block_anchor(anchor); - - /** @type {HTMLElement | SVGElement} */ - let tag; - - /** @type {Text | Comment} */ - let component_anchor; - if (hydrating) { - // Hydration: css props element is surrounded by a ssr comment ... - tag = /** @type {HTMLElement | SVGElement} */ (current_hydration_fragment[0]); - // ... and the child(ren) of the css props element is also surround by a ssr comment - component_anchor = /** @type {Comment} */ (tag.firstChild); - } else { - if (is_html) { - tag = document.createElement('div'); - tag.style.display = 'contents'; - } else { - tag = document.createElementNS(namespace_svg, 'g'); - } - insert(tag, null, anchor); - component_anchor = empty(); - tag.appendChild(component_anchor); - } - component(component_anchor); - - /** @type {Record} */ - let current_props = {}; - const effect = render_effect(() => { - const next_props = props(); - for (const key in current_props) { - if (!(key in next_props)) { - tag.style.removeProperty(key); - } - } - for (const key in next_props) { - tag.style.setProperty(key, next_props[key]); - } - current_props = next_props; - }); - effect.ondestroy = () => { - remove(tag); - }; -} - /** * @param {unknown} value * @returns {string} @@ -1855,32 +1516,6 @@ export function stringify(value) { return typeof value === 'string' ? value : value == null ? '' : value + ''; } -/** - * @param {Element | Text | Comment} dom - * @param {() => string} get_value - * @param {boolean} svg - * @returns {void} - */ -export function html(dom, get_value, svg) { - /** @type {import('./types.js').TemplateNode | import('./types.js').TemplateNode[]} */ - let html_dom; - /** @type {string} */ - let value; - const effect = render_effect(() => { - if (value !== (value = get_value())) { - if (html_dom) { - remove(html_dom); - } - html_dom = reconcile_html(dom, value, svg); - } - }); - effect.ondestroy = () => { - if (html_dom) { - remove(html_dom); - } - }; -} - /** * @template P * @param {HTMLElement} dom @@ -2580,7 +2215,22 @@ export function hydrate(component, options) { function _mount(Component, options) { const registered_events = new Set(); const container = options.target; - const block = create_root_block(options.intro || false); + + /** @type {import('#client').RootBlock} */ + const block = { + // dom + d: null, + // effect + e: null, + // intro + i: options.intro || false, + // parent + p: null, + // transition + r: null, + // type + t: ROOT_BLOCK + }; /** @type {Exports} */ // @ts-expect-error will be defined because the render effect runs synchronously @@ -2646,7 +2296,7 @@ function _mount(Component, options) { } } }; - event_handle(array_from(all_registerd_events)); + event_handle(array_from(all_registered_events)); root_event_handles.add(event_handle); mounted_components.set(component, () => { @@ -2693,29 +2343,6 @@ export function sanitize_slots(props) { return sanitized; } -/** - * @param {() => Function | null | undefined} get_snippet - * @param {Node} node - * @param {(() => any)[]} args - * @returns {void} - */ -export function snippet_effect(get_snippet, node, ...args) { - const block = create_snippet_block(); - 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)); - } - return () => { - if (block.d !== null) { - remove(block.d); - } - }; - }, block); -} - /** * @param {Node} target * @param {string} style_sheet_id diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index 1503477eb5..ca4ce940a7 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -24,7 +24,13 @@ export * from './client/dev/ownership.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/css-props.js'; export * from './client/dom/blocks/each.js'; +export * from './client/dom/blocks/html.js'; +export * from './client/dom/blocks/snippet.js'; +export * from './client/dom/blocks/svelte-component.js'; +export * from './client/dom/blocks/svelte-element.js'; +export * from './client/dom/blocks/svelte-head.js'; export * from './client/reactivity/deriveds.js'; export * from './client/reactivity/effects.js'; export * from './client/reactivity/sources.js';