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