Skip to content

Commit 47f220a

Browse files
committed
Add useStore integration for Svelte 5 runes
0 parents  commit 47f220a

21 files changed

Lines changed: 2542 additions & 0 deletions

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 2
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true

.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github: ai

.github/workflows/release.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Release
2+
on:
3+
push:
4+
tags:
5+
- '*'
6+
jobs:
7+
publish:
8+
runs-on: ubuntu-latest
9+
permissions:
10+
contents: read
11+
id-token: write
12+
steps:
13+
- name: Checkout the repository
14+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
15+
- name: Clean npm package
16+
uses: ai/clean-npm-project@0e1fd6e68c39b972235226864d47e368c550ebed # v0.2.3
17+
with:
18+
clean-docs: true
19+
- name: Install Node.js
20+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
21+
with:
22+
node-version: 26
23+
- name: Publish npm package
24+
run: npm stage publish
25+
working-directory: cleaned-project/
26+
release:
27+
runs-on: ubuntu-latest
28+
permissions:
29+
contents: write
30+
steps:
31+
- name: Checkout the repository
32+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
33+
- name: Copy changelog to release
34+
uses: ai/copy-changelog-to-release@a6dc825c34575add2da2060796794f7b84894628 # v0.2.0
35+
env:
36+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/test.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Test
2+
on:
3+
push:
4+
branches:
5+
- main
6+
- next
7+
pull_request:
8+
permissions:
9+
contents: read
10+
jobs:
11+
full:
12+
name: Node.js Latest Full
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout the repository
16+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
17+
- name: Install pnpm
18+
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
19+
with:
20+
version: 11
21+
- name: Install Node.js
22+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
23+
with:
24+
node-version: 25
25+
cache: pnpm
26+
- name: Install dependencies
27+
run: pnpm install --ignore-scripts
28+
- name: Run tests
29+
run: pnpm test
30+
short:
31+
runs-on: ubuntu-latest
32+
strategy:
33+
matrix:
34+
node-version:
35+
- 24
36+
- 22
37+
- 20
38+
name: Node.js ${{ matrix.node-version }} Quick
39+
steps:
40+
- name: Checkout the repository
41+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
42+
- name: Install pnpm
43+
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
44+
with:
45+
version: 11
46+
- name: Install Node.js ${{ matrix.node-version }}
47+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
48+
with:
49+
node-version: ${{ matrix.node-version }}
50+
cache: pnpm
51+
- name: Install dependencies
52+
run: pnpm install --ignore-scripts
53+
- name: Run unit tests
54+
run: pnpm vitest run

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
3+
coverage/

.npmignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
coverage/
2+
test/
3+
.github/
4+
oxlint.config.ts
5+
oxfmt.config.ts
6+
vitest.config.ts
7+
svelte.config.js
8+
tsconfig.json

