Skip to content

Commit da6c72d

Browse files
Example using Svelte 5 runes for state management (#21)
Co-authored-by: willow <[email protected]>
1 parent 21b27f4 commit da6c72d

25 files changed

+516
-12
lines changed

examples/svelte-state-rune/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
name: Svelte State Rune
3+
description: A uses a state rune with WXT's storage to enable clean subscriptions in Svelte (and TS) as well as persisting state.
4+
---
5+
6+
```sh
7+
npm install
8+
npm run dev
9+
```
10+
11+
Demonstrates how the browser.storage API allows different parts of the extension share state and reflect activity on the current page.
12+
13+
- The page fires a "counter:updated" event every second.
14+
- The Content Script handles these events by pushing the event payload into session storage.
15+
- The CounterState class watches the store and updates its reactive state property on change
16+
- App.svelte reflects the value of `counterState.state`
17+
18+
Open dev tools or the extension service worker logs, click the extension's action icon and watch the events being fired by the active page update the counter.
19+
20+
When closing and re-opening the Popup, the state is persisted.

examples/svelte-state-rune/index.html

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Document</title>
7+
</head>
8+
<body>
9+
<h1>Emit events</h1>
10+
11+
<script>
12+
let counter = 0;
13+
setInterval(() => {
14+
document.dispatchEvent(
15+
new CustomEvent("counter:updated", {
16+
detail: {
17+
counter: counter++,
18+
},
19+
})
20+
);
21+
}, 1000);
22+
</script>
23+
</body>
24+
</html>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "svelte-state-rune",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "wxt",
8+
"dev:firefox": "wxt -b firefox",
9+
"build": "wxt build",
10+
"build:firefox": "wxt build -b firefox",
11+
"zip": "wxt zip",
12+
"zip:firefox": "wxt zip -b firefox",
13+
"check": "svelte-check --tsconfig ./tsconfig.json",
14+
"postinstall": "wxt prepare"
15+
},
16+
"devDependencies": {
17+
"@tsconfig/svelte": "^5.0.4",
18+
"@wxt-dev/module-svelte": "^2.0.3",
19+
"svelte": "^5.28.2",
20+
"svelte-check": "^4.1.6",
21+
"tslib": "^2.7.0",
22+
"typescript": "^5.8.2",
23+
"vite": "6.3.3",
24+
"wxt": "^0.20.5"
25+
}
26+
}
Loading
559 Bytes
Loading
916 Bytes
Loading
1.3 KB
Loading
2.31 KB
Loading
Lines changed: 15 additions & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default defineBackground(() => {
2+
// Set the access level so `browser.storage.session` is defined and availble
3+
// in content scripts: https://developer.chrome.com/docs/extensions/reference/api/storage#storage_areas
4+
void browser.storage.session.setAccessLevel?.({
5+
accessLevel: "TRUSTED_AND_UNTRUSTED_CONTEXTS",
6+
});
7+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { counterStore } from "@/lib/counter-store";
2+
3+
export default defineContentScript({
4+
matches: ["http://localhost/*"],
5+
6+
main() {
7+
document.addEventListener("counter:updated", (event) => {
8+
void counterStore.setValue(event.detail);
9+
});
10+
},
11+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script lang="ts">
2+
import svelteLogo from "../../assets/svelte.svg";
3+
import { counterState } from "@/lib/counter-state.svelte";
4+
</script>
5+
6+
<main class="app">
7+
<div>
8+
<a href="https://wxt.dev" target="_blank" rel="noreferrer">
9+
<img src="/wxt.svg" class="logo" alt="WXT Logo" />
10+
</a>
11+
<a href="https://svelte.dev" target="_blank" rel="noreferrer">
12+
<img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
13+
</a>
14+
</div>
15+
<h1>WXT + Svelte</h1>
16+
17+
<div class="card">
18+
<p>{counterState.state.counter}</p>
19+
</div>
20+
21+
<p class="read-the-docs">Click on the WXT and Svelte logos to learn more</p>
22+
</main>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
:root {
2+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3+
line-height: 1.5;
4+
font-weight: 400;
5+
6+
color-scheme: light dark;
7+
color: rgba(255, 255, 255, 0.87);
8+
background-color: #242424;
9+
10+
font-synthesis: none;
11+
text-rendering: optimizeLegibility;
12+
-webkit-font-smoothing: antialiased;
13+
-moz-osx-font-smoothing: grayscale;
14+
-webkit-text-size-adjust: 100%;
15+
}
16+
17+
body {
18+
margin: 0;
19+
display: flex;
20+
place-items: center;
21+
min-width: 320px;
22+
min-height: 100vh;
23+
}
24+
25+
.app {
26+
max-width: 1280px;
27+
margin: 0 auto;
28+
padding: 2rem;
29+
text-align: center;
30+
}
31+
32+
.logo {
33+
height: 6em;
34+
padding: 1.5em;
35+
will-change: filter;
36+
transition: filter 300ms;
37+
38+
&:hover {
39+
filter: drop-shadow(0 0 2em #54bc4ae0);
40+
}
41+
42+
&.svelte:hover {
43+
filter: drop-shadow(0 0 2em #ff3e00aa);
44+
}
45+
}
46+
47+
.read-the-docs {
48+
color: #888;
49+
}
50+
51+
.card {
52+
font-size: 2rem;
53+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Default Popup Title</title>
7+
<meta name="manifest.type" content="browser_action" />
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
<script type="module" src="./main.ts"></script>
12+
</body>
13+
</html>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { mount } from "svelte";
2+
3+
import "./app.css";
4+
import App from "./App.svelte";
5+
6+
const app = mount(App, {
7+
target: document.getElementById("app")!,
8+
});
9+
10+
export default app;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { counterStore } from "./counter-store.ts";
2+
3+
class CounterState {
4+
state = $state(counterStore.fallback);
5+
6+
constructor() {
7+
counterStore.getValue().then(this.updateCounter);
8+
counterStore.watch(this.updateCounter);
9+
}
10+
11+
updateCounter = (newState: { counter: number } | null) => {
12+
this.state = newState ?? counterStore.fallback;
13+
};
14+
}
15+
16+
export const counterState = new CounterState();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const counterStore = storage.defineItem<{ counter: number }>(
2+
"session:counter",
3+
{
4+
fallback: { counter: 0 }
5+
}
6+
);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type CounterEvent = CustomEvent<{
2+
counter: number;
3+
}>;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { CounterEvent } from "./example.d.ts";
2+
3+
declare global {
4+
interface DocumentEventMap {
5+
"counter:updated": CounterEvent;
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "./.wxt/tsconfig.json",
3+
"compilerOptions": {
4+
"useDefineForClassFields": true,
5+
"allowImportingTsExtensions": true
6+
}
7+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="svelte" />
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { defineConfig } from "wxt";
2+
3+
// See https://wxt.dev/api/config.html
4+
export default defineConfig({
5+
srcDir: "src",
6+
modules: ["@wxt-dev/module-svelte"],
7+
manifest: {
8+
permissions: ["storage"],
9+
},
10+
webExt: {
11+
startUrls: ["http://localhost:3000"],
12+
},
13+
});

metadata.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,24 @@
297297
"storage"
298298
]
299299
},
300+
{
301+
"name": "Svelte State Rune",
302+
"description": "A uses a state rune with WXT's storage to enable clean subscriptions in Svelte (and TS) as well as persisting state.",
303+
"searchText": "Svelte State Rune|A uses a state rune with WXT's storage to enable clean subscriptions in Svelte (and TS) as well as persisting state.|@wxt-dev/module-svelte|svelte|vite|storage|browser.storage.session.setAccessLevel|browser.storage.session",
304+
"url": "https://github.com/wxt-dev/examples/tree/main/examples/svelte-state-rune",
305+
"apis": [
306+
"browser.storage.session.setAccessLevel",
307+
"browser.storage.session"
308+
],
309+
"packages": [
310+
"@wxt-dev/module-svelte",
311+
"svelte",
312+
"vite"
313+
],
314+
"permissions": [
315+
"storage"
316+
]
317+
},
300318
{
301319
"name": "TailwindCSS",
302320
"description": "Add TailwindCSS to your extension.",

0 commit comments

Comments
 (0)