Skip to content

Commit 9f962d6

Browse files
authored
breaking: remove $state.link (#12943)
* Revert "breaking: remove `$state.link` callback (#12942)" This reverts commit 0b51ff0. * Revert "feat: adds $state.link rune (#12545)" This reverts commit 63ec2e2. * put changesets back * changeset * merge main
1 parent af7f900 commit 9f962d6

File tree

27 files changed

+33
-315
lines changed

27 files changed

+33
-315
lines changed

.changeset/green-walls-clap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
breaking: remove `$state.link` rune pending further design work

documentation/docs/03-runes/01-state.md

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -62,33 +62,6 @@ Objects and arrays are made deeply reactive by wrapping them with [`Proxies`](ht
6262

6363
> Only POJOs (plain old JavaScript objects) are made deeply reactive. Reactivity will stop at class boundaries and leave those alone
6464
65-
## `$state.link`
66-
67-
Linked state stays up to date with its input:
68-
69-
```js
70-
let a = $state(0);
71-
let b = $state.link(a);
72-
73-
a = 1;
74-
console.log(a, b); // 1, 1
75-
```
76-
77-
You can temporarily _unlink_ state. It will be _relinked_ when the input value changes:
78-
79-
```js
80-
let a = 1;
81-
let b = 1;
82-
// ---cut---
83-
b = 2; // unlink
84-
console.log(a, b); // 1, 2
85-
86-
a = 3; // relink
87-
console.log(a, b); // 3, 3
88-
```
89-
90-
As with `$state`, if `$state.link` is passed a plain object or array it will be made deeply reactive. If passed an existing state proxy it will be reused, meaning that mutating the linked state will mutate the original. To clone a state proxy, you can use [`$state.snapshot`](#$state-snapshot).
91-
9265
## `$state.raw`
9366

9467
State declared with `$state.raw` cannot be mutated; it can only be _reassigned_. In other words, rather than assigning to a property of an object, or using an array method like `push`, replace the object or array altogether if you'd like to update it:

packages/svelte/src/ambient.d.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -124,32 +124,6 @@ declare namespace $state {
124124
*
125125
* @param initial The initial value
126126
*/
127-
128-
/**
129-
* Declares reactive state that is linked to another value. Local reassignments
130-
* will override the linked value until the linked value changes.
131-
*
132-
* Example:
133-
* ```ts
134-
* let a = $state(0);
135-
* let b = $state.link(a);
136-
137-
* a = 1;
138-
* console.log(a, b); // 1, 1
139-
140-
* b = 2; // unlink
141-
* console.log(a, b); // 1, 2
142-
*
143-
* a = 3; // relink
144-
* console.log(a, b); // 3, 3
145-
* ```
146-
*
147-
* https://svelte-5-preview.vercel.app/docs/runes#$state-link
148-
*
149-
* @param value The linked value
150-
*/
151-
export function link<T>(value: T): T;
152-
153127
export function raw<T>(initial: T): T;
154128
export function raw<T>(): T | undefined;
155129
/**

packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ export function BindDirective(node, context) {
3434
node.name !== 'this' && // bind:this also works for regular variables
3535
(!binding ||
3636
(binding.kind !== 'state' &&
37-
binding.kind !== 'linked_state' &&
3837
binding.kind !== 'raw_state' &&
3938
binding.kind !== 'prop' &&
4039
binding.kind !== 'bindable_prop' &&

packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ export function CallExpression(node, context) {
7474

7575
case '$state':
7676
case '$state.raw':
77-
case '$state.link':
7877
case '$derived':
7978
case '$derived.by':
8079
if (
@@ -86,7 +85,7 @@ export function CallExpression(node, context) {
8685

8786
if ((rune === '$derived' || rune === '$derived.by') && node.arguments.length !== 1) {
8887
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
89-
} else if ((rune === '$state' || rune === '$state.link') && node.arguments.length > 1) {
88+
} else if (rune === '$state' && node.arguments.length > 1) {
9089
e.rune_invalid_arguments_length(node, rune, 'zero or one arguments');
9190
}
9291

@@ -170,7 +169,7 @@ export function CallExpression(node, context) {
170169
}
171170

172171
// `$inspect(foo)` or `$derived(foo) should not trigger the `static-state-reference` warning
173-
if (rune === '$inspect' || rune === '$derived' || rune === '$state.link') {
172+
if (rune === '$inspect' || rune === '$derived') {
174173
context.next({ ...context.state, function_depth: context.state.function_depth + 1 });
175174
} else {
176175
context.next();

packages/svelte/src/compiler/phases/2-analyze/visitors/ExportSpecifier.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,7 @@ function validate_export(node, scope, name) {
3838
e.derived_invalid_export(node);
3939
}
4040

41-
if (
42-
(binding.kind === 'state' || binding.kind === 'raw_state' || binding.kind === 'linked_state') &&
43-
binding.reassigned
44-
) {
41+
if ((binding.kind === 'state' || binding.kind === 'raw_state') && binding.reassigned) {
4542
e.state_invalid_export(node);
4643
}
4744
}

packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export function Identifier(node, context) {
9999
context.state.function_depth === binding.scope.function_depth &&
100100
// If we have $state that can be proxied or frozen and isn't re-assigned, then that means
101101
// it's likely not using a primitive value and thus this warning isn't that helpful.
102-
(((binding.kind === 'state' || binding.kind === 'linked_state') &&
102+
((binding.kind === 'state' &&
103103
(binding.reassigned ||
104104
(binding.initial?.type === 'CallExpression' &&
105105
binding.initial.arguments.length === 1 &&

packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export function VariableDeclarator(node, context) {
2121
// TODO feels like this should happen during scope creation?
2222
if (
2323
rune === '$state' ||
24-
rune === '$state.link' ||
2524
rune === '$state.raw' ||
2625
rune === '$derived' ||
2726
rune === '$derived.by' ||
@@ -33,15 +32,13 @@ export function VariableDeclarator(node, context) {
3332
binding.kind =
3433
rune === '$state'
3534
? 'state'
36-
: rune === '$state.link'
37-
? 'linked_state'
38-
: rune === '$state.raw'
39-
? 'raw_state'
40-
: rune === '$derived' || rune === '$derived.by'
41-
? 'derived'
42-
: path.is_rest
43-
? 'rest_prop'
44-
: 'prop';
35+
: rune === '$state.raw'
36+
? 'raw_state'
37+
: rune === '$derived' || rune === '$derived.by'
38+
? 'derived'
39+
: path.is_rest
40+
? 'rest_prop'
41+
: 'prop';
4542
}
4643
}
4744

packages/svelte/src/compiler/phases/3-transform/client/types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export interface ComponentClientTransformState extends ClientTransformState {
8888
}
8989

9090
export interface StateField {
91-
kind: 'state' | 'raw_state' | 'linked_state' | 'derived' | 'derived_by';
91+
kind: 'state' | 'raw_state' | 'derived' | 'derived_by';
9292
id: PrivateIdentifier;
9393
}
9494

packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export function ClassBody(node, context) {
4444
const rune = get_rune(definition.value, context.state.scope);
4545
if (
4646
rune === '$state' ||
47-
rune === '$state.link' ||
4847
rune === '$state.raw' ||
4948
rune === '$derived' ||
5049
rune === '$derived.by'
@@ -56,11 +55,9 @@ export function ClassBody(node, context) {
5655
? 'state'
5756
: rune === '$state.raw'
5857
? 'raw_state'
59-
: rune === '$state.link'
60-
? 'linked_state'
61-
: rune === '$derived.by'
62-
? 'derived_by'
63-
: 'derived',
58+
: rune === '$derived.by'
59+
? 'derived_by'
60+
: 'derived',
6461
// @ts-expect-error this is set in the next pass
6562
id: is_private ? definition.key : null
6663
};
@@ -119,18 +116,11 @@ export function ClassBody(node, context) {
119116
'$.source',
120117
should_proxy(init, context.state.scope) ? b.call('$.proxy', init) : init
121118
)
122-
: field.kind === 'linked_state'
123-
? b.call(
124-
'$.source_link',
125-
b.thunk(
126-
should_proxy(init, context.state.scope) ? b.call('$.proxy', init) : init
127-
)
128-
)
129-
: field.kind === 'raw_state'
130-
? b.call('$.source', init)
131-
: field.kind === 'derived_by'
132-
? b.call('$.derived', init)
133-
: b.call('$.derived', b.thunk(init));
119+
: field.kind === 'raw_state'
120+
? b.call('$.source', init)
121+
: field.kind === 'derived_by'
122+
? b.call('$.derived', init)
123+
: b.call('$.derived', b.thunk(init));
134124
} else {
135125
// if no arguments, we know it's state as `$derived()` is a compile error
136126
value = b.call('$.source');
@@ -143,24 +133,8 @@ export function ClassBody(node, context) {
143133
const member = b.member(b.this, field.id);
144134
body.push(b.prop_def(field.id, value));
145135

146-
if (field.kind === 'linked_state') {
147-
// get foo() { return this.#foo; }
148-
body.push(b.method('get', definition.key, [], [b.return(b.call(member))]));
149-
150-
// set foo(value) { this.#foo = value; }
151-
const value = b.id('value');
152-
body.push(
153-
b.method(
154-
'set',
155-
definition.key,
156-
[value],
157-
[b.stmt(b.call(member, build_proxy_reassignment(value, field.id)))]
158-
)
159-
);
160-
} else {
161-
// get foo() { return this.#foo; }
162-
body.push(b.method('get', definition.key, [], [b.return(b.call('$.get', member))]));
163-
}
136+
// get foo() { return this.#foo; }
137+
body.push(b.method('get', definition.key, [], [b.return(b.call('$.get', member))]));
164138

165139
if (field.kind === 'state') {
166140
// set foo(value) { this.#foo = value; }

packages/svelte/src/compiler/phases/3-transform/client/visitors/Program.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export function Program(_, context) {
102102
if (is_prop_source(binding, context.state)) {
103103
context.state.transform[name] = {
104104
read: b.call,
105-
assign: b.call,
105+
assign: (node, value) => b.call(node, value),
106106
mutate: (node, value) => {
107107
if (binding.kind === 'bindable_prop') {
108108
// only necessary for interop with legacy parent bindings

packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export function VariableDeclaration(node, context) {
113113
const value =
114114
args.length === 0 ? b.id('undefined') : /** @type {Expression} */ (context.visit(args[0]));
115115

116-
if (rune === '$state' || rune === '$state.raw' || rune === '$state.link') {
116+
if (rune === '$state' || rune === '$state.raw') {
117117
/**
118118
* @param {Identifier} id
119119
* @param {Expression} value
@@ -122,20 +122,12 @@ export function VariableDeclaration(node, context) {
122122
const binding = /** @type {import('#compiler').Binding} */ (
123123
context.state.scope.get(id.name)
124124
);
125-
126-
if (
127-
(rune === '$state' || rune === '$state.link') &&
128-
should_proxy(value, context.state.scope)
129-
) {
125+
if (rune === '$state' && should_proxy(value, context.state.scope)) {
130126
value = b.call('$.proxy', value);
131127
}
132-
133-
if (rune === '$state.link') {
134-
value = b.call('$.source_link', b.thunk(value));
135-
} else if (is_state_source(binding, context.state.analysis)) {
128+
if (is_state_source(binding, context.state.analysis)) {
136129
value = b.call('$.source', value);
137130
}
138-
139131
return value;
140132
};
141133

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/declarations.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,5 @@ export function add_state_transformers(context) {
4949
}
5050
};
5151
}
52-
53-
if (binding.kind === 'linked_state') {
54-
context.state.transform[name] = {
55-
read: b.call,
56-
assign: b.call
57-
};
58-
}
5952
}
6053
}

packages/svelte/src/compiler/phases/3-transform/server/visitors/PropertyDefinition.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,7 @@ export function PropertyDefinition(node, context) {
1111
if (context.state.analysis.runes && node.value != null && node.value.type === 'CallExpression') {
1212
const rune = get_rune(node.value, context.state.scope);
1313

14-
if (
15-
rune === '$state' ||
16-
rune === '$state.link' ||
17-
rune === '$state.raw' ||
18-
rune === '$derived'
19-
) {
14+
if (rune === '$state' || rune === '$state.raw' || rune === '$derived') {
2015
return {
2116
...node,
2217
value:

packages/svelte/src/compiler/types/index.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,6 @@ export interface Binding {
278278
| 'bindable_prop'
279279
| 'rest_prop'
280280
| 'state'
281-
| 'linked_state'
282281
| 'raw_state'
283282
| 'derived'
284283
| 'each'

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export {
105105
user_effect,
106106
user_pre_effect
107107
} from './reactivity/effects.js';
108-
export { mutable_source, mutate, source, source_link, set } from './reactivity/sources.js';
108+
export { mutable_source, mutate, source, set } from './reactivity/sources.js';
109109
export {
110110
prop,
111111
rest_props,

packages/svelte/src/internal/client/reactivity/sources.js

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import {
2626
MAYBE_DIRTY
2727
} from '../constants.js';
2828
import * as e from '../errors.js';
29-
import { derived } from './deriveds.js';
3029

3130
let inspect_effects = new Set();
3231

@@ -46,39 +45,6 @@ export function source(v) {
4645
};
4746
}
4847

49-
/**
50-
* @template V
51-
* @param {() => V} get_value
52-
* @returns {(value?: V) => V}
53-
*/
54-
export function source_link(get_value) {
55-
var was_local = false;
56-
var local_source = source(/** @type {V} */ (undefined));
57-
58-
var linked_derived = derived(() => {
59-
var local_value = /** @type {V} */ (get(local_source));
60-
var linked_value = get_value();
61-
62-
if (was_local) {
63-
was_local = false;
64-
return local_value;
65-
}
66-
67-
return linked_value;
68-
});
69-
70-
return function (/** @type {any} */ value) {
71-
if (arguments.length > 0) {
72-
was_local = true;
73-
set(local_source, value);
74-
get(linked_derived);
75-
return value;
76-
}
77-
78-
return (local_source.v = get(linked_derived));
79-
};
80-
}
81-
8248
/**
8349
* @template V
8450
* @param {V} initial_value

packages/svelte/src/utils.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,6 @@ export function is_mathml(name) {
393393

394394
const RUNES = /** @type {const} */ ([
395395
'$state',
396-
'$state.link',
397396
'$state.raw',
398397
'$state.snapshot',
399398
'$props',

0 commit comments

Comments
 (0)