Skip to content

Commit b1dcc3b

Browse files
committed
fix: replicate Svelte 4 props update detection in legacy mode
fixes #11448 by wrapping props in deriveds
1 parent fc43cff commit b1dcc3b

File tree

8 files changed

+71
-31
lines changed

8 files changed

+71
-31
lines changed

.changeset/empty-flowers-change.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: replicate Svelte 4 props update detection in legacy mode

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export function serialize_get_binding(node, state) {
8989

9090
if (binding.kind === 'prop' || binding.kind === 'bindable_prop') {
9191
if (
92+
!state.analysis.runes ||
9293
state.analysis.accessors ||
9394
(state.analysis.immutable ? binding.reassigned : binding.mutated) ||
9495
binding.initial

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

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -93,24 +93,17 @@ export const javascript_visitors_legacy = {
9393
state.scope.get(declarator.id.name)
9494
);
9595

96-
if (
97-
state.analysis.accessors ||
98-
(state.analysis.immutable ? binding.reassigned : binding.mutated) ||
99-
declarator.init
100-
) {
101-
declarations.push(
102-
b.declarator(
103-
declarator.id,
104-
get_prop_source(
105-
binding,
106-
state,
107-
binding.prop_alias ?? declarator.id.name,
108-
declarator.init &&
109-
/** @type {import('estree').Expression} */ (visit(declarator.init))
110-
)
96+
declarations.push(
97+
b.declarator(
98+
declarator.id,
99+
get_prop_source(
100+
binding,
101+
state,
102+
binding.prop_alias ?? declarator.id.name,
103+
declarator.init && /** @type {import('estree').Expression} */ (visit(declarator.init))
111104
)
112-
);
113-
}
105+
)
106+
);
114107

115108
continue;
116109
}

packages/svelte/src/internal/client/reactivity/props.js

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from '../../../constants.js';
88
import { get_descriptor, is_function } from '../utils.js';
99
import { mutable_source, set, source } from './sources.js';
10-
import { derived } from './deriveds.js';
10+
import { derived, derived_safe_equal } from './deriveds.js';
1111
import { get, is_signals_recorded, untrack, update } from '../runtime.js';
1212
import { safe_equals } from './equality.js';
1313
import { inspect_fn } from '../dev/inspect.js';
@@ -236,18 +236,27 @@ export function prop(props, key, flags, fallback) {
236236
if (setter) setter(prop_value);
237237
}
238238

239-
var getter = runes
240-
? () => {
241-
var value = /** @type {V} */ (props[key]);
242-
if (value === undefined) return get_fallback();
243-
fallback_dirty = true;
244-
return value;
245-
}
246-
: () => {
247-
var value = /** @type {V} */ (props[key]);
248-
if (value !== undefined) fallback_value = /** @type {V} */ (undefined);
249-
return value === undefined ? fallback_value : value;
250-
};
239+
/** @type {() => V} */
240+
var getter;
241+
if (runes) {
242+
getter = () => {
243+
var value = /** @type {V} */ (props[key]);
244+
if (value === undefined) return get_fallback();
245+
fallback_dirty = true;
246+
return value;
247+
};
248+
} else {
249+
// Svelte 4 did not trigger updates when a primitive value was updated to the same value.
250+
// Replicate that behavior through using a derived
251+
var derived_getter = (immutable ? derived : derived_safe_equal)(
252+
() => /** @type {V} */ (props[key])
253+
);
254+
getter = () => {
255+
var value = get(derived_getter);
256+
if (value !== undefined) fallback_value = /** @type {V} */ (undefined);
257+
return value === undefined ? fallback_value : value;
258+
};
259+
}
251260

252261
// easy mode — prop is never written to
253262
if ((flags & PROPS_IS_UPDATED) === 0) {

packages/svelte/src/internal/client/runtime.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,16 @@ export function invalidate_inner_signals(fn) {
797797
captured_signals = previous_captured_signals;
798798
}
799799
for (signal of captured) {
800-
mutate(signal, null /* doesnt matter */);
800+
// Go one level up because derived signals created as part of props in legacy mode
801+
if ((signal.f & DERIVED) !== 0) {
802+
for (const dep of /** @type {import('#client').Derived} */ (signal).deps || []) {
803+
if ((dep.f & DERIVED) === 0) {
804+
mutate(dep, null /* doesnt matter */);
805+
}
806+
}
807+
} else {
808+
mutate(signal, null /* doesnt matter */);
809+
}
801810
}
802811
}
803812

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
export let primitive;
3+
export let object;
4+
$: primitive && console.log('primitive');
5+
$: object && console.log('object');
6+
</script>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
async test({ assert, logs, target }) {
5+
assert.deepEqual(logs, ['primitive', 'object']);
6+
await target.querySelector('button')?.click();
7+
assert.deepEqual(logs, ['primitive', 'object', 'object']);
8+
}
9+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
import Nested from './Nested.svelte';
3+
4+
let value = { count: 1 };
5+
</script>
6+
7+
<button on:click={() => value = { count: 1 }}>reassign</button>
8+
<Nested primitive={value.count} object={value} />

0 commit comments

Comments
 (0)