Skip to content
Merged
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
22 changes: 14 additions & 8 deletions docs/guide/essentials/config/browser-startup.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ outline: deep

# Browser Startup

> See the [API Reference](/api/reference/wxt/interfaces/WebExtConfig) for a full list of config.
During development, WXT will use any of the below packages to automatically open a browser with your extension installed.

- [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext)

Just install the dependency you want WXT to use to open the browser.

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.
## `web-ext` Usage

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

## Config Files
### Config Files

You can configure browser startup in 3 places:

Expand All @@ -25,9 +31,9 @@ You can configure browser startup in 3 places:
2. `<rootDir>/wxt.config.ts`: Via the [`webExt` config](/api/reference/wxt/interfaces/InlineConfig#webext), included in version control
3. `$HOME/web-ext.config.ts`: Provide default values for all WXT projects on your computer

## Recipes
### Recipes

### Set Browser Binaries
#### Set Browser Binaries

To set or customize the browser opened during development:

Expand Down Expand Up @@ -57,7 +63,7 @@ export default defineConfig({

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
#### 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.

Expand Down Expand Up @@ -94,9 +100,9 @@ Now, next time you run the `dev` script, a persistent profile will be created in
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
#### Disable Opening Browser

If you prefer to load the extension into your browser manually, you can disable the auto-open behavior:
If you don't want to uninstall `web-ext`, like to test in your normal profile, you can do so via `disabled: true`:

```ts [web-ext.config.ts]
import { defineWebExtConfig } from 'wxt';
Expand Down
35 changes: 35 additions & 0 deletions docs/guide/resources/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,41 @@ Listed below are all the breaking changes you should address when upgrading to a

Currently, WXT is in pre-release. This means changes to the second digit, `v0.X`, are considered major and have breaking changes. Once v1 is released, only major version bumps will have breaking changes.

## v0.20.0 &rarr; vX.Y.Z

### Dev Browser Startup

The package used to open the browser on startup has changed. Previously, WXT used `web-ext-run` as a direct dependency, but now we use `web-ext` as a peer dependency.

:::details

`web-ext-run` was a light-weight fork of `web-ext`, but it was difficult to maintain and quickly got out-of-date compared to `web-ext`.

:::

In v0.20, how automatic startup is enabled/disabled has changed:

- To continue opening the browser automatically, add `web-ext` as a dependency. No changes are required in your `web-ext.config.ts` files.

```sh
pnpm add -D web-ext
```

- To disable the browser automatically, **_DON'T_** add `web-ext` as a dependency. Additionally, you can remove any `web-ext.config.ts` files since they're not used if `web-ext` isn't installed:

```sh
rm web-ext.config.ts

# Keep the config in your home dir until all your projects have been upgraded
rm ~/web-ext.config.ts
```

## New Deprecations in v0.20

Deprecated APIs will be removed in the next major release.

- `wxt.config.runnerConfig` renamed to `wxt.config.webExt`.

## v0.19.0 &rarr; v0.20.0

v0.20 is a big release! There are lots of breaking changes because this version is intended to be a release candidate for v1.0. If all goes well, v1.0 will be released with no additional breaking changes.
Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/e2e/tests/dev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('Dev Mode', () => {
);

const server = await project.startServer({
runner: {
webExt: {
disabled: true,
},
});
Expand Down
159 changes: 159 additions & 0 deletions packages/wxt/e2e/tests/runners.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { expect, describe, vi, it, beforeEach } from 'vitest';
import { ExtensionRunner } from '../../src';
import { createSafariRunner } from '../../src/core/runners/safari';
import { createManualRunner } from '../../src/core/runners/manual';
import { createWebExtRunner } from '../../src/core/runners/web-ext';
import { createWslRunner } from '../../src/core/runners/wsl';
import { TestProject } from '../utils';
import { wxt } from '../../src/core/wxt';

// Globals for modifying mock behaviors

let isWsl = false;
let importWebExtRunnerError: Error | undefined = undefined;

// Mock runners to create constants for checking equality

type TestExtensionRunner = { name: string } & ExtensionRunner;

function createMockExtensionRunner(name: string): TestExtensionRunner {
return {
name,
closeBrowser: () => Promise.resolve(),
openBrowser: () => Promise.resolve(),
};
}

vi.mock('../../src/core/runners/safari', () => {
const runner = createMockExtensionRunner('safari');
return { createSafariRunner: () => runner };
});
const safariRunner = createSafariRunner();

vi.mock('../../src/core/runners/manual', () => {
const runner = createMockExtensionRunner('manual');
return { createManualRunner: () => runner };
});
const manualRunner = createManualRunner();

vi.mock('../../src/core/runners/web-ext', () => {
const runner = createMockExtensionRunner('web-ext');
return {
createWebExtRunner: () => {
if (!importWebExtRunnerError) return runner;
else throw importWebExtRunnerError;
},
};
});
const webExtRunner = createWebExtRunner();

vi.mock('../../src/core/runners/wsl', () => {
const runner = createMockExtensionRunner('wsl');
return { createWslRunner: () => runner };
});
const wslRunner = createWslRunner();

// Other mocks

vi.mock('is-wsl', () => ({
get default() {
return isWsl;
},
}));

/**
* Imitate a real module not found error - needs the correct `code` property.
*/
class ModuleNotFoundError extends Error {
code = 'ERR_MODULE_NOT_FOUND';

constructor(mod: string) {
super(`Cannot find package '${mod}' imported from ...`);
this.name = 'ModuleNotFoundError';
}
}

describe('Runners', () => {
beforeEach(() => {
isWsl = false;
importWebExtRunnerError = undefined;
});

describe('build', () => {
const command = 'build';

it('should use the manual runner as a placeholder since the runner is not used during builds', async () => {
await TestProject.simple().registerWxt(command);

expect(wxt.config.runner).toBe(manualRunner);
});
});

describe('dev', () => {
const command = 'serve';

describe('inside WSL', () => {
beforeEach(() => {
isWsl = true;
});

it('should use the WSL runner', async () => {
await TestProject.simple().registerWxt(command);

expect(wxt.config.runner).toBe(wslRunner);
});
});

describe('web-ext is installed', () => {
it('should use the web-ext runner', async () => {
await TestProject.simple().registerWxt(command);

expect(wxt.config.runner).toBe(webExtRunner);
});

describe('disabled', () => {
it('should use the manual runner', async () => {
await TestProject.simple().registerWxt(command, {
webExt: { disabled: true },
});

expect(wxt.config.runner).toBe(manualRunner);
});
});
});

describe('web-ext is not installed', () => {
beforeEach(() => {
importWebExtRunnerError = new ModuleNotFoundError('web-ext');
});

it('should use the manual runner', async () => {
await TestProject.simple().registerWxt(command);

expect(wxt.config.runner).toBe(manualRunner);
});
});

describe('some other error when importing the web-ext runner', () => {
beforeEach(() => {
importWebExtRunnerError = Error('test');
});

it('should throw the error', async () => {
await expect(TestProject.simple().registerWxt(command)).rejects.toThrow(
importWebExtRunnerError,
);
});
});

describe('targeting safari', () => {
it('should use the safari runner', async () => {
await TestProject.simple().registerWxt(command, {
browser: 'safari',
});

expect(wxt.config.runner).toBe(safariRunner);
});
});
});
});
23 changes: 23 additions & 0 deletions packages/wxt/e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import spawn from 'nano-spawn';
import {
InlineConfig,
UserConfig,
WxtCommand,
build,
createServer,
prepare,
zip,
} from '../src';
import { normalizePath } from '../src/core/utils/paths';
import merge from 'lodash.merge';
import { registerWxt } from '../src/core/wxt';

// Run "pnpm wxt" to use the "wxt" dev script, not the "wxt" binary from the
// wxt package. This uses the TS files instead of the compiled JS package
Expand All @@ -21,6 +23,18 @@ export const WXT_PACKAGE_DIR = resolve(__dirname, '..');
export const E2E_DIR = resolve(WXT_PACKAGE_DIR, 'e2e');

export class TestProject {
/**
* Create the simplest WXT project possible: one blank popup entrypoint, no
* custom config.
*/
static simple(): TestProject {
const project = new TestProject();

project.addFile('entrypoints/popup.html', '<html></html>');

return project;
}

files: Array<[string, string]> = [];
config: UserConfig | undefined;
readonly root: string;
Expand Down Expand Up @@ -79,6 +93,15 @@ export class TestProject {
return this.resolvePath(filename);
}

/**
* Register the global `wxt` object for this project. After calling, you can
* import `wxt` like normal and inspect it.
*/
async registerWxt(command: WxtCommand, config: InlineConfig = {}) {
await this.writeProjectToDisk();
await registerWxt(command, { ...config, root: this.root });
}

async prepare(config: InlineConfig = {}) {
await this.writeProjectToDisk();
await prepare({ ...config, root: this.root });
Expand Down
15 changes: 10 additions & 5 deletions packages/wxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@
"prompts": "^2.4.2",
"publish-browser-extension": "^2.3.0 || ^3.0.2 || ^4.0.0",
"scule": "^1.3.0",
"unimport": "^3.13.1 || ^4.0.0 || ^5.0.0",
"web-ext-run": "^0.2.4"
"unimport": "^3.13.1 || ^4.0.0 || ^5.0.0"
},
"peerDependencies": {
"vite": "^6.3.4 || ^7.0.0"
"vite": "^6.3.4 || ^7.0.0",
"web-ext": ">=9.2.0"
},
"devDependencies": {
"@faker-js/faker": "^10.2.0",
Expand All @@ -78,9 +78,14 @@
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vitest": "^4.0.18",
"vitest-plugin-random-seed": "^1.1.2"
"vitest-plugin-random-seed": "^1.1.2",
"web-ext": "^9.2.0"
},
"peerDependenciesMeta": {
"web-ext": {
"optional": true
}
},
"peerDependenciesMeta": {},
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git"
Expand Down
4 changes: 2 additions & 2 deletions packages/wxt/src/@types/modules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ declare module 'zip-dir' {
export = zipdir;
}

declare module 'web-ext-run' {
declare module 'web-ext' {
export interface WebExtRunInstance {
reloadAllExtensions(): Promise<void>;
exit(): Promise<void>;
Expand All @@ -31,7 +31,7 @@ declare module 'web-ext-run' {
export default webExt;
}

declare module 'web-ext-run/util/logger' {
declare module 'web-ext/util/logger' {
// https://github.com/mozilla/web-ext/blob/e37e60a2738478f512f1255c537133321f301771/src/util/logger.js#L43
export interface IConsoleStream {
stopCapturing(): void;
Expand Down
Loading