diff --git a/.changeset/tiny-moose-kiss.md b/.changeset/tiny-moose-kiss.md
new file mode 100644
index 000000000000..1c4ec34c6037
--- /dev/null
+++ b/.changeset/tiny-moose-kiss.md
@@ -0,0 +1,5 @@
+---
+"svelte": patch
+---
+
+fix: ensure store from props is hoisted correctly
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
index 481caf656182..e6fdb902f0fe 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
@@ -456,27 +456,30 @@ function get_hoistable_params(node, context) {
/** @type {import('estree').Identifier[]} */
const params = [];
- let added_props = false;
/**
- * we only want to push if it's not already present to avoid name clashing
+ * We only want to push if it's not already present to avoid name clashing
* @param {import('estree').Identifier} id
*/
- function safe_push(id) {
+ function push_unique(id) {
if (!params.find((param) => param.name === id.name)) {
params.push(id);
}
}
for (const [reference] of scope.references) {
- const binding = scope.get(reference);
+ let binding = scope.get(reference);
if (binding !== null && !scope.declarations.has(reference) && binding.initial !== node) {
if (binding.kind === 'store_sub') {
// We need both the subscription for getting the value and the store for updating
- safe_push(b.id(binding.node.name.slice(1)));
- safe_push(b.id(binding.node.name));
- } else if (
+ push_unique(b.id(binding.node.name));
+ binding = /** @type {import('#compiler').Binding} */ (
+ scope.get(binding.node.name.slice(1))
+ );
+ }
+
+ if (
// If it's a destructured derived binding, then we can extract the derived signal reference and use that.
binding.expression !== null &&
typeof binding.expression !== 'function' &&
@@ -486,7 +489,7 @@ function get_hoistable_params(node, context) {
binding.expression.object.callee.name === '$.get' &&
binding.expression.object.arguments[0].type === 'Identifier'
) {
- safe_push(b.id(binding.expression.object.arguments[0].name));
+ push_unique(b.id(binding.expression.object.arguments[0].name));
} else if (
// If we are referencing a simple $$props value, then we need to reference the object property instead
(binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
@@ -494,14 +497,10 @@ function get_hoistable_params(node, context) {
binding.initial === null &&
!context.state.analysis.accessors
) {
- // Handle $$props.something use-cases
- if (!added_props) {
- added_props = true;
- safe_push(b.id('$$props'));
- }
+ push_unique(b.id('$$props'));
} else {
// create a copy to remove start/end tags which would mess up source maps
- safe_push(b.id(binding.node.name));
+ push_unique(b.id(binding.node.name));
}
}
}
diff --git a/packages/svelte/tests/runtime-runes/samples/store-from-props-hoisting/_config.js b/packages/svelte/tests/runtime-runes/samples/store-from-props-hoisting/_config.js
new file mode 100644
index 000000000000..bd5593eb0863
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/store-from-props-hoisting/_config.js
@@ -0,0 +1,18 @@
+import { test } from '../../test';
+
+export default test({
+ compileOptions: {
+ dev: true
+ },
+ async test({ assert, target }) {
+ const button = target.querySelector('button');
+ await button?.click();
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/store-from-props-hoisting/child.svelte b/packages/svelte/tests/runtime-runes/samples/store-from-props-hoisting/child.svelte
new file mode 100644
index 000000000000..77a04f3e9f82
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/store-from-props-hoisting/child.svelte
@@ -0,0 +1,8 @@
+
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/store-from-props-hoisting/main.svelte b/packages/svelte/tests/runtime-runes/samples/store-from-props-hoisting/main.svelte
new file mode 100644
index 000000000000..0e7390d164f3
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/store-from-props-hoisting/main.svelte
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file