diff --git a/examples/svelte-state-rune/README.md b/examples/svelte-state-rune/README.md new file mode 100644 index 0000000..3971f13 --- /dev/null +++ b/examples/svelte-state-rune/README.md @@ -0,0 +1,20 @@ +--- +name: Svelte State Rune +description: A uses a state rune with WXT's storage to enable clean subscriptions in Svelte (and TS) as well as persisting state. +--- + +```sh +npm install +npm run dev +``` + +Demonstrates how the browser.storage API allows different parts of the extension share state and reflect activity on the current page. + +- The page fires a "counter:updated" event every second. +- The Content Script handles these events by pushing the event payload into session storage. +- The CounterState class watches the store and updates its reactive state property on change +- App.svelte reflects the value of `counterState.state` + +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. + +When closing and re-opening the Popup, the state is persisted. diff --git a/examples/svelte-state-rune/index.html b/examples/svelte-state-rune/index.html new file mode 100644 index 0000000..790b1fb --- /dev/null +++ b/examples/svelte-state-rune/index.html @@ -0,0 +1,24 @@ + + + + + + Document + + +

Emit events

+ + + + diff --git a/examples/svelte-state-rune/package.json b/examples/svelte-state-rune/package.json new file mode 100644 index 0000000..6dce655 --- /dev/null +++ b/examples/svelte-state-rune/package.json @@ -0,0 +1,26 @@ +{ + "name": "svelte-state-rune", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "wxt", + "dev:firefox": "wxt -b firefox", + "build": "wxt build", + "build:firefox": "wxt build -b firefox", + "zip": "wxt zip", + "zip:firefox": "wxt zip -b firefox", + "check": "svelte-check --tsconfig ./tsconfig.json", + "postinstall": "wxt prepare" + }, + "devDependencies": { + "@tsconfig/svelte": "^5.0.4", + "@wxt-dev/module-svelte": "^2.0.3", + "svelte": "^5.28.2", + "svelte-check": "^4.1.6", + "tslib": "^2.7.0", + "typescript": "^5.8.2", + "vite": "6.3.3", + "wxt": "^0.20.5" + } +} diff --git a/examples/svelte-state-rune/public/icon/128.png b/examples/svelte-state-rune/public/icon/128.png new file mode 100644 index 0000000..9e35d13 Binary files /dev/null and b/examples/svelte-state-rune/public/icon/128.png differ diff --git a/examples/svelte-state-rune/public/icon/16.png b/examples/svelte-state-rune/public/icon/16.png new file mode 100644 index 0000000..cd09f8c Binary files /dev/null and b/examples/svelte-state-rune/public/icon/16.png differ diff --git a/examples/svelte-state-rune/public/icon/32.png b/examples/svelte-state-rune/public/icon/32.png new file mode 100644 index 0000000..f51ce1b Binary files /dev/null and b/examples/svelte-state-rune/public/icon/32.png differ diff --git a/examples/svelte-state-rune/public/icon/48.png b/examples/svelte-state-rune/public/icon/48.png new file mode 100644 index 0000000..cb7a449 Binary files /dev/null and b/examples/svelte-state-rune/public/icon/48.png differ diff --git a/examples/svelte-state-rune/public/icon/96.png b/examples/svelte-state-rune/public/icon/96.png new file mode 100644 index 0000000..c28ad52 Binary files /dev/null and b/examples/svelte-state-rune/public/icon/96.png differ diff --git a/examples/svelte-state-rune/public/wxt.svg b/examples/svelte-state-rune/public/wxt.svg new file mode 100644 index 0000000..0e76320 --- /dev/null +++ b/examples/svelte-state-rune/public/wxt.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/svelte-state-rune/src/assets/svelte.svg b/examples/svelte-state-rune/src/assets/svelte.svg new file mode 100644 index 0000000..8c056ce --- /dev/null +++ b/examples/svelte-state-rune/src/assets/svelte.svg @@ -0,0 +1 @@ + diff --git a/examples/svelte-state-rune/src/entrypoints/background.ts b/examples/svelte-state-rune/src/entrypoints/background.ts new file mode 100644 index 0000000..2b548bc --- /dev/null +++ b/examples/svelte-state-rune/src/entrypoints/background.ts @@ -0,0 +1,7 @@ +export default defineBackground(() => { + // Set the access level so `browser.storage.session` is defined and availble + // in content scripts: https://developer.chrome.com/docs/extensions/reference/api/storage#storage_areas + void browser.storage.session.setAccessLevel?.({ + accessLevel: "TRUSTED_AND_UNTRUSTED_CONTEXTS", + }); +}); diff --git a/examples/svelte-state-rune/src/entrypoints/content.ts b/examples/svelte-state-rune/src/entrypoints/content.ts new file mode 100644 index 0000000..6809f7b --- /dev/null +++ b/examples/svelte-state-rune/src/entrypoints/content.ts @@ -0,0 +1,11 @@ +import { counterStore } from "@/lib/counter-store"; + +export default defineContentScript({ + matches: ["http://localhost/*"], + + main() { + document.addEventListener("counter:updated", (event) => { + void counterStore.setValue(event.detail); + }); + }, +}); diff --git a/examples/svelte-state-rune/src/entrypoints/popup/App.svelte b/examples/svelte-state-rune/src/entrypoints/popup/App.svelte new file mode 100644 index 0000000..8fecc4c --- /dev/null +++ b/examples/svelte-state-rune/src/entrypoints/popup/App.svelte @@ -0,0 +1,22 @@ + + +
+
+ + + + + + +
+

WXT + Svelte

+ +
+

{counterState.state.counter}

+
+ +

Click on the WXT and Svelte logos to learn more

+
diff --git a/examples/svelte-state-rune/src/entrypoints/popup/app.css b/examples/svelte-state-rune/src/entrypoints/popup/app.css new file mode 100644 index 0000000..94b9a0a --- /dev/null +++ b/examples/svelte-state-rune/src/entrypoints/popup/app.css @@ -0,0 +1,53 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +.app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; + + &:hover { + filter: drop-shadow(0 0 2em #54bc4ae0); + } + + &.svelte:hover { + filter: drop-shadow(0 0 2em #ff3e00aa); + } +} + +.read-the-docs { + color: #888; +} + +.card { + font-size: 2rem; +} diff --git a/examples/svelte-state-rune/src/entrypoints/popup/index.html b/examples/svelte-state-rune/src/entrypoints/popup/index.html new file mode 100644 index 0000000..5a2184e --- /dev/null +++ b/examples/svelte-state-rune/src/entrypoints/popup/index.html @@ -0,0 +1,13 @@ + + + + + + Default Popup Title + + + +
+ + + diff --git a/examples/svelte-state-rune/src/entrypoints/popup/main.ts b/examples/svelte-state-rune/src/entrypoints/popup/main.ts new file mode 100644 index 0000000..356e261 --- /dev/null +++ b/examples/svelte-state-rune/src/entrypoints/popup/main.ts @@ -0,0 +1,10 @@ +import { mount } from "svelte"; + +import "./app.css"; +import App from "./App.svelte"; + +const app = mount(App, { + target: document.getElementById("app")!, +}); + +export default app; diff --git a/examples/svelte-state-rune/src/lib/counter-state.svelte.ts b/examples/svelte-state-rune/src/lib/counter-state.svelte.ts new file mode 100644 index 0000000..c28234d --- /dev/null +++ b/examples/svelte-state-rune/src/lib/counter-state.svelte.ts @@ -0,0 +1,16 @@ +import { counterStore } from "./counter-store.ts"; + +class CounterState { + state = $state(counterStore.fallback); + + constructor() { + counterStore.getValue().then(this.updateCounter); + counterStore.watch(this.updateCounter); + } + + updateCounter = (newState: { counter: number } | null) => { + this.state = newState ?? counterStore.fallback; + }; +} + +export const counterState = new CounterState(); diff --git a/examples/svelte-state-rune/src/lib/counter-store.ts b/examples/svelte-state-rune/src/lib/counter-store.ts new file mode 100644 index 0000000..4d51383 --- /dev/null +++ b/examples/svelte-state-rune/src/lib/counter-store.ts @@ -0,0 +1,6 @@ +export const counterStore = storage.defineItem<{ counter: number }>( + "session:counter", + { + fallback: { counter: 0 } + } +); diff --git a/examples/svelte-state-rune/src/typings/example.d.ts b/examples/svelte-state-rune/src/typings/example.d.ts new file mode 100644 index 0000000..833ab9e --- /dev/null +++ b/examples/svelte-state-rune/src/typings/example.d.ts @@ -0,0 +1,3 @@ +export type CounterEvent = CustomEvent<{ + counter: number; +}>; diff --git a/examples/svelte-state-rune/src/typings/globals.ts b/examples/svelte-state-rune/src/typings/globals.ts new file mode 100644 index 0000000..b69117f --- /dev/null +++ b/examples/svelte-state-rune/src/typings/globals.ts @@ -0,0 +1,7 @@ +import type { CounterEvent } from "./example.d.ts"; + +declare global { + interface DocumentEventMap { + "counter:updated": CounterEvent; + } +} diff --git a/examples/svelte-state-rune/tsconfig.json b/examples/svelte-state-rune/tsconfig.json new file mode 100644 index 0000000..bbcd150 --- /dev/null +++ b/examples/svelte-state-rune/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "./.wxt/tsconfig.json", + "compilerOptions": { + "useDefineForClassFields": true, + "allowImportingTsExtensions": true + } +} diff --git a/examples/svelte-state-rune/wxt-env.d.ts b/examples/svelte-state-rune/wxt-env.d.ts new file mode 100644 index 0000000..1a25456 --- /dev/null +++ b/examples/svelte-state-rune/wxt-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/svelte-state-rune/wxt.config.ts b/examples/svelte-state-rune/wxt.config.ts new file mode 100644 index 0000000..41355f9 --- /dev/null +++ b/examples/svelte-state-rune/wxt.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "wxt"; + +// See https://wxt.dev/api/config.html +export default defineConfig({ + srcDir: "src", + modules: ["@wxt-dev/module-svelte"], + manifest: { + permissions: ["storage"], + }, + webExt: { + startUrls: ["http://localhost:3000"], + }, +}); diff --git a/metadata.json b/metadata.json index c498a18..634daed 100644 --- a/metadata.json +++ b/metadata.json @@ -297,6 +297,24 @@ "storage" ] }, + { + "name": "Svelte State Rune", + "description": "A uses a state rune with WXT's storage to enable clean subscriptions in Svelte (and TS) as well as persisting state.", + "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", + "url": "https://github.com/wxt-dev/examples/tree/main/examples/svelte-state-rune", + "apis": [ + "browser.storage.session.setAccessLevel", + "browser.storage.session" + ], + "packages": [ + "@wxt-dev/module-svelte", + "svelte", + "vite" + ], + "permissions": [ + "storage" + ] + }, { "name": "TailwindCSS", "description": "Add TailwindCSS to your extension.", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 008b163..38e4560 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2941,6 +2941,14 @@ packages: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} engines: {node: '>=10'} + ini@4.1.1: + resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + ini@4.1.3: + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -3154,6 +3162,10 @@ packages: resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==} engines: {node: '>=18'} + latest-version@9.0.0: + resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==} + engines: {node: '>=18'} + lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -3825,6 +3837,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -8312,6 +8327,8 @@ snapshots: queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + quick-lru@5.1.1: {} range-parser@1.2.1: {}