From 9a293ea8c4fbcfe69446d8d11fce142b9c66e00b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 1 Jul 2024 11:14:00 -0700 Subject: [PATCH] fix: add dummy anchor for dynamic component HMR wrappers (#12252) * add failing test * add dummy anchor for HMR wrappers * lint --- .../3-transform/client/visitors/template.js | 19 +++++++++-------- .../svelte/src/internal/client/dev/hmr.js | 2 +- .../client/dom/blocks/svelte-component.js | 12 +++++++++-- .../samples/hmr-removal/Child.svelte | 1 + .../samples/hmr-removal/_config.js | 21 +++++++++++++++++++ .../samples/hmr-removal/main.svelte | 11 ++++++++++ 6 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/hmr-removal/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/hmr-removal/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/hmr-removal/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 4dcd1d4087..24884305d8 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 @@ -653,9 +653,10 @@ function collect_parent_each_blocks(context) { * @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node * @param {string} component_name * @param {import('../types.js').ComponentContext} context + * @param {import('estree').Expression} anchor * @returns {import('estree').Statement} */ -function serialize_inline_component(node, component_name, context) { +function serialize_inline_component(node, component_name, context, anchor = context.state.node) { /** @type {Array} */ const props_and_spreads = []; @@ -946,13 +947,13 @@ function serialize_inline_component(node, component_name, context) { node_id, b.thunk(/** @type {import('estree').Expression} */ (context.visit(node.expression))), b.arrow( - [b.id(component_name)], + [b.id('$$anchor'), b.id(component_name)], b.block([ ...binding_initializers, b.stmt( context.state.options.dev - ? b.call('$.validate_dynamic_component', b.thunk(prev(node_id))) - : prev(node_id) + ? b.call('$.validate_dynamic_component', b.thunk(prev(b.id('$$anchor')))) + : prev(b.id('$$anchor')) ) ]) ) @@ -970,12 +971,12 @@ function serialize_inline_component(node, component_name, context) { ); statements.push( - b.stmt(b.call('$.css_props', context.state.node, b.thunk(b.object(custom_css_props)))), - b.stmt(fn(b.member(context.state.node, b.id('lastChild')))) + b.stmt(b.call('$.css_props', anchor, b.thunk(b.object(custom_css_props)))), + b.stmt(fn(b.member(anchor, b.id('lastChild')))) ); } else { context.state.template.push(''); - statements.push(b.stmt(fn(context.state.node))); + statements.push(b.stmt(fn(anchor))); } return statements.length > 1 ? b.block(statements) : statements[0]; @@ -3025,7 +3026,7 @@ export const template_visitors = { Component(node, context) { if (node.metadata.dynamic) { // Handle dynamic references to what seems like static inline components - const component = serialize_inline_component(node, '$$component', context); + const component = serialize_inline_component(node, '$$component', context, b.id('$$anchor')); context.state.init.push( b.stmt( b.call( @@ -3036,7 +3037,7 @@ export const template_visitors = { b.thunk( /** @type {import('estree').Expression} */ (context.visit(b.member_id(node.name))) ), - b.arrow([b.id('$$component')], b.block([component])) + b.arrow([b.id('$$anchor'), b.id('$$component')], b.block([component])) ) ) ); diff --git a/packages/svelte/src/internal/client/dev/hmr.js b/packages/svelte/src/internal/client/dev/hmr.js index b0ea23df8c..e9b4a60bbf 100644 --- a/packages/svelte/src/internal/client/dev/hmr.js +++ b/packages/svelte/src/internal/client/dev/hmr.js @@ -18,7 +18,7 @@ export function hmr(source) { /** @type {import("#client").Effect} */ let effect; - block(null, 0, () => { + block(anchor, 0, () => { const component = get(source); if (effect) { diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-component.js b/packages/svelte/src/internal/client/dom/blocks/svelte-component.js index 0e04eff10b..b4b897d6b4 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-component.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-component.js @@ -1,11 +1,13 @@ +import { DEV } from 'esm-env'; import { block, branch, pause_effect } from '../../reactivity/effects.js'; +import { empty } from '../operations.js'; /** * @template P * @template {(props: P) => void} C * @param {import('#client').TemplateNode} anchor * @param {() => C} get_component - * @param {(component: C) => import('#client').Dom | void} render_fn + * @param {(anchor: import('#client').TemplateNode, component: C) => import('#client').Dom | void} render_fn * @returns {void} */ export function component(anchor, get_component, render_fn) { @@ -15,6 +17,11 @@ export function component(anchor, get_component, render_fn) { /** @type {import('#client').Effect | null} */ let effect; + var component_anchor = anchor; + + // create a dummy anchor for the HMR wrapper, if such there be + if (DEV) component_anchor = empty(); + block(anchor, 0, () => { if (component === (component = get_component())) return; @@ -24,7 +31,8 @@ export function component(anchor, get_component, render_fn) { } if (component) { - effect = branch(() => render_fn(component)); + if (DEV) anchor.before(component_anchor); + effect = branch(() => render_fn(component_anchor, component)); } }); } diff --git a/packages/svelte/tests/runtime-runes/samples/hmr-removal/Child.svelte b/packages/svelte/tests/runtime-runes/samples/hmr-removal/Child.svelte new file mode 100644 index 0000000000..302a01f335 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/hmr-removal/Child.svelte @@ -0,0 +1 @@ +

hello

diff --git a/packages/svelte/tests/runtime-runes/samples/hmr-removal/_config.js b/packages/svelte/tests/runtime-runes/samples/hmr-removal/_config.js new file mode 100644 index 0000000000..8d6a3f1d5e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/hmr-removal/_config.js @@ -0,0 +1,21 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + hmr: true + }, + + html: ``, + + test({ assert, target }) { + const button = target.querySelector('button'); + + flushSync(() => button?.click()); + assert.htmlEqual(target.innerHTML, `

hello

`); + + flushSync(() => button?.click()); + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/hmr-removal/main.svelte b/packages/svelte/tests/runtime-runes/samples/hmr-removal/main.svelte new file mode 100644 index 0000000000..2cc30370b8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/hmr-removal/main.svelte @@ -0,0 +1,11 @@ + + + + +{#if open} + +{/if}