From 60d5c965bc72542b308d3a433087f1d9eefe2e35 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 11:40:20 -0400 Subject: [PATCH 01/36] groundwork --- .../internal/client/dom/blocks/svelte-head.js | 2 +- .../svelte/src/internal/client/dom/hydration.js | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) 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 b00a3a242b1e..c5fb0f277115 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-head.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-head.js @@ -1,7 +1,7 @@ import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrate_nodes } from '../hydration.js'; import { empty } from '../operations.js'; import { block } from '../../reactivity/effects.js'; -import { HYDRATION_END, HYDRATION_START } from '../../../../constants.js'; +import { HYDRATION_START } from '../../../../constants.js'; /** * @type {Node | undefined} diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index dee87b31ddcb..937afd83adee 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -21,9 +21,19 @@ export function set_hydrating(value) { */ export let hydrate_nodes = /** @type {any} */ (null); -/** @param {import('#client').TemplateNode[]} nodes */ +/** @type {import('#client').TemplateNode} */ +export let hydrate_start = /** @type {any} */ (null); + +/** @type {import('#client').TemplateNode} */ +export let hydrate_end = /** @type {any} */ (null); + +/** + * @param {import('#client').TemplateNode[]} nodes + */ export function set_hydrate_nodes(nodes) { hydrate_nodes = nodes; + hydrate_start = nodes && nodes[0]; + hydrate_end = nodes && nodes[nodes.length - 1]; } /** @@ -58,6 +68,10 @@ export function hydrate_anchor(node) { } else if (data[0] === HYDRATION_END) { if (depth === 0) { hydrate_nodes = /** @type {import('#client').TemplateNode[]} */ (nodes); + + hydrate_start = hydrate_nodes[0]; + hydrate_end = hydrate_nodes[hydrate_nodes.length - 1]; + return current; } From f6b7b82c36d3252bcb6370c51cb4746ef1a75da9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 11:42:08 -0400 Subject: [PATCH 02/36] use hydrate_start instead of hydrate_nodes[0] --- .../svelte/src/internal/client/dom/blocks/css-props.js | 4 ++-- packages/svelte/src/internal/client/dom/blocks/each.js | 10 ++++++++-- .../src/internal/client/dom/blocks/svelte-element.js | 10 ++++++++-- packages/svelte/src/internal/client/dom/template.js | 10 +++++----- 4 files changed, 23 insertions(+), 11 deletions(-) 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 709d76070004..c0e4d174cb07 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_anchor, hydrate_nodes, hydrating } from '../hydration.js'; +import { hydrate_anchor, hydrate_start, hydrating } from '../hydration.js'; import { empty } from '../operations.js'; import { render_effect } from '../../reactivity/effects.js'; import { remove } from '../reconciler.js'; @@ -20,7 +20,7 @@ export function css_props(anchor, is_html, props, component) { if (hydrating) { // Hydration: css props element is surrounded by a ssr comment ... - element = /** @type {HTMLElement | SVGElement} */ (hydrate_nodes[0]); + element = /** @type {HTMLElement | SVGElement} */ (hydrate_start); // ... and the child(ren) of the css props element is also surround by a ssr comment component_anchor = /** @type {Comment} */ ( hydrate_anchor(/** @type {Comment} */ (element.firstChild)) diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index b0c87d6e5813..2137286ea6c4 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -8,7 +8,13 @@ import { HYDRATION_END_ELSE, HYDRATION_START } from '../../../../constants.js'; -import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrating } from '../hydration.js'; +import { + hydrate_anchor, + hydrate_nodes, + hydrate_start, + hydrating, + set_hydrating +} from '../hydration.js'; import { clear_text_content, empty } from '../operations.js'; import { remove } from '../reconciler.js'; import { untrack } from '../../runtime.js'; @@ -149,7 +155,7 @@ export function each(anchor, flags, get_collection, get_key, render_fn, fallback // this is separate to the previous block because `hydrating` might change if (hydrating) { /** @type {Node} */ - var child_anchor = hydrate_nodes[0]; + var child_anchor = hydrate_start; /** @type {import('#client').EachItem | import('#client').EachState} */ var prev = state; 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 a5ba7f7dcac2..f714cfa318fc 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,11 @@ import { namespace_svg } from '../../../../constants.js'; -import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrate_nodes } from '../hydration.js'; +import { + hydrate_anchor, + hydrate_nodes, + hydrate_start, + hydrating, + set_hydrate_nodes +} from '../hydration.js'; import { empty } from '../operations.js'; import { block, @@ -107,7 +113,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat effect = branch(() => { const prev_element = element; element = hydrating - ? /** @type {Element} */ (hydrate_nodes[0]) + ? /** @type {Element} */ (hydrate_start) : ns ? document.createElementNS(ns, next_tag) : document.createElement(next_tag); diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 8f754248e7eb..9992e8ae2925 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, hydrating } from './hydration.js'; +import { hydrate_nodes, hydrate_start, hydrating } from './hydration.js'; import { clone_node, empty } from './operations.js'; import { create_fragment_from_html } from './reconciler.js'; import { current_effect } from '../runtime.js'; @@ -48,7 +48,7 @@ export function template(content, flags) { return () => { if (hydrating) { - return push_template_node(is_fragment ? hydrate_nodes : hydrate_nodes[0]); + return push_template_node(is_fragment ? hydrate_nodes : hydrate_start); } if (!node) { @@ -106,7 +106,7 @@ export function svg_template(content, flags) { return () => { if (hydrating) { - return push_template_node(is_fragment ? hydrate_nodes : hydrate_nodes[0]); + return push_template_node(is_fragment ? hydrate_nodes : hydrate_start); } if (!node) { @@ -173,7 +173,7 @@ export function mathml_template(content, flags) { return () => { if (hydrating) { - return push_template_node(is_fragment ? hydrate_nodes : hydrate_nodes[0]); + return push_template_node(is_fragment ? hydrate_nodes : hydrate_start); } if (!node) { @@ -240,7 +240,7 @@ function run_scripts(node) { export function text(anchor) { if (!hydrating) return push_template_node(empty()); - var node = hydrate_nodes[0]; + var node = hydrate_start; if (!node) { // if an {expression} is empty during SSR, `hydrate_nodes` will be empty. From b96e16d1c5389b72f78521a3717d1d4c1d79f711 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 11:42:38 -0400 Subject: [PATCH 03/36] unused import --- .../src/internal/client/dom/blocks/svelte-element.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) 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 f714cfa318fc..3ae94e67db12 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -1,11 +1,5 @@ import { namespace_svg } from '../../../../constants.js'; -import { - hydrate_anchor, - hydrate_nodes, - hydrate_start, - hydrating, - set_hydrate_nodes -} from '../hydration.js'; +import { hydrate_anchor, hydrate_start, hydrating, set_hydrate_nodes } from '../hydration.js'; import { empty } from '../operations.js'; import { block, From 54ac2b41b6b562708ab72bdb25febc68924b3dc9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 11:49:48 -0400 Subject: [PATCH 04/36] groundwork --- .../src/internal/client/dom/hydration.js | 25 +++++++++++++++++++ .../src/internal/client/dom/template.js | 15 ++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 937afd83adee..54f88e2c0599 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -95,3 +95,28 @@ export function hydrate_anchor(node) { w.hydration_mismatch(location); throw HYDRATION_ERROR; } + +export function remove_hydrate_nodes() { + let node = hydrate_start; + + while (node) { + const next = node.nextSibling; + + node.remove(); + if (node === hydrate_end) break; + + node = /** @type {import('#client').TemplateNode} */ (next); + } +} + +/** @deprecated */ +export function hydrate_nodes_to_array() { + // TODO get rid of this, just have start/end on effects + let node = hydrate_start; + const nodes = [node]; + while (node.nextSibling) { + nodes.push((node = /** @type {import('#client').TemplateNode} */ (node.nextSibling))); + if (node === hydrate_end) break; + } + return nodes; +} diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 9992e8ae2925..d47d6a8dddf8 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_start, hydrating } from './hydration.js'; +import { hydrate_nodes, hydrate_nodes_to_array, hydrate_start, hydrating } from './hydration.js'; import { clone_node, empty } from './operations.js'; import { create_fragment_from_html } from './reconciler.js'; import { current_effect } from '../runtime.js'; @@ -6,6 +6,19 @@ import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants. import { effect } from '../reactivity/effects.js'; import { is_array } from '../utils.js'; +/** + * @deprecated + * @param {boolean} is_fragment + */ +function get_hydrate_nodes(is_fragment) { + // TODO get rid of this, just have start/end on effects + if (is_fragment) { + return hydrate_nodes_to_array(); + } + + return hydrate_start; +} + /** * @template {import("#client").TemplateNode | import("#client").TemplateNode[]} T * @param {T} dom From 31288582381db3f825364aa25b1a59e0eed3e44f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 12:47:19 -0400 Subject: [PATCH 05/36] groundwork --- packages/svelte/src/internal/client/reactivity/effects.js | 4 ++++ packages/svelte/src/internal/client/reactivity/types.d.ts | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 6411e8109af7..c0aa7a72722d 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -80,6 +80,8 @@ function create_effect(type, fn, sync) { ctx: current_component_context, deps: null, dom: null, + d1: null, + d2: null, f: type | DIRTY, first: null, fn, @@ -361,6 +363,8 @@ export function destroy_effect(effect) { effect.teardown = effect.ctx = effect.dom = + effect.d1 = + effect.d2 = effect.deps = effect.parent = // @ts-expect-error diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 7502443bd10c..1fb5c6ae7e28 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -1,4 +1,4 @@ -import type { ComponentContext, Dom, Equals, TransitionManager } from '#client'; +import type { ComponentContext, Dom, Equals, TemplateNode, TransitionManager } from '#client'; export interface Signal { /** Flags bitmask */ @@ -36,7 +36,10 @@ export interface Derived extends Value, Reaction { export interface Effect extends Reaction { parent: Effect | null; + /** @deprecated */ dom: Dom | null; + d1: TemplateNode | null; + d2: TemplateNode | null; /** The associated component context */ ctx: null | ComponentContext; /** The effect function */ From 182a2c627d5e128d8144f60645d11bfdb0507d8a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 12:48:36 -0400 Subject: [PATCH 06/36] groundwork --- .../svelte/src/internal/client/dom/hydration.js | 13 +++---------- .../src/internal/client/dom/operations.js | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 54f88e2c0599..27246b7b2154 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -1,6 +1,7 @@ import { DEV } from 'esm-env'; import { HYDRATION_END, HYDRATION_START, HYDRATION_ERROR } from '../../../constants.js'; import * as w from '../warnings.js'; +import { remove_nodes } from './operations.js'; /** * Use this variable to guard everything related to hydration code so it can be treeshaken out @@ -96,17 +97,9 @@ export function hydrate_anchor(node) { throw HYDRATION_ERROR; } +/** @deprecated */ export function remove_hydrate_nodes() { - let node = hydrate_start; - - while (node) { - const next = node.nextSibling; - - node.remove(); - if (node === hydrate_end) break; - - node = /** @type {import('#client').TemplateNode} */ (next); - } + remove_nodes(hydrate_start, hydrate_end); } /** @deprecated */ diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index 2307c013b00c..586b8c6ea3e3 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -229,3 +229,20 @@ export function clear_text_content(node) { export function create_element(name) { return document.createElement(name); } + +/** + * @param {import('#client').TemplateNode} from + * @param {import('#client').TemplateNode} to + */ +export function remove_nodes(from, to) { + let node = from; + + while (node) { + const next = node.nextSibling; + + node.remove(); + if (node === to) break; + + node = /** @type {import('#client').TemplateNode} */ (next); + } +} From f264ad10fb502af5e55e00668c854fccbba50bc2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 19:39:08 -0400 Subject: [PATCH 07/36] mostly working --- .../src/internal/client/dom/blocks/html.js | 32 +++---------- .../src/internal/client/dom/blocks/snippet.js | 14 ++++++ .../client/dom/blocks/svelte-element.js | 19 ++------ .../src/internal/client/dom/template.js | 46 ++++++++++++++++++- .../src/internal/client/reactivity/effects.js | 16 +++++-- playgrounds/demo/vite.config.js | 2 +- 6 files changed, 85 insertions(+), 44 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index 6873adf70ce2..e21176e277dc 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -4,27 +4,7 @@ import { current_effect, get } from '../../runtime.js'; import { is_array } from '../../utils.js'; import { hydrate_nodes, hydrating } from '../hydration.js'; import { create_fragment_from_html, remove } from '../reconciler.js'; -import { push_template_node } from '../template.js'; - -/** - * @param {import('#client').Effect} effect - * @param {(Element | Comment | Text)[]} to_remove - * @returns {void} - */ -function remove_from_parent_effect(effect, to_remove) { - const dom = effect.dom; - - if (is_array(dom)) { - for (let i = dom.length - 1; i >= 0; i--) { - if (to_remove.includes(dom[i])) { - dom.splice(i, 1); - break; - } - } - } else if (dom !== null && to_remove.includes(dom)) { - effect.dom = null; - } -} +import { push_template_node, replace_bookends } from '../template.js'; /** * @param {Element | Text | Comment} anchor @@ -34,17 +14,19 @@ function remove_from_parent_effect(effect, to_remove) { * @returns {void} */ export function html(anchor, get_value, svg, mathml) { - const parent_effect = anchor.parentNode !== current_effect?.dom ? current_effect : null; + const parent_effect = /** @type {import('#client').Effect} */ (current_effect); let value = derived(get_value); render_effect(() => { var dom = html_to_dom(anchor, parent_effect, get(value), svg, mathml); if (dom) { + var d1 = is_array(dom) ? dom[0] : dom; + var d2 = is_array(dom) ? dom[dom.length - 1] : dom; + + replace_bookends(parent_effect, d1, d2); + return () => { - if (parent_effect !== null) { - remove_from_parent_effect(parent_effect, is_array(dom) ? dom : [dom]); - } remove(dom); }; } diff --git a/packages/svelte/src/internal/client/dom/blocks/snippet.js b/packages/svelte/src/internal/client/dom/blocks/snippet.js index 5bf13a45d8d4..bc82d30771b6 100644 --- a/packages/svelte/src/internal/client/dom/blocks/snippet.js +++ b/packages/svelte/src/internal/client/dom/blocks/snippet.js @@ -6,6 +6,7 @@ import { dev_current_component_function, set_dev_current_component_function } from '../../runtime.js'; +import { replace_bookends } from '../template.js'; /** * @template {(node: import('#client').TemplateNode, ...args: any[]) => import('#client').Dom} SnippetFn @@ -24,13 +25,26 @@ export function snippet(get_snippet, node, ...args) { block(() => { if (snippet === (snippet = get_snippet())) return; + /** @type {import('#client').TemplateNode | null} */ + let d1 = null; + + /** @type {import('#client').TemplateNode | null} */ + let d2 = null; + if (snippet_effect) { + d1 = snippet_effect.d1; + d2 = snippet_effect.d2; + destroy_effect(snippet_effect); snippet_effect = null; } if (snippet) { snippet_effect = branch(() => /** @type {SnippetFn} */ (snippet)(node, ...args)); + + if (d1 !== null && d2 !== null) { + replace_bookends(snippet_effect, d1, d2); + } } }, EFFECT_TRANSPARENT); } 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 3ae94e67db12..71fbe3d7af58 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -9,7 +9,6 @@ import { render_effect, resume_effect } from '../../reactivity/effects.js'; -import { is_array } from '../../utils.js'; import { set_should_intro } from '../../render.js'; import { current_each_item, set_current_each_item } from './each.js'; import { current_component_context, current_effect } from '../../runtime.js'; @@ -23,18 +22,9 @@ import { DEV } from 'esm-env'; * @returns {void} */ function swap_block_dom(effect, from, to) { - const dom = effect.dom; - - if (is_array(dom)) { - for (let i = 0; i < dom.length; i++) { - if (dom[i] === from) { - dom[i] = to; - break; - } - } - } else if (dom === from) { - effect.dom = to; - } + // TODO should this use `replace_bookends`? + if (effect.d1 === from) effect.d1 = to; + if (effect.d2 === from) effect.d2 = to; } /** @@ -51,6 +41,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat const filename = DEV && location && current_component_context?.function.filename; + // TODO `render_effect` wrapper should be unnecessary? render_effect(() => { /** @type {string | null} */ let tag; @@ -162,7 +153,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat }); return () => { - element?.remove(); + // element?.remove(); }; }); } diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index d47d6a8dddf8..2e8f3d3633b8 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -29,6 +29,12 @@ export function push_template_node( dom, effect = /** @type {import('#client').Effect} */ (current_effect) ) { + if (hydrating && effect.d1 === null) { + effect.d1 = is_array(dom) ? dom[0] : dom; + effect.d2 = is_array(dom) ? dom[dom.length - 1] : dom; + } + + // TODO remove var current_dom = effect.dom; if (current_dom === null) { effect.dom = dom; @@ -43,6 +49,7 @@ export function push_template_node( current_dom.push(dom); } } + return dom; } @@ -281,10 +288,47 @@ export function comment() { * Assign the created (or in hydration mode, traversed) dom elements to the current block * and insert the elements into the dom (in client mode). * @param {Text | Comment | Element} anchor - * @param {import('#client').Dom} dom + * @param {DocumentFragment | Element} dom */ export function append(anchor, dom) { if (!hydrating) { + const effect = /** @type {import('#client').Effect} */ (current_effect); + + // TODO handle component case, where effect.d1/d2 already exist + if (dom.nodeType === 11) { + effect.d1 = /** @type {import('#client').TemplateNode} */ (dom.firstChild); + effect.d2 = /** @type {import('#client').TemplateNode} */ (dom.lastChild); + } else { + effect.d1 = effect.d2 = /** @type {Element} */ (dom); + } + anchor.before(/** @type {Node} */ (dom)); } } + +/** + * + * @param {import('#client').Effect} effect + * @param {import('#client').TemplateNode} d1 + * @param {import('#client').TemplateNode} d2 + */ +export function replace_bookends(effect, d1, d2) { + /** @type {import('#client').Effect | null} */ + var parent = effect; + + // TODO i'm pretty sure we don't need to actually loop here, + // this should only apply to html/snippet tags + + while ((parent = parent.parent)) { + if (parent.d1 === null) { + continue; + } + + if (parent.d1 !== d1 && parent.d2 !== d2) { + break; + } + + if (parent.d1 === d1) parent.d1 = effect.d1; + if (parent.d2 === d2) parent.d2 = effect.d2; + } +} diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index c0aa7a72722d..27ce0e647b8b 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -36,7 +36,8 @@ import { set } from './sources.js'; import { remove } from '../dom/reconciler.js'; import * as e from '../errors.js'; import { DEV } from 'esm-env'; -import { define_property } from '../utils.js'; +import { define_property, is_array } from '../utils.js'; +import { remove_nodes } from '../dom/operations.js'; /** * @param {'$effect' | '$effect.pre' | '$inspect'} rune @@ -66,6 +67,8 @@ export function push_effect(effect, parent_effect) { } } +let uid = 1; + /** * @param {number} type * @param {(() => void | (() => void))} fn @@ -77,6 +80,7 @@ function create_effect(type, fn, sync) { /** @type {import('#client').Effect} */ var effect = { + id: uid++, ctx: current_component_context, deps: null, dom: null, @@ -317,11 +321,17 @@ export function execute_effect_teardown(effect) { */ export function destroy_effect(effect) { var dom = effect.dom; + var d1 = effect.d1; + var d2 = effect.d2; - if (dom !== null) { - remove(dom); + if (d1 !== null && d2 !== null) { + remove_nodes(d1, d2); } + // if (dom !== null) { + // remove(dom); + // } + destroy_effect_children(effect); remove_reactions(effect, 0); set_signal_status(effect, DESTROYED); diff --git a/playgrounds/demo/vite.config.js b/playgrounds/demo/vite.config.js index a6a970e8cd2c..7a44869a4bf3 100644 --- a/playgrounds/demo/vite.config.js +++ b/playgrounds/demo/vite.config.js @@ -3,7 +3,7 @@ import inspect from 'vite-plugin-inspect'; import { svelte } from '@sveltejs/vite-plugin-svelte'; export default defineConfig({ - plugins: [inspect(), svelte()], + plugins: [inspect(), svelte({ compilerOptions: { hmr: false } })], optimizeDeps: { // svelte is a local workspace package, optimizing it would require dev server restarts with --force for every change exclude: ['svelte'] From 10a3df24b4dc85d94e0c097ad7a5c74a224d89e2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 22:23:31 -0400 Subject: [PATCH 08/36] working --- .../src/internal/client/dom/template.js | 4 +- packages/svelte/tests/html_equal.js | 2 +- .../samples/noscript-removal/_config.js | 47 +++++++------------ 3 files changed, 22 insertions(+), 31 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 2e8f3d3633b8..a917d1834198 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -296,7 +296,9 @@ export function append(anchor, dom) { // TODO handle component case, where effect.d1/d2 already exist if (dom.nodeType === 11) { - effect.d1 = /** @type {import('#client').TemplateNode} */ (dom.firstChild); + const d1 = empty(); // TODO do this in template creation. should d2 be an empty as well? or should we be storing the anchor? + /** @type {import('#client').TemplateNode} */ (dom.firstChild).before(d1); + effect.d1 = d1; effect.d2 = /** @type {import('#client').TemplateNode} */ (dom.lastChild); } else { effect.d1 = effect.d2 = /** @type {Element} */ (dom); diff --git a/packages/svelte/tests/html_equal.js b/packages/svelte/tests/html_equal.js index c75e3ed4a125..0ebf1fa6bd53 100644 --- a/packages/svelte/tests/html_equal.js +++ b/packages/svelte/tests/html_equal.js @@ -84,7 +84,7 @@ export function normalize_html( .replace(/\s?onerror="this.__e=event"|\s?onload="this.__e=event"/g, '') .trim(); clean_children(node); - return node.innerHTML.replace(/<\/?noscript\/?>/g, ''); + return node.innerHTML; } catch (err) { throw new Error(`Failed to normalize HTML:\n${html}`); } diff --git a/packages/svelte/tests/runtime-legacy/samples/noscript-removal/_config.js b/packages/svelte/tests/runtime-legacy/samples/noscript-removal/_config.js index feb6fbc4f4cb..d7e1b908c009 100644 --- a/packages/svelte/tests/runtime-legacy/samples/noscript-removal/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/noscript-removal/_config.js @@ -1,38 +1,27 @@ import { test } from '../../test'; export default test({ - ssrHtml: ` - - -
foo
- -
foo
foo
- `, - test({ assert, target, compileOptions, variant }) { + test({ assert, target, variant }) { // if created on client side, should not build noscript if (variant === 'dom') { assert.equal(target.querySelectorAll('noscript').length, 0); + assert.htmlEqual( + target.innerHTML, + ` +
foo
+
foo
foo
+ ` + ); + } else { + assert.equal(target.querySelectorAll('noscript').length, 3); + assert.htmlEqual( + target.innerHTML, + ` + +
foo
+
foo
foo
+ ` + ); } - - // it's okay not to remove the node during hydration - // will not be seen by user anyway - remove_noscript(target); - - assert.htmlEqual( - target.innerHTML, - ` -
foo
-
foo
foo
- ` - ); } }); - -/** - * @param {HTMLElement} target - */ -function remove_noscript(target) { - target.querySelectorAll('noscript').forEach((elem) => { - elem.parentNode?.removeChild(elem); - }); -} From b34f1189f0bfe53aa44e19c408e58005cde728e3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 22:28:00 -0400 Subject: [PATCH 09/36] simplify --- .../src/internal/client/dom/blocks/html.js | 8 +--- .../src/internal/client/dom/blocks/snippet.js | 14 ------ .../client/dom/blocks/svelte-element.js | 2 +- .../src/internal/client/dom/template.js | 43 ------------------- .../src/internal/client/reactivity/effects.js | 8 ---- .../src/internal/client/reactivity/types.d.ts | 2 - 6 files changed, 2 insertions(+), 75 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index e21176e277dc..bf4908394025 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -1,10 +1,9 @@ import { derived } from '../../reactivity/deriveds.js'; import { render_effect } from '../../reactivity/effects.js'; import { current_effect, get } from '../../runtime.js'; -import { is_array } from '../../utils.js'; import { hydrate_nodes, hydrating } from '../hydration.js'; import { create_fragment_from_html, remove } from '../reconciler.js'; -import { push_template_node, replace_bookends } from '../template.js'; +import { push_template_node } from '../template.js'; /** * @param {Element | Text | Comment} anchor @@ -21,11 +20,6 @@ export function html(anchor, get_value, svg, mathml) { var dom = html_to_dom(anchor, parent_effect, get(value), svg, mathml); if (dom) { - var d1 = is_array(dom) ? dom[0] : dom; - var d2 = is_array(dom) ? dom[dom.length - 1] : dom; - - replace_bookends(parent_effect, d1, d2); - return () => { remove(dom); }; diff --git a/packages/svelte/src/internal/client/dom/blocks/snippet.js b/packages/svelte/src/internal/client/dom/blocks/snippet.js index bc82d30771b6..5bf13a45d8d4 100644 --- a/packages/svelte/src/internal/client/dom/blocks/snippet.js +++ b/packages/svelte/src/internal/client/dom/blocks/snippet.js @@ -6,7 +6,6 @@ import { dev_current_component_function, set_dev_current_component_function } from '../../runtime.js'; -import { replace_bookends } from '../template.js'; /** * @template {(node: import('#client').TemplateNode, ...args: any[]) => import('#client').Dom} SnippetFn @@ -25,26 +24,13 @@ export function snippet(get_snippet, node, ...args) { block(() => { if (snippet === (snippet = get_snippet())) return; - /** @type {import('#client').TemplateNode | null} */ - let d1 = null; - - /** @type {import('#client').TemplateNode | null} */ - let d2 = null; - if (snippet_effect) { - d1 = snippet_effect.d1; - d2 = snippet_effect.d2; - destroy_effect(snippet_effect); snippet_effect = null; } if (snippet) { snippet_effect = branch(() => /** @type {SnippetFn} */ (snippet)(node, ...args)); - - if (d1 !== null && d2 !== null) { - replace_bookends(snippet_effect, d1, d2); - } } }, EFFECT_TRANSPARENT); } 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 71fbe3d7af58..3d97ca551495 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -22,7 +22,7 @@ import { DEV } from 'esm-env'; * @returns {void} */ function swap_block_dom(effect, from, to) { - // TODO should this use `replace_bookends`? + // TODO this should be unnecessary... if (effect.d1 === from) effect.d1 = to; if (effect.d2 === from) effect.d2 = to; } diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index a917d1834198..5972ca0a0697 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -34,22 +34,6 @@ export function push_template_node( effect.d2 = is_array(dom) ? dom[dom.length - 1] : dom; } - // TODO remove - var current_dom = effect.dom; - if (current_dom === null) { - effect.dom = dom; - } else { - if (!is_array(current_dom)) { - current_dom = effect.dom = [current_dom]; - } - - if (is_array(dom)) { - current_dom.push(...dom); - } else { - current_dom.push(dom); - } - } - return dom; } @@ -307,30 +291,3 @@ export function append(anchor, dom) { anchor.before(/** @type {Node} */ (dom)); } } - -/** - * - * @param {import('#client').Effect} effect - * @param {import('#client').TemplateNode} d1 - * @param {import('#client').TemplateNode} d2 - */ -export function replace_bookends(effect, d1, d2) { - /** @type {import('#client').Effect | null} */ - var parent = effect; - - // TODO i'm pretty sure we don't need to actually loop here, - // this should only apply to html/snippet tags - - while ((parent = parent.parent)) { - if (parent.d1 === null) { - continue; - } - - if (parent.d1 !== d1 && parent.d2 !== d2) { - break; - } - - if (parent.d1 === d1) parent.d1 = effect.d1; - if (parent.d2 === d2) parent.d2 = effect.d2; - } -} diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 27ce0e647b8b..aadc19e51f5d 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -80,10 +80,8 @@ function create_effect(type, fn, sync) { /** @type {import('#client').Effect} */ var effect = { - id: uid++, ctx: current_component_context, deps: null, - dom: null, d1: null, d2: null, f: type | DIRTY, @@ -320,7 +318,6 @@ export function execute_effect_teardown(effect) { * @returns {void} */ export function destroy_effect(effect) { - var dom = effect.dom; var d1 = effect.d1; var d2 = effect.d2; @@ -328,10 +325,6 @@ export function destroy_effect(effect) { remove_nodes(d1, d2); } - // if (dom !== null) { - // remove(dom); - // } - destroy_effect_children(effect); remove_reactions(effect, 0); set_signal_status(effect, DESTROYED); @@ -372,7 +365,6 @@ export function destroy_effect(effect) { effect.prev = effect.teardown = effect.ctx = - effect.dom = effect.d1 = effect.d2 = effect.deps = diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index 1fb5c6ae7e28..eef3c3a2e3e2 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -36,8 +36,6 @@ export interface Derived extends Value, Reaction { export interface Effect extends Reaction { parent: Effect | null; - /** @deprecated */ - dom: Dom | null; d1: TemplateNode | null; d2: TemplateNode | null; /** The associated component context */ From af0262b12f42e08e12c2656951f22d358966698d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 22:33:01 -0400 Subject: [PATCH 10/36] simplify --- .../src/internal/client/dom/blocks/each.js | 3 +-- .../src/internal/client/dom/blocks/html.js | 8 ------- .../client/dom/blocks/svelte-element.js | 3 --- .../src/internal/client/dom/template.js | 24 +++---------------- 4 files changed, 4 insertions(+), 34 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 2137286ea6c4..fee13b63a762 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -31,7 +31,6 @@ import { import { source, mutable_source, set } from '../../reactivity/sources.js'; import { is_array, is_frozen } from '../../utils.js'; import { INERT, STATE_SYMBOL } from '../../constants.js'; -import { push_template_node } from '../template.js'; /** * The row of a keyed each block that is currently updating. We track this @@ -286,7 +285,7 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { item = items.get(key); if (item === undefined) { - var child_open = push_template_node(empty()); + var child_open = empty(); var child_anchor = current ? current.o : anchor; child_anchor.before(child_open); diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index bf4908394025..c03f87674db1 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -3,7 +3,6 @@ import { render_effect } from '../../reactivity/effects.js'; import { current_effect, get } from '../../runtime.js'; import { hydrate_nodes, hydrating } from '../hydration.js'; import { create_fragment_from_html, remove } from '../reconciler.js'; -import { push_template_node } from '../template.js'; /** * @param {Element | Text | Comment} anchor @@ -57,9 +56,6 @@ function html_to_dom(target, effect, value, svg, mathml) { if (node.childNodes.length === 1) { var child = /** @type {Text | Element | Comment} */ (node.firstChild); target.before(child); - if (effect !== null) { - push_template_node(child, effect); - } return child; } @@ -73,9 +69,5 @@ function html_to_dom(target, effect, value, svg, mathml) { target.before(node); } - if (effect !== null) { - push_template_node(nodes, effect); - } - return nodes; } 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 3d97ca551495..85ebf0f15577 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -12,7 +12,6 @@ import { import { set_should_intro } from '../../render.js'; import { current_each_item, set_current_each_item } from './each.js'; import { current_component_context, current_effect } from '../../runtime.js'; -import { push_template_node } from '../template.js'; import { DEV } from 'esm-env'; /** @@ -139,8 +138,6 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat if (prev_element) { swap_block_dom(parent_effect, prev_element, element); prev_element.remove(); - } else if (!hydrating) { - push_template_node(element, parent_effect); } }); } diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 5972ca0a0697..63a62bf6aacc 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -25,7 +25,7 @@ function get_hydrate_nodes(is_fragment) { * @param {import("#client").Effect} effect * @returns {T} */ -export function push_template_node( +function push_template_node( dom, effect = /** @type {import('#client').Effect} */ (current_effect) ) { @@ -61,12 +61,6 @@ export function template(content, flags) { } var clone = use_import_node ? document.importNode(node, true) : clone_node(node, true); - push_template_node( - is_fragment - ? /** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes]) - : /** @type {import('#client').TemplateNode} */ (clone) - ); - return clone; }; } @@ -128,12 +122,6 @@ export function svg_template(content, flags) { var clone = clone_node(node, true); - push_template_node( - is_fragment - ? /** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes]) - : /** @type {import('#client').TemplateNode} */ (clone) - ); - return clone; }; } @@ -195,12 +183,6 @@ export function mathml_template(content, flags) { var clone = clone_node(node, true); - push_template_node( - is_fragment - ? /** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes]) - : /** @type {import('#client').TemplateNode} */ (clone) - ); - return clone; }; } @@ -242,7 +224,7 @@ function run_scripts(node) { */ /*#__NO_SIDE_EFFECTS__*/ export function text(anchor) { - if (!hydrating) return push_template_node(empty()); + if (!hydrating) return empty(); var node = hydrate_start; @@ -260,10 +242,10 @@ export function comment() { if (hydrating) { return push_template_node(hydrate_nodes); } + var frag = document.createDocumentFragment(); var anchor = empty(); frag.append(anchor); - push_template_node([anchor]); return frag; } From ff629ff663e655505bcaf705c4b0261a1850660c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 22:33:54 -0400 Subject: [PATCH 11/36] simplify --- packages/svelte/src/internal/client/dom/template.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 63a62bf6aacc..c2590c411bdf 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -59,9 +59,8 @@ export function template(content, flags) { node = create_fragment_from_html(content); if (!is_fragment) node = /** @type {Node} */ (node.firstChild); } - var clone = use_import_node ? document.importNode(node, true) : clone_node(node, true); - return clone; + return use_import_node ? document.importNode(node, true) : clone_node(node, true); }; } @@ -120,9 +119,7 @@ export function svg_template(content, flags) { } } - var clone = clone_node(node, true); - - return clone; + return clone_node(node, true); }; } @@ -181,9 +178,7 @@ export function mathml_template(content, flags) { } } - var clone = clone_node(node, true); - - return clone; + return clone_node(node, true); }; } From c11bb5a10e0bd28fde760fa35b59e81e6d5845ed Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 22:40:32 -0400 Subject: [PATCH 12/36] remove unused code --- .../svelte/src/internal/client/dom/hydration.js | 17 ----------------- .../svelte/src/internal/client/dom/template.js | 15 +-------------- 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 27246b7b2154..e84ae166fa45 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -96,20 +96,3 @@ export function hydrate_anchor(node) { w.hydration_mismatch(location); throw HYDRATION_ERROR; } - -/** @deprecated */ -export function remove_hydrate_nodes() { - remove_nodes(hydrate_start, hydrate_end); -} - -/** @deprecated */ -export function hydrate_nodes_to_array() { - // TODO get rid of this, just have start/end on effects - let node = hydrate_start; - const nodes = [node]; - while (node.nextSibling) { - nodes.push((node = /** @type {import('#client').TemplateNode} */ (node.nextSibling))); - if (node === hydrate_end) break; - } - return nodes; -} diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index c2590c411bdf..e28b22df2597 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_nodes_to_array, hydrate_start, hydrating } from './hydration.js'; +import { hydrate_nodes, hydrate_start, hydrating } from './hydration.js'; import { clone_node, empty } from './operations.js'; import { create_fragment_from_html } from './reconciler.js'; import { current_effect } from '../runtime.js'; @@ -6,19 +6,6 @@ import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants. import { effect } from '../reactivity/effects.js'; import { is_array } from '../utils.js'; -/** - * @deprecated - * @param {boolean} is_fragment - */ -function get_hydrate_nodes(is_fragment) { - // TODO get rid of this, just have start/end on effects - if (is_fragment) { - return hydrate_nodes_to_array(); - } - - return hydrate_start; -} - /** * @template {import("#client").TemplateNode | import("#client").TemplateNode[]} T * @param {T} dom From 1980fa0e19bcd53a9d43bc4933cb095d66a8ff06 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 22:46:43 -0400 Subject: [PATCH 13/36] unnecessary --- .../client/dom/blocks/svelte-element.js | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) 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 85ebf0f15577..8196c066456b 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -13,19 +13,6 @@ import { set_should_intro } from '../../render.js'; import { current_each_item, set_current_each_item } from './each.js'; import { current_component_context, current_effect } from '../../runtime.js'; import { DEV } from 'esm-env'; - -/** - * @param {import('#client').Effect} effect - * @param {Element} from - * @param {Element} to - * @returns {void} - */ -function swap_block_dom(effect, from, to) { - // TODO this should be unnecessary... - if (effect.d1 === from) effect.d1 = to; - if (effect.d2 === from) effect.d2 = to; -} - /** * @param {Comment} anchor * @param {() => string} get_tag @@ -135,10 +122,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat anchor.before(element); - if (prev_element) { - swap_block_dom(parent_effect, prev_element, element); - prev_element.remove(); - } + prev_element?.remove(); }); } From 4b43ca95dc20de3a7e44d46799dd7bf79ca7176e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 22:56:04 -0400 Subject: [PATCH 14/36] reduce indirection --- .../svelte/src/internal/client/dom/hydration.js | 1 - .../src/internal/client/dom/operations.js | 17 ----------------- .../src/internal/client/reactivity/effects.js | 15 +++++++++++---- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index e84ae166fa45..937afd83adee 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -1,7 +1,6 @@ import { DEV } from 'esm-env'; import { HYDRATION_END, HYDRATION_START, HYDRATION_ERROR } from '../../../constants.js'; import * as w from '../warnings.js'; -import { remove_nodes } from './operations.js'; /** * Use this variable to guard everything related to hydration code so it can be treeshaken out diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index 586b8c6ea3e3..2307c013b00c 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -229,20 +229,3 @@ export function clear_text_content(node) { export function create_element(name) { return document.createElement(name); } - -/** - * @param {import('#client').TemplateNode} from - * @param {import('#client').TemplateNode} to - */ -export function remove_nodes(from, to) { - let node = from; - - while (node) { - const next = node.nextSibling; - - node.remove(); - if (node === to) break; - - node = /** @type {import('#client').TemplateNode} */ (next); - } -} diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index aadc19e51f5d..8c2caa4a4461 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -33,11 +33,9 @@ import { UNOWNED } from '../constants.js'; import { set } from './sources.js'; -import { remove } from '../dom/reconciler.js'; import * as e from '../errors.js'; import { DEV } from 'esm-env'; -import { define_property, is_array } from '../utils.js'; -import { remove_nodes } from '../dom/operations.js'; +import { define_property } from '../utils.js'; /** * @param {'$effect' | '$effect.pre' | '$inspect'} rune @@ -322,7 +320,16 @@ export function destroy_effect(effect) { var d2 = effect.d2; if (d1 !== null && d2 !== null) { - remove_nodes(d1, d2); + var node = d1; + + while (node) { + var n = node.nextSibling; + + node.remove(); + if (node === d2) break; + + node = /** @type {import('#client').TemplateNode} */ (n); + } } destroy_effect_children(effect); From 45ad5ddd5419c2f9398d18485e639a22572c82a4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 23:27:06 -0400 Subject: [PATCH 15/36] DRY --- .../3-transform/client/visitors/template.js | 2 +- .../src/internal/client/dom/template.js | 42 ++++--------------- packages/svelte/src/internal/client/index.js | 2 +- 3 files changed, 11 insertions(+), 35 deletions(-) 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 4cc0c836bf2e..98bef8dfe65f 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 @@ -1197,7 +1197,7 @@ function get_template_function(namespace, state) { return namespace === 'svg' ? contains_script_tag ? '$.svg_template_with_script' - : '$.svg_template' + : '$.ns_template' : namespace === 'mathml' ? '$.mathml_template' : contains_script_tag diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index e28b22df2597..a6d07733476a 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -78,12 +78,13 @@ export function template_with_script(content, flags) { /** * @param {string} content * @param {number} flags + * @param {'svg' | 'math'} ns * @returns {() => Node | Node[]} */ /*#__NO_SIDE_EFFECTS__*/ -export function svg_template(content, flags) { +export function ns_template(content, flags, ns = 'svg') { var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0; - var fn = template(`${content}`, 0); // we don't need to worry about using importNode for SVGs + var fn = template(`<${ns}>${content}`, 0); // we don't need to worry about using importNode for SVGs /** @type {Element | DocumentFragment} */ var node; @@ -94,14 +95,14 @@ export function svg_template(content, flags) { } if (!node) { - var svg = /** @type {Element} */ (fn()); + var wrapper = /** @type {Element} */ (fn()); if ((flags & TEMPLATE_FRAGMENT) === 0) { - node = /** @type {Element} */ (svg.firstChild); + node = /** @type {Element} */ (wrapper.firstChild); } else { node = document.createDocumentFragment(); - while (svg.firstChild) { - node.appendChild(svg.firstChild); + while (wrapper.firstChild) { + node.appendChild(wrapper.firstChild); } } } @@ -118,7 +119,7 @@ export function svg_template(content, flags) { /*#__NO_SIDE_EFFECTS__*/ export function svg_template_with_script(content, flags) { var first = true; - var fn = svg_template(content, flags); + var fn = ns_template(content, flags); return () => { if (hydrating) return fn(); @@ -141,32 +142,7 @@ export function svg_template_with_script(content, flags) { */ /*#__NO_SIDE_EFFECTS__*/ export function mathml_template(content, flags) { - var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0; - var fn = template(`${content}`, 0); // we don't need to worry about using importNode for MathML - - /** @type {Element | DocumentFragment} */ - var node; - - return () => { - if (hydrating) { - return push_template_node(is_fragment ? hydrate_nodes : hydrate_start); - } - - if (!node) { - var math = /** @type {Element} */ (fn()); - - if ((flags & TEMPLATE_FRAGMENT) === 0) { - node = /** @type {Element} */ (math.firstChild); - } else { - node = document.createDocumentFragment(); - while (math.firstChild) { - node.appendChild(math.firstChild); - } - } - } - - return clone_node(node, true); - }; + return ns_template(content, flags, 'math'); } /** diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 1a7eb86cc54d..80d26bb8cf01 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -78,7 +78,7 @@ export { export { append, comment, - svg_template, + ns_template, svg_template_with_script, mathml_template, template, From 1ff28d4f6e2bae44e7ca07b1c6bc3aaa30d7b1c1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 23:39:57 -0400 Subject: [PATCH 16/36] tweak --- .../internal/client/dom/blocks/css-props.js | 3 +- .../src/internal/client/dom/blocks/each.js | 7 ++--- .../src/internal/client/dom/blocks/html.js | 8 ++--- .../src/internal/client/dom/blocks/if.js | 6 ++-- .../src/internal/client/dom/operations.js | 18 ++++++++++++ .../src/internal/client/dom/template.js | 29 ++++++++++--------- .../src/internal/client/reactivity/effects.js | 12 ++------ 7 files changed, 45 insertions(+), 38 deletions(-) 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 c0e4d174cb07..042b6aa9e58f 100644 --- a/packages/svelte/src/internal/client/dom/blocks/css-props.js +++ b/packages/svelte/src/internal/client/dom/blocks/css-props.js @@ -2,7 +2,6 @@ import { namespace_svg } from '../../../../constants.js'; import { hydrate_anchor, hydrate_start, hydrating } from '../hydration.js'; import { empty } from '../operations.js'; import { render_effect } from '../../reactivity/effects.js'; -import { remove } from '../reconciler.js'; /** * @param {Element | Text | Comment} anchor @@ -60,7 +59,7 @@ export function css_props(anchor, is_html, props, component) { }); return () => { - remove(element); + element.remove(); }; }); } diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index fee13b63a762..2e6ace2c2a33 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -10,13 +10,12 @@ import { } from '../../../../constants.js'; import { hydrate_anchor, - hydrate_nodes, + hydrate_end, hydrate_start, hydrating, set_hydrating } from '../hydration.js'; -import { clear_text_content, empty } from '../operations.js'; -import { remove } from '../reconciler.js'; +import { clear_text_content, empty, remove_nodes } from '../operations.js'; import { untrack } from '../../runtime.js'; import { block, @@ -145,7 +144,7 @@ export function each(anchor, flags, get_collection, get_key, render_fn, fallback if (is_else !== (length === 0)) { // hydration mismatch — remove the server-rendered DOM and start over - remove(hydrate_nodes); + remove_nodes(hydrate_start, hydrate_end); set_hydrating(false); mismatch = true; } diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index c03f87674db1..cf930cd5e69a 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -1,6 +1,6 @@ import { derived } from '../../reactivity/deriveds.js'; import { render_effect } from '../../reactivity/effects.js'; -import { current_effect, get } from '../../runtime.js'; +import { get } from '../../runtime.js'; import { hydrate_nodes, hydrating } from '../hydration.js'; import { create_fragment_from_html, remove } from '../reconciler.js'; @@ -12,11 +12,10 @@ import { create_fragment_from_html, remove } from '../reconciler.js'; * @returns {void} */ export function html(anchor, get_value, svg, mathml) { - const parent_effect = /** @type {import('#client').Effect} */ (current_effect); let value = derived(get_value); render_effect(() => { - var dom = html_to_dom(anchor, parent_effect, get(value), svg, mathml); + var dom = html_to_dom(anchor, get(value), svg, mathml); if (dom) { return () => { @@ -31,13 +30,12 @@ export function html(anchor, get_value, svg, mathml) { * inserts it before the target anchor and returns the new nodes. * @template V * @param {Element | Text | Comment} target - * @param {import('#client').Effect | null} effect * @param {V} value * @param {boolean} svg * @param {boolean} mathml * @returns {Element | Comment | (Element | Comment | Text)[]} */ -function html_to_dom(target, effect, value, svg, mathml) { +function html_to_dom(target, value, svg, mathml) { if (hydrating) return hydrate_nodes; var html = value + ''; diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index 80ed8c09f4f6..f5f687d0a180 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -1,8 +1,8 @@ import { EFFECT_TRANSPARENT } from '../../constants.js'; -import { hydrate_nodes, hydrating, set_hydrating } from '../hydration.js'; -import { remove } from '../reconciler.js'; +import { hydrate_end, hydrate_start, hydrating, set_hydrating } from '../hydration.js'; import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js'; import { HYDRATION_END_ELSE } from '../../../../constants.js'; +import { remove_nodes } from '../operations.js'; /** * @param {Comment} anchor @@ -42,7 +42,7 @@ export function if_block( if (condition === is_else) { // Hydration mismatch: remove everything inside the anchor and start fresh. // This could happen with `{#if browser}...{/if}`, for example - remove(hydrate_nodes); + remove_nodes(hydrate_start, hydrate_end); set_hydrating(false); mismatch = true; } diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index 2307c013b00c..235ccc61ee63 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -229,3 +229,21 @@ export function clear_text_content(node) { export function create_element(name) { return document.createElement(name); } + +/** + * Remove all nodes between `from` and `to`, inclusive + * @param {import('#client').TemplateNode} from + * @param {import('#client').TemplateNode} to + */ +export function remove_nodes(from, to) { + var node = from; + + while (node) { + var next = node.nextSibling; + + node.remove(); + if (node === to) break; + + node = /** @type {import('#client').TemplateNode} */ (next); + } +} diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index a6d07733476a..350e31db9e83 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -16,7 +16,7 @@ function push_template_node( dom, effect = /** @type {import('#client').Effect} */ (current_effect) ) { - if (hydrating && effect.d1 === null) { + if (effect.d1 === null) { effect.d1 = is_array(dom) ? dom[0] : dom; effect.d2 = is_array(dom) ? dom[dom.length - 1] : dom; } @@ -215,19 +215,20 @@ export function comment() { * @param {DocumentFragment | Element} dom */ export function append(anchor, dom) { - if (!hydrating) { - const effect = /** @type {import('#client').Effect} */ (current_effect); - - // TODO handle component case, where effect.d1/d2 already exist - if (dom.nodeType === 11) { - const d1 = empty(); // TODO do this in template creation. should d2 be an empty as well? or should we be storing the anchor? - /** @type {import('#client').TemplateNode} */ (dom.firstChild).before(d1); - effect.d1 = d1; - effect.d2 = /** @type {import('#client').TemplateNode} */ (dom.lastChild); - } else { - effect.d1 = effect.d2 = /** @type {Element} */ (dom); - } + if (hydrating) return; - anchor.before(/** @type {Node} */ (dom)); + var effect = /** @type {import('#client').Effect} */ (current_effect); + + if (dom.nodeType === 11) { + // prepend an empty text node + var d1 = empty(); + + /** @type {import('#client').TemplateNode} */ (dom.firstChild).before(d1); + effect.d1 = d1; + effect.d2 = /** @type {import('#client').TemplateNode} */ (dom.lastChild); + } else { + effect.d1 = effect.d2 = /** @type {Element} */ (dom); } + + anchor.before(/** @type {Node} */ (dom)); } diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 8c2caa4a4461..bea23e3828aa 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -36,6 +36,7 @@ import { set } from './sources.js'; import * as e from '../errors.js'; import { DEV } from 'esm-env'; import { define_property } from '../utils.js'; +import { remove_nodes } from '../dom/operations.js'; /** * @param {'$effect' | '$effect.pre' | '$inspect'} rune @@ -320,16 +321,7 @@ export function destroy_effect(effect) { var d2 = effect.d2; if (d1 !== null && d2 !== null) { - var node = d1; - - while (node) { - var n = node.nextSibling; - - node.remove(); - if (node === d2) break; - - node = /** @type {import('#client').TemplateNode} */ (n); - } + remove_nodes(d1, d2); } destroy_effect_children(effect); From 86b06d124b0d7351ea8b3061823671f4247b95c9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 23:49:45 -0400 Subject: [PATCH 17/36] tweak --- .../src/internal/client/dom/blocks/html.js | 28 ++++++++++--------- .../src/internal/client/dom/reconciler.js | 16 ----------- 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index cf930cd5e69a..98fb91c41bfd 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -1,8 +1,9 @@ import { derived } from '../../reactivity/deriveds.js'; import { render_effect } from '../../reactivity/effects.js'; import { get } from '../../runtime.js'; -import { hydrate_nodes, hydrating } from '../hydration.js'; -import { create_fragment_from_html, remove } from '../reconciler.js'; +import { hydrate_start, hydrating } from '../hydration.js'; +import { remove_nodes } from '../operations.js'; +import { create_fragment_from_html } from '../reconciler.js'; /** * @param {Element | Text | Comment} anchor @@ -15,13 +16,11 @@ export function html(anchor, get_value, svg, mathml) { let value = derived(get_value); render_effect(() => { - var dom = html_to_dom(anchor, get(value), svg, mathml); + var [start, end] = html_to_dom(anchor, get(value), svg, mathml); - if (dom) { - return () => { - remove(dom); - }; - } + return () => { + remove_nodes(start, end); + }; }); } @@ -33,10 +32,12 @@ export function html(anchor, get_value, svg, mathml) { * @param {V} value * @param {boolean} svg * @param {boolean} mathml - * @returns {Element | Comment | (Element | Comment | Text)[]} + * @returns {[import('#client').TemplateNode, import('#client').TemplateNode]} */ function html_to_dom(target, value, svg, mathml) { - if (hydrating) return hydrate_nodes; + if (hydrating) { + return [hydrate_start, hydrate_start]; + } var html = value + ''; if (svg) html = `${html}`; @@ -54,10 +55,11 @@ function html_to_dom(target, value, svg, mathml) { if (node.childNodes.length === 1) { var child = /** @type {Text | Element | Comment} */ (node.firstChild); target.before(child); - return child; + return [child, child]; } - var nodes = /** @type {Array} */ ([...node.childNodes]); + var first = /** @type {import('#client').TemplateNode} */ (node.firstChild); + var last = /** @type {import('#client').TemplateNode} */ (node.lastChild); if (svg || mathml) { while (node.firstChild) { @@ -67,5 +69,5 @@ function html_to_dom(target, value, svg, mathml) { target.before(node); } - return nodes; + return [first, last]; } diff --git a/packages/svelte/src/internal/client/dom/reconciler.js b/packages/svelte/src/internal/client/dom/reconciler.js index 5b9f246ed338..de9b2db49d83 100644 --- a/packages/svelte/src/internal/client/dom/reconciler.js +++ b/packages/svelte/src/internal/client/dom/reconciler.js @@ -6,19 +6,3 @@ export function create_fragment_from_html(html) { elem.innerHTML = html; return elem.content; } - -/** - * @param {import('#client').Dom} current - */ -export function remove(current) { - if (is_array(current)) { - for (var i = 0; i < current.length; i++) { - var node = current[i]; - if (node.isConnected) { - node.remove(); - } - } - } else if (current.isConnected) { - current.remove(); - } -} From db9c144aaf502e4ef2b4e998d38fa44358bf7d38 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 19 May 2024 23:59:56 -0400 Subject: [PATCH 18/36] tidy --- .../svelte/src/internal/client/dom/operations.js | 12 ++---------- packages/svelte/src/internal/client/dom/template.js | 7 ++----- packages/svelte/src/internal/client/render.js | 3 --- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index 235ccc61ee63..4ad67faab9c0 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -1,4 +1,4 @@ -import { hydrate_anchor, hydrate_nodes, hydrating } from './hydration.js'; +import { hydrate_anchor, hydrating } from './hydration.js'; import { get_descriptor } from '../utils.js'; import { DEV } from 'esm-env'; import { init_array_prototype_warnings } from '../dev/equality.js'; @@ -165,7 +165,6 @@ export function first_child(fragment, is_text) { // text node to hydrate — we must therefore create one if (is_text && first_node?.nodeType !== 3) { const text = empty(); - hydrate_nodes.unshift(text); first_node?.before(text); return text; } @@ -191,14 +190,7 @@ export function sibling(node, is_text = false) { // 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); - } - + next_sibling?.before(text); return text; } diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 350e31db9e83..8bf4a00ffc64 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -9,13 +9,10 @@ import { is_array } from '../utils.js'; /** * @template {import("#client").TemplateNode | import("#client").TemplateNode[]} T * @param {T} dom - * @param {import("#client").Effect} effect * @returns {T} */ -function push_template_node( - dom, - effect = /** @type {import('#client').Effect} */ (current_effect) -) { +function push_template_node(dom) { + var effect = /** @type {import('#client').Effect} */ (current_effect); if (effect.d1 === null) { effect.d1 = is_array(dom) ? dom[0] : dom; effect.d2 = is_array(dom) ? dom[dom.length - 1] : dom; diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 3e2edfbea4f6..282ee232dddc 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -137,8 +137,6 @@ export function hydrate(component, options) { const target = options.target; const previous_hydrate_nodes = hydrate_nodes; - let hydrated = false; - try { // Don't flush previous effects to ensure order of outer effects stays consistent return flush_sync(() => { @@ -162,7 +160,6 @@ export function hydrate(component, options) { // 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); From 378a917be61f05f1ea946401804d35a9f55d3a89 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 20 May 2024 00:16:09 -0400 Subject: [PATCH 19/36] remove some hydrate_nodes occurrences --- .../client/dom/blocks/svelte-element.js | 4 +++- .../internal/client/dom/blocks/svelte-head.js | 19 +++++++++++++++---- .../src/internal/client/dom/hydration.js | 11 ++++++----- .../src/internal/client/dom/operations.js | 13 +++++-------- packages/svelte/src/internal/client/render.js | 10 ++++++---- 5 files changed, 35 insertions(+), 22 deletions(-) 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 8196c066456b..f2be070b15fb 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -110,7 +110,9 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat if (hydrating && !element.firstChild) { // if the element is a void element with content, add an empty // node to avoid breaking assumptions elsewhere - set_hydrate_nodes([empty()]); + // TODO is this still necessary? + var child = empty(); + set_hydrate_nodes(child, child); } // `child_anchor` is undefined if this is a void element, but we still 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 c5fb0f277115..160e6f4aeba1 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,10 @@ -import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrate_nodes } from '../hydration.js'; +import { + hydrate_anchor, + hydrate_start, + hydrate_end, + hydrating, + set_hydrate_nodes +} from '../hydration.js'; import { empty } from '../operations.js'; import { block } from '../../reactivity/effects.js'; import { HYDRATION_START } from '../../../../constants.js'; @@ -19,14 +25,16 @@ export function reset_head_anchor() { 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_hydrate_nodes = null; + let previous_hydrate_start = null; + let previous_hydrate_end = null; let was_hydrating = hydrating; /** @type {Comment | Text} */ var anchor; if (hydrating) { - previous_hydrate_nodes = hydrate_nodes; + previous_hydrate_start = hydrate_start; + previous_hydrate_end = hydrate_end; // There might be multiple head blocks in our app, so we need to account for each one needing independent hydration. if (head_anchor === undefined) { @@ -50,7 +58,10 @@ export function head(render_fn) { block(() => render_fn(anchor)); } finally { if (was_hydrating) { - set_hydrate_nodes(/** @type {import('#client').TemplateNode[]} */ (previous_hydrate_nodes)); + set_hydrate_nodes( + /** @type {import('#client').TemplateNode} */ (previous_hydrate_start), + /** @type {import('#client').TemplateNode} */ (previous_hydrate_end) + ); } } } diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 937afd83adee..11e3723389ba 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -28,12 +28,13 @@ export let hydrate_start = /** @type {any} */ (null); export let hydrate_end = /** @type {any} */ (null); /** - * @param {import('#client').TemplateNode[]} nodes + * @param {import('#client').TemplateNode} start + * @param {import('#client').TemplateNode} end */ -export function set_hydrate_nodes(nodes) { - hydrate_nodes = nodes; - hydrate_start = nodes && nodes[0]; - hydrate_end = nodes && nodes[nodes.length - 1]; +export function set_hydrate_nodes(start, end) { + hydrate_nodes = [start, end]; // TODO get rid + hydrate_start = start; + hydrate_end = end; } /** diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index 4ad67faab9c0..f817c9203ed2 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -1,4 +1,4 @@ -import { hydrate_anchor, hydrating } from './hydration.js'; +import { hydrate_anchor, hydrate_start, hydrating } from './hydration.js'; import { get_descriptor } from '../utils.js'; import { DEV } from 'esm-env'; import { init_array_prototype_warnings } from '../dev/equality.js'; @@ -147,7 +147,7 @@ export function child(node) { } /** - * @param {DocumentFragment | import('#client').TemplateNode[]} fragment + * @param {DocumentFragment} fragment * @param {boolean} is_text * @returns {Node | null} */ @@ -158,18 +158,15 @@ export function first_child(fragment, is_text) { return first_child_get.call(/** @type {DocumentFragment} */ (fragment)); } - // when we _are_ hydrating, `fragment` is an array of nodes - const first_node = /** @type {import('#client').TemplateNode[]} */ (fragment)[0]; - // if an {expression} is empty during SSR, there might be no // text node to hydrate — we must therefore create one - if (is_text && first_node?.nodeType !== 3) { + if (is_text && hydrate_start?.nodeType !== 3) { const text = empty(); - first_node?.before(text); + hydrate_start?.before(text); return text; } - return hydrate_anchor(first_node); + return hydrate_anchor(hydrate_start); } /** diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 282ee232dddc..38abfbe4ae7c 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -11,7 +11,8 @@ import { flush_sync, push, pop, current_component_context } from './runtime.js'; import { effect_root, branch } from './reactivity/effects.js'; import { hydrate_anchor, - hydrate_nodes, + hydrate_end, + hydrate_start, hydrating, set_hydrate_nodes, set_hydrating @@ -135,7 +136,8 @@ export function hydrate(component, options) { } const target = options.target; - const previous_hydrate_nodes = hydrate_nodes; + const previous_hydrate_start = hydrate_start; + const previous_hydrate_end = hydrate_end; try { // Don't flush previous effects to ensure order of outer effects stays consistent @@ -179,8 +181,8 @@ export function hydrate(component, options) { throw error; } finally { - set_hydrating(!!previous_hydrate_nodes); - set_hydrate_nodes(previous_hydrate_nodes); + set_hydrating(!!previous_hydrate_start); + set_hydrate_nodes(previous_hydrate_start, previous_hydrate_end); reset_head_anchor(); } } From 22b869628866d07c39154c05c917cd356df1c06d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 20 May 2024 00:29:46 -0400 Subject: [PATCH 20/36] tidy --- .../src/internal/client/dev/elements.js | 24 ++++++++++---- .../src/internal/client/dom/hydration.js | 3 +- .../src/internal/client/dom/template.js | 33 ++++++++++--------- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/packages/svelte/src/internal/client/dev/elements.js b/packages/svelte/src/internal/client/dev/elements.js index 0d061f26e518..65fa56ffaac8 100644 --- a/packages/svelte/src/internal/client/dev/elements.js +++ b/packages/svelte/src/internal/client/dev/elements.js @@ -1,6 +1,20 @@ import { HYDRATION_END, HYDRATION_START } from '../../../constants.js'; -import { hydrating } from '../dom/hydration.js'; -import { is_array } from '../utils.js'; +import { hydrate_end, hydrate_start, hydrating } from '../dom/hydration.js'; + +/** + * @param {import('#client').TemplateNode} from + * @param {import('#client').TemplateNode} to + */ +function find_nodes_between(from, to) { + var node = from; + var nodes = [node]; + + while (node !== to) { + nodes.push((node = /** @type {import('#client').TemplateNode} */ (node.nextSibling))); + } + + return nodes; +} /** * @param {any} fn @@ -13,9 +27,7 @@ export function add_locations(fn, filename, locations) { const dom = fn(...args); const nodes = hydrating - ? is_array(dom) - ? dom - : [dom] + ? find_nodes_between(hydrate_start, hydrate_end) : dom.nodeType === 11 ? Array.from(dom.childNodes) : [dom]; @@ -64,7 +76,7 @@ function assign_locations(nodes, filename, locations) { if (comment.data.startsWith(HYDRATION_END)) depth -= 1; } - if (depth === 0 && node.nodeType === 1) { + if (depth === 0 && node.nodeType === 1 && locations[j]) { assign_location(/** @type {Element} */ (node), filename, locations[j++]); } } diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 11e3723389ba..8c2fb2dbc85f 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -19,7 +19,7 @@ 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 hydrate_nodes = /** @type {any} */ (null); +let hydrate_nodes = /** @type {any} */ (null); /** @type {import('#client').TemplateNode} */ export let hydrate_start = /** @type {any} */ (null); @@ -32,7 +32,6 @@ export let hydrate_end = /** @type {any} */ (null); * @param {import('#client').TemplateNode} end */ export function set_hydrate_nodes(start, end) { - hydrate_nodes = [start, end]; // TODO get rid hydrate_start = start; hydrate_end = end; } diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 8bf4a00ffc64..d18bcc8b2034 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -1,24 +1,22 @@ -import { hydrate_nodes, hydrate_start, hydrating } from './hydration.js'; +import { hydrate_end, hydrate_start, hydrating } from './hydration.js'; import { clone_node, empty } from './operations.js'; import { create_fragment_from_html } from './reconciler.js'; import { current_effect } from '../runtime.js'; import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js'; import { effect } from '../reactivity/effects.js'; -import { is_array } from '../utils.js'; /** - * @template {import("#client").TemplateNode | import("#client").TemplateNode[]} T - * @param {T} dom - * @returns {T} + * @template {import("#client").TemplateNode} T + * @param {T} d1 + * @param {T} d2 */ -function push_template_node(dom) { +function push_template_node(d1, d2) { var effect = /** @type {import('#client').Effect} */ (current_effect); + if (effect.d1 === null) { - effect.d1 = is_array(dom) ? dom[0] : dom; - effect.d2 = is_array(dom) ? dom[dom.length - 1] : dom; + effect.d1 = d1; + effect.d2 = d2; } - - return dom; } /** @@ -36,7 +34,8 @@ export function template(content, flags) { return () => { if (hydrating) { - return push_template_node(is_fragment ? hydrate_nodes : hydrate_start); + push_template_node(hydrate_start, hydrate_end); + return hydrate_start; } if (!node) { @@ -80,7 +79,6 @@ export function template_with_script(content, flags) { */ /*#__NO_SIDE_EFFECTS__*/ export function ns_template(content, flags, ns = 'svg') { - var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0; var fn = template(`<${ns}>${content}`, 0); // we don't need to worry about using importNode for SVGs /** @type {Element | DocumentFragment} */ @@ -88,7 +86,8 @@ export function ns_template(content, flags, ns = 'svg') { return () => { if (hydrating) { - return push_template_node(is_fragment ? hydrate_nodes : hydrate_start); + push_template_node(hydrate_start, hydrate_end); + return hydrate_start; } if (!node) { @@ -184,18 +183,20 @@ export function text(anchor) { var node = hydrate_start; if (!node) { - // if an {expression} is empty during SSR, `hydrate_nodes` will be empty. + // if an {expression} is empty during SSR, `hydrate_start` will be missing. // we need to insert an empty text node anchor.before((node = empty())); } - return push_template_node(node); + push_template_node(node, node); + return node; } export function comment() { // we're not delegating to `template` here for performance reasons if (hydrating) { - return push_template_node(hydrate_nodes); + push_template_node(hydrate_start, hydrate_end); + return hydrate_start; } var frag = document.createDocumentFragment(); From 5262b40aaa29981987428c9aca5855d1b12ae27d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 20 May 2024 00:48:01 -0400 Subject: [PATCH 21/36] get rid of hydrate_nodes completely --- .../src/internal/client/dom/hydration.js | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 8c2fb2dbc85f..508ecb47708c 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -13,14 +13,6 @@ 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 - * 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. - * @type {import('#client').TemplateNode[]} - */ -let hydrate_nodes = /** @type {any} */ (null); - /** @type {import('#client').TemplateNode} */ export let hydrate_start = /** @type {any} */ (null); @@ -38,8 +30,8 @@ export function set_hydrate_nodes(start, end) { /** * 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. + * hydration marker, it finds the corresponding closing marker and sets `hydrate_start` + * and `hydrate_end` to the content inside, before returning the closing marker. * @param {Node} node * @returns {Node} */ @@ -55,8 +47,8 @@ export function hydrate_anchor(node) { return node; } - /** @type {Node[]} */ - var nodes = []; + /** @type {import('#client').TemplateNode} */ + var start = /** @type {any} */ (null); var depth = 0; while ((current = /** @type {Node} */ (current).nextSibling) !== null) { @@ -67,10 +59,8 @@ export function hydrate_anchor(node) { depth += 1; } else if (data[0] === HYDRATION_END) { if (depth === 0) { - hydrate_nodes = /** @type {import('#client').TemplateNode[]} */ (nodes); - - hydrate_start = hydrate_nodes[0]; - hydrate_end = hydrate_nodes[hydrate_nodes.length - 1]; + hydrate_start = start; + // hydrate_end = nodes[nodes.length - 1]; return current; } @@ -79,7 +69,8 @@ export function hydrate_anchor(node) { } } - nodes.push(current); + start ??= /** @type {import('#client').TemplateNode} */ (current); + hydrate_end = /** @type {import('#client').TemplateNode} */ (current); } let location; From 66a1368f1b0712c576c41f9483b39f636ee0cb36 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 20 May 2024 01:01:30 -0400 Subject: [PATCH 22/36] tidy --- packages/svelte/src/internal/client/dom/hydration.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 508ecb47708c..b67ec8d89d21 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -60,8 +60,6 @@ export function hydrate_anchor(node) { } else if (data[0] === HYDRATION_END) { if (depth === 0) { hydrate_start = start; - // hydrate_end = nodes[nodes.length - 1]; - return current; } From 6730fed20168bffd8e9fbc3b69c514d01eeb73a9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 20 May 2024 01:12:25 -0400 Subject: [PATCH 23/36] simplify --- .../client/dom/blocks/svelte-element.js | 202 +++++++++--------- 1 file changed, 96 insertions(+), 106 deletions(-) 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 f2be070b15fb..6008fb506a4e 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -6,12 +6,11 @@ import { branch, destroy_effect, pause_effect, - render_effect, resume_effect } from '../../reactivity/effects.js'; import { set_should_intro } from '../../render.js'; import { current_each_item, set_current_each_item } from './each.js'; -import { current_component_context, current_effect } from '../../runtime.js'; +import { current_component_context } from '../../runtime.js'; import { DEV } from 'esm-env'; /** * @param {Comment} anchor @@ -23,120 +22,111 @@ import { DEV } from 'esm-env'; * @returns {void} */ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, location) { - const parent_effect = /** @type {import('#client').Effect} */ (current_effect); - const filename = DEV && location && current_component_context?.function.filename; - // TODO `render_effect` wrapper should be unnecessary? - render_effect(() => { - /** @type {string | null} */ - let tag; - - /** @type {string | null} */ - let current_tag; - - /** @type {null | Element} */ - let element = null; - - /** @type {import('#client').Effect | null} */ - let effect; - - /** - * The keyed `{#each ...}` item block, if any, that this element is inside. - * We track this so we can set it when changing the element, allowing any - * `animate:` directive to bind itself to the correct block - */ - let each_item_block = current_each_item; - - block(() => { - const next_tag = get_tag() || null; - const ns = get_namespace - ? get_namespace() - : is_svg || next_tag === 'svg' - ? namespace_svg - : null; - // Assumption: Noone changes the namespace but not the tag (what would that even mean?) - if (next_tag === tag) return; - - // See explanation of `each_item_block` above - var previous_each_item = current_each_item; - set_current_each_item(each_item_block); - - if (effect) { - if (next_tag === null) { - // start outro - pause_effect(effect, () => { - effect = null; - current_tag = null; - element?.remove(); - }); - } else if (next_tag === current_tag) { - // same tag as is currently rendered — abort outro - resume_effect(effect); - } else { - // tag is changing — destroy immediately, render contents without intro transitions - destroy_effect(effect); - set_should_intro(false); - } + /** @type {string | null} */ + let tag; + + /** @type {string | null} */ + let current_tag; + + /** @type {null | Element} */ + let element = null; + + /** @type {import('#client').Effect | null} */ + let effect; + + /** + * The keyed `{#each ...}` item block, if any, that this element is inside. + * We track this so we can set it when changing the element, allowing any + * `animate:` directive to bind itself to the correct block + */ + let each_item_block = current_each_item; + + block(() => { + const next_tag = get_tag() || null; + const ns = get_namespace + ? get_namespace() + : is_svg || next_tag === 'svg' + ? namespace_svg + : null; + // Assumption: Noone changes the namespace but not the tag (what would that even mean?) + if (next_tag === tag) return; + + // See explanation of `each_item_block` above + var previous_each_item = current_each_item; + set_current_each_item(each_item_block); + + if (effect) { + if (next_tag === null) { + // start outro + pause_effect(effect, () => { + effect = null; + current_tag = null; + element?.remove(); + }); + } else if (next_tag === current_tag) { + // same tag as is currently rendered — abort outro + resume_effect(effect); + } else { + // tag is changing — destroy immediately, render contents without intro transitions + destroy_effect(effect); + set_should_intro(false); } - - if (next_tag && next_tag !== current_tag) { - effect = branch(() => { - const prev_element = element; - element = hydrating - ? /** @type {Element} */ (hydrate_start) - : ns - ? document.createElementNS(ns, next_tag) - : document.createElement(next_tag); - - if (DEV && location) { - // @ts-expect-error - element.__svelte_meta = { - loc: { - file: filename, - line: location[0], - column: location[1] - } - }; - } - - if (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 - ? element.firstChild && hydrate_anchor(/** @type {Comment} */ (element.firstChild)) - : element.appendChild(empty()); - - if (hydrating && !element.firstChild) { - // if the element is a void element with content, add an empty - // node to avoid breaking assumptions elsewhere - // TODO is this still necessary? - var child = empty(); - set_hydrate_nodes(child, child); + } + + if (next_tag && next_tag !== current_tag) { + effect = branch(() => { + const prev_element = element; + element = hydrating + ? /** @type {Element} */ (hydrate_start) + : ns + ? document.createElementNS(ns, next_tag) + : document.createElement(next_tag); + + if (DEV && location) { + // @ts-expect-error + element.__svelte_meta = { + loc: { + file: filename, + line: location[0], + column: location[1] } + }; + } - // `child_anchor` is undefined if this is a void element, but we still - // need to call `render_fn` in order to run actions etc. If the element - // contains children, it's a user error (which is warned on elsewhere) - // and the DOM will be silently discarded - render_fn(element, child_anchor); + if (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 + ? element.firstChild && hydrate_anchor(/** @type {Comment} */ (element.firstChild)) + : element.appendChild(empty()); + + if (hydrating && !element.firstChild) { + // if the element is a void element with content, add an empty + // node to avoid breaking assumptions elsewhere + // TODO is this still necessary? + var child = empty(); + set_hydrate_nodes(child, child); } - anchor.before(element); + // `child_anchor` is undefined if this is a void element, but we still + // need to call `render_fn` in order to run actions etc. If the element + // contains children, it's a user error (which is warned on elsewhere) + // and the DOM will be silently discarded + render_fn(element, child_anchor); + } - prev_element?.remove(); - }); - } + anchor.before(element); - tag = next_tag; - if (tag) current_tag = tag; - set_should_intro(true); + prev_element?.remove(); + }); + } - set_current_each_item(previous_each_item); - }); + tag = next_tag; + if (tag) current_tag = tag; + set_should_intro(true); - return () => { - // element?.remove(); - }; + set_current_each_item(previous_each_item); }); } From a28532df23aedad3348e88dee67afb82e50fb177 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 20 May 2024 01:14:01 -0400 Subject: [PATCH 24/36] tidy up --- packages/svelte/src/internal/client/reactivity/effects.js | 2 -- playgrounds/demo/vite.config.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index bea23e3828aa..0554271fc44b 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -66,8 +66,6 @@ export function push_effect(effect, parent_effect) { } } -let uid = 1; - /** * @param {number} type * @param {(() => void | (() => void))} fn diff --git a/playgrounds/demo/vite.config.js b/playgrounds/demo/vite.config.js index 7a44869a4bf3..a6a970e8cd2c 100644 --- a/playgrounds/demo/vite.config.js +++ b/playgrounds/demo/vite.config.js @@ -3,7 +3,7 @@ import inspect from 'vite-plugin-inspect'; import { svelte } from '@sveltejs/vite-plugin-svelte'; export default defineConfig({ - plugins: [inspect(), svelte({ compilerOptions: { hmr: false } })], + plugins: [inspect(), svelte()], optimizeDeps: { // svelte is a local workspace package, optimizing it would require dev server restarts with --force for every change exclude: ['svelte'] From 0f4189a17cbc8194c221976d4690e71ac5d64851 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 May 2024 12:18:33 -0400 Subject: [PATCH 25/36] fix --- packages/svelte/src/internal/client/dom/blocks/each.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index a52a082b8c06..d5f6e3e5610d 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -411,7 +411,7 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) { for (var i = 0; i < to_destroy.length; i += 1) { var item = to_destroy[i]; items.delete(item.k); - remove(item.o); + item.o.remove(); link(item.prev, item.next); } }); From 49c9324707b0f0d42553d77bbc3d95bfc9c51376 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 May 2024 18:14:38 -0400 Subject: [PATCH 26/36] unnecessary --- .../src/internal/client/dom/blocks/svelte-element.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) 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 3b57596d324e..49d6ddeff7df 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_anchor, hydrate_start, hydrating, set_hydrate_nodes } from '../hydration.js'; +import { hydrate_anchor, hydrate_start, hydrating } from '../hydration.js'; import { empty } from '../operations.js'; import { block, @@ -102,14 +102,6 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat ? element.firstChild && hydrate_anchor(/** @type {Comment} */ (element.firstChild)) : element.appendChild(empty()); - if (hydrating && !element.firstChild) { - // if the element is a void element with content, add an empty - // node to avoid breaking assumptions elsewhere - // TODO is this still necessary? - var child = empty(); - set_hydrate_nodes(child, child); - } - // `child_anchor` is undefined if this is a void element, but we still // need to call `render_fn` in order to run actions etc. If the element // contains children, it's a user error (which is warned on elsewhere) From 9adc60e42b4a2f5019def6a2d8a0a251cfa8414b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 May 2024 22:56:31 -0400 Subject: [PATCH 27/36] tweak --- packages/svelte/src/internal/client/dom/blocks/each.js | 6 +++--- packages/svelte/src/internal/client/dom/blocks/if.js | 5 ++--- packages/svelte/src/internal/client/dom/hydration.js | 5 +++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index d5f6e3e5610d..baf86a2c9516 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -10,12 +10,12 @@ import { } from '../../../../constants.js'; import { hydrate_anchor, - hydrate_end, hydrate_start, hydrating, + remove_hydrate_nodes, set_hydrating } from '../hydration.js'; -import { clear_text_content, empty, remove_nodes } from '../operations.js'; +import { clear_text_content, empty } from '../operations.js'; import { untrack } from '../../runtime.js'; import { block, @@ -144,7 +144,7 @@ export function each(anchor, flags, get_collection, get_key, render_fn, fallback if (is_else !== (length === 0)) { // hydration mismatch — remove the server-rendered DOM and start over - remove_nodes(hydrate_start, hydrate_end); + remove_hydrate_nodes(); set_hydrating(false); mismatch = true; } diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index f5f687d0a180..41c26bbfd8b0 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -1,8 +1,7 @@ import { EFFECT_TRANSPARENT } from '../../constants.js'; -import { hydrate_end, hydrate_start, hydrating, set_hydrating } from '../hydration.js'; +import { hydrating, remove_hydrate_nodes, set_hydrating } from '../hydration.js'; import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js'; import { HYDRATION_END_ELSE } from '../../../../constants.js'; -import { remove_nodes } from '../operations.js'; /** * @param {Comment} anchor @@ -42,7 +41,7 @@ export function if_block( if (condition === is_else) { // Hydration mismatch: remove everything inside the anchor and start fresh. // This could happen with `{#if browser}...{/if}`, for example - remove_nodes(hydrate_start, hydrate_end); + remove_hydrate_nodes(); set_hydrating(false); mismatch = true; } diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index b67ec8d89d21..ca3c0fa4ec10 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -1,6 +1,7 @@ import { DEV } from 'esm-env'; import { HYDRATION_END, HYDRATION_START, HYDRATION_ERROR } from '../../../constants.js'; import * as w from '../warnings.js'; +import { remove_nodes } from './operations.js'; /** * Use this variable to guard everything related to hydration code so it can be treeshaken out @@ -84,3 +85,7 @@ export function hydrate_anchor(node) { w.hydration_mismatch(location); throw HYDRATION_ERROR; } + +export function remove_hydrate_nodes() { + remove_nodes(hydrate_start, hydrate_end); +} From 3cdca108b37b1d0b068196a26957f02671171ce0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 May 2024 23:05:43 -0400 Subject: [PATCH 28/36] set d2 in append --- .../src/internal/client/dom/template.js | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 5e32673c007d..73c4e98be9bb 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_end, hydrate_start, hydrating } from './hydration.js'; +import { hydrate_start, hydrating } from './hydration.js'; import { empty } from './operations.js'; import { create_fragment_from_html } from './reconciler.js'; import { current_effect } from '../runtime.js'; @@ -8,14 +8,12 @@ import { effect } from '../reactivity/effects.js'; /** * @template {import("#client").TemplateNode} T * @param {T} d1 - * @param {T} d2 */ -function push_template_node(d1, d2) { +function push_template_node(d1) { var effect = /** @type {import('#client').Effect} */ (current_effect); if (effect.d1 === null) { effect.d1 = d1; - effect.d2 = d2; } } @@ -34,7 +32,7 @@ export function template(content, flags) { return () => { if (hydrating) { - push_template_node(hydrate_start, hydrate_end); + push_template_node(hydrate_start); return hydrate_start; } @@ -86,7 +84,7 @@ export function ns_template(content, flags, ns = 'svg') { return () => { if (hydrating) { - push_template_node(hydrate_start, hydrate_end); + push_template_node(hydrate_start); return hydrate_start; } @@ -188,14 +186,14 @@ export function text(anchor) { anchor.before((node = empty())); } - push_template_node(node, node); + push_template_node(node); return node; } export function comment() { // we're not delegating to `template` here for performance reasons if (hydrating) { - push_template_node(hydrate_start, hydrate_end); + push_template_node(hydrate_start); return hydrate_start; } @@ -213,20 +211,21 @@ export function comment() { * @param {DocumentFragment | Element} dom */ export function append(anchor, dom) { - if (hydrating) return; - var effect = /** @type {import('#client').Effect} */ (current_effect); - if (dom.nodeType === 11) { - // prepend an empty text node - var d1 = empty(); + if (!hydrating) { + if (dom.nodeType === 11) { + // prepend an empty text node + var d1 = empty(); - /** @type {import('#client').TemplateNode} */ (dom.firstChild).before(d1); - effect.d1 = d1; - effect.d2 = /** @type {import('#client').TemplateNode} */ (dom.lastChild); - } else { - effect.d1 = effect.d2 = /** @type {Element} */ (dom); + /** @type {import('#client').TemplateNode} */ (dom.firstChild).before(d1); + effect.d1 = d1; + } else { + effect.d1 = /** @type {Element} */ (dom); + } + + anchor.before(/** @type {Node} */ (dom)); } - anchor.before(/** @type {Node} */ (dom)); + effect.d2 = anchor?.previousSibling; } From d9f9755b383e964a836d68190cfa3a32a84a919d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 May 2024 23:13:04 -0400 Subject: [PATCH 29/36] remove hydrate_end --- .../internal/client/dom/blocks/svelte-head.js | 15 ++------- .../src/internal/client/dom/hydration.js | 32 +++++++++++++------ packages/svelte/src/internal/client/render.js | 4 +-- 3 files changed, 25 insertions(+), 26 deletions(-) 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 160e6f4aeba1..e41dfc120b57 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-head.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-head.js @@ -1,10 +1,4 @@ -import { - hydrate_anchor, - hydrate_start, - hydrate_end, - hydrating, - set_hydrate_nodes -} from '../hydration.js'; +import { hydrate_anchor, hydrate_start, hydrating, set_hydrate_nodes } from '../hydration.js'; import { empty } from '../operations.js'; import { block } from '../../reactivity/effects.js'; import { HYDRATION_START } from '../../../../constants.js'; @@ -26,7 +20,6 @@ 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_hydrate_start = null; - let previous_hydrate_end = null; let was_hydrating = hydrating; /** @type {Comment | Text} */ @@ -34,7 +27,6 @@ export function head(render_fn) { if (hydrating) { previous_hydrate_start = hydrate_start; - previous_hydrate_end = hydrate_end; // There might be multiple head blocks in our app, so we need to account for each one needing independent hydration. if (head_anchor === undefined) { @@ -58,10 +50,7 @@ export function head(render_fn) { block(() => render_fn(anchor)); } finally { if (was_hydrating) { - set_hydrate_nodes( - /** @type {import('#client').TemplateNode} */ (previous_hydrate_start), - /** @type {import('#client').TemplateNode} */ (previous_hydrate_end) - ); + set_hydrate_nodes(/** @type {import('#client').TemplateNode} */ (previous_hydrate_start)); } } } diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index ca3c0fa4ec10..166d6b6c2530 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -17,22 +17,16 @@ export function set_hydrating(value) { /** @type {import('#client').TemplateNode} */ export let hydrate_start = /** @type {any} */ (null); -/** @type {import('#client').TemplateNode} */ -export let hydrate_end = /** @type {any} */ (null); - /** * @param {import('#client').TemplateNode} start - * @param {import('#client').TemplateNode} end */ -export function set_hydrate_nodes(start, end) { +export function set_hydrate_nodes(start) { hydrate_start = start; - hydrate_end = end; } /** * This function is only called when `hydrating` is true. If passed a `` opening - * hydration marker, it finds the corresponding closing marker and sets `hydrate_start` - * and `hydrate_end` to the content inside, before returning the closing marker. + * hydration marker, it sets `hydrate_start` to be the next node and returns the closing marker * @param {Node} node * @returns {Node} */ @@ -69,7 +63,6 @@ export function hydrate_anchor(node) { } start ??= /** @type {import('#client').TemplateNode} */ (current); - hydrate_end = /** @type {import('#client').TemplateNode} */ (current); } let location; @@ -87,5 +80,24 @@ export function hydrate_anchor(node) { } export function remove_hydrate_nodes() { - remove_nodes(hydrate_start, hydrate_end); + /** @type {import('#client').TemplateNode | null} */ + var node = hydrate_start; + var depth = 0; + + while (node) { + if (node.nodeType === 8) { + var data = /** @type {Comment} */ (node).data; + + if (data === HYDRATION_START) { + depth += 1; + } else if (data[0] === HYDRATION_END) { + if (depth === 0) return; + depth -= 1; + } + } + + var next = /** @type {import('#client').TemplateNode | null} */ (node.nextSibling); + node.remove(); + node = next; + } } diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 4bb0beb49c87..2117113c555d 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -5,7 +5,6 @@ import { flush_sync, push, pop, current_component_context } from './runtime.js'; import { effect_root, branch } from './reactivity/effects.js'; import { hydrate_anchor, - hydrate_end, hydrate_start, hydrating, set_hydrate_nodes, @@ -131,7 +130,6 @@ export function hydrate(component, options) { const target = options.target; const previous_hydrate_start = hydrate_start; - const previous_hydrate_end = hydrate_end; try { // Don't flush previous effects to ensure order of outer effects stays consistent @@ -176,7 +174,7 @@ export function hydrate(component, options) { throw error; } finally { set_hydrating(!!previous_hydrate_start); - set_hydrate_nodes(previous_hydrate_start, previous_hydrate_end); + set_hydrate_nodes(previous_hydrate_start); reset_head_anchor(); } } From f5460e2b08ac827a0490c0725a463cc67a0ddf80 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 May 2024 23:21:53 -0400 Subject: [PATCH 30/36] tweak --- .../svelte/src/internal/client/dom/hydration.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 166d6b6c2530..6c4b14395fa3 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -35,18 +35,19 @@ export function hydrate_anchor(node) { return node; } - var current = /** @type {Node | null} */ (node); - // TODO this could have false positives, if a user comment consisted of `[`. need to tighten that up - if (/** @type {Comment} */ (current).data !== HYDRATION_START) { + if (/** @type {Comment} */ (node).data !== HYDRATION_START) { return node; } - /** @type {import('#client').TemplateNode} */ - var start = /** @type {any} */ (null); + hydrate_start = /** @type {import('#client').TemplateNode} */ ( + /** @type {Comment} */ (node).nextSibling + ); + + var current = hydrate_start; var depth = 0; - while ((current = /** @type {Node} */ (current).nextSibling) !== null) { + while (current !== null) { if (current.nodeType === 8) { var data = /** @type {Comment} */ (current).data; @@ -54,7 +55,6 @@ export function hydrate_anchor(node) { depth += 1; } else if (data[0] === HYDRATION_END) { if (depth === 0) { - hydrate_start = start; return current; } @@ -62,7 +62,7 @@ export function hydrate_anchor(node) { } } - start ??= /** @type {import('#client').TemplateNode} */ (current); + current = current.nextSibling; } let location; From 04415e9d9d85974b692b314a86113c2919d93d56 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 23 May 2024 23:25:08 -0400 Subject: [PATCH 31/36] tweak --- packages/svelte/src/internal/client/dom/hydration.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 6c4b14395fa3..d51ae78ca362 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -31,12 +31,8 @@ export function set_hydrate_nodes(start) { * @returns {Node} */ export function hydrate_anchor(node) { - if (node.nodeType !== 8) { - return node; - } - // TODO this could have false positives, if a user comment consisted of `[`. need to tighten that up - if (/** @type {Comment} */ (node).data !== HYDRATION_START) { + if (node.nodeType !== 8 || /** @type {Comment} */ (node).data !== HYDRATION_START) { return node; } @@ -62,7 +58,7 @@ export function hydrate_anchor(node) { } } - current = current.nextSibling; + current = /** @type {import('#client').TemplateNode} */ (current.nextSibling); } let location; From d6c3738e486cf7ae99cbdfb6ff3fa5358ea17ec3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 May 2024 11:57:24 -0400 Subject: [PATCH 32/36] avoid wrappers for --- .../phases/3-transform/server/transform-server.js | 5 ++--- .../internal/client/dom/blocks/svelte-element.js | 13 ++++++++----- .../svelte/src/internal/client/dom/operations.js | 7 ++++++- .../_expected.html | 9 +++------ .../svelte-element/_expected/server/index.svelte.js | 5 ++--- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 1800479a681b..d1646154f210 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -38,6 +38,7 @@ import { filename, locator } from '../../../state.js'; export const block_open = t_string(``); export const block_close = t_string(``); +export const block_anchor = t_string(``); /** * @param {string} value @@ -1470,8 +1471,6 @@ const template_visitors = { } }; - context.state.template.push(block_open); - const main = create_block(node, node.fragment.nodes, { ...context, state: { ...context.state, metadata } @@ -1506,7 +1505,7 @@ const template_visitors = { ) ) ), - block_close + block_anchor ); if (context.state.options.dev) { context.state.template.push(t_statement(b.stmt(b.call('$.pop_element')))); 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 49d6ddeff7df..d01610d0f889 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_anchor, hydrate_start, hydrating } from '../hydration.js'; +import { hydrate_anchor, hydrating } from '../hydration.js'; import { empty } from '../operations.js'; import { block, @@ -14,7 +14,7 @@ import { current_component_context } from '../../runtime.js'; import { DEV } from 'esm-env'; /** - * @param {Comment} anchor + * @param {Comment | Element} node * @param {() => string} get_tag * @param {boolean} is_svg * @param {undefined | ((element: Element, anchor: Node | null) => void)} render_fn, @@ -22,7 +22,7 @@ import { DEV } from 'esm-env'; * @param {undefined | [number, number]} location * @returns {void} */ -export function element(anchor, get_tag, is_svg, render_fn, get_namespace, location) { +export function element(node, get_tag, is_svg, render_fn, get_namespace, location) { const filename = DEV && location && current_component_context?.function.filename; /** @type {string | null} */ @@ -32,7 +32,9 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat let current_tag; /** @type {null | Element} */ - let element = null; + let element = hydrating && node.nodeType === 1 ? /** @type {Element} */ (node) : null; + + let anchor = /** @type {Comment} */ (hydrating && element ? element.nextSibling : node); /** @type {import('#client').Effect | null} */ let effect; @@ -51,6 +53,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat : is_svg || next_tag === 'svg' ? namespace_svg : null; + // Assumption: Noone changes the namespace but not the tag (what would that even mean?) if (next_tag === tag) return; @@ -79,7 +82,7 @@ export function element(anchor, get_tag, is_svg, render_fn, get_namespace, locat if (next_tag && next_tag !== current_tag) { effect = branch(() => { element = hydrating - ? /** @type {Element} */ (hydrate_start) + ? /** @type {Element} */ (element) : ns ? document.createElementNS(ns, next_tag) : document.createElement(next_tag); diff --git a/packages/svelte/src/internal/client/dom/operations.js b/packages/svelte/src/internal/client/dom/operations.js index 7fb84aa44c88..0d3159a06359 100644 --- a/packages/svelte/src/internal/client/dom/operations.js +++ b/packages/svelte/src/internal/client/dom/operations.js @@ -1,6 +1,7 @@ import { hydrate_anchor, hydrate_start, hydrating } from './hydration.js'; import { DEV } from 'esm-env'; import { init_array_prototype_warnings } from '../dev/equality.js'; +import { HYDRATION_END } from '../../../constants.js'; // export these for reference in the compiled code, making global name deduplication unnecessary /** @type {Window} */ @@ -98,12 +99,16 @@ export function first_child(fragment, is_text) { */ /*#__NO_SIDE_EFFECTS__*/ export function sibling(node, is_text = false) { - const next_sibling = node.nextSibling; + var next_sibling = /** @type {import('#client').TemplateNode} */ (node.nextSibling); if (!hydrating) { return next_sibling; } + if (next_sibling.nodeType === 8 && /** @type {Comment} */ (next_sibling).data === '') { + return sibling(next_sibling, is_text); + } + // 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) { diff --git a/packages/svelte/tests/server-side-rendering/samples/head-svelte-components-raw-content/_expected.html b/packages/svelte/tests/server-side-rendering/samples/head-svelte-components-raw-content/_expected.html index 3eca221c361a..48da62b4de5a 100644 --- a/packages/svelte/tests/server-side-rendering/samples/head-svelte-components-raw-content/_expected.html +++ b/packages/svelte/tests/server-side-rendering/samples/head-svelte-components-raw-content/_expected.html @@ -1,16 +1,13 @@ - lorem - - + - - + - + diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js index 2cf8edd1ee9f..1ca5efdfd6da 100644 --- a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js @@ -5,8 +5,7 @@ export default function Svelte_element($$payload, $$props) { let { tag = 'hr' } = $$props; - $$payload.out += ``; if (tag) $.element($$payload, tag, () => {}, () => {}); - $$payload.out += ``; + $$payload.out += ``; $.pop(); -} \ No newline at end of file +} From e705a67658500cd8a6312a136e8704911c2a969c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 May 2024 12:20:25 -0400 Subject: [PATCH 33/36] half working css props --- .../3-transform/server/transform-server.js | 91 ++++++++++--------- .../internal/client/dom/blocks/css-props.js | 16 +++- packages/svelte/src/internal/server/index.js | 8 +- 3 files changed, 65 insertions(+), 50 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index d1646154f210..68e0e8bed1d2 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -960,9 +960,12 @@ function serialize_element_spread_attributes( * @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node * @param {string | import('estree').Expression} component_name * @param {import('./types').ComponentContext} context - * @returns {import('estree').Statement} + * @returns {import('estree').Statement[]} */ function serialize_inline_component(node, component_name, context) { + /** @type {import('./types').Template[]} */ + const parts = []; + /** @type {Array} */ const props_and_spreads = []; @@ -1124,37 +1127,43 @@ function serialize_inline_component(node, component_name, context) { b.array(props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p))) ); - /** @type {import('estree').Statement} */ - let statement = b.stmt( - (typeof component_name === 'string' ? b.call : b.maybe_call)( - context.state.options.dev - ? b.call( - '$.validate_component', - typeof component_name === 'string' ? b.id(component_name) : component_name - ) - : component_name, - b.id('$$payload'), - props_expression - ) - ); + /** @type {import('estree').Statement[]} */ + let statements = [ + b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_open.value))), + b.stmt( + (typeof component_name === 'string' ? b.call : b.maybe_call)( + context.state.options.dev + ? b.call( + '$.validate_component', + typeof component_name === 'string' ? b.id(component_name) : component_name + ) + : component_name, + b.id('$$payload'), + props_expression + ) + ), + b.stmt(b.assignment('+=', b.id('$$payload.out'), b.literal(block_close.value))) + ]; if (custom_css_props.length > 0) { - statement = b.stmt( - b.call( - '$.css_props', - b.id('$$payload'), - b.literal(context.state.metadata.namespace === 'svg' ? false : true), - b.object(custom_css_props), - b.thunk(b.block([statement])) + statements = [ + b.stmt( + b.call( + '$.css_props', + b.id('$$payload'), + b.literal(context.state.metadata.namespace === 'svg' ? false : true), + b.object(custom_css_props), + b.thunk(b.block(statements)) + ) ) - ); + ]; } if (snippet_declarations.length > 0) { - statement = b.block([...snippet_declarations, statement]); + statements.unshift(...snippet_declarations); } - return statement; + return statements; } /** @@ -1653,29 +1662,27 @@ const template_visitors = { } }, Component(node, context) { - const state = context.state; - state.template.push(block_open); - const call = serialize_inline_component(node, node.name, context); - state.template.push(t_statement(call)); - state.template.push(block_close); + context.state.template.push( + ...serialize_inline_component(node, node.name, context).map((statement) => + t_statement(statement) + ) + ); }, SvelteSelf(node, context) { - const state = context.state; - state.template.push(block_open); - const call = serialize_inline_component(node, context.state.analysis.name, context); - state.template.push(t_statement(call)); - state.template.push(block_close); + context.state.template.push( + ...serialize_inline_component(node, context.state.analysis.name, context).map((statement) => + t_statement(statement) + ) + ); }, SvelteComponent(node, context) { - const state = context.state; - state.template.push(block_open); - const call = serialize_inline_component( - node, - /** @type {import('estree').Expression} */ (context.visit(node.expression)), - context + context.state.template.push( + ...serialize_inline_component( + node, + /** @type {import('estree').Expression} */ (context.visit(node.expression)), + context + ).map((statement) => t_statement(statement)) ); - state.template.push(t_statement(call)); - state.template.push(block_close); }, LetDirective(node, { state }) { if (node.expression && node.expression.type !== 'Identifier') { 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 042b6aa9e58f..18460e76a202 100644 --- a/packages/svelte/src/internal/client/dom/blocks/css-props.js +++ b/packages/svelte/src/internal/client/dom/blocks/css-props.js @@ -1,25 +1,31 @@ import { namespace_svg } from '../../../../constants.js'; -import { hydrate_anchor, hydrate_start, hydrating } from '../hydration.js'; +import { hydrate_anchor, hydrating } from '../hydration.js'; import { empty } from '../operations.js'; import { render_effect } from '../../reactivity/effects.js'; /** - * @param {Element | Text | Comment} anchor + * @param {HTMLElement | SVGElement | Comment} node * @param {boolean} is_html * @param {() => Record} props * @param {(anchor: Element | Text | Comment) => any} component * @returns {void} */ -export function css_props(anchor, is_html, props, component) { +export function css_props(node, is_html, props, component) { /** @type {HTMLElement | SVGElement} */ let element; + /** @type {Comment} */ + let anchor; + /** @type {Text | Comment} */ let component_anchor; if (hydrating) { // Hydration: css props element is surrounded by a ssr comment ... - element = /** @type {HTMLElement | SVGElement} */ (hydrate_start); + element = /** @type {HTMLElement | SVGElement} */ (node); + + anchor = /** @type {Comment} */ (element.nextSibling); + // ... and the child(ren) of the css props element is also surround by a ssr comment component_anchor = /** @type {Comment} */ ( hydrate_anchor(/** @type {Comment} */ (element.firstChild)) @@ -32,6 +38,8 @@ export function css_props(anchor, is_html, props, component) { element = document.createElementNS(namespace_svg, 'g'); } + anchor = /** @type {Comment} */ (node); + anchor.before(element); component_anchor = element.appendChild(empty()); } diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index bf9d5d1c57b8..58c45e25a60c 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -171,15 +171,15 @@ export function attr(name, value, boolean) { export function css_props(payload, is_html, props, component) { const styles = style_object_to_string(props); if (is_html) { - payload.out += `
`; + payload.out += `
`; } else { - payload.out += ``; + payload.out += ``; } component(); if (is_html) { - payload.out += `
`; + payload.out += `
`; } else { - payload.out += ``; + payload.out += ``; } } From 5e2cda39cc6b74e05ac83276e22e9162c94eb5ae Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 May 2024 12:32:25 -0400 Subject: [PATCH 34/36] simplify --- .../svelte/src/internal/client/dom/blocks/css-props.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) 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 18460e76a202..613718258fe3 100644 --- a/packages/svelte/src/internal/client/dom/blocks/css-props.js +++ b/packages/svelte/src/internal/client/dom/blocks/css-props.js @@ -14,9 +14,6 @@ export function css_props(node, is_html, props, component) { /** @type {HTMLElement | SVGElement} */ let element; - /** @type {Comment} */ - let anchor; - /** @type {Text | Comment} */ let component_anchor; @@ -24,8 +21,6 @@ export function css_props(node, is_html, props, component) { // Hydration: css props element is surrounded by a ssr comment ... element = /** @type {HTMLElement | SVGElement} */ (node); - anchor = /** @type {Comment} */ (element.nextSibling); - // ... and the child(ren) of the css props element is also surround by a ssr comment component_anchor = /** @type {Comment} */ ( hydrate_anchor(/** @type {Comment} */ (element.firstChild)) @@ -38,9 +33,7 @@ export function css_props(node, is_html, props, component) { element = document.createElementNS(namespace_svg, 'g'); } - anchor = /** @type {Comment} */ (node); - - anchor.before(element); + node.before(element); // TODO do we even need an anchor? Can we just pass in the correct element directly from the template? component_anchor = element.appendChild(empty()); } From c8c8848eb6b9f57eec838103db78688ac80fcf72 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 May 2024 12:33:10 -0400 Subject: [PATCH 35/36] update snapshots --- .../bind-component-snippet/_expected/server/index.svelte.js | 5 +++-- .../samples/bind-this/_expected/server/index.svelte.js | 4 ++-- .../function-prop-no-getter/_expected/server/index.svelte.js | 4 ++-- .../samples/svelte-element/_expected/server/index.svelte.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js index c4d2da79d1cd..ebb55389af5e 100644 --- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js @@ -15,7 +15,7 @@ export default function Bind_component_snippet($$payload, $$props) { let $$inner_payload; function $$render_inner($$payload) { - $$payload.out += ``; + $$payload.out += ""; TextInput($$payload, { get value() { @@ -27,7 +27,8 @@ export default function Bind_component_snippet($$payload, $$props) { } }); - $$payload.out += ` value: ${$.escape(value)}`; + $$payload.out += ""; + $$payload.out += ` value: ${$.escape(value)}`; }; do { diff --git a/packages/svelte/tests/snapshot/samples/bind-this/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-this/_expected/server/index.svelte.js index 9f00d97ad4b0..825b750e1064 100644 --- a/packages/svelte/tests/snapshot/samples/bind-this/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-this/_expected/server/index.svelte.js @@ -2,8 +2,8 @@ import * as $ from "svelte/internal/server"; export default function Bind_this($$payload, $$props) { $.push(); - $$payload.out += ``; + $$payload.out += ""; Foo($$payload, {}); - $$payload.out += ``; + $$payload.out += ""; $.pop(); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js index b096c4b6c0d7..7bb70ec5cf5f 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js @@ -11,7 +11,7 @@ export default function Function_prop_no_getter($$payload, $$props) { const plusOne = (num) => num + 1; - $$payload.out += ``; + $$payload.out += ""; Button($$payload, { onmousedown: () => count += 1, @@ -23,6 +23,6 @@ export default function Function_prop_no_getter($$payload, $$props) { $$slots: { default: true } }); - $$payload.out += ``; + $$payload.out += ""; $.pop(); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js index 1ca5efdfd6da..3be11f26105e 100644 --- a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js @@ -8,4 +8,4 @@ export default function Svelte_element($$payload, $$props) { if (tag) $.element($$payload, tag, () => {}, () => {}); $$payload.out += ``; $.pop(); -} +} \ No newline at end of file From 3296520a4df7ed40cacaf38eee1337d4f8f5410b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 24 May 2024 12:45:28 -0400 Subject: [PATCH 36/36] fix --- .../compiler/phases/3-transform/server/transform-server.js | 2 +- packages/svelte/src/internal/server/index.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 68e0e8bed1d2..7aadabeffb11 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -1160,7 +1160,7 @@ function serialize_inline_component(node, component_name, context) { } if (snippet_declarations.length > 0) { - statements.unshift(...snippet_declarations); + statements = [b.block([...snippet_declarations, ...statements])]; } return statements; diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 58c45e25a60c..c480b8055d7e 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -177,9 +177,9 @@ export function css_props(payload, is_html, props, component) { } component(); if (is_html) { - payload.out += ``; + payload.out += ``; } else { - payload.out += ``; + payload.out += ``; } }