├── .gitignore ├── .prettierignore ├── README.md ├── examples ├── active-tab-content-script │ ├── README.md │ ├── entrypoints │ │ ├── background.ts │ │ └── content │ │ │ ├── index.ts │ │ │ └── style.css │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ ├── utils │ │ └── matches.ts │ └── wxt.config.ts ├── active-tab-screenshot │ ├── README.md │ ├── entrypoints │ │ └── background.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ ├── utils │ │ └── data-urls.ts │ └── wxt.config.ts ├── background-message-forwarder │ ├── README.md │ ├── entrypoints │ │ ├── background.ts │ │ ├── content.ts │ │ └── popup │ │ │ ├── index.html │ │ │ └── main.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── basic-messaging │ ├── README.md │ ├── entrypoints │ │ ├── background.ts │ │ └── popup │ │ │ ├── index.html │ │ │ └── main.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── browser-action-mount-ui │ ├── README.md │ ├── assets │ │ └── react.svg │ ├── entrypoints │ │ ├── background.ts │ │ └── content │ │ │ ├── App.css │ │ │ ├── App.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ ├── package.json │ ├── public │ │ ├── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ │ └── wxt.svg │ ├── tsconfig.json │ └── wxt.config.ts ├── content-script-session-storage │ ├── README.md │ ├── entrypoints │ │ ├── background.ts │ │ └── content.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ ├── utils │ │ └── storage.ts │ └── wxt.config.ts ├── devtools-extension │ ├── README.md │ ├── entrypoints │ │ ├── devtools-pane │ │ │ ├── index.html │ │ │ └── main.ts │ │ ├── devtools-panel │ │ │ ├── index.html │ │ │ └── main.ts │ │ └── devtools │ │ │ ├── index.html │ │ │ └── main.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── dynamic-content-scripts │ ├── README.md │ ├── entrypoints │ │ ├── background.ts │ │ └── content.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── esm-content-script-ui │ ├── README.md │ ├── entrypoints │ │ └── content │ │ │ ├── esm-index.ts │ │ │ ├── index.ts │ │ │ └── styles.css │ ├── modules │ │ └── esm-builder.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── favicon-tracker │ ├── .gitignore │ ├── README.md │ ├── entrypoints │ │ ├── background.ts │ │ ├── content.ts │ │ └── popup │ │ │ ├── index.html │ │ │ ├── main.ts │ │ │ └── style.css │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ ├── utils │ │ ├── database.ts │ │ ├── favicon-service.ts │ │ └── types.ts │ └── wxt.config.ts ├── get-started-page │ ├── README.md │ ├── entrypoints │ │ ├── background.ts │ │ └── get-started │ │ │ ├── index.html │ │ │ └── main.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── inject-script │ ├── README.md │ ├── entrypoints │ │ ├── content.ts │ │ └── injected.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── offscreen-document-domparser │ ├── README.md │ ├── entrypoints │ │ ├── background.ts │ │ └── offscreen │ │ │ ├── index.html │ │ │ └── offscreen.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ ├── utils │ │ └── constants.ts │ └── wxt.config.ts ├── offscreen-document-setup │ ├── README.md │ ├── entrypoints │ │ ├── background.ts │ │ └── offscreen │ │ │ ├── index.html │ │ │ └── main.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── playwright-e2e-testing │ ├── README.md │ ├── assets │ │ └── typescript.svg │ ├── components │ │ └── counter.ts │ ├── e2e │ │ ├── fixtures.ts │ │ ├── pages │ │ │ └── popup.ts │ │ └── popup-counter.spec.ts │ ├── entrypoints │ │ ├── background.ts │ │ └── popup │ │ │ ├── index.html │ │ │ ├── main.ts │ │ │ └── style.css │ ├── package.json │ ├── playwright.config.ts │ ├── public │ │ ├── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ │ └── wxt.svg │ ├── tsconfig.json │ └── wxt.config.ts ├── react-content-script-ui │ ├── README.md │ ├── entrypoints │ │ └── content │ │ │ ├── App.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── react-mantine │ ├── README.md │ ├── components │ │ └── Counter.tsx │ ├── entrypoints │ │ ├── background.ts │ │ ├── content.tsx │ │ └── popup │ │ │ ├── index.html │ │ │ └── main.tsx │ ├── package.json │ ├── postcss.config.cjs │ ├── public │ │ ├── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ │ └── wxt.svg │ ├── tsconfig.json │ ├── utils │ │ └── theme.ts │ └── wxt.config.ts ├── storybook │ ├── .storybook │ │ ├── main.ts │ │ ├── preview.ts │ │ └── vite.config.ts │ ├── README.md │ ├── components │ │ ├── Button.css │ │ ├── Button.tsx │ │ └── test-Button.stories.ts │ ├── entrypoints │ │ └── content │ │ │ ├── App.tsx │ │ │ ├── index.tsx │ │ │ └── style.css │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── stories │ │ └── .keep │ ├── tsconfig.json │ └── wxt.config.ts ├── svelte-custom-store │ ├── README.md │ ├── package.json │ ├── public │ │ ├── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ │ └── wxt.svg │ ├── src │ │ ├── entrypoints │ │ │ ├── background.ts │ │ │ ├── content.ts │ │ │ └── popup │ │ │ │ ├── App.svelte │ │ │ │ ├── app.css │ │ │ │ ├── index.html │ │ │ │ └── main.ts │ │ └── lib │ │ │ └── store.ts │ ├── tsconfig.json │ ├── wxt-env.d.ts │ └── wxt.config.ts ├── tailwindcss │ ├── README.md │ ├── assets │ │ └── tailwind.css │ ├── entrypoints │ │ ├── content.ts │ │ └── popup.html │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── vanilla-i18n │ ├── README.md │ ├── entrypoints │ │ └── popup │ │ │ ├── index.html │ │ │ └── main.ts │ ├── package.json │ ├── public │ │ ├── _locales │ │ │ └── en │ │ │ │ └── messages.json │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── vitest-unit-testing │ ├── README.md │ ├── entrypoints │ │ ├── __tests__ │ │ │ └── background.test.ts │ │ └── background.ts │ ├── package.json │ ├── tsconfig.json │ └── vitest.config.ts ├── vue-overlay │ ├── README.md │ ├── assets │ │ └── vue.svg │ ├── entrypoints │ │ └── content │ │ │ ├── App.vue │ │ │ ├── index.ts │ │ │ └── reset.css │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── vue-storage-composable │ ├── README.md │ ├── composables │ │ └── useStoredValue.ts │ ├── entrypoints │ │ ├── background.ts │ │ └── popup │ │ │ ├── App.vue │ │ │ ├── index.html │ │ │ └── main.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── web-worker-setup │ ├── README.md │ ├── entrypoints │ │ └── content │ │ │ ├── index.ts │ │ │ └── worker.ts │ ├── package.json │ ├── public │ │ └── icon │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts └── wxt-i18n │ ├── README.md │ ├── entrypoints │ └── popup │ │ ├── index.html │ │ └── main.ts │ ├── locales │ └── en.yml │ ├── package.json │ ├── public │ └── icon │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 32.png │ │ ├── 48.png │ │ └── 96.png │ ├── tsconfig.json │ └── wxt.config.ts ├── metadata.json ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── libs │ ├── babel.ts │ └── loadExtensionApiMap.ts ├── parse-browser-api.ts ├── test │ └── parse-browser-api.test.ts ├── update-metadata-json.ts └── utils │ └── readFiles.ts ├── tsconfig.json └── vitest.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | *storybook.log 10 | 11 | node_modules 12 | .output 13 | stats.html 14 | stats-*.json 15 | .wxt 16 | web-ext.config.ts 17 | playwright-report 18 | test-results 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | .DS_Store 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | metadata.json 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WXT Examples 2 | 3 | Examples are available in the [`examples/` directory](./examples). 4 | 5 | To download and run a specific example locally: 6 | 7 | ```sh 8 | npx giget@latest gh:wxt-dev/examples examples/vue-overlay 9 | cd vue-overlay 10 | ``` 11 | 12 | Then follow the directions in the example's README to run it. 13 | 14 | ## Contributing Examples 15 | 16 | Want to create an example? Please do! Keep in mind that examples should be minimal, well documented, and easy to read. The README should contain basic information about starting the extension, and any addition information about what the example actually does. 17 | 18 | The README needs to contain frontmatter, which is used to generate the `metadata.json` file. This file's raw contents is used by https://wxt.dev to create a [nice example search UI](https://wxt.dev/examples.html). 19 | 20 | Before merging your PR, please update the `metadata.json` file by running the below command: 21 | 22 | ```sh 23 | pnpm -w update-metadata 24 | ``` 25 | 26 | Review that the changes to the file look good, and you're good to merge :+1: 27 | -------------------------------------------------------------------------------- /examples/active-tab-content-script/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Active Tab Content Script 3 | description: Inject a content script only when clicking on the extension action. 4 | apis: 5 | - createShadowRootUi 6 | --- 7 | 8 | ```sh 9 | pnpm i 10 | pnpm dev 11 | ``` 12 | 13 | Visit google.com, click the extension's action icon, and the content script will run. Inspect the service worker to see the return value. 14 | -------------------------------------------------------------------------------- /examples/active-tab-content-script/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | const contentMatch = new MatchPattern(CONTENT_SCRIPT_MATCHES); 2 | 3 | export default defineBackground(() => { 4 | (browser.action ?? browser.browserAction).onClicked.addListener( 5 | async (tab) => { 6 | if (tab.id && tab.url && contentMatch.includes(tab.url)) { 7 | const res = await browser.scripting.executeScript({ 8 | target: { tabId: tab.id }, 9 | files: ["/content-scripts/content.js"], 10 | }); 11 | console.log("result", res); 12 | } 13 | }, 14 | ); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/active-tab-content-script/entrypoints/content/index.ts: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | import { ContentScriptContext } from "#imports"; 3 | 4 | export default defineContentScript({ 5 | // Set "registration" to runtime so this file isn't listed in manifest 6 | registration: "runtime", 7 | // Use an empty array for matches to prevent any host_permissions be added 8 | // when using `registration: "runtime"`. 9 | matches: [], 10 | // Put the CSS in the shadow root 11 | cssInjectionMode: "ui", 12 | 13 | async main(ctx) { 14 | console.log("Content script executed!"); 15 | 16 | const ui = await createUi(ctx); 17 | ui.mount(); 18 | 19 | // Optionally, return a value to the background 20 | return "Hello world!"; 21 | }, 22 | }); 23 | 24 | function createUi(ctx: ContentScriptContext) { 25 | return createShadowRootUi(ctx, { 26 | name: "active-tab-ui", 27 | position: "inline", 28 | append: "before", 29 | onMount(container) { 30 | const app = document.createElement("p"); 31 | app.textContent = "Hello active tab!"; 32 | container.append(app); 33 | }, 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /examples/active-tab-content-script/entrypoints/content/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | color: red; 3 | background-color: black; 4 | } 5 | -------------------------------------------------------------------------------- /examples/active-tab-content-script/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "active-tab-content-script", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "wxt": "^0.20.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/active-tab-content-script/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/active-tab-content-script/public/icon/128.png -------------------------------------------------------------------------------- /examples/active-tab-content-script/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/active-tab-content-script/public/icon/16.png -------------------------------------------------------------------------------- /examples/active-tab-content-script/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/active-tab-content-script/public/icon/32.png -------------------------------------------------------------------------------- /examples/active-tab-content-script/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/active-tab-content-script/public/icon/48.png -------------------------------------------------------------------------------- /examples/active-tab-content-script/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/active-tab-content-script/public/icon/96.png -------------------------------------------------------------------------------- /examples/active-tab-content-script/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/active-tab-content-script/utils/matches.ts: -------------------------------------------------------------------------------- 1 | export const CONTENT_SCRIPT_MATCHES = "*://*.google.com/*"; 2 | -------------------------------------------------------------------------------- /examples/active-tab-content-script/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | import { CONTENT_SCRIPT_MATCHES } from "./utils/matches"; 3 | 4 | // See https://wxt.dev/api/config.html 5 | export default defineConfig({ 6 | manifest: { 7 | permissions: ["activeTab", "scripting"], 8 | action: {}, 9 | web_accessible_resources: [ 10 | // Since the content script isn't listed in the manifest, we have to 11 | // manually allow the CSS file to load. 12 | { 13 | resources: ["/content-scripts/content.css"], 14 | matches: [CONTENT_SCRIPT_MATCHES], 15 | }, 16 | ], 17 | }, 18 | webExt: { 19 | startUrls: ["https://google.com"], 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /examples/active-tab-screenshot/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Active Tab Screenshot 3 | description: Use the activeTab permission to take a screenshot of the current tab 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | 11 | On valid URLs (not chrome:// URLs), click the extension icon to download a screenshot. 12 | -------------------------------------------------------------------------------- /examples/active-tab-screenshot/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | // See https://developer.chrome.com/docs/extensions/develop/concepts/activeTab#invoking-activeTab 3 | (browser.action ?? browser.browserAction).onClicked.addListener( 4 | async (tab) => { 5 | try { 6 | const dataUrl = await browser.tabs.captureVisibleTab(); 7 | await downloadImage(dataUrl); 8 | } catch (err) { 9 | console.error("Cannot capture screenshot of current tab", tab, err); 10 | } 11 | }, 12 | ); 13 | }); 14 | 15 | async function downloadImage(dataUrl: string): Promise { 16 | const filename = `Screenshot-${new Date().toISOString().replaceAll(":", "-")}.png`; 17 | console.log(`Downloading image: ${filename}`, { dataUrl }); 18 | 19 | if (import.meta.env.MANIFEST_VERSION === 3) { 20 | // There are known issues with download images in background scripts: https://issues.chromium.org/issues/40774955 21 | // But this works well enough for small screenshots 22 | await browser.downloads.download({ 23 | url: dataUrl, 24 | filename, 25 | }); 26 | } else { 27 | // Use "createObjectURL" for MV2 28 | const blob = dataUrltoBlob(dataUrl); 29 | const objectUrl = URL.createObjectURL(blob); 30 | await browser.downloads.download({ 31 | url: objectUrl, 32 | filename, 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/active-tab-screenshot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "active-tab-screenshot", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "wxt": "^0.20.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/active-tab-screenshot/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/active-tab-screenshot/public/icon/128.png -------------------------------------------------------------------------------- /examples/active-tab-screenshot/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/active-tab-screenshot/public/icon/16.png -------------------------------------------------------------------------------- /examples/active-tab-screenshot/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/active-tab-screenshot/public/icon/32.png -------------------------------------------------------------------------------- /examples/active-tab-screenshot/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/active-tab-screenshot/public/icon/48.png -------------------------------------------------------------------------------- /examples/active-tab-screenshot/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/active-tab-screenshot/public/icon/96.png -------------------------------------------------------------------------------- /examples/active-tab-screenshot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/active-tab-screenshot/utils/data-urls.ts: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/12168909/blob-from-dataurl 2 | export function dataUrltoBlob(dataUrl: string): Blob { 3 | // convert base64 to raw binary data held in a string 4 | // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this 5 | var byteString = atob(dataUrl.split(",")[1]); 6 | 7 | // separate out the mime component 8 | var mimeString = dataUrl.split(",")[0].split(":")[1].split(";")[0]; 9 | 10 | // write the bytes of the string to an ArrayBuffer 11 | var ab = new ArrayBuffer(byteString.length); 12 | 13 | // create a view into the buffer 14 | var ia = new Uint8Array(ab); 15 | 16 | // set the bytes of the buffer to the correct values 17 | for (var i = 0; i < byteString.length; i++) { 18 | ia[i] = byteString.charCodeAt(i); 19 | } 20 | 21 | // write the ArrayBuffer to a blob, and you're done 22 | var blob = new Blob([ab], { type: mimeString }); 23 | return blob; 24 | } 25 | -------------------------------------------------------------------------------- /examples/active-tab-screenshot/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | manifest: { 6 | permissions: ["activeTab", "downloads"], 7 | action: {}, 8 | }, 9 | webExt: { 10 | startUrls: ["https://wxt.dev"], 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /examples/background-message-forwarder/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Background Message Forwarder 3 | description: Forward messages from popup to active content scripts. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | 11 | In the popup, click "Send Message" and you'll get a response from each tab open to a website. 12 | -------------------------------------------------------------------------------- /examples/background-message-forwarder/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | browser.runtime.onMessage.addListener((message, _, sendResponse) => { 3 | // Grab tabs matching content scripts 4 | browser.tabs.query({}).then(async (allTabs) => { 5 | const contentScriptMatches = new MatchPattern("*://*/*"); 6 | const contentScriptTabs = allTabs.filter( 7 | (tab) => 8 | tab.id != null && 9 | tab.url != null && 10 | contentScriptMatches.includes(tab.url), 11 | ); 12 | console.log("Sending message to tabs:", { 13 | message, 14 | tabs: contentScriptTabs, 15 | }); 16 | 17 | // Forward message to tabs, collecting the responses 18 | const responses = await Promise.all( 19 | contentScriptTabs.map(async (tab) => { 20 | const response = await browser.tabs.sendMessage(tab.id!, message); 21 | return { tab: tab.id, response }; 22 | }), 23 | ); 24 | console.log("Received responses:", responses); 25 | 26 | // Return an array of all responses back to popup. 27 | sendResponse(responses); 28 | }); 29 | 30 | return true; 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /examples/background-message-forwarder/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ["*://*/*"], 3 | main() { 4 | browser.runtime.onMessage.addListener((message, _, sendResponse) => { 5 | console.log("Content script received message:", message); 6 | sendResponse(Math.random()); 7 | return true; 8 | }); 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /examples/background-message-forwarder/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Popup 7 | 8 | 9 | 10 |
Waiting for response...
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/background-message-forwarder/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | declare const sendMessageBtn: HTMLButtonElement; 2 | declare const responseDisplay: HTMLPreElement; 3 | 4 | sendMessageBtn.onclick = async () => { 5 | const response = await browser.runtime.sendMessage({ hello: "world" }); 6 | console.log("popup", { response }); 7 | responseDisplay.textContent = JSON.stringify(response, null, 2); 8 | }; 9 | -------------------------------------------------------------------------------- /examples/background-message-forwarder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "background-message-forwarder", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "wxt": "^0.20.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/background-message-forwarder/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/background-message-forwarder/public/icon/128.png -------------------------------------------------------------------------------- /examples/background-message-forwarder/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/background-message-forwarder/public/icon/16.png -------------------------------------------------------------------------------- /examples/background-message-forwarder/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/background-message-forwarder/public/icon/32.png -------------------------------------------------------------------------------- /examples/background-message-forwarder/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/background-message-forwarder/public/icon/48.png -------------------------------------------------------------------------------- /examples/background-message-forwarder/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/background-message-forwarder/public/icon/96.png -------------------------------------------------------------------------------- /examples/background-message-forwarder/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/background-message-forwarder/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | webExt: { 6 | startUrls: ["https://wxt.dev", "https://duckduckgo.com"], 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /examples/basic-messaging/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Basic Messaging 3 | description: Send one-time messages or connect to long-lived ports. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | 11 | Open the popup. You can send a one-time message by pressing the button, or see messages come in from the long-lived port. 12 | -------------------------------------------------------------------------------- /examples/basic-messaging/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | import { Browser } from "wxt/browser"; 2 | 3 | export default defineBackground(() => { 4 | // Setup listener for one-time messages 5 | browser.runtime.onMessage.addListener((message, _, sendResponse) => { 6 | // Only respond to hello messages 7 | if (message.type === "hello") { 8 | sendResponse(`Hello ${message.name}, this is the background!`); 9 | return true; 10 | } 11 | }); 12 | 13 | // Setup broadcast channel to send messages to all connected ports 14 | let ports: Browser.runtime.Port[] = []; 15 | setInterval(() => { 16 | const message = { date: Date.now(), value: Math.random() }; 17 | ports.forEach((port) => port.postMessage(message)); 18 | }, 1e3); 19 | browser.runtime.onConnect.addListener((port) => { 20 | ports.push(port); 21 | port.onDisconnect.addListener(() => { 22 | ports.splice(ports.indexOf(port), 1); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/basic-messaging/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default Popup Title 7 | 8 | 9 | 10 |
11 |