.prettierrc.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import loguxOxfmtConfig from '@logux/oxc-configs/fmt'
2+
3+
export default loguxOxfmtConfig

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Change Log
2+
3+
This project adheres to [Semantic Versioning](http://semver.org/).
4+
5+
## 1.0.0
6+
7+
- Initial release with Svelte 5 runes support via `useStore`.

LICENSE

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
The MIT License (MIT)
2+
3+
Copyright 2020 Andrey Sitnik <andrey@sitnik.es>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
this software and associated documentation files (the "Software"), to deal in
7+
the Software without restriction, including without limitation the rights to
8+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9+
the Software, and to permit persons to whom the Software is furnished to do so,
10+
subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Nano Stores Svelte
2+
3+
<img align="right" width="92" height="92" title="Nano Stores logo"
4+
src="https://nanostores.github.io/nanostores/logo.svg">
5+
6+
Svelte 5 ([runes](https://svelte.dev/docs/svelte/what-are-runes)) integration
7+
for **[Nano Stores]**, a tiny state manager with many atomic tree-shakable
8+
stores.
9+
10+
- **Small.** Less than 1 KB. Zero dependencies.
11+
- **Fast.** With small atomic and derived stores, you do not need to update
12+
every component on every store change.
13+
- **Tree Shakable.** The chunk contains only stores used by components
14+
in the chunk.
15+
- Built on [`createSubscriber`], so it shares one subscription per store
16+
between all readers and stays correct under SSR.
17+
- Was designed to move logic from components to stores.
18+
- It has good **TypeScript** support.
19+
20+
```svelte
21+
<script>
22+
import { useStore } from '@nanostores/svelte'
23+
24+
import { profile } from '../stores/profile.js'
25+
26+
let user = useStore(profile)
27+
</script>
28+
29+
<header>Hi, {user.current.name}</header>
30+
```
31+
32+
[Nano Stores]: https://github.com/nanostores/nanostores/
33+
[`createSubscriber`]: https://svelte.dev/docs/svelte/svelte-reactivity#createSubscriber
34+
35+
---
36+
37+
<img src="https://cdn.evilmartians.com/badges/logo-no-label.svg" alt="" width="22" height="16" /> Made at <b><a href="https://evilmartians.com/devtools?utm_source=nanostores-svelte&utm_campaign=devtools-button&utm_medium=github">Evil Martians</a></b>, product consulting for <b>developer tools</b>.
38+
39+
---
40+
41+
42+
## Install
43+
44+
```sh
45+
npm install nanostores @nanostores/svelte
46+
```
47+
48+
Requires Svelte `>=5.7.0` (when [`createSubscriber`] was added).
49+
50+
51+
## Usage
52+
53+
`useStore(store)` returns a reactive holder. Read `.current` to get the store’s
54+
value. As long as you read `.current` inside a reactive context — markup,
55+
`$derived`, or `$effect` — the component updates whenever the store changes.
56+
57+
```svelte
58+
<script>
59+
import { useStore } from '@nanostores/svelte'
60+
61+
import { router } from '../stores/router.js'
62+
63+
let page = useStore(router)
64+
</script>
65+
66+
{#if page.current.route === 'home'}
67+
<HomePage />
68+
{:else}
69+
<Error404 />
70+
{/if}
71+
```
72+
73+
Because `.current` is read every time, derive from it the same way you would
74+
from any other rune:
75+
76+
```svelte
77+
<script>
78+
import { useStore } from '@nanostores/svelte'
79+
80+
import { profile } from '../stores/profile.js'
81+
82+
let user = useStore(profile)
83+
let name = $derived(user.current.name)
84+
</script>
85+
```
86+
87+
The store is only kept mounted while at least one component reads its `.current`
88+
in an effect, and it unmounts (after Nano Stores’ unmount delay) once the last
89+
reader is gone.
90+
91+
92+
### Why not Svelte’s store contract?
93+
94+
Every Nano Store implements [Svelte’s store contract], so historically you could
95+
use the `$store` auto-subscription directly. As Svelte moves from stores to
96+
runes, `useStore` is the runes-native replacement: it has no dependency on the
97+
store contract and plugs straight into Svelte’s signal graph.
98+
99+
[Svelte’s store contract]: https://svelte.dev/docs/svelte/stores
100+
101+
102+
## Store Naming
103+
104+
Nano Stores conventionally prefixes store names with `$` (`$profile`). Svelte
105+
**reserves the `$` prefix** for runes, so a `$`-prefixed identifier cannot be
106+
imported or declared inside `.svelte`, `.svelte.js`, or `.svelte.ts` files.
107+
108+
Either drop the prefix for stores used from Svelte:
109+
110+
```js
111+
// stores/profile.js
112+
import { atom } from 'nanostores'
113+
114+
export let profile = atom({ name: 'Anna' })
115+
```
116+
117+
…or alias the prefix away on import:
118+
119+
```svelte
120+
<script>
121+
import { useStore } from '@nanostores/svelte'
122+
123+
import { $profile as profile } from '../stores/profile.js'
124+
125+
let user = useStore(profile)
126+
</script>
127+
```
128+
129+
130+
## Options
131+
132+
### Keys
133+
134+
For [`map`](https://github.com/nanostores/nanostores#maps) and
135+
[`deepMap`](https://github.com/nanostores/nanostores#deep-maps) stores, use the
136+
`keys` option to re-evaluate only when specific keys change:
137+
138+
```svelte
139+
<script>
140+
import { useStore } from '@nanostores/svelte'
141+
142+
import { settings } from '../stores/settings.js'
143+
144+
let theme = useStore(settings, { keys: ['theme'] })
145+
</script>
146+
147+
<main class={theme.current.theme}>…</main>
148+
```
149+
150+
`.current` always returns the whole store value; the `keys` option only narrows
151+
which changes trigger an update.
152+
153+
154+
## Server-Side Rendering
155+
156+
`useStore` is SSR-safe. When `.current` is read outside of an effect — as it is
157+
during server rendering — it simply returns the current store value without
158+
subscribing, so no listeners leak on the server.

0 commit comments

Comments
 (0)