10 |
11 |
12 |
--------------------------------------------------------------------------------
/projects/demo/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from "@sveltejs/adapter-static";
2 |
3 | /** @type {import('@sveltejs/kit').Config} */
4 | const config = {
5 | kit: {
6 | adapter: adapter({
7 | fallback: "fallback.html",
8 | }),
9 | },
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/projects/demo/src/app.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // See https://kit.svelte.dev/docs/types#the-app-namespace
4 | // for information about these interfaces
5 | declare namespace App {
6 | // interface Locals {}
7 | // interface Platform {}
8 | // interface Session {}
9 | // interface Stuff {}
10 | }
11 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "checkJs": true,
4 | "maxNodeModuleJsDepth": 0,
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "noEmit": true,
8 | "resolveJsonModule": true,
9 | "strict": true,
10 | "target": "es2020",
11 | },
12 | "include": ["./**/*.js", "./**/*.d.ts"],
13 | "exclude": ["node_modules", "./projects/demo/build"]
14 | }
15 |
--------------------------------------------------------------------------------
/projects/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "private": true,
4 | "version": "0.0.1",
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "devDependencies": {
11 | "@babichjacob/svelte-localstorage": "workspace:1.1.2",
12 | "@sveltejs/adapter-static": "next",
13 | "@sveltejs/kit": "next",
14 | "lz-string": "^1.4.4",
15 | "svelte": "^3.53.1",
16 | "vite": "^3.2.4"
17 | },
18 | "type": "module"
19 | }
20 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish to NPM every tag (i.e. new version)
2 |
3 | on:
4 | push:
5 | tags:
6 | - "*"
7 |
8 | jobs:
9 | publish:
10 | strategy:
11 | matrix:
12 | node-version: [22]
13 | os: [ubuntu-latest]
14 | runs-on: ${{ matrix.os }}
15 | timeout-minutes: 15
16 | steps:
17 | - uses: actions/checkout@v4
18 | - uses: actions/setup-node@v4
19 | with:
20 | node-version: ${{ matrix.node-version }}
21 | registry-url: https://registry.npmjs.org/
22 | - uses: pnpm/action-setup@v4.0.0
23 | with:
24 | version: 9.x
25 | - run: pnpm install
26 | - run: pnpm check
27 | - run: pnpm -r publish --access public --no-git-checks
28 | env:
29 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@babichjacob/svelte-localstorage-workspace-root",
3 | "private": true,
4 | "version": "1.0.1",
5 | "description": "Svelte writable stores that automatically synchronize with localStorage",
6 | "author": "J / Jacob Babich ",
7 | "keywords": [
8 | "svelte",
9 | "sveltekit",
10 | "svelte-kit",
11 | "sapper",
12 | "localstorage",
13 | "local-storage"
14 | ],
15 | "license": "MIT",
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/babichjacob/svelte-localstorage.git"
19 | },
20 | "bugs": {
21 | "url": "https://github.com/babichjacob/svelte-localstorage/issues"
22 | },
23 | "homepage": "https://github.com/babichjacob/svelte-localstorage",
24 | "scripts": {
25 | "check": "tsc --project jsconfig.json"
26 | },
27 | "devDependencies": {
28 | "typescript": "^4.9.3"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/projects/svelte-localstorage/server.js:
--------------------------------------------------------------------------------
1 | import { writable } from "svelte/store";
2 |
3 | /**
4 | * @template Item
5 | * @param {string} key What key in localStorage to synchronize with
6 | * @param {Item} initial The initial value of the writable store
7 | * @param {object} param2 How to serialize and deserialize the Item
8 | * @param {function(Item): string} [param2.serialize] How to create a string representation of the Item to store in localStorage. You can also implement compression here.
9 | * @param {function(string): Item} [param2.deserialize] How to convert the string representation in localStorage to an Item. You can also implement decompression here.
10 | * @returns {import("svelte/store").Writable} A writable store that synchronizes with localStorage
11 | */
12 | export const localStorageWritable = (key, initial, param2 = {}) => {
13 | const { set, subscribe, update } = writable(initial);
14 |
15 | return { set, subscribe, update };
16 | };
17 |
--------------------------------------------------------------------------------
/projects/svelte-localstorage/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@babichjacob/svelte-localstorage",
3 | "version": "1.1.2",
4 | "description": "Svelte writable stores that automatically synchronize with localStorage",
5 | "author": "J / Jacob Babich ",
6 | "keywords": [
7 | "svelte",
8 | "localstorage",
9 | "local-storage",
10 | "svelte-kit",
11 | "sveltekit",
12 | "sapper"
13 | ],
14 | "license": "MIT",
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/babichjacob/svelte-localstorage.git"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/babichjacob/svelte-localstorage/issues"
21 | },
22 | "homepage": "https://github.com/babichjacob/svelte-localstorage",
23 | "exports": {
24 | ".": {
25 | "browser": "./browser.js",
26 | "node": "./server.js",
27 | "default": "./server.js"
28 | }
29 | },
30 | "type": "module",
31 | "dependencies": {
32 | "svelte": "^3.53.1 || ^4.2"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Jacob Babich
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/projects/demo/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
32 |
33 |
34 |
35 |
43 |
44 |
52 |
53 | ({$point.x}, {$point.y})
54 |
55 |
56 |
--------------------------------------------------------------------------------
/projects/svelte-localstorage/browser.js:
--------------------------------------------------------------------------------
1 | import { writable } from "svelte/store";
2 |
3 | /**
4 | * @template Item
5 | * @param {string} key What key in localStorage to synchronize with
6 | * @param {Item} initial The initial value of the writable store
7 | * @param {object} param2 How to serialize and deserialize the Item
8 | * @param {function(Item): string} [param2.serialize] How to create a string representation of the Item to store in localStorage. You can also implement compression here.
9 | * @param {function(string): Item} [param2.deserialize] How to convert the string representation in localStorage to an Item. You can also implement decompression here.
10 | * @returns {import("svelte/store").Writable} A writable store that synchronizes with localStorage
11 | */
12 | export const localStorageWritable = (
13 | key,
14 | initial,
15 | { serialize = JSON.stringify, deserialize = JSON.parse } = {}
16 | ) => {
17 | let currentValue = initial;
18 |
19 | /**
20 | * @param {import("svelte/store").Writable["set"]} setStore
21 | * @param {Item} value
22 | */
23 | const syncCurrentValue = (setStore, value) => {
24 | setStore(value);
25 | currentValue = value;
26 | };
27 |
28 | /** @param {string | null} localValue */
29 | const parseFromLocalStorage = (localValue) => {
30 | if (localValue === null) return initial;
31 |
32 | try {
33 | return deserialize(localValue);
34 | } catch (error) {
35 | console.error(
36 | `localStorage's value for \`${key}\` (\`${localValue}\`) could not be deserialized with ${deserialize} because of ${error}, so the initial value \`${initial}\` will be used instead`
37 | );
38 | return initial;
39 | }
40 | };
41 |
42 | const { set: setStore, subscribe } = writable(initial, (setStore) => {
43 | /** @type {string | null} */
44 | let localStorageValue = null;
45 | try {
46 | localStorageValue = localStorage.getItem(key);
47 | } catch (error) {
48 | console.error(
49 | `the \`${key}\` store's value could not be restored from localStorage because of ${error}, so the initial value \`${initial}\` will be used instead`
50 | );
51 | }
52 |
53 | syncCurrentValue(setStore, parseFromLocalStorage(localStorageValue));
54 |
55 | /** @param {StorageEvent} event */
56 | const setFromStorageEvents = (event) => {
57 | if (event.key === key)
58 | syncCurrentValue(setStore, parseFromLocalStorage(event.newValue));
59 | };
60 | window.addEventListener("storage", setFromStorageEvents);
61 | return () => window.removeEventListener("storage", setFromStorageEvents);
62 | });
63 |
64 | // Set both localStorage and this Svelte store
65 | /** @type {import("svelte/store").Writable["set"]} */
66 | const set = (value) => {
67 | syncCurrentValue(setStore, value);
68 |
69 | try {
70 | const serialized = serialize(value);
71 |
72 | try {
73 | localStorage.setItem(key, serialized);
74 | } catch (error) {
75 | console.error(
76 | `the \`${key}\` store's new value \`${value}\` (which serialized to \`${serialized}\`) could not be persisted to localStorage because of ${error}`
77 | );
78 | }
79 | } catch (error) {
80 | console.error(
81 | `the \`${key}\` store was set to \`${value}\`, but this could not be serialized with ${serialize} because of ${error}, so it won't be persisted to localStorage`
82 | );
83 | }
84 | };
85 |
86 | /** @type {import("svelte/store").Writable["update"]} */
87 | const update = (fn) => {
88 | set(fn(currentValue));
89 | };
90 |
91 | return { set, subscribe, update };
92 | };
93 |
--------------------------------------------------------------------------------
/projects/svelte-localstorage/README.md:
--------------------------------------------------------------------------------
1 |
🗄️ Svelte localStorage
2 |
3 | This library for Svelte provides writable stores that automatically synchronize with `localStorage`.
4 |
5 | It has been tested to work with Vite, with or without SvelteKit. It may also work with any other bundler that respects [`exports` maps](https://nodejs.org/api/packages.html#package-entry-points).
6 |
7 | ## 💻 Installation
8 |
9 | ```sh
10 | npm install --save-dev @babichjacob/svelte-localstorage
11 | ```
12 |
13 | ### ⌨️ TypeScript
14 |
15 | This package uses JSDoc for types and documentation, so an extra step is needed to use it in TypeScript projects [for now](https://github.com/babichjacob/svelte-localstorage/issues/22). Configure your `tsconfig.json` so that it has `compilerOptions.maxNodeModuleJsDepth` set to at least 1:
16 |
17 | ```jsonc
18 | // tsconfig.json
19 | {
20 | // When using SvelteKit: "extends": "./.svelte-kit/tsconfig.json",
21 | "compilerOptions": {
22 | // Other options...
23 | "maxNodeModuleJsDepth": 1
24 | }
25 | }
26 | ```
27 |
28 | ## 🛠 Usage
29 |
30 | Import and use the writable store creator from `@babichjacob/svelte-localstorage`:
31 |
32 | ```svelte
33 |
37 |
38 |
39 | ```
40 |
41 | You can create stores with `localStorageWritable` and read from them without having to check whether you're in the browser or on the server. You generally should only write while in the browser.
42 |
43 | ### ⚙️ Options
44 |
45 | - `key`: what key in `localStorage` to synchronize with
46 | - `initial`: the initial value of the writable store
47 | - `serde` (optional): how to serialize and deserialize the store value
48 | - `serialize` (default `JSON.stringify`): how to create a string representation of the store value to put in `localStorage`
49 | - `deserialize` (default `JSON.parse`): how to convert the string representation in `localStorage` to a value to put in the store
50 |
51 | ### 💱 Serialization and deserialization
52 |
53 | Only strings can be put in `localStorage`, so whatever values you want this store to have must be representable as strings somehow. JSON is the default format used, since it supports common types. You can pass a custom `serialize` and `deserialize` function for objects that `JSON.stringify` and `JSON.parse` can't handle, like custom `class`es:
54 |
55 | ```js
56 | import { localStorageWritable } from "@babichjacob/svelte-localstorage";
57 |
58 | class Point {
59 | constructor(x, y) {
60 | this.x = x;
61 | this.y = y;
62 | }
63 | }
64 |
65 | const point = localStorageWritable("point", new Point(0, 0), {
66 | // You can still use JSON.stringify and JSON.parse to help, if you want
67 | serialize: (pnt) => JSON.stringify([pnt.x, pnt.y]),
68 | deserialize(str) {
69 | const [x, y] = JSON.parse(str);
70 | return new Point(x, y);
71 | },
72 | });
73 | ```
74 |
75 | ### 🗜️ Compression and decompression
76 |
77 | You can further utilize `serialize` and `deserialize` to store the data compressed in `localStorage`, perhaps to stay under [the 5 MB limit](https://storage.spec.whatwg.org/#storage-endpoint-quota) your website / app has available.
78 |
79 | Any compression algorithm can be used, but [`lz-string`](https://www.npmjs.com/package/lz-string) is chosen for example:
80 |
81 | ```js
82 |
100 |
101 |