From e071734d2b2caa4df17e037300112057f50f6c52 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 26 Jun 2024 11:20:41 +0100 Subject: [PATCH] fix root cause for issue --- .../3-transform/client/visitors/template.js | 10 +++-- .../src/internal/client/dom/blocks/each.js | 10 ++--- .../src/internal/client/dom/blocks/html.js | 4 +- .../client/dom/blocks/svelte-element.js | 2 +- .../internal/client/dom/blocks/svelte-head.js | 2 +- .../src/internal/client/dom/template.js | 42 +++++++++++++------ .../samples/each-updates-7/_config.js | 27 ++++++++++++ .../samples/each-updates-7/main.svelte | 26 ++++++++++++ playgrounds/demo/server.js | 2 +- 9 files changed, 99 insertions(+), 26 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/each-updates-7/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/each-updates-7/main.svelte 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 6a426db0d4..6892eb3b3c 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 @@ -1646,7 +1646,11 @@ export const template_visitors = { add_template(template_name, args); - body.push(b.var(id, b.call(template_name)), ...state.before_init, ...state.init); + body.push( + b.var(id, b.call(template_name, b.id('$$anchor'))), + ...state.before_init, + ...state.init + ); close = b.stmt(b.call('$.append', b.id('$$anchor'), id)); } else if (is_single_child_not_needing_template) { context.visit(trimmed[0], state); @@ -1684,7 +1688,7 @@ export const template_visitors = { if (use_comment_template) { // special case — we can use `$.comment` instead of creating a unique template - body.push(b.var(id, b.call('$.comment'))); + body.push(b.var(id, b.call('$.comment', b.id('$$anchor')))); } else { let flags = TEMPLATE_FRAGMENT; @@ -1697,7 +1701,7 @@ export const template_visitors = { b.literal(flags) ]); - body.push(b.var(id, b.call(template_name))); + body.push(b.var(id, b.call(template_name, b.id('$$anchor')))); } body.push(...state.before_init, ...state.init); diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 99c17d70b6..140fcd9008 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -486,7 +486,10 @@ function create_item(anchor, prev, next, value, key, index, render_fn, flags) { * @returns {import('#client').TemplateNode} */ function get_adjusted_first_node(dom, effect) { - if ((dom.nodeType === 3 && /** @type {Text} */ (dom).data === '') || dom.nodeType === 8) { + if ( + (dom.nodeType === 3 && /** @type {Text} */ (dom).data === '') || + (dom.nodeType === 8 && /** @type {Comment} */ (dom).data !== '[') + ) { var adjusted = effect.first; var next; while (adjusted !== null) { @@ -511,10 +514,7 @@ function get_adjusted_first_node(dom, effect) { function get_first_node(effect) { var dom = effect.dom; if (is_array(dom)) { - var adjusted_dom = get_adjusted_first_node(dom[0], effect); - // If we have a sibling that contains the adjusted_dom, then use that instead. - var sibling = dom[1]; - return sibling?.contains(adjusted_dom) ? sibling : adjusted_dom; + return get_adjusted_first_node(dom[0], effect); } return get_adjusted_first_node(/** @type {import('#client').TemplateNode} **/ (dom), effect); } diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index 6873adf70c..8ab6d58d6b 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -82,7 +82,7 @@ function html_to_dom(target, effect, value, svg, mathml) { var child = /** @type {Text | Element | Comment} */ (node.firstChild); target.before(child); if (effect !== null) { - push_template_node(child, effect); + push_template_node(child, null, effect); } return child; } @@ -98,7 +98,7 @@ function html_to_dom(target, effect, value, svg, mathml) { } if (effect !== null) { - push_template_node(nodes, effect); + push_template_node(nodes, null, 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 6e0740b937..611eb6395f 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -140,7 +140,7 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio swap_block_dom(element_effect, prev_element, element); prev_element.remove(); } else { - push_template_node(element, element_effect); + push_template_node(element, null, element_effect); } if (render_fn) { 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 b00a3a242b..c5fb0f2771 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/template.js b/packages/svelte/src/internal/client/dom/template.js index 7342b71eda..14d5769f73 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -9,10 +9,12 @@ import { queue_micro_task } from './task.js'; /** * @template {import("#client").TemplateNode | import("#client").TemplateNode[]} T * @param {T} dom + * @param {import("#client").TemplateNode | null} anchor * @param {import("#client").Effect} effect */ export function push_template_node( dom, + anchor, effect = /** @type {import('#client').Effect} */ (current_effect) ) { var current_dom = effect.dom; @@ -22,11 +24,20 @@ export function push_template_node( if (!is_array(current_dom)) { current_dom = effect.dom = [current_dom]; } + const anchor_index = anchor !== null ? current_dom.indexOf(anchor) : null; if (is_array(dom)) { - current_dom.push(...dom); + if (anchor_index !== null) { + current_dom.splice(anchor_index, 0, ...dom); + } else { + current_dom.push(...dom); + } } else { - current_dom.push(dom); + if (anchor_index !== null) { + current_dom.splice(anchor_index, 0, dom); + } else { + current_dom.push(dom); + } } } return dom; @@ -45,9 +56,9 @@ export function template(content, flags) { /** @type {Node} */ var node; - return () => { + return (/** @type {Element | Comment | null} */ prev_anchor) => { if (hydrating) { - push_template_node(is_fragment ? hydrate_nodes : hydrate_start); + push_template_node(is_fragment ? hydrate_nodes : hydrate_start, prev_anchor); return hydrate_start; } @@ -61,7 +72,8 @@ export function template(content, flags) { push_template_node( is_fragment ? /** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes]) - : /** @type {import('#client').TemplateNode} */ (clone) + : /** @type {import('#client').TemplateNode} */ (clone), + prev_anchor ); return clone; @@ -106,9 +118,9 @@ export function ns_template(content, flags, ns = 'svg') { /** @type {Element | DocumentFragment} */ var node; - return () => { + return (/** @type {Element | Comment | null} */ prev_anchor) => { if (hydrating) { - push_template_node(is_fragment ? hydrate_nodes : hydrate_start); + push_template_node(is_fragment ? hydrate_nodes : hydrate_start, prev_anchor); return hydrate_start; } @@ -130,7 +142,8 @@ export function ns_template(content, flags, ns = 'svg') { push_template_node( is_fragment ? /** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes]) - : /** @type {import('#client').TemplateNode} */ (clone) + : /** @type {import('#client').TemplateNode} */ (clone), + prev_anchor ); return clone; @@ -208,7 +221,7 @@ function run_scripts(node) { */ /*#__NO_SIDE_EFFECTS__*/ export function text(anchor) { - if (!hydrating) return push_template_node(empty()); + if (!hydrating) return push_template_node(empty(), null); var node = hydrate_start; @@ -218,21 +231,24 @@ export function text(anchor) { anchor.before((node = empty())); } - push_template_node(node); + push_template_node(node, null); return node; } -export function comment() { +/** + * @param {Element | Comment | null} prev_anchor + */ +export function comment(prev_anchor) { // we're not delegating to `template` here for performance reasons if (hydrating) { - push_template_node(hydrate_nodes); + push_template_node(hydrate_nodes, prev_anchor); return hydrate_start; } var frag = document.createDocumentFragment(); var anchor = empty(); frag.append(anchor); - push_template_node([anchor]); + push_template_node([anchor], prev_anchor); return frag; } diff --git a/packages/svelte/tests/runtime-runes/samples/each-updates-7/_config.js b/packages/svelte/tests/runtime-runes/samples/each-updates-7/_config.js new file mode 100644 index 0000000000..7f1f5b6589 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-updates-7/_config.js @@ -0,0 +1,27 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ``, + + async test({ assert, target }) { + const [btn1] = target.querySelectorAll('button'); + + flushSync(() => { + btn1.click(); + }); + + flushSync(() => { + btn1.click(); + }); + + flushSync(() => { + btn1.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/each-updates-7/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-updates-7/main.svelte new file mode 100644 index 0000000000..df8b054a42 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-updates-7/main.svelte @@ -0,0 +1,26 @@ + + +{#snippet renderItem(item)} +
  • + {item.name} ({item.id}) +
  • + {#if item.color}{/if} +{/snippet} + + + diff --git a/playgrounds/demo/server.js b/playgrounds/demo/server.js index 0a545e7397..cf2a6b2ab5 100644 --- a/playgrounds/demo/server.js +++ b/playgrounds/demo/server.js @@ -34,7 +34,7 @@ async function createServer() { .replace(``, appHtml) .replace(``, headHtml); - res.status(200).set({ 'Content-Type': 'text/html' }).end(html); + res.end(html); }); return { app, vite };