Skip to content

Commit 9a293ea

Browse files
authored
fix: add dummy anchor for dynamic component HMR wrappers (#12252)
* add failing test * add dummy anchor for HMR wrappers * lint
1 parent 9f51760 commit 9a293ea

File tree

6 files changed

+54
-12
lines changed

6 files changed

+54
-12
lines changed

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -653,9 +653,10 @@ function collect_parent_each_blocks(context) {
653653
* @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node
654654
* @param {string} component_name
655655
* @param {import('../types.js').ComponentContext} context
656+
* @param {import('estree').Expression} anchor
656657
* @returns {import('estree').Statement}
657658
*/
658-
function serialize_inline_component(node, component_name, context) {
659+
function serialize_inline_component(node, component_name, context, anchor = context.state.node) {
659660
/** @type {Array<import('estree').Property[] | import('estree').Expression>} */
660661
const props_and_spreads = [];
661662

@@ -946,13 +947,13 @@ function serialize_inline_component(node, component_name, context) {
946947
node_id,
947948
b.thunk(/** @type {import('estree').Expression} */ (context.visit(node.expression))),
948949
b.arrow(
949-
[b.id(component_name)],
950+
[b.id('$$anchor'), b.id(component_name)],
950951
b.block([
951952
...binding_initializers,
952953
b.stmt(
953954
context.state.options.dev
954-
? b.call('$.validate_dynamic_component', b.thunk(prev(node_id)))
955-
: prev(node_id)
955+
? b.call('$.validate_dynamic_component', b.thunk(prev(b.id('$$anchor'))))
956+
: prev(b.id('$$anchor'))
956957
)
957958
])
958959
)
@@ -970,12 +971,12 @@ function serialize_inline_component(node, component_name, context) {
970971
);
971972

972973
statements.push(
973-
b.stmt(b.call('$.css_props', context.state.node, b.thunk(b.object(custom_css_props)))),
974-
b.stmt(fn(b.member(context.state.node, b.id('lastChild'))))
974+
b.stmt(b.call('$.css_props', anchor, b.thunk(b.object(custom_css_props)))),
975+
b.stmt(fn(b.member(anchor, b.id('lastChild'))))
975976
);
976977
} else {
977978
context.state.template.push('<!>');
978-
statements.push(b.stmt(fn(context.state.node)));
979+
statements.push(b.stmt(fn(anchor)));
979980
}
980981

981982
return statements.length > 1 ? b.block(statements) : statements[0];
@@ -3025,7 +3026,7 @@ export const template_visitors = {
30253026
Component(node, context) {
30263027
if (node.metadata.dynamic) {
30273028
// Handle dynamic references to what seems like static inline components
3028-
const component = serialize_inline_component(node, '$$component', context);
3029+
const component = serialize_inline_component(node, '$$component', context, b.id('$$anchor'));
30293030
context.state.init.push(
30303031
b.stmt(
30313032
b.call(
@@ -3036,7 +3037,7 @@ export const template_visitors = {
30363037
b.thunk(
30373038
/** @type {import('estree').Expression} */ (context.visit(b.member_id(node.name)))
30383039
),
3039-
b.arrow([b.id('$$component')], b.block([component]))
3040+
b.arrow([b.id('$$anchor'), b.id('$$component')], b.block([component]))
30403041
)
30413042
)
30423043
);

packages/svelte/src/internal/client/dev/hmr.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function hmr(source) {
1818
/** @type {import("#client").Effect} */
1919
let effect;
2020

21-
block(null, 0, () => {
21+
block(anchor, 0, () => {
2222
const component = get(source);
2323

2424
if (effect) {

packages/svelte/src/internal/client/dom/blocks/svelte-component.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import { DEV } from 'esm-env';
12
import { block, branch, pause_effect } from '../../reactivity/effects.js';
3+
import { empty } from '../operations.js';
24

35
/**
46
* @template P
57
* @template {(props: P) => void} C
68
* @param {import('#client').TemplateNode} anchor
79
* @param {() => C} get_component
8-
* @param {(component: C) => import('#client').Dom | void} render_fn
10+
* @param {(anchor: import('#client').TemplateNode, component: C) => import('#client').Dom | void} render_fn
911
* @returns {void}
1012
*/
1113
export function component(anchor, get_component, render_fn) {
@@ -15,6 +17,11 @@ export function component(anchor, get_component, render_fn) {
1517
/** @type {import('#client').Effect | null} */
1618
let effect;
1719

20+
var component_anchor = anchor;
21+
22+
// create a dummy anchor for the HMR wrapper, if such there be
23+
if (DEV) component_anchor = empty();
24+
1825
block(anchor, 0, () => {
1926
if (component === (component = get_component())) return;
2027

@@ -24,7 +31,8 @@ export function component(anchor, get_component, render_fn) {
2431
}
2532

2633
if (component) {
27-
effect = branch(() => render_fn(component));
34+
if (DEV) anchor.before(component_anchor);
35+
effect = branch(() => render_fn(component_anchor, component));
2836
}
2937
});
3038
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>hello</p>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
compileOptions: {
6+
dev: true,
7+
hmr: true
8+
},
9+
10+
html: `<button>toggle</button>`,
11+
12+
test({ assert, target }) {
13+
const button = target.querySelector('button');
14+
15+
flushSync(() => button?.click());
16+
assert.htmlEqual(target.innerHTML, `<button>toggle</button><p>hello</p>`);
17+
18+
flushSync(() => button?.click());
19+
assert.htmlEqual(target.innerHTML, `<button>toggle</button>`);
20+
}
21+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
import Child from './Child.svelte';
3+
4+
let open = $state(false);
5+
</script>
6+
7+
<button onclick={() => (open = !open)}>toggle</button>
8+
9+
{#if open}
10+
<Child />
11+
{/if}

0 commit comments

Comments
 (0)