pull/10891/head
Rich Harris 2 years ago
parent 5f7fe4d7af
commit 8285757139

@ -8,11 +8,10 @@ import {
} from '../../../../constants.js';
import {
current_hydration_fragment,
get_hydration_fragment,
hydrate_block_anchor,
hydrating,
set_current_hydration_fragment,
set_hydrating
set_hydrating,
update_hydration_fragment
} from '../hydration.js';
import { empty } from '../operations.js';
import { insert, remove } from '../reconciler.js';
@ -124,11 +123,11 @@ function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, re
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 fragment = update_hydration_fragment(hydrating_node);
if (fragment === null) {
// If fragment 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;

@ -1,8 +1,8 @@
import {
current_hydration_fragment,
get_hydration_fragment,
hydrating,
set_current_hydration_fragment
set_current_hydration_fragment,
update_hydration_fragment
} from '../hydration.js';
import { empty } from '../operations.js';
import { render_effect } from '../../reactivity/effects.js';
@ -15,14 +15,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 was_hydrating = hydrating;
let is_hydrating = hydrating;
if (is_hydrating) {
hydration_fragment = get_hydration_fragment(document.head.firstChild);
if (hydrating) {
previous_hydration_fragment = current_hydration_fragment;
set_current_hydration_fragment(hydration_fragment);
update_hydration_fragment(document.head.firstChild);
}
try {
@ -50,7 +48,7 @@ export function head(render_fn) {
}
};
} finally {
if (is_hydrating) {
if (was_hydrating) {
set_current_hydration_fragment(previous_hydration_fragment);
}
}

@ -27,17 +27,26 @@ export let current_hydration_fragment = /** @type {any} */ (null);
* @returns {void}
*/
export function set_current_hydration_fragment(fragment) {
// hydrating = fragment !== null;
current_hydration_fragment = /** @type {import('#client').TemplateNode[]} */ (fragment);
}
/**
* @param {Node | null} first
* @param {boolean} [insert_text] Whether to insert an empty text node if the fragment 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;
}
/**
* 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
* @returns {import('#client').TemplateNode[] | null}
*/
export function get_hydration_fragment(node, insert_text = false) {
function get_hydration_fragment(node, insert_text = false) {
/** @type {import('#client').TemplateNode[]} */
const fragment = [];
@ -46,13 +55,17 @@ export function get_hydration_fragment(node, insert_text = false) {
/** @type {null | string} */
let 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 (data.startsWith('ssr:')) {
const depth = data.slice(4);
if (target_depth === null) {
target_depth = depth;
} else if (depth === target_depth) {
@ -65,15 +78,19 @@ export function get_hydration_fragment(node, insert_text = false) {
} else {
fragment.push(/** @type {Text | Comment | Element} */ (current_node));
}
current_node = next_sibling;
continue;
}
}
if (target_depth !== null) {
fragment.push(/** @type {Text | Comment | Element} */ (current_node));
}
current_node = next_sibling;
}
return null;
}
@ -101,3 +118,24 @@ export function hydrate_block_anchor(node) {
set_current_hydration_fragment(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:') &&
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,4 +1,4 @@
import { current_hydration_fragment, get_hydration_fragment, hydrating } from './hydration.js';
import { capture_fragment_from_node, current_hydration_fragment, hydrating } from './hydration.js';
import { get_descriptor } from '../utils.js';
// We cache the Node and Element prototype methods, so that we can avoid doing
@ -226,24 +226,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<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;
}

@ -6,11 +6,11 @@ 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_block_anchor,
hydrating,
set_current_hydration_fragment,
set_hydrating
set_hydrating,
update_hydration_fragment
} from './dom/hydration.js';
import { array_from } from './utils.js';
import { handle_event_propagation } from './dom/elements/events.js';
@ -142,11 +142,12 @@ export function hydrate(component, options) {
init_operations();
const container = options.target;
const first_child = /** @type {ChildNode} */ (container.firstChild);
const previous_hydration_fragment = current_hydration_fragment;
// 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);
// TODO is both this and the `container.appendChild(anchor)` below necessary?
const hydration_fragment = update_hydration_fragment(first_child, true);
set_hydrating(true);
/** @type {null | Text} */

Loading…
Cancel
Save