pull/10891/head
Rich Harris 2 years ago
parent a210ea5693
commit 391df26523

@ -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 {

@ -7,11 +7,11 @@ import {
EACH_KEYED
} from '../../../../constants.js';
import {
current_hydration_fragment,
hydrate_nodes,
hydrate_block_anchor,
hydrating,
set_hydrating,
update_hydration_fragment
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);
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,16 +116,14 @@ 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 = update_hydration_fragment(hydrating_node);
var nodes = update_hydrate_nodes(hydrating_node);
if (fragment === null) {
// If fragment is null, then that means that the server rendered fewer items than what
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);
@ -138,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
);
}
@ -176,7 +173,7 @@ 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
// continue in hydration mode
set_hydrating(true);
}
});

@ -1,10 +1,5 @@
import { IS_ELSEIF } from '../../constants.js';
import {
current_hydration_fragment,
hydrate_block_anchor,
hydrating,
set_hydrating
} 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);
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,7 +80,7 @@ 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
// continue in hydration mode
set_hydrating(true);
}
});

@ -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,7 +105,7 @@ 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);

@ -1,9 +1,4 @@
import {
current_hydration_fragment,
hydrating,
set_current_hydration_fragment,
update_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,12 +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 previous_hydration_fragment = null;
let previous_hydrate_nodes = null;
let was_hydrating = hydrating;
if (hydrating) {
previous_hydration_fragment = current_hydration_fragment;
update_hydration_fragment(document.head.firstChild);
previous_hydrate_nodes = hydrate_nodes;
update_hydrate_nodes(document.head.firstChild);
}
try {
@ -49,7 +44,7 @@ export function head(render_fn) {
};
} finally {
if (was_hydrating) {
set_current_hydration_fragment(previous_hydration_fragment);
set_hydrate_nodes(previous_hydrate_nodes);
}
}
}

@ -18,35 +18,35 @@ export function set_hydrating(value) {
* the `hydrating` flag to tell whether or not we're in hydration mode at which point this is set.
* @type {import('#client').TemplateNode[]}
*/
export let current_hydration_fragment = /** @type {any} */ (null);
export let hydrate_nodes = /** @type {any} */ (null);
/**
* @param {null | import('#client').TemplateNode[]} fragment
* @param {null | import('#client').TemplateNode[]} nodes
* @returns {void}
*/
export function set_current_hydration_fragment(fragment) {
current_hydration_fragment = /** @type {import('#client').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 the fragment is empty
* @param {boolean} [insert_text] Whether to insert an empty text node if `nodes` is empty
*/
export function update_hydration_fragment(first, insert_text) {
const fragment = get_hydration_fragment(first, insert_text);
set_current_hydration_fragment(fragment);
return fragment;
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.
* @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('#client').TemplateNode[] | null}
*/
function get_hydration_fragment(node, insert_text = false) {
function get_hydrate_nodes(node, insert_text = false) {
/** @type {import('#client').TemplateNode[]} */
const fragment = [];
const nodes = [];
/** @type {null | Node} */
let current_node = node;
@ -67,14 +67,14 @@ function get_hydration_fragment(node, insert_text = false) {
if (target_depth === null) {
target_depth = depth;
} else if (depth === target_depth) {
if (insert_text && fragment.length === 0) {
if (insert_text && nodes.length === 0) {
const text = empty();
fragment.push(text);
nodes.push(text);
/** @type {Node} */ (current_node.parentNode).insertBefore(text, current_node);
}
return fragment;
return nodes;
} else {
fragment.push(/** @type {Text | Comment | Element} */ (current_node));
nodes.push(/** @type {Text | Comment | Element} */ (current_node));
}
current_node = next_sibling;
@ -83,7 +83,7 @@ function get_hydration_fragment(node, insert_text = false) {
}
if (target_depth !== null) {
fragment.push(/** @type {Text | Comment | Element} */ (current_node));
nodes.push(/** @type {Text | Comment | Element} */ (current_node));
}
current_node = next_sibling;
@ -101,19 +101,19 @@ 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]);
}
}
@ -126,13 +126,13 @@ export 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
hydrate_nodes[hydrate_nodes.length - 1] !== node
) {
const fragment = /** @type {Array<Element | Text | Comment>} */ (get_hydration_fragment(node));
const last_child = fragment[fragment.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 = fragment;
target.$$fragment = nodes;
return target;
}
return node;

@ -1,4 +1,4 @@
import { capture_fragment_from_node, current_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
@ -151,7 +151,7 @@ export function child_frag(node, is_text) {
// text node to hydrate — we must therefore create one
if (is_text && first_node?.nodeType !== 3) {
const text = empty();
current_hydration_fragment.unshift(text);
hydrate_nodes.unshift(text);
if (first_node) {
/** @type {DocumentFragment} */ (first_node.parentNode).insertBefore(text, first_node);
}
@ -183,13 +183,11 @@ export function sibling(node, is_text = false) {
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);
const index = hydrate_nodes.indexOf(/** @type {Text | Comment | Element} */ (next_sibling));
hydrate_nodes.splice(index, 0, text);
/** @type {DocumentFragment} */ (next_sibling.parentNode).insertBefore(text, next_sibling);
} else {
current_hydration_fragment.push(text);
hydrate_nodes.push(text);
}
return text;

@ -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

@ -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

@ -5,12 +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,
hydrate_nodes,
hydrate_block_anchor,
hydrating,
set_current_hydration_fragment,
set_hydrate_nodes,
set_hydrating,
update_hydration_fragment
update_hydrate_nodes
} from './dom/hydration.js';
import { array_from } from './utils.js';
import { handle_event_propagation } from './dom/elements/events.js';
@ -143,17 +143,18 @@ export function hydrate(component, options) {
const container = options.target;
const first_child = /** @type {ChildNode} */ (container.firstChild);
const previous_hydration_fragment = current_hydration_fragment;
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
// `nodes` array, resulting in a hydration error down the line
// TODO is both this and the `container.appendChild(anchor)` below necessary?
const hydration_fragment = update_hydration_fragment(first_child, true);
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);
}
@ -171,7 +172,7 @@ export function hydrate(component, options) {
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' +
@ -180,17 +181,17 @@ export function hydrate(component, options) {
: ''),
error
);
remove(hydration_fragment);
remove(nodes);
first_child.remove();
hydration_fragment[hydration_fragment.length - 1]?.nextSibling?.remove();
nodes[nodes.length - 1]?.nextSibling?.remove();
set_hydrating(false);
return mount(component, options);
} else {
throw error;
}
} finally {
set_hydrating(!!previous_hydration_fragment);
set_current_hydration_fragment(previous_hydration_fragment);
set_hydrating(!!previous_hydrate_nodes);
set_hydrate_nodes(previous_hydrate_nodes);
}
}

Loading…
Cancel
Save