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
pull/10893/head
Rich Harris 1 year ago committed by GitHub
parent 7f10642add
commit dfd1819559
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,5 +1,5 @@
import { namespace_svg } from '../../../../constants.js'; 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 { empty } from '../operations.js';
import { render_effect } from '../../reactivity/effects.js'; import { render_effect } from '../../reactivity/effects.js';
import { remove } from '../reconciler.js'; import { remove } from '../reconciler.js';
@ -22,7 +22,7 @@ export function css_props(anchor, is_html, props, component) {
if (hydrating) { if (hydrating) {
// Hydration: css props element is surrounded by a ssr comment ... // 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 // ... and the child(ren) of the css props element is also surround by a ssr comment
component_anchor = /** @type {Comment} */ (tag.firstChild); component_anchor = /** @type {Comment} */ (tag.firstChild);
} else { } else {

@ -7,11 +7,11 @@ import {
EACH_KEYED EACH_KEYED
} from '../../../../constants.js'; } from '../../../../constants.js';
import { import {
current_hydration_fragment, hydrate_nodes,
get_hydration_fragment,
hydrate_block_anchor, hydrate_block_anchor,
hydrating, hydrating,
set_current_hydration_fragment set_hydrating,
update_hydrate_nodes
} from '../hydration.js'; } from '../hydration.js';
import { empty } from '../operations.js'; import { empty } from '../operations.js';
import { insert, remove } from '../reconciler.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; let mismatch = false;
if (hydrating) { if (hydrating) {
var is_else = var is_else = /** @type {Comment} */ (hydrate_nodes?.[0])?.data === 'ssr:each_else';
/** @type {Comment} */ (current_hydration_fragment?.[0])?.data === 'ssr:each_else';
if (is_else !== (length === 0)) { if (is_else !== (length === 0)) {
// hydration mismatch — remove the server-rendered DOM and start over // hydration mismatch — remove the server-rendered DOM and start over
remove(current_hydration_fragment); remove(hydrate_nodes);
set_current_hydration_fragment(null); set_hydrating(false);
mismatch = true; mismatch = true;
} else if (is_else) { } else if (is_else) {
// Remove the each_else comment node or else it will confuse the subsequent hydration algorithm // 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 = []; var b_items = [];
// Hydrate block // Hydrate block
var hydration_list = /** @type {import('#client').TemplateNode[]} */ ( var hydration_list = /** @type {import('#client').TemplateNode[]} */ (hydrate_nodes);
current_hydration_fragment
);
var hydrating_node = hydration_list[0]; var hydrating_node = hydration_list[0];
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
var fragment = get_hydration_fragment(hydrating_node); var nodes = update_hydrate_nodes(hydrating_node);
set_current_hydration_fragment(fragment);
if (!fragment) { if (nodes === null) {
// If fragment is null, then that means that the server rendered less items than what // If `nodes` is null, then that means that the server rendered fewer items than what
// the client code specifies -> break out and continue with client-side node creation // expected, so break out and continue appending non-hydrated items
mismatch = true; mismatch = true;
set_hydrating(false);
break; break;
} }
@ -137,7 +135,7 @@ function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, re
// TODO helperise this // TODO helperise this
hydrating_node = /** @type {import('#client').TemplateNode} */ ( hydrating_node = /** @type {import('#client').TemplateNode} */ (
/** @type {Node} */ ( /** @type {Node} */ (
/** @type {Node} */ (fragment[fragment.length - 1] || hydrating_node).nextSibling /** @type {Node} */ (nodes[nodes.length - 1] || hydrating_node).nextSibling
).nextSibling ).nextSibling
); );
} }
@ -175,8 +173,8 @@ function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, re
} }
if (mismatch) { if (mismatch) {
// Set a fragment so that Svelte continues to operate in hydration mode // continue in hydration mode
set_current_hydration_fragment([]); set_hydrating(true);
} }
}); });

