Compare commits

...

59 Commits

Author SHA1 Message Date
github-actions[bot] d9f10c62ad chore(release): wxt v0.20.4
📼 VHS / Create VHS (push) Waiting to run
2025-04-25 03:03:08 +00:00
aklinker1 35d3c03e4b fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2025-04-25 01:52:10 +00:00
Yunsup Sim fdd38a1580 fix: Fix CORS error in Firefox (#1607) 2025-04-24 19:01:30 -05:00
ТΞNSΛI 3018801f06 feat: add {{packageVersion}} as template variable (#1604) 2025-04-24 12:06:50 -05:00
Anh71me 8e96bfef06 fix: fix typescript error on defineItem fallback (#1601) 2025-04-24 07:31:48 -05:00
ТΞNSΛI 31071bd11e feat: ignore elements with a vite-ignore or wxt-ignore attribute (#1603)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-04-24 00:34:49 -05:00
Yuan f5619f6be1 chore: Move the public folder outside of src for the default Svelte template (#1602) 2025-04-24 00:26:34 -05:00
Aaron adad1b5a2c chore: Fix formatting 2025-04-24 00:24:49 -05:00
Nishu a6c4e19a5d fix: adding missing "" to PublicPath and browser.runtime.getUrl (#1597) 2025-04-24 00:18:06 -05:00
Jack af6f74c344 docs: Added "[Always Light Mode]" to the homepage (#1608) 2025-04-24 00:05:37 -05:00
Aaron ce45cb8a89 docs: Add warning about --load-extension deprecation 2025-04-23 23:35:28 -05:00
aklinker1 14d4aa202d fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2025-04-23 01:51:01 +00:00
Nishu 4c35798dba feat: Ignore popup/index.ts instead of erroring (#1520)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-04-20 21:17:05 -05:00
Aaron 656a9b365f docs(Content Script UI): Add additional details about when onRemove is called 2025-04-20 21:15:08 -05:00
Namu 67fa3db921 docs: Fix import in unit-testing.md (#1598) 2025-04-20 20:36:06 -05:00
github-actions[bot] f2d3061e97 chore(release): wxt v0.20.3
📼 VHS / Create VHS (push) Waiting to run
2025-04-19 17:17:39 +00:00
Aaron 760c34e416 feat: Automatically place document-level CSS outside shadow root (#1594) 2025-04-19 12:12:04 -05:00
Bang·_· 48398b315c docs: Added "[Blens - Time Tracker and AI Insight]" to the homepage (#1587) 2025-04-18 21:53:19 -05:00
techlism 45d0d9d7f1 docs: Fix entrypoints.md examples (#1586) 2025-04-18 21:11:04 -05:00
Aaron b0f4ac8221 fix: Fix double hashing of inline script keys
Additionally, update the vite plugin name to better align with purpose
2025-04-18 20:50:02 -05:00
github-actions[bot] 719192b1aa chore(release): wxt v0.20.2
📼 VHS / Create VHS (push) Waiting to run
2025-04-19 00:40:32 +00:00
Yunsup Sim 7eb32bdac8 fix: Fix hashing issue with inline scripts (#1591) 2025-04-18 19:35:18 -05:00
nostro 862756fd5b chore: update docs to point to webExt instead of runner; update config in demo (#1582) 2025-04-16 09:42:55 -05:00
nostro 5ba16e72e0 docs: add radiofrance extension to usingWXT (#1581) 2025-04-16 09:42:20 -05:00
Aaron e43ae0504d docs: Add filenames to startup examples 2025-04-16 08:09:46 -05:00
Aaron acb6cd180c docs: Fix typo in changelog 2025-04-14 17:24:10 -05:00
github-actions[bot] a3301d0413 chore(release): wxt v0.20.1
📼 VHS / Create VHS (push) Waiting to run
2025-04-14 13:29:52 +00:00
7sDream b9e72358ac feat: type-safe import.meta.env.BROWSER with new targetBrowers config (#1574)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-04-14 08:25:04 -05:00
Aaron 61b42ef326 chore: Update comment 2025-04-13 08:55:14 -05:00
aklinker1 da9e27c28d fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2025-04-12 01:46:11 +00:00
aklinker1 6f80fbabaa fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2025-04-10 01:48:29 +00:00
Aaron d2308bd45c chore(deps): Update all dependencies (#1568) 2025-04-09 09:46:29 -05:00
nostro d35972d24f fix: add JSDoc type annotation to auto-imports for ESlint (#1558) 2025-04-08 18:10:58 -05:00
Aaron 298a264934 docs: Add not about viewing available options for wxt submit 2025-04-08 17:58:53 -05:00
Khalil Yao ad63b595b4 fix: Don't remove top-level destructured variable definitions when importing entrypoints (#1561) 2025-04-05 11:25:15 -05:00
aklinker1 986a9ceebf fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2025-04-02 01:49:03 +00:00
dependabot[bot] 682b65e723 chore(deps): bump @types/node from 20.17.6 to 20.17.30 (#1552)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 16:06:04 -05:00
dependabot[bot] cb661506c0 chore(deps): bump fast-glob from 3.3.2 to 3.3.3 (#1556)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 16:04:57 -05:00
dependabot[bot] 505aa06443 chore(deps): bump @commitlint/config-conventional from 19.7.1 to 19.8.0 (#1555)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 16:04:32 -05:00
Aaron f534a978f1 chore: Upgrade templates to wxt 0.20 (#1551) 2025-03-31 18:22:53 -05:00
Aaron 1293815934 docs: Update unocss import to match latest requirements 2025-03-31 16:17:22 -05:00
Aaron 0c0fd18bf4 docs: Split major and minor version update steps apart 2025-03-31 15:59:03 -05:00
Aaron c49c31813f docs: Add description of modules directory to project structure summaries 2025-03-31 14:24:02 -05:00
Aaron 877ab11bb4 docs: Fix knowledge file generation (#1550) 2025-03-31 13:54:04 -05:00
Alec WM 0d18d8d7e3 feat: enable wxt usage inside of devcontainers (#1406)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-03-31 09:21:56 -05:00
Aaron 5f977857a1 chore: Fix formatting 2025-03-29 09:50:26 -05:00
Aaron a63b21d417 docs: Fix diff highlights after auto-formatting 2025-03-29 09:45:01 -05:00
Aaron e1b7d6090e docs: Refresh entrypoints page with more in-depth examples and documentation 2025-03-28 20:08:53 -05:00
Aaron 1bcc5cdd81 docs: Add link to comments on blog post 2025-03-28 11:44:54 -05:00
Aaron 873fdbc4b3 Update changelogs 2025-03-28 11:10:14 -05:00
Aaron 79128effea ci: Update browser package commit message 2025-03-28 11:06:06 -05:00
Aaron 14ef94fc09 ci: Fix browser sync workflow 2025-03-28 11:05:16 -05:00
aklinker1 759e3e3a4d fix: Upgrade \@wxt-dev/browser\ to latest \@types/chrome\ version 2025-03-28 16:01:49 +00:00
Aaron 5b7b16efba fix(browser): Install latest version of @types/chrome 2025-03-28 11:01:05 -05:00
aklinker1 7fd171acb5 fix: Upgrade \@wxt-dev/browser\ to latest \@types/chrome\ version 2025-03-28 15:59:43 +00:00
Aaron 02ed3f1d44 chore: Fix type check script 2025-03-28 10:58:21 -05:00
Aaron 4f7f3f36a3 chore: Fix type check script 2025-03-28 10:58:09 -05:00
Aaron 73a8cf12a9 docs: Use nicer project structure for v0.20 directory changes 2025-03-28 10:47:36 -05:00
Aaron 5f5eb16705 docs: Add redirect for old upgrade guide 2025-03-28 10:41:24 -05:00
75 changed files with 2101 additions and 1730 deletions
+1 -2
View File
@@ -36,8 +36,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commit_message: 'fix: Upgrade \`@wxt-dev/browser\` to latest \`@types/chrome\` version'
file_pattern: 'packages/browser/**'
commit_message: 'fix: Upgrade `@wxt-dev/browser` to latest `@types/chrome` version'
- name: Publish Package
if: steps.commit.outputs.changes_detected == 'true'
+1
View File
@@ -7,6 +7,7 @@
.wxt
*.log
/docs/.vitepress/cache
docs/.vitepress/.temp
coverage
dist
node_modules
@@ -70,6 +70,9 @@ const chromeExtensionIds = [
'hafcajcllbjnoolpfngclfmmgpikdhlm', // Monochromate
'bmoggiinmnodjphdjnmpcnlleamkfedj', // AliasVault - Open-Source Password & (Email) Alias Manager
'hlnhhamckimoaiekbglafiebkfimhapb', // SnapThePrice: AI-Powered Real-time Lowest Price Finder
'gdjampjdgjmbifnhldgcnccdjkcoicmg', // radiofrance - news & broadcasts (French), music (international)
'jlnhphlghikichhgbnkepenehbmloenb', // Blens - Time Tracker and AI Insight
'njnammmpdodmfkodnfpammnpdcbhnlcm', // Always Light Mode - Setting website always in light mode
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
+27 -14
View File
@@ -1,4 +1,4 @@
import { defineConfig } from 'vitepress';
import { DefaultTheme, defineConfig } from 'vitepress';
import typedocSidebar from '../api/reference/typedoc-sidebar.json';
import {
menuGroup,
@@ -15,7 +15,7 @@ import { version as autoIconsVersion } from '../../packages/auto-icons/package.j
import { version as unocssVersion } from '../../packages/unocss/package.json';
import { version as storageVersion } from '../../packages/storage/package.json';
import { version as analyticsVersion } from '../../packages/analytics/package.json';
import knowledge from 'vitepress-knowledge';
import addKnowledge from 'vitepress-knowledge';
import {
groupIconMdPlugin,
groupIconVitePlugin,
@@ -43,18 +43,25 @@ const otherPackages = {
unocss: unocssVersion,
};
const knowledge = addKnowledge<DefaultTheme.Config>({
serverUrl: 'https://knowledge.wxt.dev',
paths: {
'/': 'docs',
'/api/': 'api-reference',
'/blog/': 'blog',
},
layoutSelectors: {
blog: '.container-content',
},
pageSelectors: {
'examples.md': '#VPContent > .VPPage',
'blog.md': '#VPContent > .VPPage',
},
});
// https://vitepress.dev/reference/site-config
export default defineConfig({
extends: knowledge({
serverUrl: 'https://knowledge.wxt.dev',
paths: {
'/': 'docs',
'/api/': 'api-reference',
},
pageSelectors: {
'examples.md': '#VPContent > .VPPage',
},
}),
extends: knowledge,
titleTemplate: `:title${titleSuffix}`,
title: 'WXT',
@@ -78,6 +85,9 @@ export default defineConfig({
},
async buildEnd(site) {
// @ts-expect-error: knowledge.buildEnd is not typed, but it exists.
await knowledge.buildEnd(site);
// Only construct the RSS document for production builds
const { default: blogDataLoader } = await import('./loaders/blog.data');
const posts = await blogDataLoader.load();
@@ -95,8 +105,8 @@ export default defineConfig({
description: post.frontmatter.description,
});
});
console.log('rss.xml:');
console.log(feed.rss2());
// console.log('rss.xml:');
// console.log(feed.rss2());
await writeFile(join(site.outDir, 'rss.xml'), feed.rss2(), 'utf8');
},
@@ -118,6 +128,9 @@ export default defineConfig({
md.use(footnote);
md.use(groupIconMdPlugin);
},
languageAlias: {
mjs: 'js',
},
},
themeConfig: {
@@ -70,3 +70,7 @@ You don't have to use `#imports` if you don't like - you can continue importing
Happy Coding 😄
> P.S. Yes, this is exactly how [Nuxt's `#imports`](https://nuxt.com/docs/guide/concepts/auto-imports#explicit-imports) works! We use the exact same library, [`unimport`](https://github.com/unjs/unimport).
---
[Discuss this blog post on Github](https://github.com/wxt-dev/wxt/discussions/1543).
@@ -8,13 +8,23 @@ outline: deep
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.
:::danger
Chrome 137 removed support for the `--load-extension` CLI flag, which WXT relied on to open the browser with an extension installed. So this feature will not work for Chrome.
You have two options:
1. Install [Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (which still supports the `--load-extension` flag) and [point the `chrome` binary to it](#set-browser-binaries), or
2. [Disable this feature](#disable-opening-browser) and manually load your extension
:::
## Config Files
You can configure browser startup in 3 places:
1. `<rootDir>/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
```ts [web-ext.config.ts]
import { defineWebExtConfig } from 'wxt';
export default defineWebExtConfig({
@@ -22,7 +32,7 @@ You can configure browser startup in 3 places:
});
```
2. `<rootDir>/wxt.config.ts`: Via the [`runner` config](/api/reference/wxt/interfaces/InlineConfig#runner), included in version control
2. `<rootDir>/wxt.config.ts`: Via the [`webExt` config](/api/reference/wxt/interfaces/InlineConfig#webext), included in version control
3. `$HOME/web-ext.config.ts`: Provide default values for all WXT projects on your computer
## Recipes
@@ -31,7 +41,7 @@ You can configure browser startup in 3 places:
To set or customize the browser opened during development:
```ts
```ts [web-ext.config.ts]
export default defineWebExtConfig({
binaries: {
chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome
@@ -81,7 +91,7 @@ You can use any directory you'd like for `--user-data-dir`, the examples above c
If you prefer to load the extension into your browser manually, you can disable the auto-open behavior:
```ts
```ts [web-ext.config.ts]
export default defineWebExtConfig({
disabled: true,
});
@@ -42,6 +42,8 @@ WXT provides some custom environment variables based on the current command:
| `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 set the [`targetBrowsers`](/api/reference/wxt/interfaces/InlineConfig#targetbrowsers) option to make the `BROWSER` variable a more specific type, like `"chrome" | "firefox"`.
You can also access all of [Vite's environment variables](https://vite.dev/guide/env-and-mode.html#env-variables):
| Usage | Type | Description |
+193 -69
View File
@@ -4,50 +4,30 @@ outline: deep
# Entrypoints
WXT uses the files inside the `entrypoints/` directory as inputs when bundling your extension. They can be HTML, JS, CSS, or any variant of those file types supported by Vite (Pug, TS, JSX, SCSS, etc).
WXT uses the files inside the `entrypoints/` directory as inputs when bundling your extension. They can be HTML, JS, CSS, or any variant of those file types supported by Vite (TS, JSX, SCSS, etc).
Here's an example set of entrypoints:
## Folder Structure
Inside the `entrypoints/` directory, an entrypoint is defined as a single file or directory (with an `index` file) inside it.
:::code-group
<!-- prettier-ignore -->
```html
```html [Single File]
📂 entrypoints/
📂 popup/
📄 index.html
📄 main.ts
📄 style.css
📄 background.ts
📄 content.ts
📄 {name}.{ext}
```
[[toc]]
<!-- prettier-ignore -->
```html [Directory]
📂 entrypoints/
📂 {name}/
📄 index.{ext}
```
## Listed vs Unlisted
For web extensions, there are two types of entrypoints:
- **Listed**: Referenced in the `manifest.json`
- **Unlisted**: Not referenced in the `manifest.json`
Throughout the rest of WXT's documentation, listed entrypoints are referred to by name. For example:
- Popup
- Options
- Background
- Content Scripts
- Etc.
Some examples of "unlisted" entrypoints:
- A welcome page shown when the extension is installed
- JS files injected by content scripts into the page's main world
:::tip
Regardless of whether an entrypoint is listed or unlisted, it will still be bundled into your extension and be available at runtime.
:::
## Adding Entrypoints
An entrypoint can be defined as a single file or directory with an `index` file inside it.
The entrypoint's `name` dictates the type of entrypoint. For example, to add a ["Background" entrypoint](#background), either of these files would work:
:::code-group
@@ -66,10 +46,96 @@ An entrypoint can be defined as a single file or directory with an `index` file
:::
The entrypoint's name dictates the type of entrypoint, listed vs unlisted. In this example, "background" is the name of the ["Background" entrypoint](#background).
Refer to the [Entrypoint Types](#entrypoint-types) section for the full list of listed entrypoints and their filename patterns.
### Including Other Files
When using an entrypoint directory, `entrypoints/{name}/index.{ext}`, you can add related files next to the `index` file.
<!-- prettier-ignore -->
```html
📂 entrypoints/
📂 popup/
📄 index.html ← This file is the entrypoint
📄 main.ts
📄 style.css
📂 background/
📄 index.ts ← This file is the entrypoint
📄 alarms.ts
📄 messaging.ts
📂 youtube.content/
📄 index.ts ← This file is the entrypoint
📄 style.css
```
:::danger
**DO NOT** put files related to an entrypoint directly inside the `entrypoints/` directory. WXT will treat them as entrypoints and try to build them, usually resulting in an error.
Instead, use a directory for that entrypoint:
<!-- prettier-ignore -->
```html
📂 entrypoints/
📄 popup.html <!-- [!code --] -->
📄 popup.ts <!-- [!code --] -->
📄 popup.css <!-- [!code --] -->
📂 popup/ <!-- [!code ++] -->
📄 index.html <!-- [!code ++] -->
📄 main.ts <!-- [!code ++] -->
📄 style.css <!-- [!code ++] -->
```
:::
### Deeply Nested Entrypoints
While the `entrypoints/` directory might resemble the `pages/` directory of other web frameworks, like Nuxt or Next.js, **it does not support deeply nesting entrypoints** in the same way.
Entrypoints must be zero or one levels deep for WXT to discover and build them:
<!-- prettier-ignore -->
```html
📂 entrypoints/
📂 youtube/ <!-- [!code --] -->
📂 content/ <!-- [!code --] -->
📄 index.ts <!-- [!code --] -->
📄 ... <!-- [!code --] -->
📂 injected/ <!-- [!code --] -->
📄 index.ts <!-- [!code --] -->
📄 ... <!-- [!code --] -->
📂 youtube.content/ <!-- [!code ++] -->
📄 index.ts <!-- [!code ++] -->
📄 ... <!-- [!code ++] -->
📂 youtube-injected/ <!-- [!code ++] -->
📄 index.ts <!-- [!code ++] -->
📄 ... <!-- [!code ++] -->
```
## Unlisted Entrypoints
In web extensions, there are two types of entrypoints:
1. **Listed**: Referenced in the `manifest.json`
2. **Unlisted**: Not referenced in the `manifest.json`
Throughout the rest of WXT's documentation, listed entrypoints are referred to by name. For example:
- Popup
- Options
- Background
- Content Script
However, not all entrypoints in web extensions are listed in the manifest. Some are not listed in the manifest, but are still used by extensions. For example:
- A welcome page shown in a new tab when the extension is installed
- JS files injected by content scripts into the main world
For more details on how to add unlisted entrypoints, see:
- [Unlisted Pages](#unlisted-pages)
- [Unlisted Scripts](#unlisted-scripts)
- [Unlisted CSS](#unlisted-css)
## Defining Manifest Options
Most listed entrypoints have options that need to be added to the `manifest.json`. However with WXT, instead of defining the options in a separate file, _you define these options inside the entrypoint file itself_.
@@ -106,8 +172,6 @@ When building your extension, WXT will look at the options defined in your entry
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/background/) &bull; [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.
<EntrypointPatterns
:patterns="[
['background.[jt]s', 'background.js'],
@@ -141,6 +205,25 @@ export default defineBackground({
:::
For MV2, the background is added as a script to the background page. For MV3, the background becomes a service worker.
When defining your background entrypoint, keep in mind that WXT will import this file in a NodeJS environment during the build process. That means you cannot place any runtime code outside the `main` function.
<!-- prettier-ignore -->
```ts
browser.action.onClicked.addListener(() => { // [!code --]
// ... // [!code --]
}); // [!code --]
export default defineBackground(() => {
browser.action.onClicked.addListener(() => { // [!code ++]
// ... // [!code ++]
}); // [!code ++]
});
```
Refer to the [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) documentation for more details.
### Bookmarks
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
@@ -169,18 +252,18 @@ export default defineBackground({
</html>
```
When you define a Bookmarks entrypoint, WXT will automatically update the manifest to override the browser's bookmarks page with your own HTML page.
### Content Scripts
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/content_scripts/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts)
See [Content Script UI](/guide/essentials/content-scripts) for more info on creating UIs and including CSS in content scripts.
<EntrypointPatterns
:patterns="[
['content.[jt]sx?', 'content-scripts/content.js'],
['content/index.[jt]sx?', 'content-scripts/content.js'],
['<name>.content.[jt]sx?', 'content-scripts/<name>.js'],
['<name>.content/index.[jt]sx?', 'content-scripts/<name>.js'],
['{name}.content.[jt]sx?', 'content-scripts/{name}.js'],
['{name}.content/index.[jt]sx?', 'content-scripts/{name}.js'],
]"
/>
@@ -213,12 +296,29 @@ export default defineContentScript({
});
```
When defining content script entrypoints, keep in mind that WXT will import this file in a NodeJS environment during the build process. That means you cannot place any runtime code outside the `main` function.
<!-- prettier-ignore -->
```ts
browser.runtime.onMessage.addListener((message) => { // [!code --]
// ... // [!code --]
}); // [!code --]
export default defineBackground(() => {
browser.runtime.onMessage.addListener((message) => { // [!code ++]
// ... // [!code ++]
}); // [!code ++]
});
```
Refer to the [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) documentation for more details.
See [Content Script UI](/guide/essentials/content-scripts) for more info on creating UIs and including CSS in content scripts.
### Devtools
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/devtools/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/devtools_page)
Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/examples/devtools-extension#readme) to add different panels and panes.
<EntrypointPatterns
:patterns="[
['devtools.html', 'devtools.html'],
@@ -242,6 +342,8 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
</html>
```
Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/examples/devtools-extension#readme) to add different panels and panes.
### History
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
@@ -270,6 +372,8 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
</html>
```
When you define a History entrypoint, WXT will automatically update the manifest to override the browser's history page with your own HTML page.
### Newtab
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
@@ -298,6 +402,8 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
</html>
```
When you define a Newtab entrypoint, WXT will automatically update the manifest to override the browser's new tab page with your own HTML page.
### Options
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/options/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/options_ui)
@@ -387,8 +493,8 @@ Firefox does not support sandboxed pages.
:patterns="[
['sandbox.html', 'sandbox.html'],
['sandbox/index.html', 'sandbox.html'],
['<name>.sandbox.html', '<name>.html'],
['<name>.sandbox/index.html', '<name>.html'],
['{name}.sandbox.html', '{name}.html'],
['{name}.sandbox/index.html', '{name}.html'],
]"
/>
@@ -414,14 +520,12 @@ Firefox does not support sandboxed pages.
[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/sidePanel/) &bull; [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.
<EntrypointPatterns
:patterns="[
['sidepanel.html', 'sidepanel.html'],
['sidepanel/index.html', 'sidepanel.html'],
['<name>.sidepanel.html', '<name>.html` '],
['<name>.sidepanel/index.html', '<name>.html` '],
['{name}.sidepanel.html', '{name}.html` '],
['{name}.sidepanel/index.html', '{name}.html` '],
]"
/>
@@ -455,20 +559,18 @@ In Chrome, side panels use the `side_panel` API, while Firefox uses the `sidebar
</html>
```
In Chrome, side panels use the `side_panel` API, while Firefox uses the `sidebar_action` API.
### Unlisted CSS
Follow Vite's guide to setup your preprocessor of choice: https://vitejs.dev/guide/features.html#css-pre-processors
CSS entrypoints are always unlisted. To add CSS to a content script, see the [Content Script](/guide/essentials/content-scripts#css) docs.
<EntrypointPatterns
:patterns="[
['<name>.(css|scss|sass|less|styl|stylus)', '<name>.css'],
['<name>/index.(css|scss|sass|less|styl|stylus)', '<name>.css'],
['{name}.(css|scss|sass|less|styl|stylus)', '{name}.css'],
['{name}/index.(css|scss|sass|less|styl|stylus)', '{name}.css'],
['content.(css|scss|sass|less|styl|stylus)', 'content-scripts/content.css'],
['content/index.(css|scss|sass|less|styl|stylus)', 'content-scripts/content.css'],
['<name>.content.(css|scss|sass|less|styl|stylus)', 'content-scripts/<name>.css'],
['<name>.content/index.(css|scss|sass|less|styl|stylus)', 'content-scripts/<name>.css'],
['{name}.content.(css|scss|sass|less|styl|stylus)', 'content-scripts/{name}.css'],
['{name}.content/index.(css|scss|sass|less|styl|stylus)', 'content-scripts/{name}.css'],
]"
/>
@@ -478,12 +580,16 @@ body {
}
```
Follow Vite's guide to setup your preprocessor of choice: https://vitejs.dev/guide/features.html#css-pre-processors
CSS entrypoints are always unlisted. To add CSS to a content script, see the [Content Script](/guide/essentials/content-scripts#css) docs.
### Unlisted Pages
<EntrypointPatterns
:patterns="[
['<name>.html', '<name>.html'],
['<name>/index.html', '<name>.html'],
['{name}.html', '{name}.html'],
['{name}/index.html', '{name}.html'],
]"
/>
@@ -505,20 +611,21 @@ body {
</html>
```
Pages are accessible at `/<name>.html`:
At runtime, unlisted pages are accessible at `/{name}.html`:
```ts
const url = browser.runtime.getURL('/<name>.html');
const url = browser.runtime.getURL('/{name}.html');
console.log(url); // "chrome-extension://<id>/<name>.html"
console.log(url); // "chrome-extension://{id}/{name}.html"
window.open(url); // Open the page in a new tab
```
### Unlisted Scripts
<EntrypointPatterns
:patterns="[
['<name>.[jt]sx?', '<name>.js'],
['<name>/index.[jt]sx?', '<name>.js'],
['{name}.[jt]sx?', '{name}.js'],
['{name}/index.[jt]sx?', '{name}.js'],
]"
/>
@@ -544,12 +651,29 @@ export default defineUnlistedScript({
:::
Scripts are accessible from `/<name>.js`:
At runtime, unlisted scripts are accessible from `/{name}.js`:
```ts
const url = browser.runtime.getURL('/<name>.js');
const url = browser.runtime.getURL('/{name}.js');
console.log(url); // "chrome-extension://<id>/<name>.js"
console.log(url); // "chrome-extension://{id}/{name}.js"
```
You are responsible for loading/running these scripts where needed. If necessary, don't forget to add the script and/or any related assets to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources).
When defining an unlisted script, keep in mind that WXT will import this file in a NodeJS environment during the build process. That means you cannot place any runtime code outside the `main` function.
<!-- prettier-ignore -->
```ts
document.querySelectorAll('a').forEach((anchor) => { // [!code --]
// ... // [!code --]
}); // [!code --]
export default defineUnlistedScript(() => {
document.querySelectorAll('a').forEach((anchor) => { // [!code ++]
// ... // [!code ++]
}); // [!code ++]
});
```
Refer to the [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) documentation for more details.
+1 -1
View File
@@ -29,7 +29,7 @@ This means you can use the promise-style API for both MV2 and MV3, and it will w
All types can be accessed via WXT's `Browser` namespace:
```ts
import { Browser } from 'wxt/browser';
import { type Browser } from 'wxt/browser';
function handleMessage(message: any, sender: Browser.runtime.MessageSender) {
// ...
+3 -2
View File
@@ -30,9 +30,10 @@ Here's a brief summary of each of these files and directories:
- `.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
- `composables/`: Auto-imported by default, contains source code for your project's 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
- `hooks/`: Auto-imported by default, contains source code for your project's hooks for React and Solid
- `modules/`: Contains [local WXT Modules](/guide/essentials/wxt-modules) for your project
- `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/environment-variables)
+1 -1
View File
@@ -23,7 +23,7 @@ WXT provides two commands to help automate submitting a new version for review a
- `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)
To get started, run `wxt submit init` and follow the prompts. Once finished, you should have a `.env.submit` file! WXT will use this file to submit your updates.
To get started, run `wxt submit init` and follow the prompts, or run `wxt submit --help` to view all available options. Once finished, you should have a `.env.submit` file! WXT will use this file to submit your updates.
> In CI, make sure you add all the environment variables to the submit step.
+1 -1
View File
@@ -85,7 +85,7 @@ import { injectScript, createShadowRootUi } from '#imports';
But Vitest sees this:
```ts
import { injectScript } from 'wxt/browser';
import { injectScript } from 'wxt/utils/inject-script';
import { createShadowRootUi } from 'wxt/utils/content-script-ui/shadow-root';
```
+13
View File
@@ -174,3 +174,16 @@ Additionally, if you want to train your own model or provide context to your edi
https://wxt.dev/knowledge/index.json
You don't need to crawl the entire website, these files already contain all the relevant docs for training a LLM on WXT. But feel free to crawl it and generate your own files if you want!
## How do I run my WXT project with docker / [devcontainers](https://containers.dev)?
To run the WXT dev server in a devcontainer, but load the dev build of your extension in your browser:
1. **Bind-mount your project directory to your host**
If you're using VS Code, you can open your project folder with the `Dev Containers: Open Folder in Container...` command. This keeps the folder synchronized between your host and the devcontainer, ensuring that the extension `dist` directory remains accessible from the host.
2. **Disable auto-opening the browser**
WXT automatically opens your browser during development, but since you're running inside a container, it won't be able to access it. Follow the instructions [here](https://wxt.dev/guide/essentials/config/browser-startup.html#disable-opening-browser) to disable browser auto-opening in your `wxt.config.ts`.
3. **Tell WXT to listen on all network interfaces**
To enable hot-reloading, your extension has to connect to the WXT dev server running inside your container. WXT will only listen on `localhost` by default, which prevents connections from outside the devcontainer. To fix this you can instruct WXT to listen on all interfaces with `wxt --host 0.0.0.0`.
+30 -13
View File
@@ -6,12 +6,27 @@ outline: deep
## Overview
To upgrade WXT to the latest version... just install it!
To upgrade WXT to the latest major version:
1. Install it, skipping scripts so `wxt prepare` doesn't run - it will probably throw an error after a major version change (we'll run it later).
```sh
pnpm i wxt@latest --ignore-scripts
```
2. Follow the upgrade steps below to fix any breaking changes.
3. Run `wxt prepare`. It should succeed and type errors will go away afterwords.
```sh
pnpm wxt prepare
```
4. Manually test to make sure both dev mode and production builds work.
For minor or patch version updates, there are no special steps. Just update it with your package manager:
```sh
pnpm i wxt@latest
```
---
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.
@@ -21,7 +36,7 @@ Currently, WXT is in pre-release. This means changes to the second digit, `v0.X`
v0.20 is a big release! There are lots of breaking changes because this version is intended to be a release candidate for v1.0. If all goes well, v1.0 will be released with no additional breaking changes.
:::tip
Read through all the changes once before making any code changes.
Read through all the changes once before updating your code.
:::
### `webextension-polyfill` Removed
@@ -92,17 +107,19 @@ The default location for the `public/` and `modules/` directories have changed t
- If you follow the default folder structure, you don't need to make any changes.
- If you set a custom `srcDir`, you have two options:
1. Move the your `public/` and `modules/` directories to the project root:
```diff
<root>/
+ modules/
+ public/
src/
components/
entrypoints/
- modules/
- public/
utils/
wxt.config.ts
<!-- prettier-ignore -->
```html
📂 {rootDir}/
📁 modules/ <!-- [!code ++] -->
📁 public/ <!-- [!code ++] -->
📂 src/
📁 components/
📁 entrypoints/
📁 modules/ <!-- [!code --] -->
📁 public/ <!-- [!code --] -->
📁 utils/
📄 app.config.ts
📄 wxt.config.ts
```
2. Keep the folders in the same place and update your project config:
```ts [wxt.config.ts]
+2
View File
@@ -32,6 +32,8 @@
/guide/build-targets.html /guide/multiple-browsers.html
/guide/installation.html /get-started/installation.html
/guide/introduction.html /get-started/introduction.html
/guide/upgrade-guide/wxt /guide/resources/upgrading.html
/guide/upgrade-guide/wxt.html /guide/resources/upgrading.html
# 0.19.0
/guide/go-further/entrypoint-side-effects.html /guide/go-further/entrypoint-loaders.html
+1 -1
View File
@@ -4,7 +4,7 @@
"engines": {
"node": ">=18.20.3"
},
"packageManager": "pnpm@10.5.2",
"packageManager": "pnpm@10.8.0",
"scripts": {
"check": "check && pnpm -r --sequential run check",
"test": "pnpm -r --sequential run test run",
+2 -17
View File
@@ -1,27 +1,12 @@
# Changelog
## v0.5.0
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.4.1...analytics-v0.5.0)
### 🚀 Enhancements
- Analytics module ([5217279](https://github.com/wxt-dev/wxt/commit/5217279))
- New `@wxt-dev/analytics` package ([#790](https://github.com/wxt-dev/wxt/pull/790))
[⚠️ breaking changes](https://wxt.dev/guide/resources/upgrading.html) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.4.1...analytics-v0.5.0)
### 🩹 Fixes
- ⚠️ Update min WXT version to 0.20 ([2e8baf0](https://github.com/wxt-dev/wxt/commit/2e8baf0))
### 🏡 Chore
- **release:** Analytics-v0.4.0 ([2faba91](https://github.com/wxt-dev/wxt/commit/2faba91))
- **release:** Analytics-v0.4.1 ([1509809](https://github.com/wxt-dev/wxt/commit/1509809))
#### ⚠️ Breaking Changes
- ⚠️ Update min WXT version to 0.20 ([2e8baf0](https://github.com/wxt-dev/wxt/commit/2e8baf0))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Aaron ([@aklinker1](https://github.com/aklinker1))
+4 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@wxt-dev/browser",
"description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome",
"version": "0.0.310",
"version": "0.0.317",
"type": "module",
"main": "src/index.mjs",
"types": "src/index.d.ts",
@@ -11,6 +11,7 @@
"directory": "packages/browser"
},
"scripts": {
"check": "check",
"gen": "tsx scripts/generate.ts"
},
"author": {
@@ -22,10 +23,11 @@
"src"
],
"devDependencies": {
"@types/chrome": "0.0.310",
"@types/chrome": "0.0.317",
"fs-extra": "catalog:",
"nano-spawn": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
},
"dependencies": {
+1 -1
View File
@@ -7,7 +7,7 @@ import { sep as posixSep } from 'node:path/posix';
// Fetch latest version
console.log('Getting latest version of \x1b[36m@types/chrome\x1b[0m');
await spawn('pnpm', ['i', '--ignore-scripts', '-D', '@types/chrome']);
await spawn('pnpm', ['i', '--ignore-scripts', '-D', '@types/chrome@latest']);
// Generate new package.json
+358 -406
View File
@@ -1867,198 +1867,146 @@ export namespace Browser {
* Permissions: "contextMenus"
*/
export namespace contextMenus {
export interface OnClickData {
/**
* Optional.
* @since Chrome 35
* The text for the context selection, if any.
*/
selectionText?: string | undefined;
/**
* Optional.
* @since Chrome 35
* A flag indicating the state of a checkbox or radio item after it is clicked.
*/
checked?: boolean | undefined;
/**
* @since Chrome 35
* The ID of the menu item that was clicked.
*/
menuItemId: number | string;
/**
* Optional.
* @since Chrome 35
* The ID of the frame of the element where the context menu was
* clicked, if it was in a frame.
*/
frameId?: number | undefined;
/**
* Optional.
* @since Chrome 35
* The URL of the frame of the element where the context menu was clicked, if it was in a frame.
*/
frameUrl?: string | undefined;
/**
* @since Chrome 35
* A flag indicating whether the element is editable (text input, textarea, etc.).
*/
editable: boolean;
/**
* Optional.
* @since Chrome 35
* One of 'image', 'video', or 'audio' if the context menu was activated on one of these types of elements.
*/
mediaType?: "image" | "video" | "audio" | undefined;
/**
* Optional.
* @since Chrome 35
* A flag indicating the state of a checkbox or radio item before it was clicked.
*/
wasChecked?: boolean | undefined;
/**
* @since Chrome 35
* The URL of the page where the menu item was clicked. This property is not set if the click occurred in a context where there is no current page, such as in a launcher context menu.
*/
pageUrl: string;
/**
* Optional.
* @since Chrome 35
* If the element is a link, the URL it points to.
*/
linkUrl?: string | undefined;
/**
* Optional.
* @since Chrome 35
* The parent ID, if any, for the item clicked.
*/
parentMenuItemId?: number | string;
/**
* Optional.
* @since Chrome 35
* Will be present for elements with a 'src' URL.
*/
srcUrl?: string | undefined;
}
type ContextType =
| "all"
| "page"
| "frame"
| "selection"
| "link"
| "editable"
| "image"
| "video"
| "audio"
| "launcher"
| "browser_action"
| "page_action"
| "action";
type ContextItemType = "normal" | "checkbox" | "radio" | "separator";
export interface CreateProperties {
/** Optional. Lets you restrict the item to apply only to documents whose URL matches one of the given patterns. (This applies to frames as well.) For details on the format of a pattern, see Match Patterns. */
documentUrlPatterns?: string[] | undefined;
/** Optional. The initial state of a checkbox or radio item: true for selected and false for unselected. Only one radio item can be selected at a time in a given group of radio items. */
checked?: boolean | undefined;
/** Optional. The text to be displayed in the item; this is required unless type is 'separator'. When the context is 'selection', you can use %s within the string to show the selected text. For example, if this parameter's value is "Translate '%s' to Pig Latin" and the user selects the word "cool", the context menu item for the selection is "Translate 'cool' to Pig Latin". */
title?: string | undefined;
/** Optional. List of contexts this menu item will appear in. Defaults to ['page'] if not specified. */
contexts?: ContextType | ContextType[] | undefined;
/**
* Optional.
* @since Chrome 20
* Whether this context menu item is enabled or disabled. Defaults to true.
*/
enabled?: boolean | undefined;
/** Optional. Similar to documentUrlPatterns, but lets you filter based on the src attribute of img/audio/video tags and the href of anchor tags. */
targetUrlPatterns?: string[] | undefined;
/**
* Optional.
* A function that will be called back when the menu item is clicked. Event pages cannot use this; instead, they should register a listener for Browser.contextMenus.onClicked.
* @param info Information sent when a context menu item is clicked.
* @param tab The details of the tab where the click took place. Note: this parameter only present for extensions.
*/
onclick?: ((info: OnClickData, tab: Browser.tabs.Tab) => void) | undefined;
/** Optional. The ID of a parent menu item; this makes the item a child of a previously added item. */
parentId?: number | string | undefined;
/** Optional. The type of menu item. Defaults to 'normal' if not specified. */
type?: ContextItemType | undefined;
/**
* Optional.
* @since Chrome 21
* The unique ID to assign to this item. Mandatory for event pages. Cannot be the same as another ID for this extension.
*/
id?: string | undefined;
/**
* Optional.
* @since Chrome 62
* Whether the item is visible in the menu.
*/
visible?: boolean | undefined;
}
export interface UpdateProperties extends Omit<CreateProperties, "id"> {}
export interface MenuClickedEvent
extends Browser.events.Event<(info: OnClickData, tab?: Browser.tabs.Tab) => void>
{}
/**
* @since Chrome 38
* The maximum number of top level extension items that can be added to an extension action context menu. Any items beyond this limit will be ignored.
* The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'launcher'. The 'launcher' context is only supported by apps and is used to add menu items to the context menu that appears when clicking the app icon in the launcher/taskbar/dock/etc. Different platforms might put limitations on what is actually supported in a launcher context menu.
* @since Chrome 44
*/
export var ACTION_MENU_TOP_LEVEL_LIMIT: number;
export enum ContextType {
ALL = "all",
PAGE = "page",
FRAME = "frame",
SELECTION = "selection",
LINK = "link",
EDITABLE = "editable",
IMAGE = "image",
VIDEO = "video",
AUDIO = "audio",
LAUNCHER = "launcher",
BROWSER_ACTION = "browser_action",
PAGE_ACTION = "page_action",
ACTION = "action",
}
/**
* Removes all context menu items added by this extension.
* Properties of the new context menu item.
* @since Chrome 123
*/
export function removeAll(): Promise<void>;
export interface CreateProperties {
/** The initial state of a checkbox or radio button: `true` for selected, `false` for unselected. Only one radio button can be selected at a time in a given group. */
checked?: boolean;
/** List of contexts this menu item will appear in. Defaults to `['page']`. */
contexts?: [`${ContextType}`, ...`${ContextType}`[]];
/** Restricts the item to apply only to documents or frames whose URL matches one of the given patterns. For details on pattern formats, see Match Patterns. */
documentUrlPatterns?: string[];
/** Whether this context menu item is enabled or disabled. Defaults to `true`. */
enabled?: boolean;
/** The unique ID to assign to this item. Mandatory for event pages. Cannot be the same as another ID for this extension. */
id?: string;
/** The ID of a parent menu item; this makes the item a child of a previously added item. */
parentId?: number | string;
/** Similar to `documentUrlPatterns`, filters based on the `src` attribute of `img`, `audio`, and `video` tags and the `href` attribute of `a` tags. */
targetUrlPatterns?: string[];
/** The text to display in the item; this is _required_ unless `type` is `separator`. When the context is `selection`, use `%s` within the string to show the selected text. For example, if this parameter's value is "Translate '%s' to Pig Latin" and the user selects the word "cool", the context menu item for the selection is "Translate 'cool' to Pig Latin". */
title?: string;
/** The type of menu item. Defaults to `normal`. */
type?: `${ItemType}`;
/** Whether the item is visible in the menu. */
visible?: boolean;
/**
* A function that is called back when the menu item is clicked. This is not available inside of a service worker; instead, you should register a listener for {@link contextMenus.onClicked}.
* @param info Information about the item clicked and the context where the click happened.
* @param tab The details of the tab where the click took place. This parameter is not present for platform apps.
*/
onclick?: (
info: OnClickData,
tab: tabs.Tab,
) => void;
}
/**
* Removes all context menu items added by this extension.
* @param callback Called when removal is complete.
* The type of menu item.
* @since Chrome 44
*/
export function removeAll(callback: () => void): void;
export enum ItemType {
NORMAL = "normal",
CHECKBOX = "checkbox",
RADIO = "radio",
SEPARATOR = "separator",
}
/** Information sent when a context menu item is clicked. */
export interface OnClickData {
/** A flag indicating the state of a checkbox or radio item after it is clicked. */
checked?: boolean;
/** A flag indicating whether the element is editable (text input, textarea, etc.). */
editable: boolean;
/**
* The ID of the frame of the element where the context menu was clicked, if it was in a frame.
* @since Chrome 51
*/
frameId?: number;
/** The URL of the frame of the element where the context menu was clicked, if it was in a frame. */
frameUrl?: string;
/** If the element is a link, the URL it points to. */
linkUrl?: string;
/** One of 'image', 'video', or 'audio' if the context menu was activated on one of these types of elements. */
mediaType?: `${ContextType.IMAGE}` | `${ContextType.VIDEO}` | `${ContextType.AUDIO}`;
/** The ID of the menu item that was clicked. */
menuItemId: number | string;
/** The URL of the page where the menu item was clicked. This property is not set if the click occurred in a context where there is no current page, such as in a launcher context menu. */
pageUrl?: string;
/** The parent ID, if any, for the item clicked.*/
parentMenuItemId?: number | string;
/** The text for the context selection, if any. */
selectionText?: string | undefined;
/** Will be present for elements with a 'src' URL. */
srcUrl?: string | undefined;
/** A flag indicating the state of a checkbox or radio item before it was clicked. */
wasChecked?: boolean | undefined;
}
/** The maximum number of top level extension items that can be added to an extension action context menu. Any items beyond this limit will be ignored. */
export const ACTION_MENU_TOP_LEVEL_LIMIT: 6;
/**
* Creates a new context menu item. Note that if an error occurs during creation, you may not find out until the creation callback fires (the details will be in Browser.runtime.lastError).
* @param callback Called when the item has been created in the browser. If there were any problems creating the item, details will be available in Browser.runtime.lastError.
* Creates a new context menu item. If an error occurs during creation, it may not be detected until the creation callback fires; details will be in {@link Browser.runtime.lastError}.
* @return The ID of the newly created item.
*/
export function create(createProperties: CreateProperties, callback?: () => void): number | string;
/**
* Updates a previously created context menu item.
* @param id The ID of the item to update.
* @param updateProperties The properties to update. Accepts the same values as the create function.
* @since Chrome 123
*/
export function update(id: string | number, updateProperties: UpdateProperties): Promise<void>;
/**
* Updates a previously created context menu item.
* @param id The ID of the item to update.
* @param updateProperties The properties to update. Accepts the same values as the create function.
* @param callback Called when the context menu has been updated.
*/
export function update(id: string | number, updateProperties: UpdateProperties, callback: () => void): void;
/**
* Removes a context menu item.
* @param menuItemId The ID of the context menu item to remove.
* @since Chrome 123
*
* Can return its result via Promise since Chrome 123.
*/
export function remove(menuItemId: string | number): Promise<void>;
/**
* Removes a context menu item.
* @param menuItemId The ID of the context menu item to remove.
* @param callback Called when the context menu has been removed.
*/
export function remove(menuItemId: string | number, callback: () => void): void;
/**
* @since Chrome 21
* Fired when a context menu item is clicked.
* Removes all context menu items added by this extension.
*
* Can return its result via Promise since Chrome 123.
*/
export var onClicked: MenuClickedEvent;
export function removeAll(): Promise<void>;
export function removeAll(callback: () => void): void;
/**
* Updates a previously created context menu item.
* @param id The ID of the item to update.
* @param updateProperties The properties to update. Accepts the same values as the {@link contextMenus.create} function.
*
* Can return its result via Promise since Chrome 123.
*/
export function update(id: string | number, updateProperties: Omit<CreateProperties, "id">): Promise<void>;
export function update(
id: string | number,
updateProperties: Omit<CreateProperties, "id">,
callback: () => void,
): void;
/** Fired when a context menu item is clicked. */
export const onClicked: events.Event<(info: OnClickData, tab?: tabs.Tab) => void>;
}
////////////////////
@@ -3073,6 +3021,71 @@ export namespace Browser {
export var themeName: "default" | "dark";
}
////////////////////
// Dev Tools - Recorder
////////////////////
/**
* Use the `Browser.devtools.recorder` API to customize the Recorder panel in DevTools.
* @since Chrome 105
*/
export namespace devtools.recorder {
/** A plugin interface that the Recorder panel invokes to customize the Recorder panel. */
export interface RecorderExtensionPlugin {
/**
* Allows the extension to implement custom replay functionality.
*
* @param recording A recording of the user interaction with the page. This should match [Puppeteer's recording schema](https://github.com/puppeteer/replay/blob/main/docs/api/interfaces/Schema.UserFlow.md).
* @since Chrome 112
*/
replay?(recording: object): void;
/**
* Converts a recording from the Recorder panel format into a string.
* @param recording A recording of the user interaction with the page. This should match [Puppeteer's recording schema](https://github.com/puppeteer/replay/blob/main/docs/api/interfaces/Schema.UserFlow.md).
*/
stringify?(recording: object): void;
/**
* Converts a step of the recording from the Recorder panel format into a string.
* @param step A step of the recording of a user interaction with the page. This should match [Puppeteer's step schema](https://github.com/puppeteer/replay/blob/main/docs/api/modules/Schema.md#step).
*/
stringifyStep?(step: object): void;
}
/**
* Represents a view created by extension to be embedded inside the Recorder panel.
* @since Chrome 112
*/
export interface RecorderView {
/** Fired when the view is hidden. */
onHidden: events.Event<() => void>;
/** Fired when the view is shown. */
onShown: events.Event<() => void>;
/** Indicates that the extension wants to show this view in the Recorder panel. */
show(): void;
}
/**
* Creates a view that can handle the replay. This view will be embedded inside the Recorder panel.
* @param title Title that is displayed next to the extension icon in the Developer Tools toolbar.
* @param pagePath Path of the panel's HTML page relative to the extension directory.
* @since Chrome 112
*/
export function createView(title: string, pagePath: string): RecorderView;
/**
* Registers a Recorder extension plugin.
* @param plugin An instance implementing the RecorderExtensionPlugin interface.
* @param name The name of the plugin.
* @param mediaType The media type of the string content that the plugin produces.
*/
export function registerRecorderExtensionPlugin(
plugin: RecorderExtensionPlugin,
name: string,
mediaType: string,
): void;
}
////////////////////
// Document Scan
////////////////////
@@ -8945,7 +8958,7 @@ export namespace Browser {
default_popup?: string | undefined;
}
// Source: https://developer.Browser.com/docs/extensions/mv3/declare_permissions/
/** Source: https://developer.Browser.com/docs/extensions/reference/permissions-list */
export type ManifestPermissions =
| "accessibilityFeatures.modify"
| "accessibilityFeatures.read"
@@ -9002,11 +9015,11 @@ export namespace Browser {
| "privacy"
| "processes"
| "proxy"
| "readingList"
| "scripting"
| "search"
| "sessions"
| "sidePanel"
| "signedInDevices"
| "storage"
| "system.cpu"
| "system.display"
@@ -9029,6 +9042,23 @@ export namespace Browser {
| "webRequestBlocking"
| "webRequestAuthProvider";
/** Source : https://developer.Browser.com/docs/extensions/reference/api/permissions */
export type ManifestOptionalPermissions = Exclude<
ManifestPermissions,
| "debugger"
| "declarativeNetRequest"
| "devtools"
| "experimental"
| "fontSettings"
| "geolocation"
| "proxy"
| "tts"
| "ttsEngine"
| "unlimitedStorage"
| "wallpaper"
| "webAuthenticationProxy"
>;
export interface SearchProvider {
name?: string | undefined;
keyword?: string | undefined;
@@ -9248,8 +9278,8 @@ export namespace Browser {
}
| undefined;
content_security_policy?: string | undefined;
optional_permissions?: string[] | undefined;
permissions?: string[] | undefined;
optional_permissions?: ManifestOptionalPermissions[] | string[] | undefined;
permissions?: ManifestPermissions[] | string[] | undefined;
web_accessible_resources?: string[] | undefined;
}
@@ -9284,7 +9314,7 @@ export namespace Browser {
sandbox?: string;
};
host_permissions?: string[] | undefined;
optional_permissions?: ManifestPermissions[] | undefined;
optional_permissions?: ManifestOptionalPermissions[] | undefined;
optional_host_permissions?: string[] | undefined;
permissions?: ManifestPermissions[] | undefined;
web_accessible_resources?: Array<{ resources: string[]; matches: string[] }> | undefined;
@@ -9798,32 +9828,6 @@ export namespace Browser {
export function updateContentScripts(scripts: RegisteredContentScript[], callback: () => void): void;
}
////////////////////
// Script Badge
////////////////////
export namespace scriptBadge {
export interface GetPopupDetails {
tabId: number;
}
export interface AttentionDetails {
tabId: number;
}
export interface SetPopupDetails {
tabId: number;
popup: string;
}
export interface ScriptBadgeClickedEvent extends Browser.events.Event<(tab: Browser.tabs.Tab) => void> {}
export function getPopup(details: GetPopupDetails, callback: Function): void;
export function getAttention(details: AttentionDetails): void;
export function setPopup(details: SetPopupDetails): void;
export var onClicked: ScriptBadgeClickedEvent;
}
////////////////////
// Sessions
////////////////////
@@ -12382,133 +12386,143 @@ export namespace Browser {
// Text to Speech
////////////////////
/**
* Use the `Browser.tts` API to play synthesized text-to-speech (TTS). See also the related ttsEngine API, which allows an extension to implement a speech engine.
* Use the `Browser.tts` API to play synthesized text-to-speech (TTS). See also the related {@link ttsEngine} API, which allows an extension to implement a speech engine.
*
* Permissions: "tts"
*/
export namespace tts {
/** @since Chrome 54 */
export enum EventType {
START = "start",
END = "end",
WORD = "word",
SENTENCE = "sentence",
MARKER = "marker",
INTERRUPTED = "interrupted",
CANCELLED = "cancelled",
ERROR = "error",
PAUSE = "pause",
RESUME = "resume",
}
/** An event from the TTS engine to communicate the status of an utterance. */
export interface TtsEvent {
/** Optional. The index of the current character in the utterance. */
charIndex?: number | undefined;
/** Optional. The error description, if the event type is 'error'. */
errorMessage?: string | undefined;
/** The index of the current character in the utterance. For word events, the event fires at the end of one word and before the beginning of the next. The `charIndex` represents a point in the text at the beginning of the next word to be spoken. */
charIndex?: number;
/** The error description, if the event type is `error`. */
errorMessage?: string;
/**
* The length of the next part of the utterance.
* For example, in a word event, this is the length of the word which will be spoken next.
* It will be set to -1 if not set by the speech engine.
* The length of the next part of the utterance. For example, in a `word` event, this is the length of the word which will be spoken next. It will be set to -1 if not set by the speech engine.
* @since Chrome 74
*/
length?: number | undefined;
length?: number;
/** The type can be `start` as soon as speech has started, `word` when a word boundary is reached, `sentence` when a sentence boundary is reached, `marker` when an SSML mark element is reached, `end` when the end of the utterance is reached, `interrupted` when the utterance is stopped or interrupted before reaching the end, `cancelled` when it's removed from the queue before ever being synthesized, or `error` when any other error occurs. When pausing speech, a `pause` event is fired if a particular utterance is paused in the middle, and `resume` if an utterance resumes speech. Note that pause and resume events may not fire if speech is paused in-between utterances. */
type: `${EventType}`;
}
/**
* The speech options for the TTS engine.
* @since Chrome 77
*/
export interface TtsOptions {
/** The TTS event types that you are interested in listening to. If missing, all event types may be sent. */
desiredEventTypes?: string[];
/** If true, enqueues this utterance if TTS is already in progress. If false (the default), interrupts any current speech and flushes the speech queue before speaking this new utterance. */
enqueue?: boolean;
/** The extension ID of the speech engine to use, if known. */
extensionId?: string;
/**
* The type can be 'start' as soon as speech has started, 'word' when a word boundary is reached, 'sentence' when a sentence boundary is reached, 'marker' when an SSML mark element is reached, 'end' when the end of the utterance is reached, 'interrupted' when the utterance is stopped or interrupted before reaching the end, 'cancelled' when it's removed from the queue before ever being synthesized, or 'error' when any other error occurs. When pausing speech, a 'pause' event is fired if a particular utterance is paused in the middle, and 'resume' if an utterance resumes speech. Note that pause and resume events may not fire if speech is paused in-between utterances.
* One of: "start", "end", "word", "sentence", "marker", "interrupted", "cancelled", "error", "pause", or "resume"
* Gender of voice for synthesized speech.
* @deprecated since Chrome 77. Gender is deprecated and will be ignored.
*/
type:
| "start"
| "end"
| "word"
| "sentence"
| "marker"
| "interrupted"
| "cancelled"
| "error"
| "pause"
| "resume";
gender?: `${VoiceGender}`;
/** The language to be used for synthesis, in the form _language_\-_region_. Examples: 'en', 'en-US', 'en-GB', 'zh-CN'. */
lang?: string;
/** Speaking pitch between 0 and 2 inclusive, with 0 being lowest and 2 being highest. 1.0 corresponds to a voice's default pitch. */
pitch?: number;
/** Speaking rate relative to the default rate for this voice. 1.0 is the default rate, normally around 180 to 220 words per minute. 2.0 is twice as fast, and 0.5 is half as fast. Values below 0.1 or above 10.0 are strictly disallowed, but many voices will constrain the minimum and maximum rates further—for example a particular voice may not actually speak faster than 3 times normal even if you specify a value larger than 3.0. */
rate?: number;
/** The TTS event types the voice must support. */
requiredEventTypes?: string[];
/** The name of the voice to use for synthesis. If empty, uses any available voice. */
voiceName?: string;
/** Speaking volume between 0 and 1 inclusive, with 0 being lowest and 1 being highest, with a default of 1.0. */
volume?: number;
/**
* This function is called with events that occur in the process of speaking the utterance.
* @param event The update event from the text-to-speech engine indicating the status of this utterance.
*/
onEvent?: (
event: TtsEvent,
) => void;
}
/** A description of a voice available for speech synthesis. */
export interface TtsVoice {
/** Optional. The language that this voice supports, in the form language-region. Examples: 'en', 'en-US', 'en-GB', 'zh-CN'. */
lang?: string | undefined;
/** All of the callback event types that this voice is capable of sending. */
eventTypes?: `${EventType}`[];
/** The ID of the extension providing this voice. */
extensionId?: string;
/**
* Optional. This voice's gender.
* One of: "male", or "female"
* This voice's gender.
* @deprecated since Chrome 70. Gender is deprecated and will be ignored.
*/
gender?: string | undefined;
/** Optional. The name of the voice. */
voiceName?: string | undefined;
/** Optional. The ID of the extension providing this voice. */
extensionId?: string | undefined;
/** Optional. All of the callback event types that this voice is capable of sending. */
eventTypes?: string[] | undefined;
/**
* Optional. If true, the synthesis engine is a remote network resource. It may be higher latency and may incur bandwidth costs.
* @since Chrome 33
*/
remote?: boolean | undefined;
gender?: `${VoiceGender}`;
/** The language that this voice supports, in the form language-region. Examples: 'en', 'en-US', 'en-GB', 'zh-CN'. */
lang?: string;
/** If true, the synthesis engine is a remote network resource. It may be higher latency and may incur bandwidth costs. */
remote?: boolean;
/** The name of the voice. */
voiceName?: string;
}
export interface SpeakOptions {
/** Optional. Speaking volume between 0 and 1 inclusive, with 0 being lowest and 1 being highest, with a default of 1.0. */
volume?: number | undefined;
/**
* Optional.
* If true, enqueues this utterance if TTS is already in progress. If false (the default), interrupts any current speech and flushes the speech queue before speaking this new utterance.
*/
enqueue?: boolean | undefined;
/**
* Optional.
* Speaking rate relative to the default rate for this voice. 1.0 is the default rate, normally around 180 to 220 words per minute. 2.0 is twice as fast, and 0.5 is half as fast. Values below 0.1 or above 10.0 are strictly disallowed, but many voices will constrain the minimum and maximum rates furtherfor example a particular voice may not actually speak faster than 3 times normal even if you specify a value larger than 3.0.
*/
rate?: number | undefined;
/**
* Optional. This function is called with events that occur in the process of speaking the utterance.
* @param event The update event from the text-to-speech engine indicating the status of this utterance.
*/
onEvent?: ((event: TtsEvent) => void) | undefined;
/**
* Optional.
* Speaking pitch between 0 and 2 inclusive, with 0 being lowest and 2 being highest. 1.0 corresponds to a voice's default pitch.
*/
pitch?: number | undefined;
/** Optional. The language to be used for synthesis, in the form language-region. Examples: 'en', 'en-US', 'en-GB', 'zh-CN'. */
lang?: string | undefined;
/** Optional. The name of the voice to use for synthesis. If empty, uses any available voice. */
voiceName?: string | undefined;
/** Optional. The extension ID of the speech engine to use, if known. */
extensionId?: string | undefined;
/**
* Optional. Gender of voice for synthesized speech.
* One of: "male", or "female"
*/
gender?: string | undefined;
/** Optional. The TTS event types the voice must support. */
requiredEventTypes?: string[] | undefined;
/** Optional. The TTS event types that you are interested in listening to. If missing, all event types may be sent. */
desiredEventTypes?: string[] | undefined;
/** @deprecated since Chrome 70. Gender is deprecated and is ignored.*/
export enum VoiceGender {
FEMALE = "female",
MALE = "male",
}
/** Checks whether the engine is currently speaking. On Mac OS X, the result is true whenever the system speech engine is speaking, even if the speech wasn't initiated by Chrome. */
export function isSpeaking(callback?: (speaking: boolean) => void): void;
/** Stops any current speech and flushes the queue of any pending utterances. In addition, if speech was paused, it will now be un-paused for the next call to speak. */
export function stop(): void;
/** Gets an array of all available voices. */
export function getVoices(): Promise<TtsVoice[]>;
export function getVoices(callback?: (voices: TtsVoice[]) => void): void;
/**
* Speaks text using a text-to-speech engine.
* @param utterance The text to speak, either plain text or a complete, well-formed SSML document. Speech engines that do not support SSML will strip away the tags and speak the text. The maximum length of the text is 32,768 characters.
* @param callback Optional. Called right away, before speech finishes. Check Browser.runtime.lastError to make sure there were no errors. Use options.onEvent to get more detailed feedback.
* Gets an array of all available voices.
*
* Can return its result via Promise since Chrome Chrome 101
*/
export function speak(utterance: string, callback?: Function): void;
export function getVoices(): Promise<TtsVoice[]>;
export function getVoices(callback: (voices: TtsVoice[]) => void): void;
/**
* Checks whether the engine is currently speaking. On Mac OS X, the result is true whenever the system speech engine is speaking, even if the speech wasn't initiated by Chrome.
*
* Can return its result via Promise since Chrome Chrome 101
*/
export function isSpeaking(): Promise<boolean>;
export function isSpeaking(callback: (speaking: boolean) => void): void;
/** Pauses speech synthesis, potentially in the middle of an utterance. A call to resume or stop will un-pause speech. */
export function pause(): void;
/** If speech was paused, resumes speaking where it left off. */
export function resume(): void;
/**
* Speaks text using a text-to-speech engine.
* @param utterance The text to speak, either plain text or a complete, well-formed SSML document. Speech engines that do not support SSML will strip away the tags and speak the text. The maximum length of the text is 32,768 characters.
* @param options Optional. The speech options.
* @param callback Optional. Called right away, before speech finishes. Check Browser.runtime.lastError to make sure there were no errors. Use options.onEvent to get more detailed feedback.
* Can return its result via Promise since Chrome Chrome 101
*/
export function speak(utterance: string, options: SpeakOptions, callback?: Function): void;
export function speak(utterance: string, options?: TtsOptions): Promise<void>;
export function speak(utterance: string, callback: () => void): void;
export function speak(utterance: string, options: TtsOptions, callback: () => void): void;
/** Stops any current speech and flushes the queue of any pending utterances. In addition, if speech was paused, it will now be un-paused for the next call to speak. */
export function stop(): void;
/**
* Pauses speech synthesis, potentially in the middle of an utterance. A call to resume or stop will un-pause speech.
* @since Chrome 29
* Called when the list of {@link TtsVoice} that would be returned by getVoices has changed.
* @since Chrome 124
*/
export function pause(): void;
/**
* If speech was paused, resumes speaking where it left off.
* @since Chrome 29
*/
export function resume(): void;
const onVoicesChanged: Browser.events.Event<() => void>;
}
////////////////////
@@ -13445,113 +13459,6 @@ export namespace Browser {
export const onErrorOccurred: WebResponseErrorEvent;
}
////////////////////
// Web Store
////////////////////
/**
* Use the Browser.webstore API to initiate app and extension installations "inline" from your site.
* @since Chrome 15
*/
export namespace webstore {
/**
* @param url Optional. If you have more than one <link> tag on your page with the chrome-webstore-item relation, you can choose which item you'd like to install by passing in its URL here. If it is omitted, then the first (or only) link will be used. An exception will be thrown if the passed in URL does not exist on the page.
* @param successCallback Optional. This function is invoked when inline installation successfully completes (after the dialog is shown and the user agrees to add the item to Chrome). You may wish to use this to hide the user interface element that prompted the user to install the app or extension.
* @param failureCallback Optional. This function is invoked when inline installation does not successfully complete. Possible reasons for this include the user canceling the dialog, the linked item not being found in the store, or the install being initiated from a non-verified site.
* Parameter error: The failure detail. You may wish to inspect or log this for debugging purposes, but you should not rely on specific strings being passed back.
* Optional parameter errorCode: The error code from the stable set of possible errors.
* * Enum of the possible install results, including error codes sent back in the event that an inline installation has failed.
* * * "otherError": An uncommon, unrecognized, or unexpected error. In some cases, the readable error string can provide more information.
* * * "aborted": The operation was aborted as the requestor is no longer alive.
* * * "installInProgress": An installation of the same extension is in progress.
* * * "notPermitted": The installation is not permitted.
* * * "invalidId": Invalid Chrome Web Store item ID.
* * * "webstoreRequestError": Failed to retrieve extension metadata from the Web Store.
* * * "invalidWebstoreResponse": The extension metadata retrieved from the Web Store was invalid.
* * * "invalidManifest": An error occurred while parsing the extension manifest retrieved from the Web Store.
* * * "iconError": Failed to retrieve the extension's icon from the Web Store, or the icon was invalid.
* * * "userCanceled": The user canceled the operation.
* * * "blacklisted": The extension is blacklisted.
* * * "missingDependencies": Unsatisfied dependencies, such as shared modules.
* * * "requirementViolations": Unsatisfied requirements, such as webgl.
* * * "blockedByPolicy": The extension is blocked by management policies.
* * * "launchFeatureDisabled": The launch feature is not available.
* * * "launchUnsupportedExtensionType": The launch feature is not supported for the extension type.
* * * "launchInProgress": A launch of the same extension is in progress.
*/
export function install(
url: string,
successCallback?: Function,
failureCallback?: (error: string, errorCode?: string) => void,
): void;
/**
* @param successCallback Optional. This function is invoked when inline installation successfully completes (after the dialog is shown and the user agrees to add the item to Chrome). You may wish to use this to hide the user interface element that prompted the user to install the app or extension.
* @param failureCallback Optional. This function is invoked when inline installation does not successfully complete. Possible reasons for this include the user canceling the dialog, the linked item not being found in the store, or the install being initiated from a non-verified site.
* Parameter error: The failure detail. You may wish to inspect or log this for debugging purposes, but you should not rely on specific strings being passed back.
* Optional parameter errorCode: The error code from the stable set of possible errors.
* * Enum of the possible install results, including error codes sent back in the event that an inline installation has failed.
* * * "otherError": An uncommon, unrecognized, or unexpected error. In some cases, the readable error string can provide more information.
* * * "aborted": The operation was aborted as the requestor is no longer alive.
* * * "installInProgress": An installation of the same extension is in progress.
* * * "notPermitted": The installation is not permitted.
* * * "invalidId": Invalid Chrome Web Store item ID.
* * * "webstoreRequestError": Failed to retrieve extension metadata from the Web Store.
* * * "invalidWebstoreResponse": The extension metadata retrieved from the Web Store was invalid.
* * * "invalidManifest": An error occurred while parsing the extension manifest retrieved from the Web Store.
* * * "iconError": Failed to retrieve the extension's icon from the Web Store, or the icon was invalid.
* * * "userCanceled": The user canceled the operation.
* * * "blacklisted": The extension is blacklisted.
* * * "missingDependencies": Unsatisfied dependencies, such as shared modules.
* * * "requirementViolations": Unsatisfied requirements, such as webgl.
* * * "blockedByPolicy": The extension is blocked by management policies.
* * * "launchFeatureDisabled": The launch feature is not available.
* * * "launchUnsupportedExtensionType": The launch feature is not supported for the extension type.
* * * "launchInProgress": A launch of the same extension is in progress.
*/
export function install(
successCallback: Function,
failureCallback?: (error: string, errorCode?: string) => void,
): void;
/**
* @param failureCallback Optional. This function is invoked when inline installation does not successfully complete. Possible reasons for this include the user canceling the dialog, the linked item not being found in the store, or the install being initiated from a non-verified site.
* Parameter error: The failure detail. You may wish to inspect or log this for debugging purposes, but you should not rely on specific strings being passed back.
* Optional parameter errorCode: The error code from the stable set of possible errors.
* * Enum of the possible install results, including error codes sent back in the event that an inline installation has failed.
* * * "otherError": An uncommon, unrecognized, or unexpected error. In some cases, the readable error string can provide more information.
* * * "aborted": The operation was aborted as the requestor is no longer alive.
* * * "installInProgress": An installation of the same extension is in progress.
* * * "notPermitted": The installation is not permitted.
* * * "invalidId": Invalid Chrome Web Store item ID.
* * * "webstoreRequestError": Failed to retrieve extension metadata from the Web Store.
* * * "invalidWebstoreResponse": The extension metadata retrieved from the Web Store was invalid.
* * * "invalidManifest": An error occurred while parsing the extension manifest retrieved from the Web Store.
* * * "iconError": Failed to retrieve the extension's icon from the Web Store, or the icon was invalid.
* * * "userCanceled": The user canceled the operation.
* * * "blacklisted": The extension is blacklisted.
* * * "missingDependencies": Unsatisfied dependencies, such as shared modules.
* * * "requirementViolations": Unsatisfied requirements, such as webgl.
* * * "blockedByPolicy": The extension is blocked by management policies.
* * * "launchFeatureDisabled": The launch feature is not available.
* * * "launchUnsupportedExtensionType": The launch feature is not supported for the extension type.
* * * "launchInProgress": A launch of the same extension is in progress.
*/
export function install(failureCallback?: (error: string, errorCode?: string) => void): void;
export interface InstallationStageEvent extends Browser.events.Event<(stage: string) => void> {}
export interface DownloadProgressEvent extends Browser.events.Event<(percentDownloaded: number) => void> {}
/**
* Fired when an inline installation enters a new InstallStage. In order to receive notifications about this event, listeners must be registered before the inline installation begins.
* @since Chrome 35
*/
export var onInstallStageChanged: InstallationStageEvent;
/**
* Fired periodically with the download progress of an inline install. In order to receive notifications about this event, listeners must be registered before the inline installation begins.
* @since Chrome 35
*/
export var onDownloadProgress: DownloadProgressEvent;
}
////////////////////
// Windows
////////////////////
@@ -14955,6 +14862,18 @@ export namespace Browser {
*/
export type ExecutionWorld = "MAIN" | "USER_SCRIPT";
/** @since Chrome 135 */
export interface InjectionResult {
/** The document associated with the injection. */
documentId: string;
/** The error, if any. `error` and `result` are mutually exclusive. */
error?: string;
/** The frame associated with the injection. */
frameId: number;
/** The result of the script execution. */
result: any;
}
export interface WorldProperties {
/** Specifies the world csp. The default is the `ISOLATED` world csp. */
csp?: string;
@@ -14971,6 +14890,18 @@ export namespace Browser {
ids?: string[];
}
/** @since Chrome 135 */
export interface InjectionTarget {
/** Whether the script should inject into all frames within the tab. Defaults to false. This must not be true if `frameIds` is specified. */
allFrames?: boolean;
/** The IDs of specific documentIds to inject into. This must not be set if `frameIds` is set. */
documentIds?: string[];
/** The IDs of specific frames to inject into. */
frameIds?: number[];
/** The ID of the tab into which to inject. */
tabId: number;
}
export interface RegisteredUserScript {
/** If true, it will inject into all frames, even if the frame is not the top-most frame in the tab. Each frame is checked independently for URL requirements; it will not inject into child frames if the URL requirements are not met. Defaults to false, meaning that only the top frame is matched. */
allFrames?: boolean;
@@ -14997,6 +14928,20 @@ export namespace Browser {
worldId?: string;
}
/** @since Chrome 135 */
export interface UserScriptInjection {
/** Whether the injection should be triggered in the target as soon as possible. Note that this is not a guarantee that injection will occur prior to page load, as the page may have already loaded by the time the script reaches the target. */
injectImmediately?: boolean;
/** The list of ScriptSource objects defining sources of scripts to be injected into the target. */
js: ScriptSource[];
/** Details specifying the target into which to inject the script. */
target: InjectionTarget;
/** The JavaScript "world" to run the script in. The default is `USER_SCRIPT`. */
world?: ExecutionWorld;
/** Specifies the user script world ID to execute in. If omitted, the script will execute in the default user script world. Only valid if `world` is omitted or is `USER_SCRIPT`. Values with leading underscores (`_`) are reserved. */
worldId?: string;
}
/**
* Properties for a script source.
*/
@@ -15049,6 +14994,13 @@ export namespace Browser {
export function getWorldConfigurations(): Promise<WorldProperties[]>;
export function getWorldConfigurations(callback: (worlds: WorldProperties[]) => void): void;
/**
* Injects a script into a target context. By default, the script will be run at `document_idle`, or immediately if the page has already loaded. If the `injectImmediately` property is set, the script will inject without waiting, even if the page has not finished loading. If the script evaluates to a promise, the browser will wait for the promise to settle and return the resulting value.
* @since Chrome 135
*/
export function execute(injection: UserScriptInjection): Promise<InjectionResult[]>;
export function execute(injection: UserScriptInjection, callback: (result: InjectionResult[]) => void): void;
/**
* Registers one or more user scripts for this extension.
*
+2
View File
@@ -11,6 +11,7 @@
"directory": "packages/browser"
},
"scripts": {
"check": "check",
"gen": "tsx scripts/generate.ts"
},
"author": {
@@ -26,6 +27,7 @@
"fs-extra": "catalog:",
"nano-spawn": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {}
}
+19 -1
View File
@@ -979,7 +979,7 @@ describe('Storage Utils', () => {
const actual = await item.getMeta();
expect(actual).toBe(expected);
expect(actual).toEqual(expected);
});
it('should return an empty object if missing', async () => {
@@ -1281,6 +1281,19 @@ describe('Storage Utils', () => {
it('should define a nullable value when options are not passed', () => {
const item = storage.defineItem<number>(`local:test`);
expectTypeOf(item).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
const item2 = storage.defineItem<number>(`local:test`, {});
expectTypeOf(item2).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
const item3 = storage.defineItem<number>(`local:test`, {
fallback: undefined,
});
expectTypeOf(item3).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
const item4 = storage.defineItem<number>(`local:test`, {
defaultValue: undefined,
});
expectTypeOf(item4).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
});
it('should define a non-null value when options are passed with a nullish default value', () => {
@@ -1288,6 +1301,11 @@ describe('Storage Utils', () => {
defaultValue: 123,
});
expectTypeOf(item).toEqualTypeOf<WxtStorageItem<number, {}>>();
const item2 = storage.defineItem(`local:test`, {
fallback: 123,
});
expectTypeOf(item2).toEqualTypeOf<WxtStorageItem<number, {}>>();
});
it('should define a nullable value when options are passed with null default value', () => {
+9 -1
View File
@@ -750,8 +750,16 @@ export interface WxtStorage {
): WxtStorageItem<TValue | null, TMetadata>;
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
key: StorageItemKey,
options: WxtStorageItemOptions<TValue>,
options: WxtStorageItemOptions<TValue> & { fallback: TValue },
): WxtStorageItem<TValue, TMetadata>;
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
key: StorageItemKey,
options: WxtStorageItemOptions<TValue> & { defaultValue: TValue },
): WxtStorageItem<TValue, TMetadata>;
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
key: StorageItemKey,
options: WxtStorageItemOptions<TValue>,
): WxtStorageItem<TValue | null, TMetadata>;
}
interface WxtStorageDriver {
+1 -1
View File
@@ -24,7 +24,7 @@ export default defineConfig({
Now in your entrypoint, import UnoCSS:
```ts
import 'uno.css';
import 'virtual:uno.css';
```
> [!IMPORTANT]
+2 -1
View File
@@ -3,6 +3,7 @@ import { presetUno } from 'unocss';
export default defineConfig({
srcDir: 'src',
targetBrowsers: ['chrome', 'firefox', 'safari'],
manifest: {
permissions: ['storage'],
default_locale: 'en',
@@ -19,7 +20,7 @@ export default defineConfig({
analysis: {
open: true,
},
runner: {
webExt: {
startUrls: ['https://duckduckgo.com'],
},
example: {
+95 -14
View File
@@ -1,8 +1,102 @@
# Changelog
## v0.20.4
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.3...wxt-v0.20.4)
### 🚀 Enhancements
- Ignore popup/index.ts instead of erroring ([#1520](https://github.com/wxt-dev/wxt/pull/1520))
- Ignore elements with a `vite-ignore` or `wxt-ignore` attribute ([#1603](https://github.com/wxt-dev/wxt/pull/1603))
- Add `{{packageVersion}}` as template variable ([#1604](https://github.com/wxt-dev/wxt/pull/1604))
### 🩹 Fixes
- Adding missing `""` to `PublicPath` and `browser.runtime.getUrl` ([#1597](https://github.com/wxt-dev/wxt/pull/1597))
- Fix CORS error in Firefox ([#1607](https://github.com/wxt-dev/wxt/pull/1607))
### 📖 Documentation
- **Content Script UI:** Add additional details about when `onRemove` is called ([656a9b3](https://github.com/wxt-dev/wxt/commit/656a9b3))
### ❤️ Contributors
- Yunsup Sim <pedogunu@gmail.com>
- ТΞNSΛI <tensai@gmx.net>
- Nishu ([@nishu-murmu](https://github.com/nishu-murmu))
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.20.3
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.2...wxt-v0.20.3)
### 🚀 Enhancements
- Automatically place document-level CSS outside shadow root ([#1594](https://github.com/wxt-dev/wxt/pull/1594))
### 🩹 Fixes
- Fix double hashing of inline script keys ([b0f4ac8](https://github.com/wxt-dev/wxt/commit/b0f4ac8))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.20.2
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.1...wxt-v0.20.2)
### 🩹 Fixes
- Fix hashing issue with inline scripts ([#1591](https://github.com/wxt-dev/wxt/pull/1591))
### 📖 Documentation
- Fix typo in changelog ([acb6cd1](https://github.com/wxt-dev/wxt/commit/acb6cd1))
### ❤️ Contributors
- Yunsup Sim ([@SimYunSup](https://github.com/SimYunSup))
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.20.1
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.0...wxt-v0.20.1)
### 🚀 Enhancements
- Enable wxt usage inside of devcontainers ([#1406](https://github.com/wxt-dev/wxt/pull/1406))
- Type-safe `import.meta.env.BROWSER` with new `targetBrowsers` config ([#1574](https://github.com/wxt-dev/wxt/pull/1574))
### 🩹 Fixes
- Don't remove top-level destructured variable definitions when importing entrypoints ([#1561](https://github.com/wxt-dev/wxt/pull/1561))
- Add JSDoc type annotation to auto-imports for ESlint ([#1558](https://github.com/wxt-dev/wxt/pull/1558))
### 📖 Documentation
- Fix knowledge file generation ([#1550](https://github.com/wxt-dev/wxt/pull/1550))
### 🏡 Chore
- **deps:** Update all dependencies ([#1568](https://github.com/wxt-dev/wxt/pull/1568))
- Update comment ([61b42ef](https://github.com/wxt-dev/wxt/commit/61b42ef))
### ❤️ Contributors
- 7sDream ([@7sDream](https://github.com/7sDream))
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Nostro ([@nostrorom](https://github.com/nostrorom))
- Khalil Yao ([@yyz945947732](https://github.com/yyz945947732))
- Alec WM ([@alecdwm](https://github.com/alecdwm))
## v0.20.0
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.29...wxt-v0.20.0)
Excited to release the next major version of WXT! Follow the [Upgrade guide](https://wxt.dev/guide/resources/upgrading.html) to update!
---
[⚠️ breaking changes](https://wxt.dev/guide/resources/upgrading.html) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.29...wxt-v0.20.0)
### 🚀 Enhancements
@@ -33,19 +127,6 @@
- Fix type errors ([aad17c8](https://github.com/wxt-dev/wxt/commit/aad17c8))
- Remove duplicate test ([e54df0a](https://github.com/wxt-dev/wxt/commit/e54df0a))
#### ⚠️ Breaking Changes
- ⚠️ Remove `webextension-polyfill` ([#1084](https://github.com/wxt-dev/wxt/pull/1084))
- ⚠️ Individual exports and introduce the `#imports` module ([#1258](https://github.com/wxt-dev/wxt/pull/1258))
- ⚠️ Reset inherited styles inside shadow root ([#1269](https://github.com/wxt-dev/wxt/pull/1269))
- ⚠️ Auto-import types ([#1315](https://github.com/wxt-dev/wxt/pull/1315))
- ⚠️ Add suffix to non-production output directories ([#1086](https://github.com/wxt-dev/wxt/pull/1086))
- ⚠️ Remove deprecated `jiti` entrypoint loader ([#1087](https://github.com/wxt-dev/wxt/pull/1087))
- ⚠️ Rename `runner` to `webExt` ([#1180](https://github.com/wxt-dev/wxt/pull/1180))
- ⚠️ Remove `transformManfiest` option ([#1181](https://github.com/wxt-dev/wxt/pull/1181))
- ⚠️ Make `publicDir` and `modulesDir` relative to project root ([#1216](https://github.com/wxt-dev/wxt/pull/1216))
- ⚠️ Move `wxt/storage` to `wxt/utils/storage` ([#1271](https://github.com/wxt-dev/wxt/pull/1271))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
@@ -110,6 +110,7 @@ export default {
name: "wxt/auto-imports",
languageOptions: {
globals,
/** @type {import('eslint').Linter.SourceType} */
sourceType: "module",
},
};
@@ -48,6 +48,7 @@ describe('TypeScript Project', () => {
declare module "wxt/browser" {
export type PublicPath =
| ""
| "/"
| "/options.html"
| "/popup.html"
@@ -396,6 +397,19 @@ describe('TypeScript Project', () => {
expect(output).toContain('./example.ts');
});
it('should set correct import.meta.env.BROWSER type based on targetBrowsers', async () => {
const project = new TestProject();
project.addFile('entrypoints/unlisted.html', '<html></html>');
project.setConfigFileConfig({
targetBrowsers: ['firefox', 'chrome'],
});
await project.prepare();
const output = await project.serializeFile('.wxt/types/globals.d.ts');
expect(output).toContain('readonly BROWSER: "firefox" | "chrome";');
});
// TODO: Once a module has been published, use it here for testing - local files are never added to the .wxt/wxt.d.ts file
it.todo(
'should add modules from NPM to the TS project if they have a configKey',
+5 -1
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.20.0",
"version": "0.20.4",
"description": "⚡ Next-gen Web Extension Framework",
"license": "MIT",
"scripts": {
@@ -167,6 +167,10 @@
"types": "./dist/utils/match-patterns.d.ts",
"default": "./dist/utils/match-patterns.mjs"
},
"./utils/split-shadow-root-css": {
"types": "./dist/utils/split-shadow-root-css.d.ts",
"default": "./dist/utils/split-shadow-root-css.mjs"
},
"./utils/storage": {
"types": "./dist/utils/storage.d.ts",
"default": "./dist/utils/storage.mjs"
+1 -3
View File
@@ -1,6 +1,4 @@
declare const __DEV_SERVER_PROTOCOL__: string;
declare const __DEV_SERVER_HOSTNAME__: string;
declare const __DEV_SERVER_PORT__: string;
declare const __DEV_SERVER_ORIGIN__: string;
// Globals defined by the vite-plugins/devServerGlobals.ts and utils/globals.ts
interface ImportMetaEnv {
@@ -141,6 +141,7 @@ export default {
name: "wxt/auto-imports",
languageOptions: {
globals,
/** @type {import('eslint').Linter.SourceType} */
sourceType: "module",
},
};
+10 -7
View File
@@ -16,7 +16,8 @@ cli
.option('-c, --config <file>', 'use specified config file')
.option('-m, --mode <mode>', 'set env mode')
.option('-b, --browser <browser>', 'specify a browser')
.option('-p, --port <port>', 'specify a port for the dev server')
.option('--host <host>', 'specify a host for the dev server to bind to')
.option('-p, --port <port>', 'specify a port for the dev server to bind to')
.option(
'-e, --filter-entrypoint <entrypoint>',
'only build specific entrypoints',
@@ -28,6 +29,12 @@ cli
.option('--mv2', 'target manifest v2')
.action(
wrapAction(async (root, flags) => {
const serverOptions: NonNullable<
NonNullable<Parameters<typeof createServer>[0]>['dev']
>['server'] = {};
if (flags.host) serverOptions.host = flags.host;
if (flags.port) serverOptions.port = parseInt(flags.port);
const server = await createServer({
root,
mode: flags.mode,
@@ -37,13 +44,9 @@ cli
debug: flags.debug,
filterEntrypoints: getArrayFromFlags(flags, 'filterEntrypoint'),
dev:
flags.port == null
Object.keys(serverOptions).length === 0
? undefined
: {
server: {
port: parseInt(flags.port),
},
},
: { server: serverOptions },
});
await server.start();
return { isOngoing: true };
+1 -1
View File
@@ -327,9 +327,9 @@ export async function createViteBuilder(
async createServer(info) {
const serverConfig: vite.InlineConfig = {
server: {
host: info.host,
port: info.port,
strictPort: true,
host: info.hostname,
origin: info.origin,
},
};
@@ -20,7 +20,7 @@ describe('Dev HTML Prerender Plugin', () => {
['~local/style.css', 'http://localhost:5173/style.css'],
['~absolute/style.css', 'http://localhost:5173/assets/style.css'],
['~file', 'http://localhost:5173/example.css'],
// Absolute paths are loaded with the `/@fs/` base path
// Paths outside the project root are loaded with the `/@fs/` base path
[
'~outside/test.css',
`http://localhost:5173/@fs${
@@ -48,7 +48,7 @@ describe('Dev HTML Prerender Plugin', () => {
},
});
const server = fakeDevServer({
hostname: 'localhost',
host: 'localhost',
port: 5173,
origin: 'http://localhost:5173',
});
@@ -4,10 +4,10 @@ import { getEntrypointName } from '../../../utils/entrypoints';
import { parseHTML } from 'linkedom';
import { dirname, relative, resolve } from 'node:path';
import { normalizePath } from '../../../utils/paths';
import { murmurHash } from 'ohash';
import { hash } from 'ohash';
// Stored outside the plugin to effect all instances of the devHtmlPrerender plugin.
const inlineScriptContents: Record<number, string> = {};
const inlineScriptContents: Record<string, string> = {};
/**
* Pre-renders the HTML entrypoints when building the extension to connect to the dev server.
@@ -85,13 +85,13 @@ export function devHtmlPrerender(
inlineScripts.forEach((script) => {
// Save the text content for later
const textContent = script.textContent ?? '';
const hash = murmurHash(textContent);
inlineScriptContents[hash] = textContent;
const key = hash(textContent);
inlineScriptContents[key] = textContent;
// Replace unsafe inline script
const virtualScript = document.createElement('script');
virtualScript.type = 'module';
virtualScript.src = `${server.origin}/@id/${virtualInlineScript}?${hash}`;
virtualScript.src = `${server.origin}/@id/${virtualInlineScript}?${key}`;
script.replaceWith(virtualScript);
});
@@ -111,7 +111,7 @@ export function devHtmlPrerender(
},
},
{
name: 'wxt:virtualize-react-refresh',
name: 'wxt:virtualize-inline-scripts',
apply: 'serve',
resolveId(id) {
// Resolve inline scripts
@@ -127,9 +127,9 @@ export function devHtmlPrerender(
load(id) {
// Resolve virtualized inline scripts
if (id.startsWith(resolvedVirtualInlineScript)) {
// id="virtual:wxt-inline-script?<hash>"
const hash = Number(id.substring(id.indexOf('?') + 1));
return inlineScriptContents[hash];
// id="virtual:wxt-inline-script?<key>"
const key = id.substring(id.indexOf('?') + 1);
return inlineScriptContents[key];
}
// Ignore chunks during HTML file pre-rendering
@@ -150,6 +150,13 @@ export function pointToDevServer(
attr: string,
) {
document.querySelectorAll(querySelector).forEach((element) => {
if (
element.hasAttribute('vite-ignore') ||
element.hasAttribute('wxt-ignore')
) {
element.removeAttribute('wxt-ignore');
return;
}
const src = element.getAttribute(attr);
if (!src || isUrl(src)) return;
@@ -15,9 +15,9 @@ export function devServerGlobals(
return {
define: {
__DEV_SERVER_PROTOCOL__: JSON.stringify('ws:'),
__DEV_SERVER_HOSTNAME__: JSON.stringify(server.hostname),
__DEV_SERVER_PORT__: JSON.stringify(server.port),
__DEV_SERVER_ORIGIN__: JSON.stringify(
server.origin.replace(/^http(s):/, 'ws$1:'),
),
},
};
},
@@ -48,7 +48,7 @@ try {
handler(html, _ctx) {
const src =
config.command === 'serve'
? `http://${config.dev.server?.hostname}:${config.dev.server?.port}/@id/${virtualHtmlModuleId}`
? `${config.dev.server?.origin}/@id/${virtualHtmlModuleId}`
: virtualHtmlModuleId;
const { document } = parseHTML(html);
+7 -9
View File
@@ -52,12 +52,8 @@ export async function createServer(
async function createServerInternal(): Promise<WxtDevServer> {
const getServerInfo = (): ServerInfo => {
const { port, hostname } = wxt.config.dev.server!;
return {
port,
hostname,
origin: `http://${hostname}:${port}`,
};
const { host, port, origin } = wxt.config.dev.server!;
return { host, port, origin };
};
let [runner, builderServer] = await Promise.all([
@@ -71,8 +67,8 @@ async function createServerInternal(): Promise<WxtDevServer> {
// Server instance must be created first so its reference can be added to the internal config used
// to pre-render entrypoints
const server: WxtDevServer = {
get hostname() {
return getServerInfo().hostname;
get host() {
return getServerInfo().host;
},
get port() {
return getServerInfo().port;
@@ -96,7 +92,9 @@ async function createServerInternal(): Promise<WxtDevServer> {
}
await builderServer.listen();
wxt.logger.success(`Started dev server @ ${server.origin}`);
const hostInfo =
server.host === 'localhost' ? '' : ` (listening on ${server.host})`;
wxt.logger.success(`Started dev server @ ${server.origin}${hostInfo}`);
await wxt.hooks.callHook('server:started', wxt, server);
// Register content scripts for the first time after the background starts
+8 -6
View File
@@ -73,16 +73,18 @@ async function getPathsDeclarationEntry(
isHtmlEntrypoint(entry) ? '.html' : '.js',
),
)
.concat([''])
.concat(await getPublicFiles());
await wxt.hooks.callHook('prepare:publicPaths', wxt, paths);
const unions = paths
.map(normalizePath)
.sort()
.map((path) => ` | "/${path}"`)
.join('\n');
const unions = [
` | ""`,
` | "/"`,
...paths
.map(normalizePath)
.sort()
.map((path) => ` | "/${path}"`),
].join('\n');
const template = `// Generated by wxt
import "wxt/browser";
@@ -0,0 +1,11 @@
import { WxtPackageManagerImpl } from './types';
export const deno: WxtPackageManagerImpl = {
overridesKey: 'na',
downloadDependency() {
throw Error('Deno not supported');
},
listDependencies() {
throw Error('Deno not supported');
},
};
@@ -14,6 +14,7 @@ import { WxtPackageManagerImpl } from './types';
import { yarn } from './yarn';
import { pnpm } from './pnpm';
import { npm } from './npm';
import { deno } from './deno';
export async function createWxtPackageManager(
root: string,
@@ -73,4 +74,5 @@ const packageManagers: Record<PackageManagerName, WxtPackageManagerImpl> = {
pnpm,
bun,
yarn,
deno,
};
+29 -4
View File
@@ -70,6 +70,12 @@ export async function resolveConfig(
if (debug) logger.level = LogLevels.debug;
const browser = mergedConfig.browser ?? 'chrome';
const targetBrowsers = mergedConfig.targetBrowsers ?? [];
if (targetBrowsers.length > 0 && !targetBrowsers.includes(browser)) {
throw new Error(
`Current target browser \`${browser}\` is not in your \`targetBrowsers\` list!`,
);
}
const manifestVersion =
mergedConfig.manifestVersion ??
(browser === 'firefox' || browser === 'safari' ? 2 : 3);
@@ -143,19 +149,37 @@ export async function resolveConfig(
let devServerConfig: ResolvedConfig['dev']['server'];
if (command === 'serve') {
const hostname = mergedConfig.dev?.server?.hostname ?? 'localhost';
if (mergedConfig.dev?.server?.hostname)
logger.warn(
`The 'hostname' option is deprecated, please use 'host' or 'origin' depending on your circumstances.`,
);
const host =
mergedConfig.dev?.server?.host ??
mergedConfig.dev?.server?.hostname ??
'localhost';
let port = mergedConfig.dev?.server?.port;
const origin =
mergedConfig.dev?.server?.origin ??
mergedConfig.dev?.server?.hostname ??
'localhost';
if (port == null || !isFinite(port)) {
port = await getPort({
// Passing host required for Mac, unsure of Windows/Linux
host,
port: 3000,
portRange: [3001, 3010],
// Passing host required for Mac, unsure of Windows/Linux
host: hostname,
});
}
const originWithProtocolAndPort = [
origin.match(/^https?:\/\//) ? '' : 'http://',
origin,
origin.match(/:[0-9]+$/) ? '' : `:${port}`,
].join('');
devServerConfig = {
host,
port,
hostname,
origin: originWithProtocolAndPort,
watchDebounce: safeStringToNumber(process.env.WXT_WATCH_DEBOUNCE) ?? 800,
};
}
@@ -179,6 +203,7 @@ export async function resolveConfig(
return {
browser,
targetBrowsers,
command,
debug,
entrypointsDir,
@@ -1463,7 +1463,7 @@ describe('Manifest Utils', () => {
command: 'build',
},
server: {
hostname: 'localhost',
host: 'localhost',
port: 3000,
origin: 'http://localhost:3000',
},
@@ -1487,8 +1487,8 @@ describe('Manifest Utils', () => {
manifestVersion: 2,
},
server: fakeWxtDevServer({
host: 'localhost',
port: 3000,
hostname: 'localhost',
origin: 'http://localhost:3000',
}),
});
@@ -1515,7 +1515,7 @@ describe('Manifest Utils', () => {
browser: 'chrome',
},
server: fakeWxtDevServer({
hostname: 'localhost',
host: 'localhost',
port: 3000,
origin: 'http://localhost:3000',
}),
@@ -1561,8 +1561,8 @@ describe('Manifest Utils', () => {
},
},
server: fakeWxtDevServer({
host: 'localhost',
port: 3000,
hostname: 'localhost',
origin: 'http://localhost:3000',
}),
});
@@ -137,5 +137,38 @@ export default defineContentScript({
expect(actual).toEqual(expected);
});
it('should not remove any variables delcared outside the main function that are used', () => {
const input = `
const [ a ] = [ 123, 456 ];
const { b } = { b: 123 };
const { c: { d } } = { c: { d: 123 } };
const { e, ...rest } = { e: 123, f: 456 };
console.log(a);
console.log(b);
console.log(d);
console.log(e);
console.log(rest);
export default defineBackground(() => {
console.log('Hello background!', { id: browser.runtime.id });
});`;
const expected = `const [ a ] = [ 123, 456 ];
const { b } = { b: 123 };
const { c: { d } } = { c: { d: 123 } };
const { e, ...rest } = { e: 123, f: 456 };
console.log(a);
console.log(b);
console.log(d);
console.log(e);
console.log(rest);
export default defineBackground();`;
const actual = removeMainFunctionCode(input).code;
expect(actual).toEqual(expected);
});
});
});
@@ -668,27 +668,46 @@ describe('findEntrypoints', () => {
expect(entrypoints[0]).toEqual(expected);
});
it('should not allow multiple entrypoints with the same name', async () => {
it('should ignore other index files in the same directory when index.html exists', async () => {
globMock.mockResolvedValueOnce([
'content/index.ts',
'popup/index.html',
'popup/index.ts',
'popup/index.css',
]);
const entrypoints = await findEntrypoints();
expect(entrypoints).toHaveLength(2);
expect(entrypoints[0]).toMatchObject({
type: 'content-script',
name: 'content',
});
expect(entrypoints[1]).toMatchObject({
type: 'popup',
name: 'popup',
});
});
it('should not allow a file entrypoint and directory entrypoint to have the same name', async () => {
globMock.mockResolvedValueOnce([
'options/index.html',
'options/index.jsx',
'popup.html',
'popup/index.html',
'popup/index.ts',
'ui.html',
'other.ts',
'other/index.ts',
]);
await expect(() => findEntrypoints()).rejects.toThrowError(
[
'Multiple entrypoints with the same name detected, only one entrypoint for each name is allowed.',
'',
'- options',
` - ${unnormalizePath('src/entrypoints/options/index.html')}`,
` - ${unnormalizePath('src/entrypoints/options/index.jsx')}`,
'- other',
` - ${unnormalizePath('src/entrypoints/other.ts')}`,
` - ${unnormalizePath('src/entrypoints/other/index.ts')}`,
'- popup',
` - ${unnormalizePath('src/entrypoints/popup.html')}`,
` - ${unnormalizePath('src/entrypoints/popup/index.html')}`,
` - ${unnormalizePath('src/entrypoints/popup/index.ts')}`,
].join('\n'),
);
});
@@ -53,20 +53,34 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
relativePaths.sort();
const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
const entrypointInfos: EntrypointInfo[] = relativePaths.reduce<
EntrypointInfo[]
>((results, relativePath) => {
const inputPath = resolve(wxt.config.entrypointsDir, relativePath);
const name = getEntrypointName(wxt.config.entrypointsDir, inputPath);
const matchingGlob = pathGlobs.find((glob) =>
minimatch(relativePath, glob),
);
if (matchingGlob) {
const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
results.push({ name, inputPath, type });
}
return results;
}, []);
const entrypointInfos: EntrypointInfo[] = relativePaths
.reduce<EntrypointInfo[]>((results, relativePath) => {
const inputPath = resolve(wxt.config.entrypointsDir, relativePath);
const name = getEntrypointName(wxt.config.entrypointsDir, inputPath);
const matchingGlob = pathGlobs.find((glob) =>
minimatch(relativePath, glob),
);
if (matchingGlob) {
const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
results.push({ name, inputPath, type });
}
return results;
}, [])
.filter(({ name, inputPath }, _, entrypointInfos) => {
// Remove <name>/index.* if <name>/index.html exists
if (inputPath.endsWith('.html')) return true;
const isIndexFile = /index\..+$/.test(inputPath);
if (!isIndexFile) return true;
const hasIndexHtml = entrypointInfos.some(
(entry) =>
entry.name === name && entry.inputPath.endsWith('index.html'),
);
if (hasIndexHtml) return false;
return true;
});
await wxt.hooks.callHook('entrypoints:found', wxt, entrypointInfos);
+4 -1
View File
@@ -12,7 +12,10 @@ export function getGlobals(
{
name: 'BROWSER',
value: config.browser,
type: `string`,
type:
config.targetBrowsers.length === 0
? 'string'
: config.targetBrowsers.map((browser) => `"${browser}"`).join(' | '),
},
{
name: 'CHROME',
+7 -1
View File
@@ -459,7 +459,13 @@ function discoverIcons(
}
function addDevModeCsp(manifest: Browser.runtime.Manifest): void {
const permission = `http://${wxt.server?.hostname ?? ''}/*`;
let permissonUrl = wxt.server?.origin;
if (permissonUrl) {
const permissionUrlInstance = new URL(permissonUrl);
permissionUrlInstance.port = '';
permissonUrl = permissionUrlInstance.toString();
}
const permission = `${permissonUrl}*`;
const allowedCsp = wxt.server?.origin ?? 'http://localhost:*';
if (manifest.manifest_version === 3) {
@@ -235,6 +235,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
return {
browser,
targetBrowsers: [],
command,
entrypointsDir: fakeDir(),
modulesDir: fakeDir(),
@@ -320,9 +321,9 @@ export const fakeWxt = fakeObjectCreator<Wxt>(() => ({
export const fakeWxtDevServer = fakeObjectCreator<WxtDevServer>(() => ({
currentOutput: fakeBuildOutput(),
hostname: 'localhost',
origin: 'http://localhost:3000',
host: 'localhost',
port: 3000,
origin: 'http://localhost:3000',
reloadContentScript: vi.fn(),
reloadExtension: vi.fn(),
reloadPage: vi.fn(),
@@ -367,9 +368,9 @@ export const fakeManifestCommand = fakeObjectCreator<Browser.commands.Command>(
);
export const fakeDevServer = fakeObjectCreator<WxtDevServer>(() => ({
hostname: 'localhost',
origin: 'http://localhost',
host: 'localhost',
port: 5173,
origin: 'http://localhost:3000',
reloadContentScript: vi.fn(),
reloadExtension: vi.fn(),
reloadPage: vi.fn(),
+71 -7
View File
@@ -50,19 +50,83 @@ function removeUnusedTopLevelVariables(mod: ProxifiedModule): number {
let deletedCount = 0;
const ast = mod.$ast as any;
for (let i = ast.body.length - 1; i >= 0; i--) {
if (ast.body[i].type === 'VariableDeclaration') {
for (let j = ast.body[i].declarations.length - 1; j >= 0; j--) {
if (!usedMap.get(ast.body[i].declarations[j].id.name)) {
ast.body[i].declarations.splice(j, 1);
const isUsed = (id: any) => {
return id?.type === 'Identifier' && usedMap.get(id.name);
};
const cleanArrayPattern = (pattern: any): boolean => {
const elements = pattern.elements;
for (let i = elements.length - 1; i >= 0; i--) {
const el = elements[i];
if (el?.type === 'Identifier' && !isUsed(el)) {
elements.splice(i, 1);
deletedCount++;
}
}
return elements.length === 0;
};
const cleanObjectPattern = (pattern: any): boolean => {
const properties = pattern.properties;
for (let i = properties.length - 1; i >= 0; i--) {
const prop = properties[i];
if (prop.type === 'Property') {
const value = prop.value;
// support nested object
if (value.type === 'ObjectPattern') {
const isEmpty = cleanObjectPattern(value);
if (isEmpty) {
properties.splice(i, 1);
}
} else if (value.type === 'ArrayPattern') {
const isEmpty = cleanArrayPattern(value);
if (isEmpty) {
properties.splice(i, 1);
}
} else if (value.type === 'Identifier' && !isUsed(value)) {
properties.splice(i, 1);
deletedCount++;
}
} else if (prop.type === 'RestElement') {
const arg = prop.argument;
if (arg.type === 'Identifier' && !isUsed(arg)) {
properties.splice(i, 1);
deletedCount++;
}
}
if (ast.body[i].declarations.length === 0) {
ast.body.splice(i, 1);
}
return properties.length === 0;
};
for (let i = ast.body.length - 1; i >= 0; i--) {
if (ast.body[i].type !== 'VariableDeclaration') continue;
for (let j = ast.body[i].declarations.length - 1; j >= 0; j--) {
const id = ast.body[i].declarations[j].id;
let shouldRemove = false;
if (id.type === 'Identifier') {
shouldRemove = !isUsed(id);
if (shouldRemove) deletedCount++;
} else if (id.type === 'ArrayPattern') {
shouldRemove = cleanArrayPattern(id);
} else if (id.type === 'ObjectPattern') {
shouldRemove = cleanObjectPattern(id);
}
if (shouldRemove) {
ast.body[i].declarations.splice(j, 1);
}
}
if (ast.body[i].declarations.length === 0) {
ast.body.splice(i, 1);
}
}
return deletedCount;
}
+3 -3
View File
@@ -26,11 +26,10 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
wxt.logger.info('Zipping extension...');
const zipFiles: string[] = [];
const packageJson = await getPackageJson();
const projectName =
wxt.config.zip.name ??
safeFilename(
(await getPackageJson())?.name || path.basename(process.cwd()),
);
safeFilename(packageJson?.name || path.basename(process.cwd()));
const applyTemplate = (template: string): string =>
template
.replaceAll('{{name}}', projectName)
@@ -39,6 +38,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
'{{version}}',
output.manifest.version_name ?? output.manifest.version,
)
.replaceAll('{{packageVersion}}', packageJson?.version)
.replaceAll('{{mode}}', wxt.config.mode)
.replaceAll('{{manifestVersion}}', `mv${wxt.config.manifestVersion}`);
+28 -6
View File
@@ -108,6 +108,12 @@ export interface InlineConfig {
* "chrome"
*/
browser?: TargetBrowser;
/**
* Target browsers to support. When set, `import.meta.env.BROWSER` will be narrowed to a string literal type containing only the specified browser names.
*
* @default []
*/
targetBrowsers?: TargetBrowser[];
/**
* Explicitly set a manifest version to target. This will override the default manifest version
* for each command, and can be overridden by the command line `--mv2` or `--mv3` option.
@@ -141,6 +147,7 @@ export interface InlineConfig {
*
* - <span v-pre>`{{name}}`</span> - The project's name converted to kebab-case
* - <span v-pre>`{{version}}`</span> - The version_name or version from the manifest
* - <span v-pre>`{{packageVersion}}`</span> - The version from the package.json
* - <span v-pre>`{{browser}}`</span> - The target browser from the `--browser` CLI flag
* - <span v-pre>`{{mode}}`</span> - The current mode
* - <span v-pre>`{{manifestVersion}}`</span> - Either "2" or "3"
@@ -165,6 +172,7 @@ export interface InlineConfig {
*
* - <span v-pre>`{{name}}`</span> - The project's name converted to kebab-case
* - <span v-pre>`{{version}}`</span> - The version_name or version from the manifest
* - <span v-pre>`{{packageVersion}}`</span> - The version from the package.json
* - <span v-pre>`{{browser}}`</span> - The target browser from the `--browser` CLI flag
* - <span v-pre>`{{mode}}`</span> - The current mode
* - <span v-pre>`{{manifestVersion}}`</span> - Either "2" or "3"
@@ -319,14 +327,26 @@ export interface InlineConfig {
*/
dev?: {
server?: {
/**
* Host to bind the dev server to.
*
* @default "localhost"
*/
host?: string;
/**
* Port to run the dev server on. Defaults to the first open port from 3000 to 3010.
*/
port?: number;
/**
* Origin to use to connect from the extension ui runtime to the dev server.
*
* @default "http://localhost:3000"
*/
origin?: string;
/**
* Hostname to run the dev server on.
*
* @default "localhost"
* @deprecated use `host` to specify the interface to bind to, or use `origin` to specify the dev server hostname.
*/
hostname?: string;
};
@@ -1067,14 +1087,14 @@ export interface WxtBuilderServer {
}
export interface ServerInfo {
/**
* Ex: `"localhost"`
*/
host: string;
/**
* Ex: `3000`
*/
port: number;
/**
* Ex: `"localhost"`
*/
hostname: string;
/**
* Ex: `"http://localhost:3000"`
*/
@@ -1299,6 +1319,7 @@ export interface ResolvedConfig {
mode: string;
command: WxtCommand;
browser: TargetBrowser;
targetBrowsers: TargetBrowser[];
manifestVersion: TargetManifestVersion;
env: ConfigEnv;
logger: Logger;
@@ -1343,8 +1364,9 @@ export interface ResolvedConfig {
dev: {
/** Only defined during dev command */
server?: {
host: string;
port: number;
hostname: string;
origin: string;
/**
* The milliseconds to debounce when a file is saved before reloading.
* The only way to set this option is to set the `WXT_WATCH_DEBOUNCE`
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@ import type { ContentScriptUi, ContentScriptUiOptions } from './types';
import { createIsolatedElement } from '@webext-core/isolated-element';
import { applyPosition, createMountFunctions, mountUi } from './shared';
import { logger } from '../internal/logger';
import { splitShadowRootCss } from '../split-shadow-root-css';
/**
* Create a content script UI inside a [`ShadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot).
@@ -17,6 +18,7 @@ export async function createShadowRootUi<TMounted>(
ctx: ContentScriptContext,
options: ShadowRootContentScriptUiOptions<TMounted>,
): Promise<ShadowRootContentScriptUi<TMounted>> {
const instanceId = crypto.randomUUID();
const css: string[] = [];
if (!options.inheritStyles) {
@@ -31,6 +33,9 @@ export async function createShadowRootUi<TMounted>(
css.push(entryCss.replaceAll(':root', ':host'));
}
// Some rules must be applied outside the shadow root, so split the CSS apart
const { shadowCss, documentCss } = splitShadowRootCss(css.join('\n').trim());
const {
isolatedElement: uiContainer,
parentElement: shadowHost,
@@ -38,7 +43,7 @@ export async function createShadowRootUi<TMounted>(
} = await createIsolatedElement({
name: options.name,
css: {
textContent: css.join('\n').trim(),
textContent: shadowCss,
},
mode: options.mode ?? 'open',
isolateEvents: options.isolateEvents,
@@ -51,6 +56,20 @@ export async function createShadowRootUi<TMounted>(
// Add shadow root element to DOM
mountUi(shadowHost, options);
applyPosition(shadowHost, shadow.querySelector('html'), options);
// Add document CSS
if (
documentCss &&
!document.querySelector(
`style[wxt-shadow-root-document-styles="${instanceId}"]`,
)
) {
const style = document.createElement('style');
style.textContent = documentCss;
style.setAttribute('wxt-shadow-root-document-styles', instanceId);
(document.head ?? document.body).append(style);
}
// Mount UI inside shadow root
mounted = options.onMount(uiContainer, shadow, shadowHost);
};
@@ -58,11 +77,20 @@ export async function createShadowRootUi<TMounted>(
const remove = () => {
// Cleanup mounted state
options.onRemove?.(mounted);
// Detach shadow root from DOM
shadowHost.remove();
// Remove document CSS
const documentStyle = document.querySelector(
`style[wxt-shadow-root-document-styles="${instanceId}"]`,
);
documentStyle?.remove();
// Remove children from uiContainer
while (uiContainer.lastChild)
uiContainer.removeChild(uiContainer.lastChild);
// Clear mounted value
mounted = undefined;
};
@@ -9,6 +9,9 @@ export type ContentScriptUiOptions<TMounted> = ContentScriptPositioningOptions &
/**
* Callback called before the UI is removed from the webpage. Use to cleanup your UI, like
* unmounting your Vue or React apps.
*
* Note that this callback is called only when `ui.remove` is called - that means it is
* not called automatically when the anchor is removed, unless you use `autoMount`.
*/
onRemove?: (mounted: TMounted | undefined) => void;
};
@@ -40,7 +40,7 @@ export function getDevServerWebSocket(): WxtWebSocket {
);
if (ws == null) {
const serverUrl = `${__DEV_SERVER_PROTOCOL__}//${__DEV_SERVER_HOSTNAME__}:${__DEV_SERVER_PORT__}`;
const serverUrl = __DEV_SERVER_ORIGIN__;
logger.debug('Connecting to dev server @', serverUrl);
ws = new WebSocket(serverUrl, 'vite-hmr') as WxtWebSocket;
@@ -0,0 +1,25 @@
/**
* Given a CSS string that will be loaded into a shadow root, split it into two parts:
* - `documentCss`: CSS that needs to be applied to the document (like `@property`)
* - `shadowCss`: CSS that needs to be applied to the shadow root
* @param css
*/
export function splitShadowRootCss(css: string): {
documentCss: string;
shadowCss: string;
} {
let shadowCss = css;
let documentCss = '';
const rulesRegex = /(\s*@property[\s\S]*?{[\s\S]*?})/gm;
let match;
while ((match = rulesRegex.exec(css)) !== null) {
documentCss += match[1];
shadowCss = shadowCss.replace(match[1], '');
}
return {
documentCss: documentCss.trim(),
shadowCss: shadowCss.trim(),
};
}
+1 -1
View File
@@ -14,8 +14,8 @@
"src/utils/define-unlisted-script.ts",
"src/utils/define-wxt-plugin.ts",
"src/utils/match-patterns.ts",
"src/utils/split-shadow-root-css.ts",
"src/utils/storage.ts",
"src/browser.ts",
"src/testing/index.ts",
"src/modules.ts"
]
+791 -1015
View File
File diff suppressed because it is too large Load Diff
+43 -43
View File
@@ -13,97 +13,97 @@ catalog:
'@aklinker1/buildc': ^1.1.4
'@aklinker1/check': ^1.4.5
'@aklinker1/rollup-plugin-visualizer': 5.12.0
'@commitlint/config-conventional': ^19.7.1
'@commitlint/types': ^19.5.0
'@faker-js/faker': ^9.2.0
'@commitlint/config-conventional': ^19.8.0
'@commitlint/types': ^19.8.0
'@faker-js/faker': ^9.6.0
'@sveltejs/vite-plugin-svelte': ^4.0.0 || ^5.0.0
'@types/chrome': ^0.0.280
'@types/chrome': ^0.0.313
'@types/fs-extra': ^11.0.4
'@types/lodash.merge': ^4.6.9
'@types/node': ^20.17.6
'@types/normalize-path': ^3.0.2
'@types/prompts': ^2.4.9
'@types/react': ^19.0.1
'@types/react-dom': ^19.0.2
'@types/react': ^19.1.0
'@types/react-dom': ^19.1.2
'@types/ua-parser-js': ^0.7.39
'@types/webextension-polyfill': ^0.12.1
'@types/webextension-polyfill': ^0.12.3
'@vitejs/plugin-react': ^4.3.4
'@vitejs/plugin-vue': ^5.2.1
'@vitest/coverage-v8': ^3.0.7
'@webext-core/fake-browser': ^1.3.1
'@vitejs/plugin-vue': ^5.2.3
'@vitest/coverage-v8': ^3.1.1
'@webext-core/fake-browser': ^1.3.2
'@webext-core/isolated-element': ^1.1.2
'@webext-core/match-patterns': ^1.0.3
async-mutex: ^0.5.0
c12: ^3.0.2
c12: ^3.0.3
cac: ^6.7.14
changelogen: ^0.6.1
chokidar: ^4.0.3
ci-info: ^4.1.0
confbox: ^0.1.8 || ^0.2.0
consola: ^3.2.3
ci-info: ^4.2.0
confbox: ^0.1.8 || ^0.2.2
consola: ^3.4.2
defu: ^6.1.4
dequal: ^2.0.3
dotenv: ^16.4.5
dotenv: ^16.4.7
dotenv-expand: ^12.0.1
esbuild: ^0.25.0
extract-zip: ^2.0.1
fast-glob: ^3.3.2
fast-glob: ^3.3.3
feed: ^4.2.2
filesize: ^10.1.6
fs-extra: ^11.2.0
fs-extra: ^11.3.0
get-port-please: ^3.1.2
giget: ^1.2.3 || ^2.0.0
happy-dom: ^17.1.8
happy-dom: ^17.4.4
hookable: ^5.5.3
import-meta-resolve: ^4.1.0
is-wsl: ^3.1.0
json5: ^2.2.3
jszip: ^3.10.1
linkedom: ^0.18.5
lint-staged: ^15.2.10
linkedom: ^0.18.9
lint-staged: ^15.5.0
lodash.merge: ^4.6.2
magicast: ^0.3.5
markdown-it-footnote: ^4.0.0
minimatch: ^10.0.1
nano-spawn: ^0.2.0
normalize-path: ^3.0.0
nypm: ^0.3.12
ohash: ^1.1.4
nypm: ^0.6.0
ohash: ^2.0.11
open: ^10.1.0
ora: ^8.1.1
oxlint: ^0.11.1
ora: ^8.2.0
oxlint: ^0.16.5
perfect-debounce: ^1.0.0
picocolors: ^1.1.1
prettier: ^3.3.3
prettier: ^3.5.3
prompts: ^2.4.2
publint: ^0.2.12
publint: ^0.3.10
publish-browser-extension: ^2.3.0 || ^3.0.0
react: ^19.0.0
react-dom: ^19.0.0
sass: ^1.80.7
react: ^19.1.0
react-dom: ^19.1.0
sass: ^1.86.3
scule: ^1.3.0
sharp: ^0.33.5
simple-git-hooks: ^2.11.1
solid-js: ^1.9.4
sharp: ^0.34.1
simple-git-hooks: ^2.12.1
solid-js: ^1.9.5
tsx: 4.19.3
typedoc: ^0.25.4
typedoc-plugin-frontmatter: ^1.1.0
typedoc-plugin-frontmatter: ^1.3.0
typedoc-plugin-markdown: 4.0.0-next.23
typedoc-vitepress-theme: 1.0.0-next.3
typescript: ^5.6.3
typescript: ^5.8.3
ua-parser-js: ^1.0.40
unbuild: ^3.5.0
unimport: ^3.13.1 || ^4.0.0
unocss: ^0.64.0 || ^0.65.0 || ^65.0.0 ||^66.0.0
vite: ^5.0.0 || ^6.0.0
unimport: ^3.13.1 || ^4.0.0 || ^5.0.0
unocss: ^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0
vite: ^5.4.17 || ^6.2.5
vite-node: ^2.1.4 || ^3.0.0
vite-plugin-solid: ^2.11.6
vitepress: ^1.6.3
vitepress-knowledge: ^0.4.0
vitepress-plugin-group-icons: ^1.3.8
vitest: ^3.0.7
vitest-mock-extended: ^3.0.1
vitepress-knowledge: ^0.4.1
vitepress-plugin-group-icons: ^1.4.1
vitest: ^3.1.1
vitest-mock-extended: ^3.1.0
vitest-plugin-random-seed: ^1.1.1
vue: ^3.5.12
web-ext-run: ^0.2.1
vue: ^3.5.13
web-ext-run: ^0.2.2
webextension-polyfill: ^0.12.0
+1 -1
View File
@@ -58,7 +58,7 @@ let versionChangelogBody = versionChangelog
if (originalBumpType === 'major') {
versionChangelogBody = versionChangelogBody.replace(
'[compare changes]',
`[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes]`,
`[⚠️ breaking changes](https://wxt.dev/guide/resources/upgrading.html) &bull; [compare changes]`,
);
}
const { releases: prevReleases } = await fs
+2
View File
@@ -9,5 +9,7 @@ export default {
'esbuild',
// Maintained manually to match min-node version
'@types/node',
// License changed in newer versions
'ua-parser-js',
],
};
+7 -7
View File
@@ -15,14 +15,14 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"devDependencies": {
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.2",
"@wxt-dev/module-react": "^1.1.2",
"typescript": "^5.6.3",
"wxt": "^0.19.29"
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.2",
"@wxt-dev/module-react": "^1.1.3",
"typescript": "^5.8.3",
"wxt": "^0.20.0"
}
}
+4 -4
View File
@@ -15,11 +15,11 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"solid-js": "^1.9.3"
"solid-js": "^1.9.5"
},
"devDependencies": {
"@wxt-dev/module-solid": "^1.1.2",
"typescript": "^5.6.3",
"wxt": "^0.19.29"
"@wxt-dev/module-solid": "^1.1.3",
"typescript": "^5.8.3",
"wxt": "^0.20.0"
}
}
+5 -5
View File
@@ -16,11 +16,11 @@
},
"devDependencies": {
"@tsconfig/svelte": "^5.0.4",
"@wxt-dev/module-svelte": "^2.0.0",
"svelte": "^5.1.16",
"svelte-check": "^4.0.7",
"@wxt-dev/module-svelte": "^2.0.3",
"svelte": "^5.25.9",
"svelte-check": "^4.1.5",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"wxt": "^0.19.29"
"typescript": "^5.8.3",
"wxt": "^0.20.0"
}
}

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Before

Width:  |  Height:  |  Size: 559 B

After

Width:  |  Height:  |  Size: 559 B

Before

Width:  |  Height:  |  Size: 916 B

After

Width:  |  Height:  |  Size: 916 B

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

+2 -2
View File
@@ -15,7 +15,7 @@
"postinstall": "wxt prepare"
},
"devDependencies": {
"typescript": "^5.6.3",
"wxt": "^0.19.29"
"typescript": "^5.8.3",
"wxt": "^0.20.0"
}
}
+4 -4
View File
@@ -15,12 +15,12 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"vue": "^3.5.12"
"vue": "^3.5.13"
},
"devDependencies": {
"@wxt-dev/module-vue": "^1.0.1",
"@wxt-dev/module-vue": "^1.0.2",
"typescript": "5.6.3",
"vue-tsc": "^2.1.10",
"wxt": "^0.19.29"
"vue-tsc": "^2.2.8",
"wxt": "^0.20.0"
}
}