@macfja/svelte-persistent-runes
Version:
A Svelte reactive rune that keep its value through pages and reloads
302 lines (249 loc) • 10.4 kB
Markdown
# Svelte persistent runes
A Svelte reactive rune that keep its value through pages and reloads





[](https://biomejs.dev)
[ **[D E M O](https://www.sveltelab.dev/5aypljhy6qtirvp)** ]
## Installation
With [NPM](https://www.npmjs.com/package/@macfja/svelte-persistent-runes)
```sh
npm install --save-dev @macfja/svelte-persistent-runes
# or
yarn add --save-dev @macfja/svelte-persistent-runes
# or
pnpm add --save-dev @macfja/svelte-persistent-runes
# or
deno install --dev npm:@macfja/svelte-persistent-runes
```
## Quickstart
Update your `./svelte.config.js` to add a new preprocessor:
```diff
import adapter from '@sveltejs/adapter-auto';
+import persist from "@macfja/svelte-persistent-runes/preprocessor"
const config = {
+ preprocess: [persist()],
kit: {
adapter: adapter()
}
};
export default config;
```
Replace your `$state` with `$persist`:
```diff
<script>
+import "@macfja/svelte-persistent-runes"
-let count = $state(0);
+let count = $persist(0, 'counter');
</script>
```
## Usage
This library have 2 parts:
- A preprocessor to add the `$persist` rune (and optionally a Vite plugin).
- A set of configuration to persist your data.
You MUST add the preprocessor to use `$persist`.
It's as simple as to add it in your Svelte configuration (`svelte.config.js`) with the import of `@macfja/svelte-persistent-runes/plugins`
<details>
<summary>./svelte.config.js</summary>
```js
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { persistPreprocessor } from "@macfja/svelte-persistent-runes/plugins"
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: [vitePreprocess(), persistPreprocessor()],
kit: {
adapter: adapter()
}
};
export default config;
```
</details>
> []
> If you are using `*.svelte.js`/`*.svelte.ts` file you need to also add a Vite plugin:
> <details><summary>./vite.config.ts</summary>
>
> ```ts
> import { sveltekit } from '@sveltejs/kit/vite';
> import { defineConfig } from 'vite';
> import { persistPlugin as persist } from "@macfja/svelte-persistent-runes/plugins";
>
> export default defineConfig({
> plugins: [persist(), sveltekit()]
> });
> ```
>
> </details>
Now that the preprocessor is added, you can use the `$persist` rune instead of the `$state` rune.
<details>
<summary>./src/anywhere/component.svelte</summary>
```html
<script>
import "@macfja/svelte-persistent-runes"
let count = $persist(0, 'counter');
</script>
<div class="counter">
<button onclick={() => (count -= 1)} aria-label="Decrease the counter by one">-</button>
<div><strong>{count}</strong></div>
<button onclick={() => (count += 1)} aria-label="Increase the counter by one">+</button>
</div>
```
</details>
<details>
<summary>./src/anywhere/data.svelte.ts</summary>
```ts
import "@macfja/svelte-persistent-runes"
export class Person {
name = $persist('John', 'user-name')
age = $persist(33, 'user-age')
greet(): string {
return `Hello ${this.name}`;
}
birthday(): string {
this.age += 1;
return `Happy birthday ${this.name}!`
}
}
export const currentUser = new Person()
```
</details>
> []
> You need to import `import "@macfja/svelte-persistent-runes"` to prevent Typescript to complain about the unknown function `$persist`
>
> ---
>
> You can add this import in an ambient Typescript Module (like `./src/app.d.ts` in SvelteKit), and you won't need to import it in every file
### Definition
```ts
type PersistentRunesOptions = {
/**
* Convert the source data into its string representation
* @param input The source data
* @return The string representation of data
*/
serialize<T>(input: T): string;
/**
* Convert back the string representation into the source data
* @param input The string representation of the date
* @return The new data based on its string representation
*/
deserialize<T>(input: string): T;
/**
* Write data into the store
* @param key The storage key to write
* @param value The data to write
*/
storageWrite(key: string, value: string): void;
/**
* Read data from the storage
* @param key The storage key to read
* @returns The data or `undefined` if the data don't exist in the storage
*/
storageRead(key: string): string | undefined;
};
/**
* A reactive state, that can restore its state upon page reload
* @param initial The initial value of the state
* @param key The storage key of the state. Must be unique in your application
* @param options The persistence options (how and where)
*/
declare function $persist<T>(initial: T, key: string, options?: Partial<PersistentRunesOption>)
```
### Options
You can customize how and where the state value is persisted.
The `$persist` runes take a third (and optional) parameter of type `PersistentRunesOption`.
The options consist of 2 main part: the serializer and the storage. It can be defined as a plain object or as the result of the `buildOptions` (impoerted from `@macfja/svelte-persistent-runes/options`)
```ts
/**
* Create a `PersistentRunesOptions` from a serializer and a storage
* @param serializer The serializer to use (if `undefined` then `JsonSerializer` will be used)
* @param storage The storage to use (if `undefined` then `BrowserLocalStorage` will be used)
*/
declare function buildOptions(
serializer: PersistentRunesSerializer | undefined,
storage: PersistentRunesStorage | undefined
): PersistentRunesOptions;
```
#### The serializer
The serializer part of the option are:
- `serialize`: This function is responsible for converting the original type into a string
- `deserialize`: This function is responsible to convert back a string to the original type
The library have several built-in serializer:
- `JsonSerializerFactory`: factory to create a JSON based serializer
- `JsonSerializer`: A basic JSON serializer (no replacer, nor reviver)
- `DevalueSerializerFactory`: factory to create a [Devalue] based serializer
- `DevalueSerializer`: A basic [Devalue] serializer (no reducers, nor revivers)
- `ESSerializerSerializerFactory`: factory to create a [ESSerializer] based serializer
- `ESSerializerSerializer`: A basic [ESSerializer] serializer (no SerializeOptions, nor classes)
- `MacfjaSerializerFactory`: factory to create a [@macfja/serializer] based serializer
- `MacfjaSerializer`: A basic [@macfja/serializer] serializer (no additional classes mapping)
- `SuperJsonSerializer`: A [superjson] serializer
- `NextJsonSerializerFactory`: factory to create a [next-json] based serializer
- `NextJsonSerializerFactory`: A basic [next-json] serializer (no options, nor replacers, nor revivers)
- `PhpSerializeSerializerFactory`: factory to create a [php-serialize] based serializer
- `PhpSerializeSerializer`: A basic [php-serialize] serializer (no options)
- `SerializeAnythingSerializerFactory`: factory to create a [serialize-anything] based serializer
- `SerializeAnythingSerializer`: A basic [serialize-anything] serializer (no options)
[ESSerializer]: https://www.npmjs.com/package/esserializer
[Devalue]: https://www.npmjs.com/package/devalue
[@macfja/serializer]: https://www.npmjs.com/package/@macfja/serializer
[superjson]: https://www.npmjs.com/package/superjson
[next-json]: https://www.npmjs.com/package/next-json
[php-serialize]: https://www.npmjs.com/package/php-serialize
[serialize-anything]: https://www.npmjs.com/package/serialize-anything
#### The storage
The storage part of the option are:
- `storageWrite`: This function is responsible to write data into the storage
- `storageRead`: This function is responsible to read data from the storage
The library have several built-in storage:
- `BrowserCookieStorageFactory`: factory to create a Cookie based storage (DOM API, browser only)
- `BrowserCookieStorage`: A basic Cookie storage (no particular options, except for `samesite: Strict`)
- `BrowserLocalStorage`: a browser localStorage storage ([DOM API](https://developer.mozilla.org/en-US/docs/Web/API/Storage), browser only)
- `BrowserSessionStorage`: a browser sessionStorage storage ([DOM API](https://developer.mozilla.org/en-US/docs/Web/API/Storage), browser only)
- `addEncryptionStorage`: a wrapper function to add AES [GCM encryption] on stored data
[GCM encryption]: https://en.wikipedia.org/wiki/Galois/Counter_Mode
#### Example
<details>
<summary>Browser session storage and @macfja/serializer</summary>
```html
<script>
import "@macfja/svelte-persistent-runes"
import {
buildOptions,
MacfjaSerializer,
BrowserSessionStorage
} "@macfja/svelte-persistent-runes/options"
let count = $persist(0, 'counter', buildOptions(MacfjaSerializer, BrowserSessionStorage));
</script>
<div class="counter">
<button onclick={() => (count -= 1)} aria-label="Decrease the counter by one">-</button>
<div><strong>{count}</strong></div>
<button onclick={() => (count += 1)} aria-label="Increase the counter by one">+</button>
</div>
```
</details>
<details>
<summary>Browser local storage, encrypted and SuperJson </summary>
```html
<script>
import "@macfja/svelte-persistent-runes"
import {
buildOptions,
SuperJsnoSerializer,
BrowserLocalStorage,
addEncryptionStorage
} "@macfja/svelte-persistent-runes/options"
let count = $persist(0, 'counter', buildOptions(
SuperJsnoSerializer,
addEncryptionStorage(BrowserLocalStorage, '12345678901234567890123456879012')
);
</script>
<div class="counter">
<button onclick={() => (count -= 1)} aria-label="Decrease the counter by one">-</button>
<div><strong>{count}</strong></div>
<button onclick={() => (count += 1)} aria-label="Increase the counter by one">+</button>
</div>
```
</details>