fix: make `$state` component exports settable (#12345)

* fix: make `$state` component exports settable

fixes #11983

* failing test

* fix

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/12353/head
Simon H 4 months ago committed by GitHub
parent 14cbb65d85
commit e8c3729fc9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: make `$state` component exports settable

@ -1010,6 +1010,9 @@ const runes_scope_tweaker = {
name: node.local.name,
alias: node.exported.name
});
const binding = state.scope.get(node.local.name);
if (binding) binding.reassigned = true;
},
ExportNamedDeclaration(node, { next, state }) {
if (!node.declaration || state.ast_type !== 'instance') {

@ -198,14 +198,38 @@ export function client_component(source, analysis, options) {
}
/** @type {Array<ESTree.Property | ESTree.SpreadElement>} */
const component_returned_object = analysis.exports.map(({ name, alias }) => {
const component_returned_object = analysis.exports.flatMap(({ name, alias }) => {
const binding = instance_state.scope.get(name);
const expression = serialize_get_binding(b.id(name), instance_state);
const getter = b.get(alias ?? name, [b.return(expression)]);
if (expression.type === 'Identifier') {
if (binding?.declaration_kind === 'let' || binding?.declaration_kind === 'var') {
return [
getter,
b.set(alias ?? name, [b.stmt(b.assignment('=', expression, b.id('$$value')))])
];
} else if (!options.dev) {
return b.init(alias ?? name, expression);
}
}
if (expression.type === 'Identifier' && !options.dev) {
return b.init(alias ?? name, expression);
if (binding?.kind === 'state' || binding?.kind === 'frozen_state') {
return [
getter,
b.set(alias ?? name, [
b.stmt(
b.call(
'$.set',
b.id(name),
b.call(binding.kind === 'state' ? '$.proxy' : '$.freeze', b.id('$$value'))
)
)
])
];
}
return b.get(alias ?? name, [b.return(expression)]);
return getter;
});
const properties = [...analysis.instance.scope.declarations].filter(

@ -4,10 +4,9 @@ import { test } from '../../test';
export default test({
test({ assert, target }) {
assert.htmlEqual(target.innerHTML, `0 0 <button>0 / 0</button>`);
const [btn] = target.querySelectorAll('button');
const btn = target.querySelector('button');
btn?.click();
flushSync();
flushSync(() => btn?.click());
assert.htmlEqual(target.innerHTML, '1 2 <button>1 / 2</button>');
}
});

@ -1,5 +1,5 @@
<script>
import Sub from './sub.svelte'
import Sub from './sub.svelte';
let sub = $state();
</script>

@ -0,0 +1,12 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
test({ assert, target }) {
assert.htmlEqual(target.innerHTML, `0 0 <button>0 / 0</button>`);
const btn = target.querySelector('button');
flushSync(() => btn?.click());
assert.htmlEqual(target.innerHTML, '1 2 <button>1 / 2</button>');
}
});

@ -0,0 +1,7 @@
<script>
import Sub from './sub.svelte';
let sub = $state();
</script>
<Sub bind:this={sub} />
<button on:click={() => sub.count++}>{sub?.count} / {sub?.doubled}</button>

@ -0,0 +1,9 @@
<script>
let count = $state(0);
let doubled = $derived(count * 2);
export { count, doubled };
</script>
{count}
{doubled}
Loading…
Cancel
Save