@ -1,10 +1,5 @@
import { IS_ELSEIF } from '../../constants.js'; import { IS_ELSEIF } from '../../constants.js';
import { import { hydrate_nodes, hydrate_block_anchor, hydrating, set_hydrating } from '../hydration.js';
current_hydration_fragment,
hydrate_block_anchor,
hydrating,
set_current_hydration_fragment
} from '../hydration.js';
import { remove } from '../reconciler.js'; import { remove } from '../reconciler.js';
import { import {
destroy_effect, destroy_effect,
@ -40,7 +35,7 @@ export function if_block(anchor, get_condition, consequent_fn, alternate_fn, els
let mismatch = false; let mismatch = false;
if (hydrating) { if (hydrating) {
const comment_text = /** @type {Comment} */ (current_hydration_fragment?.[0])?.data; const comment_text = /** @type {Comment} */ (hydrate_nodes?.[0])?.data;
if ( if (
!comment_text || !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. // Hydration mismatch: remove everything inside the anchor and start fresh.
// This could happen using when `{#if browser} .. {/if}` in SvelteKit. // This could happen using when `{#if browser} .. {/if}` in SvelteKit.
remove(current_hydration_fragment); remove(hydrate_nodes);
set_current_hydration_fragment(null); set_hydrating(false);
mismatch = true; mismatch = true;
} else { } else {
// Remove the ssr:if comment node or else it will confuse the subsequent hydration algorithm // 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) { if (mismatch) {
// Set fragment so that Svelte continues to operate in hydration mode // continue in hydration mode
set_current_hydration_fragment([]); set_hydrating(true);
} }
}); });

@ -1,5 +1,5 @@
import { namespace_svg } from '../../../../constants.js'; 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 { empty } from '../operations.js';
import { import {
destroy_effect, destroy_effect,
@ -105,22 +105,25 @@ export function element(anchor, get_tag, is_svg, render_fn) {
effect = render_effect(() => { effect = render_effect(() => {
const prev_element = element; const prev_element = element;
element = hydrating element = hydrating
? /** @type {Element} */ (current_hydration_fragment[0]) ? /** @type {Element} */ (hydrate_nodes[0])
: ns : ns
? document.createElementNS(ns, next_tag) ? document.createElementNS(ns, next_tag)
: document.createElement(next_tag); : document.createElement(next_tag);
if (render_fn) { if (render_fn) {
let anchor; // If hydrating, use the existing ssr comment as the anchor so that the
if (hydrating) { // inner open and close methods can pick up the existing nodes correctly
// Use the existing ssr comment as the anchor so that the inner open and close var child_anchor = hydrating
// methods can pick up the existing nodes correctly ? /** @type {Comment} */ (element.firstChild)
anchor = /** @type {Comment} */ (element.firstChild); : element.appendChild(empty());
} else {
anchor = empty(); if (child_anchor) {
element.appendChild(anchor); // `child_anchor` can be undefined if this is a void element with children,
// i.e. `<svelte:element this={"hr"}>...</svelte:element>`. 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); anchor.before(element);

@ -1,9 +1,4 @@
import { import { hydrate_nodes, hydrating, set_hydrate_nodes, update_hydrate_nodes } from '../hydration.js';
current_hydration_fragment,
get_hydration_fragment,
hydrating,
set_current_hydration_fragment
} from '../hydration.js';
import { empty } from '../operations.js'; import { empty } from '../operations.js';
import { render_effect } from '../../reactivity/effects.js'; import { render_effect } from '../../reactivity/effects.js';
import { remove } from '../reconciler.js'; import { remove } from '../reconciler.js';
@ -15,14 +10,12 @@ import { remove } from '../reconciler.js';
export function head(render_fn) { export function head(render_fn) {
// The head function may be called after the first hydration pass and ssr comment nodes may still be present, // 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. // therefore we need to skip that when we detect that we're not in hydration mode.
let hydration_fragment = null; let previous_hydrate_nodes = null;
let previous_hydration_fragment = null; let was_hydrating = hydrating;
let is_hydrating = hydrating; if (hydrating) {
if (is_hydrating) { previous_hydrate_nodes = hydrate_nodes;
hydration_fragment = get_hydration_fragment(document.head.firstChild); update_hydrate_nodes(document.head.firstChild);
previous_hydration_fragment = current_hydration_fragment;
set_current_hydration_fragment(hydration_fragment);
} }
try { try {
@ -50,8 +43,8 @@ export function head(render_fn) {
} }
}; };
} finally { } finally {
if (is_hydrating) { if (was_hydrating) {
set_current_hydration_fragment(previous_hydration_fragment); set_hydrate_nodes(previous_hydrate_nodes);
} }
} }
} }

@ -1,5 +1,3 @@
// Handle hydration
import { schedule_task } from './task.js'; import { schedule_task } from './task.js';
import { empty } from './operations.js'; import { empty } from './operations.js';
@ -9,66 +7,79 @@ import { empty } from './operations.js';
*/ */
export let hydrating = false; 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 * 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 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. * 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} * @returns {void}
*/ */
export function set_current_hydration_fragment(fragment) { export function set_hydrate_nodes(nodes) {
hydrating = fragment !== null; hydrate_nodes = /** @type {import('#client').TemplateNode[]} */ (nodes);
current_hydration_fragment = /** @type {import('../types.js').TemplateNode[]} */ (fragment); }
/**
* @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 `<!--ssr:...-->` comment tag pair encountered. * Returns all nodes between the first `<!--ssr:...-->` comment tag pair encountered.
* @param {Node | null} node * @param {Node | null} node
* @param {boolean} [insert_text] Whether to insert an empty text node if the fragment is empty * @param {boolean} [insert_text] Whether to insert an empty text node if `nodes` is empty
* @returns {import('../types.js').TemplateNode[] | null} * @returns {import('#client').TemplateNode[] | null}
*/ */
export function get_hydration_fragment(node, insert_text = false) { function get_hydrate_nodes(node, insert_text = false) {
/** @type {import('../types.js').TemplateNode[]} */ /** @type {import('#client').TemplateNode[]} */
const fragment = []; var nodes = [];
/** @type {null | Node} */ var current_node = /** @type {null | import('#client').TemplateNode} */ (node);
let current_node = node;
/** @type {null | string} */ /** @type {null | string} */
let target_depth = null; var target_depth = null;
while (current_node !== null) { while (current_node !== null) {
const node_type = current_node.nodeType; if (current_node.nodeType === 8) {
const next_sibling = current_node.nextSibling; var data = /** @type {Comment} */ (current_node).data;
if (node_type === 8) {
const data = /** @type {Comment} */ (current_node).data;
if (data.startsWith('ssr:')) { if (data.startsWith('ssr:')) {
const depth = data.slice(4); var depth = data.slice(4);
if (target_depth === null) { if (target_depth === null) {
target_depth = depth; target_depth = depth;
} else if (depth === target_depth) { } else if (depth === target_depth) {
if (insert_text && fragment.length === 0) { if (insert_text && nodes.length === 0) {
const text = empty(); var text = empty();
fragment.push(text); nodes.push(text);
/** @type {Node} */ (current_node.parentNode).insertBefore(text, current_node); current_node.before(text);
} }
return fragment; return nodes;
} else { } 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 = /** @type {null | import('#client').TemplateNode} */ (current_node.nextSibling);
}
current_node = next_sibling;
} }
return null; return null;
} }
@ -81,18 +92,39 @@ export function hydrate_block_anchor(node) {
if (node.nodeType === 8) { if (node.nodeType === 8) {
// @ts-ignore // @ts-ignore
let fragment = node.$$fragment; let nodes = node.$$fragment;
if (fragment === undefined) { if (nodes === undefined) {
fragment = get_hydration_fragment(node); nodes = get_hydrate_nodes(node);
} else { } else {
schedule_task(() => { schedule_task(() => {
// @ts-expect-error clean up memory // @ts-expect-error clean up memory
node.$$fragment = undefined; node.$$fragment = undefined;
}); });
} }
set_current_hydration_fragment(fragment); set_hydrate_nodes(nodes);
} else { } else {
const first_child = /** @type {Element | null} */ (node.firstChild); 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;
} }

@ -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'; import { get_descriptor } from '../utils.js';
// We cache the Node and Element prototype methods, so that we can avoid doing // We cache the Node and Element prototype methods, so that we can avoid doing
@ -123,17 +123,14 @@ export function empty() {
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function child(node) { export function child(node) {
const child = first_child_get.call(node); const child = first_child_get.call(node);
if (hydrating) { if (!hydrating) return child;
// Child can be null if we have an element with a single child, like `<p>{text}</p>`, where `text` is empty // Child can be null if we have an element with a single child, like `<p>{text}</p>`, where `text` is empty
if (child === null) { if (child === null) {
const text = empty(); return node.appendChild(empty());
node.appendChild(text);
return text;
} else {
return capture_fragment_from_node(child);
}
} }
return child;
return capture_fragment_from_node(child);
} }
/** /**
@ -144,17 +141,18 @@ export function child(node) {
*/ */
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function child_frag(node, is_text) { export function child_frag(node, is_text) {
if (hydrating) { if (!hydrating) {
const first_node = /** @type {Node[]} */ (node)[0]; return first_child_get.call(/** @type {Node} */ (node));
}
const first_node = /** @type {import('#client').TemplateNode[]} */ (node)[0];
// if an {expression} is empty during SSR, there might be no // if an {expression} is empty during SSR, there might be no
// text node to hydrate — we must therefore create one // text node to hydrate — we must therefore create one
if (is_text && first_node?.nodeType !== 3) { if (is_text && first_node?.nodeType !== 3) {
const text = empty(); const text = empty();
current_hydration_fragment.unshift(text); hydrate_nodes.unshift(text);
if (first_node) { first_node?.before(text);
/** @type {DocumentFragment} */ (first_node.parentNode).insertBefore(text, first_node);
}
return text; return text;
} }
@ -163,9 +161,6 @@ export function child_frag(node, is_text) {
} }
return first_node; return first_node;
}
return first_child_get.call(/** @type {Node} */ (node));
} }
/** /**
@ -177,19 +172,18 @@ export function child_frag(node, is_text) {
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function sibling(node, is_text = false) { export function sibling(node, is_text = false) {
const next_sibling = next_sibling_get.call(node); const next_sibling = next_sibling_get.call(node);
if (hydrating) { if (hydrating) {
// if a sibling {expression} is empty during SSR, there might be no // if a sibling {expression} is empty during SSR, there might be no
// text node to hydrate — we must therefore create one // text node to hydrate — we must therefore create one
if (is_text && next_sibling?.nodeType !== 3) { if (is_text && next_sibling?.nodeType !== 3) {
const text = empty(); const text = empty();
if (next_sibling) { if (next_sibling) {
const index = current_hydration_fragment.indexOf( const index = hydrate_nodes.indexOf(/** @type {Text | Comment | Element} */ (next_sibling));
/** @type {Text | Comment | Element} */ (next_sibling) hydrate_nodes.splice(index, 0, text);
); next_sibling.before(text);
current_hydration_fragment.splice(index, 0, text);
/** @type {DocumentFragment} */ (next_sibling.parentNode).insertBefore(text, next_sibling);
} else { } else {
current_hydration_fragment.push(text); hydrate_nodes.push(text);
} }
return text; return text;
@ -199,6 +193,7 @@ export function sibling(node, is_text = false) {
return capture_fragment_from_node(next_sibling); return capture_fragment_from_node(next_sibling);
} }
} }
return next_sibling; return next_sibling;
} }
@ -226,24 +221,3 @@ export function clear_text_content(node) {
export function create_element(name) { export function create_element(name) {
return document.createElement(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<Element | Text | Comment>} */ (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;
}

@ -1,5 +1,5 @@
import { append_child } from './operations.js'; 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'; import { is_array } from '../utils.js';
/** @param {string} html */ /** @param {string} html */
@ -76,7 +76,7 @@ export function remove(current) {
export function reconcile_html(target, value, svg) { export function reconcile_html(target, value, svg) {
hydrate_block_anchor(target); hydrate_block_anchor(target);
if (hydrating) { if (hydrating) {
return current_hydration_fragment; return hydrate_nodes;
} }
var html = value + ''; var html = value + '';
// Even if html is the empty string we need to continue to insert something or // Even if html is the empty string we need to continue to insert something or

@ -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 { child, clone_node, empty } from './operations.js';
import { import {
create_fragment_from_html, 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, // In ssr+hydration optimization mode, we might remove the template_element,
// so we need to is_fragment flag to properly handle hydrated content accordingly. // so we need to is_fragment flag to properly handle hydrated content accordingly.
const fragment = current_hydration_fragment; const nodes = hydrate_nodes;
if (fragment !== null) { if (nodes !== null) {
return is_fragment ? fragment : /** @type {Element} */ (fragment[0]); return is_fragment ? nodes : /** @type {Element} */ (nodes[0]);
} }
} }
return use_clone_node return use_clone_node

@ -5,11 +5,12 @@ import { remove } from './dom/reconciler.js';
import { flush_sync, push, pop, current_component_context } from './runtime.js'; import { flush_sync, push, pop, current_component_context } from './runtime.js';
import { render_effect, destroy_effect } from './reactivity/effects.js'; import { render_effect, destroy_effect } from './reactivity/effects.js';
import { import {
current_hydration_fragment, hydrate_nodes,
get_hydration_fragment,
hydrate_block_anchor, hydrate_block_anchor,
hydrating, hydrating,
set_current_hydration_fragment set_hydrate_nodes,
set_hydrating,
update_hydrate_nodes
} from './dom/hydration.js'; } from './dom/hydration.js';
import { array_from } from './utils.js'; import { array_from } from './utils.js';
import { handle_event_propagation } from './dom/elements/events.js'; import { handle_event_propagation } from './dom/elements/events.js';
@ -113,7 +114,6 @@ export function createRoot() {
* @returns {Exports} * @returns {Exports}
*/ */
export function mount(component, options) { export function mount(component, options) {
init_operations();
const anchor = empty(); const anchor = empty();
options.target.appendChild(anchor); options.target.appendChild(anchor);
// Don't flush previous effects to ensure order of outer effects stays consistent // Don't flush previous effects to ensure order of outer effects stays consistent
@ -138,18 +138,20 @@ export function mount(component, options) {
* @returns {Exports} * @returns {Exports}
*/ */
export function hydrate(component, options) { export function hydrate(component, options) {
init_operations();
const container = options.target; const container = options.target;
const first_child = /** @type {ChildNode} */ (container.firstChild); 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 // Call with insert_text == true to prevent empty {expressions} resulting in an empty
// fragment array, resulting in a hydration error down the line // `nodes` array, resulting in a hydration error down the line
const hydration_fragment = get_hydration_fragment(first_child, true); // TODO is both this and the `container.appendChild(anchor)` below necessary?
const previous_hydration_fragment = current_hydration_fragment; const nodes = update_hydrate_nodes(first_child, true);
set_current_hydration_fragment(hydration_fragment); set_hydrating(true);
/** @type {null | Text} */ /** @type {null | Text} */
let anchor = null; let anchor = null;
if (hydration_fragment === null) {
if (nodes === null) {
anchor = empty(); anchor = empty();
container.appendChild(anchor); container.appendChild(anchor);
} }
@ -162,12 +164,12 @@ export function hydrate(component, options) {
const instance = _mount(component, { ...options, anchor }); const instance = _mount(component, { ...options, anchor });
// flush_sync will run this callback and then synchronously run any pending effects, // 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 // which don't belong to the hydration phase anymore - therefore reset it here
set_current_hydration_fragment(null); set_hydrating(false);
finished_hydrating = true; finished_hydrating = true;
return instance; return instance;
}, false); }, false);
} catch (error) { } 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 // eslint-disable-next-line no-console
console.error( console.error(
'ERR_SVELTE_HYDRATION_MISMATCH' + 'ERR_SVELTE_HYDRATION_MISMATCH' +
@ -176,16 +178,17 @@ export function hydrate(component, options) {
: ''), : ''),
error error
); );
remove(hydration_fragment); remove(nodes);
first_child.remove(); first_child.remove();
hydration_fragment[hydration_fragment.length - 1]?.nextSibling?.remove(); nodes[nodes.length - 1]?.nextSibling?.remove();
set_current_hydration_fragment(null); set_hydrating(false);
return mount(component, options); return mount(component, options);
} else { } else {
throw error; throw error;
} }
} finally { } 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} * @returns {Exports}
*/ */
function _mount(Component, options) { function _mount(Component, options) {
init_operations();
const registered_events = new Set(); const registered_events = new Set();
const container = options.target; const container = options.target;

Loading…
Cancel
Save