From dfd1819559eaa32d8db10c50d1b8b4d4743b586a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 23 Mar 2024 13:05:12 -0400 Subject: [PATCH] chore: tidy up hydration code (#10891) * remove some indirection * tidy up * tidy * tidy up * simplify * fix * don't attempt to hydrate children of void dynamic element * simplify * tighten up * fix * add note, simplify * tidy up * changeset * revert this change, save for a separate PR --- .../internal/client/dom/blocks/css-props.js | 4 +- .../src/internal/client/dom/blocks/each.js | 36 +++--- .../src/internal/client/dom/blocks/if.js | 19 ++- .../client/dom/blocks/svelte-element.js | 25 ++-- .../internal/client/dom/blocks/svelte-head.js | 23 ++-- .../src/internal/client/dom/hydration.js | 108 ++++++++++++------ .../src/internal/client/dom/operations.js | 84 +++++--------- .../src/internal/client/dom/reconciler.js | 4 +- .../src/internal/client/dom/template.js | 8 +- packages/svelte/src/internal/client/render.js | 37 +++--- 10 files changed, 174 insertions(+), 174 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/css-props.js b/packages/svelte/src/internal/client/dom/blocks/css-props.js index 5bfb41dd91..a8e1ff57b2 100644 --- a/packages/svelte/src/internal/client/dom/blocks/css-props.js +++ b/packages/svelte/src/internal/client/dom/blocks/css-props.js @@ -1,5 +1,5 @@ import { namespace_svg } from '../../../../constants.js'; -import { current_hydration_fragment, hydrate_block_anchor, hydrating } from '../hydration.js'; +import { hydrate_nodes, hydrate_block_anchor, hydrating } from '../hydration.js'; import { empty } from '../operations.js'; import { render_effect } from '../../reactivity/effects.js'; import { remove } from '../reconciler.js'; @@ -22,7 +22,7 @@ export function css_props(anchor, is_html, props, component) { if (hydrating) { // Hydration: css props element is surrounded by a ssr comment ... - tag = /** @type {HTMLElement | SVGElement} */ (current_hydration_fragment[0]); + tag = /** @type {HTMLElement | SVGElement} */ (hydrate_nodes[0]); // ... and the child(ren) of the css props element is also surround by a ssr comment component_anchor = /** @type {Comment} */ (tag.firstChild); } else { diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index bf4d0b5a17..e3a88dafc7 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -7,11 +7,11 @@ import { EACH_KEYED } from '../../../../constants.js'; import { - current_hydration_fragment, - get_hydration_fragment, + hydrate_nodes, hydrate_block_anchor, hydrating, - set_current_hydration_fragment + set_hydrating, + update_hydrate_nodes } from '../hydration.js'; import { empty } from '../operations.js'; import { insert, remove } from '../reconciler.js'; @@ -98,17 +98,16 @@ function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, re let mismatch = false; if (hydrating) { - var is_else = - /** @type {Comment} */ (current_hydration_fragment?.[0])?.data === 'ssr:each_else'; + var is_else = /** @type {Comment} */ (hydrate_nodes?.[0])?.data === 'ssr:each_else'; if (is_else !== (length === 0)) { // hydration mismatch — remove the server-rendered DOM and start over - remove(current_hydration_fragment); - set_current_hydration_fragment(null); + remove(hydrate_nodes); + set_hydrating(false); mismatch = true; } else if (is_else) { // Remove the each_else comment node or else it will confuse the subsequent hydration algorithm - /** @type {import('#client').TemplateNode[]} */ (current_hydration_fragment).shift(); + /** @type {import('#client').TemplateNode[]} */ (hydrate_nodes).shift(); } } @@ -117,18 +116,17 @@ function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, re var b_items = []; // Hydrate block - var hydration_list = /** @type {import('#client').TemplateNode[]} */ ( - current_hydration_fragment - ); + var hydration_list = /** @type {import('#client').TemplateNode[]} */ (hydrate_nodes); var hydrating_node = hydration_list[0]; for (var i = 0; i < length; i++) { - var fragment = get_hydration_fragment(hydrating_node); - set_current_hydration_fragment(fragment); - if (!fragment) { - // If fragment is null, then that means that the server rendered less items than what - // the client code specifies -> break out and continue with client-side node creation + var nodes = update_hydrate_nodes(hydrating_node); + + if (nodes === null) { + // If `nodes` is null, then that means that the server rendered fewer items than what + // expected, so break out and continue appending non-hydrated items mismatch = true; + set_hydrating(false); break; } @@ -137,7 +135,7 @@ function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, re // TODO helperise this hydrating_node = /** @type {import('#client').TemplateNode} */ ( /** @type {Node} */ ( - /** @type {Node} */ (fragment[fragment.length - 1] || hydrating_node).nextSibling + /** @type {Node} */ (nodes[nodes.length - 1] || hydrating_node).nextSibling ).nextSibling ); } @@ -175,8 +173,8 @@ function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, re } if (mismatch) { - // Set a fragment so that Svelte continues to operate in hydration mode - set_current_hydration_fragment([]); + // continue in hydration mode + set_hydrating(true); } }); diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index 217ccac00d..84b294ccbb 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -1,10 +1,5 @@ import { IS_ELSEIF } from '../../constants.js'; -import { - current_hydration_fragment, - hydrate_block_anchor, - hydrating, - set_current_hydration_fragment -} from '../hydration.js'; +import { hydrate_nodes, hydrate_block_anchor, hydrating, set_hydrating } from '../hydration.js'; import { remove } from '../reconciler.js'; import { destroy_effect, @@ -40,7 +35,7 @@ export function if_block(anchor, get_condition, consequent_fn, alternate_fn, els let mismatch = false; if (hydrating) { - const comment_text = /** @type {Comment} */ (current_hydration_fragment?.[0])?.data; + const comment_text = /** @type {Comment} */ (hydrate_nodes?.[0])?.data; if ( !comment_text || @@ -49,12 +44,12 @@ export function if_block(anchor, get_condition, consequent_fn, alternate_fn, els ) { // 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); + remove(hydrate_nodes); + set_hydrating(false); mismatch = true; } else { // Remove the ssr:if comment node or else it will confuse the subsequent hydration algorithm - current_hydration_fragment.shift(); + hydrate_nodes.shift(); } } @@ -85,8 +80,8 @@ export function if_block(anchor, get_condition, consequent_fn, alternate_fn, els } if (mismatch) { - // Set fragment so that Svelte continues to operate in hydration mode - set_current_hydration_fragment([]); + // continue in hydration mode + set_hydrating(true); } }); diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js index bd099a1985..225721e0ff 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -1,5 +1,5 @@ import { namespace_svg } from '../../../../constants.js'; -import { current_hydration_fragment, hydrate_block_anchor, hydrating } from '../hydration.js'; +import { hydrate_nodes, hydrate_block_anchor, hydrating } from '../hydration.js'; import { empty } from '../operations.js'; import { destroy_effect, @@ -105,22 +105,25 @@ export function element(anchor, get_tag, is_svg, render_fn) { effect = render_effect(() => { const prev_element = element; element = hydrating - ? /** @type {Element} */ (current_hydration_fragment[0]) + ? /** @type {Element} */ (hydrate_nodes[0]) : ns ? document.createElementNS(ns, next_tag) : document.createElement(next_tag); if (render_fn) { - 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); + // 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 + var child_anchor = hydrating + ? /** @type {Comment} */ (element.firstChild) + : element.appendChild(empty()); + + if (child_anchor) { + // `child_anchor` can be undefined if this is a void element with children, + // i.e. `...`. This is + // user error, but we warn on it elsewhere (in dev) so here we just + // silently ignore it + render_fn(element, child_anchor); } - render_fn(element, anchor); } anchor.before(element); diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-head.js b/packages/svelte/src/internal/client/dom/blocks/svelte-head.js index 3c6bfbcf68..cd7ef3b318 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-head.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-head.js @@ -1,9 +1,4 @@ -import { - current_hydration_fragment, - get_hydration_fragment, - hydrating, - set_current_hydration_fragment -} from '../hydration.js'; +import { hydrate_nodes, hydrating, set_hydrate_nodes, update_hydrate_nodes } from '../hydration.js'; import { empty } from '../operations.js'; import { render_effect } from '../../reactivity/effects.js'; import { remove } from '../reconciler.js'; @@ -15,14 +10,12 @@ import { remove } from '../reconciler.js'; export function head(render_fn) { // 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 previous_hydrate_nodes = null; + let was_hydrating = hydrating; - 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); + if (hydrating) { + previous_hydrate_nodes = hydrate_nodes; + update_hydrate_nodes(document.head.firstChild); } try { @@ -50,8 +43,8 @@ export function head(render_fn) { } }; } finally { - if (is_hydrating) { - set_current_hydration_fragment(previous_hydration_fragment); + if (was_hydrating) { + set_hydrate_nodes(previous_hydrate_nodes); } } } diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index c896454659..efb8543589 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -1,5 +1,3 @@ -// Handle hydration - import { schedule_task } from './task.js'; import { empty } from './operations.js'; @@ -9,66 +7,79 @@ import { empty } from './operations.js'; */ export let hydrating = false; +/** @param {boolean} value */ +export function set_hydrating(value) { + hydrating = value; +} + /** * Array of nodes to traverse for hydration. This will be null if we're not hydrating, but for * the sake of simplicity we're not going to use `null` checks everywhere and instead rely on * the `hydrating` flag to tell whether or not we're in hydration mode at which point this is set. - * @type {import('../types.js').TemplateNode[]} + * @type {import('#client').TemplateNode[]} */ -export let current_hydration_fragment = /** @type {any} */ (null); +export let hydrate_nodes = /** @type {any} */ (null); /** - * @param {null | import('../types.js').TemplateNode[]} fragment + * @param {null | import('#client').TemplateNode[]} nodes * @returns {void} */ -export function set_current_hydration_fragment(fragment) { - hydrating = fragment !== null; - current_hydration_fragment = /** @type {import('../types.js').TemplateNode[]} */ (fragment); +export function set_hydrate_nodes(nodes) { + hydrate_nodes = /** @type {import('#client').TemplateNode[]} */ (nodes); +} + +/** + * @param {Node | null} first + * @param {boolean} [insert_text] Whether to insert an empty text node if `nodes` is empty + */ +export function update_hydrate_nodes(first, insert_text) { + const nodes = get_hydrate_nodes(first, insert_text); + set_hydrate_nodes(nodes); + return nodes; } /** * Returns all nodes between the first `` comment tag pair encountered. * @param {Node | null} node - * @param {boolean} [insert_text] Whether to insert an empty text node if the fragment is empty - * @returns {import('../types.js').TemplateNode[] | null} + * @param {boolean} [insert_text] Whether to insert an empty text node if `nodes` is empty + * @returns {import('#client').TemplateNode[] | null} */ -export function get_hydration_fragment(node, insert_text = false) { - /** @type {import('../types.js').TemplateNode[]} */ - const fragment = []; +function get_hydrate_nodes(node, insert_text = false) { + /** @type {import('#client').TemplateNode[]} */ + var nodes = []; - /** @type {null | Node} */ - let current_node = node; + var current_node = /** @type {null | import('#client').TemplateNode} */ (node); /** @type {null | string} */ - let target_depth = null; + var target_depth = null; + while (current_node !== null) { - const node_type = current_node.nodeType; - const next_sibling = current_node.nextSibling; - if (node_type === 8) { - const data = /** @type {Comment} */ (current_node).data; + if (current_node.nodeType === 8) { + var data = /** @type {Comment} */ (current_node).data; + if (data.startsWith('ssr:')) { - const depth = data.slice(4); + var depth = data.slice(4); + if (target_depth === null) { target_depth = depth; } else if (depth === target_depth) { - if (insert_text && fragment.length === 0) { - const text = empty(); - fragment.push(text); - /** @type {Node} */ (current_node.parentNode).insertBefore(text, current_node); + if (insert_text && nodes.length === 0) { + var text = empty(); + nodes.push(text); + current_node.before(text); } - return fragment; + return nodes; } else { - fragment.push(/** @type {Text | Comment | Element} */ (current_node)); + nodes.push(current_node); } - current_node = next_sibling; - continue; } + } else if (target_depth !== null) { + nodes.push(current_node); } - if (target_depth !== null) { - fragment.push(/** @type {Text | Comment | Element} */ (current_node)); - } - current_node = next_sibling; + + current_node = /** @type {null | import('#client').TemplateNode} */ (current_node.nextSibling); } + return null; } @@ -81,18 +92,39 @@ export function hydrate_block_anchor(node) { if (node.nodeType === 8) { // @ts-ignore - let fragment = node.$$fragment; - if (fragment === undefined) { - fragment = get_hydration_fragment(node); + let nodes = node.$$fragment; + if (nodes === undefined) { + nodes = get_hydrate_nodes(node); } else { schedule_task(() => { // @ts-expect-error clean up memory node.$$fragment = undefined; }); } - set_current_hydration_fragment(fragment); + set_hydrate_nodes(nodes); } else { const first_child = /** @type {Element | null} */ (node.firstChild); - set_current_hydration_fragment(first_child === null ? [] : [first_child]); + set_hydrate_nodes(first_child === null ? [] : [first_child]); + } +} + +/** + * Expects to only be called in hydration mode + * @param {Node} node + * @returns {Node} + */ +export function capture_fragment_from_node(node) { + if ( + node.nodeType === 8 && + /** @type {Comment} */ (node).data.startsWith('ssr:') && + hydrate_nodes[hydrate_nodes.length - 1] !== node + ) { + const nodes = /** @type {Node[]} */ (get_hydrate_nodes(node)); + const last_child = nodes[nodes.length - 1] || node; + const target = /** @type {Node} */ (last_child.nextSibling); + // @ts-ignore + target.$$fragment = nodes; + return target; } + return node; } diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index 8b8746cb3f..0ef60f52c8 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -1,4 +1,4 @@ -import { current_hydration_fragment, get_hydration_fragment, hydrating } from './hydration.js'; +import { capture_fragment_from_node, hydrate_nodes, hydrating } from './hydration.js'; import { get_descriptor } from '../utils.js'; // We cache the Node and Element prototype methods, so that we can avoid doing @@ -123,17 +123,14 @@ export function empty() { /*#__NO_SIDE_EFFECTS__*/ export function child(node) { const child = first_child_get.call(node); - if (hydrating) { - // Child can be null if we have an element with a single child, like `

{text}

`, where `text` is empty - if (child === null) { - const text = empty(); - node.appendChild(text); - return text; - } else { - return capture_fragment_from_node(child); - } + if (!hydrating) return child; + + // Child can be null if we have an element with a single child, like `

{text}

`, where `text` is empty + if (child === null) { + return node.appendChild(empty()); } - return child; + + return capture_fragment_from_node(child); } /** @@ -144,28 +141,26 @@ export function child(node) { */ /*#__NO_SIDE_EFFECTS__*/ export function child_frag(node, is_text) { - if (hydrating) { - const first_node = /** @type {Node[]} */ (node)[0]; + if (!hydrating) { + return first_child_get.call(/** @type {Node} */ (node)); + } - // if an {expression} is empty during SSR, there might be no - // text node to hydrate — we must therefore create one - if (is_text && first_node?.nodeType !== 3) { - const text = empty(); - current_hydration_fragment.unshift(text); - if (first_node) { - /** @type {DocumentFragment} */ (first_node.parentNode).insertBefore(text, first_node); - } - return text; - } + const first_node = /** @type {import('#client').TemplateNode[]} */ (node)[0]; - if (first_node !== null) { - return capture_fragment_from_node(first_node); - } + // if an {expression} is empty during SSR, there might be no + // text node to hydrate — we must therefore create one + if (is_text && first_node?.nodeType !== 3) { + const text = empty(); + hydrate_nodes.unshift(text); + first_node?.before(text); + return text; + } - return first_node; + if (first_node !== null) { + return capture_fragment_from_node(first_node); } - return first_child_get.call(/** @type {Node} */ (node)); + return first_node; } /** @@ -177,19 +172,18 @@ export function child_frag(node, is_text) { /*#__NO_SIDE_EFFECTS__*/ export function sibling(node, is_text = false) { const next_sibling = next_sibling_get.call(node); + if (hydrating) { // if a sibling {expression} is empty during SSR, there might be no // text node to hydrate — we must therefore create one if (is_text && next_sibling?.nodeType !== 3) { const text = empty(); if (next_sibling) { - const index = current_hydration_fragment.indexOf( - /** @type {Text | Comment | Element} */ (next_sibling) - ); - current_hydration_fragment.splice(index, 0, text); - /** @type {DocumentFragment} */ (next_sibling.parentNode).insertBefore(text, next_sibling); + const index = hydrate_nodes.indexOf(/** @type {Text | Comment | Element} */ (next_sibling)); + hydrate_nodes.splice(index, 0, text); + next_sibling.before(text); } else { - current_hydration_fragment.push(text); + hydrate_nodes.push(text); } return text; @@ -199,6 +193,7 @@ export function sibling(node, is_text = false) { return capture_fragment_from_node(next_sibling); } } + return next_sibling; } @@ -226,24 +221,3 @@ export function clear_text_content(node) { export function create_element(name) { return document.createElement(name); } - -/** - * Expects to only be called in hydration mode - * @param {Node} node - * @returns {Node} - */ -function capture_fragment_from_node(node) { - if ( - node.nodeType === 8 && - /** @type {Comment} */ (node).data.startsWith('ssr:') && - current_hydration_fragment[current_hydration_fragment.length - 1] !== node - ) { - const fragment = /** @type {Array} */ (get_hydration_fragment(node)); - const last_child = fragment[fragment.length - 1] || node; - const target = /** @type {Node} */ (last_child.nextSibling); - // @ts-ignore - target.$$fragment = fragment; - return target; - } - return node; -} diff --git a/packages/svelte/src/internal/client/dom/reconciler.js b/packages/svelte/src/internal/client/dom/reconciler.js index 02be902598..78ae58c80c 100644 --- a/packages/svelte/src/internal/client/dom/reconciler.js +++ b/packages/svelte/src/internal/client/dom/reconciler.js @@ -1,5 +1,5 @@ import { append_child } from './operations.js'; -import { current_hydration_fragment, hydrate_block_anchor, hydrating } from './hydration.js'; +import { hydrate_nodes, hydrate_block_anchor, hydrating } from './hydration.js'; import { is_array } from '../utils.js'; /** @param {string} html */ @@ -76,7 +76,7 @@ export function remove(current) { export function reconcile_html(target, value, svg) { hydrate_block_anchor(target); if (hydrating) { - return current_hydration_fragment; + return hydrate_nodes; } var html = value + ''; // Even if html is the empty string we need to continue to insert something or diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 76fb609b77..dc10c05dd9 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -1,4 +1,4 @@ -import { current_hydration_fragment, hydrate_block_anchor, hydrating } from './hydration.js'; +import { hydrate_nodes, hydrate_block_anchor, hydrating } from './hydration.js'; import { child, clone_node, empty } from './operations.js'; import { create_fragment_from_html, @@ -94,9 +94,9 @@ function open_template(is_fragment, use_clone_node, anchor, template_element_fn) } // In ssr+hydration optimization mode, we might remove the template_element, // so we need to is_fragment flag to properly handle hydrated content accordingly. - const fragment = current_hydration_fragment; - if (fragment !== null) { - return is_fragment ? fragment : /** @type {Element} */ (fragment[0]); + const nodes = hydrate_nodes; + if (nodes !== null) { + return is_fragment ? nodes : /** @type {Element} */ (nodes[0]); } } return use_clone_node diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 7c3151e773..d8948cfe08 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -5,11 +5,12 @@ import { remove } from './dom/reconciler.js'; import { flush_sync, push, pop, current_component_context } from './runtime.js'; import { render_effect, destroy_effect } from './reactivity/effects.js'; import { - current_hydration_fragment, - get_hydration_fragment, + hydrate_nodes, hydrate_block_anchor, hydrating, - set_current_hydration_fragment + set_hydrate_nodes, + set_hydrating, + update_hydrate_nodes } from './dom/hydration.js'; import { array_from } from './utils.js'; import { handle_event_propagation } from './dom/elements/events.js'; @@ -113,7 +114,6 @@ export function createRoot() { * @returns {Exports} */ export function mount(component, options) { - init_operations(); const anchor = empty(); options.target.appendChild(anchor); // Don't flush previous effects to ensure order of outer effects stays consistent @@ -138,18 +138,20 @@ export function mount(component, options) { * @returns {Exports} */ export function hydrate(component, options) { - init_operations(); const container = options.target; const first_child = /** @type {ChildNode} */ (container.firstChild); + const previous_hydrate_nodes = hydrate_nodes; + // Call with insert_text == true to prevent empty {expressions} resulting in an empty - // fragment array, resulting in a hydration error down the line - const hydration_fragment = get_hydration_fragment(first_child, true); - const previous_hydration_fragment = current_hydration_fragment; - set_current_hydration_fragment(hydration_fragment); + // `nodes` array, resulting in a hydration error down the line + // TODO is both this and the `container.appendChild(anchor)` below necessary? + const nodes = update_hydrate_nodes(first_child, true); + set_hydrating(true); /** @type {null | Text} */ let anchor = null; - if (hydration_fragment === null) { + + if (nodes === null) { anchor = empty(); container.appendChild(anchor); } @@ -162,12 +164,12 @@ export function hydrate(component, options) { const instance = _mount(component, { ...options, anchor }); // flush_sync will run this callback and then synchronously run any pending effects, // which don't belong to the hydration phase anymore - therefore reset it here - set_current_hydration_fragment(null); + set_hydrating(false); finished_hydrating = true; return instance; }, false); } catch (error) { - if (!finished_hydrating && options.recover !== false && hydration_fragment !== null) { + if (!finished_hydrating && options.recover !== false && nodes !== null) { // eslint-disable-next-line no-console console.error( 'ERR_SVELTE_HYDRATION_MISMATCH' + @@ -176,16 +178,17 @@ export function hydrate(component, options) { : ''), error ); - remove(hydration_fragment); + remove(nodes); first_child.remove(); - hydration_fragment[hydration_fragment.length - 1]?.nextSibling?.remove(); - set_current_hydration_fragment(null); + nodes[nodes.length - 1]?.nextSibling?.remove(); + set_hydrating(false); return mount(component, options); } else { throw error; } } finally { - set_current_hydration_fragment(previous_hydration_fragment); + set_hydrating(!!previous_hydrate_nodes); + set_hydrate_nodes(previous_hydrate_nodes); } } @@ -206,6 +209,8 @@ export function hydrate(component, options) { * @returns {Exports} */ function _mount(Component, options) { + init_operations(); + const registered_events = new Set(); const container = options.target;