Skip to content

Commit 0812b10

Browse files
authored
breaking: overhaul proxies, remove $state.is (#12916)
* chore: use closures for state proxies * use variables * early return * tidy up * move ownership stuff into separate object * put original value directly on STATE_SYMBOL * rename * tidy up * tidy * tweak * fix * remove is_frozen check * remove `$state.is` * avoid mutations * tweak * changesets * changeset * changeset * regenerate * add comment * add note * add test
1 parent 5797f5e commit 0812b10

File tree

34 files changed

+395
-402
lines changed

34 files changed

+395
-402
lines changed

.changeset/fifty-actors-agree.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: disallow `Object.defineProperty` on state proxies with non-basic descriptors

.changeset/gorgeous-pans-sort.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: allow frozen objects to be proxied

.changeset/heavy-houses-pay.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: avoid mutations to underlying proxied object with $state

.changeset/short-starfishes-beg.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.is rune

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

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -101,28 +101,6 @@ To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snaps
101101

102102
This is handy when you want to pass some state to an external library or API that doesn't expect a proxy, such as `structuredClone`.
103103

104-
## `$state.is`
105-
106-
Sometimes you might need to compare two values, one of which is a reactive `$state(...)` proxy but the other is not. For this you can use `$state.is(a, b)`:
107-
108-
```svelte
109-
<script>
110-
let foo = $state({});
111-
let bar = {};
112-
113-
foo.bar = bar;
114-
115-
console.log(foo.bar === bar); // false — `foo.bar` is a reactive proxy
116-
console.log($state.is(foo.bar, bar)); // true
117-
</script>
118-
```
119-
120-
This is handy when you might want to check if the object exists within a deeply reactive object/array.
121-
122-
Under the hood, `$state.is` uses [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) for comparing the values.
123-
124-
> Use this as an escape hatch - most of the time you don't need this. Svelte will warn you at dev time if you happen to run into this problem
125-
126104
## `$derived`
127105

128106
Derived state is declared with the `$derived` rune:

packages/svelte/messages/client-errors/errors.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@
6464

6565
> The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files
6666
67+
## state_descriptors_fixed
68+
69+
> Property descriptors defined on `$state` objects must contain `value` and always be `enumerable`, `configurable` and `writable`.
70+
6771
## state_prototype_fixed
6872

6973
> Cannot set prototype of `$state` object

packages/svelte/messages/client-warnings/warnings.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
4545
## state_proxy_equality_mismatch
4646

47-
> Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `%operator%` will produce unexpected results. Consider using `$state.is(a, b)` instead%details%
47+
> Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `%operator%` will produce unexpected results
4848
4949
`$state(...)` creates a [proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) of the value it is passed. The proxy and the value have different identities, meaning equality checks will always return `false`:
5050

@@ -57,15 +57,4 @@
5757
</script>
5858
```
5959

60-
In the rare case that you need to compare them, you can use `$state.is`, which unwraps proxies:
61-
62-
```svelte
63-
<script>
64-
let value = { foo: 'bar' };
65-
let proxy = $state(value);
66-
67-
$state.is(value, proxy); // true
68-
</script>
69-
```
70-
71-
During development, Svelte will warn you when comparing values with proxies.
60+
To resolve this, ensure you're comparing values where both values were created with `$state(...)`, or neither were. Note that `$state.raw(...)` will _not_ create a state proxy.

packages/svelte/messages/compile-errors/script.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@
122122

123123
> Cannot use rune without parentheses
124124
125+
## rune_removed
126+
127+
> The `%name%` rune has been removed
128+
125129
## rune_renamed
126130

127131
> `%name%` is now `%replacement%`

packages/svelte/src/ambient.d.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -147,27 +147,6 @@ declare namespace $state {
147147
*/
148148
export function snapshot<T>(state: T): Snapshot<T>;
149149

150-
/**
151-
* Compare two values, one or both of which is a reactive `$state(...)` proxy.
152-
*
153-
* Example:
154-
* ```ts
155-
* <script>
156-
* let foo = $state({});
157-
* let bar = {};
158-
*
159-
* foo.bar = bar;
160-
*
161-
* console.log(foo.bar === bar); // false — `foo.bar` is a reactive proxy
162-
* console.log($state.is(foo.bar, bar)); // true
163-
* </script>
164-
* ```
165-
*
166-
* https://svelte-5-preview.vercel.app/docs/runes#$state.is
167-
*
168-
*/
169-
export function is(a: any, b: any): boolean;
170-
171150
// prevent intellisense from being unhelpful
172151
/** @deprecated */
173152
export const apply: never;

packages/svelte/src/compiler/errors.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,16 @@ export function rune_missing_parentheses(node) {
348348
e(node, "rune_missing_parentheses", "Cannot use rune without parentheses");
349349
}
350350

351+
/**
352+
* The `%name%` rune has been removed
353+
* @param {null | number | NodeLike} node
354+
* @param {string} name
355+
* @returns {never}
356+
*/
357+
export function rune_removed(node, name) {
358+
e(node, "rune_removed", `The \`${name}\` rune has been removed`);
359+
}
360+
351361
/**
352362
* `%name%` is now `%replacement%`
353363
* @param {null | number | NodeLike} node

0 commit comments

Comments
 (0)