UNPKG

@macfja/svelte-persistent-runes

Version:

A Svelte reactive rune that keep its value through pages and reloads

302 lines (249 loc) 10.4 kB
# Svelte persistent runes A Svelte reactive rune that keep its value through pages and reloads ![GitHub Repo stars](https://img.shields.io/github/stars/macfja/svelte-persistent-store?style=social) ![NPM bundle size](https://img.shields.io/bundlephobia/minzip/@macfja/svelte-persistent-store) ![Download per week](https://img.shields.io/npm/dw/@macfja/svelte-persistent-store) ![License](https://img.shields.io/npm/l/@macfja/svelte-persistent-store) ![NPM version](https://img.shields.io/npm/v/@macfja/svelte-persistent-store) [![Checked with Biome](https://img.shields.io/badge/Checked_with-Biome-60a5fa?style=flat&logo=biome)](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> > [!IMPORTANT] > 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> > [!IMPORTANT] > 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>