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

@ -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,
hydrate_block_anchor, hydrate_block_anchor,
hydrating, hydrating,
set_hydrating, set_hydrating,
update_hydration_fragment 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_hydrating(false); 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,16 +116,14 @@ 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 = update_hydration_fragment(hydrating_node); var nodes = update_hydrate_nodes(hydrating_node);
if (fragment === null) { if (nodes === null) {
// If fragment is null, then that means that the server rendered fewer items than what // 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 // expected, so break out and continue appending non-hydrated items
mismatch = true; mismatch = true;
set_hydrating(false); set_hydrating(false);
@ -138,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
); );
} }
@ -176,7 +173,7 @@ 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_hydrating(true); 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_hydrating
} 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_hydrating(false); 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,7 +80,7 @@ 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_hydrating(true); 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,7 +105,7 @@ 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);

@ -1,9 +1,4 @@
import { import { hydrate_nodes, hydrating, set_hydrate_nodes, update_hydrate_nodes } from '../hydration.js';
current_hydration_fragment,
hydrating,
set_current_hydration_fragment,
update_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,12 +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 previous_hydration_fragment = null; let previous_hydrate_nodes = null;
let was_hydrating = hydrating; let was_hydrating = hydrating;
if (hydrating) { if (hydrating) {
previous_hydration_fragment = current_hydration_fragment; previous_hydrate_nodes = hydrate_nodes;
update_hydration_fragment(document.head.firstChild); update_hydrate_nodes(document.head.firstChild);
} }
try { try {
@ -49,7 +44,7 @@ export function head(render_fn) {
}; };
} finally { } finally {
if (was_hydrating) { 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. * the `hydrating` flag to tell whether or not we're in hydration mode at which point this is set.
* @type {import('#client').TemplateNode[]} * @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} * @returns {void}
*/ */
export function set_current_hydration_fragment(fragment) { export function set_hydrate_nodes(nodes) {
current_hydration_fragment = /** @type {import('#client').TemplateNode[]} */ (fragment); hydrate_nodes = /** @type {import('#client').TemplateNode[]} */ (nodes);
} }
/** /**
* @param {Node | null} first * @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) { export function update_hydrate_nodes(first, insert_text) {
const fragment = get_hydration_fragment(first, insert_text); const nodes = get_hydrate_nodes(first, insert_text);
set_current_hydration_fragment(fragment); set_hydrate_nodes(nodes);
return fragment; 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('#client').TemplateNode[] | null} * @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[]} */ /** @type {import('#client').TemplateNode[]} */
const fragment = []; const nodes = [];
/** @type {null | Node} */ /** @type {null | Node} */
let current_node = node; let current_node = node;
@ -67,14 +67,14 @@ function get_hydration_fragment(node, insert_text = false) {
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(); const text = empty();
fragment.push(text); nodes.push(text);
/** @type {Node} */ (current_node.parentNode).insertBefore(text, current_node); /** @type {Node} */ (current_node.parentNode).insertBefore(text, current_node);
} }
return fragment; return nodes;
} else { } else {
fragment.push(/** @type {Text | Comment | Element} */ (current_node)); nodes.push(/** @type {Text | Comment | Element} */ (current_node));
} }
current_node = next_sibling; current_node = next_sibling;
@ -83,7 +83,7 @@ function get_hydration_fragment(node, insert_text = false) {
} }
if (target_depth !== null) { if (target_depth !== null) {
fragment.push(/** @type {Text | Comment | Element} */ (current_node)); nodes.push(/** @type {Text | Comment | Element} */ (current_node));
} }
current_node = next_sibling; current_node = next_sibling;
@ -101,19 +101,19 @@ 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]);
} }
} }
@ -126,13 +126,13 @@ export function capture_fragment_from_node(node) {
if ( if (
node.nodeType === 8 && node.nodeType === 8 &&
/** @type {Comment} */ (node).data.startsWith('ssr:') && /** @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 nodes = /** @type {Node[]} */ (get_hydrate_nodes(node));
const last_child = fragment[fragment.length - 1] || node; const last_child = nodes[nodes.length - 1] || node;
const target = /** @type {Node} */ (last_child.nextSibling); const target = /** @type {Node} */ (last_child.nextSibling);
// @ts-ignore // @ts-ignore
target.$$fragment = fragment; target.$$fragment = nodes;
return target; return target;
} }
return node; 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'; 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
@ -151,7 +151,7 @@ export function child_frag(node, is_text) {
// 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) { if (first_node) {
/** @type {DocumentFragment} */ (first_node.parentNode).insertBefore(text, 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) { 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);
);
current_hydration_fragment.splice(index, 0, text);
/** @type {DocumentFragment} */ (next_sibling.parentNode).insertBefore(text, next_sibling); /** @type {DocumentFragment} */ (next_sibling.parentNode).insertBefore(text, next_sibling);
} else { } else {
current_hydration_fragment.push(text); hydrate_nodes.push(text);
} }
return text; return text;

@ -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,12 +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,
hydrate_block_anchor, hydrate_block_anchor,
hydrating, hydrating,
set_current_hydration_fragment, set_hydrate_nodes,
set_hydrating, set_hydrating,
update_hydration_fragment 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';
@ -143,17 +143,18 @@ export function hydrate(component, options) {
const container = options.target; const container = options.target;
const first_child = /** @type {ChildNode} */ (container.firstChild); 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 // 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? // 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); 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);
} }
@ -171,7 +172,7 @@ export function hydrate(component, options) {
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' +
@ -180,17 +181,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_hydrating(false); set_hydrating(false);
return mount(component, options); return mount(component, options);
} else { } else {
throw error; throw error;
} }
} finally { } finally {
set_hydrating(!!previous_hydration_fragment); set_hydrating(!!previous_hydrate_nodes);
set_current_hydration_fragment(previous_hydration_fragment); set_hydrate_nodes(previous_hydrate_nodes);
} }
} }

Loading…
Cancel
Save