diff --git a/README.md b/README.md index 3808327c..295b4d77 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get - 🦾 Auto-imports - 🤖 Automated publishing - 🎨 Frontend framework agnostic: works with Vue, React, Svelte, etc -- 📦 Modular architecture with [WXT modules](https://wxt.dev/guide/go-further/reusable-modules.html#overview) +- 📦 [Module system](https://wxt.dev/guide/essentials/wxt-modules.html#overview) for reusing code between extensions - 🖍️ Quickly bootstrap a new project - 📏 Bundle analysis - ⬇️ Download and bundle remote URL imports diff --git a/docs/.old/development.md b/docs/.old/development.md deleted file mode 100644 index 50098add..00000000 --- a/docs/.old/development.md +++ /dev/null @@ -1,116 +0,0 @@ -# Development - -WXT's main goal is providing the best DX it possibly can. When running your extension in dev mode, each part of your extension is reloaded separately when possible. - -| | HMR | Reloaded individually | Reload extension | Restart browser | -| ------------------- | :-: | :-------------------: | :--------------: | :----------------------------------------------------: | -| HTML File | | ✅ | -| HTML Dependency | ✅ | -| MV3 Content Script | | ✅ | -| MV2 Content Script | | | ✅ | -| Background | | | ✅ | -| manifest.json | | | | 🟡 See [#16](https://github.com/wxt-dev/wxt/issues/16) | -| `wxt.config.ts` | | | | 🟡 See [#10](https://github.com/wxt-dev/wxt/issues/10) | -| `web-ext.config.ts` | | | | 🟡 See [#10](https://github.com/wxt-dev/wxt/issues/10) | - -## Dev Mode vs Production Builds - -There are some notable differences between the development and production versions of an extension. During development: - -1. **Content scripts are not listed in the `manifest.json`** when targeting MV3. Instead, the [`scripting`](https://developer.chrome.com/docs/extensions/reference/api/scripting) permission is used to register content scripts at runtime so they can be reloaded individually. - - To get the list of content scripts during development, run the following in the background's console: - - ```ts - await chrome.scripting.getRegisteredContentScripts(); - ``` - -2. **The CSP is modified to allow loading scripts from the dev server**. Make sure you're using Chrome v110 or above for HMR to work. - -3. If you don't include a background script/service worker, one will be created to perform various tasks in dev mode, mostly related to reloading different parts of the extension on change. - -For production builds, none of the above modifications will be applied, and you're extension/manifest will only include what you have defined. - -## Configure Browser Startup - -WXT uses [`web-ext` by Mozilla](https://github.com/mozilla/web-ext) to automatically open a browser with the extension installed. You can configure the runner's behavior via the [`runner`](/api/reference/wxt/interfaces/InlineConfig#runner) option, or in a separate gitignored file, `web-ext.config.ts`. - -:::code-group - -```ts [wxt.config.ts] -import { defineConfig } from 'wxt'; - -export default defineConfig({ - runner: { - // Runner config - }, -}); -``` - -```ts [web-ext.config.ts] -import { defineRunnerConfig } from 'wxt'; - -export default defineRunnerConfig({ - // Runner config -}); -``` - -::: - -### Browser Binaries - -`web-ext`'s browser discovery is very limited. By default, it only guesses at where Chrome and Firefox are installed. If you've customized your install locations, you may need to tell `web-ext` where the binaries/executables are located using the [`binaries` option](/api/reference/wxt/interfaces/ExtensionRunnerConfig#binaries). For other Chromium based browsers, like Edge or Opera, you'll need to explicitly list them in the `binaries` option as well, otherwise they will open in Chrome by default. - -```ts -// ~/web-ext.config.ts -import { defineRunnerConfig } from 'wxt'; - -export default defineRunnerConfig({ - binaries: { - chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome - firefox: 'firefoxdeveloperedition', // Use Firefox Developer Edition instead of regular Firefox - edge: '/path/to/edge', // Open MS Edge when running "wxt -b edge" - }, -}); -``` - -:::tip -When configuring browser binaries, it's helpful to put them in `~/web-ext.config.ts` instead of the project directory's `web-ext.config.ts` file. When placed in your home directory (`~/`), this config will be used by all WXT projects, so you only need to configure the binaries once. -::: - -### Other options - -You can customize other options as well, like startup URLs, profiles, or additional command line arguments: - -```ts -// web-ext.config.ts -import { defineRunnerConfig } from 'wxt'; - -export default defineRunnerConfig({ - startUrls: ['https://google.com', 'https://duckduckgo.com'], - chromiumProfile: '/path/to/profile/to/use', - chromiumArgs: ['--window-size=400,300'], -}); -``` - -For a full list of options, see the [API Reference](/api/reference/wxt/interfaces/ExtensionRunnerConfig). - -## Reload the Extension - -Normally, to manually reload an extension, you have to visit `chrome://extensions` and click the reload button for your extension. - -When running `wxt` command to start the dev server, WXT adds a keyboard shortcut `Alt+R`, that reloads the extension when pressed, without visiting `chrome://extensions`. This can also be customized or disabled: - -```ts [wxt.config.ts] -import { defineConfig } from 'wxt'; - -export default defineConfig({ - dev: { - reloadCommand: 'Alt+T', // false, to disable - }, -}); -``` - -:::info -This shortcut is only available during development, and is not be added to your extension when running `wxt build` or `wxt-zip`. -::: diff --git a/docs/.vitepress/components/EntrypointPatterns.vue b/docs/.vitepress/components/EntrypointPatterns.vue index 0600a066..8f73d7c2 100644 --- a/docs/.vitepress/components/EntrypointPatterns.vue +++ b/docs/.vitepress/components/EntrypointPatterns.vue @@ -8,14 +8,14 @@ const props = defineProps<{ - + - - diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 33a97536..87f398dc 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -8,6 +8,9 @@ import { prepareTypedocSidebar, } from './utils/menus'; import { meta, script } from './utils/head'; +import { version as wxtVersion } from '../../packages/wxt/package.json'; +import { version as i18nVersion } from '../../packages/i18n/package.json'; +import { version as autoIconsVersion } from '../../packages/auto-icons/package.json'; const title = 'Next-gen Web Extension Framework'; const titleSuffix = ' – WXT'; @@ -64,111 +67,93 @@ export default defineConfig({ ], nav: [ - navItem('Get Started', '/get-started/introduction'), - navItem('Guide', '/guide/key-concepts/manifest'), - navItem('API', '/api/reference/wxt'), + navItem('Guide', '/guide/installation'), navItem('Examples', '/examples'), + navItem('API', '/api/reference/wxt'), + navItem(`v${wxtVersion}`, [ + navItem('wxt', [ + navItem(`v${wxtVersion}`, '/'), + navItem( + `Changelog`, + 'https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md', + ), + ]), + navItem('Other Packages', [ + navItem(`wxt/storage — ${wxtVersion}`, '/storage'), + navItem(`@wxt-dev/auto-icons — ${autoIconsVersion}`, '/auto-icons'), + navItem(`@wxt-dev/i18n — ${i18nVersion}`, '/i18n'), + ]), + ]), ], sidebar: { - '/get-started/': menuRoot([ - menuGroup('Get Started', '/get-started/', [ - menuItem('Introduction', 'introduction'), - menuItem('Installation', 'installation'), - menuItem('Configuration', 'configuration'), - menuItem('Entrypoints', 'entrypoints'), - menuItem('Assets', 'assets'), - menuItem('Publishing', 'publishing'), - menuItem('Migrate to WXT', 'migrate-to-wxt'), - menuItem('Compare', 'compare'), - ]), - ]), '/guide/': menuRoot([ - menuGroup('Key Concepts', '/guide/key-concepts/', [ - menuItem('Manifest', 'manifest'), - menuItem('Auto-imports', 'auto-imports'), - menuItem('Web Extension Polyfill', 'web-extension-polyfill'), - menuItem('Frontend Frameworks', 'frontend-frameworks'), - menuItem('Content Script UI', 'content-script-ui'), - ]), - menuGroup('Directory Structure', '/guide/directory-structure/', [ - // Folders - menuItem('.output/', 'output'), - menuItem('.wxt/', 'wxt'), - menuItem('assets/', 'assets'), - menuItem('components/', 'components'), - menuItem('composables/', 'composables'), - menuGroup('entrypoints/', '/guide/directory-structure/entrypoints/', [ - menuItem('background', 'background.md'), - menuItem('bookmarks', 'bookmarks.md'), - menuItem('*.content.ts', 'content-scripts.md'), - menuItem('*.css', 'css.md'), - menuItem('devtools', 'devtools.md'), - menuItem('history', 'history.md'), - menuItem('newtab', 'newtab.md'), - menuItem('options', 'options.md'), - menuItem('popup', 'popup.md'), - menuItem('sandbox', 'sandbox.md'), - menuItem('sidepanel', 'sidepanel.md'), - menuItem('*.html', 'unlisted-pages.md'), - menuItem('*.ts', 'unlisted-scripts.md'), - ]), - menuItem('hooks/', 'hooks'), - menuItem('public/', 'public/', [ - menuItem('_locales/', 'public/locales'), - ]), - menuItem('utils/', 'utils'), - - // Files - menuItem('.env', 'env'), - menuItem('app.config.ts', 'app-config'), - menuItem('package.json', 'package'), - menuItem('tsconfig.json', 'tsconfig'), - menuItem('web-ext.config.ts', 'web-ext-config'), - menuItem('wxt.config.ts', 'wxt-config'), - ]), - menuGroup('Extension APIs', '/guide/extension-apis/', [ - menuItem('Storage', 'storage'), - menuItem('Messaging', 'messaging'), - menuItem('I18n', 'i18n'), - menuItem('Scripting', 'scripting'), - menuItem('Others', 'others'), - ]), - menuGroup('Go Further', '/guide/go-further/', [ - menuItem('Testing', 'testing'), - menuItem('ES Modules', 'es-modules'), - menuItem('Debugging', 'debugging'), - menuItem('Handling Updates', 'handling-updates'), - menuItem('Vite', 'vite'), - menuItem('Custom Events', 'custom-events'), - menuItem('Reusable Modules', 'reusable-modules'), - menuItem('Remote Code', 'remote-code'), - menuItem('Entrypoint Loaders', 'entrypoint-loaders'), - menuItem('How WXT Works', 'how-wxt-works'), - ]), - menuGroup('Upgrade Guide', '/guide/upgrade-guide/', [ - menuItem('wxt', 'wxt'), - ]), - menuGroup('@wxt-dev/i18n', '/guide/i18n/', [ + menuGroup('Get Started', '/guide/', [ menuItem('Introduction', 'introduction.md'), menuItem('Installation', 'installation.md'), - menuItem('Messages File Format', 'messages-file-format.md'), - menuItem('Build Integrations', 'build-integrations.md'), - menuItem('Editor Support', 'editor-support.md'), + ]), + menuGroup('Essentials', '/guide/essentials/', [ + menuItem('Project Structure', 'project-structure.md'), + menuItem('Entrypoints', 'entrypoints.md'), + menuGroup( + 'Configuration', + '/guide/essentials/config/', + [ + menuItem('Manifest', 'manifest.md'), + menuItem('Browser Startup', 'browser-startup.md'), + menuItem('Auto-imports', 'auto-imports.md'), + menuItem('Environment Variables', 'environment-variables.md'), + menuItem('Runtime Config', 'runtime.md'), + menuItem('Vite', 'vite.md'), + menuItem('Build Mode', 'build-mode.md'), + menuItem('TypeScript', 'typescript.md'), + menuItem('Hooks', 'hooks.md'), + menuItem('Entrypoint Loaders', 'entrypoint-loaders.md'), + ], + true, + ), + menuItem('Extension APIs', 'extension-apis.md'), + menuItem('Assets', 'assets.md'), + menuItem('Target Different Browsers', 'target-different-browsers.md'), + menuItem('Content Scripts', 'content-scripts.md'), + menuItem('Storage', 'storage.md'), + menuItem('Messaging', 'messaging.md'), + menuItem('I18n', 'i18n.md'), + menuItem('Scripting', 'scripting.md'), + menuItem('WXT Modules', 'wxt-modules.md'), + menuItem('Frontend Frameworks', 'frontend-frameworks.md'), + menuItem('ES Modules', 'es-modules.md'), + menuItem('Remote Code', 'remote-code.md'), + menuItem('Unit Testing', 'unit-testing.md'), + menuItem('E2E Testing', 'e2e-testing.md'), + menuItem('Publishing', 'publishing.md'), + menuItem('Testing Updates', 'testing-updates.md'), + ]), + menuGroup('Resources', '/guide/resources/', [ + menuItem('Compare', 'compare.md'), + menuItem('FAQ', 'faq.md'), + menuItem('Upgrading WXT', 'upgrading.md'), + menuItem('Migrate to WXT', 'migrate.md'), + menuItem('How WXT Works', 'how-wxt-works.md'), ]), ]), '/api/': menuRoot([ - menuGroup('CLI', '/api/cli/', [ - menuItem('wxt', 'wxt.md'), - menuItem('wxt build', 'wxt-build.md'), - menuItem('wxt zip', 'wxt-zip.md'), - menuItem('wxt prepare', 'wxt-prepare.md'), - menuItem('wxt clean', 'wxt-clean.md'), - menuItem('wxt init', 'wxt-init.md'), - menuItem('wxt submit', 'wxt-submit.md'), - menuItem('wxt submit init', 'wxt-submit-init.md'), - ]), - menuGroup('API Reference', prepareTypedocSidebar(typedocSidebar)), + menuGroup( + 'CLI Reference', + '/api/cli/', + [ + menuItem('wxt', 'wxt.md'), + menuItem('wxt build', 'wxt-build.md'), + menuItem('wxt zip', 'wxt-zip.md'), + menuItem('wxt prepare', 'wxt-prepare.md'), + menuItem('wxt clean', 'wxt-clean.md'), + menuItem('wxt init', 'wxt-init.md'), + menuItem('wxt submit', 'wxt-submit.md'), + menuItem('wxt submit init', 'wxt-submit-init.md'), + ], + true, + ), + menuGroup('API Reference', prepareTypedocSidebar(typedocSidebar), true), ]), }, }, diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index 1d249f5a..a7aa5980 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -85,23 +85,6 @@ body { user-select: none; } -.VPSidebarItem.level-0.collapsible { - padding-bottom: 0.5rem; -} - -.VPSidebarItem.level-0.collapsible .items { - /*border-left: 1px solid var(--vp-c-divider);*/ -} - -.VPSidebarItem.level-1 { - padding-left: 0.75rem; -} - -.VPSidebar .group + .group { - border-top: none; - padding-top: 0; -} - .VPSidebarItem .badge { display: inline-block; min-width: 1.6em; diff --git a/docs/.vitepress/utils/menus.ts b/docs/.vitepress/utils/menus.ts index aad00c56..8395cabb 100644 --- a/docs/.vitepress/utils/menus.ts +++ b/docs/.vitepress/utils/menus.ts @@ -2,9 +2,20 @@ import { DefaultTheme } from 'vitepress'; type SidebarItem = DefaultTheme.SidebarItem; type NavItem = DefaultTheme.NavItem; +type NavItemWithLink = DefaultTheme.NavItemWithLink; +type NavItemWithChildren = DefaultTheme.NavItemWithChildren; +type NavItemChildren = DefaultTheme.NavItemChildren; -export function navItem(text: string, link: string): NavItem { - return { text, link }; +export function navItem(text: string): NavItemChildren; +export function navItem(text: string, link: string): NavItemChildren; +export function navItem(text: string, items: any[]): NavItemWithChildren; +export function navItem(text: string, arg2?: unknown): any { + if (typeof arg2 === 'string') { + return { text, link: arg2 }; + } else if (Array.isArray(arg2)) { + return { text, items: arg2 }; + } + return { text }; } export function menuRoot(items: SidebarItem[]) { @@ -14,31 +25,39 @@ export function menuRoot(items: SidebarItem[]) { }); } -export function menuGroup(text: string, items: SidebarItem[]): SidebarItem; +export function menuGroup( + text: string, + items: SidebarItem[], + collapsable?: boolean, +): SidebarItem; export function menuGroup( text: string, base: string, items: SidebarItem[], + collapsable?: boolean, ): SidebarItem; export function menuGroup( text: string, a: string | SidebarItem[], - b?: SidebarItem[], + b?: SidebarItem[] | boolean, + c?: boolean, ): SidebarItem { - const collapsed = true; - if (typeof a === 'string') { + if (typeof a === 'string' && Array.isArray(b)) { return { text, base: a, items: b, - collapsed, + collapsed: c, }; } - return { - text, - items: a, - collapsed, - }; + if (typeof a !== 'string' && !Array.isArray(b)) + return { + text, + items: a, + collapsed: b, + }; + + throw Error('Unknown overload'); } export function menuItems(items: SidebarItem[]) { diff --git a/docs/assets/init-demo.gif b/docs/assets/init-demo.gif index 474db34c..353c8b73 100644 Binary files a/docs/assets/init-demo.gif and b/docs/assets/init-demo.gif differ diff --git a/docs/auto-icons.md b/docs/auto-icons.md new file mode 100644 index 00000000..474e842c --- /dev/null +++ b/docs/auto-icons.md @@ -0,0 +1 @@ + diff --git a/docs/get-started/assets.md b/docs/get-started/assets.md deleted file mode 100644 index f8ef639a..00000000 --- a/docs/get-started/assets.md +++ /dev/null @@ -1,33 +0,0 @@ -# Assets - -WXT has two directories for storing assets like CSS, images, or fonts. - -- [public directory](/guide/directory-structure/public/): Store files that will be copied into the output directory as-is -- [assets directory](/guide/directory-structure/assets): Store files that will be processed by Vite during the build process - -To learn more about how to use assets at runtime from either of these directories, visit their guides linked above. - -## Public Directory - -Place static files like the extension icon or `_locales/` directory here. These files will be copied over to the output directory without being transformed by Vite. - -``` - -└─ public/ - ├─ icon-16.png - ├─ icon-32.png - ├─ icon-48.png - ├─ icon-96.png - └─ icon-128.png -``` - -## Assets Directory - -Files in the assets directory will be processed by Vite. They are imported in your source code, and will be transformed or renamed in the output directory. - -``` - -└─ assets/ - ├─ tailwind.css - └─ illustration.svg -``` diff --git a/docs/get-started/compare.md b/docs/get-started/compare.md deleted file mode 100644 index 37e9e9ea..00000000 --- a/docs/get-started/compare.md +++ /dev/null @@ -1,47 +0,0 @@ -# Compare - -Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework) (another framework) and [CRXJS](https://crxjs.dev/vite-plugin) (a bundler plugin). - -## Overview - -| Features | WXT | Plasmo | CRXJS | -| ---------------------------------------------------- | :--------------: | :-------------: | :--------------: | -| Supports all browsers | ✅ | ✅ | 🟡 10 | -| MV2 Support | ✅ | ✅ | 🟡 1 | -| MV3 Support | ✅ | ✅ | 🟡 1 | -| Create Extension ZIPs | ✅ | ✅ | ❌ | -| Create Firefox Sources ZIP | ✅ | ❌ | ❌ | -| First-class TypeScript support | ✅ | ✅ | ✅ | -| Entrypoint discovery | ✅ 2 | ✅ 2 | ❌ | -| Inline entrypoint config | ✅ | ✅ | ❌ 9 | -| Auto-imports | ✅ | ❌ | ❌ | -| Supports all frontend frameworks | ✅ | 🟡 3 | ✅ | -| Framework specific entrypoints (like `Popup.tsx`) | 🟡 4 | ✅ 5 | ❌ | -| Automated publishing | ✅ | ✅ | ❌ | -| Remote Code Bundling (Google Analytics) | ✅ | ✅ | ❌ | -| Dev Mode | | | -| `.env` Files | ✅ | ✅ | ✅ | -| Opens browser with extension installed | ✅ | ❌ | ❌ | -| HMR for UIs | ✅ | 🟡 6 | ✅ | -| Reload HTML Files on Change | ✅ | 🟡 7 | ✅ | -| Reload Content Scripts on Change | ✅ | 🟡 7 | ✅ | -| Reload Background on Change | 🟡 7 | 🟡 7 | 🟡 7 | -| Respects Content Script `run_at` | ✅ | ✅ | ❌ 8 | -| Built-in Utils | | | | -| Storage | ✅ | ✅ | ❌ 11 | -| Messaging | ❌ 11 | ✅ | ❌ 11 | -| Content Script UI | ✅ | ✅ | ❌ 11 | - - - 1: Either MV2 or MV3, not both. -
2: File based. -
3: Only React, Vue, and Svelte. -
4: .html .ts .tsx. -
5: .html .ts .tsx. .vue .svelte. -
6: React only. -
7: Reloads entire extension. -
8: ESM-style loaders run asynchronously. -
9: Entrypoint options all configured in `manifest.json`. -
10: As of v2.0.0-beta.23, but v2 stable hasn't been released yet. -
11: There is no built-in wrapper around this API. However, you can still access the standard APIs via chrome/browser globals or use any 3rd party NPM package. -
diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md deleted file mode 100644 index 04cb62ef..00000000 --- a/docs/get-started/configuration.md +++ /dev/null @@ -1,57 +0,0 @@ -# Configuration - -By default, WXT provides sensible configuration for bundling web extensions with Vite. - -## Config File - -To configure WXT, create a `wxt.config.ts` file in your project root. It should have the following contents: - -```ts -import { defineConfig } from 'wxt'; - -export default defineConfig({ - // My WXT config -}); -``` - -:::info -For more information on configuring WXT via the `wxt.config.ts` file, read the dedicated [`wxt.config.ts` guide](/guide/directory-structure/wxt-config). -::: - -## Manifest.json - -WXT generates your extension's `manifest.json` based on the project structure. To add additional properties, like permissions, use the [`manifest` property](/api/reference/wxt/interfaces/InlineConfig#manifest). - -```ts -import { defineConfig } from 'wxt'; - -export default defineConfig({ - manifest: { - permissions: ['storage'], - }, -}); -``` - -:::info -For more information on configuring the manifest, read the dedicated [Manifest guide](/guide/key-concepts/manifest). -::: - -## Environment - -WXT can read `.env` files, and variables are accessible via `import.meta.env.*`. - -:::code-group - -```sh [.env] -VITE_OAUTH_CLIENT_ID=abc123 -``` - -```ts [JS] -import.meta.env.VITE_OAUTH_CLIENT_ID; -``` - -::: - -:::info -For more information on using .env files, read the dedicated [`.env` guide](/guide/directory-structure/env). -::: diff --git a/docs/get-started/entrypoints.md b/docs/get-started/entrypoints.md deleted file mode 100644 index 65b7bf08..00000000 --- a/docs/get-started/entrypoints.md +++ /dev/null @@ -1,60 +0,0 @@ -# Entrypoints - -An "entrypoint" is any HTML, JS, or CSS file that needs to be bundled and included with your extension, which will be loaded and executed by the browser. - -## Defining Entrypoints - -In WXT, you create an entrypoint by adding a file to the `entrypoints/` directory. - -``` - -└─ entrypoints/ - ├─ background.ts - ├─ content.ts - ├─ injected.ts - └─ popup.html -``` - -Some entrypoint filesname patterns are reserved by WXT and effect how the manifest is generated. - -- `popup` adds an `action` to the manifest -- `background` adds a background script/service worker -- `*.content.ts` adds a content script -- ... - -> For a full list of recognized filenames, see the the [Entrypoints Directory guide](/guide/directory-structure/entrypoints/background). - -Any other files, whether JS, CSS, or HTML, is considered "unlisted". Unlisted files, like `injected.ts` from above, are just bundled to the output directory and not added to the manifest. You can still access or load them at runtime. - -## Entrypoint Options - -Most entrypoints allow customizing their options in the file you define them in. This differs from regular web extension development, where all options are placed in the `manifest.json`. - -WXT looks for custom options in the entrypoint, and adds them to the manifest when generated. - -In HTML files, options are listed as `meta` tags: - -```html - - - - - - -``` - -In TS files, options are apart of the file's default export: - -```ts -export default defineContentScript({ - matches: ['*://*.google.com/*'], - runAt: 'document_start', - main() { - // ... - }, -}); -``` - -:::info -All options for each entrypoint type is listed in the [entrypoints directory docs](/guide/directory-structure/entrypoints/background). -::: diff --git a/docs/get-started/installation.md b/docs/get-started/installation.md deleted file mode 100644 index 7bf4765c..00000000 --- a/docs/get-started/installation.md +++ /dev/null @@ -1,120 +0,0 @@ -# Installation - -Bootstrap a new project, start from scratch, or [migrate an existing project](/get-started/migrate-to-wxt). - -## Bootstrap Project - -:::code-group - -```sh [pnpm] -pnpm dlx wxt@latest init -``` - -```sh [npm] -npx wxt@latest init -``` - -```sh [bun] -# The "wxt init" command currently fails when ran with bunx. -# Use NPX as a workaround, and select "bun" as your package -# manager. To stay up to date with this issue, follow -# https://github.com/wxt-dev/wxt/issues/707 -# -# bunx wxt@latest init - -npx wxt@latest init -``` - -### Demo - -![wxt init demo](/assets/init-demo.gif) - -::: - -There are several starting templates available. - -| TypeScript | -| ------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [`vanilla`](https://github.com/wxt-dev/wxt/tree/main/templates/vanilla) | -| [`vue`](https://github.com/wxt-dev/wxt/tree/main/templates/vue) | -| [`react`](https://github.com/wxt-dev/wxt/tree/main/templates/react) | -| [`svelte`](https://github.com/wxt-dev/wxt/tree/main/templates/svelte) | -| [`solid`](https://github.com/wxt-dev/wxt/tree/main/templates/solid) | - -:::info -All templates default to TypeScript. Rename the file extensions to `.js` to use JavaScript instead. -::: - -## From Scratch - -Initialize a project and install `wxt`: - -:::code-group - -```sh [pnpm] -pnpm init -pnpm add -D wxt -``` - -```sh [npm] -npm init -npm i --save-dev wxt -``` - -```sh [yarn] -yarn init -yarn add --dev wxt -``` - -```sh [bun] -bun init -bun add --dev wxt -``` - -::: - -Add your first entrypoint: - -```ts -// entrypoints/background.ts -export default defineBackground(() => { - console.log(`Hello from ${browser.runtime.id}!`); -}); -``` - -And add scripts to your `package.json`: - -```json -{ - "scripts": { - "dev": "wxt", // [!code ++] - "dev:firefox": "wxt --browser firefox", // [!code ++] - "build": "wxt build", // [!code ++] - "build:firefox": "wxt build --browser firefox", // [!code ++] - "zip": "wxt zip", // [!code ++] - "zip:firefox": "wxt zip --browser firefox", // [!code ++] - "postinstall": "wxt prepare" // [!code ++] - } -} -``` - -## Development - -Once the project is setup, you can start the development server using the `dev` script. - -```sh -pnpm dev -``` - -:::tip 🎉 Well done! -The dev command will build the extension for development, open the browser, and reload the different parts of the extension when you save changes. -::: - -## Next Steps - -You're ready to build your web extension! - -- Read the rest of the "Get Started" pages for a high-overview of what WXT can do -- Read the [Guide](/guide/key-concepts/manifest) to learn in-depth about each feature WXT supports -- [Configure WXT](./configuration) by creating a `wxt.config.ts` file -- Checkout [example projects](https://github.com/wxt-dev/examples) to see how to perform common tasks with WXT diff --git a/docs/get-started/introduction.md b/docs/get-started/introduction.md deleted file mode 100644 index 1a66b8c6..00000000 --- a/docs/get-started/introduction.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -head: - - - link - - rel: canonical - href: https://wxt.dev ---- - -# Introduction - -## Overview - -WXT is a free and open source framework for building web extensions in a conventional, intuitive, and safe way **_for all browsers_**. - -WXT is based on [Nuxt](https://nuxt.com), and aims to provide the same great DX with TypeScript, auto-imports, and an opinionated project structure. - -![Example build output](../assets/cli-output.png) - -## Conventions - -WXT is an opinionated framework. This helps keep projects consistent and easy to pick up. - -- **Generated manifest**: Based on your project's file structure -- **Entrypoint configuration**: Configure entrypoints from the same file they're declare in -- **Type-safety is a priority**: Out-of-the-box TypeScript support with improved browser API typing -- **Simple output file structure**: Output file paths minimize the path at runtime - -## Development - -WXT's dev server supports modern features like HMR to provide a lighting fast dev mode. - -When changes can't be hot-reloaded, like content scripts or background scripts, they're reloaded individually to prevent reloading the entire extension and slowing down your development cycle. - -## Production-ready - -Production builds are optimized for store review, changing as few files as possible between builds. - -In addition, WXT fully supports Firefox's source code requirements when using a bundler. It will automatically create and upload a ZIP file of your source code. - -:::info -See [Publishing](./publishing) for more info around production builds. -::: - -## New to Extension Development? - -Most of these docs assume you have a basic understanding of how to write a chrome or web extension. - -If you've never written a web extension before or need a refresher, follow Google's ["Hello, World!" tutorial](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world) to understand the basics. diff --git a/docs/guide/directory-structure/app-config.md b/docs/guide/directory-structure/app-config.md deleted file mode 100644 index e5c2582c..00000000 --- a/docs/guide/directory-structure/app-config.md +++ /dev/null @@ -1,61 +0,0 @@ -# `/app.config.ts` - -:::warning Nuxt Users -If you're familiar with Nuxt, this file is meant to be a direct equivalent to Nuxt's `app.config.ts` file. - -However, some of Nuxt's features, like overriding the app config based on a `.env` file or automatically generating the config's types, are not implemented. They are planned, just not implemented yet. Feel free to open a PR! -::: - -## Overview - -Define runtime configuration in a single place. - -```ts -// /app.config.ts -import { defineAppConfig } from 'wxt/sandbox'; - -// Define types for your config -declare module 'wxt/sandbox' { - export interface WxtAppConfig { - theme?: 'light' | 'dark'; - } -} - -export default defineAppConfig({ - theme: 'dark', -}); -``` - -Then access the config in your extension by calling `useAppConfig`: - -```ts -console.log(useAppConfig()); // { theme: "dark" } -``` - -## Environment Variables - -If you have a `.env` file, you can access any variables defined in it here. You can convert them to better types (like booleans), add types for them, or leave them as is. - -```txt -# .env -VITE_BUG_REPORTING_DISABLED=true -VITE_API_KEY=... -``` - -```ts -// /app.config.ts - -declare module 'wxt/sandbox' { - export interface WxtAppConfig { - bugReportingDisabled: boolean; - apiKey?: string; - } -} - -export default defineAppConfig({ - bugReportingDisabled: import.meta.env.VITE_BUG_REPORTING_DISABLED === 'true', - apiKey: import.meta.env.VITE_API_KEY, -}); -``` - -> You don't have to do this, you can use `import.meta.env.VITE_*` anywhere in your runtime code, but putting them here consolidates them to one place and defines what variables are expected. diff --git a/docs/guide/directory-structure/assets.md b/docs/guide/directory-structure/assets.md deleted file mode 100644 index f28bac1a..00000000 --- a/docs/guide/directory-structure/assets.md +++ /dev/null @@ -1,42 +0,0 @@ -# `/assets` - -Files in the assets directory will be processed by Vite. They are imported in your source code, and will be transformed or renamed in the output directory. - -``` - -└─ assets/ - ├─ style.css - └─ illustration.svg -``` - -### Example - -:::code-group - -```html [popup.html] - - - - - - - - - - -``` - -```ts [content.ts] -import '~/assets/style.css'; -import illustration from '~/assets/style.svg'; - -defineContentScript({ - main() { - const image = document.createElement('img'); - image.src = illustration; - document.body.append(image); - }, -}); -``` - -::: diff --git a/docs/guide/directory-structure/components.md b/docs/guide/directory-structure/components.md deleted file mode 100644 index 0bb2f0b2..00000000 --- a/docs/guide/directory-structure/components.md +++ /dev/null @@ -1,5 +0,0 @@ -# `/components` - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/directory-structure/composables.md b/docs/guide/directory-structure/composables.md deleted file mode 100644 index c1ada3ec..00000000 --- a/docs/guide/directory-structure/composables.md +++ /dev/null @@ -1,5 +0,0 @@ -# `/composables` - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/directory-structure/entrypoints/background.md b/docs/guide/directory-structure/entrypoints/background.md deleted file mode 100644 index b5df3dd9..00000000 --- a/docs/guide/directory-structure/entrypoints/background.md +++ /dev/null @@ -1,47 +0,0 @@ -# Background - -[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/background/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/background) - -For MV2, the background is added as a script to the background page. For MV3, the background becomes a service worker. - -## Filenames - - - -## Definition - -:::warning -The main function of the background **_CANNOT BE ASYNC_**. Event listeners must be added synchronously on background startup. If your main function returns a promise, WXT will log an error. -::: - -```ts -export default defineBackground(() => { - // Executed when background is loaded -}); -``` - -or - -```ts -export default defineBackground({ - // Set manifest options - persistent: undefined | true | false, - type: undefined | 'module', - - // Set include/exclude if the background should be removed from some builds - include: undefined | string[], - exclude: undefined | string[], - - // Executed when background is loaded - main() { - // ... - }, -}); -``` - -> All manifest options default to `undefined`. diff --git a/docs/guide/directory-structure/entrypoints/bookmarks.md b/docs/guide/directory-structure/entrypoints/bookmarks.md deleted file mode 100644 index 10434f3c..00000000 --- a/docs/guide/directory-structure/entrypoints/bookmarks.md +++ /dev/null @@ -1,31 +0,0 @@ -# Bookmarks - -[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides) - -## Filenames - - - -## Definition - -```html - - - - - - Title - - - - - - - - -``` diff --git a/docs/guide/directory-structure/entrypoints/content-scripts.md b/docs/guide/directory-structure/entrypoints/content-scripts.md deleted file mode 100644 index 5ad58331..00000000 --- a/docs/guide/directory-structure/entrypoints/content-scripts.md +++ /dev/null @@ -1,107 +0,0 @@ -# Content Scripts - -[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/content_scripts/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts) - -When creating content script entrypoints, they are automatically included in the `manifest.json` along with any CSS files they import. - -## Filenames - - - -## Definition - -```ts -export default defineContentScript({ - // Set manifest options - matches: string[], - excludeMatches: undefined | [], - includeGlobs: undefined | [], - excludeGlobs: undefined | [], - allFrames: undefined | true | false, - runAt: undefined | 'document_start' | 'document_end' | 'document_idle', - matchAboutBlank: undefined | true | false, - matchOriginAsFallback: undefined | true | false, - world: undefined | 'ISOLATED' | 'MAIN', - - // Set include/exclude if the background should be removed from some builds - include: undefined | string[], - exclude: undefined | string[], - - // Configure how CSS is injected onto the page - cssInjectionMode: undefined | "manifest" | "manual" | "ui", - - // Configure how/when content script will be registered - registration: undefined | "manifest" | "runtime", - - main(ctx: ContentScriptContext) { - // Executed when content script is loaded - }, -}); -``` - -> All manifest options default to `undefined`. - -When defining multiple content scripts, content script entrypoints that have the same set of options will be merged into a single `content_script` item in the manifest. - -## CSS - -To include CSS with your content script, import the CSS file at the top of your entrypoint. - -``` - -/ -└─ entrypoints/ - └─ overlay.content/ - ├─ index.ts - └─ style.css -``` - -```ts -// entrypoints/overlay.content/index.ts -import './style.css'; - -export default defineContentScript({ - matches: ['*://google.com/*', '*://duckduckgo.com/*'], - - main(ctx) { - // ... - }, -}); -``` - -Any styles imported in your content script will be added to that content script's `css` array in your `manifest.json`: - -```json -// .output/chrome-mv3/manifest.json -{ - "content_scripts": [ - { - "matches": ["*://google.com/*", "*://duckduckgo.com/*"], - "js": ["content-scripts/overlay.js"], - "css": ["content-scripts/overlay.css"] - } - ] -} -``` - -To disable this behavior, set `cssInjectionMode` to `"manual"` or `"ui"`. - -```ts -export default defineContentScript({ - matches: ['*://google.com/*', '*://duckduckgo.com/*'], - cssInjectionMode: 'manual', - - main(ctx) { - // ... - }, -}); -``` - -See [Content Script UI](/guide/key-concepts/content-script-ui) for more info on creating UIs and including CSS in content scripts. diff --git a/docs/guide/directory-structure/entrypoints/css.md b/docs/guide/directory-structure/entrypoints/css.md deleted file mode 100644 index 0e38c46c..00000000 --- a/docs/guide/directory-structure/entrypoints/css.md +++ /dev/null @@ -1,44 +0,0 @@ -# CSS - -WXT can build CSS entrypoints individually. CSS entrypoints are always unlisted. - -See [Content Script CSS](/guide/directory-structure/entrypoints/content-scripts#css) documentation for the recommended approach to include CSS with a content script. - -:::info -If the recommended approach doesn't work for your use case, you can use any of the filename patterns below to build the styles separate from the JS and use the [`transformManifest` hook](/api/reference/wxt/interfaces/InlineConfig#transformmanifest) to manually add your CSS file to the manifest. -::: - -## Filenames - - - -## Definition - -```css -body { - /* Plain CSS file */ -} -``` - -Follow Vite's guide to setup a preprocessor: https://vitejs.dev/guide/features.html#css-pre-processors - -```sh -pnpm i sass -``` - -```scss -body { - h1 { - /* ...*/ - } -} -``` diff --git a/docs/guide/directory-structure/entrypoints/devtools.md b/docs/guide/directory-structure/entrypoints/devtools.md deleted file mode 100644 index da3cda04..00000000 --- a/docs/guide/directory-structure/entrypoints/devtools.md +++ /dev/null @@ -1,40 +0,0 @@ -# Devtools - -[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/devtools/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/devtools_page) - -## Filenames - - - -## Definition - -```html - - - - - - - - - - - - - -``` - -## Adding UI Elements - -Chrome extensions allow you to add panels and side panes to the devtools window. - -![DevTools window showing Elements panel and Styles sidebar pane.](https://developer.chrome.com/static/docs/extensions/how-to/devtools/extend-devtools/image/devtools-window-showing-e-9051f7f0347cd_1920.png) - -See the WXT's examples for a full walkthrough of extending the devtools window: - -- [Devtools Setup](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-devtools#readme) diff --git a/docs/guide/directory-structure/entrypoints/history.md b/docs/guide/directory-structure/entrypoints/history.md deleted file mode 100644 index 9fc11a1b..00000000 --- a/docs/guide/directory-structure/entrypoints/history.md +++ /dev/null @@ -1,31 +0,0 @@ -# History - -[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides) - -## Filenames - - - -## Definition - -```html - - - - - - Title - - - - - - - - -``` diff --git a/docs/guide/directory-structure/entrypoints/newtab.md b/docs/guide/directory-structure/entrypoints/newtab.md deleted file mode 100644 index e15dcb2b..00000000 --- a/docs/guide/directory-structure/entrypoints/newtab.md +++ /dev/null @@ -1,31 +0,0 @@ -# Newtab - -[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides) - -## Filenames - - - -## Definition - -```html - - - - - - Title - - - - - - - - -``` diff --git a/docs/guide/directory-structure/entrypoints/options.md b/docs/guide/directory-structure/entrypoints/options.md deleted file mode 100644 index f4af0271..00000000 --- a/docs/guide/directory-structure/entrypoints/options.md +++ /dev/null @@ -1,36 +0,0 @@ -# Options - -[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/options/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/options_ui) - -## Filenames - - - -## Definition - -```html - - - - - - Options Title - - - - - - - - - - - -``` - -> All manifest options default to `undefined` when the `meta` tag is not present. diff --git a/docs/guide/directory-structure/entrypoints/popup.md b/docs/guide/directory-structure/entrypoints/popup.md deleted file mode 100644 index 536742e4..00000000 --- a/docs/guide/directory-structure/entrypoints/popup.md +++ /dev/null @@ -1,43 +0,0 @@ -# Popup - -[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/action/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action) - -## Filenames - - - -## Definition - -```html - - - - - - Default Popup Title - - - - - - - - - - - -``` - -> All manifest options default to `undefined` when the `meta` tag is not present. diff --git a/docs/guide/directory-structure/entrypoints/sandbox.md b/docs/guide/directory-structure/entrypoints/sandbox.md deleted file mode 100644 index 886c64dc..00000000 --- a/docs/guide/directory-structure/entrypoints/sandbox.md +++ /dev/null @@ -1,37 +0,0 @@ -# Sandbox - -[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/sandbox/) - -:::tip Chromium Only -Firefox does not support sandboxed pages. -::: - -## Filenames - - - -## Definition - -```html - - - - - - Title - - - - - - - - -``` diff --git a/docs/guide/directory-structure/entrypoints/sidepanel.md b/docs/guide/directory-structure/entrypoints/sidepanel.md deleted file mode 100644 index 8e61a07c..00000000 --- a/docs/guide/directory-structure/entrypoints/sidepanel.md +++ /dev/null @@ -1,49 +0,0 @@ -# Side Panel - -[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/sidePanel/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Sidebars) - -In Chrome, side panels use the "side_panel" API, while Firefox uses the "sidebar_action" API. - -:::warning -Chrome added support for sidepanels in Manifest V3, they are not available in Manifest V2. -::: - -## Filenames - - - -## Definition - -```html - - - - - - Default Side Panel Title - - - - - - - - - - - -``` diff --git a/docs/guide/directory-structure/entrypoints/unlisted-pages.md b/docs/guide/directory-structure/entrypoints/unlisted-pages.md deleted file mode 100644 index c9812e99..00000000 --- a/docs/guide/directory-structure/entrypoints/unlisted-pages.md +++ /dev/null @@ -1,49 +0,0 @@ -# Unlisted Pages - -HTML pages that are bundled and shipped with the extension, but are not included in the manifest. - -If you plan on using the page in an iframe, don't forget to add the page to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources). - -### Examples - -- Onboarding -- Dashboard -- FAQ -- Help -- Changelog - -## Filenames - - - -Pages are accessible at `'/.html'`: - -```ts -const url = browser.runtime.getURL('/.html', ''); - -console.log(url); // "chrome-extension:///.html" -``` - -## Definition - -```html - - - - - - Title - - - - - - - - -``` diff --git a/docs/guide/directory-structure/entrypoints/unlisted-scripts.md b/docs/guide/directory-structure/entrypoints/unlisted-scripts.md deleted file mode 100644 index 89a2da53..00000000 --- a/docs/guide/directory-structure/entrypoints/unlisted-scripts.md +++ /dev/null @@ -1,37 +0,0 @@ -# Unlisted Scripts - -TypeScript files that are bundled and shipped with the extension, but are not included in the manifest. - -You are responsible for loading/running these scripts where needed. If necessary, don't forget to add the script and/or any related stylesheets to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources). - -## Filenames - - - -## Definition - -```ts -export default defineUnlistedScript(() => { - // Executed when script is loaded -}); -``` - -or - -```ts -export default defineUnlistedScript({ - // Set include/exclude if the script should be removed from some builds - include: undefined | string[], - exclude: undefined | string[], - - // Executed when script is loaded - main() { - // ... - }, -}); -``` diff --git a/docs/guide/directory-structure/env.md b/docs/guide/directory-structure/env.md deleted file mode 100644 index 6a17fbce..00000000 --- a/docs/guide/directory-structure/env.md +++ /dev/null @@ -1,5 +0,0 @@ -# `/.env` - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/directory-structure/hooks.md b/docs/guide/directory-structure/hooks.md deleted file mode 100644 index e6e19836..00000000 --- a/docs/guide/directory-structure/hooks.md +++ /dev/null @@ -1,5 +0,0 @@ -# `/hooks` - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/directory-structure/output.md b/docs/guide/directory-structure/output.md deleted file mode 100644 index bb5ef417..00000000 --- a/docs/guide/directory-structure/output.md +++ /dev/null @@ -1,5 +0,0 @@ -# `/.output` - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/directory-structure/package.md b/docs/guide/directory-structure/package.md deleted file mode 100644 index 79f23b57..00000000 --- a/docs/guide/directory-structure/package.md +++ /dev/null @@ -1,5 +0,0 @@ -# `/package.json` - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/directory-structure/public/index.md b/docs/guide/directory-structure/public/index.md deleted file mode 100644 index 765e1a44..00000000 --- a/docs/guide/directory-structure/public/index.md +++ /dev/null @@ -1,35 +0,0 @@ -# `public/` - -Place static files like the extension icon or `_locales/` directory here. These files will be copied over to the output directory without being transformed by Vite. - -``` - -└─ public/ - ├─ icon-16.png - ├─ icon-32.png - ├─ icon-48.png - ├─ icon-96.png - └─ icon-128.png -``` - -### Example - -You can reference these files by using absolute paths in HTML files or `browser.runtime.getURL` in content scripts. - -:::code-group - -```html [popup.html] - -``` - -```ts [content.ts] -defineContentScript({ - main() { - const image = document.createElement('img'); - image.src = browser.runtime.getURL('/icon-128.png'); - document.body.append(image); - }, -}); -``` - -::: diff --git a/docs/guide/directory-structure/public/locales.md b/docs/guide/directory-structure/public/locales.md deleted file mode 100644 index 36a57303..00000000 --- a/docs/guide/directory-structure/public/locales.md +++ /dev/null @@ -1,5 +0,0 @@ -# `public/_locales/` - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/directory-structure/tsconfig.md b/docs/guide/directory-structure/tsconfig.md deleted file mode 100644 index 81220e42..00000000 --- a/docs/guide/directory-structure/tsconfig.md +++ /dev/null @@ -1,5 +0,0 @@ -# `/tsconfig.json` - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/directory-structure/utils.md b/docs/guide/directory-structure/utils.md deleted file mode 100644 index 54cd86e5..00000000 --- a/docs/guide/directory-structure/utils.md +++ /dev/null @@ -1,5 +0,0 @@ -# `/utils` - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/directory-structure/web-ext-config.md b/docs/guide/directory-structure/web-ext-config.md deleted file mode 100644 index 88b33744..00000000 --- a/docs/guide/directory-structure/web-ext-config.md +++ /dev/null @@ -1,65 +0,0 @@ -# `web-ext.config.ts` - -This file lets you configure the browser startup when running `wxt dev`. - -```ts -import { defineRunnerConfig } from 'wxt'; - -export default defineRunnerConfig({ - startUrls: ['https://google.com', 'https://youtube.com'], -}); -``` - -There are three places you can customize the runner: - -- `/wxt.config.ts` - Use the `runner` option. Changes here will be committed and shared with everyone developing the project. -- `/web-ext.config.ts` - A gitignored file for you to customize the startup behavior to your liking without effecting others -- `$HOME/web-ext.config.ts` - Stores system-wide config effecting all projects running on your machine. - -See below examples on how to accomplish common configuration: - -[[toc]] - -## Configuring Binaries - -`web-ext`'s browser discovery is very limited. By default, it only guesses at where Chrome and Firefox are installed. If you've customized your install locations, you may need to tell `web-ext` where the binaries/executables are located using the [`binaries` option](/api/reference/wxt/interfaces/ExtensionRunnerConfig#binaries). For other Chromium based browsers, like Edge or Opera, you'll need to explicitly list them in the `binaries` option as well, otherwise they will open in Chrome by default. - -```ts -import { defineRunnerConfig } from 'wxt'; - -export default defineRunnerConfig({ - binaries: { - chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome - firefox: 'firefoxdeveloperedition', // Use Firefox Developer Edition instead of regular Firefox - edge: '/path/to/edge', // Open MS Edge when running "wxt -b edge" - }, -}); -``` - -## Disable Opening Browser - -Disabling the browser can be useful if it's difficult to develop your extension with fresh profiles. Maybe you need to sign into a website to see a content script run, and a fresh profile isn't helpful because it doesn't save your login info. - -To disable opening the extension automatically in a new window, just disable the runner: - -```ts -export default defineRunnerConfig({ - disabled: true, -}); -``` - -## Profile Customization - -Another option, instead of disabling the runner, to stay logged into websites is to use a custom profile. - -`web-ext` comes with some built-in ways of using an existing profile, but it's not really using the same profile. It copies the profile to a temp directory, and uses that. - -Instead, I've found it's better to pass Chrome's `--user-data-dir` argument. This let's you use a fresh profile initially, and customize it to your liking. You can install devtool extensions, set custom flags, and log into websites. Next time you run the extension in dev mode, all that will be remembered! - -```ts -export default defineRunnerConfig({ - chromiumArgs: ['--user-data-dir=./chrome-data'], -}); -``` - -> This only works for Chrome. You'll have to use `firefoxProfile` option instead, which has the same limitations mentioned above, where you won't be signed into websites automatically. diff --git a/docs/guide/directory-structure/wxt-config.md b/docs/guide/directory-structure/wxt-config.md deleted file mode 100644 index 30c0e831..00000000 --- a/docs/guide/directory-structure/wxt-config.md +++ /dev/null @@ -1,5 +0,0 @@ -# `/wxt.config.ts` - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/directory-structure/wxt.md b/docs/guide/directory-structure/wxt.md deleted file mode 100644 index 557ce148..00000000 --- a/docs/guide/directory-structure/wxt.md +++ /dev/null @@ -1,5 +0,0 @@ -# `.wxt/` - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/essentials/assets.md b/docs/guide/essentials/assets.md new file mode 100644 index 00000000..29638fd1 --- /dev/null +++ b/docs/guide/essentials/assets.md @@ -0,0 +1,82 @@ +# Assets + +## `/assets` Directory + +Any assets imported or referenced inside the `/assets/` directory will be processed by WXT's bundler. + +Here's how you access them: + +:::code-group + +```ts [JS] +import imageUrl from '~/assets/image.png'; + +const img = document.createElement('img'); +img.src = imageUrl; +``` + +```html [HTML] + +``` + +```css [CSS] +.bg-image { + background-image: url(~/assets/image.png); +} +``` + +::: + +## `/public` Directory + +Files inside `/public/` are copied into the output folder as-is, without being processed by WXT's bundler. + +Here's how you access them: + +:::code-group + +```ts [JS] +import imageUrl from '/image.png'; + +const img = document.createElement('img'); +img.src = imageUrl; +``` + +```html [HTML] + +``` + +```css [CSS] +.bg-image { + background-image: url(/image.png); +} +``` + +::: + +## Inside Content Scripts + +Assets inside content scripts are a little different. By default, when you import an asset, it returns just the path to the asset. This is because Vite assumes you're loading assets from the same hostname. + +But, inside content scripts, the hostname is whatever the tab is set to. So if you try to fetch the asset, manually or as an ``'s `src`, it will be loaded from the tab's website, not your extension. + +To fix this, you need to convert the image to a full URL using `browser.runtime.getURL`: + +```ts +// entrypoints/content.ts +import iconUrl from '/icon/128.png'; + +export default defineContentScript({ + matches: ['*://*.google.com/*'], + main() { + console.log(iconUrl); // "/icon/128.png" + console.log(browser.runtime.getURL(iconUrl)); // "chrome-extension:///icon/128.png" + }, +}); +``` + +## WASM + +:::warning 🚧 Under construction +These docs will be coming soon! +::: diff --git a/docs/guide/essentials/config/auto-imports.md b/docs/guide/essentials/config/auto-imports.md new file mode 100644 index 00000000..92aca6aa --- /dev/null +++ b/docs/guide/essentials/config/auto-imports.md @@ -0,0 +1,112 @@ +# Auto-imports + +WXT uses [`unimport`](https://www.npmjs.com/package/unimport), the same tool as Nuxt, to setup auto-imports. + +```ts +export default defineConfig({ + // See https://www.npmjs.com/package/unimport#configurations + imports: { + // ... + }, +}); +``` + +By default, WXT automatically setups up auto-imports for all of it's own APIs: + +- [`browser`](/api/reference/wxt/browser/variables/browser) from `wxt/browser` +- [`defineContentScript`](/api/reference/wxt/sandbox/functions/defineContentScript) from `wxt/sandbox` +- [`defineBackground`](/api/reference/wxt/sandbox/functions/defineBackground) from `wxt/sandbox` +- [`defineUnlistedScript`](/api/reference/wxt/sandbox/functions/defineUnlistedScript) from `wxt/sandbox` +- [`createIntegratedUi`](/api/reference/wxt/client/functions/createIntegratedUi) from `wxt/client` +- [`createShadowRootUi`](/api/reference/wxt/client/functions/createShadowRootUi) from `wxt/client` +- [`createIframeUi`](/api/reference/wxt/client/functions/createIframeUi) from `wxt/client` +- [`fakeBrowser`](/api/reference/wxt/testing/variables/fakeBrowser) from `wxt/testing` +- And more! + +WXT also adds some project directories as auto-import sources automatically: + +- `/components/*` +- `/composables/*` +- `/hooks/*` +- `/utils/*` + +All named and default exports from files in these directories are available everywhere else in your project without having to import them. + +## TypeScript + +For TypeScript and your editor to recognize auto-imported variables, you need to run the [`wxt prepare` command](/api/cli/wxt-prepare). + +Add this command to your `postinstall` script so your editor has everything it needs to report type errors after installing dependencies: + +```jsonc +// package.json +{ + "scripts": { + "postinstall": "wxt prepare", // [!code ++] + }, +} +``` + +## ESLint + +ESLint doesn't know about the auto-imported variables unless they are explicitly defined in the ESLint's `globals`. By default, WXT will generate the config if it detects ESLint is installed in your project. If the config isn't generated automatically, you can manually tell WXT to generate it. + +:::code-group + +```ts [ESLint 9] +export default defineConfig({ + imports: { + eslintrc: { + enabled: 9, + }, + }, +}); +``` + +```ts [ESLint 8] +export default defineConfig({ + imports: { + eslintrc: { + enabled: 8, + }, + }, +}); +``` + +::: + +Then in your ESLint config, import and use the generated file: + +:::code-group + +```js [ESLint 9] +// eslint.config.mjs +import autoImports from './.wxt/eslint-auto-imports.mjs'; + +export default [ + autoImports, + { + // The rest of your config... + }, +]; +``` + +```js [ESLint 8] +// .eslintrc.mjs +export default { + extends: ['./.wxt/eslintrc-auto-import.json'], + // The rest of your config... +}; +``` + +::: + +## Disabling Auto-imports + +Not all developers like auto-imports. To disable them, set `imports` to `false`. + +```ts +export default defineConfig({ + imports: false, // [!code ++] +}); +``` diff --git a/docs/guide/essentials/config/browser-startup.md b/docs/guide/essentials/config/browser-startup.md new file mode 100644 index 00000000..676d5e22 --- /dev/null +++ b/docs/guide/essentials/config/browser-startup.md @@ -0,0 +1,85 @@ +--- +outline: deep +--- + +# Browser Startup + +> See the [API Reference](/api/reference/wxt/interfaces/ExtensionRunnerConfig) for a full list of config. + +During development WXT uses [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext) to automatically open a browser window with your extension installed. + +## Config Files + +You can configure browser startup in 3 places: + +1. `/web-ext.config.ts`: Ignored from version control, this file lets you configure your own options for a specific project without affecting other developers + + ```ts + import { defineRunnerConfig } from 'wxt'; + + export default defineRunnerConfig({ + // ... + }); + ``` + +2. `/wxt.config.ts`: Via the [`runner` config](/api/reference/wxt/interfaces/InlineConfig#runner), included in version control +3. `$HOME/web-ext.config.ts`: Provide default values for all WXT projects on your computer + +## Recipes + +### Set Browser Binaries + +To set or customize the browser opened during development: + +```ts +export default defineRunnerConfig({ + binaries: { + chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome + firefox: 'firefoxdeveloperedition', // Use Firefox Developer Edition instead of regular Firefox + edge: '/path/to/edge', // Open MS Edge when running "wxt -b edge" + }, +}); +``` + +### Persist Data + +By default, to keep from modifying your browser's existing profiles, `web-ext` creates a brand new profile every time you run the `dev` script. + +Right now, Chromium based browsers are the only browsers that support overriding this behavior and persisting data when running the `dev` script multiple times. + +To persist data, set the `--user-data-dir` flag: + +:::code-group + +```ts [Mac/Linux] +export default defineRunnerConfig({ + chromiumArgs: ['--user-data-dir=./.wxt/chrome-data'], +}); +``` + +```ts [Windows] +import { resolve } from 'node:path'; + +export default defineRunnerConfig({ + // On Windows, the path must be absolute + chromiumArgs: [`--user-data-dir="${resolve(".wxt/chrome-data")}"` +}); +``` + +::: + +Now, next time you run the `dev` script, a persistent profile will be created in `.wxt/chrome-data/{profile-name}`. With a persistent profile, you can install devtools extensions to help with development, allow the browser to remember logins, etc, without worrying about the profile being reset the next time you run the `dev` script. + +:::tip +You can use any directory you'd like for `--user-data-dir`, the examples above create a persistent profile for each WXT project. To create a profile for all WXT projects, you can put the `chrome-data` directory inside you're user's home directory. +::: + +### Disable Opening Browser + +If you prefer to load the extension into your browser manually, you can disable the auto-open behavior: + +```ts +export default defineRunnerConfig({ + disabled: true, +}); +``` diff --git a/docs/guide/essentials/config/build-mode.md b/docs/guide/essentials/config/build-mode.md new file mode 100644 index 00000000..850f499f --- /dev/null +++ b/docs/guide/essentials/config/build-mode.md @@ -0,0 +1,29 @@ +# Build Modes + +Because WXT is powered by Vite, it supports [modes](https://vite.dev/guide/env-and-mode.html#modes) in the same way. + +When running any dev or build commands, pass the `--mode` flag: + +```sh +wxt --mode production +wxt build --mode development +wxt zip --mode testing +``` + +By default, `--mode` is `development` for the dev command and `production` for all other commands (build, zip, etc). + +## Get Mode at Runtime + +You can access the current mode in your extension using `import.meta.env.MODE`: + +```ts +switch (import.meta.env.MODE) { + case 'development': // ... + case 'production': // ... + + // Custom modes specified with --mode + case 'testing': // ... + case 'staging': // ... + // ... +} +``` diff --git a/docs/guide/essentials/config/entrypoint-loaders.md b/docs/guide/essentials/config/entrypoint-loaders.md new file mode 100644 index 00000000..a61708b4 --- /dev/null +++ b/docs/guide/essentials/config/entrypoint-loaders.md @@ -0,0 +1,79 @@ +# Entrypoint Loaders + +To generate the manifest and other files at build-time, WXT must import each entrypoint to get their options, like content script `matches`. For HTML files, this is easy. For JS/TS entrypoints, the process is more complicated. + +When loading your JS/TS entrypoints, they are imported into a NodeJS environment, not the `browser` environment that they normally run in. This can lead to issues commonly seen when running browser-only code in a NodeJS environment, like missing global variables. + +WXT does several pre-processing steps to try and prevent errors during this process: + +1. Use `linkedom` to make a small set of browser globals (`window`, `document`, etc) available. +2. Use `@webext-core/fake-browser` to create a fake version of the `chrome` and `browser` globals expected by extensions. +3. Pre-process the JS/TS code, stripping out the `main` function then tree-shaking unused code from the file + +However, this process is not perfect. It doesn't setup all the globals found in the browser and the APIs may behave differently. As such, **_you should avoid using browser or extension APIs outside the `main` function of your entrypoints!_** + +:::tip +If you're running into errors while importing entrypoints, run `wxt prepare --debug` to see more details about this process. When debugging, WXT will print out the pre-processed code to help you identify issues. +::: + +Once the environment has been polyfilled and your code pre-processed, it's up the entrypoint loader to import your code, extracting the options from the default export. + +There are two options for loading your entrypoints: + +1. `vite-node` - default as of `v0.19.0` +2. `jiti` (**DEPRECATED, will be removed in `v0.20.0`**) - Default before `v0.19.0` + +## vite-node + +Since 0.19.0, WXT uses `vite-node`, the same tool that powers Vitest and Nuxt, to import your entrypoint files. It re-uses the same vite config used when building your extension, making it the most stable entrypoint loader. + +## jiti + +To enable `jiti`: + +```ts +export default defineConfig({ + entrypointLoader: 'jiti', +}); +``` + +This is the original method WXT used to import TS files. However, because it doesn't support vite plugins like `vite-node`, it does one additional pre-processing step: It removes **_ALL_** imports from your code. + +That means you cannot use imported variables outside the `main` function in JS entrypoints, like for content script `matches` or other options: + +```ts +// entrypoints/content.ts +import { GOOGLE_MATCHES } from '~/utils/match-patterns'; + +export default defineContentScript({ + matches: GOOGLE_MATCHES, + main() { + // ... + }, +}); +``` + +``` +$ wxt build +wxt build + +WXT 0.14.1 +ℹ Building chrome-mv3 for production with Vite 5.0.5 +✖ Command failed after 360 ms + +[8:55:54 AM] ERROR entrypoints/content.ts: Cannot use imported variable "GOOGLE_MATCHES" before main function. +``` + +Usually, this error occurs when you try to extract options into a shared file or when running code outside the `main` function. To fix the example from above, use literal values when defining an entrypoint instead of importing them: + +```ts +import { GOOGLE_MATCHES } from '~/utils/match-patterns'; // [!code --] + +export default defineContentScript({ + matches: GOOGLE_MATCHES, // [!code --] + matches: ['*//*.google.com/*'], // [!code ++] + main() { + // ... + }, +}); +``` diff --git a/docs/guide/essentials/config/environment-variables.md b/docs/guide/essentials/config/environment-variables.md new file mode 100644 index 00000000..be97952f --- /dev/null +++ b/docs/guide/essentials/config/environment-variables.md @@ -0,0 +1,54 @@ +# Environment Variables + +## Dotenv Files + +WXT supports [dotenv files the same way as Vite](https://vite.dev/guide/env-and-mode.html#env-files). Create any of the following files: + +``` +.env +.env.local +.env.[mode] +.env.[mode].local +``` + +And any environment variables listed inside them will be available at runtime: + +```sh +# .env +VITE_API_KEY=... +``` + +```ts +await fetch(`/some-api?apiKey=${import.meta.env.VITE_API_KEY}`); +``` + +Remember to prefix any environment variables with `VITE_`, otherwise they won't be available at runtime, as per [Vite's convention](https://vite.dev/guide/env-and-mode.html#env-files). + +## Built-in Environment Variables + +WXT provides some custom environment variables based on the current command: + +| Usage | Type | Description | +| ---------------------------------- | --------- | ----------------------------------------------------- | +| `import.meta.env.MANIFEST_VERSION` | `2 │ 3` | The target manifest version | +| `import.meta.env.BROWSER` | `string` | The target browser | +| `import.meta.env.CHROME` | `boolean` | Equivalent to `import.meta.env.BROWSER === "chrome"` | +| `import.meta.env.FIREFOX` | `boolean` | Equivalent to `import.meta.env.BROWSER === "firefox"` | +| `import.meta.env.SAFARI` | `boolean` | Equivalent to `import.meta.env.BROWSER === "safari"` | +| `import.meta.env.EDGE` | `boolean` | Equivalent to `import.meta.env.BROWSER === "edge"` | +| `import.meta.env.OPERA` | `boolean` | Equivalent to `import.meta.env.BROWSER === "opera"` | + +You can also access all of [Vite's environment variables](https://vite.dev/guide/env-and-mode.html#env-variables): + +| Usage | Type | Description | +| ---------------------- | --------- | --------------------------------------------------------------------------- | +| `import.meta.env.MODE` | `string` | The [mode](/guide/essentials/config/build-mode) the extension is running in | +| `import.meta.env.PROD` | `boolean` | When `NODE_ENV='production'` | +| `import.meta.env.DEV` | `boolean` | Opposite of `import.meta.env.PROD` | + +:::details Other Vite Environment Variables +Vite provides two other environment variables, but they aren't useful in WXT projects: + +- `import.meta.env.BASE_URL`: Use `browser.runtime.getURL` instead. +- `import.meta.env.SSR`: Always `false`. + ::: diff --git a/docs/guide/essentials/config/hooks.md b/docs/guide/essentials/config/hooks.md new file mode 100644 index 00000000..930b8bdf --- /dev/null +++ b/docs/guide/essentials/config/hooks.md @@ -0,0 +1,22 @@ +# Hooks + +WXT includes a system that lets you hook into the build process and make changes. + +Here's an example hook that modifies the `manifest.json` file before it is written to the output directory: + +```ts +// wxt.config.ts +export default defineConfig({ + hooks: { + 'build:manifestGenerated': (wxt, manifest) => { + if (wxt.config.mode === 'development') { + manifest.title += ' (DEV)'; + } + }, + }, +}); +``` + +> Most hooks provide the `wxt` object as the first argument. It contains the resolved config and other info about the current build. + +Putting one-off hooks like this in your config file is simple, but if you find yourself writing lots of hooks, you should extract them into [WXT Modules](/guide/essentials/wxt-modules) instead. diff --git a/docs/guide/essentials/config/manifest.md b/docs/guide/essentials/config/manifest.md new file mode 100644 index 00000000..6aeab49d --- /dev/null +++ b/docs/guide/essentials/config/manifest.md @@ -0,0 +1,258 @@ +# Manifest + +In WXT, there is no `manifest.json` file in your source code. Instead, WXT generates it during the build process based off files in your project. + +## Manifest Config + +To manually add a property to the `manifest.json` output during builds, use the `manifest` config inside `wxt.config.ts`: + +```ts +export default defineConfig({ + manifest: { + // Put manual changes here + }, +}); +``` + +You can also define the manifest as a function, and use JS to generate it based on the target browser, mode, and more. + +```ts +export default defineConfig({ + manifest: ({ browser, manifestVersion, mode, command }) => { + return { + // ... + }; + }, +}); +``` + +### MV2 and MV3 Compatibility + +When adding properties to the manifest, always define the property in it's MV3 format when possible. When targeting MV2, WXT will automatically convert these properties to their MV2 format. + +For example, for this config: + +```ts +export default defineConfig({ + manifest: { + action: { + default_title: 'Some Title', + }, + web_accessible_resources: [ + { + matches: ['*://*.google.com/*'], + resources: ['icon/*.png'], + }, + ], + }, +}); +``` + +WXT will generate the following manifests: + +:::code-group + +```json [MV2] +{ + "manifest_version": 2, + // ... + "browser_action": { + "default_title": "Some Title" + }, + "web_accessible_resources": ["icon/*.png"] +} +``` + +```json [MV3] +{ + "manifest_version": 3, + // ... + "action": { + "default_title": "Some Title" + }, + "web_accessible_resources": [ + { + "matches": ["*://*.google.com/*"], + "resources": ["icon/*.png"] + } + ] +} +``` + +::: + +You can also specify properties specific to a single manifest version, and they will be stripped out when targeting the other manifest version. + +## Name + +> [Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/name/) + +If not provided via the `manifest` config, the manifest's `name` property defaults to your `package.json`'s `name` property. + +## Version and Version Name + +> [Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/version/) + +Your extension's `version` and `version_name` is based on the `version` from your `package.json`. + +- `version_name` is the exact string listed +- `version` is the string cleaned up, with any invalid suffixes removed + +Example: + +```json +// package.json +{ + "version": "1.3.0-alpha2" +} +``` + +```json +// .output//manifest.json +{ + "version": "1.3.0", + "version_name": "1.3.0-alpha2" +} +``` + +If a version is not present in your `package.json`, it defaults to `"0.0.0"`. + +## Icons + +WXT automatically discovers your extension's icon by looking at files in the `public/` directory: + +``` +public/ +├─ icon-16.png +├─ icon-24.png +├─ icon-48.png +├─ icon-96.png +└─ icon-128.png +``` + +Specifically, if an icon must match one of these regex to be discovered: + +<<< @/../packages/wxt/src/core/utils/manifest.ts#snippet + +If you don't like these filename or you're migrating to WXT and don't want to rename the files, you can manually specify an `icon` in your manifest: + +```ts +export default defineConfig({ + manifest: { + icons: { + 16: '/extension-icon-16.png', + 24: '/extension-icon-24.png', + 48: '/extension-icon-48.png', + 96: '/extension-icon-96.png', + 128: '/extension-icon-128.png', + }, + }, +}); +``` + +Alternatively, you can use [`@wxt-dev/auto-icons`](https://www.npmjs.com/package/@wxt-dev/auto-icons) to let WXT generate your icon at the required sizes. + +## Permissions + +> [Chrome docs](https://developer.chrome.com/docs/extensions/reference/permissions/) + +```ts +export default defineConfig({ + manifest: { + permissions: ['storage', 'tabs'], + }, +}); +``` + +## Host Permissions + +> [Chrome docs](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions#host-permissions) + +```ts +export default defineConfig({ + manifest: { + permissions: ['storage', 'tabs'], + }, +}); +``` + +:::warning +If you use host permissions and target both MV2 and MV3, make sure to only include the required host permissions for each version: + +```ts +export default defineConfig({ + manifest: ({ manifestVersion }) => ({ + host_permissions: manifestVersion === 2 ? [...] : [...], + }), +}); +``` + +::: + +## Default Locale + +```ts +export default defineConfig({ + manifest: { + name: '__MSG_extName__', + description: '__MSG_extDescription__', + default_locale: 'en', + }, +}); +``` + +> See [I18n docs](/guide/essentials/i18n) for a full guide on internationalizing your extension. + +## Actions + +In MV2, you have two options: [`browser_action`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_action) and [`page_action`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/page_action). In MV3, they were merged into a single [`action`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action) API. + +By default, whenever an `action` is generated, WXT falls back to `browser_action` when targeting MV2. + +### Action With Popup + +To generate a manifest where a UI appears after clicking the icon, just create a [Popup entrypoint](/guide/essentials/entrypoints#popup). + +```ts +export default defineConfig({ + hooks: { + build: { + manifestGenerated(manifest) { + // Update the manifest variable by reference + manifest.name = 'Overriden name'; + }, + }, + }, +}); +``` + +If you want to use a `page_action` for MV2, add the following meta tag to the HTML document's head: + +```html + +``` + +### Action Without Popup + +If you want to use the `activeTab` permission or the `browser.action.onClicked` event, but don't want to show a popup: + +1. Delete the [Popup entrypoint](/guide/essentials/entrypoints#popup) if it exists +2. Add the `action` key to your manifest: + ```ts + export default defineConfig({ + manifest: { + action: {}, + }, + }); + ``` + +Same as an action with a popup, WXT will fallback on using `browser_action` for MV2. To use a `page_action` instead, add that key as well: + +```ts +export default defineConfig({ + manifest: { + action: {}, + page_action: {}, + }, +}); +``` diff --git a/docs/guide/essentials/config/runtime.md b/docs/guide/essentials/config/runtime.md new file mode 100644 index 00000000..084a43f5 --- /dev/null +++ b/docs/guide/essentials/config/runtime.md @@ -0,0 +1,56 @@ +# Runtime Config + +> This API is still a WIP, with more features coming soon! + +Define runtime configuration in a single place, `/app.config.ts`: + +```ts +import { defineAppConfig } from 'wxt/sandbox'; + +// Define types for your config +declare module 'wxt/sandbox' { + export interface WxtAppConfig { + theme?: 'light' | 'dark'; + } +} + +export default defineAppConfig({ + theme: 'dark', +}); +``` + +:::warning +This file is committed to the repo, so don't put any secrets here. Instead, use [Environment Variables](#environment-variables) +::: + +To access runtime config, WXT provides the `useAppConfig` function: + +```ts +import { useAppConfig } from 'wxt/sandbox'; + +console.log(useAppConfig()); // { theme: "dark" } +``` + +## Environment Variables in App Config + +You can use environment variables in the `app.config.ts` file. + +```ts +declare module 'wxt/sandbox' { + export interface WxtAppConfig { + apiKey?: string; + skipWelcome: boolean; + } +} + +export default defineAppConfig({ + apiKey: import.meta.env.VITE_API_KEY, + skipWelcome: import.meta.env.VITE_SKIP_WELCOME === 'true', +}); +``` + +This has several advantages: + +- Define all expected environment variables in a single file +- Convert strings to other types, like booleans or arrays +- Provide default values if an environment variable is not provided diff --git a/docs/guide/essentials/config/typescript.md b/docs/guide/essentials/config/typescript.md new file mode 100644 index 00000000..be220883 --- /dev/null +++ b/docs/guide/essentials/config/typescript.md @@ -0,0 +1,65 @@ +# TypeScript Configuration + +When you run [`wxt prepare`](/api/cli/wxt-prepare), WXT generates a base TSConfig file for your project at `/.wxt/tsconfig.json`. + +At a minimum, you need to create a TSConfig in your root directory that looks like this: + +```jsonc +// /tsconfig.json +{ + "extends": ".wxt/tsconfig.json", +} +``` + +Or if you're in a monorepo, you may not want to extend the config. If you don't extend it, you need to add `.wxt/wxt.d.ts` to the TypeScript project: + +```ts +/// +``` + +## Compiler Options + +To specify custom compiler options, add them in `/tsconfig.json`: + +```jsonc +// /tsconfig.json +{ + "extends": ".wxt/tsconfig.json", + "compilerOptions": { + "jsx": "preserve", + }, +} +``` + +## TSConfig Paths + +WXT provides a default set of path aliases. + +| Alias | To | Example | +| ----- | ------------- | ----------------------------------------------- | +| `~~` | `/*` | `import "~~/scripts"` | +| `@@` | `/*` | `import "@@/scripts"` | +| `~` | `/*` | `import { toLowerCase } from "~/utils/strings"` | +| `@` | `/*` | `import { toLowerCase } from "@/utils/strings"` | + +To add your own, DO NOT add them to your `tsconfig.json`! Instead, use the [`alias` option](/api/reference/wxt/interfaces/InlineConfig#alias) in `wxt.config.ts`. + +This will add your custom aliases to `/.wxt/tsconfig.json` next time you run `wxt prepare`. It also adds your alias to the bundler so it can resolve imports. + +```ts +import { resolve } from 'node:path'; + +export default defineConfig({ + alias: { + // Directory: + testing: resolve('utils/testing'), + // File: + strings: resolve('utils/strings.ts'), + }, +}); +``` + +```ts +import { fakeTab } from 'testing/fake-objects'; +import { toLowerCase } from 'strings'; +``` diff --git a/docs/guide/essentials/config/vite.md b/docs/guide/essentials/config/vite.md new file mode 100644 index 00000000..47bb8eff --- /dev/null +++ b/docs/guide/essentials/config/vite.md @@ -0,0 +1,68 @@ +# Vite + +WXT uses [Vite](https://vitejs.dev/) under the hood to bundle your extension. + +This page explains how to customize your project's Vite config. Refer to [Vite's documentation](https://vite.dev/config/) to learn more about configuring the bundler. + +:::tip +In most cases, you shouldn't change Vite's build settings. WXT provides sensible defaults that output a valid extension accepted by all stores when publishing. +::: + +## Change Vite Config + +You can change Vite's config via the `wxt.config.ts` file: + +```ts +// wxt.config.ts +import { defineConfig } from 'wxt'; + +export default defineConfig({ + vite: () => ({ + // Override config here, same as `defineConfig({ ... })` + // inside vite.config.ts files + }), +}); +``` + +## Add Vite Plugins + +To add a plugin, install the NPM package and add it to the Vite config: + +```ts +// wxt.config.ts +import { defineConfig } from 'wxt'; +import VueRouter from 'unplugin-vue-router/vite'; + +export default defineConfig({ + vite: () => ({ + plugins: [ + VueRouter({ + /* ... */ + }), + ], + }), +}); +``` + +:::warning +Due to the way WXT orchestrates Vite builds, some plugins may not work as expected. For example, `vite-plugin-remove-console` normally only runs when you build for production (`vite build`). However, WXT uses a combination of dev server and builds during development, so you need to manually tell it when to run: + +```ts +// wxt.config.ts +import { defineConfig } from 'wxt'; +import removeConsole from 'vite-plugin-remove-console'; + +export default defineConfig({ + vite: (configEnv) => ({ + plugins: + configEnv.mode === 'production' + ? [removeConsole({ includes: ['log'] })] + : [], + }), +}); +``` + +Search [GitHub issues](https://github.com/wxt-dev/wxt/issues?q=is%3Aissue+label%3A%22vite+plugin%22) if you run into issues with a specific plugin. + +If an issue doesn't exist for your plugin, [open a new one](https://github.com/wxt-dev/wxt/issues/new/choose). +::: diff --git a/docs/guide/key-concepts/content-script-ui.md b/docs/guide/essentials/content-scripts.md similarity index 58% rename from docs/guide/key-concepts/content-script-ui.md rename to docs/guide/essentials/content-scripts.md index c75935ee..caf74255 100644 --- a/docs/guide/key-concepts/content-script-ui.md +++ b/docs/guide/essentials/content-scripts.md @@ -1,8 +1,102 @@ -# Content Script UI +--- +outline: deep +--- -There are three ways to mount a UI inside a content script: +# Content Scripts -[[toc]] +## Context + +The first argument to a content script's `main` function is it's "context". + +```ts +// entrypoints/content.ts +export default defineContentScript({ + main(ctx) {}, +}); +``` + +This object is responsible for tracking whether or not the content script's context is "invalidated". Most browsers, by default, do not stop content scripts if the extension is uninstalled, updated, or disabled. When this happens, content scripts start reporting this error: + +``` +Error: Extension context invalidated. +``` + +The `ctx` object provides several helpers to stop asynchronous code from running once the context is invalidated: + +```ts +ctx.addEventListener(...); +ctx.setTimeout(...); +ctx.setInterval(...); +ctx.requestAnimationFrame(...); +// and more +``` + +You can also check if the context is invalidated manually: + +```ts +if (ctx.isValid) { + // do something +} +// OR +if (ctx.isInvalid) { + // do something +} +``` + +## CSS + +In regular web extensions, CSS for content scripts is usually a separate CSS file, that is added to a CSS array in the manifest: + +```json +{ + "content_scripts": [ + { + "css": ["content/style.css"], + "js": ["content/index.js"], + "matches": ["*://*/*"] + } + ] +} +``` + +In WXT, to add CSS to a content script, simply import the CSS file into your JS entrypoint, and WXT will automatically add the bundled CSS output to the `css` array. + +```ts +// entrypoints/content/index.ts +import './style.css'; + +export default defineContentScript({ + // ... +}); +``` + +To create a standalone content script that only includes a CSS file: + +1. Create the CSS file: `entrypoints/example.content.css` +2. Use the `build:manifestGenerated` hook to add the content script to the manifest: + ```ts + // wxt.config.ts + export default defineConfig({ + hooks: { + "build:manifestGenerated": (wxt, manifest) => { + manifest.content_scripts ??= []; + manifest.content_scripts.push({ + // Build extension once to see where your CSS get's written to + css: ["content-scripts/example.css"], + matches: ["*://*/*"] + ) + } + } + }) + ``` + +## UI + +WXT provides 3 built-in utilities for adding UIs to a page from a content script: + +- [Integrated](#integrated) - `createIntegratedUi` +- [Shadow Root](#shadow-root) -`createShadowRootUi` +- [IFrame](#iframe) - `createIframeUi` Each has their own set of advantages and disadvantages. @@ -12,7 +106,7 @@ Each has their own set of advantages and disadvantages. | Shadow Root | ✅ | ✅ (off by default) | ❌ | ✅ | | IFrame | ✅ | ✅ | ✅ | ❌ | -## Integrated +### Integrated Integrated content script UIs are injected alongside the content of a page. This means that they are affected by CSS on that page. @@ -26,6 +120,7 @@ export default defineContentScript({ main(ctx) { const ui = createIntegratedUi(ctx, { position: 'inline', + anchor: 'body', onMount: (container) => { // Append children to the container const app = document.createElement('p'); @@ -51,6 +146,7 @@ export default defineContentScript({ main(ctx) { const ui = createIntegratedUi(ctx, { position: 'inline', + anchor: 'body', onMount: (container) => { // Create the app and mount it to the UI container const app = createApp(App); @@ -80,6 +176,7 @@ export default defineContentScript({ main(ctx) { const ui = createIntegratedUi(ctx, { position: 'inline', + anchor: 'body', onMount: (container) => { // Create a root on the UI container and render a component const root = ReactDOM.createRoot(container); @@ -108,6 +205,7 @@ export default defineContentScript({ main(ctx) { const ui = createIntegratedUi(ctx, { position: 'inline', + anchor: 'body', onMount: (container) => { // Create the Svelte app inside the UI container const app = new App({ @@ -137,6 +235,7 @@ export default defineContentScript({ main(ctx) { const ui = createIntegratedUi(ctx, { position: 'inline', + anchor: 'body', onMount: (container) => { // Render your app to the UI container const unmount = render(() =>
...
, container); @@ -157,13 +256,11 @@ export default defineContentScript({ See the [API Reference](/api/reference/wxt/client/functions/createIntegratedUi) for the complete list of options. -You can control how CSS is injected for an integrated content script UI with the [`cssInjectionMode`](/api/reference/wxt/interfaces/BaseContentScriptEntrypointOptions#cssinjectionmode) property. Usually, you'll want to leave it as `"manifest"`, the default, so the UI inherits its style from the website's CSS. - -## Shadow Root +### Shadow Root Often in web extensions, you don't want your content script's CSS affecting the page, or vise-versa. The [`ShadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot) API is ideal for this. -WXT's [`createShadowRootUi`](/api/reference/wxt/client/functions/createShadowRootUi) abstracts all the `ShadowRoot` setup away, making it easy to create UIs with isolated CSS. It also supports an optional `isolateEvents` parameter to further isolate user interactions. +WXT's [`createShadowRootUi`](/api/reference/wxt/client/functions/createShadowRootUi) abstracts all the `ShadowRoot` setup away, making it easy to create UIs whose styles are isolated from the page. It also supports an optional `isolateEvents` parameter to further isolate user interactions. To use `createShadowRootUi`, follow these steps: @@ -188,6 +285,7 @@ export default defineContentScript({ const ui = await createShadowRootUi(ctx, { name: 'example-ui', position: 'inline', + anchor: 'body', onMount(container) { // Define how your UI will be mounted inside the container const app = document.createElement('p'); @@ -218,6 +316,7 @@ export default defineContentScript({ const ui = await createShadowRootUi(ctx, { name: 'example-ui', position: 'inline', + anchor: 'body', onMount: (container) => { // Define how your UI will be mounted inside the container const app = createApp(App); @@ -252,6 +351,7 @@ export default defineContentScript({ const ui = await createShadowRootUi(ctx, { name: 'example-ui', position: 'inline', + anchor: 'body', onMount: (container) => { // Container is a body, and React warns when creating a root on the body, so create a wrapper div const app = document.createElement('div'); @@ -289,6 +389,7 @@ export default defineContentScript({ const ui = await createShadowRootUi(ctx, { name: 'example-ui', position: 'inline', + anchor: 'body', onMount: (container) => { // Create the Svelte app inside the UI container const app = new App({ @@ -323,6 +424,7 @@ export default defineContentScript({ const ui = await createShadowRootUi(ctx, { name: 'example-ui', position: 'inline', + anchor: 'body', onMount: (container) => { // Render your app to the UI container const unmount = render(() =>
...
, container); @@ -343,17 +445,18 @@ export default defineContentScript({ See the [API Reference](/api/reference/wxt/client/functions/createShadowRootUi) for the complete list of options. -:::info TailwindCSS -`createShadowRootUi` supports TailwindCSS out of the box! When importing the styles, just import the main CSS file containing the `@tailwind` directives, and everything will just work :+1:. -::: +Full examples: -## IFrame +- [react-content-script-ui](https://github.com/wxt-dev/examples/tree/main/examples/react-content-script-ui) +- [tailwindcss](https://github.com/wxt-dev/examples/tree/main/examples/tailwindcss) + +### IFrame If you don't need to run your UI in the same frame as the content script, you can use an IFrame to host your UI instead. Since an IFrame just hosts an HTML page, **_HMR is supported_**. WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/client/functions/createIframeUi), which simplifies setting up the IFrame. -1. Create an HTML page that will be loaded into your IFrame +1. Create an HTML page that will be loaded into your IFrame: ```html @@ -368,7 +471,7 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/client/fun ``` -1. Add the page to the manifest's `web_accessible_resources` +1. Add the page to the manifest's `web_accessible_resources`: ```ts // wxt.config.ts export default defineConfig({ @@ -382,7 +485,7 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/client/fun }, }); ``` -1. Create and mount the IFrame +1. Create and mount the IFrame: ```ts export default defineContentScript({ @@ -393,6 +496,7 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/client/fun const ui = createIframeUi(ctx, { page: '/example-iframe.html', position: 'inline', + anchor: 'body', onMount: (wrapper, iframe) => { // Add styles to the iframe like width iframe.width = '123'; @@ -406,3 +510,104 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/client/fun ``` See the [API Reference](/api/reference/wxt/client/functions/createIframeUi) for the complete list of options. + +## Isolated World vs Main World + +By default, all content scripts run in an isolated context where only the DOM is shared with the webpage it is running on - an "isolated world". In MV3, Chromium introduced the ability to run content scripts in the "main" world - where everything, not just the DOM, is available to the content script, just like if the script were loaded by the webpage. + +You can enable this for a content script by setting the `world` option: + +```ts +export default defineContentScript({ + world: 'MAIN', +}); +``` + +However, this approach has several notable drawbacks: + +- Doesn't support MV2 +- `world: "MAIN"` is only supported by Chromium browsers +- Main world content scripts don't have access to the extension API + +Instead, WXT recommends injecting a script into the main world manually using it's `injectScript` function. This will address the drawbacks mentioned before. + +- `injectScript` supports both MV2 and MV3 +- `injectScript` supports all browsers +- Having a "parent" content script means you can send messages back and forth, making it possible to access the extension API + +To use `injectScript`, we need two entrypoints, one content script and one unlisted script: + + +```html +📂 entrypoints/ + 📄 example.content.ts + 📄 example-main-world.ts +``` + +```ts +// entrypoints/example-main-world.ts +export default defineUnlistedScript(() => { + console.log('Hello from the main world!'); +}); +``` + +```ts +// entrypoints/example.content.ts +export default defineContentScript(async () => { + await injectScript('/example-main-world.js', { + keepInDom: true, + }); +}); +``` + +`injectScript` works by creating a `script` element on the page pointing to your script. This loads the script into the page's context so it runs in the main world. + +`injectScript` returns a promise, that when resolved, means the script has been evaluated by the browser and you can start communicating with it. + +:::warning Warning: `run_at` Caveat +For MV3, `injectScript` is synchronous and the injected script will be evaluated at the same time as your the content script's `run_at`. + +However for MV2, `injectScript` has to `fetch` the script's text content and create an inline ` + +``` + +## Background + +In your background script, set `type: "module"`: + +```ts +export default defineBackground({ + type: 'module', // !code ++ + main() { + // ... + }, +}); +``` + +:::warning +Only MV3 supports ESM background scripts/service workers. When targeting MV2, the `type` option is ignored and the background is always bundled into a single file as IIFE. +::: + +## Content Scripts + +WXT does not yet include built-in support for ESM content scripts. The plan is to add support for chunking to reduce bundle size, but not support HMR for now. There are several technical issues that make implementing a generic solution for HMR impossible. See [Content Script ESM Support #357](https://github.com/wxt-dev/wxt/issues/357) for details. + +If you can't wait, and need ESM support right now, you can implement ESM support manually. See the [ESM Content Script UI](https://github.com/wxt-dev/examples/tree/main/examples/esm-content-script-ui) example to get started. diff --git a/docs/guide/essentials/extension-apis.md b/docs/guide/essentials/extension-apis.md new file mode 100644 index 00000000..529558d8 --- /dev/null +++ b/docs/guide/essentials/extension-apis.md @@ -0,0 +1,92 @@ +# Extension APIs + +[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/api) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs) + +Different browsers provide different global variables for accessing the extension APIs (chrome provides `chrome`, firefox provides `browser`, etc). + +WXT simplifies this - always use `browser`: + +```ts +browser.action.onClicked.addListener(() => { + // ... +}); +``` + +Other than that, refer to Chrome and Mozilla's documentation for how to use specific APIs. Everything a normal extension can do, WXT can do as well, just via `browser` instead of `chrome`. + +## Webextension Polyfill + +> Since `v0.1.0` + +By default, WXT uses the [`webextension-polyfill` by Mozilla](https://www.npmjs.com/package/webextension-polyfill) to make the extension API consistent between browsers. + +To access types, you should import the relevant namespace from `wxt/browser`: + +```ts +import { Runtime } from 'wxt/browser'; + +function handleMessage(message: any, sender: Runtime.Sender) { + // ... +} +``` + +### Disabling the polyfill + +> Since `v0.19.0` + +After the release of MV3 and Chrome's official deprecation of MV2 in June 2024, the polyfill isn't really doing anything useful anymore. + +You can disable it with a single line: + +```ts +// wxt.config.ts +export default defineConfig({ + extensionApi: 'chrome', +}); +``` + +This will change `wxt/browser` to simply export the `browser` or `chrome` globals based on browser at runtime: + +<<< @/../packages/wxt/src/browser/chrome.ts#snippet + +Accessing types is a little different with the polyfill disabled. They do not need to be imported; they're available on the `browser` object itself: + +```ts +function handleMessage(message: any, sender: browser.runtime.Sender) { + // ... +} +``` + +## Feature Detection + +Depending on the manifest version and browser, some APIs are not available at runtime. If an API is not available, it will be `undefined`. + +:::warning +Types will not help you here. The types WXT provides for `browser` assume all APIs exist. You are responsible for knowing whether an API is available or not. +::: + +To check if an API is available, use feature detection: + +```ts +if (browser.runtime.onSuspend != null) { + browser.runtime.onSuspend.addListener(() => { + // ... + }); +} +``` + +Here, [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) is your best friend: + +```ts +browser.runtime.onSuspend?.addListener(() => { + // ... +}); +``` + +Alternatively, if you're trying to use similar APIs under different names (to support MV2 and MV3), you can do something like this: + +```ts +(browser.action ?? browser.browser_action).onClicked.addListener(() => { + // +}); +``` diff --git a/docs/guide/key-concepts/frontend-frameworks.md b/docs/guide/essentials/frontend-frameworks.md similarity index 56% rename from docs/guide/key-concepts/frontend-frameworks.md rename to docs/guide/essentials/frontend-frameworks.md index c5295310..27657a35 100644 --- a/docs/guide/key-concepts/frontend-frameworks.md +++ b/docs/guide/essentials/frontend-frameworks.md @@ -2,7 +2,7 @@ ## Built-in Modules -WXT has preconfigured modules for 4 frameworks: +WXT has preconfigured modules for the most popular frontend frameworks: - [`@wxt-dev/module-react`](https://github.com/wxt-dev/wxt/tree/main/packages/module-react) - [`@wxt-dev/module-vue`](https://github.com/wxt-dev/wxt/tree/main/packages/module-vue) @@ -64,45 +64,36 @@ export default defineConfig({ }); ``` -The WXT modules just simplify the configuration and add auto-imports. They're not much different than the above. +> The WXT modules just simplify the configuration and add auto-imports. They're not much different than the above. ## Multiple Apps -Since web extensions usually contain multiple UIs as separate HTML files (popup, options, changelog, side panel, etc), you'll need to create individual app instances, one per HTML page. +Since web extensions usually contain multiple UIs across multiple entrypoints (popup, options, changelog, side panel, content scripts, etc), you'll need to create individual app instances, one per entrypoint. -Usually, this means each entrypoint should be a directory with it's own files inside it: +Usually, this means each entrypoint should be a directory with it's own files inside it. Here's the recommended folder structure: -``` -/ - ├ assets/ <------------------ Put shared assets here - │ ├ style.css <------------ Like styles all your pages share - │ └ ... - ├ components/ <-------------- Put shared components here - │ └ ... - └ entrypoints/ - ├ popup/ <--------------- Use a folder with an index.html file in it - │ ├ index.html - │ ├ main.tsx <--------- Create and mount your app here - │ ├ style.css <-------- Have some global styles to apply? - │ └ ... <--------------- Rest of the files can be named whatever - └ options/ - ├ pages/ <------------ A good place to put your router pages - │ ├ [id]/ - │ │ └ details.tsx - │ ├ index.tsx - │ └... - ├ index.html - ├ App.vue - ├ main.ts - ├ style.css - └ router.ts + +```html +📂 {srcDir}/ + 📂 assets/ <---------- Put shared assets here + 📄 tailwind.css + 📂 components/ + 📄 Button.tsx + 📂 entrypoints/ + 📂 options/ <--------- Use a folder with an index.html file in it + 📁 pages/ <--------- A good place to put your router pages if you have them + 📄 index.html + 📄 App.tsx + 📄 main.tsx <--------- Create and mount your app here + 📄 style.css <--------- Entrypoint-specific styles + 📄 router.ts ``` ## Configuring Routers -Lots of frameworks come with routers for building a multi-page app using the URL's path. Chrome extensions don't work like this. Since HTML files are static, `chrome-extension://{id}/popup.html`, there's no way to change the entire path for routing. +All frameworks come with routers for building a multi-page app using the URL's path... But web extensions don't work like this. Since HTML files are static, `chrome-extension://{id}/popup.html`, there's no way to change the entire path for routing. -Instead, you need to configure the router to run in "hash" mode, where the routing information is apart of the URL's hash, not the path (ie: `popup.html#/` and `popup.html#/account/settings`). +Instead, you need to configure the router to run in "hash" mode, where the routing information is a part of the URL's hash, not the path (ie: `popup.html#/` and `popup.html#/account/settings`). Refer to your router's docs for information about hash mode and how to enable it. Here's a non-extensive list of a few popular routers: diff --git a/docs/guide/essentials/i18n.md b/docs/guide/essentials/i18n.md new file mode 100644 index 00000000..9c012986 --- /dev/null +++ b/docs/guide/essentials/i18n.md @@ -0,0 +1,86 @@ +# I18n + +[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/api/i18n) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n) + +This page discusses how to setup internationalization using the vanilla `browser.i18n` APIs and mentions some alternatives if you want to use something else. + +[[toc]] + +## Usage + +1. Add `default_locale` to your manifest: + ```ts + export default defineConfig({ + manifest: { + default_locale: 'en', + }, + }); + ``` +2. Create `messages.json` files in the `public/` directory: + + + ```html + 📂 {srcDir}/ + 📂 public/ + 📂 _locales/ + 📂 en/ + 📄 messages.json + 📂 de/ + 📄 messages.json + 📂 ko/ + 📄 messages.json + ``` + + ```jsonc + // public/_locales/en/messages.json + { + "helloWorld": { + "message": "Hello world!", + }, + } + ``` + +3. Get the translation: + ```ts + browser.i18n.getMessage('helloWorld'); + ``` +4. _Optional_: Add translations for extension name and description: + +```json +{ + "extName": { + "message": "..." + }, + "extDescription": { + "message": "..." + }, + "helloWorld": { + "message": "Hello world!" + } +} +``` + +```ts +export default defineConfig({ + manifest: { + name: '__MSG_extName__', + description: '__MSG_extDescription__', + default_locale: 'en', + }, +}); +``` + +## Alternatives + +The vanilla API has very few features, which is why you may want to consider using third-party NPM packages like `i18next`, `react-i18n`, `vue-i18n`, etc. + +However, it is recommended you stick with the vanilla API (or a package based on top of the vanilla API, like [`@wxt-dev/i18n`](/i18n)), because: + +- They can localize text in your manifest and CSS files +- Translations are loaded synchronously +- Translations are not bundled multiple times, keeping your extension small +- Zero configuration + +Here are some examples of how to setup a third party i18n library: + +- [vue-i18n](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vue-i18n) diff --git a/docs/guide/essentials/messaging.md b/docs/guide/essentials/messaging.md new file mode 100644 index 00000000..2cb8d5bd --- /dev/null +++ b/docs/guide/essentials/messaging.md @@ -0,0 +1,16 @@ +# Messaging + +[Chrome Docs](https://developer.chrome.com/docs/extensions/develop/concepts/messaging) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#communicating_with_background_scripts) + +Read the docs linked above to learn more about using the vanilla messaging APIs. + +## Alternatives + +The vanilla APIs are difficult to use and are a pain point to many new extension developers. For this reason, WXT recommends installing an NPM package that wraps around the vanilla APIs. + +Here are some popular messaging libraries that support all browsers and work with WXT: + +- [`trpc-chrome`](https://www.npmjs.com/package/trpc-chrome) - [tRPC](https://trpc.io/) adapter for Web Extensions. +- [`webext-bridge`](https://www.npmjs.com/package/webext-bridge) - Messaging in WebExtensions made super easy. Out of the box. +- [`@webext-core/messaging`](https://www.npmjs.com/package/@webext-core/messaging) - Light weight, type-safe wrapper around the web extension messaging APIs +- [`@webext-core/proxy-service`](https://www.npmjs.com/package/@webext-core/proxy-service) - A type-safe wrapper around the web extension messaging APIs that lets you call a function from anywhere, but execute it in the background. diff --git a/docs/guide/essentials/project-structure.md b/docs/guide/essentials/project-structure.md new file mode 100644 index 00000000..d8cb3959 --- /dev/null +++ b/docs/guide/essentials/project-structure.md @@ -0,0 +1,101 @@ +# Project Structure + +WXT follows a strict project structure. By default, it's a flat folder structure that looks like this: + + +```html +📂 {rootDir}/ + 📁 .output/ + 📁 .wxt/ + 📁 assets/ + 📁 components/ + 📁 composables/ + 📁 entrypoints/ + 📁 hooks/ + 📁 modules/ + 📁 public/ + 📁 utils/ + 📄 .env + 📄 .env.publish + 📄 app.config.ts + 📄 package.json + 📄 tsconfig.json + 📄 web-ext.config.ts + 📄 wxt.config.ts +``` + +Here's a brief summary of each of these files and directories: + +- `.output/`: All build artifacts will go here +- `.wxt/`: Generated by WXT, it contains TS config +- `assets/`: Contains all CSS, images, and other assets that should be processed by WXT +- `components/`: Auto-imported by default, contains UI components +- `composables/`: Auto-imported by default, contains composable functions for Vue +- `entrypoints/`: Contains all the entrypoints that get bundled into your extension +- `hooks/`: Auto-imported by default, contains hooks for React and Solid +- `public/`: Contains any files you want to copy into the output folder as-is, without being processed by WXT +- `utils/`: Auto-imported by default, contains generic utilities used throughout your project +- `.env`: Contains [Environment Variables](/guide/essentials/config/runtime#environment-variables) +- `.env.publish`: Contains Environment Variables for [publishing](/guide/essentials/publishing) +- `app.config.ts`: Contains [Runtime Config](/guide/essentials/config/runtime) +- `package.json`: The standard file used by your package manager +- `tsconfig.json`: Config telling TypeScript how to behave +- `web-ext.config.ts`: Configure [Browser Startup](/guide/essentials/config/browser-startup) +- `wxt.config.ts`: The main config file for WXT projects + +## Adding a `src/` Directory + +Many developers like having a `src/` directory to separate source code from configuration files. You can enable it inside the `wxt.config.ts` file: + +```ts +// wxt.config.ts +export default defineConfig({ + srcDir: 'src', +}); +``` + +After enabling it, your project structure should look like this: + + +```html +📂 {rootDir}/ +📁 .output/ +📁 .wxt/ +📂 src/ + 📁 assets/ + 📁 components/ + 📁 composables/ + 📁 entrypoints/ + 📁 hooks/ + 📁 modules/ + 📁 public/ + 📁 utils/ + 📄 app.config.ts +📄 .env +📄 .env.publish +📄 package.json +📄 tsconfig.json +📄 web-ext.config.ts +📄 wxt.config.ts +``` + +## Customizing Other Directories + +You can configure the following directories: + + +```ts +// wxt.config.ts +export default defineConfig({ + // Relative to project root + srcDir: "src", // default: "." + outDir: "dist", // default: ".output" + + // Relative to srcDir + entrypointsDir: "entries", // default: "entrypoints" + modulesDir: "wxt-modules", // default: "modules" + publicDir: "static", // default: "public" +}) +``` + +You can use absolute or relative paths. diff --git a/docs/get-started/publishing.md b/docs/guide/essentials/publishing.md similarity index 84% rename from docs/get-started/publishing.md rename to docs/guide/essentials/publishing.md index f51373fd..7edf7d1d 100644 --- a/docs/get-started/publishing.md +++ b/docs/guide/essentials/publishing.md @@ -4,7 +4,7 @@ outline: deep # Publishing -WXT will help you ZIP your extensions and submit them to the stores for review. +WXT can ZIP your extension and submit it to various stores for review or for self-hosting. ## First Time Publishing @@ -18,7 +18,7 @@ For specific details about each store, see the stores sections below. ## Automation -WXT provides two commands to help automate the release process: +WXT provides two commands to help automate submitting a new version for review and publishing: - `wxt submit init`: Setup all the required secrets and options for the `wxt submit` command - `wxt submit`: Submit new versions of your extension for review (and publish them automatically once approved) @@ -27,7 +27,7 @@ To get started, run `wxt submit init` and follow the prompts. Once finished, you > In CI, make sure you add all the environment variables to the submit step. -To release an update, build all the ZIPs you plan on releasing: +To submit a new version for publishing, build all the ZIPs you plan on releasing: ```sh wxt zip @@ -36,29 +36,25 @@ wxt zip -b firefox Then run the `wxt submit` command, passing in all the ZIP files you want to release. In this case, we'll do a release for all 3 major stores: Chrome Web Store, Edge Addons, and Firefox Addons Store. -If it's your first time running the command, you'll want to test your secrets by passing the `--dry-run` flag: +If it's your first time running the command or you recently made changes to the release process, you'll want to test your secrets by passing the `--dry-run` flag. ```sh wxt submit --dry-run \ - --chrome-zip .output/--chrome.zip \ - --firefox-zip .output/--firefox.zip --firefox-sources-zip .output/--sources.zip \ - --edge-zip .output/--chrome.zip + --chrome-zip .output/{your-extension}-{version}-chrome.zip \ + --firefox-zip .output/{your-extension}-{version}-firefox.zip --firefox-sources-zip .output/{your-extension}-{version}-sources.zip \ + --edge-zip .output/{your-extension}-{version}-chrome.zip ``` If the dry run passes, remove the flag and do the actual release: ```sh wxt submit \ - --chrome-zip .output/--chrome.zip \ - --firefox-zip .output/--firefox.zip --firefox-sources-zip .output/--sources.zip \ - --edge-zip .output/--chrome.zip + --chrome-zip .output/{your-extension}-{version}-chrome.zip \ + --firefox-zip .output/{your-extension}-{version}-firefox.zip --firefox-sources-zip .output/{your-extension}-{version}-sources.zip \ + --edge-zip .output/{your-extension}-{version}-chrome.zip ``` -:::tip -If you only need to release to a single store, only pass that store's ZIP flag. -::: - -:::tip +:::warning See the [Firefox Addon Store](#firefox-addon-store) section for more details about the `--firefox-sources-zip` option. ::: @@ -133,9 +129,7 @@ wxt zip Firefox requires you to upload a ZIP of your source code. This allows them to rebuild your extension and review the code in a readable way. More details can be found in [Firefox's docs](https://extensionworkshop.com/documentation/publish/source-code-submission/). -WXT fully supports generating and automatically submitting a source code ZIP. - -When you run `wxt zip -b firefox`, your sources are zipped into the `.output` directory alongside the extension. WXT will automatically exclude certain files such as config files, hidden files, and tests. However, it's important to manually check the ZIP to ensure it only contains the files necessary to rebuild your extension. +When running `wxt zip -b firefox`, WXT will zip both your extension and sources. Certain files (such as config files, hidden files, and tests) are automatically excluded from your sources. However, it's important to manually check the ZIP to ensure it only contains the files necessary to rebuild your extension. To customize which files are zipped, add the `zip` option to your config file. @@ -181,7 +175,7 @@ Ensure that you have a `README.md` or `SOURCE_CODE_REVIEW.md` file with the abov Make sure the build output is the exact same when running `wxt build -b firefox` in your main project and inside the zipped sources. :::warning -If you use a `.env` files, they can effect the chunk hashes in the output directory. Either delete the .env file before running `wxt zip -b firefox`, or include it in your sources zip with the [`zip.includeSources`](/api/reference/wxt/interfaces/InlineConfig#includesources) option. Be careful to not include any secrets in your `.env` files. +If you use a `.env` files, they can affect the chunk hashes in the output directory. Either delete the .env file before running `wxt zip -b firefox`, or include it in your sources zip with the [`zip.includeSources`](/api/reference/wxt/interfaces/InlineConfig#includesources) option. Be careful to not include any secrets in your `.env` files. See Issue [#377](https://github.com/wxt-dev/wxt/issues/377) for more details. ::: @@ -205,7 +199,7 @@ export default defineConfig({ Depending on your package manager, the `package.json` in the sources zip will be modified to use the downloaded dependencies via the `overrides` or `resolutions` field. :::warning -WXT uses the command `npm pack ` to download the package. That means regardless of your package manager, you need to properly setup a `.npmrc` file. NPM and PNPM both respect `.npmrc` files, but Yarn and Bun have their own ways of authorizing private registries, so you'll need to add an `.npmrc` file. +WXT uses the command `npm pack ` to download the package. That means regardless of your package manager, you need to properly setup a `.npmrc` file. NPM and PNPM both respect `.npmrc` files, but Yarn and Bun have their own ways of authorizing private registries, so you'll need to add a `.npmrc` file. ::: ### Safari diff --git a/docs/guide/go-further/remote-code.md b/docs/guide/essentials/remote-code.md similarity index 100% rename from docs/guide/go-further/remote-code.md rename to docs/guide/essentials/remote-code.md diff --git a/docs/guide/extension-apis/scripting.md b/docs/guide/essentials/scripting.md similarity index 70% rename from docs/guide/extension-apis/scripting.md rename to docs/guide/essentials/scripting.md index 9565a739..53b8a304 100644 --- a/docs/guide/extension-apis/scripting.md +++ b/docs/guide/essentials/scripting.md @@ -1,12 +1,12 @@ -# `browser.scripting` +# Scripting -[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/api/scripting) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting) +[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/api/scripting) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting) Refer to the browser docs above for basics on how the API works. ## Execute Script Return Values -When using `browser.scripting.executeScript`, you can execute content scripts or unlisted scripts. To return a value, just return a value from the script's main function. +When using `browser.scripting.executeScript`, you can execute content scripts or unlisted scripts. To return a value, just return a value from the script's `main` function. ```ts // entrypoints/background.ts @@ -19,7 +19,7 @@ console.log(res); // "Hello John!" ```ts // entrypoints/injected.js -export default defineContentScript(() => { +export default defineUnlistedScript(() => { console.log('Script was injected!'); return 'Hello John!'; }); diff --git a/docs/guide/essentials/storage.md b/docs/guide/essentials/storage.md new file mode 100644 index 00000000..5213e115 --- /dev/null +++ b/docs/guide/essentials/storage.md @@ -0,0 +1,15 @@ +# Storage + +[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/api/storage) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage) + +You can use the vanilla APIs (see docs above), use [WXT's built-in storage API](/storage), or install a package from NPM. + +## Alternatives + +1. [`wxt/storage`](/storage) (recommended): WXT ships with its own wrapper around the vanilla storage APIs that simplifies common use cases + +2. DIY: If you're migrating to WXT and already have a storage wrapper, keep using it. In the future, if you want to delete that code, you can use one of these alternatives, but there's no reason to replace working code during a migration. + +3. Any other NPM package: [There are lots of wrappers around the storage API](https://www.npmjs.com/search?q=chrome%20storage), you can find one you like. Here's some popular ones: + - [`webext-storage`](https://www.npmjs.com/package/webext-storage) - A more usable typed storage API for Web Extensions + - [`@webext-core/storage`](https://www.npmjs.com/package/@webext-core/storage) - A type-safe, localStorage-esque wrapper around the web extension storage APIs diff --git a/docs/guide/essentials/target-different-browsers.md b/docs/guide/essentials/target-different-browsers.md new file mode 100644 index 00000000..680042dd --- /dev/null +++ b/docs/guide/essentials/target-different-browsers.md @@ -0,0 +1,69 @@ +# Targeting Different Browsers + +When building an extension with WXT, you can create multiple builds of your extension targeting different browsers and manifest versions. + +## Target a Browser + +Use the `-b` CLI flag to create a separate build of your extension for a specific browser. By default, `chrome` is targetted. + +```sh +wxt # same as: wxt -b chrome +wxt -b firefox +wxt -b custom +``` + +Targeting a browser has several effects: + +1. During development, when passing `firefox`, WXT will automatically open Firefox with the extension installed. For all other browsers, it will open Chrome/Chromium +2. Changes build-time constants provided by WXT: + - `import.meta.env.BROWSER`: A string, the targeted browser + - `import.meta.env.CHROME`: A boolean equivalent to `import.meta.env.BROWSER === "chrome"` + - `import.meta.env.FIREFOX`: A boolean equivalent to `import.meta.env.BROWSER === "firefox"` + - `import.meta.env.EDGE`: A boolean equivalent to `import.meta.env.BROWSER === "edge"` + - `import.meta.env.SAFARI`: A boolean equivalent to `import.meta.env.BROWSER === "safari"` + - `import.meta.env.OPERA`: A boolean equivalent to `import.meta.env.BROWSER === "opera"` + +## Target a Manifest Version + +To target specific manifest versions, use the `--mv2` or `--mv3` CLI flags. + +:::tip Default Manifest Version +By default, WXT will target MV2 for Safari and Firefox and MV3 for all other browers. +::: + +To get the target manifest version at runtime, use the built-time constant provided by WXT: + +- `import.meta.env.MANIFEST_VERSION`: A number, either `2` or `3` + +## Filtering Entrypoints + +Every entrypoint can be included or excluded when targetting specific browsers via the `include` and `exclude` options. + +Here are some examples: + +- Content script only built when targetting `firefox`: + + ```ts + export default defineContentScript({ + include: ['firefox'], + + main(ctx) { + // ... + }, + }); + ``` + +- HTML file only built for all targets other than `chrome`: + ```html + + + + + + + + + + + + ``` diff --git a/docs/guide/essentials/testing-updates.md b/docs/guide/essentials/testing-updates.md new file mode 100644 index 00000000..e578778b --- /dev/null +++ b/docs/guide/essentials/testing-updates.md @@ -0,0 +1,28 @@ +# Testing Updates + +## Testing Permission Changes + +When `permissions`/`host_permissions` change during an update, depending on what exactly changed, the browser will disable your extension until the user accepts the new permissions. + +You can test if your permission changes will result in a disabled extension: + +- Chromium: Use [Google's Extension Update Testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool) +- Firefox: See their [Test Permission Requests](https://extensionworkshop.com/documentation/develop/test-permission-requests/) page +- Safari: Everyone breaks something in production eventually... 🫡 Good luck soldier + +## Update Event + +You can setup a callback that runs after your extension updates like so: + +```ts +browser.runtime.onInstalled.addListener(({ reason }) => { + if (reason === 'update') { + // Do something + } +}); +``` + +If the logic is simple, write a unit test to cover this logic. If you feel the need to manually test this callback, you can either: + +1. In dev mode, remove the `if` statement and reload the extension from `chrome://extensions` +2. Use [Google's Extension Update Testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool) diff --git a/docs/guide/essentials/unit-testing.md b/docs/guide/essentials/unit-testing.md new file mode 100644 index 00000000..43f38b5a --- /dev/null +++ b/docs/guide/essentials/unit-testing.md @@ -0,0 +1,80 @@ +# Unit Testing + +[[toc]] + +## Vitest + +WXT provides first class support for Vitest for unit testing: + +```ts +// vitest.config.ts +import { defineConfig } from 'vitest/config'; +import { WxtVitest } from 'wxt/testing'; + +export default defineConfig({ + plugins: [WxtVitest()], +}); +``` + +This plugin does several things: + +- Polyfills the extension API, `browser`, with an in-memory implementation using [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/fake-browser/installation) +- Adds all vite config or plugins in `wxt.config.ts` +- Configures auto-imports (if enabled) +- Applies internal WXT vite plugins for things like [bundling remote code](/guide/essentials/remote-code) +- Sets up global variables provided by WXT (`import.meta.env.BROWSER`, `import.meta.env.MANIFEST_VERSION`, `import.meta.env.IS_CHROME`, etc) +- Configures aliases (`@/*`, `@@/*`, etc) so imports can be resolved + +Here are real projects with unit testing setup. Look at the code and tests to see how they're written. + +- [`aklinker1/github-better-line-counts`](https://github.com/aklinker1/github-better-line-counts) +- [`wxt-dev/examples`'s Vitest Example](https://github.com/wxt-dev/examples/tree/main/examples/vitest-unit-testing) + +### Example Tests + +This example demonstrates that you don't have to mock `browser.storage` (used by `wxt/storage`) in tests - [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/fake-browser/installation) implements storage in-memory so it behaves like it would in a real extension! + +```ts +import { describe, it, expect } from 'vitest'; +import { fakeBrowser } from 'wxt/testing'; + +const accountStorage = storage.defineItem('local:account'); + +async function isLoggedIn(): Promise { + const value = await accountStorage.getValue(); + return value != null; +} + +describe('isLoggedIn', () => { + beforeEach(() => { + // See https://webext-core.aklinker1.io/fake-browser/reseting-state + fakeBrowser.reset(); + }); + + it('should return true when the account exists in storage', async () => { + const account: Account = { + username: '...', + preferences: { + // ... + }, + }; + await accountStorage.setValue(account); + + expect(await isLoggedIn()).toBe(true); + }); + + it('should return false when the account does not exist in storage', async () => { + await accountStorage.deleteValue(); + + expect(await isLoggedIn()).toBe(false); + }); +}); +``` + +## Other Testing Frameworks + +To use a different framework, you will likely have to disable auto-imports, setup import aliases, manually mock the extension APIs, and setup the test environment to support all of WXT's features that you use. + +It is possible to do, but will require a bit more setup. Refer to Vitest's setup for an example of how to setup a test environment: + +https://github.com/wxt-dev/wxt/blob/main/packages/wxt/src/testing/wxt-vitest-plugin.ts diff --git a/docs/guide/essentials/wxt-modules.md b/docs/guide/essentials/wxt-modules.md new file mode 100644 index 00000000..e7fa2d4e --- /dev/null +++ b/docs/guide/essentials/wxt-modules.md @@ -0,0 +1,242 @@ +# WXT Modules + +WXT provides a "module system" that let's you run code at different steps in the build process to modify it. + +[[toc]] + +## Adding a Module + +There are two ways to add a module to your project: + +1. **NPM**: install an NPM package, like [`@wxt-dev/auto-icons`](https://www.npmjs.com/package/@wxt-dev/auto-icons) and add it to your config: + ```ts + // wxt.config.ts + export default defineConfig({ + modules: ['@wxt-dev/auto-icons'], + }); + ``` + > Searching for ["wxt module"](https://www.npmjs.com/search?q=wxt%20module) on NPM is a good way to find published WXT modules. +2. **Local**: add a file to your project's `modules/` directory: + ``` + / + modules/ + my-module.ts + ``` + > To learn more about writing your own modules, read the [Writing Modules](/guide/essentials/wxt-modules) docs. + +## Module Options + +WXT modules may require or allow setting custom options to change their behavior. There are two types of options: + +1. **Build-time**: Any config used during the build process, like feature flags +2. **Runtime**: Any config accessed at runtime, like callback functions + +Build-time options are placed in your `wxt.config.ts`, while runtime options is placed in the [`app.config.ts` file](/guide/essentials/config/runtime). Refer to each module's documentation about what options are required and where they should be placed. + +If you use TypeScript, modules augment WXT's types so you will get type errors if options are missing or incorrect. + +## Writing Modules + +Here's what a basic WXT module looks like: + +```ts +import { defineWxtModule } from 'wxt/modules'; + +export default defineWxtModule({ + setup(wxt) { + // Your module code here... + }, +}); +``` + +Each module's setup function is executed after the `wxt.config.ts` file is loaded. The `wxt` object provides everything you need to write a module: + +- Use `wxt.hook(...)` to hook into the build's lifecycle and make changes +- Use `wxt.config` to get the resolved config from the project's `wxt.config.ts` file +- Use `wxt.logger` to log messages to the console +- and more! + +Refer to the [API reference](/api/reference/wxt/interfaces/Wxt) for a complete list of properties and functions available. + +Also to make sure and read about all the [hooks that are available](https://wxt.dev/api/reference/wxt/interfaces/WxtHooks) - they are essential to writing modules. + +### Recipes + +Modules are complex and require a deeper understanding of WXT's code and how it works. The best way to learn is by example. + +#### Update resolved config + +```ts +import { defineWxtModule } from 'wxt/modules'; + +export default defineWxtModule({ + setup(wxt) { + wxt.hook('ready', () => { + wxt.config.outDir = 'dist'; + }); + }, +}); +``` + +#### Add built-time config + +```ts +import { defineWxtModule } from 'wxt/modules'; +import 'wxt'; + +export interface MyModuleOptions { + // Add your build-time options here... +} +declare module 'wxt' { + export interface InlineConfig { + // Add types for the "myModule" key in wxt.config.ts + myModule: MyModuleOptions; + } +} + +export default defineWxtModule({ + configKey: 'myModule', + + // Build time config is available via the second argument of setup + setup(wxt, options) { + console.log(options); + }, +}); +``` + +#### Add runtime config + +```ts +import { defineWxtModule } from 'wxt/modules'; +import 'wxt/sandbox'; + +export interface MyModuleRuntimeOptions { + // Add your runtime options here... +} +declare module 'wxt/sandbox' { + export interface WxtAppConfig { + myModule: MyModuleOptions; + } +} +``` + +Runtime options are returned when calling + +```ts +const config = useAppConfig(); +console.log(config.myModule); +``` + +This is very useful when [generating runtime code](#generate-runtime-module). + +#### Generate output file + +```ts +import { defineWxtModule } from 'wxt/modules'; + +export default defineWxtModule({ + setup(wxt) { + // Relative to the output directory + const generatedFilePath = 'some-file.txt'; + + wxt.hook('build:publicAssets', (_, assets) => { + assets.push({ + relativeDest: generatedFilePath, + contents: 'some generated text', + }); + }); + + wxt.hook('build:manifestGenerated', (_, manifest) => { + manifest.web_accessible_resources ??= []; + manifest.web_accessible_resources.push({ + matches: ['*://*'], + resources: [generatedFilePath], + }); + }); + }, +}); +``` + +This file could then be loaded at runtime: + +```ts +const res = await fetch(browser.runtime.getURL('/some-text.txt')); +``` + +#### Generate runtime module + +Create a file in `.wxt`, add an alias to import it, and add auto-imports for exported variables. + +```ts +import { defineWxtModule } from 'wxt/modules'; +import { resolve } from 'node:path'; + +export default defineWxtModule({ + imports: [ + // Add auto-imports + { from: '#analytics', name: 'analytics' }, + { from: '#analytics', name: 'reportEvent' }, + { from: '#analytics', name: 'reportPageView' }, + ], + + setup(wxt) { + const analyticsModulePath = resolve( + wxt.config.wxtDir, + 'analytics/index.ts', + ); + const analyticsModuleCode = ` + import { createAnalytics } from 'some-module'; + + export const analytics = createAnalytics(useAppConfig().analytics); + export const { reportEvent, reportPageView } = analytics; + `; + + addAlias(wxt, '#analytics', analyticsModulePath); + + wxt.hook('prepare:types', async (_, entries) => { + entries.push({ + path: analyticsModulePath, + text: analyticsModuleCode, + }); + }); + }, +}); +``` + +#### Generate declaration file + +```ts +import { defineWxtModule } from 'wxt/modules'; +import { resolve } from 'node:path'; + +export default defineWxtModule({ + setup(wxt) { + const typesPath = resolve(wxt.config.wxtDir, 'my-module/types.d.ts'); + const typesCode = ` + // Declare global types, perform type augmentation + `; + + wxt.hook('prepare:types', async (_, entries) => { + entries.push({ + path: 'my-module/types.d.ts', + text: ` + // Declare global types, perform type augmentation, etc + `, + // IMPORTANT - without this line your declaration file will not be a part of the TS project: + tsReference: true, + }); + }); + }, +}); +``` + +### Example Modules + +You should also look through the code of modules other people have written and published. Here's some examples: + +- [`@wxt-dev/auto-icons`](https://github.com/wxt-dev/wxt/blob/main/packages/auto-icons) +- [`@wxt-dev/i18n`](https://github.com/wxt-dev/wxt/blob/main/packages/i18n) +- [`@wxt-dev/module-vue`](https://github.com/wxt-dev/wxt/blob/main/packages/module-vue) +- [`@wxt-dev/module-solid`](https://github.com/wxt-dev/wxt/blob/main/packages/module-solid) +- [`@wxt-dev/module-react`](https://github.com/wxt-dev/wxt/blob/main/packages/module-react) +- [`@wxt-dev/module-svelte`](https://github.com/wxt-dev/wxt/blob/main/packages/module-svelte) diff --git a/docs/guide/extension-apis/i18n.md b/docs/guide/extension-apis/i18n.md deleted file mode 100644 index e02e0134..00000000 --- a/docs/guide/extension-apis/i18n.md +++ /dev/null @@ -1,56 +0,0 @@ -# Internationalization - -This guide is for using the vanilla, `browser.i18n` APIs. There are two other alternatives: - -1. [`@wxt-dev/i18n`](/guide/i18n/installation) - a wrapper around `browser.i18n` APIs with additional features, a simplified localization file format, and editor support -2. Third party packages - You can use any i18n package on NPM, most of which are more feature rich than `browser.i18n` and `@wxt-dev/i18n` - -:::info -Currently, using the `browser.i18n` APIs are the recommended approach. WXT has some built-in support for them and they work well enough. `@wxt-dev/i18n` was recently released, and it will become the recommended approach after some of the bugs have been worked out. Head over to [it's docs](/guide/i18n/introduction.md) to learn more. -::: - -## Setup - -First familiarize yourself with [Chrome's docs](https://developer.chrome.com/docs/extensions/reference/api/i18n). The only difference when using these APIs with WXT is where you put the localization files - in the [`public` directory](/guide/directory-structure/public/). - -``` -/ -└─ public/ - └─ _locales/ - ├─ en/ - │ └─ messages.json - ├─ de/ - │ └─ messages.json - └─ ko/ - └─ messages.json -``` - -Next, to set a `default_locale` on your manifest, add it to your `wxt.config.ts` file: - -```ts -// wxt.config.ts -export default defineConfig({ - manifest: { - default_locale: 'en', - name: '__MSG_extName__', - description: '__MSG_extDescription__', - }, -}); -``` - -> You can localize the `name` and `description` of your extension from the `manifest` config as well. - -Finally, to get a translation, call `browser.i18n.getMessage`: - -```ts -browser.i18n.getMessage('extName'); -browser.i18n.getMessage('extDescription'); -browser.i18n.getMessage(/* etc */); -``` - -## Examples - -See the official localization examples for more details: - -- [I18n](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-i18n#readme) -- [Vue I18n](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vue-i18n#readme) diff --git a/docs/guide/extension-apis/messaging.md b/docs/guide/extension-apis/messaging.md deleted file mode 100644 index a55f1355..00000000 --- a/docs/guide/extension-apis/messaging.md +++ /dev/null @@ -1,36 +0,0 @@ -# Messaging - -## Overview - -Follow [Chrome's message passing guide](https://developer.chrome.com/docs/extensions/mv3/messaging/) to understand how message passing works in web extensions. In Google's examples, just replace `chrome` with `browser`, and it will work in WXT. - -Here's a basic request/response example: - -```ts -// popup/main.ts -const res = await browser.runtime.sendMessage('ping'); - -console.log(res); // "pong" -``` - -```ts -// background.ts -export default defineBackground(() => { - browser.runtime.onMessage.addListener((message, sender, sendResponse) => { - console.log(message); // "ping" - - // Wait 1 second and respond with "pong" - setTimeout(() => sendResponse('pong'), 1000); - return true; - }); -}); -``` - -## Third Party Libraries - -There are a number of message passing libraries you can use to improve the message passing experience. - -- [`@webext-core/messaging`](https://webext-core.aklinker1.io/guide/messaging/) - "A light-weight, type-safe wrapper around the `browser.runtime` messaging APIs" -- [`@webext-core/proxy-service`](https://webext-core.aklinker1.io/guide/proxy-service/) - "Create RPC-like services that can be called from anywhere but run in the background" -- [`webext-bridge`](https://github.com/zikaari/webext-bridge) - "Messaging in Web Extensions made super easy. Out of the box." -- [`trpc-chrome`](https://www.npmjs.com/package/trpc-chrome) - "tRPC adapter for Web Extensions 🧩" diff --git a/docs/guide/extension-apis/others.md b/docs/guide/extension-apis/others.md deleted file mode 100644 index db707c98..00000000 --- a/docs/guide/extension-apis/others.md +++ /dev/null @@ -1,5 +0,0 @@ -# All Other APIs - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/go-further/custom-events.md b/docs/guide/go-further/custom-events.md deleted file mode 100644 index 9963a574..00000000 --- a/docs/guide/go-further/custom-events.md +++ /dev/null @@ -1,5 +0,0 @@ -# Custom Events - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/go-further/debugging.md b/docs/guide/go-further/debugging.md deleted file mode 100644 index 26920e60..00000000 --- a/docs/guide/go-further/debugging.md +++ /dev/null @@ -1,5 +0,0 @@ -# Debugging - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/go-further/entrypoint-loaders.md b/docs/guide/go-further/entrypoint-loaders.md deleted file mode 100644 index 4f295390..00000000 --- a/docs/guide/go-further/entrypoint-loaders.md +++ /dev/null @@ -1,120 +0,0 @@ -# Entrypoint Loaders - -Because entrypoint options, like content script `matches`, are listed in the entrypoint's JS file, WXT has to import them during the build process to use those options when generating the manifest. - -There are two options for loading your entrypoints: - -1. `vite-node` - default as of `v0.19.0` -2. `jiti` (**DEPRECATED, will be removed in `v0.20.0`**) - Default before `v0.19.0` - -## vite-node - -Since 0.19.0, WXT uses `vite-node`, the same tool that powers Vitest and Nuxt, to import your entrypoint files. - -If you use any runtime packages that depend on `webextension-polyfill`, you need to add them to [Vite's `ssr.noExternal` option](https://vitejs.dev/config/ssr-options#ssr-noexternal): - -```ts -export default defineConfig({ - vite: () => ({ - ssr: { - noExternal: ['@webext-core/messaging', '@webext-core/proxy-service'], - }, - }), -}); -``` - -:::details Why? -This tells Vite it needs process these module's, letting WXT properly disable the polyfill in the NodeJS environment so it doesn't cause any build errors like this: - -``` -ERROR This script should only be loaded in a browser extension -``` - -::: - -To get a list of installed packages that use on `webextension-polyfill`, run your package manager's `list` command. Here's an example with PNPM: - -```sh -$ pnpm why webextension-polyfill - -dependencies: -@webext-core/messaging 1.4.0 -└── webextension-polyfill 0.10.0 -@webext-core/proxy-service 1.2.0 -├─┬ @webext-core/messaging 1.4.0 peer -│ └── webextension-polyfill 0.10.0 -└── webextension-polyfill 0.12.0 peer - -devDependencies: -@wxt-dev/module-vue 1.0.0 -└─┬ wxt 0.19.0-alpha1 peer - └── webextension-polyfill 0.12.0 -webextension-polyfill 0.12.0 -wxt 0.19.0-alpha1 -└── webextension-polyfill 0.12.0 -``` - -Ignoring WXT itself (it's added automatically for you), there are three packages that depend on the polyfill: `@wxt-dev/module-vue`, `@webext-core/messaging`, and `@webext-core/proxy-service`. Since the vue module is a build dependency, with no runtime code, you don't have to add it. That means for this case, you need to add `@webext-core/messaging`, and `@webext-core/proxy-service`, as shown in the original code snippet. - -## jiti - -The original method WXT used to import TS files. However, because it doesn't support vite plugins like `vite-node`, there is one main caveot to it's usage: **_module side-effects_**. - -To enable `jiti`: - -```ts -export default defineConfig({ - entrypointLoader: 'jiti', -}); -``` - -You cannot use imported variables outside the `main` function in JS entrypoints. This includes options, as shown below: - -```ts -// entrypoints/content.ts -import { GOOGLE_MATCHES } from '~/utils/match-patterns'; - -export default defineContentScript({ - matches: GOOGLE_MATCHES, - main() { - // ... - }, -}); -``` - -``` -$ wxt build -wxt build - -WXT 0.14.1 -ℹ Building chrome-mv3 for production with Vite 5.0.5 -✖ Command failed after 360 ms - -[8:55:54 AM] ERROR entrypoints/content.ts: Cannot use imported variable "GOOGLE_MATCHES" before main function. See https://wxt.dev/guide/entrypoints.html#side-effects -``` - -This throws an error because WXT needs to import each entrypoint during the build process to extract its definition (containing the `match`, `runAt`, `include`/`exclude`, etc.) to render the `manifest.json` correctly. Before loading an entrypoint, a transformation is applied to remove all imports. This prevents imported modules (local or NPM) with side-effects from running during the build process, potentially throwing an error. - -:::details Why? - -When importing your entrypoint to get its definition, the file is imported in a **_node environment_**, and doesn't have access to the `window`, `chrome`, or `browser` globals a web extension usually has access to. If WXT doesn't remove all the imports from the file, the imported modules could try and access one of these variables, throwing an error. - -::: - -:::warning -See [`wxt-dev/wxt#336`](https://github.com/wxt-dev/wxt/issues/336) to track the status of this bug. -::: - -Usually, this error occurs when you try to extract options into a shared file or try to run code outside the `main` function. To fix the example from above, use literal values when defining an entrypoint instead of importing them: - -```ts -import { GOOGLE_MATCHES } from '~/utils/match-patterns'; // [!code --] - -export default defineContentScript({ - matches: GOOGLE_MATCHES, // [!code --] - matches: ['*//*.google.com/*'], // [!code ++] - main() { - // ... - }, -}); -``` diff --git a/docs/guide/go-further/es-modules.md b/docs/guide/go-further/es-modules.md deleted file mode 100644 index ad9a859f..00000000 --- a/docs/guide/go-further/es-modules.md +++ /dev/null @@ -1,40 +0,0 @@ -# ES Modules - -Configure entrypoints to use ESM at runtime. - -Currently, ESM entrypoints are opt-in, so you must configure each entrypoint with that in mind. - -## HTML Pages - -In general, you should always make HTML pages import ESM scripts, unless you need to support old browsers. - -To make a script ESM, add `type="module"`: - - -```html - - -``` - -## Background - -In your background script, set `type: "module"`: - -```ts -export default defineBackground({ - type: 'module', // !code ++ - main() { - // ... - }, -}); -``` - -:::warning -Only MV3 support ESM background scripts/service workers. When targeting MV2, the `type` option is ignored and the background is always bundled into a single file as IIFE. -::: - -## Content Scripts - -WXT does not include built-in support for ESM content scripts. There are several technical issues that make implementing a generic solution impossible. See [Content Script ESM Support #357](https://github.com/wxt-dev/wxt/issues/357) for details. - -Instead, depending on your requirements, you can implement ESM support manually. See the [ESM Content Script UI](https://github.com/wxt-dev/examples/tree/main/examples/esm-content-script-ui) example to get started. diff --git a/docs/guide/go-further/handling-updates.md b/docs/guide/go-further/handling-updates.md deleted file mode 100644 index 63f39d2f..00000000 --- a/docs/guide/go-further/handling-updates.md +++ /dev/null @@ -1,77 +0,0 @@ -# Handling Extension Updates - -When releasing an update to your extension, there's a couple of things you need to keep in mind: - -[[toc]] - -## Content Script Cleanup - -Old content scripts are not automatically stopped when an extension updates and reloads. Often, this leads to "Invalidated context" errors in production when a content script from an old version of your extension tries to use an extension API. - -WXT provides a utility for handling this process: `ContentScriptContext`. An instance of this class is provided to you automatically inside the `main` function of each content script. - -When your extension updates or reloads, the context will become invalidated, and will trigger any `ctx.onInvalidated` listeners you add: - -```ts -export default defineContentScript({ - main(ctx) { - ctx.onInvalidated(() => { - // Do something - }); - }, -) -``` - -The `ctx` also provides other convenient APIs for stopping your content script without manually calling `onInvalidated` to add a listener: - -1. Setting timers: - ```ts - ctx.setTimeout(() => { ... }, ...); - ctx.setInterval(() => { ... }, ...); - ctx.requestAnimationFrame(() => { ... }); - ``` -1. Adding DOM events: - ```ts - ctx.addEventListener(window, "mousemove", (event) => { ... }); - ``` -1. Implements [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) for canceling standard APIs: - ```ts - fetch('...', { - signal: ctx.signal, - }); - ``` - -Other WXT APIs require a `ctx` object so they can clean themselves up. For example, [`createIntegratedUi`](/guide/key-concepts/content-script-ui#integrated), [`createShadowRootUi`](/guide/key-concepts/content-script-ui#shadow-root), and [`createIframeUi`](/guide/key-concepts/content-script-ui#iframe) automatically unmount and stop a UI when the script is invalidated. - -:::warning -When working with content scripts, **you should always use the `ctx` object to stop any async or future work.** - -This prevents old content scripts from interfering with new content scripts, and prevents error messages from being logged to the console in production. -::: - -## Testing Permission Changes - -When `permissions`/`host_permissions` change during an update, depending on what exactly changed, the browser will disable your extension until the user accepts the new permissions. - -You can test if your permission changes will result in a disabled extension: - -- Chromium: Use [Google's Extension Update Testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool) -- Firefox: See their [Test Permission Requests](https://extensionworkshop.com/documentation/develop/test-permission-requests/) page -- Safari: Everyone breaks something in production eventually... 🫡 Good luck soldier - -## Update Event - -You can setup a callback that runs after your extension updates like so: - -```ts -browser.runtime.onInstalled.addListener(({ reason }) => { - if (reason === 'update') { - // Do something - } -}); -``` - -If the logic is simple, write a unit test to cover this logic. If you feel the need to manually test this callback, you can either: - -1. In dev mode, remove the `if` statement and reload the extension from `chrome://extensions` -2. Use [Google's Extension Update Testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool) diff --git a/docs/guide/go-further/reusable-modules.md b/docs/guide/go-further/reusable-modules.md deleted file mode 100644 index 50aaf8f0..00000000 --- a/docs/guide/go-further/reusable-modules.md +++ /dev/null @@ -1,186 +0,0 @@ ---- -outline: deep ---- - -# Reusable Modules - -## Overview - -WXT provides a "module" API that lets you modify the build process. This API lets you add entrypoints, inject runtime code, add vite plugins, and more! - -What's more, these modules can be shared on NPM and re-used between projects! - -## Adding a Module - -There are two ways to add a module to your project: - -1. **Local file**: Any file present in the `modules/` directory will be treated as a module and loaded at build-time by WXT. You can use `modules/*.ts` or `modules/*/index.ts`, similar to entrypoints. - - ```ts - // modules/example.ts - import { defineWxtModule } from 'wxt/modules'; - - export default defineWxtModule((wxt) => { - // ... - }); - ``` - -2. **NPM package**: Find WXT modules on NPM and include them in your project: - ```ts - // wxt.config.ts - export default defineConfig({ - // Add the module to your project - modules: ['@wxt-dev/auto-icons'], - }); - ``` - -## Writing Modules - -Modules contain a setup function that is executed at the beginning of the build process. - -:::code-group - -```ts [Function Definition] -import { defineWxtModule } from 'wxt/modules'; - -export default defineWxtModule((wxt) => { - // ... -}); -``` - -```ts [Object Definition] -import { defineWxtModule } from 'wxt/modules'; - -export default defineWxtModule({ - // Add metadata... - setup(wxt) { - // ... - }, -}); -``` - -::: - -### Module Options - -You can define custom options for your module by setting the `configKey`: - -```ts -// modules/analytics.ts -import { defineWxtModule } from 'wxt/modules'; - -export default defineWxtModule({ - configKey: 'analytics', - setup(wxt, options) { - console.log(options); // { clientId: "..." } - }, -}); - -// Define the option types -export interface AnalyticsModuleOptions { - clientId: string; -} - -// Use "module augmentation" to add types for the new key -declare module 'wxt' { - export interface InlineConfig { - analytics: AnalyticsModuleOptions; - } -} -``` - -Now, when the user provides options to the `analytics` key in their `wxt.config.ts`, those options are passed into the setup function as the second argument. - -```ts -export default defineConfig({ - analytics: { clientId: '...' }, -}); -``` - -### Actually Doing Something - -The first argument of the setup function, `wxt`, provides full access to the current build's context. You can access the resolved configuration via `wxt.config`, or setup hooks to manipulate the build at different steps of the build process with `wxt.hooks`. - -Here's an example that updates the `outDir` based on the build mode. It's a very simple example of how to access config and setup a hook. - -```ts -export default defineWxtModule((wxt) => { - if (wxt.config.mode === 'development') { - // Use the "ready" hook to update wxt.config - wxt.hooks.hook('ready', (wxt) => { - wxt.config.outDir = wxt.config.outDir.replace('.output', '.output/dev'); - }); - } -}); -``` - -:::info Async Modules -Both the `setup` function and hook callbacks can be async. Don't forget to add `await`! -::: - -It's important to understand the basics of how hooks work. Make sure to read the [API reference](/api/reference/wxt/interfaces/WxtHooks.html) for the full list of hooks and what they should be used for. They are the key to modifying your extension. - -### Module Utils - -Additionally, WXT provides several helper functions that setup hooks behind the scenes to streamline common operations. - -For example, if you want to include an entrypoint from inside a module, you can use the `addEntrypoint` util: - -```ts -// modules/changelog.ts -import { defineWxtModule, addEntrypoint } from 'wxt/modules'; -import { resolve } from 'node:path'; - -export default defineWxtModule({ - name: 'changelog', - setup(wxt) { - addEntrypoint(wxt, { - type: 'unlisted-page', - name: 'changelog', - // Point to the "modules/changelog.html" file - inputPath: resolve(__dirname, 'changelog.html'), - outputDir: wxt.config.outDir, - options: {}, - }); - }, -}); -``` - -Refer to the [API reference](/api/reference/wxt/modules/#functions) for the full list of the utilities. - -## Plugins - -Whereas modules are executed at build-time, plugins are executed at runtime. As of now, the only way to add a plugin is with the `addWxtPlugin` helper inside a module. - -Here's a minimal example to execute something at runtime. - -:::code-group - -```ts [modules/example/index.ts] -import { defineWxtModule, addWxtPlugin } from 'wxt/modules'; -import { resolve } from 'node:path'; - -export default defineWxtModule((wxt) => { - addWxtPlugin(wxt, resolve(__dirname, 'plugin.ts')); -}); -``` - -```ts [modules/example/plugin.ts] -import { defineWxtPlugin } from 'wxt/sandbox'; - -export default defineWxtPlugin(() => { - console.log('Executing plugin!'); -}); -``` - -::: - -:::warning Async Plugins -Unlike modules, **_plugins cannot be async_**!! If you need to do some async work and expose that result to the rest of the extension, store the result's promise synchronously and await it later on. -::: - -## Publishing to NPM - -:::warning 🚧 Under construction -These docs will be coming soon! -::: diff --git a/docs/guide/go-further/testing.md b/docs/guide/go-further/testing.md deleted file mode 100644 index 8c5f2300..00000000 --- a/docs/guide/go-further/testing.md +++ /dev/null @@ -1,27 +0,0 @@ -# Testing - -## Official Frameworks - -WXT officially supports [Vitest](https://vitest.dev/) for unit tests and either [Playwright](https://playwright.dev/) or [Puppeteer](https://pptr.dev/) for E2E tests against Chromium browsers. - -For details setting up each testing framework, see the official examples: - -- [Vitest](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-vitest#readme) -- [Playwright](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-playwright#readme) -- [Puppeteer](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-puppeteer#readme) - -### Unofficial Frameworks - -Puppeteer and Playwright are the only E2E test runners that support Chrome Extensions. There are no other options at the time of writing. - -There are other options for unit tests however, like [Jest](https://jestjs.io/), [Mocha](https://mochajs.org/), or [`node:test`](https://nodejs.org/api/test.html). **_WXT does not claim to support any of them_** because none of them support all of WXT's features, like TypeScript or auto-imports. - -If you want to try to use a different framework for unit tests, you will need to configure the environment manually: - -- **Auto-imports**: Add `unimport` to your test environment or disable them by setting `imports: false` in your `wxt.config.ts` file -- **`browser` mock**: Mock the `webextension-polyfill` module globally with `wxt/dist/virtual/mock-browser.mjs` -- **[Remote Code Bundling](/guide/go-further/remote-code)**: If you use it, configure your environment to handle the `url:` module prefix -- **Global Variables**: If you consume them, manually define globals provided by WXT (like `import.meta.env.BROWSER`) by adding them to the global scope before accessing them (`import.meta.env.BROWSER = "chrome"`) -- **Import paths**: If you use the `@/` or `~/` path aliases, add them to your test environment - -[Here's how Vitest is configured](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/src/testing/wxt-vitest-plugin.ts) for reference. diff --git a/docs/guide/go-further/vite.md b/docs/guide/go-further/vite.md deleted file mode 100644 index 98e67d10..00000000 --- a/docs/guide/go-further/vite.md +++ /dev/null @@ -1,39 +0,0 @@ -# Vite - -Under the hood, WXT uses Vite to bundle your web extension. - -## Basic Vite Configuration - -All of Vite's config can be customized by setting the `vite` configuration in your `wxt.config.ts` file. - -```ts -import { defineConfig } from 'wxt'; - -export default defineConfig({ - vite: () => ({ - // Same as `defineConfig({ ... })` inside vite.config.ts - }), -}); -``` - -## Using Plugins - -Plugins can be passed into the `vite` configuration in your `wxt.config.ts` file, just like any other option. - -```ts -import { defineConfig } from 'wxt'; - -export default defineConfig({ - vite: () => ({ - plugins: [ - // ... - ], - }), -}); -``` - -:::warning UNEXPECTED BEHAVIOR -Due to the way WXT orchestrates Vite builds, some plugins may not work as expected. Search [GitHub issues](https://github.com/wxt-dev/wxt/issues?q=is%3Aissue+label%3A%22vite+plugin%22) if you run into issues with a specific plugin. - -If one doesn't exist, please open a [new issue](https://github.com/wxt-dev/wxt/issues/new/choose)! -::: diff --git a/docs/guide/i18n/build-integrations.md b/docs/guide/i18n/build-integrations.md deleted file mode 100644 index 0dd9aba2..00000000 --- a/docs/guide/i18n/build-integrations.md +++ /dev/null @@ -1,82 +0,0 @@ -# Build Integrations - -To use the custom messages file format, you'll need to use `@wxt-dev/i18n/build` to transform the custom format to the one expected by browsers. - -Here's a list of build tools that already have an integration: - -[[toc]] - -:::info -If you want to contribute, please do! In particular, an `unplugin` integration would be awesome! -::: - -## WXT - -See [Installation with WXT](./installation#with-wxt). - -But TLDR, all you need is: - -```ts -// wxt.config.ts -export default defineConfig({ - modules: ['@wxt-dev/i18n/module'], -}); -``` - -Types are generated whenever you run `wxt prepare` or another build command: - -```sh -wxt prepare -wxt -wxt build -wxt zip -# etc -``` - -## Custom - -If you're not using WXT, you'll have to pre-process the localization files yourself. Here's a basic script to generate the `_locales/.../messages.json` and `wxt-i18n-structure.d.ts` files: - -```ts -// build-i18n.js -import { - parseMessagesFile, - generateChromeMessagesFile, - generateTypeFile, -} from '@wxt-dev/i18n/build'; - -// Read your localization files -const messages = { - en: await parseMessagesFile('path/locales/en.yml'), - de: await parseMessagesFile('path/locales/de.yml'), - // ... -}; - -// Generate JSON files for the browser -await generateChromeMessagesFile('dist/_locales/en/messages.json', messages.en); -await generateChromeMessagesFile('dist/_locales/de/messages.json', messages.de); -// ... - -// Generate a types file based on your default_locale -await generateTypeFile('wxt-i18n-structure.d.ts', messages.en); -``` - -Then run the script: - -```sh -node generate-i18n.js -``` - -If your build tool has a dev mode, you'll also want to rerun the script whenever you change a localization file. - -### Type Safety - -Once you've generated `wxt-i18n-structure.d.ts` (see the [Custom](#custom) section), you can use it to pass the generated type into `createI18n`: - -```ts -import type { WxtI18nStructure } from './wxt-i18n-structure'; - -export const i18n = createI18n(); -``` - -And just like that, you have type safety! diff --git a/docs/guide/i18n/editor-support.md b/docs/guide/i18n/editor-support.md deleted file mode 100644 index 704ab104..00000000 --- a/docs/guide/i18n/editor-support.md +++ /dev/null @@ -1,48 +0,0 @@ -# Editor Support - -For better DX, you can configure your editor with plugins and extensions. - -[[toc]] - -## VS Code - -Install the [I18n Ally Extension](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) to: - -- Go to translation definition -- Inline previews of text -- Hover to see other translations - -You'll need to configure it the extension so it knows where your localization files are and what function represents getting a translation: - -`.vscode/i18n-ally-custom-framework.yml`: - -```yml -# An array of strings which contain Language Ids defined by VS Code -# You can check available language ids here: https://code.visualstudio.com/docs/languages/identifiers -languageIds: - - typescript - -# Look for t("...") -usageMatchRegex: - - "[^\\w\\d]t\\(['\"`]({key})['\"`]" - -# Disable other built-in i18n ally frameworks -monopoly: true -``` - -`.vscode/settings.json`: - -```json -{ - "i18n-ally.localesPaths": ["src/locales"], - "i18n-ally.keystyle": "nested" -} -``` - -## Zed - -As of time of writing, Aug 18, 2024, no extensions exist for Zed to add I18n support. - -## IntelliJ - -Unknown - Someone who uses IntelliJ will have to open a PR for this! diff --git a/docs/guide/i18n/installation.md b/docs/guide/i18n/installation.md deleted file mode 100644 index f1779ffd..00000000 --- a/docs/guide/i18n/installation.md +++ /dev/null @@ -1,81 +0,0 @@ -# Installation - -[[toc]] - -### With WXT - -1. Install `@wxt-dev/i18n` via your package manager: - - ```sh - pnpm i @wxt-dev/i18n - ``` - -2. Add the WXT module to your `wxt.config.ts` file and setup a default locale: - - ```ts - export default defineConfig({ - modules: ['@wxt-dev/i18n/module'], - manifest: { - default_locale: 'en', - }, - }); - ``` - -3. Create a localization file at `/locales/.yml` or move your existing localization files there. - - ```yml - # /locales/en.yml - helloWorld: Hello world! - ``` - - :::tip - `@wxt-dev/i18n` supports the standard messages format, so if you already have localization files at `/public/_locale//messages.json`, you don't need to convert them to YAML or refactor them - just move them to `/locales/.json` and they'll just work out of the box! - ::: - -4. To get a translation, use the auto-imported `i18n` object or import it manually: - - ```ts - import { i18n } from '#i18n'; - - i18n.t('helloWorld'); // "Hello world!" - ``` - -And you're done! Using WXT, you get type-safety out of the box. - -### Without WXT - -1. Install `@wxt-dev/i18n` via your package manager: - - ```sh - pnpm i @wxt-dev/i18n - ``` - -2. Create a messages file at `_locales//messages.json` or move your existing translations there: - - ```json - { - "helloWorld": { - "message": "Hello world!" - } - } - ``` - - :::info - For the best DX, you should to integrate `@wxt-dev/i18n` into your build process. This enables: - - 1. Plural forms - 2. Simple messages file format - 3. Type safety - - See [Build Integrations](./build-integrations) to set it up. - ::: - -3. Create the `i18n` object, export it, and use it wherever you want! - - ```ts - import { createI18n } from '@wxt-dev/i18n'; - - export const i18n = createI18n(); - - i18n.t('helloWorld'); // "Hello world!"; - ``` diff --git a/docs/guide/i18n/introduction.md b/docs/guide/i18n/introduction.md deleted file mode 100644 index 37404dda..00000000 --- a/docs/guide/i18n/introduction.md +++ /dev/null @@ -1,22 +0,0 @@ -# Introduction - -:::info -You don't have to use `wxt` to use this package - it will work in any bundler setup. See [Installation without WXT](./installation#without-wxt) for more details. -::: - -`@wxt-dev/i18n` is a simple, type-safe wrapper around the `browser.i18n` APIs. It provides several benefits over the standard API: - -1. Simple messages file format -2. Plural form support -3. Integrates with the [I18n Ally VS Code extension](./editor-support#vscode) - -It also provides several benefits over other non-web extension specific i18n packages: - -1. Does not bundle localization files into every entrypoint -2. Don't need to fetch the localization files asynchronously -3. Can localize extension name in manifest -4. Can access localized strings inside CSS files - -However, it does have one major downside: - -1. Like the `browser.i18n` API, to change the language, users must change the browser's language diff --git a/docs/guide/i18n/messages-file-format.md b/docs/guide/i18n/messages-file-format.md deleted file mode 100644 index f6e699d9..00000000 --- a/docs/guide/i18n/messages-file-format.md +++ /dev/null @@ -1,170 +0,0 @@ -# Messages File Format - -You can only use the file format discussed on this page if you have [integrated `@wxt-dev/i18n` into your build process](./build-integrations). If you have not integrated it into your build process, you must use JSON files in the `_locales` directory, like a normal web extension. - -[[toc]] - -## File Extensions - -You can define your messages in several different file types: - -- `.yml` -- `.yaml` -- `.json` -- `.jsonc` -- `.json5` -- `.toml` - -## Nested Keys - -You can have translations at the top level or nest them into groups: - -```yml -ok: OK -cancel: Cancel -welcome: - title: Welcome to XYZ -dialogs: - confirmation: - title: 'Are you sure?' -``` - -To access a nested key, use `.`: - -```ts -i18n.t('ok'); // "OK" -i18n.t('cancel'); // "Cancel" -i18n.t('welcome.title'); // "Welcome to XYZ" -i18n.t('dialogs.confirmation.title'); // "Are you sure?" -``` - -## Substitutions - -Because `@wxt-dev/i18n` is based on `browser.i18n`, you define substitutions the same way, with `$1`-`$9`: - -```yml -hello: Hello $1! -order: Thanks for ordering your $1 -``` - -### Escapting `$` - -To escape the dollar sign, put another `$` in front of it: - -```yml -dollars: $$$1 -``` - -```ts -i18n.t('dollars', ['1.00']); // "$1.00" -``` - -## Plural Forms - -:::warning -Plural support languages like Arabic, that have different forms for "few" or "many", is not supported right now. Feel free to open a PR if you are interested in this! -::: - -To get a different translation based on a count: - -```yml -items: - 1: 1 item - n: $1 items -``` - -Then you pass in the count as the second argument to `i18n.t`: - -```ts -i18n.t('items', 0); // "0 items" -i18n.t('items', 1); // "1 item" -i18n.t('items', 2); // "2 items" -``` - -To add a custom translation for 0 items: - -```yml -items: - 0: No items - 1: 1 item - n: $1 items -``` - -```ts -i18n.t('items', 0); // "No items" -i18n.t('items', 1); // "1 item" -i18n.t('items', 2); // "2 items" -``` - -If you need to pass a custom substitution for `$1` instead of the count, just add the substitution: - -```yml -items: - 0: No items - 1: $1 item - n: $1 items -``` - -```ts -i18n.t('items', 0, ['Zero']); // "No items" -i18n.t('items', 1, ['One']); // "One item" -i18n.t('items', 2, ['Multiple']); // "Multiple items" -``` - -## Verbose Keys - -`@wxt-dev/i18n` is compatible with the message format used by [`browser.i18n`](https://developer.chrome.com/docs/extensions/reference/api/i18n). - -:::info -This means if you're migrating to `@wxt-dev/i18n` and you're already using the verbose format, you don't have to change anything! -::: - -A key is treated as "verbose" when it is: - -1. At the top level (not nested) -2. Only contains the following properties: `message`, `description` and/or `placeholder` - -:::code-group - -```json [JSON] -{ - "appName": { - "message": "GitHub - Better Line Counts", - "description": "The app's name, should not be translated", - }, - "ok": "OK", - "deleteConfirmation": { - "title": "Delete XYZ?" - "message": "You cannot undo this action once taken." - } -} -``` - -```yml [YAML] -appName: - message: GitHub - Better Line Counts - description: The app's name, should not be translated -ok: OK -deleteConfirmation: - title: Delete XYZ? - message: You cannot undo this action once taken. -``` - -::: - -In this example, only `appName` is considered verbose. `deleteConfirmation` is not verbose because it contains the additional property `title`. - -```ts -i18n.t('appName'); // ✅ "GitHub - Better Line Counts" -i18n.t('appName.message'); // ❌ -i18n.t('ok'); // ✅ "OK" -i18n.t('deleteConfirmation'); // ❌ -i18n.t('deleteConfirmation.title'); // ✅ "Delete XYZ?" -i18n.t('deleteConfirmation.message'); // ✅ "You cannot undo this action once taken." -``` - -If this is confusing, don't worry! With type-safety, you'll get a type error if you do it wrong. If type-safety is disabled, you'll get a runtime warning in the devtools console. - -:::warning -Using the verbose format is not recommended. I have yet to see a translation service and software that supports this format out of the box. Stick with the simple format when you can. -::: diff --git a/docs/guide/installation.md b/docs/guide/installation.md new file mode 100644 index 00000000..1f59ad49 --- /dev/null +++ b/docs/guide/installation.md @@ -0,0 +1,123 @@ +# Installation + +Bootstrap a new project, start from scratch, or [migrate an existing project](/guide/resources/migrate). + +[[toc]] + +## Bootstrap Project + +Run the [`init` command](/api/cli/wxt-init), and follow the instructions. + +:::code-group + +```sh [PNPM] +pnpm dlx wxt@latest init +``` + +```sh [Bun] +bunx wxt@latest init +``` + +```sh [NPM] +npx wxt@latest init +``` + +```sh [Yarn] +# Use NPM initially, but select Yarn when prompted +npx wxt@latest init +``` + +::: + +:::info Starter Templates: +[Vanilla](https://github.com/wxt-dev/wxt/tree/main/templates/vanilla)
[Vue](https://github.com/wxt-dev/wxt/tree/main/templates/vue)
[React](https://github.com/wxt-dev/wxt/tree/main/templates/react)
[Svelte](https://github.com/wxt-dev/wxt/tree/main/templates/svelte)
[Solid](https://github.com/wxt-dev/wxt/tree/main/templates/solid) + +All templates use TypeScript by default. To use JavaScript, change the file extensions. +::: + +### Demo + +![wxt init demo](/assets/init-demo.gif) + +Once you've run the `dev` command, continue to [Next Steps](#next-steps)! + +## From Scratch + +1. Create a new project + :::code-group + ```sh [PNPM] + cd my-project + pnpm init + ``` + ```sh [Bun] + cd my-project + bun init + ``` + ```sh [NPM] + cd my-project + npm init + ``` + ```sh [Yarn] + cd my-project + yarn init + ``` + ::: +2. Install WXT: + :::code-group + ```sh [PNPM] + pnpm i -D wxt + ``` + ```sh [Bun] + bun i -D wxt + ``` + ```sh [NPM] + npm i -D wxt + ``` + ```sh [Yarn] + yarn add --dev wxt + ``` + ::: +3. Add an entrypoint, `my-project/entrypoints/background.ts`: + :::code-group + ```ts + export default defineBackground(() => { + console.log('Hello world!'); + }); + ``` + ::: +4. Add scripts to your `package.json`: + ```json + { + "scripts": { + "dev": "wxt", // [!code ++] + "dev:firefox": "wxt -b firefox", // [!code ++] + "build": "wxt build", // [!code ++] + "build:firefox": "wxt build -b firefox", // [!code ++] + "zip": "wxt zip", // [!code ++] + "zip:firefox": "wxt zip -b firefox", // [!code ++] + "postinstall": "wxt prepare" // [!code ++] + } + } + ``` +5. Run your extension in dev mode + :::code-group + ```sh [PNPM] + pnpm dev + ``` + ```sh [Bun] + bun run dev + ``` + ```sh [NPM] + npm run dev + ``` + ```sh [Yarn] + yarn dev + ``` + ::: + WXT will automatically open a browser window with your extension installed. + +## Next Steps + +- Keep reading on about WXT's [Project Structure](/guide/essentials/project-structure) and other essential concepts to learn +- Configure [automatic browser startup](/guide/essentials/config/browser-startup) during dev mode +- Explore [WXT's example library](/examples) to see how to use specific APIs or perform common tasks diff --git a/docs/guide/introduction.md b/docs/guide/introduction.md new file mode 100644 index 00000000..c373e7c5 --- /dev/null +++ b/docs/guide/introduction.md @@ -0,0 +1,26 @@ +# Welcome to WXT! + +WXT is a modern, open-source framework for building web extensions. Inspired by Nuxt, its goals are to: + +- Provide an awesome [DX](https://about.gitlab.com/topics/devops/what-is-developer-experience/) +- Provide first-class support for all major browsers + +Check out the [comparison](/guide/resources/compare) to see how WXT compares to other tools for building web extnesions. + +## Prerequisites + +These docs assume you have a basic knowledge of how Chrome extensions are structured and how you access the extension APIs. + +:::warning New to extension development? +If you have never written an extension before, follow Chrome's [Hello World tutorial](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world) to first **_create an extension without WXT_**, then come back here. +::: + +You should also be aware of [Chrome's extension docs](https://developer.chrome.com/docs/extensions) and [Mozilla's extension docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions). WXT does not change how you use the extension APIs, and you'll need to refer to these docs often when using specific APIs. + +
+ +--- + +
+ +Alright, got a basic understanding of how web extensions are structured? Do you know how to access the extension APIs? Then continue to the [Installation page](/guide/installation) to create your first WXT extension. diff --git a/docs/guide/key-concepts/auto-imports.md b/docs/guide/key-concepts/auto-imports.md deleted file mode 100644 index ac3b699d..00000000 --- a/docs/guide/key-concepts/auto-imports.md +++ /dev/null @@ -1,117 +0,0 @@ -# Auto-imports - -WXT uses the same tool as Nuxt for auto-imports, [`unimport`](https://github.com/unjs/unimport). - -## WXT Auto-imports - -Some WXT APIs can be used without importing them: - -- [`browser`](/api/reference/wxt/browser/variables/browser) from `wxt/browser`, a small wrapper around `webextension-polyfill` -- [`defineContentScript`](/api/reference/wxt/sandbox/functions/defineContentScript) from `wxt/sandbox` -- [`defineBackground`](/api/reference/wxt/sandbox/functions/defineBackground) from `wxt/sandbox` -- [`defineUnlistedScript`](/api/reference/wxt/sandbox/functions/defineUnlistedScript) from `wxt/sandbox` -- [`createIntegratedUi`](/api/reference/wxt/client/functions/createIntegratedUi) from `wxt/client` -- [`createShadowRootUi`](/api/reference/wxt/client/functions/createShadowRootUi) from `wxt/client` -- [`createIframeUi`](/api/reference/wxt/client/functions/createIframeUi) from `wxt/client` -- [`fakeBrowser`](/api/reference/wxt/testing/variables/fakeBrowser) from `wxt/testing` - -And more! - -## Project Auto-imports - -In addition WXT APIs, default and named exports from inside the following directories can be used without listing them in imports. - -- `/components/*` -- `/composables/*` -- `/hooks/*` -- `/utils/*` - -To add auto-imports from subdirectories, like `utils/api/some-file.ts`, re-export them from the base directory: - -```ts -// utils/index.ts -export * from './api/some-file.ts'; -``` - -Alternatively, you could add the directory to the list of auto-import directories in your config file. - -## TypeScript - -For TypeScript to work, you need to run the `wxt prepare` command. This will ensure types are generated for auto-imports. - -This should be added to your `postinstall` script so your editor has everything it needs to report type errors after installing dependencies: - -```json -// package.json -{ - "scripts": { - "postinstall": "wxt prepare" // [!code ++] - } -} -``` - -## Customization - -You can override the default auto-import behavior in your `wxt.config.ts` file. - -See [`unimport`'s documentation](https://github.com/unjs/unimport#configurations) for a complete list of options. - -```ts -import { defineConfig } from 'wxt'; - -export default defineConfig({ - imports: { - // Add auto-imports for vue functions like createApp, ref, computed, watch, toRaw, etc... - presets: ['vue'], - }, -}); -``` - -## Disabling Auto-imports - -To disable auto-imports, set `imports: false` - -```ts -export default defineConfig({ - imports: false, -}); -``` - -## ESLint - -ESLint doesn't know about the auto-imported variables unless they are explicitly defined in the `globals` config. By default, WXT will generate the config if it detects ESLint is installed in your project. If the config isn't generated automatically, you can manually tell WXT to generate it. - -```ts -// wxt.config.ts -export default defineConfig({ - imports: { - eslintrc: { - enabled: 8, // Generate ESLint v8 compatible config - // or - enabled: 9, // Generate ESLint v9 compatible config - }, - }, -}); -``` - -### ESLint 9 and above - -WXT supports the "flat config" file format introduced in ESLint 9. Just import the generated file and add it to the array of config to extend. - -```js -// eslint.config.mjs -import autoImports from './.wxt/eslint-auto-imports.mjs'; - -export default [autoImports]; -``` - -### ESLint 8 and below - -Just extend the generated file: - -```js -// .eslintrc.mjs -export default { - extends: ['./.wxt/eslintrc-auto-import.json'], -}; -``` diff --git a/docs/guide/key-concepts/manifest.md b/docs/guide/key-concepts/manifest.md deleted file mode 100644 index 6ee21f6f..00000000 --- a/docs/guide/key-concepts/manifest.md +++ /dev/null @@ -1,249 +0,0 @@ -# Manifest - -## Overview - -Sometimes, you'll need to make manual changes to how the `manifest.json` is generated. You can do this by using the `manifest` configuration: - -```ts -// wxt.config.ts -export default defineConfig({ - manifest: { - // Put manual changes here - }, -}); -``` - -## Manifest Version Compatibility - -When defining options in the manifest, always define them for MV3 when possible. WXT will either convert them to their MV2 equivalents or remove them from the generated manifest if there is not MV2 equivalent. - -So for fields like `web_accessible_resources` or `content_security_policy`, you just need to list them in their MV3 forms. Other fields, like `side_panel`, which doesn't exist in MV2, will be removed automatically. - -Here's an example `wxt.config.ts` file: - -```ts -import { defineConfig } from 'wxt'; - -export default defineConfig({ - manifest: { - action: { - default_title: 'Some Title', - }, - web_accessible_resources: [ - { - matches: ['*://*.google.com/*'], - resources: ['icon/*.png'], - }, - ], - }, -}); -``` - -And here's the different `manifest.json` files generated: - -:::code-group - -```json [MV2] -{ - "manifest_version": 2, - // ... - "browser_action": { - "default_title": "Some Title" - }, - "web_accessible_resources": ["icon/*.png"] -} -``` - -```json [MV3] -{ - "manifest_version": 3, - // ... - "action": { - "default_title": "Some Title" - }, - "web_accessible_resources": [ - { - "matches": ["*://*.google.com/*"], - "resources": ["icon/*.png"] - } - ] -} -``` - -::: - -## Name - -If not provided via the `manifest` config, the [manifest's `name`](https://developer.chrome.com/docs/extensions/mv3/manifest/name/) defaults to your `package.json`'s `name` property. - -## Version - -The [manifest's `version` and `version_name`](https://developer.chrome.com/docs/extensions/mv3/manifest/version/) properties are based on the `version` field listed in your `package.json` or `wxt.config.ts`. - -- `version_name` is the exact string listed in your `package.json` or `wxt.config.ts` file -- `version` is the string cleaned up, with any invalid suffixes removed - -If a version is not found, a warning is logged and the version defaults to `"0.0.0"`. - -#### Example - -```json -// package.json -{ - "version": "1.3.0-alpha2" -} -``` - -```json -// .output//manifest.json -{ - "version": "1.3.0", - "version_name": "1.3.0-alpha2" -} -``` - -## `icons` - -By default, WXT will discover icons in your [`public` directory](/guide/directory-structure/public/) and use them for the [manifest's `icons`](https://developer.chrome.com/docs/extensions/mv3/manifest/icons/). - -``` -public/ -├─ icon-16.png -├─ icon-24.png -├─ icon-48.png -├─ icon-96.png -└─ icon-128.png -``` - -Icon files need to match the following regex to be automatically included in the manifest. Most design software can output icons in one of these formats - -<<< @/../packages/wxt/src/core/utils/manifest.ts#snippet - -If you prefer to use filenames in a different format, you can add the icons manually in your `wxt.config.ts` file: - -```ts -export default defineConfig({ - manifest: { - icons: { - 16: '/extension-icon-16.png', - 24: '/extension-icon-24.png', - 48: '/extension-icon-48.png', - 96: '/extension-icon-96.png', - 128: '/extension-icon-128.png', - }, - }, -}); -``` - -## Permissions - -[Permissions](https://developer.chrome.com/docs/extensions/reference/permissions/) must be listed in the manifest config. - -```ts -export default defineConfig({ - manifest: { - permissions: ['storage', 'tabs'], - }, -}); -``` - -## Host Permissions - -[Host Permissions](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions#host-permissions) must be listed in the manifest config. - -```ts -export default defineConfig({ - manifest: { - host_permissions: ['*://*.google.com/*'], - }, -}); -``` - -:::warning -If you use host permissions and target both MV2 and MV3, make sure to only include the required host permissions for each version: - -```ts -export default defineConfig({ - manifest: ({ manifestVersion }) => ({ - host_permissions: manifestVersion === 2 ? [...] : [...], - }), -}); -``` - -::: - -## Default Locale - -See the dedicated [I18n docs](/guide/extension-apis/i18n) for setting up localization and a `default_locale`. - -## Actions - -In MV2, you had two options: [`browser_action`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_action) and [`page_action`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/page_action). In MV3, they were merged into a single [`action`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action) API. - -By default, whenever an action is generated, WXT falls back to `browser_action` when targetting MV2. - -### Action With Popup - -To generate a manifest where a UI appears after clicking the icon, just create a [popup entrypoint](/guide/directory-structure/entrypoints/popup). - -If you want to use a `page_action` for MV2, add the following `meta` tag to the HTML document's head: - -```html - -``` - -### Action Without Popup - -If you want to use the `activeTab` permission or the `browser.action.onClick` event, but don't want to show a popup UI: - -1. Delete the [popup entrypoint](/guide/directory-structure/entrypoints/popup) if it exists -2. Add the `action` key to your manifest: - ```ts - // wxt.config.ts - export default defineConfig({ - manifest: { - action: {}, - }, - }); - ``` - -Same as an action with a popup, WXT will fallback on using `browser_action` for MV2. To use a `page_action` instead, add that key as well: - -```ts -// wxt.config.ts -export default defineConfig({ - manifest: { - action: {}, - page_action: {}, - }, -}); -``` - -## Full Control - -The `manifest` option can also be set equal to a function, letting you use logical statements to determine what should be output. - -```ts -// wxt.config.ts -export default defineConfig({ - manifest: ({ manifestVersion, browser, mode, command }) => { - return { ... } - } -}) -``` - -Or, you can use the `build:manifestGenerated` hook to transform the manifest before it is written to the output directory. - -```ts -// wxt.config.ts -export default defineConfig({ - hooks: { - build: { - manifestGenerated(manifest) { - // Update the manifest variable by reference - manifest.name = 'Overriden name'; - }, - }, - }, -}); -``` diff --git a/docs/guide/key-concepts/multiple-browsers.md b/docs/guide/key-concepts/multiple-browsers.md deleted file mode 100644 index ee5a743d..00000000 --- a/docs/guide/key-concepts/multiple-browsers.md +++ /dev/null @@ -1,133 +0,0 @@ -# Multiple Browsers - -You can build an extension for any combination of browser and manifest version. Different browsers and manifest versions support different APIs and entrypoints, so be sure to check that your extension functions as expected for each target. - -Separate build targets are written to their own output directories: - -``` - -└─ .output - ├─ chrome-mv3 - ├─ firefox-mv2 - ├─ edge-mv3 - └─ ... -``` - -## Target Browser - -To build for a specific browser, pass the `-b --browser` flag from the CLI: - -```sh -wxt --browser firefox -wxt build --browser firefox -``` - -By default, it will build for `chrome`. When excluding the [manifest version flags](#target-manifest-version), it will default to the commonly accepted manifest version for that browser. - -| Browser | Default Manifest Version | -| ---------------- | :----------------------: | -| `chrome` | 3 | -| `firefox` | 2 | -| `safari` | 2 | -| `edge` | 3 | -| Any other string | 3 | - -:::tip -To configure which browser is opened when running dev mode via `wxt -b `, see the [`web-ext.config.ts` docs](/guide/directory-structure/web-ext-config). -::: - -## Target Manifest Version - -To build for a specific manifest version, pass either the `--mv2` flag or `--mv3` flag from the CLI. - -```sh -wxt --mv2 -wxt build --mv2 -``` - -When the `-b --browser` flag is not passed, it defaults to `chrome`. So here, we're targeting MV2 for Chrome. - -## Customizing Entrypoints - -There are several ways to customize entrypoint definitions per browser. - -First, you can use either the `include` or `exclude` option to include or exclude the entrypoint from specific browsers. Here are some examples - -:::code-group - -```ts [Background] -export default defineBackground({ - // Only include a background script when targeting chrome - include: ['chrome'], -}); -``` - -```ts [Content Script] -export default defineContentScript({ - // Do not add this content script to the manifest when targeting firefox - exclude: ['firefox'], -}); -``` - -```html [HTML page] - - - - - - - -``` - -::: - -Second, you can change individual options per-browser: - -:::code-group - -```ts [Background] -export default defineBackground({ - persistent: { - // Use a non-persistent background script for just safari - safari: false, - }, -}); -``` - -```ts [Content Script] -export default defineContentScript({ - matches: { - // Run the content script on different pages for each browser - chrome: ['*://*.google.com/*'], - firefox: ['*://*.duckduckgo.com/*'], - edge: ['*://*.bing.com/*'], - }, -}); -``` - -::: - -:::warning -Only `defineBackground` and `defineContentScript` support per-browser options right now. -::: - -## Runtime - -To determine the browser or manifest version at runtime, you can use any of the below variables: - -- `import.meta.env.BROWSER`: A string, the target browser, usually equal to the `--browser` flag -- `import.meta.env.MANIFEST_VERSION`: A number, either `2` or `3`, depending on the manifest version targeted -- `import.meta.env.CHROME`: A boolean equivalent to `import.meta.env.BROWSER === "chrome"` -- `import.meta.env.FIREFOX`: A boolean equivalent to `import.meta.env.BROWSER === "firefox"` -- `import.meta.env.EDGE`: A boolean equivalent to `import.meta.env.BROWSER === "edge"` -- `import.meta.env.SAFARI`: A boolean equivalent to `import.meta.env.BROWSER === "safari"` -- `import.meta.env.OPERA`: A boolean equivalent to `import.meta.env.BROWSER === "opera"` -- `import.meta.env.COMMAND`: A string, `"serve"` when running `wxt` for development or `"build"` in all other cases. - -:::info -These variables are constants defined at build time based on the build target. They do not actually detect which browser the code is running in. - -For example, if you build for `--browser chrome` and publish it on Edge, `import.meta.env.BROWSER` will be `"chrome"`, not `"edge"`. You have to build a separate ZIP for `--browser edge` before `import.meta.env.BROWSER` will be `"edge"`. - -If you need to know the actual browser your code is being ran on, you should use a [user agent parser](https://www.npmjs.com/package/ua-parser-js). -::: diff --git a/docs/guide/key-concepts/web-extension-polyfill.md b/docs/guide/key-concepts/web-extension-polyfill.md deleted file mode 100644 index 09ae0139..00000000 --- a/docs/guide/key-concepts/web-extension-polyfill.md +++ /dev/null @@ -1,108 +0,0 @@ -# Web Extension Polyfill - -## Overview - -WXT is built on top [`webextension-polyfill` by Mozilla](https://www.npmjs.com/package/webextension-polyfill). The polyfill standardizes much of web extension APIs so they behave the same across different browsers and manifest versions. - -Unlike with Chrome Extension development, which uses a `chrome` global, you need to import the `browser` variable from WXT to access the extension APIs: - -```ts -import { browser } from 'wxt/browser'; - -console.log(browser.runtime.id); -``` - -If you use auto-imports (enabled by default), you don't need to import this variable, it will work just like the `chrome` global: - -```ts -console.log(browser.runtime.id); -``` - -## Handling Differences - -Web extensions behave **_VERY_** differently between browsers and manifest versions. You will have to handle these API differences yourself. - -:::info -MDN has great compatibility tables for tracking which browsers support which APIs: [Web Extension Browser Support for JavaScript APIs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs) -::: - -Lets go over a few approaches: - -1. **Feature detection**: If an API isn't available, it is `undefined`. So check if that's the case before using it. - - ```ts - if (browser.runtime.onStartup) { - browser.runtime.onStartup.addListener(...); - } - ``` - - If there's a similar API you can fallback on, you can do something like this: - - ```ts - (browser.action ?? browser.browserAction).setBadgeColor('red'); - ``` - -2. **Check the browser**: WXT provides environment variables about which browser is being targeted. - ```ts - if (!import.meta.env.SAFARI) { - // Safari doesn't implement `onStartup` correctly, so we need a custom solution - // ... - } else { - browser.runtime.onStartup.addListener(...) - } - ``` -3. **Check the manifest version**: WXT provides environment variables about which manifest version is being targeted. - ```ts - if (import.meta.env.MANIFEST_VERSION === 3) { - // MV3 only code... - } else { - // MV2 only code... - } - ``` - -### Environment Variables - -| Name | Type | Description | -| ---------------------------------- | --------- | ----------------------------------------------------- | -| `import.meta.env.BROWSER` | `string` | The target browser | -| `import.meta.env.MANIFEST_VERSION` | `2 │ 3` | The target manifest version | -| `import.meta.env.CHROME` | `boolean` | equivalent to `import.meta.env.BROWSER === "chrome"` | -| `import.meta.env.FIREFOX` | `boolean` | equivalent to `import.meta.env.BROWSER === "firefox"` | -| `import.meta.env.SAFARI` | `boolean` | equivalent to `import.meta.env.BROWSER === "safari"` | -| `import.meta.env.EDGE` | `boolean` | equivalent to `import.meta.env.BROWSER === "edge"` | -| `import.meta.env.OPERA` | `boolean` | equivalent to `import.meta.env.BROWSER === "opera"` | - -WXT uses Vite, so all of Vite's `import.meta.env` variables are also available: - - - -## Augmented Types - -Based on the files in your project, WXT will modify some of the polyfill's types to be type-safe. - -For example, `browser.runtime.getURL` will be typed to only allow getting the URL of known files. `browser.i18n.getMessage` will only allow getting translations of messages defined in your `public/_locales//messages.json` file. - -## Missing Types - -Some newer APIs that Chrome provides are missing types. Don't worry, the APIs are present at runtime! The polyfill only provides types for standard and stable APIs that work on all browsers, so just be careful when you use them. - -If you're using TypeScript, you can use `@ts-expect-error` to ignore any errors when using an API that doesn't have any types. - -```ts -// @ts-expect-error: desktopCapture is not typed -browser.desktopCapture.chooseDesktopMedia(...) -``` - -Note that when running this code in a different browser that doesn't support the `desktopCapture` API, `browser.desktopCapture` will evaluate to `undefined` and an error will be thrown. - -## Missing Permissions - -Just like with the `chrome` global, you need to request the required permissions to use each API. Otherwise, `browser.{apiName}` will be `undefined`. - -For example, if you try to use `browser.storage.local.getItem(...)` without requesting the `storage` permission, the extension will throw an error: - -``` -Cannot access property "local" of undefined. -``` - -You can request permissions using the [`wxt.config.ts` file](/guide/directory-structure/wxt-config#permissions). diff --git a/docs/guide/key-concepts/wxt-submit.md b/docs/guide/key-concepts/wxt-submit.md deleted file mode 100644 index 2c194929..00000000 --- a/docs/guide/key-concepts/wxt-submit.md +++ /dev/null @@ -1,9 +0,0 @@ -# `wxt submit` - -> Alias for [`publish-browser-extension`](https://www.npmjs.com/package/publish-browser-extension) - - - -
{{ data.submit }}
diff --git a/docs/guide/resources/compare.md b/docs/guide/resources/compare.md new file mode 100644 index 00000000..ef025f7f --- /dev/null +++ b/docs/guide/resources/compare.md @@ -0,0 +1,57 @@ +# Compare + +Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework) (another framework) and [CRXJS](https://crxjs.dev/vite-plugin) (a bundler plugin). + +## Overview + +- ✅ - Full support +- 🟡 - Partial support +- ❌ - No support + +| Features | WXT | Plasmo | CRXJS | +| ------------------------------------------------------- | :--------------: | :-------------: | :--------------: | +| Supports all browsers | ✅ | ✅ | 🟡 10 | +| MV2 Support | ✅ | ✅ | 🟡 1 | +| MV3 Support | ✅ | ✅ | 🟡 1 | +| Create Extension ZIPs | ✅ | ✅ | ❌ | +| Create Firefox Sources ZIP | ✅ | ❌ | ❌ | +| First-class TypeScript support | ✅ | ✅ | ✅ | +| Entrypoint discovery | ✅ 2 | ✅ 2 | ❌ | +| Inline entrypoint config | ✅ | ✅ | ❌ 9 | +| Auto-imports | ✅ | ❌ | ❌ | +| Reusable module system | ✅ | ❌ | ❌ | +| Supports all frontend frameworks | ✅ | 🟡 3 | ✅ | +| Framework specific entrypoints (like `Popup.tsx`) | 🟡 4 | ✅ 5 | ❌ | +| Automated publishing | ✅ | ✅ | ❌ | +| Remote Code Bundling (Google Analytics) | ✅ | ✅ | ❌ | +| Unlisted HTML Pages | ✅ | ✅ | ✅ | +| Unlisted Scripts | ✅ | ❌ | ❌ | +| ESM Content Scripts | ❌ 12 | ❌ | ✅ | +| Dev Mode | | | +| `.env` Files | ✅ | ✅ | ✅ | +| Opens browser with extension installed | ✅ | ❌ | ❌ | +| HMR for UIs | ✅ | 🟡 6 | ✅ | +| Reload HTML Files on Change | ✅ | 🟡 7 | ✅ | +| Reload Content Scripts on Change | ✅ | 🟡 7 | ✅ | +| Reload Background on Change | 🟡 7 | 🟡 7 | 🟡 7 | +| Respects Content Script `run_at` | ✅ | ✅ | ❌ 8 | +| Built-in Wrappers | | | | +| Storage | ✅ | ✅ | ❌ 11 | +| Messaging | ❌ 11 | ✅ | ❌ 11 | +| Content Script UI | ✅ | ✅ | ❌ 11 | +| I18n | ✅ | ❌ | ❌ | + + + 1: Either MV2 or MV3, not both. +
2: File based. +
3: Only React, Vue, and Svelte. +
4: .html .ts .tsx. +
5: .html .ts .tsx. .vue .svelte. +
6: React only. +
7: Reloads entire extension. +
8: ESM-style loaders run asynchronously. +
9: Entrypoint options all configured in manifest.json. +
10: As of v2.0.0-beta.23, but v2 stable hasn't been released yet. +
11: There is no built-in wrapper around this API. However, you can still access the standard APIs via chrome/browser globals or use any 3rd party NPM package. +
12: WIP, moving very slowly. Follow wxt-dev/wxt#357 for updates. +
diff --git a/docs/guide/resources/faq.md b/docs/guide/resources/faq.md new file mode 100644 index 00000000..275bfb3b --- /dev/null +++ b/docs/guide/resources/faq.md @@ -0,0 +1,21 @@ +--- +outline: false +--- + +# FAQ + +Commonly asked questions about how to use WXT or why it behaves the way it does. + +[[toc]] + +--- + +### Why are content scripts not showing up in the manifest? + +During development, WXT registers content scripts dynamically so they can be reloaded individually when a file is saved without reloading your entire extension. + +To list the content scripts registered during development, open the service worker's console and run: + +```js +await chrome.scripting.getRegisteredContentScripts(); +``` diff --git a/docs/guide/go-further/how-wxt-works.md b/docs/guide/resources/how-wxt-works.md similarity index 100% rename from docs/guide/go-further/how-wxt-works.md rename to docs/guide/resources/how-wxt-works.md diff --git a/docs/get-started/migrate-to-wxt.md b/docs/guide/resources/migrate.md similarity index 67% rename from docs/get-started/migrate-to-wxt.md rename to docs/guide/resources/migrate.md index d51a3339..d7a14901 100644 --- a/docs/get-started/migrate-to-wxt.md +++ b/docs/guide/resources/migrate.md @@ -18,15 +18,18 @@ pnpm dlx wxt@latest init example-wxt --template vanilla In general, you'll need to:   Install `wxt`
+  [Extend `.wxt/tsconfig.json`](/guide/essentials/config/typescript.html#typescript-configuration) in your project's `tsconfig.json`
Update/create `package.json` scripts to use `wxt` (don't forget about `postinstall`)
Move entrypoints into `entrypoints/` directory
Move assets into either the `assets/` or `public/` directories
-  Move manifest.json content into `wxt.config.ts`
+  Move `manifest.json` content into `wxt.config.ts`
Convert custom import syntax to be compatible with Vite
-  Add a default export to JS entrypoints
+  Add a default export to JS entrypoints (`defineBackground`, `defineContentScript`, or `defineUnlistedScript`)
Use the `browser` global instead of `chrome`
-  Compare final `manifest.json` files, making sure permissions and host permissions are unchanged. Use [Google's update testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool) if your extension is already live on the Chrome Web Store
-  Extension output by `wxt build` works the same way as before the migration
+  Compare final `manifest.json` files, making sure permissions and host permissions are unchanged
+:::warning +If your extension is already live on the Chrome Web Store, use [Google's update testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool) to make sure no new permissions are being requested. +::: Every project is different, so there's no one-solution-fits-all to migrating your project. Just make sure `wxt dev` runs, `wxt build` results in a working extension, and the list of permissions in the `manifest.json` hasn't changed. If all that looks good, you've finished migrating your extension! @@ -34,6 +37,18 @@ Every project is different, so there's no one-solution-fits-all to migrating you Here's specific steps for other popular frameworks/build tools. +### Plasmo + +1. Install `wxt` +2. Move entrypoints into `entrypoints/` directory + - For JS entrypoints, merge the named exports used to configure your JS entrypoints into WXT's default export + - For HTML entrypoints, you cannot use JSX/Vue/Svelte files directly, you need to create an HTML file and manually create and mount your app. Refer to the [React](https://github.com/wxt-dev/wxt/tree/main/templates/react/entrypoints/popup), [Vue](https://github.com/wxt-dev/wxt/tree/main/templates/vue/entrypoints/popup), and [Svelte](https://github.com/wxt-dev/wxt/tree/main/templates/svelte/src/entrypoints/popup) templates as an example. +3. Move public `assets/*` into the `public/` directory +4. If you use CSUI, migrate to WXT's `createContentScriptUi` +5. Convert Plasmo's custom import resolutions to Vite's +6. If importing remote code via a URL, add a `url:` prefix so it works with WXT +7. Compare your output `manifest.json` files from before the migration to after the migration. They should have the same content. If not, tweak your entrypoints and config to get as close as possible. + ### `vite-plugin-web-extension` Since you're already using Vite, it's a simple refactor. @@ -45,21 +60,3 @@ Since you're already using Vite, it's a simple refactor. 5. Move the `manifest.json` into `wxt.config.ts` 6. Move any custom settings from `vite.config.ts` into `wxt.config.ts`'s 7. Compare `dist/manifest.json` to `.output/*/manifest.json`, they should have the same content as before. If not, tweak your entrypoints and config to get as close as possible. - -### `plasmo` - -1. Install `wxt` -2. Move entrypoints into `entrypoints/` directory, merging the named exports used to configure your JS entrypoints into WXT's default export -3. Move public `assets/*` into the `public/` directory -4. If you use CSUI, migrate to WXT's `createContentScriptUi` -5. Convert Plasmo's custom import resolutions to Vite's -6. If importing remote code via a URL, add a `url:` prefix so it works with WXT -7. Compare your output `manifest.json` files from before the migration to after the migration. They should have the same content. If not, tweak your entrypoints and config to get as close as possible. - - diff --git a/docs/guide/upgrade-guide/wxt.md b/docs/guide/resources/upgrading.md similarity index 89% rename from docs/guide/upgrade-guide/wxt.md rename to docs/guide/resources/upgrading.md index fae42874..4bf8f1c1 100644 --- a/docs/guide/upgrade-guide/wxt.md +++ b/docs/guide/resources/upgrading.md @@ -8,7 +8,9 @@ To upgrade WXT to the latest version... just install it! pnpm i wxt@latest ``` -If there was a major version change, follow the steps below to fix breaking changes. +Listed below are all the breaking changes you should address when upgrading to a new version of WXT. + +Currently, WXT is in pre-release. This means changes to the second digit, `v0.X`, are considered major and have breaking changes. Once v1 is released, only major version bumps will have breaking changes. ## v0.18.5 → v0.19.0 @@ -27,7 +29,7 @@ export default defineConfig({ }); ``` -> [Read the full docs](/guide/go-further/entrypoint-loaders#vite-node) for more information. +> [Read the full docs](/guide/essentials/config/entrypoint-loaders#vite-node) for more information. :::details This change enables: @@ -92,7 +94,28 @@ Vite also provides steps for migrating to ESM. Check them out for more details: ### New `modules/` Directory -WXT now recognizes the `modules/` directory as a folder containing [WXT modules](/guide/go-further/reusable-modules). +WXT now recognizes the `modules/` directory as a folder containing [WXT modules](/guide/essentials/wxt-modules). + +If you already have `/modules` or `/Modules` directory, `wxt prepare` and other commands will fail. + +You have two options: + +1. [Recommended] Keep your files where they are and tell WXT to look in a different folder: + ```ts + // wxt.config.ts + export default defineConfig({ + modulesDir: 'wxt-modules', // defaults to "modules" + }); + ``` +2. Rename your `modules` directory to something else. + +## v0.18.0 → v0.18.5 + +> When this version was released, it was not considered a breaking change... but it should have been. + +### New `modules/` Directory + +WXT now recognizes the `modules/` directory as a folder containing [WXT modules](/guide/essentials/wxt-modules). If you already have `/modules` or `/Modules` directory, `wxt prepare` and other commands will fail. diff --git a/docs/i18n.md b/docs/i18n.md new file mode 100644 index 00000000..1447f7d5 --- /dev/null +++ b/docs/i18n.md @@ -0,0 +1,5 @@ +--- +outline: deep +--- + + diff --git a/docs/index.md b/docs/index.md index d0c15a8e..b880af3e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,29 +13,29 @@ hero: actions: - theme: brand text: Get Started - link: /get-started/installation + link: /guide/installation - theme: alt text: Learn More - link: /get-started/introduction + link: /guide/introduction features: - icon: 🌐 title: Supported Browsers details: WXT will build extensions for Chrome, Firefox, Edge, Safari, and any Chromium based browser. - link: /guide/key-concepts/web-extension-polyfill + link: /guide/essentials/target-different-browsers linkText: Read docs - icon: ✅ title: MV2 and MV3 details: Build Manifest V2 or V3 extensions for any browser using the same codebase. - link: /guide/key-concepts/manifest + link: /guide/essentials/config/manifest linkText: Read docs - icon: ⚡ title: Fast Dev Mode - details: Lighting fast HMR for UI development and fast reloads for content/background scripts enables faster iterations. + details: Lightning fast HMR for UI development and fast reloads for content/background scripts enables faster iterations. - icon: 📂 title: File Based Entrypoints details: Manifest is generated based on files in the project with inline configuration. - link: /get-started/entrypoints + link: /guide/essentials/project-structure linkText: See project structure - icon: 🚔 title: TypeScript @@ -43,7 +43,7 @@ features: - icon: 🦾 title: Auto-imports details: Nuxt-like auto-imports to speed up development. - link: /guide/key-concepts/auto-imports + link: /guide/essentials/config/auto-imports linkText: Read docs - icon: 🤖 title: Automated Publishing @@ -51,17 +51,17 @@ features: - icon: 🎨 title: Frontend Framework Agnostic details: Works with any front-end framework with a Vite plugin. - link: /guide/key-concepts/frontend-frameworks + link: /guide/essentials/frontend-frameworks linkText: Add a framework - icon: 📦 - title: Modular Architecture + title: Module System details: Reuse build-time and runtime-code across multiple extensions. - link: /guide/go-further/reusable-modules + link: /guide/essentials/wxt-modules linkText: Read docs - icon: 🖍️ title: Bootstrap a New Project details: Get started quickly with several awesome project templates. - link: /get-started/installation#bootstrap-project + link: /guide/installation#bootstrap-project linkText: See templates - icon: 📏 title: Bundle Analysis @@ -69,13 +69,13 @@ features: - icon: ⬇️ title: Bundle Remote Code details: Downloads and bundles remote code imported from URLs. - link: /guide/go-further/remote-code + link: /guide/essentials/remote-code linkText: Read docs --- ## Put Developer Experience First -WXT's simplifies the chrome extension development process by providing tools for zipping and publishing, the best-in-class dev mode, an opinionated project structure, and more. Iterate faster, develop features not build scripts, and use everything the JS ecosystem has to offer. +WXT simplifies the chrome extension development process by providing tools for zipping and publishing, the best-in-class dev mode, an opinionated project structure, and more. Iterate faster, develop features not build scripts, and use everything the JS ecosystem has to offer.
diff --git a/docs/public/_redirects b/docs/public/_redirects index efd94762..ccc1a02a 100644 --- a/docs/public/_redirects +++ b/docs/public/_redirects @@ -6,6 +6,7 @@ # OLD /config.html /api/reference/wxt/interfaces/InlineConfig.html /api/config.html /api/reference/wxt/interfaces/InlineConfig.html +/api/config /api/reference/wxt/interfaces/InlineConfig.html /entrypoints /entrypoints/background.html /get-started/assets.html /guide/assets.html /get-started/build-targets.html /guide/multiple-browsers.html @@ -34,3 +35,71 @@ # 0.19.0 /guide/go-further/entrypoint-side-effects.html /guide/go-further/entrypoint-loaders.html + +# https://github.com/wxt-dev/wxt/issues/704 +# Generated via `pnpm docs:build && find docs/.vitepress/dist -type f -name "*.html"` + +/guide/i18n/build-integrations.html /i18n#build-integrations +/guide/i18n/introduction.html /i18n +/guide/i18n/messages-file-format.html /i18n#messages-file-format +/guide/i18n/editor-support.html /i18n#editor-support +/guide/i18n/installation.html /i18n#installation +/guide/key-concepts/content-script-ui.html /guide/essentials/content-scripts.html#ui +/guide/key-concepts/manifest.html /guide/essentials/config/manifest.html +/guide/key-concepts/wxt-submit.html /api/cli/wxt-submit.html +/guide/key-concepts/auto-imports.html /guide/essentials/config/auto-imports.html +/guide/key-concepts/web-extension-polyfill.html /guide/essentials/extension-apis.html +/guide/key-concepts/frontend-frameworks.html /guide/essentials/frontend-frameworks.html +/guide/key-concepts/multiple-browsers.html /guide/essentials/target-different-browsers.html +/guide/go-further/entrypoint-loaders.html /guide/essentials/config/entrypoint-loaders.html +/guide/go-further/es-modules.html /guide/essentials/es-modules.html +/guide/go-further/handling-updates.html /guide/essentials/testing-updates.html +/guide/go-further/custom-events.html /guide/essentials/content-scripts.html#dealing-with-spas +/guide/go-further/debugging.html /TODO +/guide/go-further/remote-code.html /guide/essentials/remote-code.html +/guide/go-further/vite.html /guide/essentials/config/vite.html +/guide/go-further/testing.html /guide/essentials/unit-testing.html +/guide/go-further/how-wxt-works.html /guide/resources/how-wxt-works.html +/guide/go-further/reusable-modules.html /guide/essentials/wxt-modules.html +/guide/extension-apis/messaging.html /guide/essentials/messaging.html +/guide/extension-apis/i18n.html /guide/essentials/i18n.html +/guide/extension-apis/storage.html /guide/essentials/storage.html +/guide/extension-apis/scripting.html /guide/essentials/scripting.html +/guide/extension-apis/others.html /guide/essentials/extension-apis.html +/guide/upgrade-guide/wxt.html /guide/resources/upgrading.html +/guide/directory-structure/components.html /guide/essentials/project-structure.html +/guide/directory-structure/hooks.html /guide/essentials/config/hooks.html +/guide/directory-structure/assets.html /guide/essentials/assets.html#assets-directory +/guide/directory-structure/package.html /guide/essentials/project-structure.html +/guide/directory-structure/env.html /guide/essentials/config/runtime.html +/guide/directory-structure/wxt-config.html /guide/essentials/project-structure.html +/guide/directory-structure/wxt.html /guide/essentials/project-structure.html +/guide/directory-structure/public/locales.html /guide/essentials/project-structure.html +/guide/directory-structure/public/index.html /guide/essentials/assets.html#public-directory +/guide/directory-structure/entrypoints/devtools.html /guide/essentials/entrypoints.html#devtools +/guide/directory-structure/entrypoints/sidepanel.html /guide/essentials/entrypoints.html#sidepanel +/guide/directory-structure/entrypoints/content-scripts.html /guide/essentials/entrypoints.html#content-scripts +/guide/directory-structure/entrypoints/newtab.html /guide/essentials/entrypoints.html#newtab +/guide/directory-structure/entrypoints/bookmarks.html /guide/essentials/entrypoints.html#bookmarks +/guide/directory-structure/entrypoints/unlisted-pages.html /guide/essentials/entrypoints.html#unlisted-pages +/guide/directory-structure/entrypoints/unlisted-scripts.html /guide/essentials/entrypoints.html#unlisted-scripts +/guide/directory-structure/entrypoints/options.html /guide/essentials/entrypoints.html#options +/guide/directory-structure/entrypoints/background.html /guide/essentials/entrypoints.html#background +/guide/directory-structure/entrypoints/popup.html /guide/essentials/entrypoints.html#popup +/guide/directory-structure/entrypoints/history.html /guide/essentials/entrypoints.html#history +/guide/directory-structure/entrypoints/sandbox.html /guide/essentials/entrypoints.html#sandbox +/guide/directory-structure/entrypoints/css.html /guide/essentials/entrypoints.html#unlisted-css +/guide/directory-structure/web-ext-config.html /guide/essentials/config/browser-startup.html +/guide/directory-structure/tsconfig.html /guide/essentials/config/typescript.html +/guide/directory-structure/utils.html /guide/essentials/project-structure.html +/guide/directory-structure/app-config.html /guide/essentials/config/runtime.html +/guide/directory-structure/composables.html /guide/essentials/project-structure.html +/guide/directory-structure/output.html /guide/essentials/project-structure.html +/get-started/assets.html /guide/essentials/assets.html +/get-started/introduction.html /guide/introduction.html +/get-started/configuration.html /guide/essentials/config/manifest.html +/get-started/publishing.html /guide/essentials/publishing.html +/get-started/migrate-to-wxt.html /guide/resources/migrate.html +/get-started/compare.html /guide/resources/compare.html +/get-started/entrypoints.html /guide/essentials/entrypoints.html +/get-started/installation.html /guide/installation.html diff --git a/docs/guide/extension-apis/storage.md b/docs/storage.md similarity index 96% rename from docs/guide/extension-apis/storage.md rename to docs/storage.md index 9d593074..6fb876d3 100644 --- a/docs/guide/extension-apis/storage.md +++ b/docs/storage.md @@ -2,7 +2,9 @@ outline: deep --- -# Storage API +# WXT Storage + +[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md) WXT provides a simplified API to replace the `browser.storage.*` APIs. Use the `storage` auto-import from `wxt/storage` or import it manually to get started: @@ -10,22 +12,17 @@ WXT provides a simplified API to replace the `browser.storage.*` APIs. Use the ` import { storage } from 'wxt/storage'; ``` -:::warning -To use the `wxt/storage` API, the `"storage"` permission must be added to the manifest: - -```ts -// wxt.config.ts -export default defineConfig({ - manifest: { - permissions: ['storage'], - }, -}); -``` - -More info on permissions [here](/guide/key-concepts/manifest#permissions). -::: - -[[toc]] +> [!IMPORTANT] +> To use the `wxt/storage` API, the `"storage"` permission must be added to the manifest: +> +> ```ts +> // wxt.config.ts +> export default defineConfig({ +> manifest: { +> permissions: ['storage'], +> }, +> }); +> ``` ## Basic Usage diff --git a/packages/auto-icons/README.md b/packages/auto-icons/README.md index 0cef2993..3ee34773 100644 --- a/packages/auto-icons/README.md +++ b/packages/auto-icons/README.md @@ -1,5 +1,7 @@ # WXT Auto Icons +[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/auto-icons/CHANGELOG.md) + ## Features - Generate extension icons with the correct sizes diff --git a/packages/i18n/README.md b/packages/i18n/README.md index ea50ac14..d1fc05dc 100644 --- a/packages/i18n/README.md +++ b/packages/i18n/README.md @@ -1,3 +1,368 @@ -# WXT I18n +# `@wxt-dev/i18n` -[Read the docs](https://wxt.dev/guide/i18n/installation) to get started. +[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/i18n/CHANGELOG.md) + +`@wxt-dev/i18n` is a simple, type-safe wrapper around the `browser.i18n` APIs. It provides several benefits over the standard API: + +1. Simple messages file format +2. Plural form support +3. Integrates with the [I18n Ally VS Code extension](#vscode) + +It also provides several benefits over other non-web extension specific i18n packages: + +1. Does not bundle localization files into every entrypoint +2. Don't need to fetch the localization files asynchronously +3. Can localize text in manifest and CSS files + +However, it does have one major downside: + +1. Like the `browser.i18n` API, to change the language, users must change the browser's language + +> [!IMPORTANT] +> You don't have to use `wxt` to use this package - it will work in any bundler setup. See [Installation without WXT](#without-wxt) for more details. + +## Installation + +### With WXT + +1. Install `@wxt-dev/i18n` via your package manager: + + ```sh + pnpm i @wxt-dev/i18n + ``` + +2. Add the WXT module to your `wxt.config.ts` file and setup a default locale: + + ```ts + export default defineConfig({ + modules: ['@wxt-dev/i18n/module'], + manifest: { + default_locale: 'en', + }, + }); + ``` + +3. Create a localization file at `/locales/.yml` or move your existing localization files there. + + ```yml + # /locales/en.yml + helloWorld: Hello world! + ``` + + > `@wxt-dev/i18n` supports the standard messages format, so if you already have localization files at `/public/_locale//messages.json`, you don't need to convert them to YAML or refactor them - just move them to `/locales/.json` and they'll just work out of the box! + +4. To get a translation, use the auto-imported `i18n` object or import it manually: + + ```ts + import { i18n } from '#i18n'; + + i18n.t('helloWorld'); // "Hello world!" + ``` + +And you're done! Using WXT, you get type-safety out of the box. + +### Without WXT + +1. Install `@wxt-dev/i18n` via your package manager: + + ```sh + pnpm i @wxt-dev/i18n + ``` + +2. Create a messages file at `_locales//messages.json` or move your existing translations there: + + ```json + { + "helloWorld": { + "message": "Hello world!" + } + } + ``` + + > [!NOTE] + > For the best DX, you should to integrate `@wxt-dev/i18n` into your build process. This enables: + + > 1. Plural forms + > 2. Simple messages file format + > 3. Type safety + > + > See [Build Integrations](#build-integrations) to set it up. + +3. Create the `i18n` object, export it, and use it wherever you want! + + ```ts + import { createI18n } from '@wxt-dev/i18n'; + + export const i18n = createI18n(); + + i18n.t('helloWorld'); // "Hello world!"; + ``` + +## Messages File Format + +> [!DANGER] +> You can only use the file format discussed on this page if you have [integrated `@wxt-dev/i18n` into your build process](#build-integrations). If you have not integrated it into your build process, you must use JSON files in the `_locales` directory, like a normal web extension. + +### File Extensions + +You can define your messages in several different file types: + +- `.yml` +- `.yaml` +- `.json` +- `.jsonc` +- `.json5` +- `.toml` + +### Nested Keys + +You can have translations at the top level or nest them into groups: + +```yml +ok: OK +cancel: Cancel +welcome: + title: Welcome to XYZ +dialogs: + confirmation: + title: 'Are you sure?' +``` + +To access a nested key, use `.`: + +```ts +i18n.t('ok'); // "OK" +i18n.t('cancel'); // "Cancel" +i18n.t('welcome.title'); // "Welcome to XYZ" +i18n.t('dialogs.confirmation.title'); // "Are you sure?" +``` + +### Substitutions + +Because `@wxt-dev/i18n` is based on `browser.i18n`, you define substitutions the same way, with `$1`-`$9`: + +```yml +hello: Hello $1! +order: Thanks for ordering your $1 +``` + +#### Escapting `$` + +To escape the dollar sign, put another `$` in front of it: + +```yml +dollars: $$$1 +``` + +```ts +i18n.t('dollars', ['1.00']); // "$1.00" +``` + +### Plural Forms + +> [!WARNING] +> Plural support languages like Arabic, that have different forms for "few" or "many", is not supported right now. Feel free to open a PR if you are interested in this! + +To get a different translation based on a count: + +```yml +items: + 1: 1 item + n: $1 items +``` + +Then you pass in the count as the second argument to `i18n.t`: + +```ts +i18n.t('items', 0); // "0 items" +i18n.t('items', 1); // "1 item" +i18n.t('items', 2); // "2 items" +``` + +To add a custom translation for 0 items: + +```yml +items: + 0: No items + 1: 1 item + n: $1 items +``` + +```ts +i18n.t('items', 0); // "No items" +i18n.t('items', 1); // "1 item" +i18n.t('items', 2); // "2 items" +``` + +If you need to pass a custom substitution for `$1` instead of the count, just add the substitution: + +```yml +items: + 0: No items + 1: $1 item + n: $1 items +``` + +```ts +i18n.t('items', 0, ['Zero']); // "No items" +i18n.t('items', 1, ['One']); // "One item" +i18n.t('items', 2, ['Multiple']); // "Multiple items" +``` + +### Verbose Keys + +`@wxt-dev/i18n` is compatible with the message format used by [`browser.i18n`](https://developer.chrome.com/docs/extensions/reference/api/i18n). + +> [!IMPORTANT] +> This means if you're migrating to `@wxt-dev/i18n` and you're already using the verbose format, you don't have to change anything! + +A key is treated as "verbose" when it is: + +1. At the top level (not nested) +2. Only contains the following properties: `message`, `description` and/or `placeholder` + +```json +{ + "appName": { + "message": "GitHub - Better Line Counts", + "description": "The app's name, should not be translated", + }, + "ok": "OK", + "deleteConfirmation": { + "title": "Delete XYZ?" + "message": "You cannot undo this action once taken." + } +} +``` + +In this example, only `appName` is considered verbose. `deleteConfirmation` is not verbose because it contains the additional property `title`. + +```ts +i18n.t('appName'); // ✅ "GitHub - Better Line Counts" +i18n.t('appName.message'); // ❌ +i18n.t('ok'); // ✅ "OK" +i18n.t('deleteConfirmation'); // ❌ +i18n.t('deleteConfirmation.title'); // ✅ "Delete XYZ?" +i18n.t('deleteConfirmation.message'); // ✅ "You cannot undo this action once taken." +``` + +If this is confusing, don't worry! With type-safety, you'll get a type error if you do it wrong. If type-safety is disabled, you'll get a runtime warning in the devtools console. + +> [!WARNING] +> Using the verbose format is not recommended. I have yet to see a translation service and software that supports this format out of the box. Stick with the simple format when you can. + +## Build Integrations + +To use the custom messages file format, you'll need to use `@wxt-dev/i18n/build` to transform the custom format to the one expected by browsers. + +### WXT + +See [Installation with WXT](#with-wxt). + +But TLDR, all you need is: + +```ts +// wxt.config.ts +export default defineConfig({ + modules: ['@wxt-dev/i18n/module'], +}); +``` + +Types are generated whenever you run `wxt prepare` or another build command. + +### Custom + +If you're not using WXT, you'll have to pre-process the localization files yourself. Here's a basic script to generate the `_locales/.../messages.json` and `wxt-i18n-structure.d.ts` files: + +```ts +// build-i18n.js +import { + parseMessagesFile, + generateChromeMessagesFile, + generateTypeFile, +} from '@wxt-dev/i18n/build'; + +// Read your localization files +const messages = { + en: await parseMessagesFile('path/locales/en.yml'), + de: await parseMessagesFile('path/locales/de.yml'), + // ... +}; + +// Generate JSON files for the browser +await generateChromeMessagesFile('dist/_locales/en/messages.json', messages.en); +await generateChromeMessagesFile('dist/_locales/de/messages.json', messages.de); +// ... + +// Generate a types file based on your default_locale +await generateTypeFile('wxt-i18n-structure.d.ts', messages.en); +``` + +Then run the script: + +```sh +node generate-i18n.js +``` + +If your build tool has a dev mode, you'll also want to rerun the script whenever you change a localization file. + +#### Type Safety + +Once you've generated `wxt-i18n-structure.d.ts` (see the [Custom](#custom) section), you can use it to pass the generated type into `createI18n`: + +```ts +import type { WxtI18nStructure } from './wxt-i18n-structure'; + +export const i18n = createI18n(); +``` + +And just like that, you have type safety! + +## Editor Support + +For better DX, you can configure your editor with plugins and extensions. + +### VS Code + +The [I18n Ally Extension](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) adds several features to VS Code: + +- Go to translation definition +- Inline previews of text +- Hover to see other translations + +You'll need to configure it the extension so it knows where your localization files are and what function represents getting a translation: + +`.vscode/i18n-ally-custom-framework.yml`: + +```yml +# An array of strings which contain Language Ids defined by VS Code +# You can check available language ids here: https://code.visualstudio.com/docs/languages/identifiers +languageIds: + - typescript + +# Look for t("...") +usageMatchRegex: + - "[^\\w\\d]t\\(['\"`]({key})['\"`]" + +# Disable other built-in i18n ally frameworks +monopoly: true +``` + +`.vscode/settings.json`: + +```json +{ + "i18n-ally.localesPaths": ["src/locales"], + "i18n-ally.keystyle": "nested" +} +``` + +### Zed + +As of time of writing, Aug 18, 2024, no extensions exist for Zed to add I18n support. + +### Jetbrains IDEs + +Install the [I18n Ally plugin](https://plugins.jetbrains.com/plugin/17212-i18n-ally). The docs are limited around their Jetbrains support, but you'll probably need to configure the plugin similar to [VS Code](#vs-code)... Not sure where the files go though. + +Please open a PR if you figure it out! diff --git a/packages/wxt/README.md b/packages/wxt/README.md index f629dbd0..530b187b 100644 --- a/packages/wxt/README.md +++ b/packages/wxt/README.md @@ -71,7 +71,7 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get - 🦾 Auto-imports - 🤖 Automated publishing - 🎨 Frontend framework agnostic: works with Vue, React, Svelte, etc -- 📦 Modular architecture with [WXT modules](https://wxt.dev/guide/go-further/reusable-modules.html#overview) +- 📦 [Module system](https://wxt.dev/guide/essentials/wxt-modules.html#overview) for reusing code between extensions - 🖍️ Quickly bootstrap a new project - 📏 Bundle analysis - ⬇️ Download and bundle remote URL imports diff --git a/packages/wxt/src/browser/chrome.ts b/packages/wxt/src/browser/chrome.ts index cb9a4430..d1dad85d 100644 --- a/packages/wxt/src/browser/chrome.ts +++ b/packages/wxt/src/browser/chrome.ts @@ -13,9 +13,11 @@ export type WxtBrowser = Omit & { i18n: WxtI18n & Omit<(typeof chrome)['i18n'], 'getMessage'>; }; +// #region snippet export const browser: WxtBrowser = // @ts-expect-error globalThis.browser?.runtime?.id == null ? globalThis.chrome : // @ts-expect-error globalThis.browser; +// #endregion snippet diff --git a/packages/wxt/src/client/content-scripts/ui/index.ts b/packages/wxt/src/client/content-scripts/ui/index.ts index c9ba6fb4..6c7a614a 100644 --- a/packages/wxt/src/client/content-scripts/ui/index.ts +++ b/packages/wxt/src/client/content-scripts/ui/index.ts @@ -17,7 +17,7 @@ export * from './types'; /** * Create a content script UI without any isolation. * - * @see https://wxt.dev/guide/key-concepts/content-script-ui.html#integrated + * @see https://wxt.dev/guide/essentials/content-scripts.html#integrated */ export function createIntegratedUi( ctx: ContentScriptContext, @@ -53,7 +53,7 @@ export function createIntegratedUi( /** * Create a content script UI using an iframe. * - * @see https://wxt.dev/guide/key-concepts/content-script-ui.html#iframe + * @see https://wxt.dev/guide/essentials/content-scripts.html#iframe */ export function createIframeUi( ctx: ContentScriptContext, @@ -96,7 +96,7 @@ export function createIframeUi( * * > This function is async because it has to load the CSS via a network call. * - * @see https://wxt.dev/guide/key-concepts/content-script-ui.html#shadowroot + * @see https://wxt.dev/guide/essentials/content-scripts.html#shadowroot */ export async function createShadowRootUi( ctx: ContentScriptContext, diff --git a/packages/wxt/src/core/builders/vite/index.ts b/packages/wxt/src/core/builders/vite/index.ts index 9af93a7a..dfe7fc9b 100644 --- a/packages/wxt/src/core/builders/vite/index.ts +++ b/packages/wxt/src/core/builders/vite/index.ts @@ -111,7 +111,7 @@ export async function createViteBuilder( plugins, esbuild: { // Add a footer with the returned value so it can return values to `scripting.executeScript` - // Footer is added apart of esbuild to make sure it's not minified. It + // Footer is added a part of esbuild to make sure it's not minified. It // get's removed if added to `build.rollupOptions.output.footer` // See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript#return_value footer: iifeReturnValueName + ';', diff --git a/packages/wxt/src/core/generate-wxt-dir.ts b/packages/wxt/src/core/generate-wxt-dir.ts index ef39c8a8..cf9980c7 100644 --- a/packages/wxt/src/core/generate-wxt-dir.ts +++ b/packages/wxt/src/core/generate-wxt-dir.ts @@ -22,7 +22,7 @@ export async function generateWxtDir(entrypoints: Entrypoint[]): Promise { // Add references to modules installed from NPM to the TS project so their // type augmentation can update InlineConfig correctly. Local modules defined - // in /modules are already apart of the project, so we don't need to + // in /modules are already a part of the project, so we don't need to // add them. wxt.config.userModules.forEach((module) => { if (module.type === 'node_module') entries.push({ module: module.id }); diff --git a/packages/wxt/src/virtual/README.md b/packages/wxt/src/virtual/README.md index b06cebed..248c7042 100644 --- a/packages/wxt/src/virtual/README.md +++ b/packages/wxt/src/virtual/README.md @@ -2,7 +2,7 @@ This folder contains scripts that are either loaded as entrypoints to JS files or included in HTML files, just like a project using WXT might load their own scripts. -While they are bundled and shipped inside WXT, Vite considers them apart of your project's source code, not WXT's. This means they cannot import 3rd party modules directly, otherwise `pnpm i --shamefully-hoist=false` and Yarn PnP will fail. +While they are bundled and shipped inside WXT, Vite considers them a part of your project's source code, not WXT's. This means they cannot import 3rd party modules directly, otherwise `pnpm i --shamefully-hoist=false` and Yarn PnP will fail. For this reason, the virtual entrypoints get their own TS project to isolate them from the rest of the project. They can only import from `wxt/*` or utils that don't have any imports from node_modules, like the logger.
Input PatternFilename Output Path
+ entrypoints/{{ pattern[0] }} @@ -31,7 +31,7 @@ const props = defineProps<{ /> + /{{ pattern[1] }}