Skip to content

Commit acc8361

Browse files
committed
init
1 parent 80557bb commit acc8361

File tree

11 files changed

+145
-1
lines changed

11 files changed

+145
-1
lines changed

.changeset/breezy-baboons-exercise.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': minor
3+
---
4+
5+
feat: add `$state.invalidate` rune

documentation/docs/98-reference/.generated/compile-errors.md

+6
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,12 @@ Cannot export state from a module if it is reassigned. Either export a function
828828
`%rune%(...)` can only be used as a variable declaration initializer or a class field
829829
```
830830

831+
### state_invalidate_nonreactive_argument
832+
833+
```
834+
`$state.invalidate` only takes a variable declared with `$state` or `$state.raw` as its argument
835+
```
836+
831837
### store_invalid_scoped_subscription
832838

833839
```

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

+4
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ It's possible to export a snippet from a `<script module>` block, but only if it
220220

221221
> `%rune%(...)` can only be used as a variable declaration initializer or a class field
222222
223+
## state_invalidate_nonreactive_argument
224+
225+
> `$state.invalidate` only takes a variable declared with `$state` or `$state.raw` as its argument
226+
223227
## store_invalid_scoped_subscription
224228

225229
> Cannot subscribe to stores that are not declared at the top level of the component

packages/svelte/src/ambient.d.ts

+24
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,30 @@ declare namespace $state {
9393
: never
9494
: never;
9595

96+
/**
97+
* Forces an update on a `$state` or `$state.raw` variable.
98+
* This is primarily meant as an escape hatch to be able to use external or native classes
99+
* with Svelte's reactivity system.
100+
* If you used Svelte 3 or 4, this is the equivalent of `foo = foo`.
101+
* Example:
102+
* ```svelte
103+
* <script>
104+
* import Counter from 'external-class';
105+
*
106+
* let counter = $state(new Counter());
107+
*
108+
* function increment() {
109+
* counter.increment();
110+
* $state.invalidate(counter);
111+
* }
112+
* </script>
113+
* <button onclick={increment}>
114+
* Count is {counter.count}
115+
* </button>
116+
* ```
117+
*/
118+
export function invalidate(source: unknown): void;
119+
96120
/**
97121
* Declares state that is _not_ made deeply reactive — instead of mutating it,
98122
* you must reassign it.

packages/svelte/src/compiler/errors.js

+9
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,15 @@ export function state_invalid_placement(node, rune) {
480480
e(node, 'state_invalid_placement', `\`${rune}(...)\` can only be used as a variable declaration initializer or a class field\nhttps://svelte.dev/e/state_invalid_placement`);
481481
}
482482

483+
/**
484+
* `$state.invalidate` only takes a variable declared with `$state` or `$state.raw` as its argument
485+
* @param {null | number | NodeLike} node
486+
* @returns {never}
487+
*/
488+
export function state_invalidate_nonreactive_argument(node) {
489+
e(node, 'state_invalidate_nonreactive_argument', `\`$state.invalidate\` only takes a variable declared with \`$state\` or \`$state.raw\` as its argument\nhttps://svelte.dev/e/state_invalidate_nonreactive_argument`);
490+
}
491+
483492
/**
484493
* Cannot subscribe to stores that are not declared at the top level of the component
485494
* @param {null | number | NodeLike} node

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

+18
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,24 @@ export function CallExpression(node, context) {
111111
break;
112112
}
113113

114+
case '$state.invalidate':
115+
if (node.arguments.length !== 1) {
116+
e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
117+
} else {
118+
let arg = node.arguments[0];
119+
if (arg.type !== 'Identifier') {
120+
e.rune_invalid_arguments(node, rune);
121+
}
122+
let binding = context.state.scope.get(arg.name);
123+
if (binding) {
124+
if (binding.kind === 'raw_state' || binding.kind === 'state') {
125+
binding.reassigned = true;
126+
break;
127+
}
128+
}
129+
e.state_invalidate_nonreactive_argument(node);
130+
}
131+
114132
case '$state':
115133
case '$state.raw':
116134
case '$derived':

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

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export function CallExpression(node, context) {
2323
/** @type {Expression} */ (context.visit(node.arguments[0])),
2424
is_ignored(node, 'state_snapshot_uncloneable') && b.true
2525
);
26+
case '$state.invalidate':
27+
return b.call('$.invalidate', node.arguments[0]);
2628

