Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ coverage
dist
node_modules
TODOs.md
web-ext.config.js
web-ext.config.ts
wxt.runner.config.ts
templates/*/pnpm-lock.yaml
templates/*/yarn.lock
templates/*/package-lock.json
Expand Down
86 changes: 34 additions & 52 deletions docs/guide/essentials/config/browser-startup.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,93 +6,75 @@ outline: deep

> See the [API Reference](/api/reference/wxt/interfaces/WebExtConfig) for a full list of config.

During development, WXT uses [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext) to automatically open a browser window with your extension installed.
During development, WXT uses [`@wxt-dev/runner`](https://www.npmjs.com/package/@wxt-dev/runner) to automatically open a browser window with your extension installed.

:::danger
Chrome 137 removed support for the `--load-extension` CLI flag, which WXT relied on to open the browser with an extension installed. So this feature will not work for Chrome.
:::warning
Chrome 137 removed support for the `--load-extension` CLI flag, which WXT v0.20.6 and below relied upon. The current version of WXT relies on very new APIs CDP and WebDriver Bidi.

You have two options:
Make sure your development browser meets these minimum requirements:

1. Install [Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (which still supports the `--load-extension` flag) and [point the `chrome` binary to it](#set-browser-binaries), or
2. [Disable this feature](#disable-opening-browser) and manually load your extension
| Browser | Minimum Version | Release Date |
| -------- | :-------------: | :-----------: |
| Chromium | 126 | June 11, 2024 |
| Firefox | 139 | May 27, 2025 |

:::
- Chromium: Version 110 or higher, released
- Firefox: v139 or higher, released May 27, 2025
:::

To use older versions of Chrome or Firefox, you can [disable the runner](#disable-opening-browser) and manually install the extension from the browsers settings.

## Config Files

You can configure browser startup in 3 places:

1. `<rootDir>/web-ext.config.ts`: Ignored from version control, this file lets you configure your own options for a specific project without affecting other developers
1. `<rootDir>/wxt.runner.config.ts`: Ignored from version control, this file lets you configure your own options for a specific project without affecting other developers

```ts [web-ext.config.ts]
import { defineWebExtConfig } from 'wxt';
```ts [wxt.runner.config.ts]
import { defineRunnerConfig } from 'wxt';

export default defineWebExtConfig({
export default defineRunnerConfig({
// ...
});
```

2. `<rootDir>/wxt.config.ts`: Via the [`webExt` config](/api/reference/wxt/interfaces/InlineConfig#webext), included in version control
3. `$HOME/web-ext.config.ts`: Provide default values for all WXT projects on your computer
2. `<rootDir>/wxt.config.ts`: Via the [`runner` config](/api/reference/wxt/interfaces/InlineConfig#runner), included in version control
3. `$HOME/.wxtrunnerrc`: Provide default values for all WXT projects on your computer

## Recipes

For more information on configuring `@wxt-dev/runner`, see [`@wxt-dev/runner`'s documentation](/runner#options).

### Set Browser Binaries

To set or customize the browser opened during development:
To set or customize the binaries used during development for all your projects:

```ts [web-ext.config.ts]
export default defineWebExtConfig({
binaries: {
chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome
firefox: 'firefoxdeveloperedition', // Use Firefox Developer Edition instead of regular Firefox
edge: '/path/to/edge', // Open MS Edge when running "wxt -b edge"
},
});
```ini [$HOME/.wxtrunnerrc]
# Use Chrome Beta instead of regular Chrome when targeting "chrome"
browserBinaries.chrome=/path/to/chrome-beta

# Point Firefox to a custom install location
browserBinaries.firefox=/path/to/firefox
```

By default, WXT will try to automatically discover where Chrome/Firefox are installed. However, if you have chrome installed in a non-standard location, you need to set it manually as shown above.

### Persist Data

By default, to keep from modifying your browser's existing profiles, `web-ext` creates a brand new profile every time you run the `dev` script.

Right now, Chromium based browsers are the only browsers that support overriding this behavior and persisting data when running the `dev` script multiple times.

To persist data, set the `--user-data-dir` flag:

:::code-group
For security reasons, browsers will not allow you to open an existing profile during development. Instead, `@wxt-dev/runner` provides a way to persist data across sessions:

```ts [Mac/Linux]
export default defineWebExtConfig({
chromiumArgs: ['--user-data-dir=./.wxt/chrome-data'],
```ts [wxt.runner.config.ts]
export default defineRunnerConfig({
dataPersistence: 'project', // or "user" for using a single profile for all projects
});
```

```ts [Windows]
import { resolve } from 'node:path';

export default defineWebExtConfig({
// On Windows, the path must be absolute
chromiumProfile: resolve('.wxt/chrome-data'),
keepProfileChanges: true,
});
```

:::

Now, next time you run the `dev` script, a persistent profile will be created in `.wxt/chrome-data/{profile-name}`. With a persistent profile, you can install devtools extensions to help with development, allow the browser to remember logins, etc, without worrying about the profile being reset the next time you run the `dev` script.

:::tip
You can use any directory you'd like for `--user-data-dir`, the examples above create a persistent profile for each WXT project. To create a profile for all WXT projects, you can put the `chrome-data` directory inside your user's home directory.
:::

### Disable Opening Browser

If you prefer to load the extension into your browser manually, you can disable the auto-open behavior:

```ts [web-ext.config.ts]
export default defineWebExtConfig({
```ts [wxt.runner.config.ts]
export default defineRunnerConfig({
disabled: true,
});
```
6 changes: 3 additions & 3 deletions docs/guide/essentials/project-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ WXT follows a strict project structure. By default, it's a flat folder structure
📄 app.config.ts
📄 package.json
📄 tsconfig.json
📄 web-ext.config.ts
📄 wxt.config.ts
📄 wxt.runner.config.ts
```

Here's a brief summary of each of these files and directories:
Expand All @@ -41,8 +41,8 @@ Here's a brief summary of each of these files and directories:
- `app.config.ts`: Contains [Runtime Config](/guide/essentials/config/runtime)
- `package.json`: The standard file used by your package manager
- `tsconfig.json`: Config telling TypeScript how to behave
- `web-ext.config.ts`: Configure [Browser Startup](/guide/essentials/config/browser-startup)
- `wxt.config.ts`: The main config file for WXT projects
- `wxt.runner.config.ts`: Configure [Browser Startup](/guide/essentials/config/browser-startup)

## Adding a `src/` Directory

Expand Down Expand Up @@ -75,8 +75,8 @@ After enabling it, your project structure should look like this:
📄 .env.publish
📄 package.json
📄 tsconfig.json
📄 web-ext.config.ts
📄 wxt.config.ts
📄 wxt.runner.config.ts
```

## Customizing Other Directories
Expand Down
4 changes: 4 additions & 0 deletions docs/runner.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
---
outline: deep
---

<!--@include: ../packages/runner/README.md-->
46 changes: 17 additions & 29 deletions packages/runner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,15 @@

Programmatically open a browser and install a web extension from a local directory.

###### With WXT
## Usage

> [!WARNING]
> This package is intended to replace [`web-ext`](https://github.com/mozilla/web-ext) in the future, but it is not ready at the moment. Once it's ready for testing in WXT, more details will be added here.
### With WXT

```ts
// ~/wxt.runner.config.ts OR <project>/wxt.runner.config.ts
import { defineRunnerConfig } from 'wxt';
See WXT's [browser startup docs](https://wxt.dev/guide/essentials/config/browser-startup.html).

export default defineRunnerConfig({
// Options go here
});
```
### JS API

###### JS API
Pass a directory to the `run` function to install an extension and open the browser.

```ts
import { run } from '@wxt-dev/runner';
Expand All @@ -29,9 +23,9 @@ await run({

## Features

- Supports all Chromium and Firefox based browsers
- Zero dependencies
- One-line config for persisting data between launches
- Supports all Chromium and Firefox based browsers
- 🧪 Zero dependencies
- 🎉 One-line config for persisting data between launches

## Requirements

Expand All @@ -44,16 +38,10 @@ await run({

You also need to have a specific version of the browser installed that supports the latest features so extensions can be loaded:

| Browser | Version |
| -------- | -------- |
| Chromium | Unknown |
| Firefox | &ge; 139 |

## TODO

- [x] Provide install functions to allow hooking into already running instances of Chrome/Firefox
- [ ] Try to setup E2E tests on Firefox with Puppeteer using this approach
- [ ] Try to setup E2E tests on Chrome with Puppeteer using this approach
| Browser | Version | Release Date |
| -------- | :-----: | :-----------: |
| Chromium | &ge;126 | June 11, 2024 |
| Firefox | &ge;139 | May 27, 2025 |

## Options

Expand Down Expand Up @@ -86,9 +74,9 @@ await run({
});
```

- `"none"` (default): Use a brand new browser profile every time the browser is opened (stored in the system's tmp directory)
- `"project"`: Create a new profile that is re-used for your current directory (by default stored in `.wxt-runner` or `.wxt/runner` for WXT projects)
- `"user"`: Create a new profile that is re-used for all projects using `@wxt-dev/runner` (by default stored in `$HOME/.wxt-runner`)
- `"none"` (default): Use a new profile every time the browser is opened
- `"project"`: Store user data in `.wxt-runner` (or `.wxt/runner` for WXT projects) so it is reused by future sessions for this project.
- `"user"`: Store user data in `$HOME/.wxt-runner` so it is reused by future sessions for all your projects.

These presets configure different flags for different operating systems when spawning the browser process.

Expand All @@ -102,7 +90,7 @@ If you want to customize your data persistence beyond what these presets define,
- You want to use a specific version/release of the browser.
- You're using a less popular browser and `@wxt-dev/runner` doesn't have hard-coded paths for it.

To do this, use the `browserBinaries` option and set the path to the browser's binary:
To do this, use the `browserBinaries` option and set the path to the target's binary:

```ts
import { run } from '@wxt-dev/runner';
Expand Down Expand Up @@ -212,7 +200,7 @@ To see debug logs, set the `DEBUG` env var to `"@wxt-dev/runner"`. This will pri

## Implementation Details

All this package does is spawn a child process to open the browser with some default flags before using remote protocols to install the extension.
`@wxt-dev/runner` spawns a child process using the target browser's binary path and some default flags. Then it uses the browser's remote protocol to install the extension.

### Firefox

Expand Down
2 changes: 1 addition & 1 deletion packages/runner/src/bidi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { debug } from './debug';

const debugBidi = debug.scoped('bidi');

export interface BidiConnection extends Disposable {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Extract disposable changes to own PR.

export interface BidiConnection {
send<T>(method: string, params: any, timeout?: number): Promise<T>;
close(): void;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/runner/src/cdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { debug } from './debug';

const debugCdp = debug.scoped('cdp');

export interface CDPConnection extends Disposable {
export interface CDPConnection {
send<T>(method: string, params: any, timeout?: number): Promise<T>;
close(): void;
}
Expand Down
47 changes: 28 additions & 19 deletions packages/runner/src/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,25 @@ export async function installFirefox(
debuggerUrl: string,
extensionDir: string,
): Promise<BidiWebExtensionInstallResponse> {
using bidi = await createBidiConnection(debuggerUrl);
const bidi = await createBidiConnection(debuggerUrl);

// Start a session
await bidi.send<unknown>('session.new', { capabilities: {} });
try {
// Start a session
await bidi.send<unknown>('session.new', { capabilities: {} });

// Install the extension
return await bidi.send<BidiWebExtensionInstallResponse>(
'webExtension.install',
{
extensionData: {
type: 'path',
path: extensionDir,
// Install the extension
return await bidi.send<BidiWebExtensionInstallResponse>(
'webExtension.install',
{
extensionData: {
type: 'path',
path: extensionDir,
},
},
},
);
);
} finally {
bidi.close();
}
}

export type BidiWebExtensionInstallResponse = {
Expand All @@ -45,13 +49,18 @@ export async function installChromium(
browserProcess: ChildProcess,
extensionDir: string,
): Promise<CdpExtensionsLoadUnpackedResponse> {
using cdp = createCdpConnection(browserProcess);
return await cdp.send<CdpExtensionsLoadUnpackedResponse>(
'Extensions.loadUnpacked',
{
path: extensionDir,
},
);
const cdp = createCdpConnection(browserProcess);

try {
return await cdp.send<CdpExtensionsLoadUnpackedResponse>(
'Extensions.loadUnpacked',
{
path: extensionDir,
},
);
} finally {
cdp.close();
}
}

export type CdpExtensionsLoadUnpackedResponse = {
Expand Down
7 changes: 5 additions & 2 deletions packages/wxt-demo/wxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { defineConfig } from 'wxt';
import { presetUno } from 'unocss';

const startUrls = ['https://duckduckgo.com'];

export default defineConfig({
srcDir: 'src',
targetBrowsers: ['chrome', 'firefox', 'safari'],
Expand All @@ -20,8 +22,9 @@ export default defineConfig({
analysis: {
open: true,
},
webExt: {
startUrls: ['https://duckduckgo.com'],
runner: {
chromiumArgs: [...startUrls],
firefoxArgs: [...startUrls],
},
example: {
a: 'a',
Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/e2e/tests/hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ describe('Hooks', () => {

const server = await project.startServer({
hooks,
webExt: {
runner: {
disabled: true,
},
});
Expand Down
4 changes: 2 additions & 2 deletions packages/wxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@webext-core/isolated-element": "^1.1.2",
"@webext-core/match-patterns": "^1.0.3",
"@wxt-dev/browser": "workspace:^",
"@wxt-dev/runner": "workspace:^",
"@wxt-dev/storage": "workspace:^1.0.0",
"async-mutex": "^0.5.0",
"c12": "^3.0.3",
Expand Down Expand Up @@ -59,8 +60,7 @@
"scule": "^1.3.0",
"unimport": "^3.13.1 || ^4.0.0 || ^5.0.0",
"vite": "^5.4.19 || ^6.3.4",
"vite-node": "^2.1.4 || ^3.1.2",
"web-ext-run": "^0.2.3"
"vite-node": "^2.1.4 || ^3.1.2"
},
"devDependencies": {
"@aklinker1/check": "2.0.0",
Expand Down
Loading