Compare commits

...

50 Commits

Author SHA1 Message Date
GitHub Actions e632a2964a chore(release): @wxt-dev/module-react v1.1.1 2024-08-06 01:55:29 +00:00
GitHub Actions f736a14651 chore(release): @wxt-dev/auto-icons v1.0.2 2024-08-05 23:52:51 +00:00
uncenter a8b0d40959 docs(auto-icons): fix configuration example typo (#905) 2024-08-05 18:48:54 -05:00
Aaron 4c5504d139 Update changelog 2024-08-04 10:18:02 -05:00
GitHub Actions fb86deb388 chore(release): wxt v0.19.4 2024-08-04 14:31:30 +00:00
Aaron 3a8e6135ac fix: Fix createIframeUi page option types 2024-08-04 09:27:46 -05:00
Aaron a38de0c03f feat: Add injectScript helper (#900) 2024-08-04 09:16:46 -05:00
Aaron ea97410fb4 fix(types): PublicPath type resolution with extensionApi: "chrome" (#901) 2024-08-04 08:59:45 -05:00
hikiko4ern e36549ddce fix: do not clear .wxt/tsconfig.json in findEntrypoints if it exists (#898) 2024-08-03 21:59:12 -05:00
KnightYoshi 10851ae01c docs: add missing extension-apis segment (#896) 2024-08-03 19:28:53 -05:00
GitHub Actions d553ef6b29 chore(release): wxt v0.19.3 2024-08-02 21:48:05 +00:00
Aaron 15bf0da82e fix(content-script-context): Fix initialization logic for Firefox (#895) 2024-08-02 16:43:40 -05:00
hikiko4ern fc246ffaac fix: add consola to wxt dependencies (#892) 2024-08-01 19:05:28 -05:00
Florian Metz 9785eff21e chore: add more metadata for npm (#885) 2024-08-01 18:00:00 -05:00
Himanshu Patil 52fbd2c99e docs: Improve prepare:types hook JSDoc (#886) 2024-08-01 17:11:20 -05:00
GitHub Actions 648ae4fb8d chore(release): @wxt-dev/auto-icons v1.0.1 2024-07-30 18:23:12 +00:00
Aaron Klinker bbeeabc9ea Revert version change for auto-icons module 2024-07-30 13:18:35 -05:00
web-dahuyou 35cf6e7bc0 docs: Add "NiceTab" to homepage (#882) 2024-07-30 13:12:00 -05:00
Florian Metz 78822acdac fix(auto-icons): path option (#880)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-07-30 13:06:41 -05:00
不游 aea123890f docs: Add "1Proompt" to homepage (#879) 2024-07-29 07:50:28 -05:00
Matt Strayer c369f4a955 docs: Add "mindful" to homepage (#878) 2024-07-28 16:33:58 -05:00
GitHub Actions 16d8240406 chore(release): wxt v0.19.2 2024-07-28 15:49:52 +00:00
Aaron 24cd01dd31 fix: Don't default to dev mode for production builds when using vite-node loader (#877) 2024-07-28 10:45:32 -05:00
Aaron fb22beadb8 fix: Remove unused top-level functions and variables when loading entrypoints with the vite-node loader (#875) 2024-07-28 09:27:29 -05:00
Aaron Klinker fa85177648 docs(modules): Adjust wording 2024-07-27 17:04:36 -05:00
Aaron Klinker ed07a49dd2 docs: Update README and homepage features with WXT modules 2024-07-27 16:57:33 -05:00
Aaron 09c9de0a60 docs: Fix environment variable reference (#870) 2024-07-27 16:15:28 -05:00
Aaron 523296b29a chore(deps): Upgrade all dependencies (#869) 2024-07-27 16:07:50 -05:00
Aaron Klinker ef67ef47aa chore: Update template package names 2024-07-27 15:37:48 -05:00
Aaron Klinker cb5eb68d62 Fix changelog 2024-07-27 15:11:27 -05:00
GitHub Actions 2e9ab75e2b chore(release): wxt v0.19.1 2024-07-27 20:09:05 +00:00
Aaron 0c2d795800 pref: Tree-shake unrelated code inside vite-node entrypoint loader (#867) 2024-07-27 15:03:23 -05:00
Aaron 97aa173f7e fix: Define web globals when importing entrypoints (#865) 2024-07-27 14:48:55 -05:00
Aaron 9eb64d94d6 docs: Add README for auto-icons module 2024-07-26 14:00:37 -05:00
Florian Metz a0971cab52 feat: Auto Icons Module (#851)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-07-25 23:41:53 -05:00
Aaron 127562e9d0 ci: Fix breaking changes link generation in changelog 2024-07-25 22:48:05 -05:00
Aaron 038e7450e0 Fix diff 2024-07-25 22:40:33 -05:00
Aaron 6c00763187 docs: Add missing ssr.noExternal docs to vite-node loader 2024-07-25 22:38:21 -05:00
Aaron 2da0f3d686 Fix changelog 2024-07-25 22:14:06 -05:00
GitHub Actions 742d2f1536 chore(release): wxt v0.19.0 2024-07-26 01:03:48 +00:00
Aaron b4b69783d5 docs: Add upgrade guide and breaking changes (#860) 2024-07-25 19:59:15 -05:00
Aaron c8ddc66cfa feat!: Use vite-node to load entrypoints by default (#859)
BREAKING CHANGE: Switch from using `jiti` to import entrypoints during the build process by default to `vite-node`.

To continue using `jiti`, add the following to your `wxt.config.ts` file:

```ts
export default defineConfig({
  entrypointLoader: "jiti",
})
```

NOTE: "jiti" is deprecated and will be removed in the next major version, `v0.20.0`.
2024-07-25 17:25:46 -05:00
Aaron 2b6ff8d30f docs: Update labels in content script UI positioning screenshot 2024-07-25 11:45:49 -05:00
Aaron d79d7ed778 feat(hooks): Add prepare:publicPaths hook (#858) 2024-07-25 11:32:16 -05:00
Aaron Klinker ab234c0f45 docs: Review full docs, fix typos, re-order sections 2024-07-25 00:10:04 -05:00
Aaron Klinker eeb43c6bf4 docs: Fix typo 2024-07-24 23:22:14 -05:00
Aaron 17bda1eb34 feat(storage): init option and rename defaultValue to fallback (#827) 2024-07-24 23:18:16 -05:00
Aaron 96e28c415c docs: Extract comparison comments to footnotes (#853) 2024-07-24 23:17:01 -05:00
Florian Metz 1f9f6d9681 chore: update readme (#852) 2024-07-24 21:11:28 -05:00
Aaron f1bb7cb0c2 feat(experimental): First-class support for excluding webextension-polyfill (#847) 2024-07-24 17:09:56 -05:00
110 changed files with 7445 additions and 4842 deletions
+1
View File
@@ -12,6 +12,7 @@ on:
- module-vue
- module-svelte
- module-solid
- auto-icons
jobs:
validate:
+2 -1
View File
@@ -18,7 +18,7 @@
<br/>
<span>⚡</span>
<br/>
<q><i>It's like Nuxt, but for Chrome Extensions</i></q>
<q><i>It's like Nuxt, but for Web Extensions</i></q>
</p>
<p align="center">
@@ -59,6 +59,7 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
- 🦾 Auto-imports
- 🤖 Automated publishing
- 🎨 Frontend framework agnostic: works with Vue, React, Svelte, etc
- 📦 Modular architecture with [WXT modules](https://wxt.dev/guide/go-further/reusable-modules.html#overview)
- 🖍️ Quickly bootstrap a new project
- 📏 Bundle analysis
- ⬇️ Download and bundle remote URL imports
@@ -35,6 +35,9 @@ const chromeExtensionIds = [
'bcpgdpedphodjcjlminjbdeejccjbimp', // 汇率转换-中文版本
'loeilaonggnalkaiiaepbegccilkmjjp', // Currency Converter Plus
'npcnninnjghigjfiecefheeibomjpkak', // Respond Easy
'cfkdcideecefncbglkhneoflfnmhoicc', // mindful - stay focused on your goals
'lnhejcpclabmbgpiiomjbhalblnnbffg', // 1Proompt
'fonflmjnjbkigocpoommgmhljdpljain', // NiceTab - https://github.com/web-dahuyou/NiceTab
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
+4 -1
View File
@@ -142,9 +142,12 @@ export default defineConfig({
menuItem('Custom Events', 'custom-events'),
menuItem('Reusable Modules', 'reusable-modules'),
menuItem('Remote Code', 'remote-code'),
menuItem('Entrypoint Side Effects', 'entrypoint-side-effects'),
menuItem('Entrypoint Loaders', 'entrypoint-loaders'),
menuItem('How WXT Works', 'how-wxt-works'),
]),
menuGroup('Upgrade Guide', '/guide/upgrade-guide/', [
menuItem('wxt', 'wxt'),
]),
]),
'/api/': menuRoot([
menuGroup('CLI', '/api/cli/', [
+41 -27
View File
@@ -4,30 +4,44 @@ Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework)
## Overview
| Features | WXT | Plasmo | CRXJS |
| ---------------------------------------------------- | :-------------------------: | :--------------------------------------: | :-------------------------------------: |
| Supports all browsers | | ✅ | ✅ As of `v2.0.0-beta.23` |
| MV2 Support | | ✅ | 🟡 Either MV2 or MV3 |
| MV3 Support | | ✅ | 🟡 Either MV2 or MV3 |
| Create Extension ZIPs | ✅ | | |
| Create Firefox Sources ZIP | ✅ | | |
| First-class TypeScript support | ✅ | | |
| Entrypoint discovery | File based | File based | ❌ |
| Inline entrypoint config | | ✅ | Manifest based |
| Auto-imports | ✅ | | |
| Supports all frontend frameworks | ✅ | 🟡 Only React, Vue, and Svelte | ✅ |
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 `.html` `.ts` `.tsx` | ✅ `.html` `.ts` `.tsx` `.vue` `.svelte` | ❌ |
| Automated publishing | ✅ | | |
| Remote Code Bundling (Google Analytics) | ✅ | | |
| <strong style="opacity: 50%">Dev Mode</strong> | | |
| `.env` Files | ✅ | | |
| Opens browser and install extension | | ❌ | ❌ |
| HMR for UIs | ✅ | 🟡 React only | ✅ |
| Reload HTML Files on Change | | 🟡 Reloads entire extension | ✅ |
| Reload Content Scripts on Change | | 🟡 Reloads entire extension | ✅ |
| Reload Background on Change | 🟡 Reloads entire extension | 🟡 Reloads entire extension | 🟡 Reloads entire extension |
| Respects Content Script `run_at` | | ✅ | ❌ ESM-style loaders run asynchronously |
| <strong style="opacity: 50%">Built-in Utils</strong> | | | |
| Storage | | ✅ | ❌ |
| Messaging | ❌ | | ❌ |
| Content Script UI | | ✅ | ❌ |
| Features | WXT | Plasmo | CRXJS |
| ---------------------------------------------------- | :--------------: | :-------------: | :--------------: |
| Supports all browsers | | ✅ | 🟡 <sup>10</sup> |
| MV2 Support | | ✅ | 🟡 <sup>1</sup> |
| MV3 Support | | ✅ | 🟡 <sup>1</sup> |
| Create Extension ZIPs | ✅ | ✅ | |
| Create Firefox Sources ZIP | ✅ | ❌ | |
| First-class TypeScript support | ✅ | ✅ | |
| Entrypoint discovery | ✅ <sup>2</sup> | ✅ <sup>2</sup> | ❌ |
| Inline entrypoint config | | ✅ | ❌ <sup>9</sup> |
| Auto-imports | ✅ | ❌ | |
| Supports all frontend frameworks | ✅ | 🟡 <sup>3</sup> | ✅ |
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 <sup>4</sup> | ✅ <sup>5</sup> | ❌ |
| Automated publishing | ✅ | ✅ | |
| Remote Code Bundling (Google Analytics) | ✅ | ✅ | |
| <strong style="opacity: 50%">Dev Mode</strong> | | |
| `.env` Files | ✅ | ✅ | |
| Opens browser with extension installed | | ❌ | ❌ |
| HMR for UIs | ✅ | 🟡 <sup>6</sup> | ✅ |
| Reload HTML Files on Change | | 🟡 <sup>7</sup> | ✅ |
| Reload Content Scripts on Change | | 🟡 <sup>7</sup> | ✅ |
| Reload Background on Change | 🟡 <sup>7</sup> | 🟡 <sup>7</sup> | 🟡 <sup>7</sup> |
| Respects Content Script `run_at` | | ✅ | ❌ <sup>8</sup> |
| <strong style="opacity: 50%">Built-in Utils</strong> | | | |
| Storage | | ✅ | ❌ <sup>11</sup> |
| Messaging | ❌ <sup>11</sup> || ❌ <sup>11</sup> |
| Content Script UI | | ✅ | ❌ <sup>11</sup> |
<small>
<sup>1</sup>: Either MV2 or MV3, not both.
<br/><sup>2</sup>: File based.
<br/><sup>3</sup>: Only React, Vue, and Svelte.
<br/><sup>4</sup>: <code>.html</code> <code>.ts</code> <code>.tsx</code>.
<br/><sup>5</sup>: <code>.html</code> <code>.ts</code> <code>.tsx</code>. <code>.vue</code> <code>.svelte</code>.
<br/><sup>6</sup>: React only.
<br/><sup>7</sup>: Reloads entire extension.
<br/><sup>8</sup>: ESM-style loaders run asynchronously.
<br/><sup>9</sup>: Entrypoint options all configured in `manifest.json`.
<br/><sup>10</sup>: As of <code>v2.0.0-beta.23</code>, but v2 stable hasn't been released yet.
<br/><sup>11</sup>: There is no built-in wrapper around this API. However, you can still access the standard APIs via <code>chrome</code>/<code>browser</code> globals or use any 3rd party NPM package.
</small>
+1 -1
View File
@@ -11,7 +11,7 @@ head:
WXT is a free and open source framework for building web extensions in an conventional, intuitive, and safe way **_for all browsers_**.
WXT is based of [Nuxt](https://nuxt.com), and aims to provide the same great DX with TypeScript, auto-imports, and an opinionated project structure.
WXT is based on [Nuxt](https://nuxt.com), and aims to provide the same great DX with TypeScript, auto-imports, and an opinionated project structure.
![Example build output](../assets/cli-output.png)
+2 -2
View File
@@ -176,6 +176,8 @@ bun zip:firefox
:::
Ensure that you have a `README.md` or `SOURCE_CODE_REVIEW.md` file with the above commands so that the Firefox team knows how to build your extension.
Make sure the build output is the exact same when running `wxt build -b firefox` in your main project and inside the zipped sources.
:::warning
@@ -184,8 +186,6 @@ If you use a `.env` files, they can effect the chunk hashes in the output direct
See Issue [#377](https://github.com/wxt-dev/wxt/issues/377) for more details.
:::
Ensure that you have a `README.md` or `SOURCE_CODE_REVIEW.md` file with the above commands so that the Firefox team knows how to build your extension.
#### Private Packages
If you use private packages and you don't want to provide your auth token to the Firefox team during the review process, you can use `zip.downloadPackages` to download any private packages and include them in the zip.
+3 -3
View File
@@ -53,9 +53,9 @@ declare module 'wxt/sandbox' {
}
export default defineAppConfig({
bugReportingDisabled: process.env.VITE_BUG_REPORTING_DISABLED === 'true',
apiKey: process.env.VITE_API_KEY,
bugReportingDisabled: import.meta.env.VITE_BUG_REPORTING_DISABLED === 'true',
apiKey: import.meta.env.VITE_API_KEY,
});
```
> You don't have to do this, you can use `process.env.VITE_*` anywhere in your runtime code, but putting them here consolidates them to one place and defines what variables are expected.
> You don't have to do this, you can use `import.meta.env.VITE_*` anywhere in your runtime code, but putting them here consolidates them to one place and defines what variables are expected.
+38 -6
View File
@@ -121,7 +121,7 @@ Storage items contain the same APIs as the `storage` variable, but you can confi
const showChangelogOnUpdate = storage.defineItem<boolean>(
'local:showChangelogOnUpdate',
{
defaultValue: true,
fallback: true,
},
);
```
@@ -153,7 +153,7 @@ type IgnoredWebsiteV1 = string;
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV1[]>(
'local:ignoredWebsites',
{
defaultValue: [],
fallback: [],
version: 1,
},
);
@@ -173,7 +173,7 @@ export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV1[]>( // [!code
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( // [!code ++]
'local:ignoredWebsites',
{
defaultValue: [],
fallback: [],
version: 1, // [!code --]
version: 2, // [!code ++]
migrations: { // [!code ++]
@@ -205,7 +205,7 @@ export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( // [!code
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV3[]>( // [!code ++]
'local:ignoredWebsites',
{
defaultValue: [],
fallback: [],
version: 2, // [!code --]
version: 3, // [!code ++]
migrations: {
@@ -242,7 +242,7 @@ Lets look at the same ignored websites example from before, but start with an un
export const ignoredWebsites = storage.defineItem<string[]>(
'local:ignoredWebsites',
{
defaultValue: [],
fallback: [],
},
);
```
@@ -262,7 +262,7 @@ export const ignoredWebsites = storage.defineItem<string[]>( // [!code --]
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( // [!code ++]
'local:ignoredWebsites',
{
defaultValue: [],
fallback: [],
version: 2, // [!code ++]
migrations: { // [!code ++]
// Ran when migrating from v1 to v2 // [!code ++]
@@ -279,3 +279,35 @@ export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV2[]>( // [!code
### Running Migrations
As soon as `storage.defineItem` is called, WXT checks if migrations need to be ran, and if so, runs them. Calls to get or update the storage item's value or metadata (`getValue`, `setValue`, `removeValue`, `getMeta`, etc) will automatically wait for the migration process to finish before actually reading or writing values.
### Default Values
With `storage.defineItem`, there are multiple ways of defining default values:
1. `fallback` - Return this value from `getValue` instead of `null` if the value is missing.
This option is great for providing default values for settings:
```ts
const theme = storage.defineItem('local:theme', {
fallback: 'dark',
});
const allowEditing = storage.defineItem('local:allow-editing', {
fallback: true,
});
```
2. `init` - Initialize and save a value in storage if it is not already saved.
This is great for values that need to be initialized or set once:
```ts
const userId = storage.defineItem('local:user-id', {
init: () => globalThis.crypto.randomUUID(),
});
const installDate = storage.defineItem('local:install-date', {
init: () => new Date().getTime(),
});
```
The value is initialized in storage immediately.
+120
View File
@@ -0,0 +1,120 @@
# Entrypoint Loaders
Because entrypoint options, like content script `matches`, are listed in the entrypoint's JS file, WXT has to import them during the build process to use those options when generating the manifest.
There are two options for loading your entrypoints:
1. `vite-node` - default as of `v0.19.0`
2. `jiti` (**DEPRECATED, will be removed in `v0.20.0`**) - Default before `v0.19.0`
## vite-node
Since 0.19.0, WXT uses `vite-node`, the same tool that powers Vitest and Nuxt, to import your entrypoint files.
If you use any runtime packages that depend on `webextension-polyfill`, you need to add them to [Vite's `ssr.noExternal` option](https://vitejs.dev/config/ssr-options#ssr-noexternal):
```ts
export default defineConfig({
vite: () => ({
ssr: {
noExternal: ['@webext-core/messaging', '@webext-core/proxy-service'],
},
}),
});
```
:::details Why?
This tells Vite it needs process these module's, letting WXT properly disable the polyfill in the NodeJS environment so it doesn't cause any build errors like this:
```
ERROR This script should only be loaded in a browser extension
```
:::
To get a list of installed packages that use on `webextension-polyfill`, run your package manager's `list` command. Here's an example with PNPM:
```sh
$ pnpm why webextension-polyfill
dependencies:
@webext-core/messaging 1.4.0
└── webextension-polyfill 0.10.0
@webext-core/proxy-service 1.2.0
├─┬ @webext-core/messaging 1.4.0 peer
│ └── webextension-polyfill 0.10.0
└── webextension-polyfill 0.12.0 peer
devDependencies:
@wxt-dev/module-vue 1.0.0
└─┬ wxt 0.19.0-alpha1 peer
└── webextension-polyfill 0.12.0
webextension-polyfill 0.12.0
wxt 0.19.0-alpha1
└── webextension-polyfill 0.12.0
```
Ignoring WXT itself (it's added automatically for you), there are three packages that depend on the polyfill: `@wxt-dev/module-vue`, `@webext-core/messaging`, and `@webext-core/proxy-service`. Since the vue module is a build dependency, with no runtime code, you don't have to add it. That means for this case, you need to add `@webext-core/messaging`, and `@webext-core/proxy-service`, as shown in the original code snippet.
## jiti
The original method WXT used to import TS files. However, because it doesn't support vite plugins like `vite-node`, there is one main caveot to it's usage: **_module side-effects_**.
To enable `jiti`:
```ts
export default defineConfig({
entrypointLoader: 'jiti',
});
```
You cannot use imported variables outside the `main` function in JS entrypoints. This includes options, as shown below:
```ts
// entrypoints/content.ts
import { GOOGLE_MATCHES } from '~/utils/match-patterns';
export default defineContentScript({
matches: GOOGLE_MATCHES,
main() {
// ...
},
});
```
```
$ wxt build
wxt build
WXT 0.14.1
Building chrome-mv3 for production with Vite 5.0.5
✖ Command failed after 360 ms
[8:55:54 AM] ERROR entrypoints/content.ts: Cannot use imported variable "GOOGLE_MATCHES" before main function. See https://wxt.dev/guide/entrypoints.html#side-effects
```
This throws an error because WXT needs to import each entrypoint during the build process to extract its definition (containing the `match`, `runAt`, `include`/`exclude`, etc.) to render the `manifest.json` correctly. Before loading an entrypoint, a transformation is applied to remove all imports. This prevents imported modules (local or NPM) with side-effects from running during the build process, potentially throwing an error.
:::details Why?
When importing your entrypoint to get its definition, the file is imported in a **_node environment_**, and doesn't have access to the `window`, `chrome`, or `browser` globals a web extension usually has access to. If WXT doesn't remove all the imports from the file, the imported modules could try and access one of these variables, throwing an error.
:::
:::warning
See [`wxt-dev/wxt#336`](https://github.com/wxt-dev/wxt/issues/336) to track the status of this bug.
:::
Usually, this error occurs when you try to extract options into a shared file or try to run code outside the `main` function. To fix the example from above, use literal values when defining an entrypoint instead of importing them:
```ts
import { GOOGLE_MATCHES } from '~/utils/match-patterns'; // [!code --]
export default defineContentScript({
matches: GOOGLE_MATCHES, // [!code --]
matches: ['*//*.google.com/*'], // [!code ++]
main() {
// ...
},
});
```
@@ -1,52 +0,0 @@
# Entrypoint Side Effects
You cannot use imported variables outside the `main` function in JS entrypoints. This includes options, as shown below:
```ts
// entrypoints/content.ts
import { GOOGLE_MATCHES } from '~/utils/match-patterns';
export default defineContentScript({
matches: GOOGLE_MATCHES,
main() {
// ...
},
});
```
```
$ wxt build
wxt build
WXT 0.14.1
Building chrome-mv3 for production with Vite 5.0.5
✖ Command failed after 360 ms
[8:55:54 AM] ERROR entrypoints/content.ts: Cannot use imported variable "GOOGLE_MATCHES" before main function. See https://wxt.dev/guide/entrypoints.html#side-effects
```
This throws an error because WXT needs to import each entrypoint during the build process to extract its definition (containing the `match`, `runAt`, `include`/`exclude`, etc.) to render the `manifest.json` correctly. Before loading an entrypoint, a transformation is applied to remove all imports. This prevents imported modules (local or NPM) with side-effects from running during the build process, potentially throwing an error.
:::details Why?
When importing your entrypoint to get its definition, the file is imported in a **_node environment_**, and doesn't have access to the `window`, `chrome`, or `browser` globals a web extension usually has access to. If WXT doesn't remove all the imports from the file, the imported modules could try and access one of these variables, throwing an error.
:::
:::warning
See [`wxt-dev/wxt#336`](https://github.com/wxt-dev/wxt/issues/336) to track the status of this bug.
:::
Usually, this error occurs when you try to extract options into a shared file or try to run code outside the `main` function. To fix the example from above, use literal values when defining an entrypoint instead of importing them:
```ts
import { GOOGLE_MATCHES } from '~/utils/match-patterns'; // [!code --]
export default defineContentScript({
matches: GOOGLE_MATCHES, // [!code --]
matches: ['*//*.google.com/*'], // [!code ++]
main() {
// ...
},
});
```
+6 -46
View File
@@ -6,7 +6,7 @@ When releasing an update to your extension, there's a couple of things you need
## Content Script Cleanup
Old content scripts are not automatically stopped when an extension updates and reloads. Often, this leads to "Invalidated context" errors in production when a content script from an old version of your extension tries to use a web extension API (ie, the `browser` or `chrome` globals).
Old content scripts are not automatically stopped when an extension updates and reloads. Often, this leads to "Invalidated context" errors in production when a content script from an old version of your extension tries to use an extension API.
WXT provides a utility for handling this process: `ContentScriptContext`. An instance of this class is provided to you automatically inside the `main` function of each content script.
@@ -51,33 +51,12 @@ This prevents old content scripts from interfering with new content scripts, and
## Testing Permission Changes
When `permissions`/`host_permissions` change during an update, depending on what exactly changed, Chrome will disable your extension until the user accepts the new permissions.
When `permissions`/`host_permissions` change during an update, depending on what exactly changed, the browser will disable your extension until the user accepts the new permissions.
It is possible to test this before you release an update, but it's not a simple process:
You can test if your permission changes will result in a disabled extension:
1. Get 2 ZIPs of your extension, both generated by `wxt zip`. The first contains a previous version of your extension, the second contains the latest version. Make sure the second ZIP's version is higher than the first's.
2. Unzip the two ZIP files somewhere next to each other that's easy to locate.
3. In Chrome, open `chrome://extensions` and make sure developer mode is enabled
4. Pack the first extension into a CRX, generating a new private key:
1. Click "Pack Extension" in the top left
2. For "Extension root directory", enter the path to the first extracted zip directory. The directory should contain a `manifest.json` file
3. Leave "Private key file" blank
4. Click "Pack Extension". This will generate a `.crx` and `.pem` file
5. Pack the second extension into a CRX, reusing the private key generated by the previous step
1. Click "Pack Extension" in the top left
2. For "Extension root directory", enter the path to the second extracted zip directory.
3. For "Private key file", enter the path to the generated `.pem` private key file
4. Click "Pack Extension". This will generate a second `.crx` file.
6. Install the first CRX file by dragging and dropping it onto the `chrome://extensions` page
7. Install the second CRX file by dragging and dropping it onthe the `chrome://extensions` page
If a new permission must be accepted, you'll be prompted to accept it after dropping the second CRX file onto the page.
:::Info Note
Note: Chrome no longer allows self-signed CRX extensions to run, but that's OK for this test case. We're still prompted to accept new permissions, even if we can't interact with the installed extension.
To validate this, you can create a third ZIP file with a rare permission like `geolocation` in the manifest, that's guarenteed to reprompt permissions when added.
:::
- Chromium: Use [Google's Extension Update Testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool)
- Firefox/Safari: Everyone breaks something in production eventually... 🫡 Good luck soldier
## Update Event
@@ -94,23 +73,4 @@ browser.runtime.onInstalled.addListener(({ reason }) => {
If the logic is simple, write a unit test to cover this logic. If you feel the need to manually test this callback, you can either:
1. In dev mode, remove the `if` statement and reload the extension from `chrome://extensions`
2. Build two ZIPs with the same runtime ID and actually update the extension
The first approach is very straightforward. The second is more complicated...
Here are the steps:
So the steps:
1. Checkout an old commit.
2. [Add a `key`](https://developer.chrome.com/docs/extensions/reference/manifest/key#keep-consistent-id) to the `manifest` in your `wxt.config.ts`.
3. Run `wxt zip` to create the first ZIP.
4. Stash or reset changes and checkout your latest code.
5. Add back the same `key` to your manifest.
6. Make sure the extension's version is higher than the first zip. It can be any version that's higher, since you won't be releasing this version.
7. Run `wxt zip` to create the second ZIP.
8. In a fresh chrome profile, go to `chrome://extensions`, enable dev mode, and drag and drop the first zip onto the page to install it.
9. In the extension, play around and setup your test case.
10. Back on `chrome://extensions`, drag and drop your second zip onto the page.
If you setup the `key` correctly, it will cause the extension to act like it was updated instead of installing a second version of your extension.
2. Use [Google's Extension Update Testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool)
+5 -5
View File
@@ -25,12 +25,12 @@ There are two ways to add a module to your project:
});
```
2. **NPM package**: By convention, any package on NPM that starts with `@wxt-dev/module-*` or `wxt-module-*` can be installed and included in your project:
2. **NPM package**: Find WXT modules on NPM and include them in your project:
```ts
// wxt.config.ts
export default defineConfig({
// Add the module to your project
modules: ['wxt-module-example'],
modules: ['@wxt-dev/auto-icons'],
});
```
@@ -99,9 +99,9 @@ export default defineConfig({
### Actually Doing Something
The first argument of the setup function, `wxt`, provides full access to the current build's context. You can access the resolved configuration via `wxt.config`, or setup hooks to manipulate the build at different steps of the build process.
The first argument of the setup function, `wxt`, provides full access to the current build's context. You can access the resolved configuration via `wxt.config`, or setup hooks to manipulate the build at different steps of the build process with `wxt.hooks`.
Here's an example that shows you how to update the `outDir` based on the build mode. It does a good job showing how to access config, set up a hook, and do something when the hook is called.
Here's an example that updates the `outDir` based on the build mode. It's a very simple example of how to access config and setup a hook.
```ts
export default defineWxtModule((wxt) => {
@@ -176,7 +176,7 @@ export default defineWxtPlugin(() => {
:::
:::warning Async Plugins
Unlike modules, **_plugins cannot be async_**!! If you need to do some async work and expose that result to the rest of the extension, wrap it in an object and handle awaiting the promise internally.
Unlike modules, **_plugins cannot be async_**!! If you need to do some async work and expose that result to the rest of the extension, store the result's promise synchronously and await it later on.
:::
## Publishing to NPM
+1 -1
View File
@@ -96,7 +96,7 @@ export default defineConfig({
### ESLint 9 and above
WXT supports the new "flat config" file format. Just import the generated file and add it to the array of config to extend.
WXT supports the "flat config" file format introduced in ESLint 9. Just import the generated file and add it to the array of config to extend.
```js
// eslint.config.mjs
@@ -51,7 +51,7 @@ export default defineConfig({
If your framework doesn't have an official WXT module, no worries! WXT supports any framework with a Vite plugin.
Just add the Vite plugin to your config and you're good to go! Use the framework in HTML pages or content scripts, it will just work 👍
Just add the Vite plugin to your config and you're good to go! Use the framework in HTML pages or content scripts and it will just work 👍
```ts
import { defineConfig } from 'wxt';
@@ -102,7 +102,7 @@ Usually, this means each entrypoint should be a directory with it's own files in
Lots of frameworks come with routers for building a multi-page app using the URL's path. Chrome extensions don't don't work like this. Since HTML files are static, `chrome-extension://{id}/popup.html`, there's no way to change the entire path for routing.
Instead, you need to configure the router to run in "hash" mode, where the routing information is apart of the URL's hash, not the path (ie: `popup.html#/` and `popup.html#/account/settings`)
Instead, you need to configure the router to run in "hash" mode, where the routing information is apart of the URL's hash, not the path (ie: `popup.html#/` and `popup.html#/account/settings`).
Refer to your router's docs for information about hash mode and how to enable it. Here's a non-extensive list of a few popular routers:
+3 -3
View File
@@ -102,7 +102,7 @@ If a version is not found, a warning is logged and the version defaults to `"0.0
}
```
### `icons`
## `icons`
By default, WXT will discover icons in your [`public` directory](/guide/directory-structure/public/) and use them for the [manifest's `icons`](https://developer.chrome.com/docs/extensions/mv3/manifest/icons/).
@@ -135,7 +135,7 @@ export default defineConfig({
});
```
### Permissions
## Permissions
[Permissions](https://developer.chrome.com/docs/extensions/reference/permissions/) must be listed in the manifest config.
@@ -147,7 +147,7 @@ export default defineConfig({
});
```
### Host Permissions
## Host Permissions
[Host Permissions](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions#host-permissions) must be listed in the manifest config.
@@ -4,7 +4,7 @@
WXT is built on top [`webextension-polyfill` by Mozilla](https://www.npmjs.com/package/webextension-polyfill). The polyfill standardizes much of web extension APIs so they behave the same across different browsers and manifest versions.
Unlike with Chrome Extension development, which uses a `chrome` global, you need to import the `browser` variable from WXT to use the polyfill:
Unlike with Chrome Extension development, which uses a `chrome` global, you need to import the `browser` variable from WXT to access the extension APIs:
```ts
import { browser } from 'wxt/browser';
@@ -80,11 +80,11 @@ WXT uses Vite, so all of Vite's `import.meta.env` variables are also available:
Based on the files in your project, WXT will modify some of the polyfill's types to be type-safe.
For example, `browser.runtime.getURL` will be typed to only allow getting the URL of known files.
For example, `browser.runtime.getURL` will be typed to only allow getting the URL of known files. `browser.i18n.getMessage` will only allow getting translations of messages defined in your `public/_locales/<default-locale>/messages.json` file.
## Missing Types
Some newer APIs that Chrome provides are missing types. But don't worry, the APIs are present at runtime! The polyfill only provides types for standard and stable APIs that work on all browsers, so just be careful when you use them.
Some newer APIs that Chrome provides are missing types. Don't worry, the APIs are present at runtime! The polyfill only provides types for standard and stable APIs that work on all browsers, so just be careful when you use them.
If you're using TypeScript, you can use `@ts-expect-error` to ignore any errors when using an API that doesn't have any types.
+291
View File
@@ -0,0 +1,291 @@
# Upgrading WXT
## Overview
To upgrade WXT to the latest version... just install it!
```sh
pnpm i wxt@latest
```
If there was a major version change, follow the steps below to fix breaking changes.
## v0.18.0 &rarr; v0.19.0
### `vite-node` Entrypoint Loader
The default entrypoint loader has changed to `vite-node`. If you use any NPM packages that depend on the `webextension-polyfill`, you need to add them to Vite's `ssr.noExternal` option:
<!-- prettier-ignore -->
```ts
export default defineConfig({
vite: () => ({ // [!code ++]
ssr: { // [!code ++]
noExternal: ['@webext-core/messaging', '@webext-core/proxy-service'], // [!code ++]
}, // [!code ++]
}), // [!code ++]
});
```
> [Read the full docs](/guide/go-further/entrypoint-loaders#vite-node) for more information.
:::details This change enables:
Importing variables and using them in the entrypoint options:
```ts
// entrypoints/content.ts
import { GOOGLE_MATCHES } from '~/utils/constants'
export default defineContentScript({
matches: [GOOGLE_MATCHES],
main: () => ...,
})
```
Using Vite-specific APIs like `import.meta.glob` to define entrypoint options:
```ts
// entrypoints/content.ts
const providers: Record<string, any> = import.meta.glob('../providers/*', {
eager: true,
});
export default defineContentScript({
matches: Object.values(providers).flatMap(
(provider) => provider.default.paths,
),
async main() {
console.log('Hello content.');
},
});
```
Basically, you can now import and do things outside the `main` function of the entrypoint - you could not do that before. Still though, be careful. It is recommended to avoid running code outside the `main` function to keep your builds fast.
:::
To continue using the old approach, add the following to your `wxt.config.ts` file:
```ts
export default defineConfig({
entrypointLoader: 'jiti', // [!code ++]
});
```
:::warning
`entrypointLoader: "jiti"` is deprecated and will be removed in the next major version.
:::
### Drop CJS Support
WXT no longer ships with Common JS support. If you're using CJS, here's your migration steps:
1. Add [`"type": "module"`](https://nodejs.org/api/packages.html#type) to your `package.json`.
2. Change the file extension of any `.js` files that use CJS syntax to `.cjs`, or update them to use EMS syntax.
Vite also provides steps for migrating to ESM. Check them out for more details: https://vitejs.dev/guide/migration#deprecate-cjs-node-api
## v0.17.0 &rarr; v0.18.0
### Automatic MV3 `host_permissions` to MV2 `permissions`
> Out of an abundance of caution, this change has been marked as a breaking change because permission generation is different.
If you list `host_permissions` in your `wxt.config.ts`'s manifest and have released your extension, double check that your `permissions` and `host_permissions` have not changed for all browsers you target in your `.output/*/manifest.json` files. Permission changes can cause the extension to be disabled on update, and can cause a drop in users, so be sure to double check for differences compared to the previous manifest version.
## v0.16.0 &rarr; v0.17.0
### Storage - `defineItem` Requires `defaultValue` Option
If you were using `defineItem` with versioning and no default value, you will need to add `defaultValue: null` to the options and update the first type parameter:
```ts
const item = storage.defineItem<number>("local:count", { // [!code --]
const item = storage.defineItem<number | null>("local:count", { // [!code ++]
defaultValue: null, // [!code ++]
version: ...,
migrations: ...,
})
```
The `defaultValue` property is now required if passing in the second options argument.
If you exclude the second options argument, it will default to being nullable, as before.
```ts
const item: WxtStorageItem<number | null> =
storage.defineItem<number>('local:count');
const value: number | null = await item.getValue();
```
### Storage - Fix Types In `watch` Callback
> If you don't use TypeScript, this isn't a breaking change, this is just a type change.
```ts
const item = storage.defineItem<number>('local:count', { defaultValue: 0 });
item.watch((newValue: number | null, oldValue: number | null) => { // [!code --]
item.watch((newValue: number, oldValue: number) => { // [!code ++]
// ...
});
```
## v0.15.0 &rarr; v0.16.0
### Output Directory Structure Changed
JS entrypoints in the output directory have been moved. Unless you're doing some kind of post-build work referencing files, you don't have to make any changes.
```
.output/
<target>/
chunks/
some-shared-chunk-<hash>.js
popup-<hash>.js // [!code --]
popup.html
popup.html
popup.js // [!code ++]
```
## v0.14.0 &rarr; v0.15.0
### Renamed `zip.ignoredSources` to `zip.excludeSources`
```ts
// wxt.config.ts
export default defineConfig({
zip: {
ignoredSources: [
/*...*/
], // [!code --]
excludeSources: [
/*...*/
], // [!code ++]
},
});
```
### Renamed Undocumented Constants
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: https://wxt.dev/guide/multiple-browsers.html#runtime
- `__BROWSER__``import.meta.env.BROWSER`
- `__COMMAND__``import.meta.env.COMMAND`
- `__MANIFEST_VERSION__``import.meta.env.MANIFEST_VERSION`
- `__IS_CHROME__``import.meta.env.CHROME`
- `__IS_FIREFOX__``import.meta.env.FIREFOX`
- `__IS_SAFARI__``import.meta.env.SAFARI`
- `__IS_EDGE__``import.meta.env.EDGE`
- `__IS_OPERA__``import.meta.env.OPERA`
## v0.13.0 &rarr; v0.14.0
### Content Script UI API changes
`createContentScriptUi` and `createContentScriptIframe`, and some of their options, have been renamed:
- `createContentScriptUi({ ... })` &rarr; `createShadowRootUi({ ... })`
- `createContentScriptIframe({ ... })` &rarr; `createIframeUi({ ... })`
- `type: "inline" | "overlay" | "modal"` has been changed to `position: "inline" | "overlay" | "modal"`
- `onRemove` is now called **_before_** the UI is removed from the DOM, previously it was called after the UI was removed
- `mount` option has been renamed to `onMount`, to better match the related option, `onRemove`.
## v0.12.0 &rarr; v0.13.0
### New `wxt/storage` APIs
`wxt/storage` no longer relies on [`unstorage`](https://www.npmjs.com/package/unstorage). Some `unstorage` APIs, like `prefixStorage`, have been removed, while others, like `snapshot`, are methods on the new `storage` object. Most of the standard usage remains the same. See https://wxt.dev/guide/storage and https://wxt.dev/api/reference/wxt/storage/ for more details ([#300](https://github.com/wxt-dev/wxt/pull/300))
## v0.11.0 &rarr; v0.12.0
### API Exports Changed
`defineContentScript` and `defineBackground` are now exported from `wxt/sandbox` instead of `wxt/client`. ([#284](https://github.com/wxt-dev/wxt/pull/284))
- If you use auto-imports, no changes are required.
- If you have disabled auto-imports, you'll need to manually update your import statements:
```ts
import { defineBackground, defineContentScript } from 'wxt/client'; // [!code --]
import { defineBackground, defineContentScript } from 'wxt/sandbox'; // [!code ++]
```
## v0.10.0 &rarr; v0.11.0
### Vite 5
You will need to update any other Vite plugins to a version that supports Vite 5.
## v0.9.0 &rarr; v0.10.0
### Extension Icon Discovery
WXT no longer discovers icons other than `.png` files. If you previously used `.jpg`, `.jpeg`, `.bmp`, or `.svg`, you'll need to convert your icons to `.png` files or manually add them to the manifest inside your `wxt.config.ts` file.
## v0.8.0 &rarr; v0.9.0
### Removed `WebWorker` Types by Default
Removed [`"WebWorker"` types](https://www.typescriptlang.org/tsconfig/lib.html) from `.wxt/tsconfig.json`. These types are useful for MV3 projects using a service worker.
To add them back to your project, add the following to your project's TSConfig:
```json
{
"extends": "./.wxt/tsconfig.json",
"compilerOptions": {
// [!code ++]
"lib": ["ESNext", "DOM", "WebWorker"] // [!code ++]
} // [!code ++]
}
```
## v0.7.0 &rarr; v0.8.0
### `defineUnlistedScript`
Unlisted scripts must now `export default defineUnlistedScript(...)`.
### `BackgroundDefinition` Type
Rename `BackgroundScriptDefintition` to `BackgroundDefinition`.
## v0.6.0 &rarr; v0.7.0
### Content Script CSS Output Location Changed
Content script CSS used to be output to `assets/<name>.css`, but is now `content-scripts/<name>.css` to match the docs.
## v0.5.0 &rarr; v0.6.0
### Require a Function for `vite` Config
The `vite` config option must now be a function. If you were using an object before, change it from `vite: { ... }` to `vite: () => ({ ... })`.
## v0.4.0 &rarr; v0.5.0
### Revert Move Public Directory
Change default `publicDir` to from `<rootDir>/public` to `<srcDir>/public`.
## v0.3.0 &rarr; v0.4.0
### Update Default Path Aliases
Use relative path aliases inside `.wxt/tsconfig.json`.
## v0.2.0 &rarr; v0.3.0
### Move Public Directory
Change default `publicDir` to from `<srcDir>/public` to `<rootDir>/public`.
### Improve Type Safety
Add type safety to `browser.runtime.getURL`.
## v0.1.0 &rarr; v0.2.0
### Rename `defineBackground`
Rename `defineBackgroundScript` to `defineBackground`.
+5
View File
@@ -53,6 +53,11 @@ features:
details: Works with any front-end framework with a Vite plugin.
link: /guide/key-concepts/frontend-frameworks
linkText: Add a framework
- icon: 📦
title: Modular Architecture
details: Reuse build-time and runtime-code across multiple extensions.
link: /guide/go-further/reusable-modules
linkText: Read docs
- icon: 🖍️
title: Bootstrap a New Project
details: Get started quickly with several awesome project templates.
+5
View File
@@ -2,6 +2,8 @@
# https://docs.netlify.com/routing/redirects/
# Old URLs -> New URLs
# OLD
/config.html /api/reference/wxt/interfaces/InlineConfig.html
/api/config.html /api/reference/wxt/interfaces/InlineConfig.html
/entrypoints /entrypoints/background.html
@@ -29,3 +31,6 @@
/guide/build-targets.html /guide/multiple-browsers.html
/guide/installation.html /get-started/installation.html
/guide/introduction.html /get-started/introduction.html
# 0.19.0
/guide/go-further/entrypoint-side-effects.html /guide/go-further/entrypoint-loaders.html
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

+7 -8
View File
@@ -2,10 +2,9 @@
"private": true,
"type": "module",
"engines": {
"node": ">=18.20.3",
"pnpm": "^8.6.3"
"node": ">=18.20.3"
},
"packageManager": "pnpm@8.15.9",
"packageManager": "pnpm@9.6.0",
"scripts": {
"check": "check && pnpm -r --sequential run check",
"test": "pnpm -r --sequential run test run",
@@ -21,7 +20,7 @@
"@aklinker1/buildc": "^1.0.11",
"@aklinker1/check": "^1.3.1",
"@types/fs-extra": "^11.0.4",
"@vitest/coverage-v8": "^2.0.0",
"@vitest/coverage-v8": "^2.0.4",
"changelogen": "^0.5.5",
"consola": "^3.2.3",
"dependency-graph": "^1.0.0",
@@ -37,12 +36,12 @@
"typedoc": "^0.25.4",
"typedoc-plugin-markdown": "4.0.0-next.23",
"typedoc-vitepress-theme": "1.0.0-next.3",
"typescript": "^5.5.3",
"typescript": "^5.5.4",
"vitepress": "^1.3.1",
"vitest-mock-extended": "^1.3.2",
"vue": "^3.4.33",
"vitest-mock-extended": "^2.0.0",
"vue": "^3.4.34",
"wxt": "workspace:*",
"yaml": "^2.4.5"
"yaml": "^2.5.0"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
+34
View File
@@ -0,0 +1,34 @@
# Changelog
## v1.0.2
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.1...auto-icons-v1.0.2)
### 📖 Documentation
- **auto-icons:** Fix configuration example typo ([#905](https://github.com/wxt-dev/wxt/pull/905))
### 🏡 Chore
- Add more metadata for npm ([#885](https://github.com/wxt-dev/wxt/pull/885))
### ❤️ Contributors
- Uncenter ([@uncenter](http://github.com/uncenter))
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
## v1.0.1
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.0...auto-icons-v1.0.1)
### 🩹 Fixes
- **auto-icons:** Path option ([#880](https://github.com/wxt-dev/wxt/pull/880))
### 🏡 Chore
- **deps:** Upgrade all dependencies ([#869](https://github.com/wxt-dev/wxt/pull/869))
### ❤️ Contributors
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
+42
View File
@@ -0,0 +1,42 @@
# WXT Auto Icons
## Features
- Generate extension icons with the correct sizes
- Make the icon greyscale during development
## Usage
Install the package:
```sh
npm i --save-dev @wxt-dev/auto-icons
pnpm i -D @wxt-dev/auto-icons
yarn add --dev @wxt-dev/auto-icons
bun i -D @wxt-dev/auto-icons
```
Add the module to `wxt.config.ts`:
```ts
export default defineConfig({
modules: ['@wxt-dev/auto-icons'],
});
```
And finally, save the base icon to `<srcDir>/assets/icon.png`.
## Configuration
The module can be configured via the `autoIcons` config:
```ts
export default defineConfig({
modules: ['@wxt-dev/auto-icons'],
autoIcons: {
// ...
},
});
```
Options have JSDocs available in your editor, or you can read them in the source code: [`AutoIconsOptions`](./src/index.ts).
+59
View File
@@ -0,0 +1,59 @@
{
"name": "@wxt-dev/auto-icons",
"description": "WXT module for automatically generating extension icons in different sizes",
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/auto-icons"
},
"homepage": "https://github.com/wxt-dev/wxt/blob/main/packages/auto-icons/README.md",
"keywords": [
"wxt",
"module",
"icons",
"manifest"
],
"author": {
"name": "Florian Metz",
"email": "me@timeraa.dev"
},
"contributors": [
{
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
}
],
"license": "MIT",
"version": "1.0.2",
"type": "module",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
}
},
"files": [
"dist"
],
"scripts": {
"build": "buildc -- unbuild",
"check": "pnpm build && check"
},
"peerDependencies": {
"wxt": ">=0.19.0"
},
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.9",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
},
"dependencies": {
"defu": "^6.1.4",
"fs-extra": "^11.2.0",
"sharp": "^0.33.4"
}
}
+108
View File
@@ -0,0 +1,108 @@
import 'wxt';
import { defineWxtModule } from 'wxt/modules';
import { resolve, relative } from 'node:path';
import defu from 'defu';
import sharp from 'sharp';
import { ensureDir, exists } from 'fs-extra';
export default defineWxtModule<AutoIconsOptions>({
name: '@wxt-dev/auto-icons',
configKey: 'autoIcons',
async setup(wxt, options) {
const parsedOptions = defu<Required<AutoIconsOptions>, AutoIconsOptions[]>(
options,
{
enabled: true,
baseIconPath: resolve(wxt.config.srcDir, 'assets/icon.png'),
grayscaleOnDevelopment: true,
sizes: [128, 48, 32, 16],
},
);
const resolvedPath = resolve(wxt.config.srcDir, parsedOptions.baseIconPath);
if (!parsedOptions.enabled)
return wxt.logger.warn(`\`[auto-icons]\` ${this.name} disabled`);
if (!(await exists(resolvedPath))) {
return wxt.logger.warn(
`\`[auto-icons]\` Skipping icon generation, no base icon found at ${relative(process.cwd(), resolvedPath)}`,
);
}
wxt.hooks.hook('build:manifestGenerated', async (wxt, manifest) => {
if (manifest.icons)
return wxt.logger.warn(
'`[auto-icons]` icons property found in manifest, overwriting with auto-generated icons',
);
manifest.icons = Object.fromEntries(
parsedOptions.sizes.map((size) => [size, `icons/${size}.png`]),
);
});
wxt.hooks.hook('build:done', async (wxt, output) => {
const image = sharp(resolvedPath).png();
if (
wxt.config.mode === 'development' &&
parsedOptions.grayscaleOnDevelopment
) {
image.grayscale();
}
const outputFolder = wxt.config.outDir;
for (const size of parsedOptions.sizes) {
const resized = image.resize(size);
ensureDir(resolve(outputFolder, 'icons'));
await resized.toFile(resolve(outputFolder, `icons/${size}.png`));
output.publicAssets.push({
type: 'asset',
fileName: `icons/${size}.png`,
});
}
});
wxt.hooks.hook('prepare:publicPaths', (wxt, paths) => {
for (const size of parsedOptions.sizes) {
paths.push(`icons/${size}.png`);
}
});
},
});
/**
* Options for the auto-icons module
*/
export interface AutoIconsOptions {
/**
* Enable auto-icons generation
* @default true
*/
enabled?: boolean;
/**
* Path to the image to use.
*
* Path is relative to the project's src directory.
* @default "<srcDir>/assets/icon.png"
*/
baseIconPath?: string;
/**
* Grayscale the image when in development mode to indicate development
* @default true
*/
grayscaleOnDevelopment?: boolean;
/**
* Sizes to generate icons for
* @default [128, 48, 32, 16]
*/
sizes?: number[];
}
declare module 'wxt' {
export interface InlineConfig {
autoIcons?: AutoIconsOptions;
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"exclude": ["node_modules/**", "dist/**"]
}
+14
View File
@@ -1,5 +1,19 @@
# Changelog
## v1.1.1
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.0...module-react-v1.1.1)
### 🏡 Chore
- **deps:** Bump all non-major dependencies ([#834](https://github.com/wxt-dev/wxt/pull/834))
- **deps:** Upgrade all dependencies ([#869](https://github.com/wxt-dev/wxt/pull/869))
- Add more metadata for npm ([#885](https://github.com/wxt-dev/wxt/pull/885))
### ❤️ Contributors
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
## v1.1.0
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.0.0...module-react-v1.1.0)
-1
View File
@@ -9,7 +9,6 @@ export default defineWxtModule<ReactModuleOptions>({
const { vite } = options ?? {};
addViteConfig(wxt, () => ({
// @ts-expect-error: Vite version conflicts
plugins: [react(vite)],
}));
+20 -3
View File
@@ -1,6 +1,23 @@
{
"name": "@wxt-dev/module-react",
"version": "1.1.0",
"description": "WXT module to enable React support",
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/module-react"
},
"homepage": "https://github.com/wxt-dev/wxt/blob/main/packages/module-react/README.md",
"keywords": [
"wxt",
"module",
"react"
],
"author": {
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
},
"license": "MIT",
"version": "1.1.1",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
@@ -22,7 +39,7 @@
],
"scripts": {
"dev": "wxt",
"check": "buildc --deps-only -- check",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"prepare": "buildc --deps-only -- wxt prepare"
},
@@ -39,7 +56,7 @@
"publint": "^0.2.9",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.5.3",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
-1
View File
@@ -9,7 +9,6 @@ export default defineWxtModule<SolidModuleOptions>({
const { vite } = options ?? {};
addViteConfig(wxt, () => ({
// @ts-expect-error: Vite version mismatch
plugins: [solid(vite)],
build: {
target: 'esnext',
+20 -3
View File
@@ -1,5 +1,22 @@
{
"name": "@wxt-dev/module-solid",
"description": "WXT module to enable SolidJS support",
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/module-solid"
},
"homepage": "https://github.com/wxt-dev/wxt/blob/main/packages/module-solid/README.md",
"keywords": [
"wxt",
"module",
"solidjs"
],
"author": {
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
},
"license": "MIT",
"version": "1.1.1",
"type": "module",
"main": "./dist/index.cjs",
@@ -22,7 +39,7 @@
],
"scripts": {
"dev": "wxt",
"check": "buildc --deps-only -- check",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"prepare": "buildc --deps-only -- wxt prepare"
},
@@ -35,8 +52,8 @@
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.9",
"solid-js": "^1.8.18",
"typescript": "^5.5.3",
"solid-js": "^1.8.19",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
+19 -2
View File
@@ -1,5 +1,22 @@
{
"name": "@wxt-dev/module-svelte",
"description": "WXT module to enable Svelte support",
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/module-svelte"
},
"homepage": "https://github.com/wxt-dev/wxt/blob/main/packages/module-svelte/README.md",
"keywords": [
"wxt",
"module",
"svelte"
],
"author": {
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
},
"license": "MIT",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.cjs",
@@ -22,7 +39,7 @@
],
"scripts": {
"build": "buildc -- unbuild",
"check": "buildc --deps-only -- check"
"check": "pnpm build && check"
},
"peerDependencies": {
"wxt": ">=0.18.6"
@@ -33,7 +50,7 @@
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.9",
"typescript": "^5.5.3",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
-1
View File
@@ -14,7 +14,6 @@ export default defineWxtModule<SvelteModuleOptions>({
addViteConfig(wxt, () => ({
plugins: [
// @ts-expect-error: Vite version mismatch
svelte({
// Using a svelte.config.js file causes a segmentation fault when importing the file
configFile: false,
+20 -3
View File
@@ -1,5 +1,22 @@
{
"name": "@wxt-dev/module-vue",
"description": "WXT module to enable Vue support",
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/module-vue"
},
"homepage": "https://github.com/wxt-dev/wxt/blob/main/packages/module-vue/README.md",
"keywords": [
"wxt",
"module",
"vue"
],
"author": {
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
},
"license": "MIT",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.cjs",
@@ -22,18 +39,18 @@
],
"scripts": {
"build": "buildc -- unbuild",
"check": "buildc --deps-only -- check"
"check": "pnpm build && check"
},
"peerDependencies": {
"wxt": ">=0.18.6"
},
"dependencies": {
"@vitejs/plugin-vue": "^5.0.5"
"@vitejs/plugin-vue": "^5.1.1"
},
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.9",
"typescript": "^5.5.3",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
+4 -2
View File
@@ -21,11 +21,13 @@
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/chrome": "^0.0.269",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@wxt-dev/auto-icons": "workspace:*",
"sass": "^1.77.8",
"typescript": "^5.5.3",
"vitest": "^2.0.3",
"typescript": "^5.5.4",
"vitest": "^2.0.4",
"vitest-plugin-random-seed": "^1.1.0",
"wxt": "workspace:*"
},

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -20,7 +20,7 @@ export default defineBackground({
// @ts-expect-error: should only accept entrypoints or public assets
browser.runtime.getURL('/');
browser.runtime.getURL('/background.js');
browser.runtime.getURL('/icon/128.png');
browser.runtime.getURL('/icons/128.png');
browser.runtime.getURL('/example.html#hash');
browser.runtime.getURL('/example.html?query=param');
// @ts-expect-error: should only allow hashes/query params on HTML files
@@ -0,0 +1,3 @@
import autoIcons from '@wxt-dev/auto-icons';
export default autoIcons;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

+1 -4
View File
@@ -6,8 +6,5 @@ export default defineProject({
mockReset: true,
restoreMocks: true,
},
plugins: [
// @ts-expect-error: Vite version mismatch
WxtVitest(),
],
plugins: [WxtVitest()],
});
+1 -3
View File
@@ -2,6 +2,7 @@ import { defineConfig } from 'wxt';
export default defineConfig({
srcDir: 'src',
extensionApi: 'chrome',
manifest: {
permissions: ['storage'],
default_locale: 'en',
@@ -18,9 +19,6 @@ export default defineConfig({
analysis: {
open: true,
},
experimental: {
entrypointImporter: 'vite-node',
},
runner: {
startUrls: ['https://duckduckgo.com'],
},
+131 -199
View File
@@ -1,5 +1,118 @@
# Changelog
## v0.19.4
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.3...wxt-v0.19.4)
### 🚀 Enhancements
- Add `injectScript` helper ([#900](https://github.com/wxt-dev/wxt/pull/900))
### 🩹 Fixes
- Do not clear `.wxt/tsconfig.json` in `findEntrypoints` if it exists ([#898](https://github.com/wxt-dev/wxt/pull/898))
- **types:** `PublicPath` type resolution with `extensionApi: "chrome"` ([#901](https://github.com/wxt-dev/wxt/pull/901))
- Fix `createIframeUi` `page` option types ([3a8e613](https://github.com/wxt-dev/wxt/commit/3a8e613))
### ❤️ Contributors
- Hikiko4ern ([@hikiko4ern](http://github.com/hikiko4ern))
## v0.19.3
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.2...wxt-v0.19.3)
### 🩹 Fixes
- Add `consola` to `wxt` dependencies ([#892](https://github.com/wxt-dev/wxt/pull/892))
- **content-script-context:** Fix initialization logic for Firefox ([#895](https://github.com/wxt-dev/wxt/pull/895))
### 📖 Documentation
- Improve `prepare:types` hook JSDoc ([#886](https://github.com/wxt-dev/wxt/pull/886))
### ❤️ Contributors
- Hikiko4ern ([@hikiko4ern](http://github.com/hikiko4ern))
- Himanshu Patil ([@mehimanshupatil](https://github.com/mehimanshupatil))
## v0.19.2
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.1...wxt-v0.19.2)
### 🩹 Fixes
- Remove unused top-level functions and variables when loading entrypoints with the `vite-node` loader ([#875](https://github.com/wxt-dev/wxt/pull/875))
- Don't default to dev mode for production builds when using `vite-node` loader ([#877](https://github.com/wxt-dev/wxt/pull/877))
### 📖 Documentation
- Update README and homepage features with WXT modules ([ed07a49](https://github.com/wxt-dev/wxt/commit/ed07a49))
### 🏡 Chore
- **deps:** Upgrade all dependencies ([#869](https://github.com/wxt-dev/wxt/pull/869))
## v0.19.1
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.0...wxt-v0.19.1)
### 🚀 Enhancements
- Auto Icons Module ([#851](https://github.com/wxt-dev/wxt/pull/851))
### 🔥 Performance
- Tree-shake unrelated code inside `vite-node` entrypoint loader ([#867](https://github.com/wxt-dev/wxt/pull/867))
### 🩹 Fixes
- Define web globals when importing entrypoints ([#865](https://github.com/wxt-dev/wxt/pull/865))
### ❤️ Contributors
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
## v0.19.0
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.18.15...wxt-v0.19.0)
### 🚀 Enhancements
- **experimental:** First-class support for excluding `webextension-polyfill` ([#847](https://github.com/wxt-dev/wxt/pull/847))
- **storage:** `init` option and rename `defaultValue` to `fallback` ([#827](https://github.com/wxt-dev/wxt/pull/827))
- **hooks:** Add `prepare:publicPaths` hook ([#858](https://github.com/wxt-dev/wxt/pull/858))
- ⚠️ Use `vite-node` to load entrypoints by default ([#859](https://github.com/wxt-dev/wxt/pull/859))
### 🔥 Performance
- **size:** ⚠️ Switch from `tsup` to `unbuild` for building WXT ([#848](https://github.com/wxt-dev/wxt/pull/848))
### 🩹 Fixes
- Wrong module hook type ([#849](https://github.com/wxt-dev/wxt/pull/849))
### 📖 Documentation
- Update labels in content script UI positioning screenshot ([2b6ff8d](https://github.com/wxt-dev/wxt/commit/2b6ff8d))
- Add upgrade guide and breaking changes ([#860](https://github.com/wxt-dev/wxt/pull/860))
### 🏡 Chore
- **deps:** Bump all non-major dependencies ([#834](https://github.com/wxt-dev/wxt/pull/834))
- **dev-deps:** Upgrade `vitest` from 1.6 to 2.0 ([#836](https://github.com/wxt-dev/wxt/pull/836))
- **deps:** Upgrade `async-mutex` from 0.4.1 to 0.5.0 ([#837](https://github.com/wxt-dev/wxt/pull/837))
- **deps:** Upgrade `esbuild` from 0.19.12 to 0.23.0 ([#838](https://github.com/wxt-dev/wxt/pull/838))
- **deps:** Upgrade `vite-node` from 1.6 to 2.0 ([#839](https://github.com/wxt-dev/wxt/pull/839))
- **deps:** Upgrade `ora` from 7 to 8 ([#841](https://github.com/wxt-dev/wxt/pull/841))
- **deps:** Upgrade `webextension-polyfill` from 0.10 to 0.12 ([#842](https://github.com/wxt-dev/wxt/pull/842))
- **deps:** Upgrade `minimatch` from 9 to 10 ([#840](https://github.com/wxt-dev/wxt/pull/840))
- **dev-deps:** Upgrade `happy-dom` from 13 to 14 ([#843](https://github.com/wxt-dev/wxt/pull/843))
### ❤️ Contributors
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
## v0.18.15
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.18.14...wxt-v0.18.15)
@@ -298,7 +411,7 @@
## v0.18.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.12...v0.18.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.17.12...v0.18.0)
### 🚀 Enhancements
@@ -333,12 +446,6 @@
- Refactor repo to a standard monorepo ([#646](https://github.com/wxt-dev/wxt/pull/646))
- Fix formatting after monorepo refactor ([6ca3767](https://github.com/wxt-dev/wxt/commit/6ca3767))
#### ⚠️ Breaking Changes
- ⚠️ Automatically move `host_permissions` to `permissions` for MV2 ([#626](https://github.com/wxt-dev/wxt/pull/626))
Out of an abundance of caution, I've marked this as a breaking change because permission generation has changed. **_If you list `host_permissions` in your `wxt.config.ts`'s manifest and have released your extension_**, double check that your `permissions` and `host_permissions` have not changed for all browsers you target in your `.output/*/manifest.json` files. Permission changes can cause the extension to be disabled on update, and can cause a drop in users, so be sure to double check for differences compared to the previous manifest version.
### ❤️ Contributors
- Alegal200 ([@alegal200](https://github.com/alegal200))
@@ -605,7 +712,7 @@
## v0.17.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.11...v0.17.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.11...v0.17.0)
### 🚀 Enhancements
@@ -620,70 +727,6 @@
- **storage:** Update docs ([91fc41c](https://github.com/wxt-dev/wxt/commit/91fc41c))
#### ⚠️ Breaking Changes
`v0.17.0` introduces several breaking changes to `wxt/storage`.
First, if you were using `defineItem` with versioning and no default value, you will need to add `defaultValue: null` to the options and update the first type parameter:
```ts
// < 0.17
const item = storage.defineItem<number>("local:count", {
version: ...,
migrations: ...,
})
// >= 0.17
const item = storage.defineItem<number | null>("local:count", {
defaultValue: null,
version: ...,
migrations: ...,
})
```
The `defaultValue` property is now required if passing in the second options argument.
If you exclude the second options argument, it will default to being nullable, as before.
```ts
const item: WxtStorageItem<number | null> =
storage.defineItem<number>('local:count');
const value: number | null = await item.getValue();
```
> If you don't use typescript, there aren't any breaking changes, this is just a type change.
For storage items that are not nullable, the `watch` callback types has improved and will use the default value instead of `null` when the value is missing:
```ts
// >=0.17
const item = storage.defineItem<number>('local:count', { defaultValue: 0 });
item.watch((newValue: number | null, oldValue: number | null) => {
// ...
});
// >=0.17
const item = storage.defineItem<number>('local:count', { defaultValue: 0 });
item.watch((newValue: number, oldValue: number) => {
// ...
});
```
You can also access the default value directly off the item:
```ts
console.log(item.defaultValue); // 0
```
The second breaking change is that migrations for versioned items only run when the extension is updated. Before, they were ran whenever the storage item was created, in any entrypoint (background, popup, content script, etc). Now, in v0.17, storage items will only run migrations when the `browser.runtime.onInstalled` event is fired with `reason = "update"` in the background. See the updated docs to make sure they run correctly: https://wxt.dev/guide/storage.html#running-migrations. TLDR: you need to import all storage items into the background entrypoint for the `onInstalled` hook to fire properly and thus run the migrations.
To keep the old behavior, call the new `migrate` function to run migrations as soon as an item is defined:
```ts
const item = storage.defineItem(...);
item.migrate();
```
## v0.16.11
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.16.10...v0.16.11)
@@ -889,7 +932,7 @@ item.migrate();
## v0.16.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.15.4...v0.16.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.15.4...v0.16.0)
### 🚀 Enhancements
@@ -905,31 +948,6 @@ item.migrate();
- **deps-dev:** Bump p-map from 7.0.0 to 7.0.1 ([#396](https://github.com/wxt-dev/wxt/pull/396))
- **deps-dev:** Bump @vitest/coverage-v8 from 1.0.1 to 1.2.2 ([#397](https://github.com/wxt-dev/wxt/pull/397))
#### ⚠️ Breaking Changes
In [#398](https://github.com/wxt-dev/wxt/pull/398), HTML pages' JS entrypoints in the output directory have been moved. Unless you're doing some kind of post-build work referencing files, you don't have to make any changes.
- Before:
```
.output/
<target>/
chunks/
some-shared-chunk-<hash>.js
popup-<hash>.js
popup.html
```
- After:
```
.output/
<target>/
chunks/
some-shared-chunk-<hash>.js
popup.html
popup.js
```
This effects all HTML files, not just the Popup. The hash has been removed, and it's been moved to the root of the build target folder, not inside the `chunks/` directory. Moving files like this has not historically increased review times or triggered in-depth reviews when submitting updates to the stores.
## v0.15.4
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.15.3...v0.15.4)
@@ -988,7 +1006,7 @@ This effects all HTML files, not just the Popup. The hash has been removed, and
## v0.15.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.14.7...v0.15.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.14.7...v0.15.0)
### 🚀 Enhancements
@@ -1008,21 +1026,6 @@ This effects all HTML files, not just the Popup. The hash has been removed, and
- Update contributor docs ([eb758bd](https://github.com/wxt-dev/wxt/commit/eb758bd))
#### ⚠️ Breaking Changes
Renamed `zip.ignoredSources` to `zip.excludeSources` in [#378](https://github.com/wxt-dev/wxt/pull/378)
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: https://wxt.dev/guide/multiple-browsers.html#runtime
- `__BROWSER__` &rarr; `import.meta.env.BROWSER`
- `__COMMAND__` &rarr; `import.meta.env.COMMAND`
- `__MANIFEST_VERSION__` &rarr; `import.meta.env.MANIFEST_VERSION`
- `__IS_CHROME__` &rarr; `import.meta.env.CHROME`
- `__IS_FIREFOX__` &rarr; `import.meta.env.FIREFOX`
- `__IS_SAFARI__` &rarr; `import.meta.env.SAFARI`
- `__IS_EDGE__` &rarr; `import.meta.env.EDGE`
- `__IS_OPERA__` &rarr; `import.meta.env.OPERA`
### ❤️ Contributors
- Nenad Novaković ([@dvlden](https://github.com/dvlden))
@@ -1143,22 +1146,12 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.14.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.13.5...v0.14.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.13.5...v0.14.0)
### 🚀 Enhancements
- ⚠️ Refactor content script UI functions and add helper for "integrated" UIs ([#333](https://github.com/wxt-dev/wxt/pull/333))
#### ⚠️ Breaking Changes
`createContentScriptUi` and `createContentScriptIframe`, and some of their options, have been renamed:
- `createContentScriptUi({ ... })` &rarr; `createShadowRootUi({ ... })`
- `createContentScriptIframe({ ... })` &rarr; `createIframeUi({ ... })`
- `type: "inline" | "overlay" | "modal"` has been changed to `position: "inline" | "overlay" | "modal"`
- `onRemove` is now called **_before_** the UI is removed from the DOM, previously it was called after the UI was removed
- `mount` option has been renamed to `onMount`, to better match the related option, `onRemove`.
## v0.13.5
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.13.4...v0.13.5)
@@ -1248,16 +1241,12 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.13.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.12.5...v0.13.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.12.5...v0.13.0)
### 🚀 Enhancements
- ⚠️ New `wxt/storage` APIs ([#300](https://github.com/wxt-dev/wxt/pull/300))
#### ⚠️ Breaking Changes
- `wxt/storage` no longer relies on [`unstorage`](https://www.npmjs.com/package/unstorage). Some `unstorage` APIs, like `prefixStorage`, have been removed, while others, like `snapshot`, are methods on the new `storage` object. Most of the standard usage remains the same. See https://wxt.dev/guide/storage and https://wxt.dev/api/reference/wxt/storage/ for more details ([#300](https://github.com/wxt-dev/wxt/pull/300))
## v0.12.5
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.12.4...v0.12.5)
@@ -1305,7 +1294,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.12.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.11.2...v0.12.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.11.2...v0.12.0)
### 🚀 Enhancements
@@ -1325,17 +1314,6 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
- Convert WXT CLI to an ESM binary ([#279](https://github.com/wxt-dev/wxt/pull/279))
#### ⚠️ Breaking Changes
`defineContentScript` and `defineBackground` are now exported from `wxt/sandbox` instead of `wxt/client`. ([#284](https://github.com/wxt-dev/wxt/pull/284))
- If you use auto-imports, no changes are required.
- If you have disabled auto-imports, you'll need to manually update your import statements:
```diff
- import { defineBackground, defineContentScript } from 'wxt/client';
+ import { defineBackground, defineContentScript } from 'wxt/sandbox';
```
## v0.11.2
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.11.1...v0.11.2)
@@ -1368,7 +1346,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.11.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.10.4...v0.11.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.10.4...v0.11.0)
### 🚀 Enhancements
@@ -1383,10 +1361,6 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
- Speed up CI using `pnpm` instead of `npm` ([#259](https://github.com/wxt-dev/wxt/pull/259))
- Abstract vite from WXT's core logic ([#242](https://github.com/wxt-dev/wxt/pull/242))
#### ⚠️ Breaking Changes
- You will need to update any other Vite plugins to a version that supports Vite 5 ([#261](https://github.com/wxt-dev/wxt/pull/261))
### ❤️ Contributors
- Ítalo Brasil ([@italodeverdade](http://github.com/italodeverdade))
@@ -1459,7 +1433,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.10.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.9.2...v0.10.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.9.2...v0.10.0)
### 🚀 Enhancements
@@ -1532,7 +1506,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.9.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.8.7...v0.9.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.8.7...v0.9.0)
### 🩹 Fixes
@@ -1547,18 +1521,6 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
- Fix Svelte and React template READMEs ([#207](https://github.com/wxt-dev/wxt/pull/207))
#### ⚠️ Breaking Changes
- ⚠️ Removed [`"WebWorker"` types](https://www.typescriptlang.org/tsconfig/lib.html) from `.wxt/tsconfig.json` ([#209](https://github.com/wxt-dev/wxt/pull/209)). These types are useful for MV3 projects using a service worker. To add them back to your project, add the following to your project's TSConfig:
```diff
{
"extends": "./.wxt/tsconfig.json",
+ "compilerOptions": {
+ "lib": ["ESNext", "DOM", "WebWorker"]
+ }
}
```
### ❤️ Contributors
- yyyanghj ([@yyyanghj](https://github.com/yyyanghj))
@@ -1667,7 +1629,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.8.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.7.5...v0.8.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.7.5...v0.8.0)
### 🚀 Enhancements
@@ -1681,11 +1643,6 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
- ⚠️ Rename `BackgroundScriptDefintition` to `BackgroundDefinition` ([446f265](https://github.com/wxt-dev/wxt/commit/446f265))
#### ⚠️ Breaking Changes
- ⚠️ Unlisted scripts must now `export default defineUnlistedScript(...)` ([#167](https://github.com/wxt-dev/wxt/pull/167))
- ⚠️ Rename `BackgroundScriptDefintition` to `BackgroundDefinition` ([446f265](https://github.com/wxt-dev/wxt/commit/446f265))
### ❤️ Contributors
- 渣渣120 [@WOSHIZHAZHA120](https://github.com/WOSHIZHAZHA120)
@@ -1768,7 +1725,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.7.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.6.6...v0.7.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.6.6...v0.7.0)
### 🚀 Enhancements
@@ -1784,10 +1741,6 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
- Store user config metadata in memory ([0591050](https://github.com/wxt-dev/wxt/commit/0591050))
#### ⚠️ Breaking Changes
- ⚠️ Content script CSS used to be output to `assets/<name>.css`, but is now `content-scripts/<name>.css` to match the docs. ([#140](https://github.com/wxt-dev/wxt/pull/140))
## v0.6.6
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.6.5...v0.6.6)
@@ -1870,7 +1823,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.6.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.5.6...v0.6.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.5.6...v0.6.0)
### 🚀 Enhancements
@@ -1882,10 +1835,6 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
- Use the same mode for each build step ([1f6a931](https://github.com/wxt-dev/wxt/commit/1f6a931))
- Disable dev logs in production ([3f260ee](https://github.com/wxt-dev/wxt/commit/3f260ee))
#### ⚠️ Breaking Changes
- ⚠️ The `vite` config option must now be a function. If you were using an object before, change it from `vite: { ... }` to `vite: () => ({ ... })`. ([#121](https://github.com/wxt-dev/wxt/pull/121))
## v0.5.6
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.5.5...v0.5.6)
@@ -1946,7 +1895,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.5.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.4.1...v0.5.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.4.1...v0.5.0)
### 🩹 Fixes
@@ -1969,10 +1918,6 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
- **deps-dev:** Bump @types/node from 20.5.0 to 20.5.9 ([#110](https://github.com/wxt-dev/wxt/pull/110))
- Add entrypoints debug log ([dbd84c8](https://github.com/wxt-dev/wxt/commit/dbd84c8))
#### ⚠️ Breaking Changes
- ⚠️ Change default `publicDir` to `<srcDir>/public` ([5f15f9c](https://github.com/wxt-dev/wxt/commit/5f15f9c))
## v0.4.1
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.4.0...v0.4.1)
@@ -1987,7 +1932,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.4.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.3.2...v0.4.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.3.2...v0.4.0)
### 🚀 Enhancements
@@ -2030,10 +1975,6 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
- Remove explicit icon config from templates ([93bfee0](https://github.com/wxt-dev/wxt/commit/93bfee0))
- Use import aliases in Vue template ([#104](https://github.com/wxt-dev/wxt/pull/104))
#### ⚠️ Breaking Changes
- ⚠️ Use relative path aliases inside `.wxt/tsconfig.json` ([#102](https://github.com/wxt-dev/wxt/pull/102))
## v0.3.2
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.3.1...v0.3.2)
@@ -2100,7 +2041,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.3.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.2.5...v0.3.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.2.5...v0.3.0)
### 🚀 Enhancements
@@ -2124,11 +2065,6 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
- Improve checks against `demo/` extension ([9cc464f](https://github.com/wxt-dev/wxt/commit/9cc464f))
#### ⚠️ Breaking Changes
- ⚠️ Add type safety to `browser.runtime.getURL` ([58a84ec](https://github.com/wxt-dev/wxt/commit/58a84ec))
- ⚠️ Change default `publicDir` to `<rootDir>/public` ([19c0948](https://github.com/wxt-dev/wxt/commit/19c0948))
## v0.2.5
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.2.4...v0.2.5)
@@ -2191,7 +2127,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
## v0.2.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.1.6...v0.2.0)
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/v0.1.6...v0.2.0)
### 🚀 Enhancements
@@ -2209,10 +2145,6 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
- Run `wxt prepare` on `postinstall` ([c1ea9ba](https://github.com/wxt-dev/wxt/commit/c1ea9ba))
- Don't format lockfile ([5c7e041](https://github.com/wxt-dev/wxt/commit/5c7e041))
#### ⚠️ Breaking Changes
- ⚠️ Rename `defineBackgroundScript` to `defineBackground` ([5b48ae9](https://github.com/wxt-dev/wxt/commit/5b48ae9))
## v0.1.6
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.1.5...v0.1.6)
@@ -2421,4 +2353,4 @@ Initial release of WXT. Full support for production builds and initial toolkit f
### 🤖 CI
- Create validation workflow ([#12](https://github.com/wxt-dev/wxt/pull/12))
- Create release workflow ([#13](https://github.com/wxt-dev/wxt/pull/13))
- Create release workflow ([#13](https://github.com/wxt-dev/wxt/pull/13))
+1
View File
@@ -59,6 +59,7 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
- 🦾 Auto-imports
- 🤖 Automated publishing
- 🎨 Frontend framework agnostic: works with Vue, React, Svelte, etc
- 📦 Modular architecture with [WXT modules](https://wxt.dev/guide/go-further/reusable-modules.html#overview)
- 🖍️ Quickly bootstrap a new project
- 📏 Bundle analysis
- ⬇️ Download and bundle remote URL imports
+1 -1
View File
@@ -13,7 +13,7 @@ export default defineBuildConfig([
{
builder: 'mkdist',
input: 'src',
pattern: ['**/*', '!**/__tests__', '!**/*.md', '!virtual'],
pattern: ['**/*', '!**/__tests__', '!**/*.md', '!virtual', '!@types'],
declaration: true,
},
],
@@ -19,6 +19,7 @@ exports[`Auto Imports > eslintrc > "enabled: 8" should output a JSON config file
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"injectScript": true,
"storage": true,
"useAppConfig": true
}
@@ -44,6 +45,7 @@ const globals = {
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"injectScript": true,
"storage": true,
"useAppConfig": true
}
@@ -77,6 +79,7 @@ exports[`Auto Imports > eslintrc > "enabled: true" should output a JSON config f
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"injectScript": true,
"storage": true,
"useAppConfig": true
}
@@ -103,6 +106,7 @@ exports[`Auto Imports > eslintrc > should allow customizing the output 1`] = `
"defineUnlistedScript": "readonly",
"defineWxtPlugin": "readonly",
"fakeBrowser": "readonly",
"injectScript": "readonly",
"storage": "readonly",
"useAppConfig": "readonly"
}
@@ -31,6 +31,7 @@ describe('Auto Imports', () => {
const defineUnlistedScript: typeof import('wxt/sandbox')['defineUnlistedScript']
const defineWxtPlugin: typeof import('wxt/sandbox')['defineWxtPlugin']
const fakeBrowser: typeof import('wxt/testing')['fakeBrowser']
const injectScript: typeof import('wxt/client')['injectScript']
const storage: typeof import('wxt/storage')['storage']
const useAppConfig: typeof import('wxt/client')['useAppConfig']
}
+5
View File
@@ -5,6 +5,7 @@ import { WxtHooks } from '../../src/types';
const hooks: WxtHooks = {
ready: vi.fn(),
'prepare:types': vi.fn(),
'prepare:publicPaths': vi.fn(),
'build:before': vi.fn(),
'build:done': vi.fn(),
'build:manifestGenerated': vi.fn(),
@@ -43,6 +44,7 @@ describe('Hooks', () => {
expectHooksToBeCalled({
ready: true,
'prepare:types': true,
'prepare:publicPaths': true,
'build:before': false,
'build:done': false,
'build:publicAssets': false,
@@ -63,6 +65,7 @@ describe('Hooks', () => {
expectHooksToBeCalled({
ready: true,
'prepare:types': true,
'prepare:publicPaths': true,
'build:before': true,
'build:done': true,
'build:publicAssets': true,
@@ -83,6 +86,7 @@ describe('Hooks', () => {
expectHooksToBeCalled({
ready: true,
'prepare:types': true,
'prepare:publicPaths': true,
'build:before': true,
'build:done': true,
'build:publicAssets': true,
@@ -109,6 +113,7 @@ describe('Hooks', () => {
expectHooksToBeCalled({
ready: true,
'prepare:types': true,
'prepare:publicPaths': true,
'build:before': true,
'build:done': true,
'build:publicAssets': true,
@@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest';
import { TestProject } from '../utils';
describe.each(['jiti', 'vite-runtime', 'vite-node'] as const)(
describe.each(['vite-node', 'jiti'] as const)(
'Manifest Content (Vite runtime? %s)',
(entrypointImporter) => {
it.each([
+8 -16
View File
@@ -192,10 +192,8 @@ describe('Module Helpers', () => {
const expectedText = addPluginModule(project);
await project.build({
experimental: {
// reduce build output when comparing test failures
includeBrowserPolyfill: false,
},
// reduce build output when comparing test failures
extensionApi: 'chrome',
});
await expect(project.serializeOutput()).resolves.toContain(expectedText);
@@ -214,10 +212,8 @@ describe('Module Helpers', () => {
const expectedText = addPluginModule(project);
await project.build({
experimental: {
// reduce build output when comparing test failures
includeBrowserPolyfill: false,
},
// reduce build output when comparing test failures
extensionApi: 'chrome',
});
await expect(project.serializeOutput()).resolves.toContain(expectedText);
@@ -237,10 +233,8 @@ describe('Module Helpers', () => {
const expectedText = addPluginModule(project);
await project.build({
experimental: {
// reduce build output when comparing test failures
includeBrowserPolyfill: false,
},
// reduce build output when comparing test failures
extensionApi: 'chrome',
});
await expect(project.serializeOutput()).resolves.toContain(expectedText);
@@ -255,10 +249,8 @@ describe('Module Helpers', () => {
const expectedText = addPluginModule(project);
await project.build({
experimental: {
// reduce build output when comparing test failures
includeBrowserPolyfill: false,
},
// reduce build output when comparing test failures
extensionApi: 'chrome',
});
await expect(project.serializeOutput()).resolves.toContain(expectedText);
+20 -12
View File
@@ -262,10 +262,9 @@ describe('Output Directory Structure', () => {
project.addFile('entrypoints/popup/main.ts', `logHello('popup')`);
await project.build({
experimental: {
// Simplify the build output for comparison
includeBrowserPolyfill: false,
},
// Simplify the build output for comparison
extensionApi: 'chrome',
vite: () => ({
build: {
// Make output for snapshot readible
@@ -278,9 +277,10 @@ describe('Output Directory Structure', () => {
.toMatchInlineSnapshot(`
".output/chrome-mv3/background.js
----------------------------------------
var _a, _b;
import { l as logHello, i as initPlugins } from "./chunks/_virtual_wxt-plugins-OjKtWpmY.js";
function defineBackground(arg) {
if (typeof arg === "function") return { main: arg };
if (arg == null || typeof arg === "function") return { main: arg };
return arg;
}
const definition = defineBackground({
@@ -289,7 +289,11 @@ describe('Output Directory Structure', () => {
logHello("background");
}
});
chrome;
// @ts-expect-error
((_b = (_a = globalThis.browser) == null ? void 0 : _a.runtime) == null ? void 0 : _b.id) == null ? globalThis.chrome : (
// @ts-expect-error
globalThis.browser
);
function print(method, ...args) {
return;
}
@@ -343,10 +347,9 @@ describe('Output Directory Structure', () => {
project.addFile('entrypoints/popup/main.ts', `logHello('popup')`);
await project.build({
experimental: {
// Simplify the build output for comparison
includeBrowserPolyfill: false,
},
// Simplify the build output for comparison
extensionApi: 'chrome',
vite: () => ({
build: {
// Make output for snapshot readible
@@ -361,8 +364,9 @@ describe('Output Directory Structure', () => {
----------------------------------------
var _background = function() {
"use strict";
var _a, _b;
function defineBackground(arg) {
if (typeof arg === "function") return { main: arg };
if (arg == null || typeof arg === "function") return { main: arg };
return arg;
}
function logHello(name) {
@@ -377,7 +381,11 @@ describe('Output Directory Structure', () => {
_background;
function initPlugins() {
}
chrome;
// @ts-expect-error
((_b = (_a = globalThis.browser) == null ? void 0 : _a.runtime) == null ? void 0 : _b.id) == null ? globalThis.chrome : (
// @ts-expect-error
globalThis.browser
);
function print(method, ...args) {
return;
}
+2 -2
View File
@@ -3,7 +3,7 @@ import { TestProject } from '../utils';
describe('Remote Code', () => {
it('should download "url:*" modules and include them in the final bundle', async () => {
const url = 'https://code.jquery.com/jquery-3.7.1.slim.min.js';
const url = 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js';
const project = new TestProject();
project.addFile(
'entrypoints/popup.ts',
@@ -16,7 +16,7 @@ describe('Remote Code', () => {
const output = await project.serializeFile('.output/chrome-mv3/popup.js');
expect(output).toContain(
// Some text that will hopefully be in future versions of this script
'jQuery v3.7.1',
'lodash.com',
);
expect(output).not.toContain(url);
expect(
@@ -53,7 +53,7 @@ describe('TypeScript Project', () => {
| "/popup.html"
| "/sandbox.html"
type HtmlPublicPath = Extract<PublicPath, \`\${string}.html\`>
export interface WxtRuntime extends Runtime.Static {
export interface WxtRuntime {
getURL(path: PublicPath): string;
getURL(path: \`\${HtmlPublicPath}\${string}\`): string;
}
+2 -4
View File
@@ -88,7 +88,7 @@ describe('User Config', () => {
`);
});
it('should exclude the polyfill when the experimental setting is set to false', async () => {
it('should exclude the polyfill when extensionApi="chrome"', async () => {
const buildBackground = async (config?: InlineConfig) => {
const background = `export default defineBackground(() => console.log(browser.runtime.id));`;
const projectWithPolyfill = new TestProject();
@@ -101,9 +101,7 @@ describe('User Config', () => {
const withPolyfill = await buildBackground();
const withoutPolyfill = await buildBackground({
experimental: {
includeBrowserPolyfill: false,
},
extensionApi: 'chrome',
});
expect(withoutPolyfill).not.toBe(withPolyfill);
});
+24 -10
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.18.15",
"version": "0.19.4",
"description": "Next gen framework for developing web extensions",
"repository": {
"type": "git",
@@ -46,8 +46,12 @@
"default": "./dist/sandbox/index.mjs"
},
"./browser": {
"types": "./dist/browser.d.ts",
"default": "./dist/browser.mjs"
"types": "./dist/browser/index.d.ts",
"default": "./dist/browser/index.mjs"
},
"./browser/chrome": {
"types": "./dist/browser/chrome.d.ts",
"import": "./dist/browser/chrome.mjs"
},
"./testing": {
"types": "./dist/testing/index.d.ts",
@@ -68,7 +72,7 @@
"scripts": {
"wxt": "tsx src/cli/index.ts",
"build": "buildc -- unbuild",
"check": "buildc --deps-only -- run-s -c check:*",
"check": "pnpm build && run-s -c check:*",
"check:default": "check",
"check:tsc-virtual": "tsc --noEmit -p src/virtual",
"test": "buildc --deps-only -- vitest",
@@ -87,6 +91,7 @@
"cac": "^6.7.14",
"chokidar": "^3.6.0",
"ci-info": "^4.0.0",
"consola": "^3.2.3",
"defu": "^6.1.4",
"dequal": "^2.0.3",
"esbuild": "^0.23.0",
@@ -112,19 +117,20 @@
"picocolors": "^1.0.1",
"prompts": "^2.4.2",
"publish-browser-extension": "^2.1.3",
"unimport": "^3.9.0",
"vite": "^5.3.4",
"vite-node": "^2.0.0",
"unimport": "^3.9.1",
"vite": "^5.3.5",
"vite-node": "^2.0.4",
"web-ext-run": "^0.2.1",
"webextension-polyfill": "^0.12.0"
},
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"@faker-js/faker": "^8.4.1",
"@types/chrome": "^0.0.269",
"@types/fs-extra": "^11.0.4",
"@types/lodash.merge": "^4.6.9",
"@types/natural-compare": "^1.4.3",
"@types/node": "^20.14.11",
"@types/node": "^20.14.12",
"@types/normalize-path": "^3.0.2",
"@types/prompts": "^2.4.9",
"execa": "^9.3.0",
@@ -134,9 +140,17 @@
"p-map": "^7.0.2",
"publint": "^0.2.9",
"tsx": "4.15.7",
"typescript": "^5.5.3",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"vitest": "^2.0.3",
"vitest": "^2.0.4",
"vitest-plugin-random-seed": "^1.1.0"
},
"peerDependencies": {
"@types/chrome": "*"
},
"peerDependenciesMeta": {
"@types/chrome": {
"optional": true
}
}
}
@@ -49,8 +49,3 @@ declare module 'web-ext-run/util/logger' {
}
export const consoleStream: IConsoleStream;
}
declare module 'wxt/browser' {
// Overridden when types are generated per project
export type PublicPath = string;
}
+54 -10
View File
@@ -1,6 +1,6 @@
import { fakeBrowser } from '@webext-core/fake-browser';
import { describe, it, expect, beforeEach, vi, expectTypeOf } from 'vitest';
import { browser } from '../browser';
import { browser } from 'wxt/browser';
import { WxtStorageItem, storage } from '../storage';
/**
@@ -11,6 +11,14 @@ async function waitForMigrations() {
return new Promise((res) => setTimeout(res));
}
/**
* Same as `waitForMigrations`, the fake browser being synchronous means the
* `init` logic is finished by the next task in the event queue.
*/
async function waitForInit() {
return new Promise((res) => setTimeout(res));
}
describe('Storage Utils', () => {
beforeEach(() => {
fakeBrowser.reset();
@@ -813,20 +821,56 @@ describe('Storage Utils', () => {
});
});
describe('defaultValue', () => {
it('should return the default value when provided', () => {
const defaultValue = 123;
const item = storage.defineItem(`local:test`, {
defaultValue,
describe.each(['fallback', 'defaultValue'] as const)(
'%s option',
(fallbackKey) => {
it('should return the default value when provided', () => {
const fallback = 123;
const item = storage.defineItem(`local:test`, {
[fallbackKey]: fallback,
});
expect(item.fallback).toBe(fallback);
expect(item.defaultValue).toBe(fallback);
});
expect(item.defaultValue).toBe(defaultValue);
it('should return null when not provided', () => {
const item = storage.defineItem<number>(`local:test`);
expect(item.fallback).toBeNull();
expect(item.defaultValue).toBeNull();
});
},
);
describe('init option', () => {
it('should only call init once (per JS context) when calling getValue successively, avoiding race conditions', async () => {
const expected = 1;
const init = vi
.fn()
.mockResolvedValueOnce(expected)
.mockResolvedValue('not' + expected);
const item = storage.defineItem('local:test', { init });
await waitForInit();
const p1 = item.getValue();
const p2 = item.getValue();
await expect(p1).resolves.toBe(expected);
await expect(p2).resolves.toBe(expected);
expect(init).toBeCalledTimes(1);
});
it('should return null when not provided', () => {
const item = storage.defineItem<number>(`local:test`);
it('should initialize the value in storage immediately', async () => {
const expected = 1;
const init = vi.fn().mockReturnValue(expected);
storage.defineItem('local:test', { init });
expect(item.defaultValue).toBeNull();
await waitForInit();
await expect(storage.getItem('local:test')).resolves.toBe(expected);
});
});
+23
View File
@@ -0,0 +1,23 @@
/// <reference types="chrome" />
import type { WxtRuntime, WxtI18n } from './index';
/**
* EXPERIMENTAL
*
* Includes the `chrome` API and types when using `extensionApi: 'chrome'`.
*
* @module wxt/browser/chrome
*/
export type Chrome = typeof chrome;
export type WxtBrowser = Omit<Chrome, 'runtime' | 'i18n'> & {
runtime: WxtRuntime & Omit<Chrome['runtime'], 'getURL'>;
i18n: WxtI18n & Omit<Chrome['i18n'], 'getMessage'>;
};
export const browser: WxtBrowser =
// @ts-expect-error
globalThis.browser?.runtime?.id == null
? globalThis.chrome
: // @ts-expect-error
globalThis.browser;
@@ -1,18 +1,21 @@
/**
* Includes the `browser` API and types when using `extensionApi: 'webextension-polyfill'` (the default).
*
* @module wxt/browser
*/
import originalBrowser, { Browser, Runtime, I18n } from 'webextension-polyfill';
export interface AugmentedBrowser extends Browser {
runtime: WxtRuntime;
i18n: WxtI18n;
}
import originalBrowser, { Browser } from 'webextension-polyfill';
export interface WxtRuntime extends Runtime.Static {
export type AugmentedBrowser = Omit<Browser, 'runtime' | 'i18n'> & {
runtime: WxtRuntime & Omit<Browser['runtime'], 'getURL'>;
i18n: WxtI18n & Omit<Browser['i18n'], 'getMessage'>;
};
export interface WxtRuntime {
// Overriden per-project
}
export interface WxtI18n extends I18n.Static {
export interface WxtI18n {
// Overriden per-project
}
@@ -0,0 +1,59 @@
/** @vitest-environment happy-dom */
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ContentScriptContext } from '..';
import { fakeBrowser } from '@webext-core/fake-browser';
import { sleep } from '../../../core/utils/time';
describe('Content Script Context', () => {
beforeEach(() => {
vi.useRealTimers();
fakeBrowser.runtime.id = 'anything';
});
it("should recognize when the content script has lost it's connection to the extension API", () => {
const ctx = new ContentScriptContext('test');
const onInvalidated = vi.fn();
ctx.onInvalidated(onInvalidated);
// @ts-expect-error
delete fakeBrowser.runtime.id;
const isValid = ctx.isValid;
expect(onInvalidated).toBeCalled();
expect(isValid).toBe(false);
});
it('should invalidate the current content script when a new context is created', async () => {
const name = 'test';
const onInvalidated = vi.fn();
const ctx = new ContentScriptContext(name);
ctx.onInvalidated(onInvalidated);
// Wait for events to run before next tick next tick
await sleep(0);
// Create a new context after first is initialized, and wait for it to initialize
new ContentScriptContext(name);
await sleep(0);
expect(onInvalidated).toBeCalled();
expect(ctx.isValid).toBe(false);
});
it('should not invalidate the current content script when a new context is created with a different name', async () => {
const onInvalidated = vi.fn();
const ctx = new ContentScriptContext('test1');
ctx.onInvalidated(onInvalidated);
// Wait for events to run before next tick next tick
await sleep(0);
// Create a new context after first is initialized, and wait for it to initialize
new ContentScriptContext('test2');
await sleep(0);
expect(onInvalidated).not.toBeCalled();
expect(ctx.isValid).toBe(true);
});
});
@@ -1,5 +1,5 @@
import { ContentScriptDefinition } from '../../types';
import { browser } from '../../browser';
import { browser } from 'wxt/browser';
import { logger } from '../../sandbox/utils/logger';
import { WxtLocationChangeEvent, getUniqueEventName } from './custom-events';
import { createLocationWatcher } from './location-watcher';
@@ -46,13 +46,13 @@ export class ContentScriptContext implements AbortController {
public readonly options?: Omit<ContentScriptDefinition, 'main'>,
) {
this.#abortController = new AbortController();
if (this.#isTopFrame) {
this.#listenForNewerScripts({ ignoreFirstEvent: true });
this.#stopOldScripts();
}
this.setTimeout(() => {
// Run on next tick so the listener it adds isn't triggered by stopOldScript
} else {
this.#listenForNewerScripts();
});
}
}
get signal() {
@@ -224,12 +224,18 @@ export class ContentScriptContext implements AbortController {
);
}
#listenForNewerScripts() {
#listenForNewerScripts(options?: { ignoreFirstEvent?: boolean }) {
let isFirst = true;
const cb = (event: MessageEvent) => {
if (
event.data?.type === ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE &&
event.data?.contentScriptName === this.contentScriptName
) {
const wasFirst = isFirst;
isFirst = false;
if (wasFirst && options?.ignoreFirstEvent) return;
this.notifyInvalidated();
}
};
@@ -1,4 +1,4 @@
import { browser } from '../../browser';
import { browser } from 'wxt/browser';
export class WxtLocationChangeEvent extends Event {
static EVENT_NAME = getUniqueEventName('wxt:locationchange');
@@ -22,5 +22,5 @@ export function getUniqueEventName(eventName: string): string {
? 'build'
: import.meta.env.ENTRYPOINT;
return `${browser.runtime.id}:${entrypointName}:${eventName}`;
return `${browser?.runtime?.id}:${entrypointName}:${eventName}`;
}
@@ -1,4 +1,4 @@
import { browser } from '../../../browser';
import { browser } from 'wxt/browser';
import { ContentScriptContext } from '..';
import {
ContentScriptAnchoredOptions,
@@ -62,6 +62,7 @@ export function createIframeUi<TMounted>(
const wrapper = document.createElement('div');
wrapper.setAttribute('data-wxt-iframe', '');
const iframe = document.createElement('iframe');
// @ts-expect-error: getURL is defined per-project, but not inside the package
iframe.src = browser.runtime.getURL(options.page);
wrapper.appendChild(iframe);
@@ -258,6 +259,7 @@ function mountUi(
* Load the CSS for the current entrypoint.
*/
async function loadCss(): Promise<string> {
// @ts-expect-error: getURL is defined per-project, but not inside the package
const url = browser.runtime.getURL(
`/content-scripts/${import.meta.env.ENTRYPOINT}.css`,
);
@@ -83,7 +83,8 @@ export type IframeContentScriptUiOptions<TMounted> =
* The path to the HTML page that will be shown in the iframe. This string is passed into
* `browser.runtime.getURL`.
*/
page: PublicPath;
// @ts-expect-error: HtmlPublicPath is generated per-project
page: import('wxt/browser').HtmlPublicPath;
/**
* Callback executed when mounting the UI. Use this function to customize the iframe or wrapper
* element's appearance. It is called every time `ui.mount()` is called.
@@ -183,9 +184,9 @@ export interface ContentScriptModalPositioningOptions {
}
/**
* Choose between `"inline"`, `"overlay"`, or `"modal" `types.
* Choose between `"inline"`, `"overlay"`, or `"modal"` positions.
*
* ![Visualization of different types](https://wxt.dev/content-script-ui-type.png)
* ![Visualization of different types](https://wxt.dev/content-script-ui-position.png)
*/
export type ContentScriptPositioningOptions =
| ContentScriptInlinePositioningOptions
+1
View File
@@ -5,3 +5,4 @@
*/
export * from './content-scripts';
export * from './app-config';
export * from './inject-script';
+47
View File
@@ -0,0 +1,47 @@
import { browser } from 'wxt/browser';
export type ScriptPublicPath = Extract<
// @ts-expect-error: PublicPath is generated per-project
import('wxt/browser').PublicPath,
`${string}.js`
>;
/**
* This function can only be called inside content scripts.
*
* Inject an unlisted script into the page. Scripts are added to the `<head>`
* element or `document.documentElement` if there is no head.
*
* Make sure to add the injected script to your manifest's
* `web_accessible_resources`.
*/
export async function injectScript(
path: ScriptPublicPath,
options?: InjectScriptOptions,
): Promise<void> {
// @ts-expect-error: getURL is defined per-project, but not inside the package
const url = browser.runtime.getURL(path);
const script = document.createElement('script');
if (browser.runtime.getManifest().manifest_version === 2) {
// MV2 requires using an inline script
script.innerHTML = await fetch(url).then((res) => res.text());
} else {
// MV3 requires using src
script.src = url;
}
if (!options?.keepInDom) {
script.onload = () => script.remove();
}
(document.head ?? document.documentElement).append(script);
}
export interface InjectScriptOptions {
/**
* By default, the injected script is removed from the DOM after being
* injected. To disable this behavior, set this flag to true.
*/
keepInDom?: boolean;
}
+15 -25
View File
@@ -35,7 +35,9 @@ export async function createViteBuilder(
/**
* Returns the base vite config shared by all builds based on the inline and user config.
*/
const getBaseConfig = async () => {
const getBaseConfig = async (baseConfigOptions?: {
excludeAnalysisPlugin?: boolean;
}) => {
const config: vite.InlineConfig = await wxtConfig.vite(wxtConfig.env);
config.root = wxtConfig.root;
@@ -66,12 +68,17 @@ export async function createViteBuilder(
wxtPlugins.tsconfigPaths(wxtConfig),
wxtPlugins.noopBackground(),
wxtPlugins.globals(wxtConfig),
wxtPlugins.excludeBrowserPolyfill(wxtConfig),
wxtPlugins.resolveExtensionApi(wxtConfig),
wxtPlugins.defineImportMeta(),
wxtPlugins.wxtPluginLoader(wxtConfig),
wxtPlugins.resolveAppConfig(wxtConfig),
);
if (wxtConfig.analysis.enabled) {
if (
wxtConfig.analysis.enabled &&
// If included, vite-node entrypoint loader will increment the
// bundleAnalysis's internal build index tracker, which we don't want
!baseConfigOptions?.excludeAnalysisPlugin
) {
config.plugins.push(wxtPlugins.bundleAnalysis(wxtConfig));
}
@@ -209,38 +216,21 @@ export async function createViteBuilder(
name: 'Vite',
version: vite.version,
async importEntrypoint(path) {
switch (wxtConfig.experimental.entrypointImporter) {
switch (wxtConfig.entrypointLoader) {
default:
case 'jiti': {
return await importEntrypointFile(path);
}
case 'vite-runtime': {
const baseConfig = await getBaseConfig();
const envConfig: vite.InlineConfig = {
plugins: [
wxtPlugins.webextensionPolyfillMock(wxtConfig),
wxtPlugins.removeEntrypointMainFunction(wxtConfig, path),
],
};
const config = vite.mergeConfig(baseConfig, envConfig);
const server = await vite.createServer(config);
await server.listen();
const runtime = await vite.createViteRuntime(server, { hmr: false });
const module = await runtime.executeUrl(path);
await server.close();
return module.default;
}
case 'vite-node': {
const baseConfig = await getBaseConfig();
const baseConfig = await getBaseConfig({
excludeAnalysisPlugin: true,
});
// Disable dep optimization, as recommended by vite-node's README
baseConfig.optimizeDeps ??= {};
baseConfig.optimizeDeps.noDiscovery = true;
baseConfig.optimizeDeps.include = [];
const envConfig: vite.InlineConfig = {
plugins: [
wxtPlugins.webextensionPolyfillMock(wxtConfig),
wxtPlugins.removeEntrypointMainFunction(wxtConfig, path),
],
plugins: [wxtPlugins.removeEntrypointMainFunction(wxtConfig, path)],
};
const config = vite.mergeConfig(baseConfig, envConfig);
const server = await vite.createServer(config);
@@ -1,33 +0,0 @@
import { ResolvedConfig } from '../../../../types';
import type * as vite from 'vite';
/**
* Apply the experimental config for disabling the polyfill. It works by aliasing the
* `webextension-polyfill` module to a virtual module and exporting the `chrome` global from the
* virtual module.
*/
export function excludeBrowserPolyfill(config: ResolvedConfig): vite.Plugin {
const virtualId = 'virtual:wxt-webextension-polyfill-disabled';
return {
name: 'wxt:exclude-browser-polyfill',
config() {
// Only apply the config if we're disabling the polyfill
if (config.experimental.includeBrowserPolyfill) return;
return {
resolve: {
alias: {
'webextension-polyfill': virtualId,
},
},
};
},
load(id) {
if (id === virtualId) {
// Use chrome instead of the polyfill when disabled.
return 'export default chrome';
}
},
};
}
@@ -0,0 +1,37 @@
import path from 'node:path';
import type * as vite from 'vite';
import { ResolvedConfig } from '../../../../types';
/**
* Mock `webextension-polyfill`, `wxt/browser`, and `wxt/browser/*` by inlining
* all dependencies that import them and adding a custom alias so that Vite
* resolves to a mocked version of the module.
*
* TODO: Detect non-wxt dependencies (like `@webext-core/*`) that import `webextension-polyfill` via
* `npm list` and inline them automatically.
*/
export function extensionApiMock(config: ResolvedConfig): vite.PluginOption {
return {
name: 'wxt:extension-api-mock',
config() {
const replacement = path.resolve(
config.wxtModuleDir,
'dist/virtual/mock-browser',
);
return {
resolve: {
alias: [
{ find: 'webextension-polyfill', replacement },
// wxt/browser, wxt/browser/...
{ find: /^wxt\/browser.*/, replacement },
],
},
ssr: {
// Inline all WXT modules so vite processes them so the aliases can
// be resolved
noExternal: ['wxt'],
},
};
},
};
}
@@ -8,8 +8,8 @@ export * from './noopBackground';
export * from './cssEntrypoints';
export * from './bundleAnalysis';
export * from './globals';
export * from './webextensionPolyfillMock';
export * from './excludeBrowserPolyfill';
export * from './extensionApiMock';
export * from './resolveExtensionApi';
export * from './entrypointGroupGlobals';
export * from './defineImportMeta';
export * from './removeEntrypointMainFunction';
@@ -14,8 +14,17 @@ export function removeEntrypointMainFunction(
const absPath = normalizePath(resolve(config.root, path));
return {
name: 'wxt:remove-entrypoint-main-function',
transform(code, id) {
if (id === absPath) return removeMainFunctionCode(code);
transform: {
order: 'pre',
handler(code, id) {
if (id === absPath) {
const newCode = removeMainFunctionCode(code);
config.logger.debug('vite-node transformed entrypoint', path);
config.logger.debug(`Original:\n---\n${code}\n---`);
config.logger.debug(`Transformed:\n---\n${newCode.code}\n---`);
return newCode;
}
},
},
};
}
@@ -0,0 +1,27 @@
import { ResolvedConfig } from '../../../../types';
import type * as vite from 'vite';
/**
* Apply the experimental config for which extension API is used. This only
* effects the extension API included at RUNTIME - during development, types
* depend on the import.
*
* NOTE: this only works if we import `wxt/browser` instead of using the relative path.
*/
export function resolveExtensionApi(config: ResolvedConfig): vite.Plugin {
return {
name: 'wxt:resolve-extension-api',
config() {
// Only apply the config if we're disabling the polyfill
if (config.extensionApi === 'webextension-polyfill') return;
return {
resolve: {
alias: [
{ find: /^wxt\/browser$/, replacement: 'wxt/browser/chrome' },
],
},
};
},
};
}
@@ -1,41 +0,0 @@
import path from 'node:path';
import type * as vite from 'vite';
import { ResolvedConfig } from '../../../../types';
/**
* Mock `webextension-polyfill` by inlining all dependencies that import it and adding a custom
* alias so that Vite resolves to a mocked version of the module.
*
* There are two ways to mark a module as inline:
* 1. Use partial file paths ("wxt/dist/browser.js") in the `test.server.deps.inline` option.
* 2. Use module names ("wxt" or "@webext-core/messaging") in the `ssr.noExternalize` option.
*
* This plugin uses the second approach since it's a little more intuative to understand.
*
* TODO: Detect non-wxt dependencies (like `@webext-core/*`) that import `webextension-polyfill` via
* `npm list` and inline them automatically.
*/
export function webextensionPolyfillMock(
config: ResolvedConfig,
): vite.PluginOption {
return {
name: 'wxt:testing-inline-deps',
config() {
return {
resolve: {
alias: {
// Alias to use a mocked version of the polyfill
'webextension-polyfill': path.resolve(
config.wxtModuleDir,
'dist/virtual/mock-browser',
),
},
},
ssr: {
// Inline all WXT modules
noExternal: ['wxt'],
},
};
},
};
}
@@ -1,34 +1,41 @@
lockfileVersion: '6.0'
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
mime-types:
specifier: 2.1.35
version: 2.1.35
importers:
devDependencies:
flatten:
specifier: 1.0.3
version: 1.0.3
.:
dependencies:
mime-types:
specifier: 2.1.35
version: 2.1.35
devDependencies:
flatten:
specifier: 1.0.3
version: 1.0.3
packages:
/flatten@1.0.3:
flatten@1.0.3:
resolution: {integrity: sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==}
deprecated: flatten is deprecated in favor of utility frameworks such as lodash.
dev: true
/mime-db@1.52.0:
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: false
/mime-types@2.1.35:
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
snapshots:
flatten@1.0.3: {}
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
dev: false
@@ -6,23 +6,28 @@ describe('Transform Utils', () => {
it.each(['defineBackground', 'defineUnlistedScript'])(
'should remove the first arrow function argument for %s',
(def) => {
const input = `export default ${def}(() => {
const input = `
export default ${def}(() => {
console.log();
})`;
const expected = `export default ${def}(() => {})`;
})
`;
const expected = `export default ${def}();`;
const actual = removeMainFunctionCode(input).code;
expect(actual).toEqual(expected);
},
);
it.each(['defineBackground', 'defineUnlistedScript'])(
'should remove the first function argument for %s',
(def) => {
const input = `export default ${def}(function () {
const input = `
export default ${def}(function () {
console.log();
})`;
const expected = `export default ${def}(function () {})`;
})
`;
const expected = `export default ${def}();`;
const actual = removeMainFunctionCode(input).code;
@@ -35,10 +40,12 @@ describe('Transform Utils', () => {
'defineContentScript',
'defineUnlistedScript',
])('should remove the main field from %s', (def) => {
const input = `export default ${def}({
asdf: "asdf",
main: () => {},
})`;
const input = `
export default ${def}({
asdf: "asdf",
main: () => {},
})
`;
const expected = `export default ${def}({
asdf: "asdf"
})`;
@@ -47,5 +54,88 @@ describe('Transform Utils', () => {
expect(actual).toEqual(expected);
});
it('should remove unused imports', () => {
const input = `
import { defineBackground } from "wxt/sandbox"
import { test1 } from "somewhere1"
import test2 from "somewhere2"
export default defineBackground(() => {})
`;
const expected = `import { defineBackground } from "wxt/sandbox"
export default defineBackground();`;
const actual = removeMainFunctionCode(input).code;
expect(actual).toEqual(expected);
});
it('should remove explict side-effect imports', () => {
const input = `
import { defineBackground } from "wxt/sandbox"
import "my-polyfill"
import "./style.css"
export default defineBackground(() => {})
`;
const expected = `import { defineBackground } from "wxt/sandbox"
export default defineBackground();`;
const actual = removeMainFunctionCode(input).code;
expect(actual).toEqual(expected);
});
it("should remove any functions delcared outside the main function that aren't used", () => {
const input = `
function getMatches() {
return ["*://*/*"]
}
function unused1() {}
function unused2() {
unused1();
}
export default defineContentScript({
matches: getMatches(),
main: () => {},
})
`;
const expected = `function getMatches() {
return ["*://*/*"]
}
export default defineContentScript({
matches: getMatches()
})`;
const actual = removeMainFunctionCode(input).code;
expect(actual).toEqual(expected);
});
it("should remove any variables delcared outside the main function that aren't used", () => {
const input = `
const unused1 = "a", matches = ["*://*/*"];
let unused2 = unused1 + "b";
export default defineContentScript({
matches,
main: () => {}
})
`;
const expected = `const matches = ["*://*/*"];
export default defineContentScript({
matches
})`;
const actual = removeMainFunctionCode(input).code;
expect(actual).toEqual(expected);
});
});
});
@@ -28,6 +28,7 @@ import { VIRTUAL_NOOP_BACKGROUND_MODULE_ID } from '../../utils/constants';
import { CSS_EXTENSIONS_PATTERN } from '../../utils/paths';
import pc from 'picocolors';
import { wxt } from '../../wxt';
import { createExtensionEnvironment } from '../environments';
/**
* Return entrypoints and their configuration by looking through the project's files.
@@ -35,7 +36,17 @@ import { wxt } from '../../wxt';
export async function findEntrypoints(): Promise<Entrypoint[]> {
// Make sure required TSConfig file exists to load dependencies
await fs.mkdir(wxt.config.wxtDir, { recursive: true });
await fs.writeJson(resolve(wxt.config.wxtDir, 'tsconfig.json'), {});
try {
await fs.writeJson(
resolve(wxt.config.wxtDir, 'tsconfig.json'),
{},
{ flag: 'wx' },
);
} catch (err) {
if (!(err instanceof Error) || !('code' in err) || err.code !== 'EEXIST') {
throw err;
}
}
const relativePaths = await glob(Object.keys(PATH_GLOB_TO_TYPE_MAP), {
cwd: wxt.config.entrypointsDir,
@@ -72,47 +83,50 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
// Import entrypoints to get their config
let hasBackground = false;
const entrypoints: Entrypoint[] = await Promise.all(
entrypointInfos.map(async (info): Promise<Entrypoint> => {
const { type } = info;
switch (type) {
case 'popup':
return await getPopupEntrypoint(info);
case 'sidepanel':
return await getSidepanelEntrypoint(info);
case 'options':
return await getOptionsEntrypoint(info);
case 'background':
hasBackground = true;
return await getBackgroundEntrypoint(info);
case 'content-script':
return await getContentScriptEntrypoint(info);
case 'unlisted-page':
return await getUnlistedPageEntrypoint(info);
case 'unlisted-script':
return await getUnlistedScriptEntrypoint(info);
case 'content-script-style':
return {
...info,
type,
outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
options: {
include: undefined,
exclude: undefined,
},
};
default:
return {
...info,
type,
outputDir: wxt.config.outDir,
options: {
include: undefined,
exclude: undefined,
},
};
}
}),
const env = createExtensionEnvironment();
const entrypoints: Entrypoint[] = await env.run(() =>
Promise.all(
entrypointInfos.map(async (info): Promise<Entrypoint> => {
const { type } = info;
switch (type) {
case 'popup':
return await getPopupEntrypoint(info);
case 'sidepanel':
return await getSidepanelEntrypoint(info);
case 'options':
return await getOptionsEntrypoint(info);
case 'background':
hasBackground = true;
return await getBackgroundEntrypoint(info);
case 'content-script':
return await getContentScriptEntrypoint(info);
case 'unlisted-page':
return await getUnlistedPageEntrypoint(info);
case 'unlisted-script':
return await getUnlistedScriptEntrypoint(info);
case 'content-script-style':
return {
...info,
type,
outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
options: {
include: undefined,
exclude: undefined,
},
};
default:
return {
...info,
type,
outputDir: wxt.config.outDir,
options: {
include: undefined,
exclude: undefined,
},
};
}
}),
),
);
if (wxt.config.command === 'serve' && !hasBackground) {
@@ -42,6 +42,11 @@ export async function generateTypesDir(
// import.meta.env.*
entries.push(await getGlobalsDeclarationEntry());
// @types/chrome
if (wxt.config.extensionApi === 'chrome') {
entries.push({ module: '@types/chrome' });
}
// tsconfig.json
entries.push(await getTsConfigEntry());
@@ -70,7 +75,7 @@ export async function generateTypesDir(
async function getPathsDeclarationEntry(
entrypoints: Entrypoint[],
): Promise<WxtDirFileEntry> {
const unions = entrypoints
const paths = entrypoints
.map((entry) =>
getEntrypointBundlePath(
entry,
@@ -78,10 +83,14 @@ async function getPathsDeclarationEntry(
isHtmlEntrypoint(entry) ? '.html' : '.js',
),
)
.concat(await getPublicFiles())
.concat(await getPublicFiles());
await wxt.hooks.callHook('prepare:publicPaths', wxt, paths);
const unions = paths
.map(normalizePath)
.map((path) => ` | "/${path}"`)
.sort()
.map((path) => ` | "/${path}"`)
.join('\n');
const template = `// Generated by wxt
@@ -91,7 +100,7 @@ declare module "wxt/browser" {
export type PublicPath =
{{ union }}
type HtmlPublicPath = Extract<PublicPath, \`\${string}.html\`>
export interface WxtRuntime extends Runtime.Static {
export interface WxtRuntime {
getURL(path: PublicPath): string;
getURL(path: \`\${HtmlPublicPath}\${string}\`): string;
}
@@ -1,4 +1,4 @@
import type { Manifest } from '../../../browser';
import type { Manifest } from 'wxt/browser';
import { BuildOutput, Entrypoint, EntrypointGroup } from '../../../types';
import { generateTypesDir } from './generate-wxt-dir';
import { buildEntrypoints } from './build-entrypoints';
@@ -146,6 +146,8 @@ export async function resolveConfig(
{},
);
const extensionApi = mergedConfig.extensionApi ?? 'webextension-polyfill';
return {
browser,
command,
@@ -155,7 +157,13 @@ export async function resolveConfig(
filterEntrypoints,
env,
fsCache: createFsCache(wxtDir),
imports: await getUnimportOptions(wxtDir, srcDir, logger, mergedConfig),
imports: await getUnimportOptions(
wxtDir,
srcDir,
logger,
extensionApi,
mergedConfig,
),
logger,
manifest: await resolveManifestConfig(env, mergedConfig.manifest),
manifestVersion,
@@ -174,10 +182,9 @@ export async function resolveConfig(
analysis: resolveAnalysisConfig(root, mergedConfig),
userConfigMetadata: userConfigMetadata ?? {},
alias,
experimental: defu(mergedConfig.experimental, {
includeBrowserPolyfill: true,
entrypointImporter: 'jiti' as const,
}),
extensionApi,
entrypointLoader: mergedConfig.entrypointLoader ?? 'vite-node',
experimental: defu(mergedConfig.experimental, {}),
dev: {
server: devServerConfig,
reloadCommand,
@@ -306,6 +313,7 @@ async function getUnimportOptions(
wxtDir: string,
srcDir: string,
logger: Logger,
extensionApi: ResolvedConfig['extensionApi'],
config: InlineConfig,
): Promise<WxtResolvedUnimportOptions | false> {
if (config.imports === false) return false;
@@ -324,7 +332,10 @@ async function getUnimportOptions(
// ignored.
ignore: ['options'],
},
{ package: 'wxt/browser' },
{
package:
extensionApi === 'chrome' ? 'wxt/browser/chrome' : 'wxt/browser',
},
{ package: 'wxt/sandbox' },
{ package: 'wxt/storage' },
],
@@ -1,4 +1,4 @@
import type { Manifest, Scripting } from '../../browser';
import type { Manifest, Scripting } from 'wxt/browser';
import { ContentScriptEntrypoint, ResolvedConfig } from '../../types';
import { getEntrypointBundlePath } from './entrypoints';
@@ -0,0 +1,21 @@
import { parseHTML } from 'linkedom';
import { createEnvironment, Environment, EnvGlobals } from './environment';
export function createBrowserEnvironment(): Environment {
return createEnvironment(getBrowserEnvironmentGlobals);
}
export function getBrowserEnvironmentGlobals(): EnvGlobals {
const { window, document, global } = parseHTML(`
<html>
<head></head>
<body></body>
</html>
`);
return {
...global,
window,
document,
self: global,
};
}
@@ -0,0 +1,49 @@
export interface Environment {
setup: () => () => void;
run: <T>(fn: () => Promise<T>) => Promise<T>;
}
export function createEnvironment(getGlobals: () => EnvGlobals): Environment {
const setup = () => {
const envGlobals = getGlobals();
const ogGlobals = getOgGlobals(envGlobals);
applyGlobals(envGlobals);
return () => {
applyGlobals(ogGlobals);
};
};
const run = async (fn: () => any) => {
const teardown = setup();
try {
return await fn();
} finally {
teardown();
}
};
return {
setup,
run,
};
}
export type EnvGlobals = Record<string, any>;
export function getOgGlobals(envGlobals: EnvGlobals): EnvGlobals {
return Object.keys(envGlobals).reduce<typeof envGlobals>((acc, key) => {
// @ts-expect-error: Untyped key on globalThis
acc[key] = globalThis[key];
return acc;
}, {});
}
export function applyGlobals(globals: EnvGlobals): void {
Object.entries(globals).forEach(([key, envValue]) => {
try {
// @ts-expect-error: Untyped key on globalThis
globalThis[key] = envValue;
} catch (err) {
// ignore any globals that can't be set
}
});
}
@@ -0,0 +1,15 @@
import { fakeBrowser } from '@webext-core/fake-browser';
import { getBrowserEnvironmentGlobals } from './browser-environment';
import { createEnvironment, Environment } from './environment';
export function createExtensionEnvironment(): Environment {
return createEnvironment(getExtensionEnvironmentGlobals);
}
export function getExtensionEnvironmentGlobals() {
return {
...getBrowserEnvironmentGlobals(),
chrome: fakeBrowser,
browser: fakeBrowser,
};
}
@@ -0,0 +1,2 @@
export * from './browser-environment';
export * from './extension-environment';
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Manifest } from '../../browser';
import type { Manifest } from 'wxt/browser';
import {
Entrypoint,
BackgroundEntrypoint,
@@ -4,7 +4,7 @@
import { resolve } from 'path';
import { faker } from '@faker-js/faker';
import merge from 'lodash.merge';
import { Commands, type Manifest } from '../../../browser';
import { Commands, type Manifest } from 'wxt/browser';
import {
FsCache,
ResolvedConfig,
@@ -296,10 +296,9 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
transformManifest: () => {},
userConfigMetadata: {},
alias: {},
experimental: {
includeBrowserPolyfill: true,
entrypointImporter: 'jiti',
},
extensionApi: 'webextension-polyfill',
entrypointLoader: 'vite-node',
experimental: {},
dev: {
reloadCommand: 'Alt+R',
},
+146 -6
View File
@@ -2,8 +2,10 @@ import { ProxifiedModule, parseModule } from 'magicast';
/**
* Removes any code used at runtime related to an entrypoint's main function.
* - Removes or clears out `main` function from returned object
* - TODO: Removes unused imports after main function has been removed to prevent importing runtime modules
* 1. Removes or clears out `main` function from returned object
* 2. Removes any unused functions/variables outside the definition that aren't being called/used
* 3. Removes unused imports
* 3. Removes value-less, side-effect only imports (like `import "./styles.css"` or `import "webextension-polyfill"`)
*/
export function removeMainFunctionCode(code: string): {
code: string;
@@ -11,16 +13,26 @@ export function removeMainFunctionCode(code: string): {
} {
const mod = parseModule(code);
emptyMainFunction(mod);
let removedCount = 0;
let depth = 0;
const maxDepth = 10;
do {
removedCount = 0;
removedCount += removeUnusedTopLevelVariables(mod);
removedCount += removeUnusedTopLevelFunctions(mod);
removedCount += removeUnusedImports(mod);
} while (removedCount > 0 && depth++ <= maxDepth);
removeSideEffectImports(mod);
return mod.generate();
}
function emptyMainFunction(mod: ProxifiedModule) {
function emptyMainFunction(mod: ProxifiedModule): void {
if (mod.exports?.default?.$type === 'function-call') {
if (mod.exports.default.$ast?.arguments?.[0]?.body) {
// Remove body from function
// ex: "fn(() => { ... })" to "fn(() => {})"
// ex: "fn(function () { ... })" to "fn(function () {})"
mod.exports.default.$ast.arguments[0].body.body = [];
// ex: "fn(() => { ... })" to "fn()"
// ex: "fn(function () { ... })" to "fn()"
delete mod.exports.default.$ast.arguments[0];
} else if (mod.exports.default.$ast?.arguments?.[0]?.properties) {
// Remove main field from object
// ex: "fn({ ..., main: () => {} })" to "fn({ ... })"
@@ -31,3 +43,131 @@ function emptyMainFunction(mod: ProxifiedModule) {
}
}
}
function removeUnusedTopLevelVariables(mod: ProxifiedModule): number {
const simpleAst = getSimpleAstJson(mod.$ast);
const usedMap = findUsedIdentifiers(simpleAst);
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);
deletedCount++;
}
}
if (ast.body[i].declarations.length === 0) {
ast.body.splice(i, 1);
}
}
}
return deletedCount;
}
function removeUnusedTopLevelFunctions(mod: ProxifiedModule): number {
const simpleAst = getSimpleAstJson(mod.$ast);
const usedMap = findUsedIdentifiers(simpleAst);
let deletedCount = 0;
const ast = mod.$ast as any;
for (let i = ast.body.length - 1; i >= 0; i--) {
if (
ast.body[i].type === 'FunctionDeclaration' &&
!usedMap.get(ast.body[i].id.name)
) {
ast.body.splice(i, 1);
deletedCount++;
}
}
return deletedCount;
}
function removeUnusedImports(mod: ProxifiedModule): number {
const simpleAst = getSimpleAstJson(mod.$ast);
const usedMap = findUsedIdentifiers(simpleAst);
const importSymbols = Object.keys(mod.imports);
let deletedCount = 0;
importSymbols.forEach((name) => {
if (usedMap.get(name)) return;
delete mod.imports[name];
deletedCount++;
});
return deletedCount;
}
// TODO: Do a more complex declaration analysis where shadowed variables are detected and ignored.
// Right now, this code assumes there are no shadowed variables.
function findUsedIdentifiers(simpleAst: any) {
const usedMap = new Map<string, boolean>();
const queue: any[] = [simpleAst];
for (const item of queue) {
if (!item) {
} else if (Array.isArray(item)) {
queue.push(...item);
} else if (item.type === 'ImportDeclaration') {
// Don't look inside imports, identifiers are only used for declaration
continue;
} else if (item.type === 'Identifier') {
usedMap.set(item.name, true);
} else if (typeof item === 'object') {
const filterFns: Record<string, (entry: [string, any]) => boolean> = {
// Ignore the function declaration's name
FunctionDeclaration: ([key]) => key !== 'id',
// Ignore object property names
ObjectProperty: ([key]) => key !== 'key',
// Ignore variable declaration's name
VariableDeclarator: ([key]) => key !== 'id',
};
queue.push(
Object.entries(item)
.filter(filterFns[item.type] ?? (() => true))
.map(([_, value]) => value),
);
}
}
return usedMap;
}
function deleteImportAst(
mod: ProxifiedModule,
shouldDelete: (node: any) => boolean,
): void {
const importIndexesToDelete: number[] = [];
(mod.$ast as any).body.forEach((node: any, index: number) => {
if (node.type === 'ImportDeclaration' && shouldDelete(node)) {
importIndexesToDelete.push(index);
}
});
importIndexesToDelete.reverse().forEach((i) => {
delete (mod.$ast as any).body[i];
});
}
function removeSideEffectImports(mod: ProxifiedModule): void {
deleteImportAst(mod, (node) => node.specifiers.length === 0);
}
/**
* Util to get the AST as a simple JSON object, stripping out large objects and file locations to keep it readible
*/
function getSimpleAstJson(ast: any): any {
if (!ast) {
return ast;
} else if (Array.isArray(ast)) {
return ast.map(getSimpleAstJson);
} else if (typeof ast === 'object') {
return Object.fromEntries(
Object.entries(ast)
.filter(
([key, value]) => key !== 'loc' && key !== 'start' && key !== 'end',
)
.map(([key, value]) => [key, getSimpleAstJson(value)]),
);
} else {
return ast;
}
}
+4
View File
@@ -27,6 +27,10 @@ export async function registerWxt(
inlineConfig: InlineConfig = {},
getServer?: (config: ResolvedConfig) => Promise<WxtDevServer>,
): Promise<void> {
// Default NODE_ENV environment variable before other packages, like vite, do it
// See https://github.com/wxt-dev/wxt/issues/873#issuecomment-2254555523
process.env.NODE_ENV ??= command === 'serve' ? 'development' : 'production';
const hooks = createHooks<WxtHooks>();
const config = await resolveConfig(inlineConfig, command);
const server = await getServer?.(config);
@@ -7,6 +7,6 @@ export function defineBackground(
export function defineBackground(
arg: (() => void) | BackgroundDefinition,
): BackgroundDefinition {
if (typeof arg === 'function') return { main: arg };
if (arg == null || typeof arg === 'function') return { main: arg };
return arg;
}
@@ -9,6 +9,6 @@ export function defineUnlistedScript(
export function defineUnlistedScript(
arg: (() => void) | UnlistedScriptDefinition,
): UnlistedScriptDefinition {
if (typeof arg === 'function') return { main: arg };
if (arg == null || typeof arg === 'function') return { main: arg };
return arg;
}
+62 -12
View File
@@ -5,10 +5,11 @@
*
* @module wxt/storage
*/
import { Storage, browser } from './browser';
import { Storage, browser } from 'wxt/browser';
import { dequal } from 'dequal/lite';
import { logger } from './sandbox/utils/logger';
import { toArray } from './core/utils/arrays';
import { Mutex } from 'async-mutex';
export const storage = createStorage();
@@ -43,8 +44,8 @@ function createStorage(): WxtStorage {
};
};
const getMetaKey = (key: string) => key + '$';
const getValueOrDefault = (value: any, defaultValue: any) =>
value ?? defaultValue ?? null;
const getValueOrFallback = (value: any, fallback: any) =>
value ?? fallback ?? null;
const getMetaValue = (properties: any) =>
typeof properties === 'object' && !Array.isArray(properties)
? properties
@@ -56,7 +57,7 @@ function createStorage(): WxtStorage {
opts: GetItemOptions<any> | undefined,
) => {
const res = await driver.getItem<any>(driverKey);
return getValueOrDefault(res, opts?.defaultValue);
return getValueOrFallback(res, opts?.fallback ?? opts?.defaultValue);
};
const getMeta = async (driver: WxtStorageDriver, driverKey: string) => {
const metaKey = getMetaKey(driverKey);
@@ -148,9 +149,10 @@ function createStorage(): WxtStorage {
const driverResults = await drivers[driverArea].getItems(keys);
return driverResults.map((driverResult) => {
const key = `${driverArea}:${driverResult.key}` as StorageItemKey;
const value = getValueOrDefault(
const opts = keyToOptsMap.get(key);
const value = getValueOrFallback(
driverResult.value,
keyToOptsMap.get(key)?.defaultValue,
opts?.fallback ?? opts?.defaultValue,
);
return { key, value };
});
@@ -302,15 +304,38 @@ function createStorage(): WxtStorage {
logger.error(`Migration failed for ${key}`, err);
});
const getDefaultValue = () => opts?.defaultValue ?? null;
const initMutex = new Mutex();
const getFallback = () => opts?.fallback ?? opts?.defaultValue ?? null;
const getOrInitValue = () =>
initMutex.runExclusive(async () => {
const value = await driver.getItem<any>(driverKey);
// Don't init value if it already exists or the init function isn't provided
if (value != null || opts?.init == null) return value;
const newValue = await opts.init();
await driver.setItem<any>(driverKey, newValue);
return newValue;
});
// Initialize the value once migrations have finished
migrationsDone.then(getOrInitValue);
return {
get defaultValue() {
return getDefaultValue();
return getFallback();
},
get fallback() {
return getFallback();
},
getValue: async () => {
await migrationsDone;
return await getItem(driver, driverKey, opts);
if (opts?.init) {
return await getOrInitValue();
} else {
return await getItem(driver, driverKey, opts);
}
},
getMeta: async () => {
await migrationsDone;
@@ -334,7 +359,7 @@ function createStorage(): WxtStorage {
},
watch: (cb) =>
watch(driver, driverKey, (newValue, oldValue) =>
cb(newValue ?? getDefaultValue(), oldValue ?? getDefaultValue()),
cb(newValue ?? getFallback(), oldValue ?? getFallback()),
),
migrate,
};
@@ -567,7 +592,14 @@ export interface WxtStorageItem<
TValue,
TMetadata extends Record<string, unknown>,
> {
/**
* @deprecated Renamed to `fallback`, use it instead.
*/
defaultValue: TValue;
/**
* The value provided by the `fallback` option.
*/
fallback: TValue;
/**
* Get the latest value from storage.
*/
@@ -610,9 +642,13 @@ export type StorageItemKey = `${StorageArea}:${string}`;
export interface GetItemOptions<T> {
/**
* Value returned from `getValue` when it would otherwise return null.
* @deprecated Renamed to `fallback`, use it instead.
*/
defaultValue?: T;
/**
* Default value returned when `getItem` would otherwise return `null`.
*/
fallback?: T;
}
export interface RemoveItemOptions {
@@ -633,7 +669,21 @@ export interface SnapshotOptions {
}
export interface WxtStorageItemOptions<T> {
defaultValue: T;
/**
* @deprecated Renamed to `fallback`, use it instead.
*/
defaultValue?: T;
/**
* Default value returned when `getValue` would otherwise return `null`.
*/
fallback?: T;
/**
* If passed, a value in storage will be initialized immediately after
* defining the storage item. This function returns the value that will be
* saved to storage during the initialization process if a value doesn't
* already exist.
*/
init?: () => T | Promise<T>;
/**
* Provide a version number for the storage item to enable migrations. When changing the version
* in the future, migration functions will be ran on application startup.
+20 -17
View File
@@ -3,7 +3,7 @@ import {
download,
tsconfigPaths,
globals,
webextensionPolyfillMock,
extensionApiMock,
resolveAppConfig,
} from '../core/builders/vite/plugins';
import { resolveConfig } from '../core/utils/building';
@@ -26,20 +26,23 @@ import { createUnimport } from 'unimport';
*
* @param inlineConfig Customize WXT's config for testing. Any config specified here overrides the config from your `wxt.config.ts` file.
*/
export function WxtVitest(inlineConfig?: InlineConfig): vite.PluginOption {
return resolveConfig(inlineConfig ?? {}, 'serve').then(async (config) => {
const plugins = [
webextensionPolyfillMock(config),
globals(config),
download(config),
tsconfigPaths(config),
resolveAppConfig(config),
];
if (config.imports !== false) {
const unimport = createUnimport(config.imports);
await unimport.init();
plugins.push(unimportPlugin(unimport));
}
return plugins;
});
export async function WxtVitest(
inlineConfig?: InlineConfig,
): Promise<vite.PluginOption[]> {
const config = await resolveConfig(inlineConfig ?? {}, 'serve');
const plugins: vite.PluginOption[] = [
globals(config),
download(config),
tsconfigPaths(config),
resolveAppConfig(config),
extensionApiMock(config),
];
if (config.imports !== false) {
const unimport = createUnimport(config.imports);
await unimport.init();
plugins.push(unimportPlugin(unimport));
}
return plugins;
}
@@ -1,8 +1,8 @@
import type * as vite from 'vite';
import type { Manifest, Scripting } from '../browser';
import type { Manifest, Scripting } from 'wxt/browser';
import { UnimportOptions, Import } from 'unimport';
import { LogLevel } from 'consola';
import { ContentScriptContext } from '../client/content-scripts/content-script-context';
import type { ContentScriptContext } from './client/content-scripts/content-script-context';
import type { PluginVisualizerOptions } from '@aklinker1/rollup-plugin-visualizer';
import type { FSWatcher } from 'chokidar';
import { ResolvedConfig as C12ResolvedConfig } from 'c12';
@@ -292,43 +292,34 @@ export interface InlineConfig {
* }
*/
alias?: Record<string, string>;
/**
* Which extension API to use.
*
* - `"webextension-polyfill"`: Use `browser` and types from [`webextension-polyfill`](https://www.npmjs.com/package/webextension-polyfill).
* - `"chrome"` (unstable): Use the regular `chrome` (or `browser` for Firefox/Safari) globals provided by the browser. Types provided by [`@types/chrome`](https://www.npmjs.com/package/@types/chrome), make sure to install the package or types won't work.
*
* @default "webextension-polyfill"
* @since 0.19.0
*/
extensionApi?: 'webextension-polyfill' | 'chrome';
/**
* @deprecated Will be removed in v0.20.0, please migrate to using `vite-node`, the new default.
*
* Method used to import entrypoint files during the build process to extract their options.
*
* - `"vite-node"` (default as of 0.19.0): Uses `vite-node` to import the entrypoints. Automatically includes vite config based on your wxt.config.ts file
* - `"jiti"`: Simplest and fastest, but doesn't allow using any imported variables outside the entrypoint's main function
*
* @see {@link https://wxt.dev/guide/go-further/entrypoint-importers.html|Entrypoint Importers}
*
* @default "vite-node"
* @since 0.19.0
*/
entrypointLoader?: 'vite-node' | 'jiti';
/**
* Experimental settings - use with caution.
*/
experimental?: {
/**
* Whether to use [`webextension-polyfill`](https://www.npmjs.com/package/webextension-polyfill)
* when importing `browser` from `wxt/browser`.
*
* When set to `false`, WXT will export the chrome global instead of the polyfill from
* `wxt/browser`.
*
* You should use `browser` to access the web extension APIs.
*
* @experimental This option will remain experimental until Manifest V2 is dead.
*
* @default true
* @example
* export default defineConfig({
* experimental: {
* includeBrowserPolyfill: false
* }
* })
*/
includeBrowserPolyfill?: boolean;
/**
* Method used to import entrypoint files during the build process to extract their options.
*
* - "jiti": Simplest and fastest, but doesn't allow using any imported variables outside the entrypoint's main function
* - "vite-runtime" (unstable): Uses Vite 5.3's new runtime API to import the entrypoints. Automatically includes vite config based on your wxt.config.ts file
* - "vite-node" (unstable): Uses `vite-node` to import the entrypoints. Automatically includes vite config based on your wxt.config.ts file
*
* @see {@link https://wxt.dev/guide/go-further/entrypoint-side-effects.html|Entrypoint Side-effect Docs}
*
* @default "jiti"
*/
entrypointImporter?: 'jiti' | 'vite-runtime' | 'vite-node';
};
experimental?: {};
/**
* Config effecting dev mode only.
*/
@@ -1055,12 +1046,31 @@ export interface WxtHooks {
* // variable called "example" in the TS project.
* entries.push({
* path: "types/example.d.ts",
* textContent: "declare const a: string;",
* text: "declare const a: string;",
* tsReference: true,
* });
* // use module to add Triple-Slash Directive in .wxt/wxt.d.ts
* // eg: /// <reference types="@types/example" />
* entries.push({
* module: '@types/example'
* });
* })
*/
'prepare:types': (wxt: Wxt, entries: WxtDirEntry[]) => HookResult;
/**
* Called before generating the list of public paths inside
* `.wxt/types/paths.d.ts`. Use this hook to add additional paths (relative
* to output directory) WXT doesn't add automatically.
*
* @param wxt The configured WXT object
* @param paths This list of paths TypeScript allows `browser.runtime.getURL` to be called with.
*
* @example
* wxt.hooks.hook('prepare:publicPaths', (wxt, paths) => {
* paths.push('/icons/128.png');
* })
*/
'prepare:publicPaths': (wxt: Wxt, paths: string[]) => HookResult;
/**
* Called before the build is started in both dev mode and build mode.
*
@@ -1068,7 +1078,9 @@ export interface WxtHooks {
*/
'build:before': (wxt: Wxt) => HookResult;
/**
* Called once the build process has finished.
* Called once the build process has finished. You can add files to the build
* summary here by pushing to `output.publicAssets`.
*
* @param wxt The configured WXT object
* @param output The results of the build
*/
@@ -1202,10 +1214,9 @@ export interface ResolvedConfig {
* Import aliases to absolute paths.
*/
alias: Record<string, string>;
experimental: {
includeBrowserPolyfill: boolean;
entrypointImporter: 'jiti' | 'vite-runtime' | 'vite-node';
};
extensionApi: 'webextension-polyfill' | 'chrome';
entrypointLoader: 'vite-node' | 'jiti';
experimental: {};
dev: {
/** Only defined during dev command */
server?: {
@@ -1402,6 +1413,10 @@ export type WxtDirEntry = WxtDirTypeReferenceEntry | WxtDirFileEntry;
* Represents type reference to a node module to be added to `.wxt/wxt.d.ts` file
*/
export interface WxtDirTypeReferenceEntry {
/**
* Specifies the module name that will be used in the `/// <reference types="..." />` directive.
* This value will be added to the `.wxt/wxt.d.ts` file to include type definitions from the specified module.
*/
module: string;
}
+3 -2
View File
@@ -1,3 +1,4 @@
import { fakeBrowser as mockBrowser } from 'wxt/testing';
import { fakeBrowser } from 'wxt/testing';
export default mockBrowser;
export const browser = fakeBrowser;
export default fakeBrowser;

Some files were not shown because too many files have changed in this diff Show More