Skip to content

Commit 93110a3

Browse files
authored
docs: explain restriction on exporting reassigned state (#15713)
1 parent 0ff3d74 commit 93110a3

File tree

2 files changed

+81
-1
lines changed

2 files changed

+81
-1
lines changed

documentation/docs/01-introduction/04-svelte-js-files.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: .svelte.js and .svelte.ts files
44

55
Besides `.svelte` files, Svelte also operates on `.svelte.js` and `.svelte.ts` files.
66

7-
These behave like any other `.js` or `.ts` module, except that you can use runes. This is useful for creating reusable reactive logic, or sharing reactive state across your app.
7+
These behave like any other `.js` or `.ts` module, except that you can use runes. This is useful for creating reusable reactive logic, or sharing reactive state across your app (though note that you [cannot export reassigned state]($state#Passing-state-across-modules)).
88

99
> [!LEGACY]
1010
> This is a concept that didn't exist prior to Svelte 5

documentation/docs/02-runes/02-$state.md

+80
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,83 @@ console.log(total.value); // 7
250250
```
251251

252252
...though if you find yourself writing code like that, consider using [classes](#Classes) instead.
253+
254+
## Passing state across modules
255+
256+
You can declare state in `.svelte.js` and `.svelte.ts` files, but you can only _export_ that state if it's not directly reassigned. In other words you can't do this:
257+
258+
```js
259+
/// file: state.svelte.js
260+
export let count = $state(0);
261+
262+
export function increment() {
263+
count += 1;
264+
}
265+
```
266+
267+
That's because every reference to `count` is transformed by the Svelte compiler — the code above is roughly equivalent to this:
268+
269+
```js
270+
/// file: state.svelte.js (compiler output)
271+
// @filename: index.ts
272+
interface Signal<T> {
273+
value: T;
274+
}
275+
276+
interface Svelte {
277+
state<T>(value?: T): Signal<T>;
278+
get<T>(source: Signal<T>): T;
279+
set<T>(source: Signal<T>, value: T): void;
280+
}
281+
declare const $: Svelte;
282+
// ---cut---
283+
export let count = $.state(0);
284+
285+
export function increment() {
286+
$.set(count, $.get(count) + 1);
287+
}
288+
```
289+
290+
> [!NOTE] You can see the code Svelte generates by clicking the 'JS Output' tab in the [playground](/playground).
291+
292+
Since the compiler only operates on one file at a time, if another file imports `count` Svelte doesn't know that it needs to wrap each reference in `$.get` and `$.set`:
293+
294+
```js
295+
// @filename: state.svelte.js
296+
export let count = 0;
297+
298+
// @filename: index.js
299+
// ---cut---
300+
import { count } from './state.svelte.js';
301+
302+
console.log(typeof count); // 'object', not 'number'
303+
```
304+
305+
This leaves you with two options for sharing state between modules — either don't reassign it...
306+
307+
```js
308+
// This is allowed — since we're updating
309+
// `counter.count` rather than `counter`,
310+
// Svelte doesn't wrap it in `$.state`
311+
export const counter = $state({
312+
count: 0
313+
});
314+
315+
export function increment() {
316+
counter.count += 1;
317+
}
318+
```
319+
320+
...or don't directly export it:
321+
322+
```js
323+
let count = $state(0);
324+
325+
export function getCount() {
326+
return count;
327+
}
328+
329+
export function increment() {
330+
count += 1;
331+
}
332+
```

0 commit comments

Comments
 (0)