feat: allow `$derived($state())` for deep reactive deriveds

allow-state-as-derived-init
paoloricciuti 1 week ago
parent 0cdc431562
commit e846ebb578

@ -0,0 +1,5 @@
---
'svelte': minor
---
feat: allow `$derived($state())` for deep reactive deriveds

@ -121,7 +121,15 @@ export function CallExpression(node, context) {
is_class_property_definition(parent) ||
is_class_property_assignment_at_constructor_root(parent, context);
if (!valid) {
if (
!valid &&
(rune !== '$state' ||
!(
parent.type === 'CallExpression' &&
parent.callee.type === 'Identifier' &&
parent.callee.name === '$derived'
))
) {
e.state_invalid_placement(node, rune);
}

@ -5,6 +5,7 @@ import * as b from '#compiler/builders';
import { get_rune } from '../../../scope.js';
import { should_proxy } from '../utils.js';
import { get_inspect_args } from '../../utils.js';
import { get_parent } from '../../../../utils/ast.js';
/**
* @param {CallExpression} node
@ -12,6 +13,7 @@ import { get_inspect_args } from '../../utils.js';
*/
export function CallExpression(node, context) {
const rune = get_rune(node, context.state.scope);
const parent = get_parent(context.path, -1);
switch (rune) {
case '$host':
@ -40,7 +42,25 @@ export function CallExpression(node, context) {
}
const callee = b.id('$.state', node.callee.loc);
return b.call(callee, value);
let retval = b.call(callee, value);
// this is not the path you would expect from `$derived($state(...))`, but
// it looks like this because we visit the arguments of the derived in the
// VariableDeclaration visitor
if (
(parent.type === 'VariableDeclaration' &&
parent.declarations.length === 1 &&
parent.declarations[0].init?.type === 'CallExpression' &&
parent.declarations[0].init.callee.type === 'Identifier' &&
parent.declarations[0].init?.callee.name === '$derived') ||
(parent.type === 'CallExpression' &&
parent.callee.type === 'Identifier' &&
parent.callee.name === '$derived')
) {
retval = b.call('$.get', retval);
}
return retval;
}
case '$derived':

@ -0,0 +1,6 @@
export class Test {
local_arr;
constructor(arr) {
this.local_arr = $derived($state(arr()));
}
}

@ -0,0 +1,13 @@
<script>
import { Test } from "./Class.svelte.js";
let { arr } = $props();
let test = new Test(() => arr);
</script>
<button onclick={()=>{
test.local_arr.push(test.local_arr.length + 1);
}}>push</button>
{#each test.local_arr as item}
<p>{item}</p>
{/each}

@ -0,0 +1,37 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
async test({ target, assert }) {
const [update_parent, push] = target.querySelectorAll('button');
let p_tags = target.querySelectorAll('p');
assert.equal(p_tags.length, 3);
assert.equal(p_tags[0].textContent, '1');
assert.equal(p_tags[1].textContent, '2');
assert.equal(p_tags[2].textContent, '3');
flushSync(() => {
push.click();
});
p_tags = target.querySelectorAll('p');
assert.equal(p_tags.length, 4);
assert.equal(p_tags[0].textContent, '1');
assert.equal(p_tags[1].textContent, '2');
assert.equal(p_tags[2].textContent, '3');
assert.equal(p_tags[3].textContent, '4');
flushSync(() => {
update_parent.click();
});
p_tags = target.querySelectorAll('p');
assert.equal(p_tags.length, 3);
assert.equal(p_tags[0].textContent, '4');
assert.equal(p_tags[1].textContent, '5');
assert.equal(p_tags[2].textContent, '6');
}
});

@ -0,0 +1,10 @@
<script>
import Component from './Component.svelte';
let arr = $state.raw([1, 2, 3]);
</script>
<button onclick={()=>{
arr = [4,5,6];
}}>update</button>
<Component {arr}/>

@ -0,0 +1,12 @@
<script>
let { arr } = $props();
let local_arr = $derived($state(arr));
</script>
<button onclick={()=>{
local_arr.push(local_arr.length + 1);
}}>push</button>
{#each local_arr as item}
<p>{item}</p>
{/each}

@ -0,0 +1,37 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
async test({ target, assert }) {
const [update_parent, push] = target.querySelectorAll('button');
let p_tags = target.querySelectorAll('p');
assert.equal(p_tags.length, 3);
assert.equal(p_tags[0].textContent, '1');
assert.equal(p_tags[1].textContent, '2');
assert.equal(p_tags[2].textContent, '3');
flushSync(() => {
push.click();
});
p_tags = target.querySelectorAll('p');
assert.equal(p_tags.length, 4);
assert.equal(p_tags[0].textContent, '1');
assert.equal(p_tags[1].textContent, '2');
assert.equal(p_tags[2].textContent, '3');
assert.equal(p_tags[3].textContent, '4');
flushSync(() => {
update_parent.click();
});
p_tags = target.querySelectorAll('p');
assert.equal(p_tags.length, 3);
assert.equal(p_tags[0].textContent, '4');
assert.equal(p_tags[1].textContent, '5');
assert.equal(p_tags[2].textContent, '6');
}
});

@ -0,0 +1,10 @@
<script>
import Component from './Component.svelte';
let arr = $state.raw([1, 2, 3]);
</script>
<button onclick={()=>{
arr = [4,5,6];
}}>update</button>
<Component {arr}/>
Loading…
Cancel
Save