feat: allow $derived runes to be re-assigned locally

reassign-derived
Dominic Gannaway 2 months ago
parent 3b3ed77783
commit 11ac7101dd

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: allow $derived runes to be re-assigned locally

@ -20,7 +20,21 @@ export function validate_assignment(node, argument, state) {
if (state.analysis.runes) { if (state.analysis.runes) {
if (binding?.kind === 'derived') { if (binding?.kind === 'derived') {
e.constant_assignment(node, 'derived state'); if (binding.declaration_kind === 'const') {
e.constant_assignment(node, 'constant derived state');
}
const binding_path = binding.references.find((r) => r.node === binding.node);
if (binding_path) {
let declarator_path = binding_path.path.findLast((n) => n.type === 'VariableDeclarator');
if (
declarator_path &&
(declarator_path.id.type === 'ObjectPattern' ||
declarator_path.id.type === 'ArrayPattern')
) {
e.constant_assignment(node, 'destructured derived state');
}
}
} }
if (binding?.kind === 'each') { if (binding?.kind === 'each') {

@ -166,10 +166,15 @@ export function VariableDeclaration(node, context) {
if (rune === '$derived' || rune === '$derived.by') { if (rune === '$derived' || rune === '$derived.by') {
if (declarator.id.type === 'Identifier') { if (declarator.id.type === 'Identifier') {
const binding = /** @type {Binding} */ (context.state.scope.get(declarator.id.name));
declarations.push( declarations.push(
b.declarator( b.declarator(
declarator.id, declarator.id,
b.call('$.derived', rune === '$derived.by' ? value : b.thunk(value)) b.call(
binding.reassigned ? '$.derived_source' : '$.derived',
rune === '$derived.by' ? value : b.thunk(value)
)
) )
); );
} else { } else {

@ -19,7 +19,7 @@ export function add_state_transformers(context) {
for (const [name, binding] of context.state.scope.declarations) { for (const [name, binding] of context.state.scope.declarations) {
if ( if (
is_state_source(binding, context.state.analysis) || is_state_source(binding, context.state.analysis) ||
binding.kind === 'derived' || (binding.kind === 'derived' && !binding.reassigned) ||
binding.kind === 'legacy_reactive' binding.kind === 'legacy_reactive'
) { ) {
context.state.transform[name] = { context.state.transform[name] = {
@ -50,7 +50,7 @@ export function add_state_transformers(context) {
}; };
} }
if (binding.kind === 'linked_state') { if (binding.kind === 'linked_state' || (binding.kind === 'derived' && binding.reassigned)) {
context.state.transform[name] = { context.state.transform[name] = {
read: b.call, read: b.call,
assign: b.call assign: b.call

@ -93,7 +93,7 @@ export {
template_with_script, template_with_script,
text text
} from './dom/template.js'; } from './dom/template.js';
export { derived, derived_safe_equal } from './reactivity/deriveds.js'; export { derived, derived_source, derived_safe_equal } from './reactivity/deriveds.js';
export { export {
effect_tracking, effect_tracking,
effect_root, effect_root,

@ -9,10 +9,12 @@ import {
current_skip_reaction, current_skip_reaction,
update_reaction, update_reaction,
destroy_effect_children, destroy_effect_children,
increment_version increment_version,
get
} from '../runtime.js'; } from '../runtime.js';
import { equals, safe_equals } from './equality.js'; import { equals, safe_equals } from './equality.js';
import * as e from '../errors.js'; import * as e from '../errors.js';
import { set, source } from './sources.js';
/** /**
* @template V * @template V
@ -50,6 +52,39 @@ export function derived(fn) {
return signal; return signal;
} }
/**
* @template V
* @param {() => V} get_value
* @returns {(value?: V) => V}
*/
export function derived_source(get_value) {
var was_local = false;
var local_source = source(/** @type {V} */ (undefined));
var linked_derived = derived(() => {
var local_value = /** @type {V} */ (get(local_source));
var linked_value = get_value();
if (was_local) {
was_local = false;
return local_value;
}
return linked_value;
});
return function (/** @type {any} */ value) {
if (arguments.length > 0) {
was_local = true;
set(local_source, value);
get(linked_derived);
return value;
}
return (local_source.v = get(linked_derived));
};
}
/** /**
* @template V * @template V
* @param {() => V} fn * @param {() => V} fn

@ -3,6 +3,6 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'constant_assignment', code: 'constant_assignment',
message: 'Cannot assign to derived state' message: 'Cannot assign to destructured derived state'
} }
}); });

@ -1,5 +1,5 @@
<script> <script>
const a = $state(0); const a = $state(0);
let b = $derived(a); let [b] = $derived([a]);
b = 1; b = 1;
</script> </script>

@ -3,6 +3,6 @@ import { test } from '../../test';
export default test({ export default test({
error: { error: {
code: 'constant_assignment', code: 'constant_assignment',
message: 'Cannot assign to derived state' message: 'Cannot assign to destructured derived state'
} }
}); });

@ -1,5 +1,5 @@
<script> <script>
const a = $state(0); const a = $state(0);
let b = $derived(a); let {b} = $derived({a});
b++; b++;
</script> </script>
Loading…
Cancel
Save