2729
case '$effect.root':
2830
return b.call(

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,15 @@ export {
113113
user_effect,
114114
user_pre_effect
115115
} from './reactivity/effects.js';
116-
export { mutable_source, mutate, set, state, update, update_pre } from './reactivity/sources.js';
116+
export {
117+
invalidate,
118+
mutable_source,
119+
mutate,
120+
set,
121+
state,
122+
update,
123+
update_pre
124+
} from './reactivity/sources.js';
117125
export {
118126
prop,
119127
rest_props,

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

+43
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,49 @@ export function internal_set(source, value) {
211211
return value;
212212
}
213213

214+
/**
215+
* @param {Source} source
216+
*/
217+
export function invalidate(source) {
218+
source.wv = increment_write_version();
219+
220+
mark_reactions(source, DIRTY);
221+
222+
// It's possible that the current reaction might not have up-to-date dependencies
223+
// whilst it's actively running. So in the case of ensuring it registers the reaction
224+
// properly for itself, we need to ensure the current effect actually gets
225+
// scheduled. i.e: `$effect(() => x++)`
226+
if (
227+
is_runes() &&
228+
active_effect !== null &&
229+
(active_effect.f & CLEAN) !== 0 &&
230+
(active_effect.f & (BRANCH_EFFECT | ROOT_EFFECT)) === 0
231+
) {
232+
if (untracked_writes === null) {
233+
set_untracked_writes([source]);
234+
} else {
235+
untracked_writes.push(source);
236+
}
237+
}
238+
239+
if (DEV && inspect_effects.size > 0) {
240+
const inspects = Array.from(inspect_effects);
241+
242+
for (const effect of inspects) {
243+
// Mark clean inspect-effects as maybe dirty and then check their dirtiness
244+
// instead of just updating the effects - this way we avoid overfiring.
245+
if ((effect.f & CLEAN) !== 0) {
246+
set_signal_status(effect, MAYBE_DIRTY);
247+
}
248+
if (check_dirtiness(effect)) {
249+
update_effect(effect);
250+
}
251+
}
252+
253+
inspect_effects.clear();
254+
}
255+
}
256+
214257
/**
215258
* @template {number | bigint} T
216259
* @param {Source<T>} source

packages/svelte/src/utils.js

+1
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ export function is_mathml(name) {
430430

431431
const RUNES = /** @type {const} */ ([
432432
'$state',
433+
'$state.invalidate',
433434
'$state.raw',
434435
'$state.snapshot',
435436
'$props',

packages/svelte/types/index.d.ts

+24
Original file line numberDiff line numberDiff line change
@@ -2760,6 +2760,30 @@ declare namespace $state {
27602760
: never
27612761
: never;
27622762

2763+
/**
2764+
* Forces an update on a `$state` or `$state.raw` variable.
2765+
* This is primarily meant as an escape hatch to be able to use external or native classes
2766+
* with Svelte's reactivity system.
2767+
* If you used Svelte 3 or 4, this is the equivalent of `foo = foo`.
2768+
* Example:
2769+
* ```svelte
2770+
* <script>
2771+
* import Counter from 'external-class';
2772+
*
2773+
* let counter = $state(new Counter());
2774+
*
2775+
* function increment() {
2776+
* counter.increment();
2777+
* $state.invalidate(counter);
2778+
* }
2779+
* </script>
2780+
* <button onclick={increment}>
2781+
* Count is {counter.count}
2782+
* </button>
2783+
* ```
2784+
*/
2785+
export function invalidate(source: unknown): void;
2786+
27632787
/**
27642788
* Declares state that is _not_ made deeply reactive — instead of mutating it,
27652789
* you must reassign it.

0 commit comments

Comments
 (0)