Messaging Example

12 |
13 |

One-time Message

14 | 15 |
Waiting for message to be sent...
16 | 17 |
Waiting for message to be sent...
18 |
19 |
20 |

Long-lived Messages

21 |
    22 |
    23 |
    24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/basic-messaging/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | declare const sendHelloMessageBtn: HTMLButtonElement; 2 | declare const helloResponsePre: HTMLPreElement; 3 | declare const sendUnknownMessageBtn: HTMLButtonElement; 4 | declare const unknownResponsePre: HTMLPreElement; 5 | declare const longLivedMessageList: HTMLUListElement; 6 | 7 | sendHelloMessageBtn.onclick = async () => { 8 | const response = await browser.runtime.sendMessage({ 9 | type: "hello", 10 | name: "Aaron", 11 | }); 12 | helloResponsePre.textContent = JSON.stringify(response) || "(No response)"; 13 | }; 14 | 15 | sendUnknownMessageBtn.onclick = async () => { 16 | const response = await browser.runtime.sendMessage({ type: "unknown" }); 17 | console.log({ response }); 18 | unknownResponsePre.textContent = JSON.stringify(response) || "(No response)"; 19 | }; 20 | 21 | const port = browser.runtime.connect(); 22 | port.onMessage.addListener((message) => { 23 | const li = document.createElement("li"); 24 | li.textContent = JSON.stringify(message); 25 | longLivedMessageList.append(li); 26 | }); 27 | -------------------------------------------------------------------------------- /examples/basic-messaging/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "messaging", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "wxt": "^0.20.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/basic-messaging/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/basic-messaging/public/icon/128.png -------------------------------------------------------------------------------- /examples/basic-messaging/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/basic-messaging/public/icon/16.png -------------------------------------------------------------------------------- /examples/basic-messaging/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/basic-messaging/public/icon/32.png -------------------------------------------------------------------------------- /examples/basic-messaging/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/basic-messaging/public/icon/48.png -------------------------------------------------------------------------------- /examples/basic-messaging/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/basic-messaging/public/icon/96.png -------------------------------------------------------------------------------- /examples/basic-messaging/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/basic-messaging/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({}); 5 | -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Mount UI On Browser Action Click 3 | description: Click on the extension to mount the UI on the web page. 4 | apis: 5 | - createShadowRootUi 6 | --- 7 | 8 | ```sh 9 | pnpm i 10 | pnpm dev 11 | ``` 12 | 13 | On any webpage, click the extension icon to mount UI. 14 | -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/assets/react.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | console.log("Hello background!", { id: browser.runtime.id }); 3 | 4 | (browser.action ?? browser.browserAction).onClicked.addListener( 5 | async (tab) => { 6 | console.log("browser action triggered,", tab); 7 | if (tab.id) { 8 | await browser.tabs.sendMessage(tab.id, { type: "MOUNT_UI" }); 9 | } 10 | }, 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/entrypoints/content/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #54bc4ae0); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/entrypoints/content/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import reactLogo from "@/assets/react.svg"; 3 | import wxtLogo from "@/public/wxt.svg"; 4 | import "./App.css"; 5 | 6 | function App() { 7 | const [count, setCount] = useState(0); 8 | 9 | return ( 10 | <> 11 |
    12 | 13 | WXT logo 14 | 15 | 16 | React logo 17 | 18 |
    19 |

    WXT + React

    20 |
    21 | 24 |

    25 | Edit src/App.tsx and save to test HMR 26 |

    27 |
    28 |

    29 | Click on the WXT and React logos to learn more 30 |

    31 | 32 | ); 33 | } 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/entrypoints/content/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client"; 2 | import { createShadowRootUi } from "#imports"; 3 | import App from "./App.tsx"; 4 | import "./style.css"; 5 | 6 | export default defineContentScript({ 7 | matches: ["*://*/*"], 8 | async main(ctx) { 9 | console.log("Hello content script."); 10 | 11 | const ui = await createShadowRootUi(ctx, { 12 | name: "wxt-react-example", 13 | position: "inline", 14 | anchor: "body", 15 | append: "first", 16 | onMount: (container) => { 17 | // Don't mount react app directly on 18 | const wrapper = document.createElement("div"); 19 | container.append(wrapper); 20 | 21 | const root = ReactDOM.createRoot(wrapper); 22 | root.render(); 23 | return { root, wrapper }; 24 | }, 25 | onRemove: (elements) => { 26 | elements?.root.unmount(); 27 | elements?.wrapper.remove(); 28 | }, 29 | }); 30 | 31 | browser.runtime.onMessage.addListener((event) => { 32 | if (event.type === "MOUNT_UI") { 33 | // dynamic mount by user action via messaging. 34 | ui.mount(); 35 | } 36 | }); 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/entrypoints/content/style.css: -------------------------------------------------------------------------------- 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 | a { 18 | font-weight: 500; 19 | color: #646cff; 20 | text-decoration: inherit; 21 | } 22 | a:hover { 23 | color: #535bf2; 24 | } 25 | 26 | body { 27 | margin: 0; 28 | display: flex; 29 | place-items: center; 30 | min-width: 320px; 31 | min-height: 100vh; 32 | } 33 | 34 | h1 { 35 | font-size: 3.2em; 36 | line-height: 1.1; 37 | } 38 | 39 | button { 40 | border-radius: 8px; 41 | border: 1px solid transparent; 42 | padding: 0.6em 1.2em; 43 | font-size: 1em; 44 | font-weight: 500; 45 | font-family: inherit; 46 | background-color: #1a1a1a; 47 | cursor: pointer; 48 | transition: border-color 0.25s; 49 | } 50 | button:hover { 51 | border-color: #646cff; 52 | } 53 | button:focus, 54 | button:focus-visible { 55 | outline: 4px auto -webkit-focus-ring-color; 56 | } 57 | 58 | @media (prefers-color-scheme: light) { 59 | :root { 60 | color: #213547; 61 | background-color: #ffffff; 62 | } 63 | a:hover { 64 | color: #747bff; 65 | } 66 | button { 67 | background-color: #f9f9f9; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser-action-mount-ui", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "dependencies": { 17 | "react": "^18.3.1", 18 | "react-dom": "^18.3.1" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.3.3", 22 | "@types/react-dom": "^18.3.0", 23 | "@wxt-dev/module-react": "^1.1.3", 24 | "typescript": "5.6.2", 25 | "wxt": "^0.20.5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/browser-action-mount-ui/public/icon/128.png -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/browser-action-mount-ui/public/icon/16.png -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/browser-action-mount-ui/public/icon/32.png -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/browser-action-mount-ui/public/icon/48.png -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/browser-action-mount-ui/public/icon/96.png -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/public/wxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json", 3 | "compilerOptions": { 4 | "allowImportingTsExtensions": true, 5 | "jsx": "react-jsx" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/browser-action-mount-ui/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | modules: ["@wxt-dev/module-react"], 6 | webExt: { 7 | startUrls: ["https://www.google.com/"], 8 | }, 9 | manifest: { 10 | // Required, don't open popup, only action 11 | action: {}, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /examples/content-script-session-storage/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Content Script Session Storage 3 | description: Setup and use session storage in content scripts. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | 11 | On `duckduckgo.com`, open dev tools and see the log of a value from session storage. 12 | -------------------------------------------------------------------------------- /examples/content-script-session-storage/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | // Set a value in session storage, don't need to await it. 3 | const startTime = Date.now(); 4 | void sessionStartTime.setValue(startTime); 5 | console.log("Setting session start time:", new Date(startTime).toISOString()); 6 | 7 | // Set the access level so `browser.storage.session` is defined and availble 8 | // in content scripts: https://developer.chrome.com/docs/extensions/reference/api/storage#storage_areas 9 | // @ts-expect-error: setAccessLevel not typed 10 | void browser.storage.session.setAccessLevel?.({ 11 | accessLevel: "TRUSTED_AND_UNTRUSTED_CONTEXTS", 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /examples/content-script-session-storage/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ["*://*/*"], 3 | 4 | async main() { 5 | const startTime = await sessionStartTime.getValue(); 6 | if (startTime == null) { 7 | console.log("No start time, reload tab"); 8 | } else { 9 | console.log("Session start time:", new Date(startTime).toISOString()); 10 | } 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /examples/content-script-session-storage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "content-script-session-storage", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "wxt": "^0.20.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/content-script-session-storage/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/content-script-session-storage/public/icon/128.png -------------------------------------------------------------------------------- /examples/content-script-session-storage/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/content-script-session-storage/public/icon/16.png -------------------------------------------------------------------------------- /examples/content-script-session-storage/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/content-script-session-storage/public/icon/32.png -------------------------------------------------------------------------------- /examples/content-script-session-storage/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/content-script-session-storage/public/icon/48.png -------------------------------------------------------------------------------- /examples/content-script-session-storage/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/content-script-session-storage/public/icon/96.png -------------------------------------------------------------------------------- /examples/content-script-session-storage/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/content-script-session-storage/utils/storage.ts: -------------------------------------------------------------------------------- 1 | import { storage } from "wxt/utils/storage"; 2 | 3 | export const sessionStartTime = 4 | storage.defineItem("session:start-time"); 5 | -------------------------------------------------------------------------------- /examples/content-script-session-storage/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | manifest: { 6 | permissions: ["storage"], 7 | }, 8 | webExt: { 9 | startUrls: ["https://duckduckgo.com/"], 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /examples/devtools-extension/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Devtools Extension 3 | description: Create an extension that adds to the browser's devtools. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | 11 | Then right-click and inspect on a website. You'll see a new devtools tab, "Example Panel", and a new pane, "Example pane", on the "elements" tab. 12 | -------------------------------------------------------------------------------- /examples/devtools-extension/entrypoints/devtools-pane/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

    Example sidebar pane details

    9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/devtools-extension/entrypoints/devtools-pane/main.ts: -------------------------------------------------------------------------------- 1 | // JS for custom sidebar pane 2 | -------------------------------------------------------------------------------- /examples/devtools-extension/entrypoints/devtools-panel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

    Example panel details

    9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/devtools-extension/entrypoints/devtools-panel/main.ts: -------------------------------------------------------------------------------- 1 | // JS for custom panel 2 | -------------------------------------------------------------------------------- /examples/devtools-extension/entrypoints/devtools/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/devtools-extension/entrypoints/devtools/main.ts: -------------------------------------------------------------------------------- 1 | browser.devtools.panels.create( 2 | "Example Panel", 3 | "icon/128.png", 4 | "devtools-panel.html", 5 | ); 6 | 7 | browser.devtools.panels.elements 8 | .createSidebarPane("Example Pane") 9 | .then((pane) => { 10 | pane.setPage("devtools-pane.html"); 11 | }); 12 | -------------------------------------------------------------------------------- /examples/devtools-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devtools-extension", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "wxt": "^0.20.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/devtools-extension/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/devtools-extension/public/icon/128.png -------------------------------------------------------------------------------- /examples/devtools-extension/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/devtools-extension/public/icon/16.png -------------------------------------------------------------------------------- /examples/devtools-extension/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/devtools-extension/public/icon/32.png -------------------------------------------------------------------------------- /examples/devtools-extension/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/devtools-extension/public/icon/48.png -------------------------------------------------------------------------------- /examples/devtools-extension/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/devtools-extension/public/icon/96.png -------------------------------------------------------------------------------- /examples/devtools-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/devtools-extension/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | webExt: { 6 | startUrls: ["https://wxt.dev/"], 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /examples/dynamic-content-scripts/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dynamic Content Scripts 3 | description: Use "webext-dynamic-content-scripts" and "webext-permission-toggle" to allow users to run content scripts on any page without requesting "*://*/*" on install. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | 11 | > https://github.com/fregante/webext-dynamic-content-scripts/blob/main/how-to-add-github-enterprise-support-to-web-extensions.md 12 | 13 | By default, the content script will run on . It won't run on . To run the content script on `google.com`, right click the extension icon and choose "Enable dynamic-content-scripts on this domain", then you'll see the log indicating the content script is running. 14 | -------------------------------------------------------------------------------- /examples/dynamic-content-scripts/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | import "webext-dynamic-content-scripts"; 2 | import addPermissionToggle from "webext-permission-toggle"; 3 | 4 | export default defineBackground(() => { 5 | console.log("Hello background!", { id: browser.runtime.id }); 6 | addPermissionToggle(); 7 | }); 8 | -------------------------------------------------------------------------------- /examples/dynamic-content-scripts/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ["*://*.wxt.dev/*"], 3 | main() { 4 | console.info("Content script loaded!"); 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /examples/dynamic-content-scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamic-content-scripts", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "wxt": "^0.20.5" 19 | }, 20 | "dependencies": { 21 | "webext-dynamic-content-scripts": "^10.0.3", 22 | "webext-permission-toggle": "^5.0.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/dynamic-content-scripts/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/dynamic-content-scripts/public/icon/128.png -------------------------------------------------------------------------------- /examples/dynamic-content-scripts/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/dynamic-content-scripts/public/icon/16.png -------------------------------------------------------------------------------- /examples/dynamic-content-scripts/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/dynamic-content-scripts/public/icon/32.png -------------------------------------------------------------------------------- /examples/dynamic-content-scripts/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/dynamic-content-scripts/public/icon/48.png -------------------------------------------------------------------------------- /examples/dynamic-content-scripts/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/dynamic-content-scripts/public/icon/96.png -------------------------------------------------------------------------------- /examples/dynamic-content-scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/dynamic-content-scripts/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | manifest: { 6 | // These permissions are required for "webext-dynamic-content-scripts" and 7 | // "webext-permission-toggle" to work. 8 | permissions: ["storage", "scripting", "activeTab", "contextMenus"], 9 | 10 | // @ts-ignore: Valid MV3 key for chrome 11 | optional_host_permissions: ["*://*/*"], 12 | }, 13 | 14 | // Required for webext-permission-toggle 15 | action: {}, 16 | 17 | hooks: { 18 | "build:manifestGenerated": (wxt, manifest) => { 19 | if (wxt.config.command === "serve") { 20 | // During development, content script is not listed in manifest, causing 21 | // "webext-dynamic-content-scripts" to throw an error. So we need to 22 | // add it manually. 23 | manifest.content_scripts ??= []; 24 | manifest.content_scripts.push({ 25 | matches: ["*://*.wxt.dev/*"], 26 | js: ["content-scripts/content.js"], 27 | // If the script has CSS, add it here. 28 | }); 29 | } 30 | }, 31 | }, 32 | 33 | webExt: { 34 | startUrls: ["https://wxt.dev", "https://google.com"], 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /examples/esm-content-script-ui/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ESM Content Script UI 3 | description: Pre-bundle a content script as ESM with code-splitting enabled, dynamically import it from a content script, and load a UI with scoped CSS. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/esm-content-script-ui/entrypoints/content/esm-index.ts: -------------------------------------------------------------------------------- 1 | // This file is built using separate vite config from other WXT entrypoints, so 2 | // it can't import from `#imports`. 3 | import { ContentScriptContext } from "wxt/utils/content-script-context"; 4 | import "./styles.css"; 5 | 6 | export default async (ctx: ContentScriptContext) => { 7 | // Just demoing that dynamic imports work and support chunking, see 8 | // examples/esm-content-script-setup/.output/chrome-mv3/content-scripts/esm 9 | // This could be a normal import 10 | const { createShadowRootUi } = await import( 11 | "wxt/utils/content-script-ui/shadow-root" 12 | ); 13 | const stylesText = await fetch( 14 | browser.runtime.getURL("/content-scripts/esm/content.css"), 15 | ).then((res) => res.text()); 16 | const ui = await createShadowRootUi(ctx, { 17 | name: "esm-ui-example", 18 | position: "inline", 19 | append: "first", 20 | onMount(uiContainer, shadow) { 21 | // Add our ESM styles to shadow root 22 | const style = document.createElement("style"); 23 | style.textContent = stylesText.replaceAll(":root", ":host"); 24 | shadow.querySelector("head")!.append(style); 25 | 26 | uiContainer.textContent = "ESM UI!"; 27 | }, 28 | }); 29 | ui.mount(); 30 | }; 31 | -------------------------------------------------------------------------------- /examples/esm-content-script-ui/entrypoints/content/index.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ["*://*/*"], 3 | async main(ctx) { 4 | /* @vite-ignore */ 5 | const mod = await import( 6 | browser.runtime.getURL("/content-scripts/esm/content.js") 7 | ); 8 | return await mod.default(ctx); 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /examples/esm-content-script-ui/entrypoints/content/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /examples/esm-content-script-ui/modules/esm-builder.ts: -------------------------------------------------------------------------------- 1 | import { defineWxtModule } from "wxt/modules"; 2 | import { InlineConfig, Rollup, build, mergeConfig } from "vite"; 3 | import { resolve } from "node:path"; 4 | import { ContentScriptEntrypoint } from "wxt"; 5 | 6 | export default defineWxtModule((wxt) => { 7 | let baseViteConfig: InlineConfig; 8 | wxt.hooks.hook("vite:build:extendConfig", ([entrypoint], config) => { 9 | if (entrypoint.name === "content") baseViteConfig = config; 10 | }); 11 | 12 | const buildEsmContentScript = async () => { 13 | wxt.logger.info("`[esm-builder]` Building `content/esm-index`..."); 14 | const prebuildConfig: InlineConfig = { 15 | esbuild: { 16 | footer: "", 17 | }, 18 | build: { 19 | lib: { 20 | entry: resolve(wxt.config.entrypointsDir, "content/esm-index.ts"), 21 | fileName: "content", 22 | formats: ["es"], 23 | name: "_content", 24 | }, 25 | rollupOptions: { 26 | output: { 27 | entryFileNames: "content.js", 28 | assetFileNames: "[name][extname]", 29 | }, 30 | }, 31 | outDir: resolve(wxt.config.outDir, "content-scripts/esm"), 32 | }, 33 | }; 34 | const finalConfig = mergeConfig(baseViteConfig, prebuildConfig); 35 | console.log(JSON.stringify(finalConfig, null, 2)); 36 | await build(finalConfig); 37 | wxt.logger.success("`[esm-builder]` Done!"); 38 | }; 39 | 40 | let contentScriptEntrypoint: ContentScriptEntrypoint; 41 | wxt.hooks.hook("entrypoints:resolved", (_, entrypoints) => { 42 | contentScriptEntrypoint = entrypoints.find( 43 | (e) => e.name === "content", 44 | ) as ContentScriptEntrypoint; 45 | }); 46 | 47 | // Build the ESM content script 48 | wxt.hooks.hook("build:done", () => buildEsmContentScript()); 49 | 50 | // Rebuilt during development 51 | wxt.hooks.hookOnce("build:done", () => { 52 | const esmBase = resolve(wxt.config.entrypointsDir, "content"); 53 | const ignoredFiles = new Set([resolve(esmBase, "index.ts")]); 54 | wxt.server?.watcher.on("all", async (_, file) => { 55 | if (file.startsWith(esmBase) && !ignoredFiles.has(file)) { 56 | await buildEsmContentScript(); 57 | wxt.server?.reloadContentScript({ 58 | contentScript: { 59 | matches: contentScriptEntrypoint.options.matches, 60 | js: ["/content-scripts/content.js"], 61 | }, 62 | }); 63 | wxt.logger.success( 64 | "`[esm-builder]` Reloaded `content` after changing ESM code", 65 | ); 66 | } 67 | }); 68 | }); 69 | 70 | // Add web_accessible_resources to manifest 71 | wxt.hooks.hook("build:manifestGenerated", (_, manifest) => { 72 | manifest.web_accessible_resources ??= []; 73 | // @ts-expect-error: MV2 types are conflicting with MV3 declaration 74 | // Note, this also works when targetting MV2 - WXT automatically transforms it to the MV2 syntax 75 | manifest.web_accessible_resources.push({ 76 | matches: contentScriptEntrypoint.options.matches, 77 | resources: ["/content-scripts/esm/*"], 78 | }); 79 | }); 80 | 81 | // Add public paths to prevent type errors 82 | wxt.hooks.hook("prepare:publicPaths", (_, paths) => { 83 | paths.push( 84 | "content-scripts/esm/content.js", 85 | "content-scripts/esm/content.css", 86 | ); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /examples/esm-content-script-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esm-content-script-ui", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "vite": "^6.3.3", 19 | "wxt": "^0.20.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/esm-content-script-ui/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/esm-content-script-ui/public/icon/128.png -------------------------------------------------------------------------------- /examples/esm-content-script-ui/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/esm-content-script-ui/public/icon/16.png -------------------------------------------------------------------------------- /examples/esm-content-script-ui/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/esm-content-script-ui/public/icon/32.png -------------------------------------------------------------------------------- /examples/esm-content-script-ui/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/esm-content-script-ui/public/icon/48.png -------------------------------------------------------------------------------- /examples/esm-content-script-ui/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/esm-content-script-ui/public/icon/96.png -------------------------------------------------------------------------------- /examples/esm-content-script-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/esm-content-script-ui/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | webExt: { 6 | startUrls: ["https://wxt.dev"], 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /examples/favicon-tracker/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .output 12 | stats.html 13 | stats-*.json 14 | .wxt 15 | web-ext.config.ts 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /examples/favicon-tracker/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Favicon Tracker 3 | description: Track and save favicons for all the websites you've been to. 4 | apis: 5 | - indexeddb 6 | --- 7 | 8 | ```sh 9 | pnpm i 10 | pnpm dev 11 | ``` 12 | -------------------------------------------------------------------------------- /examples/favicon-tracker/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | // Open database 3 | const db = openExtensionDatabase(); 4 | 5 | // Register proxy-service so other JS context's can get or insert favicons 6 | const faviconService = registerFaviconService(db); 7 | 8 | // Store favicons of websites you navigated to 9 | browser.tabs.onUpdated.addListener(async (id) => { 10 | // Grab all the tab details 11 | const tab = await browser.tabs.get(id); 12 | const url = tab.url ?? tab.pendingUrl; 13 | const faviconUrl = tab.favIconUrl; 14 | if (!url || !faviconUrl) return; 15 | 16 | // Add favicon to database 17 | const hostname = new URL(url).hostname; 18 | console.log(`Saving ${hostname}: ${faviconUrl}`); 19 | await faviconService.upsert({ hostname, faviconUrl }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/favicon-tracker/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ["*://*.google.com/*"], 3 | main() { 4 | console.log("Hello content."); 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /examples/favicon-tracker/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default Popup Title 7 | 8 | 9 | 10 |
    11 |

    Loading...

    12 |
    13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/favicon-tracker/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | 3 | declare const app: HTMLDivElement; 4 | const faviconService = getFaviconService(); 5 | 6 | loadFavicons(); 7 | 8 | async function loadFavicons() { 9 | // Load favicons 10 | app.innerHTML = "

    Loading...

    "; 11 | const favicons = await faviconService.getAll(); 12 | app.removeChild(app.firstChild!); 13 | 14 | // Render favicons 15 | favicons.map((favicon) => { 16 | const img = getFaviconImg(favicon); 17 | app.append(img); 18 | }); 19 | } 20 | 21 | function getFaviconImg(info: FaviconInfo) { 22 | const img = document.createElement("img"); 23 | img.src = info.faviconUrl; 24 | img.title = info.hostname; 25 | return img; 26 | } 27 | -------------------------------------------------------------------------------- /examples/favicon-tracker/entrypoints/popup/style.css: -------------------------------------------------------------------------------- 1 | /* Reset */ 2 | html, 3 | body { 4 | padding: 0; 5 | margin: 0; 6 | min-width: 0; 7 | } 8 | 9 | body { 10 | background-color: #202428; 11 | color: white; 12 | width: 300px; 13 | height: 300px; 14 | padding: 16px; 15 | } 16 | 17 | #app { 18 | display: grid; 19 | grid-template-columns: repeat(6, 1fr); 20 | align-items: start; 21 | justify-content: start; 22 | gap: 16px; 23 | } 24 | 25 | img { 26 | width: 32px; 27 | height: 32px; 28 | padding: 4px; 29 | box-sizing: border-box; 30 | outline: 1px solid rgba(255, 255, 255, 0.12); 31 | border-radius: 4px; 32 | transition: 250ms; 33 | cursor: pointer; 34 | } 35 | 36 | img:hover { 37 | background-color: black; 38 | } 39 | -------------------------------------------------------------------------------- /examples/favicon-tracker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "favicon-tracker", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "dependencies": { 17 | "@webext-core/proxy-service": "^1.2.0", 18 | "idb": "^8.0.0" 19 | }, 20 | "devDependencies": { 21 | "typescript": "^5.8.2", 22 | "wxt": "^0.20.5" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/favicon-tracker/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/favicon-tracker/public/icon/128.png -------------------------------------------------------------------------------- /examples/favicon-tracker/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/favicon-tracker/public/icon/16.png -------------------------------------------------------------------------------- /examples/favicon-tracker/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/favicon-tracker/public/icon/32.png -------------------------------------------------------------------------------- /examples/favicon-tracker/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/favicon-tracker/public/icon/48.png -------------------------------------------------------------------------------- /examples/favicon-tracker/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/favicon-tracker/public/icon/96.png -------------------------------------------------------------------------------- /examples/favicon-tracker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/favicon-tracker/utils/database.ts: -------------------------------------------------------------------------------- 1 | import { DBSchema, IDBPDatabase, openDB } from "idb"; 2 | import { FaviconInfo } from "./types"; 3 | 4 | interface ExtensionDatabaseSchema extends DBSchema { 5 | favicons: { 6 | key: string; 7 | value: FaviconInfo; 8 | }; 9 | } 10 | 11 | export type ExtensionDatabase = IDBPDatabase; 12 | 13 | export function openExtensionDatabase(): Promise { 14 | return openDB("favicon-tracker", 1, { 15 | upgrade(database) { 16 | database.createObjectStore("favicons", { keyPath: "hostname" }); 17 | }, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /examples/favicon-tracker/utils/favicon-service.ts: -------------------------------------------------------------------------------- 1 | import { defineProxyService } from "@webext-core/proxy-service"; 2 | import type { FaviconInfo } from "./types"; 3 | import type { ExtensionDatabase } from "./database"; 4 | 5 | export interface FaviconService { 6 | getAll(): Promise; 7 | upsert(info: FaviconInfo): Promise; 8 | } 9 | 10 | function createFaviconService(_db: Promise): FaviconService { 11 | return { 12 | async getAll() { 13 | // Can't await promises inside the background's main function, so instead 14 | // we await the promise inside the service: 15 | const db = await _db; 16 | return await db.getAll("favicons"); 17 | }, 18 | async upsert(info) { 19 | const db = await _db; 20 | await db.put("favicons", info); 21 | }, 22 | }; 23 | } 24 | 25 | export const [registerFaviconService, getFaviconService] = defineProxyService( 26 | "favicon-service", 27 | createFaviconService, 28 | ); 29 | -------------------------------------------------------------------------------- /examples/favicon-tracker/utils/types.ts: -------------------------------------------------------------------------------- 1 | export interface FaviconInfo { 2 | hostname: string; 3 | faviconUrl: string; 4 | } 5 | -------------------------------------------------------------------------------- /examples/favicon-tracker/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | manifest: { 6 | permissions: ["tabs"], 7 | }, 8 | webExt: { 9 | startUrls: [ 10 | "https://google.com", 11 | "https://duckduckgo.com", 12 | "https://wxt.dev", 13 | "https://vitest.dev/", 14 | "https://vitejs.dev", 15 | "https://www.typescriptlang.org/", 16 | "https://react.dev/", 17 | "https://vuejs.org/", 18 | "https://svelte.dev/", 19 | "https://developer.mozilla.org/", 20 | "https://stackoverflow.com/", 21 | "https://figma.com/", 22 | "https://youtube.com/", 23 | "https://instagram.com/", 24 | "https://reddit.com/", 25 | ], 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /examples/get-started-page/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Get Started Page 3 | description: Open a unlisted HTML page when the extension is installed. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | 11 | For more details, see: 12 | -------------------------------------------------------------------------------- /examples/get-started-page/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | browser.runtime.onInstalled.addListener(async ({ reason }) => { 3 | if (reason !== "install") return; 4 | 5 | // Open a tab on install 6 | await browser.tabs.create({ 7 | url: browser.runtime.getURL("/get-started.html"), 8 | active: true, 9 | }); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /examples/get-started-page/entrypoints/get-started/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Get Started 7 | 8 | 9 |

    Get started content...

    10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/get-started-page/entrypoints/get-started/main.ts: -------------------------------------------------------------------------------- 1 | declare const closeBtn: HTMLButtonElement; 2 | 3 | closeBtn.onclick = () => { 4 | window.close(); 5 | }; 6 | -------------------------------------------------------------------------------- /examples/get-started-page/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-started-page", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "wxt": "^0.20.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/get-started-page/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/get-started-page/public/icon/128.png -------------------------------------------------------------------------------- /examples/get-started-page/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/get-started-page/public/icon/16.png -------------------------------------------------------------------------------- /examples/get-started-page/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/get-started-page/public/icon/32.png -------------------------------------------------------------------------------- /examples/get-started-page/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/get-started-page/public/icon/48.png -------------------------------------------------------------------------------- /examples/get-started-page/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/get-started-page/public/icon/96.png -------------------------------------------------------------------------------- /examples/get-started-page/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/get-started-page/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({}); 5 | -------------------------------------------------------------------------------- /examples/inject-script/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Inject Script 3 | description: Inject a script into a webpage. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/inject-script/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | export default defineContentScript({ 2 | matches: ["*://*/*"], 3 | async main() { 4 | console.log("Injecting script..."); 5 | await injectScript("/injected.js", { 6 | keepInDom: true, 7 | }); 8 | console.log("Done!"); 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /examples/inject-script/entrypoints/injected.ts: -------------------------------------------------------------------------------- 1 | export default defineUnlistedScript(() => { 2 | console.log("Hello from injected.ts"); 3 | }); 4 | -------------------------------------------------------------------------------- /examples/inject-script/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inject-script", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "wxt": "^0.20.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/inject-script/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/inject-script/public/icon/128.png -------------------------------------------------------------------------------- /examples/inject-script/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/inject-script/public/icon/16.png -------------------------------------------------------------------------------- /examples/inject-script/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/inject-script/public/icon/32.png -------------------------------------------------------------------------------- /examples/inject-script/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/inject-script/public/icon/48.png -------------------------------------------------------------------------------- /examples/inject-script/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/inject-script/public/icon/96.png -------------------------------------------------------------------------------- /examples/inject-script/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/inject-script/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | manifest: { 6 | web_accessible_resources: [ 7 | { 8 | resources: ["injected.js"], 9 | matches: ["*://*/*"], 10 | }, 11 | ], 12 | }, 13 | webExt: { 14 | startUrls: ["https://wxt.dev"], 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /examples/offscreen-document-domparser/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Parse DOM in an Offscreen Document 3 | description: Use the DOMParser API in an offscreen document to process HTML snippets in the background. 4 | apis: 5 | - DOMParser 6 | --- 7 | 8 | Docs about the offscreen: https://developer.chrome.com/docs/extensions/reference/api/offscreen 9 | 10 | ```sh 11 | pnpm i 12 | pnpm dev 13 | ``` 14 | 15 | On any webpage, click the extension icon to log the number of ` 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/offscreen-document-domparser/entrypoints/offscreen/offscreen.ts: -------------------------------------------------------------------------------- 1 | import { MESSAGE_TARGET, OFFSCREEN_KEYS } from "@/utils/constants.js"; 2 | 3 | console.log("hello offscreen"); 4 | 5 | browser.runtime.onMessage.addListener(handleMessages); 6 | 7 | async function handleMessages(message: any) { 8 | if (message?.target !== MESSAGE_TARGET.OFFSCREEN || !message?.type) { 9 | return; 10 | } 11 | 12 | switch (message.type as OFFSCREEN_KEYS) { 13 | case OFFSCREEN_KEYS.SCRIPT_COUNTS: 14 | console.log(window); 15 | 16 | scriptCounts(message.data); 17 | break; 18 | default: 19 | console.warn(`Unexpected message received: '${message.type}'.`); 20 | return; 21 | } 22 | } 23 | 24 | function scriptCounts(htmlString: string) { 25 | const parser = new DOMParser(); 26 | const document = parser.parseFromString(htmlString, "text/html"); 27 | 28 | const count = document.querySelectorAll("script").length; 29 | 30 | sendToBackground(OFFSCREEN_KEYS.SCRIPT_COUNTS, count.toString()); 31 | } 32 | 33 | function sendToBackground(type: string, data: string) { 34 | chrome.runtime.sendMessage({ 35 | type, 36 | target: MESSAGE_TARGET.BACKGROUND, 37 | data, 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /examples/offscreen-document-domparser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "offscreen-document-domparser", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "@types/chrome": "^0.0.269", 18 | "typescript": "^5.8.2", 19 | "wxt": "^0.20.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/offscreen-document-domparser/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/offscreen-document-domparser/public/icon/128.png -------------------------------------------------------------------------------- /examples/offscreen-document-domparser/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/offscreen-document-domparser/public/icon/16.png -------------------------------------------------------------------------------- /examples/offscreen-document-domparser/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/offscreen-document-domparser/public/icon/32.png -------------------------------------------------------------------------------- /examples/offscreen-document-domparser/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/offscreen-document-domparser/public/icon/48.png -------------------------------------------------------------------------------- /examples/offscreen-document-domparser/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/offscreen-document-domparser/public/icon/96.png -------------------------------------------------------------------------------- /examples/offscreen-document-domparser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["DOM", "WebWorker"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/offscreen-document-domparser/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const OFFSCREEN_DOCUMENT_PATH = "/offscreen.html"; 2 | export enum MESSAGE_TARGET { 3 | OFFSCREEN = "offscreen", 4 | BACKGROUND = "background", 5 | } 6 | export enum OFFSCREEN_KEYS { 7 | SCRIPT_COUNTS = "script_counts", 8 | } 9 | -------------------------------------------------------------------------------- /examples/offscreen-document-domparser/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | extensionApi: "chrome", 6 | manifest: { 7 | action: {}, 8 | permissions: ["offscreen", "activeTab"], 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /examples/offscreen-document-setup/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Basic Offscreen Document Setup 3 | description: Basic setup for using an offscreen document 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/offscreen-document-setup/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | // @ts-expect-error: MV3 only API not typed 3 | browser.offscreen.createDocument({ 4 | url: "/offscreen.html", 5 | reasons: ["CLIPBOARD"], 6 | justification: "", 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /examples/offscreen-document-setup/entrypoints/offscreen/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/offscreen-document-setup/entrypoints/offscreen/main.ts: -------------------------------------------------------------------------------- 1 | // Log the `window` object to show what APIs are available 2 | console.log("Offscreen document is working!", window); 3 | -------------------------------------------------------------------------------- /examples/offscreen-document-setup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "offscreen-document-setup", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "wxt": "^0.20.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/offscreen-document-setup/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/offscreen-document-setup/public/icon/128.png -------------------------------------------------------------------------------- /examples/offscreen-document-setup/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/offscreen-document-setup/public/icon/16.png -------------------------------------------------------------------------------- /examples/offscreen-document-setup/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/offscreen-document-setup/public/icon/32.png -------------------------------------------------------------------------------- /examples/offscreen-document-setup/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/offscreen-document-setup/public/icon/48.png -------------------------------------------------------------------------------- /examples/offscreen-document-setup/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/offscreen-document-setup/public/icon/96.png -------------------------------------------------------------------------------- /examples/offscreen-document-setup/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/offscreen-document-setup/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | manifest: { 6 | permissions: ["offscreen"], 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Playwright E2e Testing 3 | description: Setup end-to-end tests for an extension. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm build 9 | pnpm exec playwright install 10 | pnpm e2e 11 | pnpm e2e --ui 12 | ``` 13 | 14 | Before running E2E tests, you need to build the latest version of your extension. 15 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/assets/typescript.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/components/counter.ts: -------------------------------------------------------------------------------- 1 | export function setupCounter(element: HTMLButtonElement) { 2 | let counter = 0; 3 | const setCounter = (count: number) => { 4 | counter = count; 5 | element.innerHTML = `count is ${counter}`; 6 | }; 7 | element.addEventListener("click", () => setCounter(counter + 1)); 8 | setCounter(0); 9 | } 10 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/e2e/fixtures.ts: -------------------------------------------------------------------------------- 1 | import { test as base, chromium, type BrowserContext } from "@playwright/test"; 2 | import path from "path"; 3 | 4 | const pathToExtension = path.resolve(".output/chrome-mv3"); 5 | 6 | export const test = base.extend<{ 7 | context: BrowserContext; 8 | extensionId: string; 9 | }>({ 10 | context: async ({}, use) => { 11 | const context = await chromium.launchPersistentContext("", { 12 | headless: false, 13 | args: [ 14 | `--disable-extensions-except=${pathToExtension}`, 15 | `--load-extension=${pathToExtension}`, 16 | ], 17 | }); 18 | await use(context); 19 | await context.close(); 20 | }, 21 | extensionId: async ({ context }, use) => { 22 | let background: { url(): string }; 23 | if (pathToExtension.endsWith("-mv3")) { 24 | [background] = context.serviceWorkers(); 25 | if (!background) background = await context.waitForEvent("serviceworker"); 26 | } else { 27 | [background] = context.backgroundPages(); 28 | if (!background) 29 | background = await context.waitForEvent("backgroundpage"); 30 | } 31 | 32 | const extensionId = background.url().split("/")[2]; 33 | await use(extensionId); 34 | }, 35 | }); 36 | export const expect = test.expect; 37 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/e2e/pages/popup.ts: -------------------------------------------------------------------------------- 1 | import { Page } from "@playwright/test"; 2 | 3 | export async function openPopup(page: Page, extensionId: string) { 4 | await page.goto(`chrome-extension://${extensionId}/popup.html`); 5 | 6 | await page.waitForSelector("#counter"); 7 | 8 | const popup = { 9 | getCounter: () => page.waitForSelector("#counter"), 10 | clickCounter: async () => { 11 | const counter = await popup.getCounter(); 12 | await counter.click(); 13 | }, 14 | getCounterText: async () => { 15 | const counter = await popup.getCounter(); 16 | return await counter.evaluate((el) => el.textContent); 17 | }, 18 | }; 19 | return popup; 20 | } 21 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/e2e/popup-counter.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "./fixtures"; 2 | import { openPopup } from "./pages/popup"; 3 | 4 | test("Popup counter increments when clicked", async ({ page, extensionId }) => { 5 | const popup = await openPopup(page, extensionId); 6 | expect(await popup.getCounterText()).toEqual("count is 0"); 7 | 8 | await popup.clickCounter(); 9 | expect(await popup.getCounterText()).toEqual("count is 1"); 10 | 11 | await popup.clickCounter(); 12 | expect(await popup.getCounterText()).toEqual("count is 2"); 13 | }); 14 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | console.log("Hello background!", { id: browser.runtime.id }); 3 | }); 4 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default Popup Title 7 | 8 | 9 | 10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | import typescriptLogo from "@/assets/typescript.svg"; 3 | import viteLogo from "/wxt.svg"; 4 | import { setupCounter } from "@/components/counter"; 5 | 6 | document.querySelector("#app")!.innerHTML = ` 7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 |

    WXT + TypeScript

    15 |
    16 | 17 |
    18 |

    19 | Click on the WXT and TypeScript logos to learn more 20 |

    21 |
    22 | `; 23 | 24 | setupCounter(document.querySelector("#counter")!); 25 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/entrypoints/popup/style.css: -------------------------------------------------------------------------------- 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 | a { 18 | font-weight: 500; 19 | color: #646cff; 20 | text-decoration: inherit; 21 | } 22 | a:hover { 23 | color: #535bf2; 24 | } 25 | 26 | body { 27 | margin: 0; 28 | display: flex; 29 | place-items: center; 30 | min-width: 320px; 31 | min-height: 100vh; 32 | } 33 | 34 | h1 { 35 | font-size: 3.2em; 36 | line-height: 1.1; 37 | } 38 | 39 | #app { 40 | max-width: 1280px; 41 | margin: 0 auto; 42 | padding: 2rem; 43 | text-align: center; 44 | } 45 | 46 | .logo { 47 | height: 6em; 48 | padding: 1.5em; 49 | will-change: filter; 50 | transition: filter 300ms; 51 | } 52 | .logo:hover { 53 | filter: drop-shadow(0 0 2em #54bc4ae0); 54 | } 55 | .logo.vanilla:hover { 56 | filter: drop-shadow(0 0 2em #3178c6aa); 57 | } 58 | 59 | .card { 60 | padding: 2em; 61 | } 62 | 63 | .read-the-docs { 64 | color: #888; 65 | } 66 | 67 | button { 68 | border-radius: 8px; 69 | border: 1px solid transparent; 70 | padding: 0.6em 1.2em; 71 | font-size: 1em; 72 | font-weight: 500; 73 | font-family: inherit; 74 | background-color: #1a1a1a; 75 | cursor: pointer; 76 | transition: border-color 0.25s; 77 | } 78 | button:hover { 79 | border-color: #646cff; 80 | } 81 | button:focus, 82 | button:focus-visible { 83 | outline: 4px auto -webkit-focus-ring-color; 84 | } 85 | 86 | @media (prefers-color-scheme: light) { 87 | :root { 88 | color: #213547; 89 | background-color: #ffffff; 90 | } 91 | a:hover { 92 | color: #747bff; 93 | } 94 | button { 95 | background-color: #f9f9f9; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxt-starter", 3 | "description": "manifest.json description", 4 | "private": true, 5 | "version": "0.0.0", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "wxt", 9 | "build": "wxt build", 10 | "postinstall": "wxt prepare", 11 | "e2e": "playwright test" 12 | }, 13 | "devDependencies": { 14 | "@playwright/test": "^1.40.1", 15 | "playwright": "^1.40.1", 16 | "typescript": "^5.8.2", 17 | "wxt": "^0.20.5" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from "@playwright/test"; 2 | 3 | export default defineConfig({ 4 | testDir: "e2e", 5 | 6 | // Fail the build on CI if you accidentally left test.only in the source code. 7 | forbidOnly: !!process.env.CI, 8 | 9 | // Retry on CI only. 10 | retries: process.env.CI ? 2 : 0, 11 | 12 | // Opt out of parallel tests on CI. 13 | workers: process.env.CI ? 1 : undefined, 14 | 15 | // Reporter to use 16 | reporter: "html", 17 | 18 | use: { 19 | // Collect trace when retrying the failed test. 20 | trace: "on-first-retry", 21 | }, 22 | 23 | // Configure projects for major browsers. 24 | projects: [ 25 | { 26 | name: "chromium", 27 | use: { ...devices["Desktop Chrome"] }, 28 | }, 29 | ], 30 | }); 31 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/playwright-e2e-testing/public/icon/128.png -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/playwright-e2e-testing/public/icon/16.png -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/playwright-e2e-testing/public/icon/32.png -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/playwright-e2e-testing/public/icon/48.png -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/playwright-e2e-testing/public/icon/96.png -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/public/wxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/playwright-e2e-testing/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({}); 5 | -------------------------------------------------------------------------------- /examples/react-content-script-ui/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: React Content Script UI 3 | description: Basic example of using createShadowRootUi with React. 4 | apis: 5 | - createShadowRootUi 6 | --- 7 | 8 | ```sh 9 | pnpm i 10 | pnpm dev 11 | ``` 12 | -------------------------------------------------------------------------------- /examples/react-content-script-ui/entrypoints/content/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export default () => { 4 | const [count, setCount] = useState(1); 5 | const increment = () => setCount((count) => count + 1); 6 | 7 | return ( 8 |
    9 |

    This is React. {count}

    10 | 11 |
    12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /examples/react-content-script-ui/entrypoints/content/index.tsx: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | 5 | export default defineContentScript({ 6 | matches: ["*://*/*"], 7 | cssInjectionMode: "ui", 8 | 9 | async main(ctx) { 10 | const ui = await createShadowRootUi(ctx, { 11 | name: "wxt-react-example", 12 | position: "inline", 13 | anchor: "body", 14 | append: "first", 15 | onMount: (container) => { 16 | // Don't mount react app directly on 17 | const wrapper = document.createElement("div"); 18 | container.append(wrapper); 19 | 20 | const root = ReactDOM.createRoot(wrapper); 21 | root.render(); 22 | return { root, wrapper }; 23 | }, 24 | onRemove: (elements) => { 25 | elements?.root.unmount(); 26 | elements?.wrapper.remove(); 27 | }, 28 | }); 29 | 30 | ui.mount(); 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /examples/react-content-script-ui/entrypoints/content/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | body { 7 | background-color: black; 8 | padding: 16px; 9 | } 10 | -------------------------------------------------------------------------------- /examples/react-content-script-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-content-script-ui", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "dependencies": { 17 | "react": "^18.3.1", 18 | "react-dom": "^18.3.1" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.3.3", 22 | "@types/react-dom": "^18.3.0", 23 | "@wxt-dev/module-react": "^1.1.3", 24 | "typescript": "^5.8.2", 25 | "wxt": "^0.20.5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/react-content-script-ui/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/react-content-script-ui/public/icon/128.png -------------------------------------------------------------------------------- /examples/react-content-script-ui/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/react-content-script-ui/public/icon/16.png -------------------------------------------------------------------------------- /examples/react-content-script-ui/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/react-content-script-ui/public/icon/32.png -------------------------------------------------------------------------------- /examples/react-content-script-ui/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/react-content-script-ui/public/icon/48.png -------------------------------------------------------------------------------- /examples/react-content-script-ui/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/react-content-script-ui/public/icon/96.png -------------------------------------------------------------------------------- /examples/react-content-script-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json", 3 | "compilerOptions": { 4 | "allowImportingTsExtensions": true, 5 | "jsx": "react-jsx" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/react-content-script-ui/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | modules: ["@wxt-dev/module-react"], 6 | webExt: { 7 | startUrls: ["https://google.com"], 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /examples/react-mantine/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: React Mantine 3 | description: Add mantine to HTML pages and content scripts 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | 11 | When running `dev`, you'll see some error logs: 12 | 13 | ``` 14 | Error when using sourcemap for reporting an error: Can't resolve original location of error. 15 | ``` 16 | 17 | No idea what these are or how to fix them, but they don't seem to cause problems, so you can ignore them. 18 | -------------------------------------------------------------------------------- /examples/react-mantine/components/Counter.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Button, Container } from "@mantine/core"; 3 | 4 | function Counter() { 5 | const [count, setCount] = useState(0); 6 | 7 | return ( 8 | <> 9 | 10 | 13 | 14 | 15 | ); 16 | } 17 | 18 | export default Counter; 19 | -------------------------------------------------------------------------------- /examples/react-mantine/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | console.log("Hello background!", { id: browser.runtime.id }); 3 | }); 4 | -------------------------------------------------------------------------------- /examples/react-mantine/entrypoints/content.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { ContentScriptContext } from "#imports"; 4 | import Counter from "@/components/Counter"; 5 | import { MantineProvider } from "@mantine/core"; 6 | // Remember to import Mantine's styles 7 | import "@mantine/core/styles.css"; 8 | 9 | export default defineContentScript({ 10 | matches: ["*://*/*"], 11 | cssInjectionMode: "ui", 12 | async main(ctx) { 13 | const ui = await createUi(ctx); 14 | ui.mount(); 15 | }, 16 | }); 17 | 18 | // 19 | // Mantine doesn't work with shadow roots by default. We have to pass custom 20 | // values for the MantineProvider's `cssVariablesSelector` and `getRootElement` 21 | // options. 22 | // 23 | // We'll point both at the HTML element inside the shadow root WXT creates. 24 | // 25 | 26 | function createUi(ctx: ContentScriptContext) { 27 | return createShadowRootUi(ctx, { 28 | name: "mantine-example", 29 | position: "inline", 30 | append: "first", 31 | onMount(uiContainer, shadow) { 32 | const app = document.createElement("div"); 33 | uiContainer.append(app); 34 | 35 | // Create a root on the UI container and render a component 36 | const root = ReactDOM.createRoot(app); 37 | root.render( 38 | 39 | shadow.querySelector("html")!} 43 | > 44 | 45 | 46 | , 47 | ); 48 | return root; 49 | }, 50 | onRemove(root) { 51 | root?.unmount(); 52 | }, 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /examples/react-mantine/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default Popup Title 7 | 8 | 9 | 10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-mantine/entrypoints/popup/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { MantineProvider } from "@mantine/core"; 4 | import Counter from "@/components/Counter.tsx"; 5 | // Remember to import Mantine's styles 6 | import "@mantine/core/styles.css"; 7 | 8 | // Nothing special here 9 | ReactDOM.createRoot(document.getElementById("root")!).render( 10 | 11 | 12 | 13 | 14 | , 15 | ); 16 | -------------------------------------------------------------------------------- /examples/react-mantine/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-mantine", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "dependencies": { 17 | "@mantine/core": "^7.10.2", 18 | "react": "^18.3.1", 19 | "react-dom": "^18.3.1" 20 | }, 21 | "devDependencies": { 22 | "@types/react": "^18.3.3", 23 | "@types/react-dom": "^18.3.0", 24 | "@wxt-dev/module-react": "^1.1.3", 25 | "postcss": "^8.4.38", 26 | "postcss-preset-mantine": "^1.15.0", 27 | "postcss-simple-vars": "^7.0.1", 28 | "typescript": "^5.8.2", 29 | "wxt": "^0.20.5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/react-mantine/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | "postcss-preset-mantine": {}, 4 | "postcss-simple-vars": { 5 | variables: { 6 | "mantine-breakpoint-xs": "36em", 7 | "mantine-breakpoint-sm": "48em", 8 | "mantine-breakpoint-md": "62em", 9 | "mantine-breakpoint-lg": "75em", 10 | "mantine-breakpoint-xl": "88em", 11 | }, 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /examples/react-mantine/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/react-mantine/public/icon/128.png -------------------------------------------------------------------------------- /examples/react-mantine/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/react-mantine/public/icon/16.png -------------------------------------------------------------------------------- /examples/react-mantine/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/react-mantine/public/icon/32.png -------------------------------------------------------------------------------- /examples/react-mantine/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/react-mantine/public/icon/48.png -------------------------------------------------------------------------------- /examples/react-mantine/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/react-mantine/public/icon/96.png -------------------------------------------------------------------------------- /examples/react-mantine/public/wxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/react-mantine/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json", 3 | "compilerOptions": { 4 | "allowImportingTsExtensions": true, 5 | "jsx": "react-jsx" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/react-mantine/utils/theme.ts: -------------------------------------------------------------------------------- 1 | import { createTheme } from "@mantine/core"; 2 | 3 | export default createTheme({ 4 | /** Put your mantine theme override here */ 5 | }); 6 | -------------------------------------------------------------------------------- /examples/react-mantine/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | modules: ["@wxt-dev/module-react"], 6 | webExt: { 7 | startUrls: ["https://google.com"], 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /examples/storybook/.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from "@storybook/react-vite"; 2 | 3 | const config: StorybookConfig = { 4 | stories: [ 5 | "../{stories,components}/**/*.mdx", 6 | "../{stories,components}/**/*.stories.@(js|jsx|mjs|ts|tsx)", 7 | ], 8 | addons: [ 9 | "@storybook/addon-onboarding", 10 | "@storybook/addon-links", 11 | "@storybook/addon-essentials", 12 | "@chromatic-com/storybook", 13 | "@storybook/addon-interactions", 14 | ], 15 | framework: { 16 | name: "@storybook/react-vite", 17 | options: { 18 | builder: { 19 | viteConfigPath: ".storybook/vite.config.ts", 20 | }, 21 | }, 22 | }, 23 | }; 24 | export default config; 25 | -------------------------------------------------------------------------------- /examples/storybook/.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from "@storybook/react"; 2 | 3 | const preview: Preview = { 4 | parameters: { 5 | controls: { 6 | matchers: { 7 | color: /(background|color)$/i, 8 | date: /Date$/i, 9 | }, 10 | }, 11 | }, 12 | }; 13 | 14 | export default preview; 15 | -------------------------------------------------------------------------------- /examples/storybook/.storybook/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { WxtVitest } from "wxt/testing"; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | // Add all the vite config from your wxt.config.ts, including the built-in 7 | // plugins and config WXT sets up automatically. 8 | WxtVitest(), 9 | ], 10 | }); 11 | -------------------------------------------------------------------------------- /examples/storybook/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Storybook 3 | description: Integrate Storybook with WXT to develop components. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm storybook 9 | ``` 10 | 11 | To add storybook: 12 | 13 | 1. Create the `.storybook/vite.config.ts` file from this example 14 | 2. Install the vite builder 15 | ```sh 16 | pnpm i @storybook/builder-vite 17 | ``` 18 | 3. Run `storybook init` like usual 19 | ```sh 20 | pnpm dlx storybook@latest init 21 | ``` 22 | 4. Update `.storybook/main.ts` to use `./.storybook/vite.config.ts` instead of `./vite.config.ts` 23 | ```diff 24 | framework: { 25 | name: "@storybook/react-vite", 26 | options: { 27 | + builder: { 28 | + viteConfigPath: ".storybook/vite.config.ts", 29 | + }, 30 | }, 31 | }, 32 | ``` 33 | -------------------------------------------------------------------------------- /examples/storybook/components/Button.css: -------------------------------------------------------------------------------- 1 | .storybook-button { 2 | display: inline-block; 3 | cursor: pointer; 4 | border: 0; 5 | border-radius: 3em; 6 | font-weight: 700; 7 | line-height: 1; 8 | font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | } 10 | .storybook-button--primary { 11 | background-color: #1ea7fd; 12 | color: white; 13 | } 14 | .storybook-button--secondary { 15 | box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; 16 | background-color: transparent; 17 | color: #333; 18 | } 19 | .storybook-button--small { 20 | padding: 10px 16px; 21 | font-size: 12px; 22 | } 23 | .storybook-button--medium { 24 | padding: 11px 20px; 25 | font-size: 14px; 26 | } 27 | .storybook-button--large { 28 | padding: 12px 24px; 29 | font-size: 16px; 30 | } 31 | -------------------------------------------------------------------------------- /examples/storybook/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "./Button.css"; 4 | 5 | export interface ButtonProps { 6 | /** Is this the principal call to action on the page? */ 7 | primary?: boolean; 8 | /** What background color to use */ 9 | backgroundColor?: string; 10 | /** How large should the button be? */ 11 | size?: "small" | "medium" | "large"; 12 | /** Button contents */ 13 | label: string; 14 | /** Optional click handler */ 15 | onClick?: () => void; 16 | } 17 | 18 | /** Primary UI component for user interaction */ 19 | export const Button = ({ 20 | primary = false, 21 | size = "medium", 22 | backgroundColor, 23 | label, 24 | ...props 25 | }: ButtonProps) => { 26 | const mode = primary 27 | ? "storybook-button--primary" 28 | : "storybook-button--secondary"; 29 | return ( 30 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /examples/storybook/components/test-Button.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | import { fn } from "@storybook/test"; 3 | 4 | import { Button } from "./Button"; 5 | 6 | // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export 7 | const meta = { 8 | component: Button, 9 | parameters: { 10 | // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout 11 | layout: "centered", 12 | }, 13 | // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs 14 | tags: ["autodocs"], 15 | // More on argTypes: https://storybook.js.org/docs/api/argtypes 16 | argTypes: { 17 | backgroundColor: { control: "color" }, 18 | }, 19 | // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args 20 | args: { onClick: fn() }, 21 | } satisfies Meta; 22 | 23 | export default meta; 24 | type Story = StoryObj; 25 | 26 | // More on writing stories with args: https://storybook.js.org/docs/writing-stories/args 27 | export const Primary: Story = { 28 | args: { 29 | primary: true, 30 | label: "Button", 31 | }, 32 | }; 33 | 34 | export const Secondary: Story = { 35 | args: { 36 | label: "Button", 37 | }, 38 | }; 39 | 40 | export const Large: Story = { 41 | args: { 42 | size: "large", 43 | label: "Button", 44 | }, 45 | }; 46 | 47 | export const Small: Story = { 48 | args: { 49 | size: "small", 50 | label: "Button", 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /examples/storybook/entrypoints/content/App.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/Button"; 2 | import { useState } from "react"; 3 | 4 | export default () => { 5 | const [count, setCount] = useState(1); 6 | const increment = () => setCount((count) => count + 1); 7 | 8 | return ( 9 |
    10 |

    This is React. {count}

    11 |
    13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /examples/storybook/entrypoints/content/index.tsx: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | 5 | export default defineContentScript({ 6 | matches: ["*://*/*"], 7 | cssInjectionMode: "ui", 8 | 9 | async main(ctx) { 10 | const ui = await createShadowRootUi(ctx, { 11 | name: "wxt-react-example", 12 | position: "inline", 13 | anchor: "body", 14 | append: "first", 15 | onMount: (container) => { 16 | // Don't mount react app directly on 17 | const wrapper = document.createElement("div"); 18 | container.append(wrapper); 19 | 20 | const root = ReactDOM.createRoot(wrapper); 21 | root.render(); 22 | return { root, wrapper }; 23 | }, 24 | onRemove: (elements) => { 25 | elements?.root.unmount(); 26 | elements?.wrapper.remove(); 27 | }, 28 | }); 29 | 30 | ui.mount(); 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /examples/storybook/entrypoints/content/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | body { 7 | background-color: black; 8 | padding: 16px; 9 | color: white; 10 | } 11 | -------------------------------------------------------------------------------- /examples/storybook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storybook", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare", 15 | "storybook": "storybook dev -p 6006", 16 | "build-storybook": "storybook build" 17 | }, 18 | "dependencies": { 19 | "react": "^18.3.1", 20 | "react-dom": "^18.3.1" 21 | }, 22 | "devDependencies": { 23 | "@chromatic-com/storybook": "^1.9.0", 24 | "@storybook/addon-essentials": "^8.6.12", 25 | "@storybook/addon-interactions": "^8.6.12", 26 | "@storybook/addon-links": "^8.6.12", 27 | "@storybook/addon-onboarding": "^8.6.12", 28 | "@storybook/blocks": "^8.6.12", 29 | "@storybook/builder-vite": "^8.6.12", 30 | "@storybook/react": "^8.6.12", 31 | "@storybook/react-vite": "^8.6.12", 32 | "@storybook/test": "^8.6.12", 33 | "@types/react": "^18.3.3", 34 | "@types/react-dom": "^18.3.0", 35 | "@wxt-dev/module-react": "^1.1.3", 36 | "storybook": "^8.6.12", 37 | "typescript": "^5.8.2", 38 | "vite": "^6.3.3", 39 | "wxt": "^0.20.5" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/storybook/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/storybook/public/icon/128.png -------------------------------------------------------------------------------- /examples/storybook/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/storybook/public/icon/16.png -------------------------------------------------------------------------------- /examples/storybook/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/storybook/public/icon/32.png -------------------------------------------------------------------------------- /examples/storybook/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/storybook/public/icon/48.png -------------------------------------------------------------------------------- /examples/storybook/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/storybook/public/icon/96.png -------------------------------------------------------------------------------- /examples/storybook/stories/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/storybook/stories/.keep -------------------------------------------------------------------------------- /examples/storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json", 3 | "compilerOptions": { 4 | "allowImportingTsExtensions": true, 5 | "jsx": "react-jsx" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/storybook/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | modules: ["@wxt-dev/module-react"], 6 | webExt: { 7 | startUrls: ["https://google.com"], 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Svelte Custom Store 3 | description: A custom svelte store wrapper around 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 | Visit example.org, open dev tools or the extension service worker logs, click the extension's action icon and toggle the checkbox. 12 | You will see that both content script and background script update their (local) state. When closing and re-opening the Popup, the state is persisted. 13 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-custom-store", 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 | "wxt": "^0.20.5" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/svelte-custom-store/public/icon/128.png -------------------------------------------------------------------------------- /examples/svelte-custom-store/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/svelte-custom-store/public/icon/16.png -------------------------------------------------------------------------------- /examples/svelte-custom-store/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/svelte-custom-store/public/icon/32.png -------------------------------------------------------------------------------- /examples/svelte-custom-store/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/svelte-custom-store/public/icon/48.png -------------------------------------------------------------------------------- /examples/svelte-custom-store/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/svelte-custom-store/public/icon/96.png -------------------------------------------------------------------------------- /examples/svelte-custom-store/public/wxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/src/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | import { someProperty } from "@/lib/store"; 2 | import { get } from "svelte/store"; 3 | 4 | export default defineBackground(() => { 5 | // Listen for the command to open the popup 6 | console.log("someProperty", get(someProperty)); 7 | 8 | someProperty.subscribe((value) => { 9 | console.log("someProperty changed", value); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/src/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | import { someProperty } from "@/lib/store"; 2 | import { get } from "svelte/store"; 3 | 4 | export default defineContentScript({ 5 | matches: ["*://*.example.org/*"], 6 | 7 | // open example.org to see console output when toggling 8 | 9 | main() { 10 | console.log("someProperty initial value", get(someProperty)); 11 | 12 | someProperty.subscribe((value) => { 13 | console.log("someProperty changed", value); 14 | }); 15 | 16 | // can also be used in background script 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/src/entrypoints/popup/App.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/src/entrypoints/popup/app.css: -------------------------------------------------------------------------------- 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 | @media (prefers-color-scheme: light) { 33 | :root { 34 | color: #213547; 35 | background-color: #ffffff; 36 | } 37 | a:hover { 38 | color: #747bff; 39 | } 40 | button { 41 | background-color: #f9f9f9; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/src/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default Popup Title 7 | 8 | 9 | 10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/src/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | import "./app.css"; 2 | import App from "./App.svelte"; 3 | 4 | const app = new App({ 5 | target: document.getElementById("app")!, 6 | }); 7 | 8 | export default app; 9 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/src/lib/store.ts: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | import type { StorageItemKey } from "wxt/utils/storage"; 3 | 4 | // In theory, it should be possible to remove the storageItem.watch call 5 | // and only listen to changes in the svelte store, 6 | // but then the changes don't propagate from popup to content/background. 7 | // Improvements welcome! 8 | function createStore(value: T, storageKey: StorageItemKey) { 9 | const { subscribe, set } = writable(value); 10 | 11 | const storageItem = storage.defineItem(storageKey, { 12 | fallback: value, 13 | }); 14 | 15 | storageItem.getValue().then(set); 16 | 17 | const unwatch = storageItem.watch(set); // not sure when or where to call unwatch 18 | 19 | return { 20 | subscribe, 21 | set: (value: T) => { 22 | storageItem.setValue(value); 23 | }, 24 | }; 25 | } 26 | 27 | export const someProperty = createStore(true, "local:someProperty"); 28 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json", 3 | "compilerOptions": { 4 | "useDefineForClassFields": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/wxt-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/svelte-custom-store/wxt.config.ts: -------------------------------------------------------------------------------- 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 | }); 11 | -------------------------------------------------------------------------------- /examples/tailwindcss/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: TailwindCSS 3 | description: Add TailwindCSS to your extension. 4 | apis: 5 | - createShadowRootUi 6 | --- 7 | 8 | Follow "Using Vite" installation: https://tailwindcss.com/docs/installation/using-vite 9 | 10 | ```sh 11 | pnpm i 12 | pnpm dev 13 | ``` 14 | -------------------------------------------------------------------------------- /examples/tailwindcss/assets/tailwind.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | -------------------------------------------------------------------------------- /examples/tailwindcss/entrypoints/content.ts: -------------------------------------------------------------------------------- 1 | import type { ContentScriptContext } from "#imports"; 2 | import "~/assets/tailwind.css"; 3 | 4 | export default defineContentScript({ 5 | matches: ["*://*/*"], 6 | cssInjectionMode: "ui", 7 | 8 | async main(ctx) { 9 | const ui = await createUi(ctx); 10 | ui.mount(); 11 | }, 12 | }); 13 | 14 | function createUi(ctx: ContentScriptContext) { 15 | return createShadowRootUi(ctx, { 16 | name: "tailwind-shadow-root-example", 17 | position: "inline", 18 | anchor: "body", 19 | append: "first", 20 | onMount: (uiContainer) => { 21 | const p = document.createElement("p"); 22 | p.classList.add("text-lg", "text-red-500", "font-bold", "p-8"); 23 | p.textContent = "Hello from shadow root with TailwindCSS applied"; 24 | uiContainer.append(p); 25 | }, 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /examples/tailwindcss/entrypoints/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Default Popup Title 7 | 8 | 9 | 10 | 11 |

    Hello World

    12 |

    13 | This page is styled with 14 | TailwindCSS. 20 |

    21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/tailwindcss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwindcss", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "@tailwindcss/vite": "^4.1.4", 18 | "tailwindcss": "^4.0.9", 19 | "typescript": "^5.8.2", 20 | "wxt": "^0.20.5" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/tailwindcss/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/tailwindcss/public/icon/128.png -------------------------------------------------------------------------------- /examples/tailwindcss/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/tailwindcss/public/icon/16.png -------------------------------------------------------------------------------- /examples/tailwindcss/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/tailwindcss/public/icon/32.png -------------------------------------------------------------------------------- /examples/tailwindcss/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/tailwindcss/public/icon/48.png -------------------------------------------------------------------------------- /examples/tailwindcss/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/tailwindcss/public/icon/96.png -------------------------------------------------------------------------------- /examples/tailwindcss/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/tailwindcss/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | import tailwindcss from "@tailwindcss/vite"; 3 | 4 | // See https://wxt.dev/api/config.html 5 | export default defineConfig({ 6 | vite: () => ({ 7 | plugins: [tailwindcss()], 8 | }), 9 | }); 10 | -------------------------------------------------------------------------------- /examples/vanilla-i18n/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Vanilla I18n 3 | description: Use the vanilla browser.i18n APIs to localize your extension. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/vanilla-i18n/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

    9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/vanilla-i18n/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | document.title = browser.i18n.getMessage("popupTitle"); 2 | 3 | declare const messageH1: HTMLHeadingElement; 4 | messageH1.textContent = browser.i18n.getMessage("helloWorld", "John Smith"); 5 | -------------------------------------------------------------------------------- /examples/vanilla-i18n/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vanilla-i18n", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "wxt": "^0.20.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/vanilla-i18n/public/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "Vanilla I18n Example" 4 | }, 5 | "extDescription": { 6 | "message": "Use the browser.i18n APIs to localize your extension" 7 | }, 8 | "popupTitle": { 9 | "message": "Example Popup Title" 10 | }, 11 | "helloWorld": { 12 | "message": "Hello $1!" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/vanilla-i18n/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vanilla-i18n/public/icon/128.png -------------------------------------------------------------------------------- /examples/vanilla-i18n/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vanilla-i18n/public/icon/16.png -------------------------------------------------------------------------------- /examples/vanilla-i18n/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vanilla-i18n/public/icon/32.png -------------------------------------------------------------------------------- /examples/vanilla-i18n/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vanilla-i18n/public/icon/48.png -------------------------------------------------------------------------------- /examples/vanilla-i18n/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vanilla-i18n/public/icon/96.png -------------------------------------------------------------------------------- /examples/vanilla-i18n/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/vanilla-i18n/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | manifest: { 6 | name: "__MSG_extName__", 7 | description: "__MSG_extDescription__", 8 | default_locale: "en", 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /examples/vitest-unit-testing/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Vitest Unit Testing 3 | description: Setup unit tests and mock the browser APIs. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm test 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/vitest-unit-testing/entrypoints/__tests__/background.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach } from "vitest"; 2 | import background from "../background"; 3 | import { fakeBrowser } from "wxt/testing"; 4 | import type { Runtime } from "wxt/browser"; 5 | 6 | describe("Background Entrypoint", () => { 7 | beforeEach(() => { 8 | // Reset the in-memory state, including storage 9 | fakeBrowser.reset(); 10 | }); 11 | 12 | it("should store the current date on install", async () => { 13 | const expected = "2023-12-22T15:27:25.950Z"; 14 | vi.setSystemTime(expected); 15 | 16 | background.main(); 17 | await fakeBrowser.runtime.onInstalled.trigger({ 18 | reason: "install", 19 | temporary: false, 20 | }); 21 | const actual = await storage.getItem("local:installDate"); 22 | 23 | expect(actual).toBe(expected); 24 | }); 25 | 26 | it.each(["update", "browser_update"])( 27 | "should not store the current date on %s", 28 | async (reason) => { 29 | const previous = "2023-12-22T15:27:25.950Z"; 30 | await storage.setItem("local:installDate", previous); 31 | 32 | background.main(); 33 | await fakeBrowser.runtime.onInstalled.trigger({ 34 | reason, 35 | temporary: false, 36 | }); 37 | const actual = await storage.getItem("local:installDate"); 38 | 39 | expect(actual).toBe(previous); 40 | }, 41 | ); 42 | }); 43 | -------------------------------------------------------------------------------- /examples/vitest-unit-testing/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | browser.runtime.onInstalled.addListener(({ reason }) => { 3 | if (reason === "install") { 4 | storage.setItem("local:installDate", new Date().toISOString()); 5 | } 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /examples/vitest-unit-testing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vitest-unit-testing", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "wxt", 8 | "build": "wxt build", 9 | "test": "vitest", 10 | "compile": "tsc --noEmit", 11 | "postinstall": "wxt prepare" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^5.8.2", 15 | "vitest": "^3.1.2", 16 | "wxt": "^0.20.5" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/vitest-unit-testing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/vitest-unit-testing/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | import { WxtVitest } from "wxt/testing"; 3 | 4 | export default defineConfig({ 5 | // Configure test behavior however you like 6 | test: { 7 | mockReset: true, 8 | restoreMocks: true, 9 | }, 10 | 11 | // This is the line that matters! 12 | plugins: [WxtVitest()], 13 | 14 | // If any dependencies rely on webextension-polyfill, add them here to the `ssr.noExternal` option. 15 | // Example: 16 | // ssr: { 17 | // noExternal: ['@webext-core/storage'], 18 | // }, 19 | }); 20 | -------------------------------------------------------------------------------- /examples/vue-overlay/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Vue Overlay 3 | description: Show a simple overlay in the top right corner of every website. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/vue-overlay/assets/vue.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/vue-overlay/entrypoints/content/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | 33 | -------------------------------------------------------------------------------- /examples/vue-overlay/entrypoints/content/index.ts: -------------------------------------------------------------------------------- 1 | import { ContentScriptContext } from "#imports"; 2 | import App from "./App.vue"; 3 | import { createApp } from "vue"; 4 | import "./reset.css"; 5 | 6 | export default defineContentScript({ 7 | matches: ["*://*/*"], 8 | cssInjectionMode: "ui", 9 | 10 | async main(ctx) { 11 | const ui = await defineOverlay(ctx); 12 | 13 | // Mount initially 14 | ui.mount(); 15 | 16 | // Re-mount when page changes 17 | ctx.addEventListener(window, "wxt:locationchange", (event) => { 18 | ui.mount(); 19 | }); 20 | }, 21 | }); 22 | 23 | function defineOverlay(ctx: ContentScriptContext) { 24 | return createShadowRootUi(ctx, { 25 | name: "vue-overlay", 26 | position: "modal", 27 | zIndex: 99999, 28 | onMount(container, _shadow, shadowHost) { 29 | const app = createApp(App); 30 | app.mount(container); 31 | shadowHost.style.pointerEvents = "none"; 32 | return app; 33 | }, 34 | onRemove(app) { 35 | app?.unmount(); 36 | }, 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /examples/vue-overlay/entrypoints/content/reset.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | -------------------------------------------------------------------------------- /examples/vue-overlay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-overlay", 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 | "compile": "vue-tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "dependencies": { 17 | "vue": "^3.4.27" 18 | }, 19 | "devDependencies": { 20 | "@wxt-dev/module-vue": "^1.0.2", 21 | "typescript": "^5.8.2", 22 | "vue-tsc": "^2.0.21", 23 | "wxt": "^0.20.5" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/vue-overlay/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vue-overlay/public/icon/128.png -------------------------------------------------------------------------------- /examples/vue-overlay/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vue-overlay/public/icon/16.png -------------------------------------------------------------------------------- /examples/vue-overlay/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vue-overlay/public/icon/32.png -------------------------------------------------------------------------------- /examples/vue-overlay/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vue-overlay/public/icon/48.png -------------------------------------------------------------------------------- /examples/vue-overlay/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vue-overlay/public/icon/96.png -------------------------------------------------------------------------------- /examples/vue-overlay/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/vue-overlay/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | modules: ["@wxt-dev/module-vue"], 6 | webExt: { 7 | startUrls: ["https://duckduckgo.com"], 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /examples/vue-storage-composable/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Vue Storage Composable 3 | description: Create a composable for managing a value in storage. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | 11 | Then open the popup. The background increments the value in storage, and the popup displays the value. 12 | -------------------------------------------------------------------------------- /examples/vue-storage-composable/composables/useStoredValue.ts: -------------------------------------------------------------------------------- 1 | import { UseAsyncStateOptions, useAsyncState } from "@vueuse/core"; 2 | import { computed, onMounted, onUnmounted } from "vue"; 3 | import { storage, StorageItemKey } from "wxt/utils/storage"; 4 | 5 | export default function ( 6 | key: StorageItemKey, 7 | initialValue?: T, 8 | opts?: UseAsyncStateOptions, 9 | ) { 10 | const { 11 | state, 12 | execute: _, // Don't include "execute" in returned object 13 | ...asyncState 14 | } = useAsyncState( 15 | () => storage.getItem(key), 16 | initialValue ?? null, 17 | opts, 18 | ); 19 | 20 | // Listen for changes 21 | let unwatch: (() => void) | undefined; 22 | onMounted(() => { 23 | unwatch = storage.watch(key, async (newValue) => { 24 | state.value = newValue ?? initialValue ?? null; 25 | }); 26 | }); 27 | onUnmounted(() => { 28 | unwatch?.(); 29 | }); 30 | 31 | return { 32 | // Use a writable computed ref to write updates to storage 33 | state: computed({ 34 | get() { 35 | return state.value; 36 | }, 37 | set(newValue) { 38 | void storage.setItem(key, newValue); 39 | state.value = newValue; 40 | }, 41 | }), 42 | ...asyncState, 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /examples/vue-storage-composable/entrypoints/background.ts: -------------------------------------------------------------------------------- 1 | export default defineBackground(() => { 2 | // Modify the storage item in the background to demonstrate that the composable works 3 | setInterval(async () => { 4 | const oldValue = await storage.getItem("session:count"); 5 | const newValue = (oldValue ?? 0) + 1; 6 | await storage.setItem("session:count", newValue); 7 | }, 1000); 8 | }); 9 | -------------------------------------------------------------------------------- /examples/vue-storage-composable/entrypoints/popup/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /examples/vue-storage-composable/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
    9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/vue-storage-composable/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | 4 | createApp(App).mount("#app"); 5 | -------------------------------------------------------------------------------- /examples/vue-storage-composable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-storage-composable", 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 | "compile": "vue-tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "dependencies": { 17 | "@vueuse/core": "^11.1.0", 18 | "vue": "^3.4.27" 19 | }, 20 | "devDependencies": { 21 | "@wxt-dev/module-vue": "^1.0.2", 22 | "typescript": "^5.8.2", 23 | "vue-tsc": "^2.0.21", 24 | "wxt": "^0.20.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/vue-storage-composable/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vue-storage-composable/public/icon/128.png -------------------------------------------------------------------------------- /examples/vue-storage-composable/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vue-storage-composable/public/icon/16.png -------------------------------------------------------------------------------- /examples/vue-storage-composable/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vue-storage-composable/public/icon/32.png -------------------------------------------------------------------------------- /examples/vue-storage-composable/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vue-storage-composable/public/icon/48.png -------------------------------------------------------------------------------- /examples/vue-storage-composable/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/vue-storage-composable/public/icon/96.png -------------------------------------------------------------------------------- /examples/vue-storage-composable/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/vue-storage-composable/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | modules: ["@wxt-dev/module-vue"], 6 | manifest: { 7 | permissions: ["storage"], 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /examples/web-worker-setup/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Web Worker Setup 3 | description: Show how to create web workers using Vite's built-in support. 4 | apis: 5 | - WebWorker 6 | --- 7 | 8 | ```sh 9 | pnpm i 10 | pnpm dev 11 | ``` 12 | -------------------------------------------------------------------------------- /examples/web-worker-setup/entrypoints/content/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-expect-error: Query params not typed 2 | import MyWorker from "./worker?worker&inline"; 3 | 4 | export default defineContentScript({ 5 | matches: ["*://*/*"], 6 | main() { 7 | console.log("Creating web worker"); 8 | const worker = new MyWorker(); 9 | console.log("Created!"); 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /examples/web-worker-setup/entrypoints/content/worker.ts: -------------------------------------------------------------------------------- 1 | // Work around for: https://github.com/wxt-dev/wxt/issues/942 2 | // @ts-ignore 3 | globalThis._content = undefined; 4 | 5 | // Worker code below: 6 | console.log("Hello from worker"); 7 | -------------------------------------------------------------------------------- /examples/web-worker-setup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-worker-setup", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^5.8.2", 18 | "wxt": "^0.20.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/web-worker-setup/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/web-worker-setup/public/icon/128.png -------------------------------------------------------------------------------- /examples/web-worker-setup/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/web-worker-setup/public/icon/16.png -------------------------------------------------------------------------------- /examples/web-worker-setup/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/web-worker-setup/public/icon/32.png -------------------------------------------------------------------------------- /examples/web-worker-setup/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/web-worker-setup/public/icon/48.png -------------------------------------------------------------------------------- /examples/web-worker-setup/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/web-worker-setup/public/icon/96.png -------------------------------------------------------------------------------- /examples/web-worker-setup/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["DOM", "ESNext", "WebWorker"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/web-worker-setup/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | webExt: { 6 | startUrls: ["https://wxt.dev"], 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /examples/wxt-i18n/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: WXT I18n 3 | description: Use @wxt-dev/i18n to localize your extension. 4 | --- 5 | 6 | ```sh 7 | pnpm i 8 | pnpm dev 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/wxt-i18n/entrypoints/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

    9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/wxt-i18n/entrypoints/popup/main.ts: -------------------------------------------------------------------------------- 1 | document.title = i18n.t("popup.title"); 2 | 3 | declare const messageH1: HTMLHeadingElement; 4 | messageH1.textContent = i18n.t("popup.hello", ["John Smith"]); 5 | -------------------------------------------------------------------------------- /examples/wxt-i18n/locales/en.yml: -------------------------------------------------------------------------------- 1 | extension: 2 | name: Vanilla I18n Example 3 | description: Use the @wxt-dev/i18n to localize your extension 4 | popup: 5 | title: Example Popup Title 6 | hello: Hello $1! 7 | -------------------------------------------------------------------------------- /examples/wxt-i18n/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxt-i18n", 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 | "compile": "tsc --noEmit", 14 | "postinstall": "wxt prepare" 15 | }, 16 | "devDependencies": { 17 | "@wxt-dev/i18n": "^0.2.3", 18 | "typescript": "^5.8.2", 19 | "wxt": "^0.20.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/wxt-i18n/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/wxt-i18n/public/icon/128.png -------------------------------------------------------------------------------- /examples/wxt-i18n/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/wxt-i18n/public/icon/16.png -------------------------------------------------------------------------------- /examples/wxt-i18n/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/wxt-i18n/public/icon/32.png -------------------------------------------------------------------------------- /examples/wxt-i18n/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/wxt-i18n/public/icon/48.png -------------------------------------------------------------------------------- /examples/wxt-i18n/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wxt-dev/examples/21b27f4ff636fe7bd688037d25aea996f7896a69/examples/wxt-i18n/public/icon/96.png -------------------------------------------------------------------------------- /examples/wxt-i18n/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.wxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /examples/wxt-i18n/wxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "wxt"; 2 | 3 | // See https://wxt.dev/api/config.html 4 | export default defineConfig({ 5 | modules: ["@wxt-dev/i18n/module"], 6 | manifest: { 7 | name: "__MSG_extension_name__", 8 | description: "__MSG_extension_description__", 9 | default_locale: "en", 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "packageManager": "pnpm@10.7.0", 4 | "scripts": { 5 | "format": "prettier --write .", 6 | "test": "vitest", 7 | "update-metadata": "tsx scripts/update-metadata-json.ts" 8 | }, 9 | "devDependencies": { 10 | "@babel/parser": "^7.25.3", 11 | "@babel/traverse": "^7.25.3", 12 | "@babel/types": "^7.25.2", 13 | "@types/babel__traverse": "^7.20.6", 14 | "consola": "^3.2.3", 15 | "fast-glob": "^3.3.2", 16 | "prettier": "^3.3.2", 17 | "tsx": "^4.15.4", 18 | "typescript": "^5.8.2", 19 | "vitest": "3.1.2", 20 | "yaml": "^2.4.5" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - examples/* 3 | -------------------------------------------------------------------------------- /scripts/libs/babel.ts: -------------------------------------------------------------------------------- 1 | import { ParserOptions, ParserPlugin } from "@babel/parser"; 2 | 3 | /** 4 | * @description 5 | * reference from magicast's option 6 | * https://github.com/unjs/magicast/blob/7b3f0bd9bdbc07d7c66408645a5dcd309511119e/src/babel.ts 7 | */ 8 | export function getBabelParserOptions(): ParserOptions { 9 | return { 10 | sourceType: "module", 11 | strictMode: false, 12 | allowImportExportEverywhere: true, 13 | allowReturnOutsideFunction: true, 14 | startLine: 1, 15 | tokens: true, 16 | plugins: [ 17 | "asyncGenerators", 18 | "bigInt", 19 | "classPrivateMethods", 20 | "classPrivateProperties", 21 | "classProperties", 22 | "classStaticBlock", 23 | "decimal", 24 | "decorators-legacy", 25 | "doExpressions", 26 | "dynamicImport", 27 | "exportDefaultFrom", 28 | "exportExtensions" as any as ParserPlugin, 29 | "exportNamespaceFrom", 30 | "functionBind", 31 | "functionSent", 32 | "importAssertions", 33 | "importMeta", 34 | "nullishCoalescingOperator", 35 | "numericSeparator", 36 | "objectRestSpread", 37 | "optionalCatchBinding", 38 | "optionalChaining", 39 | [ 40 | "pipelineOperator", 41 | { 42 | proposal: "minimal", 43 | }, 44 | ] as any as ParserPlugin, 45 | [ 46 | "recordAndTuple", 47 | { 48 | syntaxType: "hash", 49 | }, 50 | ], 51 | "throwExpressions", 52 | "topLevelAwait", 53 | "v8intrinsic", 54 | "jsx", 55 | "typescript", 56 | ], 57 | } as const satisfies ParserOptions; 58 | } 59 | -------------------------------------------------------------------------------- /scripts/libs/loadExtensionApiMap.ts: -------------------------------------------------------------------------------- 1 | // This JSON data is retrieved from https://github.com/GoogleChrome/chrome-extensions-samples 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | export async function loadExtensionApiMap(): Promise< 15 | Record> 16 | > { 17 | const res = await fetch( 18 | "https://raw.githubusercontent.com/GoogleChrome/chrome-extensions-samples/main/.repo/sample-list-generator/extension-apis.json", 19 | ); 20 | return await res.json(); 21 | } 22 | -------------------------------------------------------------------------------- /scripts/parse-browser-api.ts: -------------------------------------------------------------------------------- 1 | import parser from "@babel/parser"; 2 | import _traverse, { NodePath } from "@babel/traverse"; 3 | import types from "@babel/types"; 4 | import { getBabelParserOptions } from "./libs/babel.js"; 5 | import { loadExtensionApiMap } from "./libs/loadExtensionApiMap.js"; 6 | 7 | // tsx can't be default import, but vitest can be default import 8 | // @ts-expect-error https://github.com/babel/babel/discussions/13093 9 | const traverse = (_traverse.default || _traverse) as typeof _traverse; 10 | 11 | const EXTENSION_API_MAP = await loadExtensionApiMap(); 12 | 13 | type Parts = string[]; 14 | type PartsCollection = Parts[]; 15 | type ApiType = "event" | "method" | "property" | "type" | "unknown"; 16 | type BrowserApiItem = { 17 | namespace: string; 18 | propertyName: string; 19 | type: ApiType; 20 | parts: Parts; 21 | }; 22 | type BrowserApiItemCollection = BrowserApiItem[]; 23 | 24 | export function collectUsedBrowserApi(fileContent: string) { 25 | const ast = parser.parse(fileContent, getBabelParserOptions()); 26 | 27 | const usedBrowserApi = new Set(); 28 | 29 | const extractApi = (path: NodePath) => { 30 | const partsCollection = getFullMemberExpression(path.node); 31 | const browserApi = pickBrowserApi(partsCollection); 32 | const filteredBrowserApi = filterBrowserApi(browserApi); 33 | for (const { parts } of filteredBrowserApi) { 34 | usedBrowserApi.add(parts.join(".")); 35 | } 36 | }; 37 | 38 | traverse(ast, { 39 | MemberExpression(path) { 40 | extractApi(path); 41 | }, 42 | OptionalMemberExpression(path) { 43 | extractApi(path); 44 | }, 45 | }); 46 | 47 | return usedBrowserApi; 48 | } 49 | 50 | function getFullMemberExpression(node: types.Node): PartsCollection { 51 | const partsCollection: PartsCollection = []; 52 | const parts: Parts = []; 53 | 54 | while (node) { 55 | if ( 56 | types.isMemberExpression(node) || 57 | types.isOptionalMemberExpression(node) 58 | ) { 59 | if (types.isIdentifier(node.property)) { 60 | parts.unshift(node.property.name); 61 | } 62 | node = node.object; 63 | } else if (types.isLogicalExpression(node)) { 64 | /** 65 | * Recursively retrieve left and right sides. Then merge it with the member's name. 66 | * @example (browser.action ?? browser.browserAction).onClicked.addListener 67 | * left: browser.action 68 | * right: browser.browserAction 69 | * parts: onClicked.addListener 70 | */ 71 | const left = getFullMemberExpression(node.left).flat(); 72 | const right = getFullMemberExpression(node.right).flat(); 73 | partsCollection.push([...left, ...parts]); 74 | partsCollection.push([...right, ...parts]); 75 | break; 76 | } else if (node.type === "Identifier") { 77 | // Add the object name(chrome, browser, globalThis, ..etc). 78 | parts.unshift(node.name); 79 | break; 80 | } else { 81 | break; 82 | } 83 | } 84 | 85 | return [parts, ...partsCollection].filter((parts) => parts.length > 0); 86 | } 87 | 88 | function pickBrowserApi(partsCollection: PartsCollection): PartsCollection { 89 | const pickedCollection = partsCollection.map((parts) => { 90 | let objectName = parts.shift(); 91 | 92 | if (!objectName || !["chrome", "browser"].includes(objectName)) { 93 | return; 94 | } 95 | 96 | if (objectName === "chrome") { 97 | objectName = "browser"; 98 | } 99 | 100 | parts = [objectName, ...parts]; 101 | 102 | return parts; 103 | }); 104 | 105 | return pickedCollection.filter((parts) => !!parts); 106 | } 107 | 108 | export function filterBrowserApi( 109 | partsCollection: PartsCollection, 110 | ): BrowserApiItemCollection { 111 | const filteredCollection = partsCollection.map( 112 | (parts) => { 113 | if (!parts) { 114 | return; 115 | } 116 | 117 | const apiItem = distinguishBrowserApi(parts); 118 | 119 | /** 120 | * Exclude undefined item & Type API(like Enums) 121 | * @example 122 | * `browser.offscreen.Reason` 123 | */ 124 | if (!apiItem || apiItem.type === "type") { 125 | return; 126 | } 127 | 128 | /** 129 | * Trim target API for ignore. 130 | * @example 131 | * before 132 | * ["browser", "runtime", "onMessage", "addListener"] 133 | * after 134 | * ["browser", "runtime", "onMessage"] 135 | */ 136 | const TRIM_MEMBERS = ["addListener"]; 137 | const trimApiIndex = apiItem.parts.findLastIndex((name) => 138 | TRIM_MEMBERS.includes(name), 139 | ); 140 | if (trimApiIndex > 0) { 141 | apiItem.parts.splice(trimApiIndex); 142 | } 143 | 144 | if (apiItem.parts.length < 3) { 145 | return; 146 | } 147 | 148 | return apiItem; 149 | }, 150 | ); 151 | 152 | return filteredCollection.filter((apiItem) => !!apiItem); 153 | } 154 | 155 | /** 156 | * For some api namespaces consist of two identifiers like `browser.devtools.panels`. Checks for matching joined namespace. 157 | * And convert Parts to BrowserApiItem. 158 | */ 159 | export function distinguishBrowserApi( 160 | parts: Parts, 161 | ): BrowserApiItem | undefined { 162 | const getApiType = (namespace: string, propertyName: string): ApiType => { 163 | const apiTypes = EXTENSION_API_MAP[namespace]; 164 | 165 | if (apiTypes.methods.includes(propertyName)) { 166 | return "method"; 167 | } 168 | if (apiTypes.events.includes(propertyName)) { 169 | return "event"; 170 | } 171 | if (apiTypes.properties.includes(propertyName)) { 172 | return "property"; 173 | } 174 | if (apiTypes.types.includes(propertyName)) { 175 | return "type"; 176 | } 177 | return "unknown"; 178 | }; 179 | 180 | const twoNamespace = `${parts[1]}.${parts[2]}`; 181 | 182 | if (EXTENSION_API_MAP[twoNamespace]) { 183 | const propertyName = parts[3]; 184 | if (!propertyName) { 185 | return; 186 | } 187 | return { 188 | namespace: twoNamespace, 189 | propertyName, 190 | type: getApiType(twoNamespace, propertyName), 191 | parts, 192 | }; 193 | } else { 194 | const namespace = parts[1]; 195 | const propertyName = parts[2]; 196 | if (!propertyName) { 197 | return; 198 | } 199 | return { 200 | namespace, 201 | propertyName, 202 | type: getApiType(namespace, propertyName), 203 | parts, 204 | }; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /scripts/test/parse-browser-api.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { 3 | collectUsedBrowserApi, 4 | filterBrowserApi, 5 | distinguishBrowserApi, 6 | } from "../parse-browser-api"; 7 | 8 | describe("parse-browser-api", () => { 9 | describe("collectUsedBrowserApi", () => { 10 | it("should convert api chrome to browser", () => { 11 | const file = ` 12 | const a = chrome.action.getBadgeText(); 13 | const b = browser.action.setBadgeText(a); 14 | `; 15 | 16 | const result = collectUsedBrowserApi(file); 17 | expect(result).toStrictEqual( 18 | new Set(["browser.action.getBadgeText", "browser.action.setBadgeText"]), 19 | ); 20 | }); 21 | 22 | it("should also correctly return api that use optional chaining", () => { 23 | const file = ` 24 | const a = browser.action?.getBadgeText(); 25 | const b = browser.action?.setBadgeText(a); 26 | const c = browser.devtools?.panels?.create() 27 | `; 28 | 29 | const result = collectUsedBrowserApi(file); 30 | expect(result).toStrictEqual( 31 | new Set([ 32 | "browser.action.getBadgeText", 33 | "browser.action.setBadgeText", 34 | "browser.devtools.panels.create", 35 | ]), 36 | ); 37 | }); 38 | 39 | it("should not return non-browser API", () => { 40 | const file = ` 41 | const a = globalThis.wxt?.someMethod(b) 42 | const b = window.document.querySelector('body') 43 | const c = myObject.week.monday; 44 | `; 45 | 46 | const result = collectUsedBrowserApi(file); 47 | expect(result).toStrictEqual(new Set([])); 48 | }); 49 | 50 | it("should return correct apis for example code (normal)", () => { 51 | const file = ` 52 | export default defineBackground(() => { 53 | if (browser.runtime.getManifest().manifest_version === 2) { 54 | } 55 | const onClicked = 56 | browser.action?.onClicked ?? browser.browserAction.onClicked; 57 | 58 | let ports: Runtime.Port[] = []; 59 | browser.runtime.onConnect.addListener((port) => { 60 | ports.push(port); 61 | port.onDisconnect.addListener(() => { 62 | ports.splice(ports.indexOf(port), 1); 63 | }); 64 | }); 65 | 66 | browser.runtime.onMessage.addListener(async (message) => { 67 | const allTabs = await browser.tabs.query({}); 68 | return await Promise.all( 69 | alltabs.map(async (tab) => { 70 | const response = await browser.tabs.sendMessage(tab.id!, message); 71 | return { tab: tab.id, response }; 72 | }) 73 | ); 74 | }); 75 | }); 76 | `; 77 | 78 | const result = collectUsedBrowserApi(file); 79 | expect(result).toStrictEqual( 80 | new Set([ 81 | "browser.runtime.getManifest", 82 | "browser.action.onClicked", 83 | "browser.browserAction.onClicked", 84 | "browser.runtime.onConnect", 85 | "browser.runtime.onMessage", 86 | "browser.tabs.query", 87 | "browser.tabs.sendMessage", 88 | ]), 89 | ); 90 | }); 91 | }); 92 | 93 | it("should return correct apis for example code (special case)", () => { 94 | const file = ` 95 | const a = (browser.action ?? browser.browserAction).onClicked.addListener( 96 | () => { 97 | console.log("(parentheses) with Nullish coalescing"); 98 | } 99 | ); 100 | `; 101 | 102 | const result = collectUsedBrowserApi(file); 103 | expect(result).toStrictEqual( 104 | new Set(["browser.action.onClicked", "browser.browserAction.onClicked"]), 105 | ); 106 | }); 107 | 108 | describe("filterBrowserApi", () => { 109 | it("should return empty if no parts", () => { 110 | const result = filterBrowserApi([]); 111 | expect(result).toStrictEqual([]); 112 | }); 113 | 114 | it("should return empty if parts length less than 2", () => { 115 | const result = filterBrowserApi([["browser"], ["browser", "runtime"]]); 116 | expect(result).toStrictEqual([]); 117 | }); 118 | 119 | it("should return empty if two identifiers namespaces with no property", () => { 120 | const result = filterBrowserApi([["browser", "devtools", "panels"]]); 121 | expect(result).toStrictEqual([]); 122 | }); 123 | 124 | it("should be excluded if apiType is 'type'", () => { 125 | const result = filterBrowserApi([ 126 | ["browser", "offscreen", "Reason"], 127 | ["browser", "devtools", "panels", "Button"], 128 | ]); 129 | expect(result).toStrictEqual([]); 130 | }); 131 | 132 | it("should be trimmed the ignore api", () => { 133 | const result = filterBrowserApi([ 134 | ["browser", "tabs", "onCreated", "addListener"], 135 | ]); 136 | expect(result[0].parts).toStrictEqual(["browser", "tabs", "onCreated"]); 137 | }); 138 | 139 | it("should return unlimited depth parts", () => { 140 | const result = filterBrowserApi([ 141 | ["browser", "runtime", ...[...Array(48)].map((_, i) => `${i}`)], 142 | ]); 143 | expect(result[0].parts.length).toBe(50); 144 | }); 145 | }); 146 | 147 | describe("distinguishBrowserApi", () => { 148 | it("should return undefined if no property", () => { 149 | for (const parts of [ 150 | ["browser", "runtime"], 151 | ["browser", "devtools", "panels"], 152 | ]) { 153 | const result = distinguishBrowserApi(parts); 154 | expect(result).toBe(undefined); 155 | } 156 | }); 157 | 158 | it.each([ 159 | { 160 | parts: ["browser", "tabs", "captureVisibleTab"], 161 | expected: { 162 | namespace: "tabs", 163 | propertyName: "captureVisibleTab", 164 | type: "method", 165 | parts: ["browser", "tabs", "captureVisibleTab"], 166 | }, 167 | }, 168 | { 169 | parts: ["browser", "tabs", "onCreated", "addListener"], 170 | expected: { 171 | namespace: "tabs", 172 | propertyName: "onCreated", 173 | type: "event", 174 | parts: ["browser", "tabs", "onCreated", "addListener"], 175 | }, 176 | }, 177 | { 178 | parts: ["browser", "devtools", "panels", "Button"], 179 | expected: { 180 | namespace: "devtools.panels", 181 | propertyName: "Button", 182 | type: "type", 183 | parts: ["browser", "devtools", "panels", "Button"], 184 | }, 185 | }, 186 | { 187 | parts: [ 188 | "browser", 189 | "devtools", 190 | "panels", 191 | "elements", 192 | "createSidebarPane", 193 | ], 194 | expected: { 195 | namespace: "devtools.panels", 196 | propertyName: "elements", 197 | type: "property", 198 | parts: [ 199 | "browser", 200 | "devtools", 201 | "panels", 202 | "elements", 203 | "createSidebarPane", 204 | ], 205 | }, 206 | }, 207 | ])( 208 | "should convert parts to BrowserApiItem: $case", 209 | ({ parts, expected }) => { 210 | const result = distinguishBrowserApi(parts); 211 | expect(result).toStrictEqual(expected); 212 | }, 213 | ); 214 | }); 215 | }); 216 | -------------------------------------------------------------------------------- /scripts/update-metadata-json.ts: -------------------------------------------------------------------------------- 1 | import { readdir, readFile, writeFile } from "node:fs/promises"; 2 | import { execSync } from "node:child_process"; 3 | import { consola } from "consola"; 4 | import YAML from "yaml"; 5 | import { collectUsedBrowserApi } from "./parse-browser-api.js"; 6 | import { readFilesInDir, readJsFilesInDir } from "./utils/readFiles.js"; 7 | 8 | interface MetadataJson { 9 | examples: Array<{ 10 | name: string; 11 | description?: string; 12 | url: string; 13 | searchText: string; 14 | apis: string[]; 15 | packages: string[]; 16 | permissions: string[]; 17 | }>; 18 | allPackages: string[]; 19 | allPermissions: string[]; 20 | allApis: string[]; 21 | } 22 | 23 | const examples: MetadataJson["examples"] = []; 24 | const allPackages = new Set(); 25 | const allPermissions = new Set(); 26 | const allApis = new Set(); 27 | 28 | const ignoredPackages = new Set([ 29 | "wxt", 30 | "typescript", 31 | "vue-tsc", 32 | "svelte-check", 33 | "tslib", 34 | "@tsconfig/svelte", 35 | ]); 36 | const ignoredPackagePrefixes = ["@types", "@storybook", "@chromatic-com"]; 37 | 38 | const includedBundleImports = ["wxt/utils/storage"]; 39 | 40 | consola.info("Building all extensions..."); 41 | execSync(`pnpm -r build`); 42 | 43 | consola.info("Processing examples..."); 44 | const exampleDirs = (await readdir("examples")).map((dir) => `examples/${dir}`); 45 | 46 | function collectPermissions(manifest: any) { 47 | const permissions: string[] = manifest.permissions ?? []; 48 | permissions.forEach((permission) => allPermissions.add(permission)); 49 | return permissions; 50 | } 51 | 52 | function collectPackages(packageJson: any, files: Record) { 53 | const packages = [ 54 | ...Object.keys(packageJson.dependencies ?? {}), 55 | ...Object.keys(packageJson.devDependencies ?? {}), 56 | ].filter( 57 | (pkg) => 58 | !ignoredPackages.has(pkg) && 59 | !ignoredPackagePrefixes.some((prefix) => pkg.startsWith(prefix)), 60 | ); 61 | Object.values(files).forEach((file) => { 62 | includedBundleImports.forEach((bundleImport) => { 63 | const bundleImportMatch = `from "${bundleImport}"`; 64 | if (file.includes(bundleImportMatch)) { 65 | packages.push(bundleImport); 66 | } 67 | }); 68 | }); 69 | packages.forEach((pkg) => allPackages.add(pkg)); 70 | return [...new Set(packages)]; 71 | } 72 | 73 | async function collectApis(files: Record) { 74 | const dirApis = new Set(); 75 | for (const textContent of Object.values(files)) { 76 | const apis = collectUsedBrowserApi(textContent); 77 | apis.forEach((api) => { 78 | dirApis.add(api); 79 | allApis.add(api); 80 | }); 81 | } 82 | return [...dirApis]; 83 | } 84 | 85 | function extractFrontmatter(readmeText: string): any { 86 | return YAML.parse(readmeText.split("---")[1].trim()); 87 | } 88 | 89 | for (const exampleDir of exampleDirs) { 90 | consola.log(` - \`${exampleDir}\``); 91 | 92 | const packageJsonPath = `${exampleDir}/package.json`; 93 | const packageJsonText = await readFile(packageJsonPath, "utf8").catch( 94 | () => void 0, 95 | ); 96 | if (packageJsonText == null) { 97 | consola.warn("Skipped, not found:", packageJsonPath); 98 | continue; 99 | } 100 | const packageJson = JSON.parse(packageJsonText); 101 | 102 | const readmePath = `${exampleDir}/README.md`; 103 | const readmeText = await readFile(readmePath, "utf8").catch(() => void 0); 104 | if (readmeText == null) { 105 | consola.warn("Skipped, not found:", readmePath); 106 | continue; 107 | } 108 | 109 | const manifestPath = `${exampleDir}/.output/chrome-mv3/manifest.json`; 110 | const manifestText = await readFile(manifestPath, "utf8").catch(() => void 0); 111 | if (manifestText == null) { 112 | consola.warn("Skipped, not found:", manifestPath); 113 | continue; 114 | } 115 | const manifest = JSON.parse(manifestText); 116 | 117 | const frontmatter = extractFrontmatter(readmeText); 118 | const dirContent = await readFilesInDir(exampleDir); 119 | const dirJsContent = await readJsFilesInDir(exampleDir); 120 | const packages = collectPackages(packageJson, dirContent); 121 | const permissions = collectPermissions(manifest); 122 | const apis = await collectApis(dirJsContent); 123 | frontmatter.apis?.forEach((api: string) => { 124 | allApis.add(api); 125 | apis.push(api); 126 | }); 127 | frontmatter.packages?.forEach((pkg: string) => { 128 | allPackages.add(pkg); 129 | packages.push(pkg); 130 | }); 131 | frontmatter.permissions?.forEach((permission: string) => { 132 | allPermissions.add(permission); 133 | permissions.push(permission); 134 | }); 135 | 136 | examples.push({ 137 | name: frontmatter.name, 138 | description: frontmatter.description, 139 | searchText: [ 140 | frontmatter.name, 141 | frontmatter.description ?? "", 142 | ...packages, 143 | ...permissions, 144 | ...apis, 145 | ].join("|"), 146 | url: `https://github.com/wxt-dev/examples/tree/main/${exampleDir}`, 147 | apis, 148 | packages, 149 | permissions, 150 | }); 151 | } 152 | 153 | const metadataJson: MetadataJson = { 154 | examples, 155 | allPackages: [...allPackages].sort(), 156 | allPermissions: [...allPermissions].sort(), 157 | allApis: [...allApis].sort(), 158 | }; 159 | 160 | consola.info(`Writing ${examples.length} examples to \`metadata.json\`...`); 161 | await writeFile( 162 | "metadata.json", 163 | JSON.stringify(metadataJson, null, 2) + "\n", 164 | "utf8", 165 | ); 166 | consola.success("Done!"); 167 | -------------------------------------------------------------------------------- /scripts/utils/readFiles.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "node:fs/promises"; 2 | import glob from "fast-glob"; 3 | 4 | async function _readFilesInDir( 5 | globPattern: string, 6 | ): Promise> { 7 | const files = await glob(globPattern, { 8 | ignore: ["**/node_modules/**", "**/package.json"], 9 | }); 10 | const result: Record = {}; 11 | for (const file of files) { 12 | result[file] = await readFile(file, "utf8"); 13 | } 14 | return result; 15 | } 16 | 17 | export function readFilesInDir(dir: string) { 18 | return _readFilesInDir(`${dir}/**`); 19 | } 20 | export function readJsFilesInDir(dir: string) { 21 | return _readFilesInDir(`${dir}/**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}`); 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "noEmit": true, 6 | "esModuleInterop": true, 7 | "moduleResolution": "Bundler", 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "skipLibCheck": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defaultExclude, defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | exclude: [...defaultExclude, "examples/**"], 6 | }, 7 | }); 8 | --------------------------------------------------------------------------------