diff --git a/packages/svelte/scripts/check-treeshakeability.js b/packages/svelte/scripts/check-treeshakeability.js index 66129b8657..be744874c7 100644 --- a/packages/svelte/scripts/check-treeshakeability.js +++ b/packages/svelte/scripts/check-treeshakeability.js @@ -109,7 +109,7 @@ const bundle = await bundle_code( ).js.code ); -if (!bundle.includes('hydrate_nodes')) { +if (!bundle.includes('hydrate_nodes') && !bundle.includes('hydrate_anchor')) { // eslint-disable-next-line no-console console.error(`✅ Hydration code treeshakeable`); } else { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index edbfe19bde..8468d4d594 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -1045,7 +1045,7 @@ function create_block(parent, name, nodes, context) { ); /** @type {import('estree').Expression[]} */ - const args = [b.id('$$anchor'), template_name]; + const args = [template_name]; if (state.metadata.context.template_needs_import_node) { args.push(b.false); @@ -1089,7 +1089,7 @@ function create_block(parent, name, nodes, context) { if (use_comment_template) { // special case — we can use `$.comment` instead of creating a unique template - body.push(b.var(id, b.call('$.comment', b.id('$$anchor')))); + body.push(b.var(id, b.call('$.comment'))); } else { state.hoisted.push( b.var( @@ -1103,7 +1103,7 @@ function create_block(parent, name, nodes, context) { ); /** @type {import('estree').Expression[]} */ - const args = [b.id('$$anchor'), template_name]; + const args = [template_name]; if (state.metadata.context.template_needs_import_node) { args.push(b.false); diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index b437c17389..6afb629e2f 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -1,5 +1,4 @@ import { is_promise } from '../../../common.js'; -import { hydrate_block_anchor } from '../hydration.js'; import { remove } from '../reconciler.js'; import { current_component_context, @@ -23,8 +22,6 @@ import { INERT } from '../../constants.js'; export function await_block(anchor, get_input, pending_fn, then_fn, catch_fn) { const component_context = current_component_context; - hydrate_block_anchor(anchor); - /** @type {any} */ let input; 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 68efe677e7..d9e3e8b088 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 { hydrate_nodes, hydrate_block_anchor, hydrating } from '../hydration.js'; +import { hydrate_anchor, hydrate_nodes, hydrating } from '../hydration.js'; import { empty } from '../operations.js'; import { render_effect } from '../../reactivity/effects.js'; import { remove } from '../reconciler.js'; @@ -12,8 +12,6 @@ import { remove } from '../reconciler.js'; * @returns {void} */ export function css_props(anchor, is_html, props, component) { - hydrate_block_anchor(anchor); - /** @type {HTMLElement | SVGElement} */ let element; @@ -24,7 +22,9 @@ export function css_props(anchor, is_html, props, component) { // Hydration: css props element is surrounded by a ssr comment ... element = /** @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} */ (element.firstChild); + component_anchor = /** @type {Comment} */ ( + hydrate_anchor(/** @type {Comment} */ (element.firstChild)) + ); } else { if (is_html) { element = document.createElement('div'); diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 02ec4b94e7..739601f01e 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -6,13 +6,7 @@ import { EACH_ITEM_REACTIVE, EACH_KEYED } from '../../../../constants.js'; -import { - hydrate_nodes, - hydrate_block_anchor, - hydrating, - set_hydrating, - update_hydrate_nodes -} from '../hydration.js'; +import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrating } from '../hydration.js'; import { empty } from '../operations.js'; import { insert, remove } from '../reconciler.js'; import { untrack } from '../../runtime.js'; @@ -59,11 +53,15 @@ function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, re var state = { flags, items: [] }; var is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; - hydrate_block_anchor(is_controlled ? /** @type {Node} */ (anchor.firstChild) : anchor); if (is_controlled) { var parent_node = /** @type {Element} */ (anchor); - parent_node.append((anchor = empty())); + + anchor = hydrating + ? /** @type {Comment | Text} */ ( + hydrate_anchor(/** @type {Comment | Text} */ (parent_node.firstChild)) + ) + : parent_node.appendChild(empty()); } /** @type {import('#client').Effect | null} */ @@ -115,14 +113,11 @@ function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, re if (hydrating) { var b_items = []; - // Hydrate block - var hydration_list = /** @type {import('#client').TemplateNode[]} */ (hydrate_nodes); - var hydrating_node = hydration_list[0]; + /** @type {Node} */ + var child_anchor = hydrate_nodes[0]; for (var i = 0; i < length; i++) { - var nodes = update_hydrate_nodes(hydrating_node); - - if (nodes === null) { + if (child_anchor.nodeType !== 8 || /** @type {Comment} */ (child_anchor).data !== '[') { // 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; @@ -130,17 +125,19 @@ function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, re break; } + child_anchor = hydrate_anchor(child_anchor); b_items[i] = create_item(array[i], keys?.[i], i, render_fn, flags); - - // TODO helperise this - hydrating_node = /** @type {import('#client').TemplateNode} */ ( - /** @type {Node} */ ( - /** @type {Node} */ (nodes[nodes.length - 1] || hydrating_node).nextSibling - ).nextSibling - ); + child_anchor = /** @type {Comment} */ (child_anchor.nextSibling); } - remove_excess_hydration_nodes(hydration_list, hydrating_node); + // remove excess nodes + if (length > 0) { + while (child_anchor !== anchor) { + var next = /** @type {import('#client').TemplateNode} */ (child_anchor.nextSibling); + /** @type {import('#client').TemplateNode} */ (child_anchor).remove(); + child_anchor = next; + } + } state.items = b_items; } @@ -440,20 +437,6 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) { }); } -/** - * The server could have rendered more list items than the client specifies. - * In that case, we need to remove the remaining server-rendered nodes. - * @param {import('#client').TemplateNode[]} hydration_list - * @param {import('#client').TemplateNode | null} next_node - */ -function remove_excess_hydration_nodes(hydration_list, next_node) { - if (next_node === null) return; - var idx = hydration_list.indexOf(next_node); - if (idx !== -1 && hydration_list.length > idx + 1) { - remove(hydration_list.slice(idx)); - } -} - /** * Longest Increased Subsequence algorithm * @param {Int32Array} a diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index dde4de4dbc..d8e252c3f3 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -1,5 +1,5 @@ import { IS_ELSEIF } from '../../constants.js'; -import { hydrate_nodes, hydrate_block_anchor, hydrating, set_hydrating } from '../hydration.js'; +import { hydrate_nodes, hydrating, set_hydrating } from '../hydration.js'; import { remove } from '../reconciler.js'; import { destroy_effect, @@ -23,8 +23,6 @@ export function if_block( alternate_fn = null, elseif = false ) { - hydrate_block_anchor(anchor); - /** @type {import('#client').Effect | null} */ let consequent_effect = null; diff --git a/packages/svelte/src/internal/client/dom/blocks/key.js b/packages/svelte/src/internal/client/dom/blocks/key.js index 55dcda75f7..66cdb3ed68 100644 --- a/packages/svelte/src/internal/client/dom/blocks/key.js +++ b/packages/svelte/src/internal/client/dom/blocks/key.js @@ -1,5 +1,4 @@ import { UNINITIALIZED } from '../../constants.js'; -import { hydrate_block_anchor } from '../hydration.js'; import { remove } from '../reconciler.js'; import { pause_effect, render_effect } from '../../reactivity/effects.js'; import { safe_not_equal } from '../../reactivity/equality.js'; @@ -12,8 +11,6 @@ import { safe_not_equal } from '../../reactivity/equality.js'; * @returns {void} */ export function key_block(anchor, get_key, render_fn) { - hydrate_block_anchor(anchor); - /** @type {V | typeof UNINITIALIZED} */ let key = UNINITIALIZED; diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-component.js b/packages/svelte/src/internal/client/dom/blocks/svelte-component.js index 7ef6f3d66f..ff00b90127 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-component.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-component.js @@ -1,4 +1,3 @@ -import { hydrate_block_anchor } from '../hydration.js'; import { pause_effect, render_effect } from '../../reactivity/effects.js'; import { remove } from '../reconciler.js'; import { current_effect } from '../../runtime.js'; @@ -14,8 +13,6 @@ import { current_effect } from '../../runtime.js'; * @returns {void} */ export function component(anchor, get_component, render_fn) { - hydrate_block_anchor(anchor); - /** @type {C} */ let component; 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 225721e0ff..58645c4eac 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 { hydrate_nodes, hydrate_block_anchor, hydrating } from '../hydration.js'; +import { hydrate_anchor, hydrate_nodes, hydrating } from '../hydration.js'; import { empty } from '../operations.js'; import { destroy_effect, @@ -44,8 +44,6 @@ function swap_block_dom(effect, from, to) { export function element(anchor, get_tag, is_svg, render_fn) { const parent_effect = /** @type {import('#client').Effect} */ (current_effect); - hydrate_block_anchor(anchor); - /** @type {string | null} */ let tag; @@ -114,7 +112,7 @@ export function element(anchor, get_tag, is_svg, render_fn) { // 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.firstChild && hydrate_anchor(/** @type {Comment} */ (element.firstChild)) : element.appendChild(empty()); if (child_anchor) { 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 5ef9c53ea8..4a534dd101 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-head.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-head.js @@ -1,4 +1,4 @@ -import { hydrate_nodes, hydrating, set_hydrate_nodes, update_hydrate_nodes } from '../hydration.js'; +import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrate_nodes } from '../hydration.js'; import { empty } from '../operations.js'; import { render_effect } from '../../reactivity/effects.js'; import { remove } from '../reconciler.js'; @@ -15,7 +15,13 @@ export function head(render_fn) { if (hydrating) { previous_hydrate_nodes = hydrate_nodes; - update_hydrate_nodes(document.head.firstChild); + + let anchor = /** @type {import('#client').TemplateNode} */ (document.head.firstChild); + while (anchor.nodeType !== 8 || /** @type {Comment} */ (anchor).data !== '[') { + anchor = /** @type {import('#client').TemplateNode} */ (anchor.nextSibling); + } + + anchor = /** @type {import('#client').TemplateNode} */ (hydrate_anchor(anchor)); } var anchor = document.head.appendChild(empty()); diff --git a/packages/svelte/src/internal/client/dom/elements/custom-element.js b/packages/svelte/src/internal/client/dom/elements/custom-element.js index c53447913d..0f6cb7dc67 100644 --- a/packages/svelte/src/internal/client/dom/elements/custom-element.js +++ b/packages/svelte/src/internal/client/dom/elements/custom-element.js @@ -98,7 +98,7 @@ if (typeof HTMLElement === 'function') { * @param {Element} anchor */ return (anchor) => { - const node = open(anchor, () => { + const node = open(() => { const slot = document.createElement('slot'); if (name !== 'default') { slot.name = name; diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 01cc9a29f7..cb3bee021c 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -1,6 +1,3 @@ -import { schedule_task } from './task.js'; -import { empty } from './operations.js'; - /** * Use this variable to guard everything related to hydration code so it can be treeshaken out * if the user doesn't use the `hydrate` method and these code paths are therefore not needed. @@ -29,102 +26,46 @@ export function set_hydrate_nodes(nodes) { } /** - * @param {Node | null} first - * @param {boolean} [insert_text] Whether to insert an empty text node if `nodes` is empty + * This function is only called when `hydrating` is true. If passed a `` opening + * hydration marker, it finds the corresponding closing marker and sets `hydrate_nodes` + * to everything between the markers, before returning the closing marker. + * @param {Node} node + * @returns {Node} */ -export function update_hydrate_nodes(first, insert_text) { - const nodes = get_hydrate_nodes(first, insert_text); - set_hydrate_nodes(nodes); - return nodes; -} +export function hydrate_anchor(node) { + if (node.nodeType !== 8) { + return node; + } -/** - * 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 `nodes` is empty - * @returns {import('#client').TemplateNode[] | null} - */ -function get_hydrate_nodes(node, insert_text = false) { - /** @type {import('#client').TemplateNode[]} */ - var nodes = []; + var current = /** @type {Node | null} */ (node); - var current_node = /** @type {null | import('#client').TemplateNode} */ (node); + // TODO this could have false positives, if a user comment consisted of `[`. need to tighten that up + if (/** @type {Comment} */ (current)?.data !== '[') { + return node; + } + /** @type {Node[]} */ + var nodes = []; var depth = 0; - var will_start = false; - var started = false; - - while (current_node !== null) { - if (current_node.nodeType === 8) { - var data = /** @type {Comment} */ (current_node).data; + while ((current = /** @type {Node} */ (current).nextSibling) !== null) { + if (current.nodeType === 8) { + var data = /** @type {Comment} */ (current).data; if (data === '[') { depth += 1; - will_start = true; } else if (data === ']') { - if (!started) { - // TODO get rid of this — it exists because each blocks are doubly wrapped - return null; + if (depth === 0) { + hydrate_nodes = /** @type {import('#client').TemplateNode[]} */ (nodes); + return current; } - if (--depth === 0) { - if (insert_text && nodes.length === 0) { - var text = empty(); - nodes.push(text); - current_node.before(text); - } - - return nodes; - } + depth -= 1; } } - if (started) { - nodes.push(current_node); - } - - current_node = /** @type {null | import('#client').TemplateNode} */ (current_node.nextSibling); - - started = will_start; + nodes.push(current); } - return null; -} - -/** - * @param {Node} node - * @returns {void} - */ -export function hydrate_block_anchor(node) { - if (!hydrating) return; - - // @ts-ignore - var nodes = node.$$fragment ?? get_hydrate_nodes(node); - set_hydrate_nodes(nodes); -} - -/** - * 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 === '[' && - 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; - schedule_task(() => { - // @ts-expect-error clean up memory - target.$$fragment = undefined; - }); - return target; - } - return node; + throw new Error('Expected a closing hydration marker'); } diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index 4261c66cac..5861e26e14 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -1,4 +1,4 @@ -import { capture_fragment_from_node, hydrate_nodes, hydrating } from './hydration.js'; +import { hydrate_anchor, 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 @@ -132,7 +132,7 @@ export function child(node) { return node.appendChild(empty()); } - return capture_fragment_from_node(child); + return hydrate_anchor(child); } /** @@ -159,11 +159,7 @@ export function first_child(fragment, is_text) { return text; } - if (first_node !== null) { - return capture_fragment_from_node(first_node); - } - - return first_node; + return hydrate_anchor(first_node); } /** @@ -176,28 +172,26 @@ export function first_child(fragment, is_text) { 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 = hydrate_nodes.indexOf(/** @type {Text | Comment | Element} */ (next_sibling)); - hydrate_nodes.splice(index, 0, text); - next_sibling.before(text); - } else { - hydrate_nodes.push(text); - } - - return text; - } + if (!hydrating) { + return next_sibling; + } - if (next_sibling !== null) { - return capture_fragment_from_node(next_sibling); + // 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 = hydrate_nodes.indexOf(/** @type {Text | Comment | Element} */ (next_sibling)); + hydrate_nodes.splice(index, 0, text); + next_sibling.before(text); + } else { + hydrate_nodes.push(text); } + + return text; } - return next_sibling; + return hydrate_anchor(/** @type {Node} */ (next_sibling)); } /** diff --git a/packages/svelte/src/internal/client/dom/reconciler.js b/packages/svelte/src/internal/client/dom/reconciler.js index ca12e72e5b..b580267e4b 100644 --- a/packages/svelte/src/internal/client/dom/reconciler.js +++ b/packages/svelte/src/internal/client/dom/reconciler.js @@ -1,5 +1,4 @@ -import { append_child } from './operations.js'; -import { hydrate_nodes, hydrate_block_anchor, hydrating } from './hydration.js'; +import { hydrate_nodes, hydrating } from './hydration.js'; import { is_array } from '../utils.js'; /** @param {string} html */ @@ -74,7 +73,6 @@ export function remove(current) { * @returns {Element | Comment | (Element | Comment | Text)[]} */ export function reconcile_html(target, value, svg) { - hydrate_block_anchor(target); if (hydrating) { return hydrate_nodes; } diff --git a/packages/svelte/src/internal/client/dom/task.js b/packages/svelte/src/internal/client/dom/task.js index 6568000a36..a14344fd26 100644 --- a/packages/svelte/src/internal/client/dom/task.js +++ b/packages/svelte/src/internal/client/dom/task.js @@ -22,18 +22,6 @@ function process_raf_task() { run_all(tasks); } -/** - * @param {() => void} fn - * @returns {void} - */ -export function schedule_task(fn) { - if (!is_task_queued) { - is_task_queued = true; - setTimeout(process_task, 0); - } - current_queued_tasks.push(fn); -} - /** * @param {() => void} fn * @returns {void} diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 4b438a6baa..9e23f58853 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -1,4 +1,4 @@ -import { hydrate_nodes, hydrate_block_anchor, hydrating } from './hydration.js'; +import { hydrate_nodes, hydrating } from './hydration.js'; import { child, clone_node, empty } from './operations.js'; import { create_fragment_from_html, @@ -83,18 +83,12 @@ export function svg_template_with_script(svg, return_fragment) { /** * @param {boolean} is_fragment * @param {boolean} use_clone_node - * @param {null | Text | Comment | Element} anchor * @param {() => Node} [template_element_fn] * @returns {Element | DocumentFragment | Node[]} */ /*#__NO_SIDE_EFFECTS__*/ -function open_template(is_fragment, use_clone_node, anchor, template_element_fn) { +function open_template(is_fragment, use_clone_node, template_element_fn) { if (hydrating) { - if (anchor !== null) { - // TODO why is this sometimes null and sometimes not? needs clear documentation - hydrate_block_anchor(anchor); - } - return is_fragment ? hydrate_nodes : /** @type {Element} */ (hydrate_nodes[0]); } @@ -104,23 +98,21 @@ function open_template(is_fragment, use_clone_node, anchor, template_element_fn) } /** - * @param {null | Text | Comment | Element} anchor * @param {() => Node} template_element_fn * @param {boolean} [use_clone_node] * @returns {Element} */ -export function open(anchor, template_element_fn, use_clone_node = true) { - return /** @type {Element} */ (open_template(false, use_clone_node, anchor, template_element_fn)); +export function open(template_element_fn, use_clone_node = true) { + return /** @type {Element} */ (open_template(false, use_clone_node, template_element_fn)); } /** - * @param {null | Text | Comment | Element} anchor * @param {() => Node} template_element_fn * @param {boolean} [use_clone_node] * @returns {Element | DocumentFragment | Node[]} */ -export function open_frag(anchor, template_element_fn, use_clone_node = true) { - return open_template(true, use_clone_node, anchor, template_element_fn); +export function open_frag(template_element_fn, use_clone_node = true) { + return open_template(true, use_clone_node, template_element_fn); } const space_template = template(' ', false); @@ -132,7 +124,7 @@ const comment_template = template('', true); /*#__NO_SIDE_EFFECTS__*/ export function space_frag(anchor) { /** @type {Node | null} */ - var node = /** @type {any} */ (open(anchor, space_template)); + var node = /** @type {any} */ (open(space_template)); // if an {expression} is empty during SSR, there might be no // text node to hydrate (or an anchor comment is falsely detected instead) // — we must therefore create one @@ -161,12 +153,9 @@ export function space(anchor) { return anchor; } -/** - * @param {null | Text | Comment | Element} anchor - */ /*#__NO_SIDE_EFFECTS__*/ -export function comment(anchor) { - return open_frag(anchor, comment_template); +export function comment() { + return open_frag(comment_template); } /** diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index fea594d9c8..7c1e223ca7 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -11,12 +11,11 @@ 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 { + hydrate_anchor, hydrate_nodes, - hydrate_block_anchor, hydrating, set_hydrate_nodes, - set_hydrating, - update_hydrate_nodes + set_hydrating } from './dom/hydration.js'; import { array_from } from './utils.js'; import { handle_event_propagation } from './dom/elements/events.js'; @@ -66,7 +65,6 @@ export function set_text(dom, value) { * @param {null | ((anchor: Comment) => void)} fallback_fn */ export function slot(anchor, slot_fn, slot_props, fallback_fn) { - hydrate_block_anchor(anchor); if (slot_fn === undefined) { if (fallback_fn !== null) { fallback_fn(anchor); @@ -138,27 +136,25 @@ export function hydrate(component, options) { 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 - // `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); - let hydrated = false; try { // Don't flush previous effects to ensure order of outer effects stays consistent return flush_sync(() => { - const anchor = nodes === null ? container.appendChild(empty()) : null; + set_hydrating(true); + + const anchor = hydrate_anchor(first_child); 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_hydrating(false); hydrated = true; + return instance; }, false); } catch (error) { - if (!hydrated && options.recover !== false && nodes !== null) { + if (!hydrated && options.recover !== false) { // eslint-disable-next-line no-console console.error( 'ERR_SVELTE_HYDRATION_MISMATCH' + @@ -188,7 +184,7 @@ export function hydrate(component, options) { * @param {import('../../main/public.js').ComponentType>} Component * @param {{ * target: Document | Element | ShadowRoot; - * anchor: null | Text; + * anchor: null | Node; * props?: Props; * events?: { [Property in keyof Events]: (e: Events[Property]) => any }; * context?: Map; diff --git a/packages/svelte/tests/runtime-browser/driver.js b/packages/svelte/tests/runtime-browser/driver.js index 7c8d32274d..7a5603e9b8 100644 --- a/packages/svelte/tests/runtime-browser/driver.js +++ b/packages/svelte/tests/runtime-browser/driver.js @@ -25,7 +25,8 @@ export default async function (target) { target, props: config.props, intro: config.intro, - hydrate: __HYDRATE__ + hydrate: __HYDRATE__, + recover: false }, config.options || {} ); diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index e8074b9e5b..708e2180e6 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -261,7 +261,7 @@ async function run_test_variant( target, immutable: config.immutable, intro: config.intro, - recover: config.recover === undefined ? false : config.recover, + recover: config.recover ?? false, hydrate: variant === 'hydrate' }); diff --git a/packages/svelte/tests/snapshot/samples/bind-this/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-this/_expected/client/index.svelte.js index 15f8e4f211..c5a68bdb8c 100644 --- a/packages/svelte/tests/snapshot/samples/bind-this/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-this/_expected/client/index.svelte.js @@ -7,7 +7,7 @@ export default function Bind_this($$anchor, $$props) { $.push($$props, false); $.init(); - var fragment = $.comment($$anchor); + var fragment = $.comment(); var node = $.first_child(fragment); $.bind_this(Foo(node, {}), ($$value) => foo = $$value, () => foo); diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js index 3f13e05aff..946778169a 100644 --- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js @@ -11,7 +11,7 @@ export default function Main($$anchor, $$props) { // needs to be a snapshot test because jsdom does auto-correct the attribute casing let x = 'test'; let y = () => 'test'; - var fragment = $.open_frag($$anchor, frag, false); + var fragment = $.open_frag(frag, false); var div = $.first_child(fragment); var svg = $.sibling($.sibling(div, true)); var custom_element = $.sibling($.sibling(svg, true)); diff --git a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js index 7e1177d2b7..69beacc312 100644 --- a/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/each-string-template/_expected/client/index.svelte.js @@ -7,7 +7,7 @@ export default function Each_string_template($$anchor, $$props) { $.push($$props, false); $.init(); - var fragment = $.comment($$anchor); + var fragment = $.comment(); var node = $.first_child(fragment); $.each_indexed(node, 1, () => ['foo', 'bar', 'baz'], ($$anchor, thing, $$index) => { diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js index c50bc71263..0fd6ddfa1a 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js @@ -13,7 +13,7 @@ export default function Function_prop_no_getter($$anchor, $$props) { } const plusOne = (num) => num + 1; - var fragment = $.comment($$anchor); + var fragment = $.comment(); var node = $.first_child(fragment); Button(node, { diff --git a/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js index 2b53401f64..0587fc3988 100644 --- a/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/hello-world/_expected/client/index.svelte.js @@ -9,7 +9,7 @@ export default function Hello_world($$anchor, $$props) { $.push($$props, false); $.init(); - var h1 = $.open($$anchor, frag); + var h1 = $.open(frag); $.close($$anchor, h1); $.pop(); diff --git a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js index 309b17fe2f..a714d84f8d 100644 --- a/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/state-proxy-literal/_expected/client/index.svelte.js @@ -17,7 +17,7 @@ export default function State_proxy_literal($$anchor, $$props) { let str = $.source(''); let tpl = $.source(``); - var fragment = $.open_frag($$anchor, frag); + var fragment = $.open_frag(frag); var input = $.first_child(fragment); $.remove_input_attr_defaults(input); diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js index 26c7ca22b1..cf329f7148 100644 --- a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js @@ -7,7 +7,7 @@ export default function Svelte_element($$anchor, $$props) { $.push($$props, true); let tag = $.prop($$props, "tag", 3, 'hr'); - var fragment = $.comment($$anchor); + var fragment = $.comment(); var node = $.first_child(fragment); $.element(node, tag, false);