Compare commits
271 Commits
v0.17.5
...
wxt-v0.19.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 742d2f1536 | |||
| b4b69783d5 | |||
| c8ddc66cfa | |||
| 2b6ff8d30f | |||
| d79d7ed778 | |||
| ab234c0f45 | |||
| eeb43c6bf4 | |||
| 17bda1eb34 | |||
| 96e28c415c | |||
| 1f9f6d9681 | |||
| f1bb7cb0c2 | |||
| 5eb5be7123 | |||
| 886b2081c2 | |||
| c69e51ce50 | |||
| 861efa3415 | |||
| 792b57542d | |||
| 97e6678e5b | |||
| 66c4ec3f99 | |||
| 8988d6a241 | |||
| b52856bea4 | |||
| 16fee19b85 | |||
| 14011c19cb | |||
| 005a9dff16 | |||
| 3109bbadbe | |||
| 34ae689569 | |||
| 1f216dd57b | |||
| a7963e23db | |||
| 069bd6c435 | |||
| 88a1244ca9 | |||
| 7ee391f688 | |||
| 2b3eab9c2a | |||
| a3fc9e1e18 | |||
| 559d32e6eb | |||
| f904b79367 | |||
| 9cd7cca81c | |||
| 3366c1e35f | |||
| 82be31a408 | |||
| d767f3f598 | |||
| 48967c7c5f | |||
| 6c4da7225d | |||
| e9025fb13e | |||
| 8db6b3187b | |||
| a19f9e12c4 | |||
| a82eb46511 | |||
| e50f2b047a | |||
| 1b41cd26ee | |||
| e32fbed1b7 | |||
| f8acdd6352 | |||
| 9ea89c6340 | |||
| a521a80d43 | |||
| 4c6a57dd33 | |||
| 3ce8c8b1f4 | |||
| 4ee8f7837e | |||
| 1e8e5488af | |||
| 5704765cc4 | |||
| 8c7adbf463 | |||
| 8c4436efe8 | |||
| 5e361c0085 | |||
| ccfc207e92 | |||
| 36be4ce771 | |||
| 18f8a387b0 | |||
| 48c2c07557 | |||
| 61548a9478 | |||
| fd678e9572 | |||
| 63fb913f76 | |||
| faffe20a62 | |||
| 3ed8bd10fd | |||
| 6f3f270573 | |||
| 5347cb4626 | |||
| d6b4d2d1f6 | |||
| 0d7ae287da | |||
| 90cb3bd3bf | |||
| 60eb97c4b2 | |||
| 0ffc6eae3e | |||
| 06a8c5fb0e | |||
| eb86413f35 | |||
| b29d49cbf1 | |||
| c0a0edb30c | |||
| b15a933c75 | |||
| 1851be22a7 | |||
| 39f6c29e4f | |||
| 936e89fbfb | |||
| ad365ecde4 | |||
| b4283e37bd | |||
| b467ad760a | |||
| 9fc6408ef9 | |||
| 394cb1a520 | |||
| ffec88549b | |||
| 4758bea187 | |||
| 6671471f75 | |||
| ae78dc7fb3 | |||
| b62af3d007 | |||
| 43ae44ecd5 | |||
| 5bab9d2f59 | |||
| 7edf1c8103 | |||
| 53b4eac955 | |||
| f9a10c2c52 | |||
| 8206ca9164 | |||
| 1a823cc008 | |||
| efc011eb81 | |||
| 35f6d28977 | |||
| 43fcbec0eb | |||
| a0d56b4d27 | |||
| 38ccb6ace6 | |||
| aaf75f1de1 | |||
| 21e8ca0a2c | |||
| 55444858bc | |||
| 771ea03069 | |||
| 38b71d1350 | |||
| cb781aceb5 | |||
| d53c792218 | |||
| 907f4d3672 | |||
| 8432d73a6b | |||
| 94a5435089 | |||
| edf3cf3f17 | |||
| 48783baec3 | |||
| c70f047696 | |||
| e6e0c713bc | |||
| 3691554a73 | |||
| 1a3436606b | |||
| 2c9e7750ec | |||
| fa67910007 | |||
| c77773eb20 | |||
| 148f3d53a6 | |||
| c7af789173 | |||
| 274d39b9a4 | |||
| d1edb2da0e | |||
| ed860027ef | |||
| 488fa6e773 | |||
| ea51cc04a9 | |||
| 6d1761533f | |||
| e35b540a3b | |||
| 7080b36173 | |||
| 4437dc404b | |||
| b3ddab7bcb | |||
| eb3b9bc334 | |||
| 34ea23030b | |||
| a25721708f | |||
| 6427e74edb | |||
| 15395a8c46 | |||
| 454b076eaf | |||
| 00597f6790 | |||
| ca29feaecf | |||
| f6420e5fdf | |||
| 78e0646043 | |||
| c15e75e6ab | |||
| 863a006ee7 | |||
| 9fd54cfa73 | |||
| 3535fb77fa | |||
| be22fc43cb | |||
| 3fd09edd25 | |||
| d36007b453 | |||
| 9eda887fd8 | |||
| b79220dbd1 | |||
| 927d29535f | |||
| 12805e9862 | |||
| d889a21465 | |||
| 8125d74057 | |||
| c39087a81a | |||
| 7b849b80cb | |||
| b98a5c378e | |||
| 215def7e15 | |||
| 3b954cc6fb | |||
| 20bdd8ad2f | |||
| cb9a9c0530 | |||
| 48d8d236c0 | |||
| 87d9511b31 | |||
| 5d8efef02f | |||
| c9008cd835 | |||
| 203a9cc234 | |||
| 5fcaf7c68b | |||
| c74e530d4a | |||
| 3f78be0c95 | |||
| 2a3d52045d | |||
| ea5b81d25e | |||
| cec9d7103a | |||
| 34dd2c1487 | |||
| a5667f85d0 | |||
| ec3dd52f83 | |||
| 87ebde3abb | |||
| dc5b55bffb | |||
| df20993917 | |||
| caa0fb31e6 | |||
| 95da4fb054 | |||
| a917f32a0e | |||
| 6ca376749c | |||
| 792e3032b9 | |||
| 38a5a2c440 | |||
| 21106ce188 | |||
| ec7263e7de | |||
| e7c3fc2a5e | |||
| f808cb7526 | |||
| c4795e7479 | |||
| b44a96ab81 | |||
| 3bfdc5c755 | |||
| acb75543ff | |||
| 25b45b9c09 | |||
| efd5c40043 | |||
| e47519fb76 | |||
| 171e32c272 | |||
| 54e5ed0bb4 | |||
| a94d673f31 | |||
| b7f7222c26 | |||
| aa5a9d4fa9 | |||
| 77da2d6ade | |||
| 3119c04e10 | |||
| c09994263e | |||
| 2a4fa9ae5c | |||
| c833d3b15b | |||
| d6e095bfce | |||
| 825160a3cc | |||
| e29ca33f82 | |||
| ca5a52c6f1 | |||
| 982c69f8ae | |||
| 6fc7d9254a | |||
| 2e0e104d48 | |||
| 02430fd7b8 | |||
| 2e6d35d193 | |||
| 7cf412ac1f | |||
| 9398cbb596 | |||
| ca9b96142e | |||
| db57c8e079 | |||
| 7e37e11caf | |||
| bb90029c54 | |||
| d3b14afbce | |||
| ba2197a609 | |||
| 4c558d3eb6 | |||
| dcd175444a | |||
| 16fc58404a | |||
| 28c7e7d9ed | |||
| 5a3b42a4b0 | |||
| c9c5d721ec | |||
| 1556b9ad73 | |||
| ca15b1f321 | |||
| 448436cb08 | |||
| c9ddc3d0f2 | |||
| 6b72f883b6 | |||
| cd3446bd7f | |||
| 829b371bba | |||
| 36b399dd49 | |||
| 08f32aa685 | |||
| 05db264e02 | |||
| de5a01834c | |||
| 1d260dbacb | |||
| 53c63b4556 | |||
| 816878bc15 | |||
| e087a0d6ef | |||
| 56b71491a6 | |||
| 121b521157 | |||
| 16c2e3218b | |||
| d9cba55d34 | |||
| 832cd5d640 | |||
| bb95e08d71 | |||
| 35269dad9e | |||
| 64d1498460 | |||
| 0d540a6df9 | |||
| b47c150daa | |||
| 38345481df | |||
| da92449e0f | |||
| 23b3980645 | |||
| 81b4b629c9 | |||
| 4f7e9cb050 | |||
| b226d9b947 | |||
| aabba41e86 | |||
| f19e6908b0 | |||
| 5f651cc3fe | |||
| 8a6acbaaf2 | |||
| 13ab8e4d01 | |||
| bd039dcbab | |||
| 9cd4e8342b | |||
| c9633cefec |
@@ -1,50 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report an issue with WXT
|
||||
title: ''
|
||||
labels: triage
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
### Describe the bug
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
### To Reproduce
|
||||
|
||||
Share minimal reproduction. Examples of a minimal reproduction:
|
||||
|
||||
- PR with a failing test case
|
||||
- ZIP file containing a minimal WXT project (be sure to include your lockfile!!)
|
||||
|
||||
> **_⚠️ If you don't upload a minimal reproduction, your issue will be closed until a reproduction is added._**
|
||||
>
|
||||
> Why? https://antfu.me/posts/why-reproductions-are-required
|
||||
|
||||
Steps to reproduce the bug using the reproduction:
|
||||
|
||||
1. Install dependencies: `pnpm i`
|
||||
2. Start dev mode: `pnpm dev`
|
||||
3. Click this...
|
||||
4. Do that...
|
||||
5. Etc...
|
||||
|
||||
### Expected behavior
|
||||
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
### Screenshots
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
### Environment
|
||||
|
||||
<!--- Run `npx envinfo --system --browsers --binaries --npmPackages wxt,vite` and paste the output below -->
|
||||
|
||||
```
|
||||
Paste output here
|
||||
```
|
||||
|
||||
### Additional context
|
||||
|
||||
Add any other context about the problem here, otherwise you can delete this section.
|
||||
@@ -0,0 +1,74 @@
|
||||
name: "\U0001F41E Bug report"
|
||||
description: Report an issue with WXT
|
||||
labels: [pending-triage]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks!
|
||||
placeholder: I am doing ... What I expect is ... What actually happening is ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: |
|
||||
Please provide a minimal reproduction. This can include:
|
||||
|
||||
- A PR with a failing test case
|
||||
- A link to a github repo
|
||||
- A ZIP you upload to this issue
|
||||
|
||||
A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is required ([Why?](https://antfu.me/posts/why-reproductions-are-required)). If a report is vague (e.g. just a generic error message) and has no reproduction or a partial reproduction, it will be closed immediately and labeled with as "needs-reproduction". Once a reproduction is provided, it will be re-opened.
|
||||
placeholder: Reproduction URL or attach a ZIP
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction-steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Please provide any reproduction steps that may need to be described. E.g. if it happens only when running the dev or build script make sure it's clear which one to use.
|
||||
placeholder: Run `npm install` followed by `npm run dev`
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: System Info
|
||||
description: Output of `npx envinfo --system --browsers --binaries --npmPackages wxt,vite`
|
||||
render: shell
|
||||
placeholder: System, Binaries, Browsers
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: package-manager
|
||||
attributes:
|
||||
label: Used Package Manager
|
||||
description: Select the used package manager
|
||||
options:
|
||||
- npm
|
||||
- yarn
|
||||
- pnpm
|
||||
- bun
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: Validations
|
||||
description: Before submitting the issue, please make sure you do the following
|
||||
options:
|
||||
- label: Read the [Contributing Guidelines](https://github.com/wxt-dev/wxt/blob/main/CONTRIBUTING.md).
|
||||
required: true
|
||||
- label: Read the [docs](https://wxt.dev/guide/installation.html).
|
||||
required: true
|
||||
- label: Check that there isn't [already an issue](https://github.com/wxt-dev/wxt/issues) that reports the same bug to avoid creating a duplicate.
|
||||
required: true
|
||||
- label: Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/wxt-dev/wxt/discussions) or join our [Discord Chat Server](https://discord.gg/ZFsZqGery9).
|
||||
required: true
|
||||
- label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.
|
||||
required: true
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Discord Chat
|
||||
url: https://discord.gg/ZFsZqGery9
|
||||
about: Ask questions and discuss with other WXT users in real time.
|
||||
- name: Questions & Discussions
|
||||
url: https://github.com/wxt-dev/wxt/discussions
|
||||
about: Use GitHub discussions for message-board style questions and discussions.
|
||||
@@ -4,9 +4,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
name: Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: Package to release
|
||||
default: wxt
|
||||
type: choice
|
||||
options:
|
||||
- wxt
|
||||
- module-react
|
||||
- module-vue
|
||||
- module-svelte
|
||||
- module-solid
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
@@ -22,14 +33,18 @@ jobs:
|
||||
run: |
|
||||
git config user.email "github-actions@users.noreply.github.com"
|
||||
git config user.name "GitHub Actions"
|
||||
pnpx changelogen@latest --release
|
||||
pnpm tsx scripts/bump-package-version.ts ${{ inputs.package }}
|
||||
git push
|
||||
git push --tags
|
||||
|
||||
- name: NPM
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
|
||||
pnpm build
|
||||
pnpm publish
|
||||
working-directory: packages/${{ inputs.package }}
|
||||
|
||||
- name: GitHub Release
|
||||
run: pnpx changelogen@latest gh release --token ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
name: Sync Releases
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- CHANGELOG.md
|
||||
inputs:
|
||||
package:
|
||||
description: Package to sync
|
||||
default: wxt
|
||||
type: choice
|
||||
options:
|
||||
- wxt
|
||||
- module-react
|
||||
- module-vue
|
||||
- module-svelte
|
||||
- module-solid
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
- run: pnpm sync-releases all --token ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -7,45 +7,35 @@ on:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
formatting:
|
||||
checks:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm format:check
|
||||
lint:
|
||||
- run: pnpm check
|
||||
builds:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm lint
|
||||
type-check:
|
||||
- run: pnpm buildc all
|
||||
build-demo:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm compile
|
||||
validate-demo:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm tsc --noEmit
|
||||
working-directory: demo
|
||||
- run: pnpm build:all
|
||||
working-directory: demo
|
||||
working-directory: packages/wxt-demo
|
||||
- run: pnpm wxt zip
|
||||
working-directory: demo
|
||||
- run: pnpm vitest run
|
||||
working-directory: demo
|
||||
working-directory: packages/wxt-demo
|
||||
tests:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- name: pnpm test:coverage
|
||||
run: pnpm test:coverage --reporter=default --reporter=hanging-process
|
||||
run: pnpm test:coverage -- --reporter=default --reporter=hanging-process
|
||||
- uses: codecov/codecov-action@v4
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
@@ -54,10 +44,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm test run
|
||||
- run: pnpm test
|
||||
template:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
template:
|
||||
- react
|
||||
@@ -69,9 +60,10 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm pack
|
||||
working-directory: packages/wxt
|
||||
- run: npm i
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: npm i -D ../../wxt-*.tgz
|
||||
- run: npm i -D ../../packages/wxt/wxt-*.tgz
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: pnpm compile
|
||||
if: matrix.template != 'svelte'
|
||||
|
||||
+2
-1
@@ -17,6 +17,7 @@ web-ext.config.ts
|
||||
templates/*/pnpm-lock.yaml
|
||||
templates/*/yarn.lock
|
||||
templates/*/package-lock.json
|
||||
docs/api
|
||||
docs/api/reference
|
||||
stats.html
|
||||
.tool-versions
|
||||
.cache
|
||||
|
||||
+21
-4
@@ -37,13 +37,13 @@ pnpm build
|
||||
|
||||
```sh
|
||||
# Build WXT package, then build demo extension
|
||||
cd demo
|
||||
cd packages/wxt-demo
|
||||
pnpm build
|
||||
```
|
||||
|
||||
```sh
|
||||
# Build WXT package, then start the demo extension in dev mode
|
||||
cd demo
|
||||
cd packages/wxt-demo
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
@@ -75,7 +75,7 @@ To run tests for a specific file, add the filename at the end of the test comman
|
||||
pnpm test manifest-contents
|
||||
```
|
||||
|
||||
Unit and E2E tests are ran together via [Vitest workspaces](https://vitest.dev/guide/#workspaces-support).
|
||||
All test (unit and E2E) for all packages are ran together via [Vitest workspaces](https://vitest.dev/guide/#workspaces-support).
|
||||
|
||||
If you want to manually test a change, you can modify the demo project for your test, but please don't leave those changes committed once you open a PR.
|
||||
|
||||
@@ -90,7 +90,7 @@ npm run dev
|
||||
npm run build
|
||||
```
|
||||
|
||||
Note that templates are hardcoded to a specific version of `wxt` from NPM, they do not use the local version. PR checks will test your changes against the templates, but if you want to manually do it, update the package.json dependency:
|
||||
Note that templates are hardcoded to a specific version of `wxt` from NPM, they do not use the local version. PR checks will test your PR's changes against the templates, but if you want to manually do it, update the package.json dependency:
|
||||
|
||||
```diff
|
||||
"devDependencies": {
|
||||
@@ -112,3 +112,20 @@ cp -r templates/vanilla templates/<new-template-name>
|
||||
```
|
||||
|
||||
That's it. Once your template is merged, it will be available inside `wxt init` immediately. You don't need to release a new version of WXT to release a new template.
|
||||
|
||||
## Releasing Updates
|
||||
|
||||
Releases are done with GitHub actions:
|
||||
|
||||
- Use the [Release workflow](https://github.com/wxt-dev/wxt/actions/workflows/release.yml) to release a single package in the monorepo. This automatically detects the version change with conventional commits, builds and uploads the package to NPM, and creates a GitHub release.
|
||||
- Use the [Sync Releases workflow](https://github.com/wxt-dev/wxt/actions/workflows/sync-releases.yml) to sync the GitHub releases with changes to the changelog. To change a release, update the `CHANGELOG.md` file and run the workflow. It will sync the releases of a single package in the monorepo.
|
||||
|
||||
## Upgrading Dependencies
|
||||
|
||||
Use [`taze`](https://www.npmjs.com/package/taze) to upgrade dependencies throughout the entire monorepo.
|
||||
|
||||
```ts
|
||||
pnpm dlx taze -r
|
||||
```
|
||||
|
||||
Configuration is in [`taze.config.ts`](./taze.config.ts).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<h1 align="center">
|
||||
<img style="vertical-align:middle" width="44" src="./docs/public/hero-logo.svg" alt="WXT Logo">
|
||||
<img style="vertical-align:middle" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
|
||||
<span>WXT</span>
|
||||
</h1>
|
||||
|
||||
@@ -18,31 +18,33 @@
|
||||
<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">
|
||||
<a href="https://wxt.dev" target="_blank">Get Started</a>
|
||||
•
|
||||
<a href="https://wxt.dev/guide/installation.html" target="_blank">Installation</a>
|
||||
<a href="https://wxt.dev/guide/installation.html" target="_blank">Get Started</a>
|
||||
•
|
||||
<a href="https://wxt.dev/api/config.html" target="_blank">Configuration</a>
|
||||
•
|
||||
<a href="https://wxt.dev/examples.html" target="_blank">Examples</a>
|
||||
•
|
||||
<a href="https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md" target="_blank">Changelog</a>
|
||||
•
|
||||
<a href="https://discord.gg/ZFsZqGery9" target="_blank">Discord</a>
|
||||
</p>
|
||||
|
||||

|
||||

|
||||
|
||||
## Demo
|
||||
|
||||
https://github.com/wxt-dev/wxt/assets/10101283/07359e53-f491-43b6-8e8f-fae94aec8063
|
||||
https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94
|
||||
|
||||
## Quick Start
|
||||
|
||||
Bootstrap a new project:
|
||||
|
||||
```sh
|
||||
pnpx wxt@latest init <project-name>
|
||||
pnpm dlx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
|
||||
@@ -60,6 +62,7 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
|
||||
- 🖍️ Quickly bootstrap a new project
|
||||
- 📏 Bundle analysis
|
||||
- ⬇️ Download and bundle remote URL imports
|
||||
- 📦 Modular architecture with [modules](https://wxt.dev/guide/go-further/reusable-modules.html#overview)
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import '../dist/cli.js';
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "demo",
|
||||
"version": "1.0.0",
|
||||
"description": "Demo extension for WXT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "pnpm -w build && wxt",
|
||||
"build": "pnpm -w build && wxt build",
|
||||
"build:all": "pnpm -w build && run-s -s 'build:all:*'",
|
||||
"build:all:chrome-mv3": "wxt build",
|
||||
"build:all:chrome-mv2": "wxt build --mv2",
|
||||
"build:all:firefox-mv3": "wxt build -b firefox --mv3",
|
||||
"build:all:firefox-mv2": "wxt build -b firefox",
|
||||
"test": "pnpm -w build && vitest",
|
||||
"zip": "pnpm -w build && wxt zip",
|
||||
"compile": "pnpm -w build && tsc --noEmit",
|
||||
"postinstall": "pnpm -w build && wxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"vitest": "^1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.34",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"sass": "^1.69.5",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"extends": ["../tsconfig.base.json", "./.wxt/tsconfig.json"],
|
||||
"compilerOptions": {
|
||||
"allowImportingTsExtensions": true,
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { WxtVitest } from 'wxt/testing';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
mockReset: true,
|
||||
restoreMocks: true,
|
||||
},
|
||||
plugins: [WxtVitest()],
|
||||
});
|
||||
@@ -33,7 +33,7 @@ For production builds, none of the above modifications will be applied, and you'
|
||||
|
||||
## Configure Browser Startup
|
||||
|
||||
WXT uses [`web-ext` by Mozilla](https://github.com/mozilla/web-ext) to automatically open a browser with the extension installed. You can configure the runner's behavior via the [`runner`](/api/wxt/interfaces/InlineConfig#runner) option, or in a separate gitignored file, `web-ext.config.ts`.
|
||||
WXT uses [`web-ext` by Mozilla](https://github.com/mozilla/web-ext) to automatically open a browser with the extension installed. You can configure the runner's behavior via the [`runner`](/api/reference/wxt/interfaces/InlineConfig#runner) option, or in a separate gitignored file, `web-ext.config.ts`.
|
||||
|
||||
:::code-group
|
||||
|
||||
@@ -59,7 +59,7 @@ export default defineRunnerConfig({
|
||||
|
||||
### Browser Binaries
|
||||
|
||||
`web-ext`'s browser discovery is very limited. By default, it only guesses at where Chrome and Firefox are installed. If you've customized your install locations, you may need to tell `web-ext` where the binaries/executables are located using the [`binaries` option](/api/wxt/interfaces/ExtensionRunnerConfig#binaries). For other Chromium based browsers, like Edge or Opera, you'll need to explicitly list them in the `binaries` option as well, otherwise they will open in Chrome by default.
|
||||
`web-ext`'s browser discovery is very limited. By default, it only guesses at where Chrome and Firefox are installed. If you've customized your install locations, you may need to tell `web-ext` where the binaries/executables are located using the [`binaries` option](/api/reference/wxt/interfaces/ExtensionRunnerConfig#binaries). For other Chromium based browsers, like Edge or Opera, you'll need to explicitly list them in the `binaries` option as well, otherwise they will open in Chrome by default.
|
||||
|
||||
```ts
|
||||
// ~/web-ext.config.ts
|
||||
@@ -89,17 +89,27 @@ import { defineRunnerConfig } from 'wxt';
|
||||
export default defineRunnerConfig({
|
||||
startUrls: ['https://google.com', 'https://duckduckgo.com'],
|
||||
chromiumProfile: '/path/to/profile/to/use',
|
||||
chromiumArgs: ['--window-size=400x300'],
|
||||
chromiumArgs: ['--window-size=400,300'],
|
||||
});
|
||||
```
|
||||
|
||||
For a full list of options, see the [API Reference](/api/wxt/interfaces/ExtensionRunnerConfig).
|
||||
For a full list of options, see the [API Reference](/api/reference/wxt/interfaces/ExtensionRunnerConfig).
|
||||
|
||||
## Reload the Extension
|
||||
|
||||
Normally, to manually reload an extension, you have to visit `chrome://extensions` and click the reload button for your extension.
|
||||
|
||||
When running `wxt` command to start the dev server, WXT adds a keyboard shortcut, `ctrl+E` for Windows/Linux and `cmd+E` for Mac, that reloads the extension when pressed, without visiting `chrome://extensions`.
|
||||
When running `wxt` command to start the dev server, WXT adds a keyboard shortcut `Alt+R`, that reloads the extension when pressed, without visiting `chrome://extensions`. This can also be customized or disabled:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
dev: {
|
||||
reloadCommand: 'Alt+T', // false, to disable
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
This shortcut is only available during development, and is not be added to your extension when running `wxt build` or `wxt-zip`.
|
||||
@@ -1,34 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
tag?: string;
|
||||
}>();
|
||||
|
||||
const examples = ref();
|
||||
onMounted(async () => {
|
||||
const res = await fetch(
|
||||
'https://raw.githubusercontent.com/wxt-dev/wxt-examples/main/examples.json',
|
||||
);
|
||||
examples.value = await res.json();
|
||||
});
|
||||
|
||||
const filteredExamples = computed(() => {
|
||||
if (props.tag == null) return examples.value;
|
||||
|
||||
return examples.value.filter((example) => {
|
||||
return example.tags?.includes(props.tag);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<li v-if="examples == null">Loading...</li>
|
||||
<template v-else>
|
||||
<li v-for="example of filteredExamples">
|
||||
<a :href="example.url" target="_blank">{{ example.name }}</a>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
@@ -0,0 +1,226 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, computed, toRaw, Ref } from 'vue';
|
||||
import ExampleSearchFilterByItem from './ExampleSearchFilterByItem.vue';
|
||||
import ExampleSearchResult from './ExampleSearchResult.vue';
|
||||
import { ExamplesMetadata, KeySelectedObject } from '../utils/types';
|
||||
|
||||
const props = defineProps<{
|
||||
tag?: string;
|
||||
}>();
|
||||
|
||||
const exampleMetadata = ref<ExamplesMetadata>();
|
||||
onMounted(async () => {
|
||||
const res = await fetch(
|
||||
'https://raw.githubusercontent.com/wxt-dev/examples/main/metadata.json',
|
||||
);
|
||||
exampleMetadata.value = await res.json();
|
||||
});
|
||||
|
||||
const searchText = ref('');
|
||||
const selectedApis = ref<KeySelectedObject>({});
|
||||
const selectedPermissions = ref<KeySelectedObject>({});
|
||||
const selectedPackages = ref<KeySelectedObject>({});
|
||||
|
||||
function useRequiredItems(selectedItems: Ref<KeySelectedObject>) {
|
||||
return computed(() =>
|
||||
Array.from(
|
||||
Object.entries(toRaw(selectedItems.value)).reduce(
|
||||
(set, [pkg, checked]) => {
|
||||
if (checked) set.add(pkg);
|
||||
return set;
|
||||
},
|
||||
new Set<string>(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
const requiredApis = useRequiredItems(selectedApis);
|
||||
const requiredPermissions = useRequiredItems(selectedPermissions);
|
||||
const requiredPackages = useRequiredItems(selectedPackages);
|
||||
|
||||
function doesExampleMatchSelected(
|
||||
exampleItems: string[],
|
||||
requiredItems: Ref<string[]>,
|
||||
) {
|
||||
const exampleItemsSet = new Set(exampleItems);
|
||||
return !requiredItems.value.find((item) => !exampleItemsSet.has(item));
|
||||
}
|
||||
|
||||
const filteredExamples = computed(() => {
|
||||
const text = searchText.value.toLowerCase();
|
||||
return exampleMetadata.value.examples.filter((example) => {
|
||||
const matchesText = example.searchText.toLowerCase().includes(text);
|
||||
const matchesApis = doesExampleMatchSelected(example.apis, requiredApis);
|
||||
const matchesPermissions = doesExampleMatchSelected(
|
||||
example.permissions,
|
||||
requiredPermissions,
|
||||
);
|
||||
const matchesPackages = doesExampleMatchSelected(
|
||||
example.packages,
|
||||
requiredPackages,
|
||||
);
|
||||
return matchesText && matchesApis && matchesPermissions && matchesPackages;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="example-layout">
|
||||
<div class="search">
|
||||
<input v-model="searchText" placeholder="Search for an example..." />
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<ExampleSearchFilterByItem
|
||||
label="APIs"
|
||||
:items="exampleMetadata?.allApis"
|
||||
v-model="selectedApis"
|
||||
/>
|
||||
<ExampleSearchFilterByItem
|
||||
label="Permissions"
|
||||
:items="exampleMetadata?.allPermissions"
|
||||
v-model="selectedPermissions"
|
||||
/>
|
||||
<ExampleSearchFilterByItem
|
||||
label="Packages"
|
||||
:items="exampleMetadata?.allPackages"
|
||||
v-model="selectedPackages"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="results">
|
||||
<p v-if="exampleMetadata == null">Loading examples...</p>
|
||||
<template v-else>
|
||||
<ul class="search-results">
|
||||
<ExampleSearchResult
|
||||
v-for="example of filteredExamples"
|
||||
:key="example.name"
|
||||
:example
|
||||
/>
|
||||
</ul>
|
||||
<p v-if="filteredExamples.length === 0">No matching examples</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.example-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-areas:
|
||||
'search'
|
||||
'results';
|
||||
gap: 16px;
|
||||
}
|
||||
@media only screen and (min-width: 720px) {
|
||||
.example-layout {
|
||||
grid-template-columns: 256px 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-areas:
|
||||
'filters search'
|
||||
'filters results';
|
||||
}
|
||||
}
|
||||
.search {
|
||||
grid-area: search;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
border-radius: 16px;
|
||||
}
|
||||
.filters {
|
||||
display: none;
|
||||
grid-area: filters;
|
||||
}
|
||||
@media only screen and (min-width: 720px) {
|
||||
.filters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
.results {
|
||||
grid-area: results;
|
||||
}
|
||||
|
||||
.box {
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.search input {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.checkbox-col {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
max-height: 200px;
|
||||
font-size: 14px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.checkbox-col .header {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
.checkbox-col p {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: flex-start;
|
||||
text-wrap: wrap;
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
span {
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.checkbox-col input[type='checkbox'] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.checkbox-col-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.search-results {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
@media only screen and (min-width: 800px) {
|
||||
.search-results {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 1024px) {
|
||||
.search-results {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: red;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,100 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, toRaw } from 'vue';
|
||||
import { KeySelectedObject } from '../utils/types';
|
||||
|
||||
const props = defineProps<{
|
||||
label: string;
|
||||
items?: string[];
|
||||
}>();
|
||||
|
||||
const selectedItems = defineModel<KeySelectedObject>({
|
||||
required: true,
|
||||
});
|
||||
|
||||
const count = computed(() => {
|
||||
return Object.values(toRaw(selectedItems.value)).filter(Boolean).length;
|
||||
});
|
||||
|
||||
function toggleItem(pkg: string) {
|
||||
selectedItems.value = {
|
||||
...toRaw(selectedItems.value),
|
||||
[pkg]: !selectedItems.value[pkg],
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="filter-container">
|
||||
<p class="header">
|
||||
<span>Filter by {{ label }}</span> <span v-if="count">({{ count }})</span>
|
||||
</p>
|
||||
<div class="scroll-container">
|
||||
<ul>
|
||||
<li v-for="item in items">
|
||||
<label :title="item">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="selectedItems[item]"
|
||||
@input="toggleItem(item)"
|
||||
/>
|
||||
<span>{{ item }}</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.filter-container {
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.scroll-container ul {
|
||||
position: absolute;
|
||||
overflow-y: auto;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-size: small;
|
||||
padding: 8px 16px 16px 16px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
opacity: 50%;
|
||||
}
|
||||
label {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: flex-start;
|
||||
text-wrap: wrap;
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 140%;
|
||||
cursor: pointer;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
span {
|
||||
padding-top: 1px;
|
||||
}
|
||||
input[type='checkbox'] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts" setup>
|
||||
import { Example } from '../utils/types';
|
||||
|
||||
const props = defineProps<{
|
||||
example: Example;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li>
|
||||
<a :href="example.url" target="_blank">
|
||||
<p class="name">{{ example.name }}</p>
|
||||
<p class="description">{{ example.description }}</p>
|
||||
<p class="link">Open →</p>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
* {
|
||||
min-width: 0;
|
||||
}
|
||||
a {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 2px solid var(--vp-c-bg-soft);
|
||||
border-radius: 16px;
|
||||
color: var(--vp-c-text-1) !important;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
outline: 2px solid var(--vp-c-brand-2);
|
||||
}
|
||||
.name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-shrink: 0;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.description {
|
||||
height: 53px;
|
||||
opacity: 70%;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
line-height: 120%;
|
||||
min-height: 0;
|
||||
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.link {
|
||||
opacity: 0;
|
||||
transition: 250ms;
|
||||
color: var(--vp-c-brand-2);
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
a:hover .link {
|
||||
opacity: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -20,6 +20,21 @@ const chromeExtensionIds = [
|
||||
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
|
||||
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
|
||||
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
|
||||
'ceicccfeikoipigeghddpocceifjelph', // Plex Skipper
|
||||
'aelkipgppclpfimeamgmlonimflbhlgf', // GitHub Custom Notifier
|
||||
'djnlaiohfaaifbibleebjggkghlmcpcj', // Fluent Read
|
||||
'nhclljcpfmmaiojbhhnkpjcfmacfcian', // Facebook Video Controls
|
||||
'mblkhbaakhbhiimkbcnmeciblfhmafna', // ElemSnap - Quick capture of webpage elements and conversion to images,
|
||||
'oajalfneblkfiejoadecnmodfpnaeblh', // MS Edge TTS (Text to Speech)
|
||||
'nedcanggplmbbgmlpcjiafgjcpdimpea', // YTBlock - Block any content from YouTube™
|
||||
'oadbjpccljkplmhnjekgjamejnbadlne', // demo.fun - Interactive product demos that convert
|
||||
'iopdafdcollfgaoffingmahpffckmjni', // SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
|
||||
'khjdmjcmpolknpccmaaipmidphjokhdf', // WorkFlowy MultiFlow
|
||||
'fencadnndhdeggodopebjgdfdlhcimfk', // 香草布丁🌿🍮- https://github.com/Xdy1579883916/vanilla-pudding
|
||||
'bnacincmbaknlbegecpioobkfgejlojp', // MaxFocus: Link Preview
|
||||
'bcpgdpedphodjcjlminjbdeejccjbimp', // 汇率转换-中文版本
|
||||
'loeilaonggnalkaiiaepbegccilkmjjp', // Currency Converter Plus
|
||||
'npcnninnjghigjfiecefheeibomjpkak', // Respond Easy
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
@@ -45,90 +60,56 @@ function getStoreUrl(extension: ChromeExtension) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="vp-doc">
|
||||
<div class="container">
|
||||
<h2 id="whos-using-wxt">Who's Using WXT?</h2>
|
||||
<p>
|
||||
Battle tested and ready for production. Explore chrome extensions made
|
||||
with WXT.
|
||||
</p>
|
||||
<p v-if="isLoading" style="text-align: center; opacity: 50%">
|
||||
Loading...
|
||||
</p>
|
||||
<p
|
||||
v-else-if="err || sortedExtensions.length === 0"
|
||||
style="text-align: center; opacity: 50%"
|
||||
>
|
||||
Failed to load extension details.
|
||||
</p>
|
||||
<ul v-else>
|
||||
<li
|
||||
v-for="extension of sortedExtensions"
|
||||
:key="extension.id"
|
||||
class="relative"
|
||||
>
|
||||
<img
|
||||
:src="extension.iconUrl"
|
||||
:alt="`${extension.name} icon`"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<div class="relative">
|
||||
<a
|
||||
:href="getStoreUrl(extension)"
|
||||
target="_blank"
|
||||
:title="extension.name"
|
||||
class="extension-name"
|
||||
>{{ extension.name }}</a
|
||||
>
|
||||
<p class="description" :title="extension.shortDescription">
|
||||
{{ extension.shortDescription }}
|
||||
</p>
|
||||
</div>
|
||||
<p class="user-count">
|
||||
<span>{{ extension.weeklyActiveUsers.toLocaleString() }} users</span
|
||||
><template v-if="extension.rating != null"
|
||||
>,
|
||||
<span>{{ extension.rating }} stars</span>
|
||||
</template>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="centered pr">
|
||||
<p v-if="isLoading" style="text-align: center; opacity: 50%">Loading...</p>
|
||||
<p
|
||||
v-else-if="err || sortedExtensions.length === 0"
|
||||
style="text-align: center; opacity: 50%"
|
||||
>
|
||||
Failed to load extension details.
|
||||
</p>
|
||||
<ul v-else>
|
||||
<li
|
||||
v-for="extension of sortedExtensions"
|
||||
:key="extension.id"
|
||||
class="relative"
|
||||
>
|
||||
<img
|
||||
:src="extension.iconUrl"
|
||||
:alt="`${extension.name} icon`"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<div class="relative">
|
||||
<a
|
||||
href="https://github.com/wxt-dev/wxt/edit/main/docs/.vitepress/components/UsingWxtSection.vue"
|
||||
:href="getStoreUrl(extension)"
|
||||
target="_blank"
|
||||
>Open a PR</a
|
||||
:title="extension.name"
|
||||
class="extension-name"
|
||||
>{{ extension.name }}</a
|
||||
>
|
||||
to add your extension to the list!
|
||||
<p class="description" :title="extension.shortDescription">
|
||||
{{ extension.shortDescription }}
|
||||
</p>
|
||||
</div>
|
||||
<p class="user-count">
|
||||
<span>{{ extension.weeklyActiveUsers.toLocaleString() }} users</span
|
||||
><template v-if="extension.rating != null"
|
||||
>,
|
||||
<span>{{ extension.rating }} stars</span>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="centered pr">
|
||||
<a
|
||||
href="https://github.com/wxt-dev/wxt/edit/main/docs/.vitepress/components/UsingWxtSection.vue"
|
||||
target="_blank"
|
||||
>Open a PR</a
|
||||
>
|
||||
to add your extension to the list!
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vp-doc {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.vp-doc {
|
||||
padding: 0 48px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.vp-doc {
|
||||
padding: 0 64px;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1152px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
li img {
|
||||
width: 116px;
|
||||
height: 116px;
|
||||
|
||||
+122
-106
@@ -1,23 +1,16 @@
|
||||
import { DefaultTheme, defineConfig } from 'vitepress';
|
||||
import { generateCliDocs } from './plugins/generate-cli-docs';
|
||||
import typedocSidebar from '../api/typedoc-sidebar.json';
|
||||
|
||||
const filteredTypedocSidebar = typedocSidebar.filter(
|
||||
(item) => item.text !== 'API',
|
||||
);
|
||||
// Typedoc's markdown theme adds collapse: true to all our items, event ones without any children,
|
||||
// so they need to be removed.
|
||||
function removeCollapsedWithNoItems(items: DefaultTheme.SidebarItem[]) {
|
||||
for (const item of items) {
|
||||
if (item.items) removeCollapsedWithNoItems(item.items);
|
||||
else delete item.collapsed;
|
||||
}
|
||||
}
|
||||
removeCollapsedWithNoItems(filteredTypedocSidebar);
|
||||
import { defineConfig } from 'vitepress';
|
||||
import typedocSidebar from '../api/reference/typedoc-sidebar.json';
|
||||
import {
|
||||
menuGroup,
|
||||
menuItem,
|
||||
menuRoot,
|
||||
navItem,
|
||||
prepareTypedocSidebar,
|
||||
} from './utils/menus';
|
||||
import { meta, script } from './utils/head';
|
||||
|
||||
const title = 'Next-gen Web Extension Framework';
|
||||
const titleSuffix = ' – WXT';
|
||||
|
||||
const description =
|
||||
"WXT provides the best developer experience, making it quick, easy, and fun to develop chrome extensions for all browsers. With built-in utilities for building, zipping, and publishing your extension, it's easy to get started.";
|
||||
const ogTitle = `${title}${titleSuffix}`;
|
||||
@@ -31,7 +24,6 @@ export default defineConfig({
|
||||
description,
|
||||
vite: {
|
||||
clearScreen: false,
|
||||
plugins: [generateCliDocs()],
|
||||
},
|
||||
lastUpdated: true,
|
||||
sitemap: {
|
||||
@@ -39,20 +31,16 @@ export default defineConfig({
|
||||
},
|
||||
|
||||
head: [
|
||||
['meta', { property: 'og:type', content: 'website' }],
|
||||
['meta', { property: 'og:title', content: ogTitle }],
|
||||
['meta', { property: 'og:image', content: ogImage }],
|
||||
['meta', { property: 'og:url', content: ogUrl }],
|
||||
['meta', { property: 'og:description', content: description }],
|
||||
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
|
||||
[
|
||||
'script',
|
||||
{
|
||||
async: '',
|
||||
'data-website-id': 'c1840c18-a12c-4a45-a848-55ae85ef7915',
|
||||
src: 'https://umami.aklinker1.io/script.js',
|
||||
},
|
||||
],
|
||||
meta('og:type', 'website'),
|
||||
meta('og:title', ogTitle),
|
||||
meta('og:image', ogImage),
|
||||
meta('og:url', ogUrl),
|
||||
meta('og:description', description),
|
||||
meta('twitter:card', 'summary_large_image', { useName: true }),
|
||||
script('https://umami.aklinker1.io/script.js', {
|
||||
'data-website-id': 'c1840c18-a12c-4a45-a848-55ae85ef7915',
|
||||
async: '',
|
||||
}),
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
@@ -61,91 +49,119 @@ export default defineConfig({
|
||||
src: '/logo.svg',
|
||||
alt: 'WXT logo',
|
||||
},
|
||||
|
||||
editLink: {
|
||||
pattern: 'https://github.com/wxt-dev/wxt/edit/main/docs/:path',
|
||||
},
|
||||
|
||||
search: {
|
||||
provider: 'local',
|
||||
},
|
||||
|
||||
socialLinks: [
|
||||
{ icon: 'discord', link: 'https://discord.gg/ZFsZqGery9' },
|
||||
{ icon: 'github', link: 'https://github.com/wxt-dev/wxt' },
|
||||
],
|
||||
|
||||
nav: [
|
||||
{ text: 'Guide', link: '/guide/installation.md' },
|
||||
{ text: 'Entrypoints', link: '/entrypoints/background.md' },
|
||||
{ text: 'Examples', link: '/examples.md' },
|
||||
{ text: 'API', link: '/api/cli.md' },
|
||||
navItem('Get Started', '/get-started/introduction'),
|
||||
navItem('Guide', '/guide/key-concepts/manifest'),
|
||||
navItem('API', '/api/reference/wxt'),
|
||||
navItem('Examples', '/examples'),
|
||||
],
|
||||
|
||||
sidebar: {
|
||||
'/guide/': [
|
||||
{
|
||||
text: 'Guide',
|
||||
items: [
|
||||
{ text: 'Introduction', link: '/guide/introduction.md' },
|
||||
{ text: 'Installation', link: '/guide/installation.md' },
|
||||
{ text: 'Configuration', link: '/guide/configuration.md' },
|
||||
{ text: 'Entrypoints', link: '/guide/entrypoints.md' },
|
||||
{ text: 'Manifest.json', link: '/guide/manifest.md' },
|
||||
{ text: 'Extension APIs', link: '/guide/extension-apis.md' },
|
||||
{ text: 'Storage', link: '/guide/storage.md' },
|
||||
{ text: 'Assets', link: '/guide/assets.md' },
|
||||
{ text: 'Content Script UI', link: '/guide/content-script-ui.md' },
|
||||
{ text: 'Multiple Browsers', link: '/guide/multiple-browsers.md' },
|
||||
{ text: 'ES Modules', link: '/guide/esm.md' },
|
||||
{ text: 'Auto-imports', link: '/guide/auto-imports.md' },
|
||||
{ text: 'Vite', link: '/guide/vite.md' },
|
||||
{ text: 'Remote Code', link: '/guide/remote-code.md' },
|
||||
{ text: 'Publishing', link: '/guide/publishing.md' },
|
||||
{ text: 'Development', link: '/guide/development.md' },
|
||||
{ text: 'Testing', link: '/guide/testing.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Other',
|
||||
items: [
|
||||
{ text: 'Migrate to WXT', link: '/guide/migrate-to-wxt.md' },
|
||||
{ text: 'Compare', link: '/guide/compare.md' },
|
||||
],
|
||||
},
|
||||
],
|
||||
'/entrypoints/': [
|
||||
{
|
||||
text: 'Entrypoints',
|
||||
items: [
|
||||
{ text: 'Background', link: '/entrypoints/background.md' },
|
||||
{ text: 'Bookmarks', link: '/entrypoints/bookmarks.md' },
|
||||
{
|
||||
text: 'Content Scripts',
|
||||
link: '/entrypoints/content-scripts.md',
|
||||
},
|
||||
{ text: 'CSS', link: '/entrypoints/css.md' },
|
||||
{ text: 'Devtools', link: '/entrypoints/devtools.md' },
|
||||
{ text: 'History', link: '/entrypoints/history.md' },
|
||||
{ text: 'Newtab', link: '/entrypoints/newtab.md' },
|
||||
{ text: 'Options', link: '/entrypoints/options.md' },
|
||||
{ text: 'Popup', link: '/entrypoints/popup.md' },
|
||||
{ text: 'Sandbox', link: '/entrypoints/sandbox.md' },
|
||||
{ text: 'Side Panel', link: '/entrypoints/sidepanel.md' },
|
||||
{ text: 'Unlisted Pages', link: '/entrypoints/unlisted-pages.md' },
|
||||
{
|
||||
text: 'Unlisted Scripts',
|
||||
link: '/entrypoints/unlisted-scripts.md',
|
||||
},
|
||||
].sort((l, r) => l.text.localeCompare(r.text)),
|
||||
},
|
||||
],
|
||||
'/api/': [
|
||||
{
|
||||
items: [
|
||||
{ text: 'CLI', link: '/api/cli.md' },
|
||||
{
|
||||
text: 'Modules',
|
||||
items: filteredTypedocSidebar,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
'/get-started/': menuRoot([
|
||||
menuGroup('Get Started', '/get-started/', [
|
||||
menuItem('Introduction', 'introduction'),
|
||||
menuItem('Installation', 'installation'),
|
||||
menuItem('Configuration', 'configuration'),
|
||||
menuItem('Entrypoints', 'entrypoints'),
|
||||
menuItem('Assets', 'assets'),
|
||||
menuItem('Publishing', 'publishing'),
|
||||
menuItem('Migrate to WXT', 'migrate-to-wxt'),
|
||||
menuItem('Compare', 'compare'),
|
||||
]),
|
||||
]),
|
||||
'/guide/': menuRoot([
|
||||
menuGroup('Key Concepts', '/guide/key-concepts/', [
|
||||
menuItem('Manifest', 'manifest'),
|
||||
menuItem('Auto-imports', 'auto-imports'),
|
||||
menuItem('Web Extension Polyfill', 'web-extension-polyfill'),
|
||||
menuItem('Frontend Frameworks', 'frontend-frameworks'),
|
||||
menuItem('Content Script UI', 'content-script-ui'),
|
||||
]),
|
||||
menuGroup('Directory Structure', '/guide/directory-structure/', [
|
||||
// Folders
|
||||
menuItem('.output/', 'output'),
|
||||
menuItem('.wxt/', 'wxt'),
|
||||
menuItem('assets/', 'assets'),
|
||||
menuItem('components/', 'components'),
|
||||
menuItem('composables/', 'composables'),
|
||||
menuGroup('entrypoints/', '/guide/directory-structure/entrypoints/', [
|
||||
menuItem('background', 'background.md'),
|
||||
menuItem('bookmarks', 'bookmarks.md'),
|
||||
menuItem('*.content.ts', 'content-scripts.md'),
|
||||
menuItem('*.css', 'css.md'),
|
||||
menuItem('devtools', 'devtools.md'),
|
||||
menuItem('history', 'history.md'),
|
||||
menuItem('newtab', 'newtab.md'),
|
||||
menuItem('options', 'options.md'),
|
||||
menuItem('popup', 'popup.md'),
|
||||
menuItem('sandbox', 'sandbox.md'),
|
||||
menuItem('sidepanel', 'sidepanel.md'),
|
||||
menuItem('*.html', 'unlisted-pages.md'),
|
||||
menuItem('*.ts', 'unlisted-scripts.md'),
|
||||
]),
|
||||
menuItem('hooks/', 'hooks'),
|
||||
menuItem('public/', 'public/', [
|
||||
menuItem('_locales/', 'public/locales'),
|
||||
]),
|
||||
menuItem('utils/', 'utils'),
|
||||
|
||||
socialLinks: [{ icon: 'github', link: 'https://github.com/wxt-dev/wxt' }],
|
||||
// Files
|
||||
menuItem('.env', 'env'),
|
||||
menuItem('app.config.ts', 'app-config'),
|
||||
menuItem('package.json', 'package'),
|
||||
menuItem('tsconfig.json', 'tsconfig'),
|
||||
menuItem('web-ext.config.ts', 'web-ext-config'),
|
||||
menuItem('wxt.config.ts', 'wxt-config'),
|
||||
]),
|
||||
menuGroup('Extension APIs', '/guide/extension-apis/', [
|
||||
menuItem('Storage', 'storage'),
|
||||
menuItem('Messaging', 'messaging'),
|
||||
menuItem('Scripting', 'scripting'),
|
||||
menuItem('Others', 'others'),
|
||||
]),
|
||||
menuGroup('Go Further', '/guide/go-further/', [
|
||||
menuItem('Testing', 'testing'),
|
||||
menuItem('ES Modules', 'es-modules'),
|
||||
menuItem('Debugging', 'debugging'),
|
||||
menuItem('Handling Updates', 'handling-updates'),
|
||||
menuItem('Vite', 'vite'),
|
||||
menuItem('Custom Events', 'custom-events'),
|
||||
menuItem('Reusable Modules', 'reusable-modules'),
|
||||
menuItem('Remote Code', 'remote-code'),
|
||||
menuItem('Entrypoint Side Effects', 'entrypoint-side-effects'),
|
||||
menuItem('How WXT Works', 'how-wxt-works'),
|
||||
]),
|
||||
menuGroup('Upgrade Guide', '/guide/upgrade-guide/', [
|
||||
menuItem('wxt', 'wxt'),
|
||||
]),
|
||||
]),
|
||||
'/api/': menuRoot([
|
||||
menuGroup('CLI', '/api/cli/', [
|
||||
menuItem('wxt', 'wxt.md'),
|
||||
menuItem('wxt build', 'wxt-build.md'),
|
||||
menuItem('wxt zip', 'wxt-zip.md'),
|
||||
menuItem('wxt prepare', 'wxt-prepare.md'),
|
||||
menuItem('wxt clean', 'wxt-clean.md'),
|
||||
menuItem('wxt init', 'wxt-init.md'),
|
||||
menuItem('wxt submit', 'wxt-submit.md'),
|
||||
menuItem('wxt submit init', 'wxt-submit-init.md'),
|
||||
]),
|
||||
menuGroup('API Reference', prepareTypedocSidebar(typedocSidebar)),
|
||||
]),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { resolve, join } from 'node:path';
|
||||
import consola from 'consola';
|
||||
import { execaCommand } from 'execa';
|
||||
|
||||
const cliDir = resolve('packages/wxt/src/cli/commands');
|
||||
const cliDirGlob = resolve(cliDir, '**');
|
||||
|
||||
export default {
|
||||
watch: [cliDirGlob],
|
||||
async load() {
|
||||
consola.info(`Generating CLI docs`);
|
||||
|
||||
const [wxt, build, zip, prepare, clean, init, submit, submitInit] =
|
||||
await Promise.all([
|
||||
getWxtHelp(''),
|
||||
getWxtHelp('build'),
|
||||
getWxtHelp('zip'),
|
||||
getWxtHelp('prepare'),
|
||||
getWxtHelp('clean'),
|
||||
getWxtHelp('init'),
|
||||
getPublishExtensionHelp(''),
|
||||
getPublishExtensionHelp('init'),
|
||||
]);
|
||||
|
||||
consola.success(`Generated CLI docs`);
|
||||
return {
|
||||
wxt,
|
||||
build,
|
||||
zip,
|
||||
prepare,
|
||||
clean,
|
||||
init,
|
||||
submit,
|
||||
submitInit,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
async function getHelp(command: string): Promise<string> {
|
||||
const res = await execaCommand(command + ' --help', {
|
||||
cwd: 'packages/wxt',
|
||||
});
|
||||
return res.stdout;
|
||||
}
|
||||
|
||||
function getWxtHelp(command: string): Promise<string> {
|
||||
return getHelp(`pnpm -s wxt ${command}`.trim());
|
||||
}
|
||||
|
||||
async function getPublishExtensionHelp(command: string): Promise<string> {
|
||||
const res = await getHelp(
|
||||
`./node_modules/.bin/publish-extension ${command}`.trim(),
|
||||
);
|
||||
return res.replace(/\$ publish-extension/g, '$ wxt submit');
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
name: string;
|
||||
docs: string;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# CLI Reference
|
||||
|
||||
> Reference generated from `wxt <command> --help`
|
||||
|
||||
{{ DOCS }}
|
||||
@@ -1,114 +0,0 @@
|
||||
import { relative, resolve } from 'node:path';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { format } from 'prettier';
|
||||
import consola from 'consola';
|
||||
import { execaCommand } from 'execa';
|
||||
|
||||
let hasGenerated = false;
|
||||
|
||||
const cliDir = resolve('src/cli/commands');
|
||||
const cliDirGlob = resolve(cliDir, '**');
|
||||
const cliTemplatePath = resolve('docs/.vitepress/plugins/cli.tpl.md');
|
||||
const cliPath = resolve('docs/api/cli.md');
|
||||
|
||||
const PREFACE = `<!--
|
||||
DO NOT EDIT
|
||||
Generated by \`wxt/docs/.vitepress/plugins/generate-config-docs.ts\`
|
||||
To make changes to the config reference, update the JSDoc in \`src/core/types/external.ts\`.
|
||||
-->`;
|
||||
|
||||
export function generateCliDocs() {
|
||||
writeFileSync(cliPath, '');
|
||||
|
||||
const generateDocs = async () => {
|
||||
consola.info(`Generating ${relative(process.cwd(), cliPath)}`);
|
||||
try {
|
||||
const res = await execaCommand('pnpm -s wxt --help');
|
||||
const dev = splitInfo(res.stdout);
|
||||
const lines: Array<string | string[]> = [
|
||||
`## \`wxt\``,
|
||||
'```sh',
|
||||
dev.rest,
|
||||
'```',
|
||||
];
|
||||
|
||||
const commands = await Promise.allSettled(
|
||||
extractCommands(dev.info).map(async (command) => {
|
||||
const res = await execaCommand(`pnpm -s wxt ${command} --help`);
|
||||
const { rest: docs } = splitInfo(res.stdout);
|
||||
return [`## \`wxt ${command}\``, '```sh', docs, '```'];
|
||||
}),
|
||||
);
|
||||
lines.push(
|
||||
...commands
|
||||
.filter((res) => res.status === 'fulfilled')
|
||||
.map(
|
||||
(res) => (res as unknown as PromiseFulfilledResult<string>).value,
|
||||
),
|
||||
);
|
||||
|
||||
const text = await format(
|
||||
PREFACE +
|
||||
'\n\n' +
|
||||
readFileSync(cliTemplatePath, 'utf-8').replace(
|
||||
'{{ DOCS }}',
|
||||
lines.flat().join('\n'),
|
||||
),
|
||||
{ parser: 'markdown' },
|
||||
);
|
||||
|
||||
writeFileSync(cliPath, text);
|
||||
consola.success(`Generated ${relative(process.cwd(), cliPath)}`);
|
||||
} catch (err) {
|
||||
consola.fail(`Failed to generate ${relative(process.cwd(), cliPath)}`);
|
||||
consola.error(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
name: 'docs:generate-cli-docs',
|
||||
async config() {
|
||||
if (!hasGenerated) {
|
||||
hasGenerated = true;
|
||||
await generateDocs();
|
||||
}
|
||||
},
|
||||
configureServer(server: any) {
|
||||
server.watcher.add(cliDirGlob);
|
||||
},
|
||||
async handleHotUpdate(ctx: { file: string }) {
|
||||
if (ctx.file === cliTemplatePath || ctx.file.includes(cliDir)) {
|
||||
await generateDocs();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function splitInfo(text: string): { info: string; rest: string } {
|
||||
const infoStart = text.indexOf('For more info,');
|
||||
const infoEnd = text.indexOf('\n\nOptions:');
|
||||
|
||||
if (infoStart === -1 || infoEnd === -1) {
|
||||
return { info: '', rest: text };
|
||||
}
|
||||
|
||||
const info = text.substring(infoStart, infoEnd).trim();
|
||||
const rest = text.replace(info, '').trim().replace('\n\n\n\n', '\n\n');
|
||||
|
||||
return { info, rest };
|
||||
}
|
||||
|
||||
function extractCommands(info: string): string[] {
|
||||
const commands: string[] = [];
|
||||
|
||||
// Split the info by line and iterate through each line
|
||||
info.split('\n').forEach((line) => {
|
||||
// Use regex to capture the command after "$ wxt " and before "--help"
|
||||
const match = line.match(/\$ wxt (\w+) --help/);
|
||||
if (match && match[1]) {
|
||||
commands.push(match[1]);
|
||||
}
|
||||
});
|
||||
|
||||
return commands;
|
||||
}
|
||||
@@ -72,6 +72,45 @@
|
||||
.vp-doc .no-vertical-dividers td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.vp-doc .no-vertical-dividers tr {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.VPSidebar {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.VPSidebarItem.level-0.collapsible {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.VPSidebarItem.level-0.collapsible .items {
|
||||
/*border-left: 1px solid var(--vp-c-divider);*/
|
||||
}
|
||||
|
||||
.VPSidebarItem.level-1 {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
.VPSidebar .group + .group {
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.VPSidebarItem .badge {
|
||||
display: inline-block;
|
||||
min-width: 1.6em;
|
||||
padding: 0.3em 0.4em 0.2em;
|
||||
border-radius: 1rem;
|
||||
font-size: 0.75em;
|
||||
line-height: 1;
|
||||
margin-left: 0.5rem;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
background-color: var(--vp-c-default-2);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import DefaultTheme from 'vitepress/theme';
|
||||
import Icon from '../components/Icon.vue';
|
||||
import EntrypointPatterns from '../components/EntrypointPatterns.vue';
|
||||
import UsingWxtSection from '../components/UsingWxtSection.vue';
|
||||
import ExampleList from '../components/ExampleList.vue';
|
||||
import ExampleSearch from '../components/ExampleSearch.vue';
|
||||
import './custom.css';
|
||||
|
||||
export default {
|
||||
@@ -11,6 +11,6 @@ export default {
|
||||
ctx.app.component('Icon', Icon);
|
||||
ctx.app.component('EntrypointPatterns', EntrypointPatterns);
|
||||
ctx.app.component('UsingWxtSection', UsingWxtSection);
|
||||
ctx.app.component('ExampleList', ExampleList);
|
||||
ctx.app.component('ExampleSearch', ExampleSearch);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { HeadConfig } from 'vitepress/types/shared';
|
||||
|
||||
export function meta(
|
||||
property: string,
|
||||
content: string,
|
||||
options?: { useName: boolean },
|
||||
): HeadConfig {
|
||||
return [
|
||||
'meta',
|
||||
{
|
||||
[options?.useName ? 'name' : 'property']: property,
|
||||
content,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function script(
|
||||
src: string,
|
||||
props: Record<string, string> = {},
|
||||
): HeadConfig {
|
||||
return [
|
||||
'script',
|
||||
{
|
||||
...props,
|
||||
src,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { DefaultTheme } from 'vitepress';
|
||||
|
||||
type SidebarItem = DefaultTheme.SidebarItem;
|
||||
type NavItem = DefaultTheme.NavItem;
|
||||
|
||||
export function navItem(text: string, link: string): NavItem {
|
||||
return { text, link };
|
||||
}
|
||||
|
||||
export function menuRoot(items: SidebarItem[]) {
|
||||
return items.map((item, index) => {
|
||||
// item.collapsed = false; // uncomment to expand all level-0 items
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
export function menuGroup(text: string, items: SidebarItem[]): SidebarItem;
|
||||
export function menuGroup(
|
||||
text: string,
|
||||
base: string,
|
||||
items: SidebarItem[],
|
||||
): SidebarItem;
|
||||
export function menuGroup(
|
||||
text: string,
|
||||
a: string | SidebarItem[],
|
||||
b?: SidebarItem[],
|
||||
): SidebarItem {
|
||||
const collapsed = true;
|
||||
if (typeof a === 'string') {
|
||||
return {
|
||||
text,
|
||||
base: a,
|
||||
items: b,
|
||||
collapsed,
|
||||
};
|
||||
}
|
||||
return {
|
||||
text,
|
||||
items: a,
|
||||
collapsed,
|
||||
};
|
||||
}
|
||||
|
||||
export function menuItems(items: SidebarItem[]) {
|
||||
return {
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
export function menuItem(
|
||||
text: string,
|
||||
link: string,
|
||||
items?: SidebarItem[],
|
||||
): SidebarItem {
|
||||
if (items) {
|
||||
return { text, link, items };
|
||||
}
|
||||
return { text, link };
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up and add badges to typedoc leaf sections
|
||||
*/
|
||||
export function prepareTypedocSidebar(items: SidebarItem[]) {
|
||||
// skip contents file
|
||||
const filtered = items.slice(1);
|
||||
|
||||
// remove Typedoc's collapse: true from text nodes
|
||||
const prepareItems = (items: DefaultTheme.SidebarItem[], depth = 0) => {
|
||||
for (const item of items) {
|
||||
if (item.items) {
|
||||
prepareItems(item.items, depth + 1);
|
||||
const hasLeaves = item.items.some((item) => !item.items);
|
||||
if (hasLeaves) {
|
||||
item.text += ` <span class="badge">${item.items.length}</span>`;
|
||||
}
|
||||
} else {
|
||||
delete item.collapsed;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// process
|
||||
prepareItems(filtered);
|
||||
|
||||
// return
|
||||
return filtered;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export interface Example {
|
||||
name: string;
|
||||
description?: string;
|
||||
url: string;
|
||||
searchText: string;
|
||||
apis: string[];
|
||||
permissions: string[];
|
||||
packages: string[];
|
||||
}
|
||||
|
||||
export type ExamplesMetadata = {
|
||||
examples: Example[];
|
||||
allApis: string[];
|
||||
allPermissions: string[];
|
||||
allPackages: string[];
|
||||
};
|
||||
|
||||
export type KeySelectedObject = Record<string, boolean | undefined>;
|
||||
@@ -0,0 +1,7 @@
|
||||
# `wxt build`
|
||||
|
||||
<script setup>
|
||||
import { data } from '../../.vitepress/loaders/cli.data.ts'
|
||||
</script>
|
||||
|
||||
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.build }}</code></pre></div>
|
||||
@@ -0,0 +1,7 @@
|
||||
# `wxt clean`
|
||||
|
||||
<script setup>
|
||||
import { data } from '../../.vitepress/loaders/cli.data.ts'
|
||||
</script>
|
||||
|
||||
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.clean }}</code></pre></div>
|
||||
@@ -0,0 +1,7 @@
|
||||
# `wxt init`
|
||||
|
||||
<script setup>
|
||||
import { data } from '../../.vitepress/loaders/cli.data.ts'
|
||||
</script>
|
||||
|
||||
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.init }}</code></pre></div>
|
||||
@@ -0,0 +1,7 @@
|
||||
# `wxt prepare`
|
||||
|
||||
<script setup>
|
||||
import { data } from '../../.vitepress/loaders/cli.data.ts'
|
||||
</script>
|
||||
|
||||
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.prepare }}</code></pre></div>
|
||||
@@ -0,0 +1,9 @@
|
||||
# `wxt submit init`
|
||||
|
||||
> Alias for [`publish-browser-extension`](https://www.npmjs.com/package/publish-browser-extension)
|
||||
|
||||
<script setup>
|
||||
import { data } from '../../.vitepress/loaders/cli.data.ts'
|
||||
</script>
|
||||
|
||||
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.submitInit }}</code></pre></div>
|
||||
@@ -0,0 +1,9 @@
|
||||
# `wxt submit`
|
||||
|
||||
> Alias for [`publish-browser-extension`](https://www.npmjs.com/package/publish-browser-extension)
|
||||
|
||||
<script setup>
|
||||
import { data } from '../../.vitepress/loaders/cli.data.ts'
|
||||
</script>
|
||||
|
||||
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.submit }}</code></pre></div>
|
||||
@@ -0,0 +1,7 @@
|
||||
# `wxt zip`
|
||||
|
||||
<script setup>
|
||||
import { data } from '../../.vitepress/loaders/cli.data.ts'
|
||||
</script>
|
||||
|
||||
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.zip }}</code></pre></div>
|
||||
@@ -0,0 +1,7 @@
|
||||
# `wxt`
|
||||
|
||||
<script setup>
|
||||
import { data } from '../../.vitepress/loaders/cli.data.ts'
|
||||
</script>
|
||||
|
||||
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.wxt }}</code></pre></div>
|
||||
+16
-4
@@ -1,7 +1,19 @@
|
||||
# Examples
|
||||
---
|
||||
layout: page
|
||||
---
|
||||
|
||||
Simple walkthroughs to accomplish common tasks or patterns with WXT.
|
||||
<style>
|
||||
.examples-container {
|
||||
padding: 32px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ExampleList />
|
||||
<div class="examples-container">
|
||||
<div class="vp-doc">
|
||||
<h1>Examples</h1>
|
||||
</div>
|
||||
|
||||
> Full code available at [`wxt-dev/wxt-examples`](https://github.com/wxt-dev/wxt-examples)
|
||||
<br />
|
||||
|
||||
<ExampleSearch />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Assets
|
||||
|
||||
WXT has two directories for storing assets like CSS, images, or fonts.
|
||||
|
||||
- [public directory](/guide/directory-structure/public/): Store files that will be copied into the output directory as-is
|
||||
- [assets directory](/guide/directory-structure/assets): Store files that will be processed by Vite during the build process
|
||||
|
||||
To learn more about how to use assets at runtime from either of these directories, visit their guides linked above.
|
||||
|
||||
## Public Directory
|
||||
|
||||
Place static files like the extension icon or `_locales/` directory here. These files will be copied over to the output directory without being transformed by Vite.
|
||||
|
||||
```
|
||||
<srcDir>
|
||||
└─ public/
|
||||
├─ icon-16.png
|
||||
├─ icon-32.png
|
||||
├─ icon-48.png
|
||||
├─ icon-96.png
|
||||
└─ icon-128.png
|
||||
```
|
||||
|
||||
## Assets Directory
|
||||
|
||||
Files in the assets directory will be processed by Vite. They are imported in your source code, and will be transformed or renamed in the output directory.
|
||||
|
||||
```
|
||||
<srcDir>
|
||||
└─ assets/
|
||||
├─ tailwind.css
|
||||
└─ illustration.svg
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
# Compare
|
||||
|
||||
Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework) (another framework) and [CRXJS](https://crxjs.dev/vite-plugin) (a bundler plugin).
|
||||
|
||||
## Overview
|
||||
|
||||
| Features | WXT | Plasmo | CRXJS |
|
||||
| ---------------------------------------------------- | :--------------: | :-------------: | :--------------: |
|
||||
| Supports all browsers | ✅ | ✅ | 🟡 <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>
|
||||
@@ -0,0 +1,57 @@
|
||||
# Configuration
|
||||
|
||||
By default, WXT provides sensible configuration for bundling web extensions with Vite.
|
||||
|
||||
## Config File
|
||||
|
||||
To configure WXT, create a `wxt.config.ts` file in your project root. It should have the following contents:
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
// My WXT config
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
For more information on configuring WXT via the `wxt.config.ts` file, read the dedicated [`wxt.config.ts` guide](/guide/directory-structure/wxt-config).
|
||||
:::
|
||||
|
||||
## Manifest.json
|
||||
|
||||
WXT generates your extension's `manifest.json` based on the project structure. To add additional properties, like permissions, use the [`manifest` property](/api/reference/wxt/interfaces/InlineConfig#manifest).
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['storage'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
For more information on configuring the manifest, read the dedicated [Manifest guide](/guide/key-concepts/manifest).
|
||||
:::
|
||||
|
||||
## Environment
|
||||
|
||||
WXT can read `.env` files, and variables are accessible via `import.meta.env.*`.
|
||||
|
||||
:::code-group
|
||||
|
||||
```sh [.env]
|
||||
VITE_OAUTH_CLIENT_ID=abc123
|
||||
```
|
||||
|
||||
```ts [JS]
|
||||
import.meta.env.VITE_OAUTH_CLIENT_ID;
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::info
|
||||
For more information on using .env files, read the dedicated [`.env` guide](/guide/directory-structure/env).
|
||||
:::
|
||||
@@ -0,0 +1,60 @@
|
||||
# Entrypoints
|
||||
|
||||
An "entrypoint" is any HTML, JS, or CSS file that needs to be bundled and included with your extension, which will be loaded and executed by the browser.
|
||||
|
||||
## Defining Entrypoints
|
||||
|
||||
In WXT, you create an entrypoint by adding a file to the `entrypoints/` directory.
|
||||
|
||||
```
|
||||
<rootDir>
|
||||
└─ entrypoints/
|
||||
├─ background.ts
|
||||
├─ content.ts
|
||||
├─ injected.ts
|
||||
└─ popup.html
|
||||
```
|
||||
|
||||
Some entrypoint filesname patterns are reserved by WXT and effect how the manifest is generated.
|
||||
|
||||
- `popup` adds an `action` to the manifest
|
||||
- `background` adds a background script/service worker
|
||||
- `*.content.ts` adds a content script
|
||||
- ...
|
||||
|
||||
> For a full list of recognized filenames, see the the [Entrypoints Directory guide](/guide/directory-structure/entrypoints/background).
|
||||
|
||||
Any other files, whether JS, CSS, or HTML, is considered "unlisted". Unlisted files, like `injected.ts` from above, are just bundled to the output directory and not added to the manifest. You can still access or load them at runtime.
|
||||
|
||||
## Entrypoint Options
|
||||
|
||||
Most entrypoints allow customizing their options in the file you define them in. This differs from regular web extension development, where all options are placed in the `manifest.json`.
|
||||
|
||||
WXT looks for custom options in the entrypoint, and adds them to the manifest when generated.
|
||||
|
||||
In HTML files, options are listed as `meta` tags:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<!-- Defining the popup's "default_icon" field -->
|
||||
<meta name="manifest.default_icon" content="{ '16': '/icon/16.png' }" />
|
||||
</head>
|
||||
</html>
|
||||
```
|
||||
|
||||
In TS files, options are apart of the file's default export:
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
matches: ['*://*.google.com/*'],
|
||||
runAt: 'document_start',
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
All options for each entrypoint type is listed in the [entrypoints directory docs](/guide/directory-structure/entrypoints/background).
|
||||
:::
|
||||
@@ -1,19 +1,30 @@
|
||||
# Installation
|
||||
|
||||
Bootstrap a new project, start from scratch, or [migrate an existing project](/guide/migrate-to-wxt).
|
||||
Bootstrap a new project, start from scratch, or [migrate an existing project](/get-started/migrate-to-wxt).
|
||||
|
||||
## Bootstrap Project
|
||||
|
||||
:::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpx wxt@latest init <project-name>
|
||||
pnpm dlx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
# The "wxt init" command currently fails when ran with bunx.
|
||||
# Use NPX as a workaround, and select "bun" as your package
|
||||
# manager. To stay up to date with this issue, follow
|
||||
# https://github.com/wxt-dev/wxt/issues/707
|
||||
#
|
||||
# bunx wxt@latest init <project-name>
|
||||
|
||||
npx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
There are several starting templates available.
|
||||
@@ -32,47 +43,30 @@ All templates default to TypeScript. Rename the file extensions to `.js` to use
|
||||
|
||||
## From Scratch
|
||||
|
||||
Create a new NPM project:
|
||||
Initialize a project and install `wxt`:
|
||||
|
||||
:::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
mkdir project-name
|
||||
cd project-name
|
||||
pnpm init
|
||||
echo 'shamefully-hoist=true' >> .npmrc
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
mkdir project-name
|
||||
cd project-name
|
||||
npm init
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
mkdir project-name
|
||||
cd project-name
|
||||
yarn init
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Then install `wxt`:
|
||||
|
||||
:::code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add -D wxt
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm init
|
||||
npm i --save-dev wxt
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn init
|
||||
yarn add --dev wxt
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun init
|
||||
bun add --dev wxt
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Add your first entrypoint:
|
||||
@@ -84,7 +78,7 @@ export default defineBackground(() => {
|
||||
});
|
||||
```
|
||||
|
||||
Finally, add scripts to your `package.json`:
|
||||
And add scripts to your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -100,51 +94,23 @@ Finally, add scripts to your `package.json`:
|
||||
}
|
||||
```
|
||||
|
||||
## Migrate an Existing Project
|
||||
|
||||
Before starting the migration, it is recommended to run `pnpx wxt@latest init` to see what a basic project looks like. Once you have an understanding of how WXT projects are structured, you're ready to convert the project, using the initialized project as a reference.
|
||||
|
||||
Migrating a project to WXT comes down to a few steps:
|
||||
|
||||
1. Install WXT and remove any old build tools: `pnpm i -D wxt`
|
||||
1. Refactoring/move your entrypoints to the `entrypoints` directory
|
||||
1. Moving public assets to the `public` directory
|
||||
1. Update the dev and build scripts to use WXT
|
||||
1. Ensure project is compatible with Vite, which is used under the hood to bundle your extension
|
||||
|
||||
:::info
|
||||
Since projects vary greatly in setup, [start a discussion on GitHub](https://github.com/wxt-dev/wxt/discussions/new/choose) if you need help migrating your project to WXT.
|
||||
:::
|
||||
|
||||
## Development
|
||||
|
||||
Once you've installed WXT, you can start the development server using the `dev` script.
|
||||
Once the project is setup, you can start the development server using the `dev` script.
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
:::tip 🎉 Well done!
|
||||
|
||||
The dev command will build the extension for development, open the browser, and reload the different parts of the extension when you save changes.
|
||||
:::
|
||||
|
||||
:::details Development Manifest
|
||||
When running the dev command, WXT will make several changes to your `manifest.json` to improve your development experience:
|
||||
|
||||
- If missing, add a background script/service worker to enable fast reloads
|
||||
- Add several `permissions` and `host_permissions` to enable HMR and fast reloads
|
||||
- Modify the CSP to allow connections with the dev server
|
||||
- Remove `content_scripts` and register them at runtime so they can be easily reloaded when you save a file
|
||||
|
||||
If you're an experienced web extension developer and think the dev manifest looks wrong, this is why. Run a production build with `wxt build` to see the unmodified `manifest.json`.
|
||||
:::
|
||||
|
||||
## Next Steps
|
||||
|
||||
You're ready to build your web extension!
|
||||
|
||||
- Learn how to [add entrypoints](./entrypoints) like the popup, options page, or content scripts
|
||||
- Configure your entrypoints to [use ESM](./esm) at runtime
|
||||
- Read the rest of the "Get Started" pages for a high-overview of what WXT can do
|
||||
- Read the [Guide](/guide/key-concepts/manifest) to learn in-depth about each feature WXT supports
|
||||
- [Configure WXT](./configuration) by creating a `wxt.config.ts` file
|
||||
- Checkout [example projects](https://github.com/wxt-dev/wxt-examples) to see how to perform common tasks with WXT
|
||||
- Checkout [example projects](https://github.com/wxt-dev/examples) to see how to perform common tasks with WXT
|
||||
@@ -7,9 +7,11 @@ head:
|
||||
|
||||
# Introduction
|
||||
|
||||
## Overview
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
@@ -37,3 +39,9 @@ In addition, WXT fully supports Firefox's source code requirements when using a
|
||||
:::info
|
||||
See [Publishing](./publishing) for more info around production builds.
|
||||
:::
|
||||
|
||||
## New to Extension Development?
|
||||
|
||||
Most of these docs assume you have a basic understanding of how to write a chrome or web extension.
|
||||
|
||||
If you've never written a web extension before or need a refresher, follow Google's ["Hello, World!" tutorial](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world) to understand the basics.
|
||||
@@ -4,7 +4,7 @@ outline: deep
|
||||
|
||||
# Migrate to WXT
|
||||
|
||||
> If you have problems migrating to WXT, feel free to ask for help in GitHub by [starting a discussion](https://github.com/wxt-dev/wxt/discussions/new?category=q-a)!
|
||||
> If you have problems migrating to WXT, feel free to ask for help in GitHub by [starting a discussion](https://github.com/wxt-dev/wxt/discussions/new?category=q-a) or in [Discord](https://discord.gg/ZFsZqGery9)!
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -12,21 +12,21 @@ Always start by generating a new vanilla project and merging it into your projec
|
||||
|
||||
```sh
|
||||
cd path/to/your/project
|
||||
pnpx wxt@latest init example-wxt --template vanilla
|
||||
pnpm dlx wxt@latest init example-wxt --template vanilla
|
||||
```
|
||||
|
||||
In general, you'll need to:
|
||||
|
||||
<input type="checkbox" /> Install `wxt`<br />
|
||||
<input type="checkbox" /> Update/create `package.json` scripts to use `wxt` (don't forget about `postinstall`)<br />
|
||||
<input type="checkbox" /> Move entrypoints into `entrypoints/` directory<br />
|
||||
<input type="checkbox" /> Move assets into either the `assets/` or `public/` directories<br />
|
||||
<input type="checkbox" /> Move manifest.json content into `wxt.config.ts`<br />
|
||||
<input type="checkbox" /> Convert custom import syntax to be compatible with Vite<br />
|
||||
<input type="checkbox" /> Add a default export to JS entrypoints<br />
|
||||
<input type="checkbox" /> Use the `browser` global instead of `chrome`<br />
|
||||
<input type="checkbox" /> Compare final `manifest.json` files, making sure permissions and host permissions are unchanged<br />
|
||||
<input type="checkbox" /> Extension output by `wxt build` works the same way as before the migration<br />
|
||||
 <input type="checkbox" /> Install `wxt`<br />
|
||||
 <input type="checkbox" /> Update/create `package.json` scripts to use `wxt` (don't forget about `postinstall`)<br />
|
||||
 <input type="checkbox" /> Move entrypoints into `entrypoints/` directory<br />
|
||||
 <input type="checkbox" /> Move assets into either the `assets/` or `public/` directories<br />
|
||||
 <input type="checkbox" /> Move manifest.json content into `wxt.config.ts`<br />
|
||||
 <input type="checkbox" /> Convert custom import syntax to be compatible with Vite<br />
|
||||
 <input type="checkbox" /> Add a default export to JS entrypoints<br />
|
||||
 <input type="checkbox" /> Use the `browser` global instead of `chrome`<br />
|
||||
 <input type="checkbox" /> Compare final `manifest.json` files, making sure permissions and host permissions are unchanged. Use [Google's update testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool) if your extension is already live on the Chrome Web Store<br />
|
||||
 <input type="checkbox" /> Extension output by `wxt build` works the same way as before the migration<br />
|
||||
|
||||
Every project is different, so there's no one-solution-fits-all to migrating your project. Just make sure `wxt dev` runs, `wxt build` results in a working extension, and the list of permissions in the `manifest.json` hasn't changed. If all that looks good, you've finished migrating your extension!
|
||||
|
||||
@@ -55,3 +55,11 @@ Since you're already using Vite, it's a simple refactor.
|
||||
5. Convert Plasmo's custom import resolutions to Vite's
|
||||
6. If importing remote code via a URL, add a `url:` prefix so it works with WXT
|
||||
7. Compare your output `manifest.json` files from before the migration to after the migration. They should have the same content. If not, tweak your entrypoints and config to get as close as possible.
|
||||
|
||||
<!--
|
||||
## Migrate an Existing Project
|
||||
|
||||
:::info
|
||||
Since projects vary greatly in setup, [start a discussion on GitHub](https://github.com/wxt-dev/wxt/discussions/new/choose) if you need help migrating your project to WXT.
|
||||
:::
|
||||
-->
|
||||
@@ -20,8 +20,8 @@ For specific details about each store, see the stores sections below.
|
||||
|
||||
WXT provides two commands to help automate the release process:
|
||||
|
||||
- `wxt submit init`: Setup all the required secrets and options for the `wxt submit` command
|
||||
- `wxt submit`: Submit new versions of your extension for review (and publish them automatically once approved)
|
||||
- `wxt submit init`: Help setup all the required secrets and options for the `wxt submit` command
|
||||
|
||||
To get started, run `wxt submit init` and follow the prompts. Once finished, you should have a `.env.submit` file! WXT will use this file to submit your updates.
|
||||
|
||||
@@ -64,12 +64,57 @@ See the [Firefox Addon Store](#firefox-addon-store) section for more details abo
|
||||
|
||||
## GitHub Action
|
||||
|
||||
Here's an example of a GitHub Action to automate submitting new versions of your extension for review. Ensure that you've added all required secrets used in the workflow to the repo's settings.
|
||||
Here's an example of a GitHub Action that submits new versions of an extension for review. Ensure that you've added all required secrets used in the workflow to the repo's settings.
|
||||
|
||||
```yml
|
||||
# TODO
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
submit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Zip extensions
|
||||
run: |
|
||||
pnpm zip
|
||||
pnpm zip:firefox
|
||||
|
||||
- name: Submit to stores
|
||||
run: |
|
||||
pnpm wxt submit \
|
||||
--chrome-zip .output/*-chrome.zip \
|
||||
--firefox-zip .output/*-firefox.zip --firefox-sources-zip .output/*-sources.zip
|
||||
env:
|
||||
CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
||||
CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }}
|
||||
FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }}
|
||||
FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }}
|
||||
```
|
||||
|
||||
The action above lays the foundation for a basic workflow, including `zip` and `submit` steps. To further enhance your GitHub Action and delve into more complex scenarios, consider exploring the following examples from real projects. They introduce advanced features such as version management, changelog generation, and GitHub releases, tailored for different needs:
|
||||
|
||||
- [`aklinker1/github-better-line-counts`](https://github.com/aklinker1/github-better-line-counts/blob/main/.github/workflows/submit.yml) - Conventional commits, automated version bump and changelog generation, triggered manually, optional dry run for testing
|
||||
- [`GuiEpi/plex-skipper`](https://github.com/GuiEpi/plex-skipper/blob/main/.github/workflows/deploy.yml) - Triggered automatically when `package.json` version is changed, creates and uploads artifacts to GitHub release.
|
||||
|
||||
> These examples are designed to provide clear insights and are a good starting point for customizing your own workflows. Feel free to explore and adapt them to your project needs.
|
||||
|
||||
## Stores
|
||||
|
||||
### Chrome Web Store
|
||||
@@ -124,18 +169,23 @@ yarn
|
||||
yarn zip:firefox
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun i
|
||||
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
|
||||
If you use a `.env` files, they can effect the chunk hashes in the output directory. Either delete the .env file before running `wxt zip -b firefox`, or include it in your sources zip with the [`zip.includeSources`](/api/wxt/interfaces/InlineConfig#includesources) option. Be careful to not include any secrets in your `.env` files.
|
||||
If you use a `.env` files, they can effect the chunk hashes in the output directory. Either delete the .env file before running `wxt zip -b firefox`, or include it in your sources zip with the [`zip.includeSources`](/api/reference/wxt/interfaces/InlineConfig#includesources) option. Be careful to not include any secrets in your `.env` files.
|
||||
|
||||
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.
|
||||
@@ -1,85 +0,0 @@
|
||||
# Assets
|
||||
|
||||
WXT has two directories for storing assets like CSS, images, or fonts.
|
||||
|
||||
- `<srcDir>/public`: Store files that will be copied into the output directory as-is
|
||||
- `<srcDir>/assets`: Store files that will be processed by Vite during the build process
|
||||
|
||||
## `/public` Directory
|
||||
|
||||
Place static files like the extension icon or `_locales/` directory here. These files will be copied over to the output directory without being transformed by Vite.
|
||||
|
||||
```
|
||||
<srcDir>
|
||||
└─ public/
|
||||
├─ icon-16.png
|
||||
├─ icon-32.png
|
||||
├─ icon-48.png
|
||||
├─ icon-96.png
|
||||
└─ icon-128.png
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
You can reference these files by using absolute paths in HTML files or `browser.runtime.getURL` in content scripts.
|
||||
|
||||
:::code-group
|
||||
|
||||
```html [popup.html]
|
||||
<img src="/icon-128.png" />
|
||||
```
|
||||
|
||||
```ts [content.ts]
|
||||
defineContentScript({
|
||||
main() {
|
||||
const image = document.createElement('img');
|
||||
image.src = browser.runtime.getURL('/icon-128.png');
|
||||
document.body.append(image);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## `/assets` Directory
|
||||
|
||||
Files in the assets directory will be processed by Vite. They are imported in your source code, and will be transformed or renamed in the output directory.
|
||||
|
||||
```
|
||||
<srcDir>
|
||||
└─ assets/
|
||||
├─ style.css
|
||||
└─ illustration.svg
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
:::code-group
|
||||
|
||||
```html [popup.html]
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="~/assets/style.css" />
|
||||
<!-- ... -->
|
||||
</head>
|
||||
<body>
|
||||
<img src="~/illustration.svg" />
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```ts [content.ts]
|
||||
import '~/assets/style.css';
|
||||
import illustration from '~/assets/style.svg';
|
||||
|
||||
defineContentScript({
|
||||
main() {
|
||||
const image = document.createElement('img');
|
||||
image.src = illustration;
|
||||
document.body.append(image);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
@@ -1,80 +0,0 @@
|
||||
# Auto-imports
|
||||
|
||||
WXT uses the same tool as Nuxt for auto-imports, [`unimport`](https://github.com/unjs/unimport).
|
||||
|
||||
:::info Testing
|
||||
To setup your test environment for auto-imports, see [Testing](/guide/testing).
|
||||
:::
|
||||
|
||||
## WXT Auto-imports
|
||||
|
||||
Some WXT APIs can be used without importing them:
|
||||
|
||||
- [`browser`](/api/wxt/browser/variables/browser) from `wxt/browser`, a small wrapper around `webextension-polyfill`
|
||||
- [`defineContentScript`](/api/wxt/sandbox/functions/defineContentScript) from `wxt/sandbox`
|
||||
- [`defineBackground`](/api/wxt/sandbox/functions/defineBackground) from `wxt/sandbox`
|
||||
- [`defineUnlistedScript`](/api/wxt/sandbox/functions/defineUnlistedScript) from `wxt/sandbox`
|
||||
- [`createIntegratedUi`](/api/wxt/client/functions/createIntegratedUi) from `wxt/client`
|
||||
- [`createShadowRootUi`](/api/wxt/client/functions/createShadowRootUi) from `wxt/client`
|
||||
- [`createIframeUi`](/api/wxt/client/functions/createIframeUi) from `wxt/client`
|
||||
- [`fakeBrowser`](/api/wxt/testing/variables/fakeBrowser) from `wxt/testing`
|
||||
|
||||
And more!
|
||||
|
||||
## Project Auto-imports
|
||||
|
||||
In addition WXT APIs, default and named exports from inside the following directories can be used without listing them in imports.
|
||||
|
||||
- `<srcDir>/components/*`
|
||||
- `<srcDir>/composables/*`
|
||||
- `<srcDir>/hooks/*`
|
||||
- `<srcDir>/utils/*`
|
||||
|
||||
To add auto-imports from subdirectories, like `utils/api/some-file.ts`, re-export them from the base directory:
|
||||
|
||||
```ts
|
||||
// utils/index.ts
|
||||
export * from './api/some-file.ts';
|
||||
```
|
||||
|
||||
Alternatively, you could add the directory to the list of auto-import directories in your config file.
|
||||
|
||||
## TypeScript
|
||||
|
||||
For TypeScript to work, you need to run the `wxt prepare` command. This will ensure types are generated for auto-imports.
|
||||
|
||||
This should be added to your `postinstall` script:
|
||||
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"scripts": {
|
||||
"postinstall": "wxt prepare" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
You can override the default auto-import behavior in your `wxt.config.ts` file.
|
||||
|
||||
See [`unimport`'s documentation](https://github.com/unjs/unimport#configurations) for a complete list of options.
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
imports: {
|
||||
// Add auto-imports for vue functions like createApp, ref, computed, watch, toRaw, etc...
|
||||
presets: ['vue'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
To disable auto-imports, set `imports: false`
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
imports: false,
|
||||
});
|
||||
```
|
||||
@@ -1,50 +0,0 @@
|
||||
# Compare
|
||||
|
||||
Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework) (another web extension framework) and [CRXJS](https://crxjs.dev/vite-plugin) (the most popular bundler plugin).
|
||||
|
||||
## Overview
|
||||
|
||||
| Features | WXT | Plasmo | CRXJS |
|
||||
| ---------------------------------------------------- | :-------------------------: | :--------------------------------------: | :---------------------------------------------------------------------: |
|
||||
| Supports all browsers | ✅ | ✅ | ❌ See [#56](https://github.com/crxjs/chrome-extension-tools/issues/56) |
|
||||
| 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 | ✅ | ✅ | ❌ |
|
||||
|
||||
## Why use WXT?
|
||||
|
||||
WXT's main goal is improving the development experience (DX) of creating web extensions, while not sacrificing support. There are two things WXT does differently:
|
||||
|
||||
1. Automatically opens a browser with the extension installed when starting development
|
||||
2. Reload each part of the extension individually rather than reloading the entire extension
|
||||
|
||||
Opening a browser automatically makes it super easy to start and stop development without having to manually load the extension in your browser.
|
||||
|
||||
Reloading each part of the extension individually improves your iteration speed while developing UIs. This is because reloading the entire extension on every change will close the popup and any tabs open to an extension page, like options. If you save a file associated with a UI and a content script while working on the UI, it will randomly close because it needed to reload the extension when the content script changed. This interrupts your development flow and is really annoying.
|
||||
|
||||
WXT solves this problem by reloading HTML pages and content scripts individually (when possible) to keep your UIs open while you develop them. This is a MV3 feature, so if you're developing a MV2 extension, you'll get the same dev experience as Plasmo.
|
||||
|
||||
:::info
|
||||
Unfortunately, there isn't an API for reloading the background page/service worker individually, so if you change a file used by the background, the entire extension will reload. See [Issue #53](https://github.com/wxt-dev/wxt/issues/53) for more details.
|
||||
:::
|
||||
@@ -1,99 +0,0 @@
|
||||
# Configuration
|
||||
|
||||
WXT's behavior can be configured via the `wxt.config.ts` file. In this file, you can add Vite plugins, change the directory structure of your project, and set fields on your `manifest.json`.
|
||||
|
||||
## Config File
|
||||
|
||||
To configure WXT, create a `wxt.config.ts` file in your project root. It should have the following contents:
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
// My WXT config
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
See the [Config reference](/api/wxt/interfaces/InlineConfig) for a full list of options.
|
||||
:::
|
||||
|
||||
## Directory Config
|
||||
|
||||
WXT allows you to edit several directories to your liking:
|
||||
|
||||
- `root` (default: `process.cwd()`) - Root of the WXT project
|
||||
- `srcDir` (default: `<rootDir>`) - Location of all your source code
|
||||
- `entrypointsDir` (default: `<srcDir>/entrypoints`) - Folder containing all the entrypoints.
|
||||
- `publicDir` (default: `<srcDir>/public`) - Folder containing [public assets](/guide/assets)
|
||||
|
||||
### Example
|
||||
|
||||
You want a `src/` directory to contain all your source code, and you want to rename `entrypoints/` → `entries/`:
|
||||
|
||||
```
|
||||
<rootDir>
|
||||
├─ src/
|
||||
│ └─ entries/
|
||||
│ ├─ background.ts
|
||||
│ └─ ...
|
||||
└─ wxt.config.ts
|
||||
```
|
||||
|
||||
Your config would look like this:
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
srcDir: 'src',
|
||||
entrypointsDir: 'entries',
|
||||
});
|
||||
```
|
||||
|
||||
## Frontend Frameworks
|
||||
|
||||
Adding a framework like Vue, React, or Svelte is easy!
|
||||
|
||||
In the `wxt.config.ts` file, install and add the framework's Vite plugin to the config.
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [Vue]
|
||||
import { defineConfig } from 'wxt';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
|
||||
export default defineConfig({
|
||||
vite: () => ({
|
||||
plugins: [vue()],
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
```ts [React]
|
||||
import { defineConfig } from 'wxt';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
vite: () => ({
|
||||
plugins: [react()],
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
```ts [Svelte]
|
||||
import { defineConfig } from 'wxt';
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
export default defineConfig({
|
||||
vite: () => ({
|
||||
plugins: [svelte()],
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::info
|
||||
Internally, WXT runs `vite build` multiple times during a single build. It can't share the same instance of some plugins between each build, so a function is used to return completely new instances of each plugin for each build.
|
||||
:::
|
||||
@@ -0,0 +1,61 @@
|
||||
# `<srcDir>/app.config.ts`
|
||||
|
||||
:::warning Nuxt Users
|
||||
If you're familiar with Nuxt, this file is meant to be a direct equivalent to Nuxt's `app.config.ts` file.
|
||||
|
||||
However, some of Nuxt's features, like overriding the app config based on a `.env` file or automatically generating the config's types, are not implemented. They are planned, just not implemented yet. Feel free to open a PR!
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
Define runtime configuration in a single place.
|
||||
|
||||
```ts
|
||||
// <srcDir>/app.config.ts
|
||||
import { defineAppConfig } from 'wxt/sandbox';
|
||||
|
||||
// Define types for your config
|
||||
declare module 'wxt/sandbox' {
|
||||
export interface WxtAppConfig {
|
||||
theme?: 'light' | 'dark';
|
||||
}
|
||||
}
|
||||
|
||||
export default defineAppConfig({
|
||||
theme: 'dark',
|
||||
});
|
||||
```
|
||||
|
||||
Then access the config in your extension by calling `useAppConfig`:
|
||||
|
||||
```ts
|
||||
console.log(useAppConfig()); // { theme: "dark" }
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
If you have a `.env` file, you can access any variables defined in it here. You can convert them to better types (like booleans), add types for them, or leave them as is.
|
||||
|
||||
```txt
|
||||
# .env
|
||||
VITE_BUG_REPORTING_DISABLED=true
|
||||
VITE_API_KEY=...
|
||||
```
|
||||
|
||||
```ts
|
||||
// <srcDir>/app.config.ts
|
||||
|
||||
declare module 'wxt/sandbox' {
|
||||
export interface WxtAppConfig {
|
||||
bugReportingDisabled: boolean;
|
||||
apiKey?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export default defineAppConfig({
|
||||
bugReportingDisabled: process.env.VITE_BUG_REPORTING_DISABLED === 'true',
|
||||
apiKey: process.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.
|
||||
@@ -0,0 +1,42 @@
|
||||
# `<srcDir>/assets`
|
||||
|
||||
Files in the assets directory will be processed by Vite. They are imported in your source code, and will be transformed or renamed in the output directory.
|
||||
|
||||
```
|
||||
<srcDir>
|
||||
└─ assets/
|
||||
├─ style.css
|
||||
└─ illustration.svg
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
:::code-group
|
||||
|
||||
```html [popup.html]
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="~/assets/style.css" />
|
||||
<!-- ... -->
|
||||
</head>
|
||||
<body>
|
||||
<img src="~/assets/illustration.svg" />
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```ts [content.ts]
|
||||
import '~/assets/style.css';
|
||||
import illustration from '~/assets/style.svg';
|
||||
|
||||
defineContentScript({
|
||||
main() {
|
||||
const image = document.createElement('img');
|
||||
image.src = illustration;
|
||||
document.body.append(image);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
@@ -0,0 +1,5 @@
|
||||
# `<srcDir>/components`
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -0,0 +1,5 @@
|
||||
# `<srcDir>/composables`
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
+2
-48
@@ -40,7 +40,7 @@ export default defineContentScript({
|
||||
// Configure how/when content script will be registered
|
||||
registration: undefined | "manifest" | "runtime",
|
||||
|
||||
main(ctx) {
|
||||
main(ctx: ContentScriptContext) {
|
||||
// Executed when content script is loaded
|
||||
},
|
||||
});
|
||||
@@ -50,52 +50,6 @@ export default defineContentScript({
|
||||
|
||||
When defining multiple content scripts, content script entrypoints that have the same set of options will be merged into a single `content_script` item in the manifest.
|
||||
|
||||
## Context
|
||||
|
||||
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).
|
||||
|
||||
WXT provides a utility for handling this process: `ContentScriptContext`. An instance of this class is provided to you automatically inside the `main` function of your content script.
|
||||
|
||||
When your extension updates or is uninstalled, the context will become invalidated, and will trigger any `ctx.onInvalidated` listeners you add:
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
// ...
|
||||
main(ctx: ContentScriptContext) {
|
||||
// Add custom listeners for stopping work
|
||||
ctx.onInvalidated(() => {
|
||||
// ...
|
||||
});
|
||||
|
||||
// Timeout utilities that are automatically cleared when invalidated
|
||||
ctx.setTimeout(() => {
|
||||
// ...
|
||||
}, 5e3);
|
||||
ctx.setInterval(() => {
|
||||
// ...
|
||||
}, 60e3);
|
||||
|
||||
// Or add event listeners that get removed when invalidated
|
||||
ctx.addEventListener(document, 'visibilitychange', (event) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
// You can also stop fetch requests
|
||||
fetch('...url', { signal: ctx.signal });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The class extends [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) and provides other utilities for stopping a content script's logic once it becomes invalidated.
|
||||
|
||||
:::warning
|
||||
When working with content scripts, **you should always use the `ctx` object to stop any async or future work.**
|
||||
|
||||
This prevents old content scripts from interfering with new content scripts, and prevents error messages from the console in production.
|
||||
|
||||
If you're using a framework like React, Vue, Svelte, etc., make sure you're unmounting your UI properly in the `onRemove` option of [`createShadowRootUi`](https://wxt.dev/guide/content-script-ui.html#shadow-root).
|
||||
:::
|
||||
|
||||
## CSS
|
||||
|
||||
To include CSS with your content script, import the CSS file at the top of your entrypoint.
|
||||
@@ -150,4 +104,4 @@ export default defineContentScript({
|
||||
});
|
||||
```
|
||||
|
||||
See [Content Script UI](/guide/content-script-ui) for more info on creating UIs and including CSS in content scripts.
|
||||
See [Content Script UI](/guide/key-concepts/content-script-ui) for more info on creating UIs and including CSS in content scripts.
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
WXT can build CSS entrypoints individually. CSS entrypoints are always unlisted.
|
||||
|
||||
See [Content Script CSS](/entrypoints/content-scripts#css) documentation for the recommended approach to include CSS with a content script.
|
||||
See [Content Script CSS](/guide/directory-structure/entrypoints/content-scripts#css) documentation for the recommended approach to include CSS with a content script.
|
||||
|
||||
:::info
|
||||
If the recommended approach doesn't work for your use case, you can use any of the filename patterns below to build the styles separate from the JS and use the [`transformManifest` hook](/api/wxt/interfaces/InlineConfig#transformmanifest) to manually add your CSS file to the manifest.
|
||||
If the recommended approach doesn't work for your use case, you can use any of the filename patterns below to build the styles separate from the JS and use the [`transformManifest` hook](/api/reference/wxt/interfaces/InlineConfig#transformmanifest) to manually add your CSS file to the manifest.
|
||||
:::
|
||||
|
||||
## Filenames
|
||||
+1
-1
@@ -37,4 +37,4 @@ Chrome extensions allow you to add panels and side panes to the devtools window.
|
||||
|
||||
See the WXT's examples for a full walkthrough of extending the devtools window:
|
||||
|
||||
<ExampleList tag="devtools" />
|
||||
- [Devtools Setup](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-devtools#readme)
|
||||
@@ -12,8 +12,8 @@ Firefox does not support sandboxed pages.
|
||||
:patterns="[
|
||||
['sandbox.html', 'sandbox.html'],
|
||||
['sandbox/index.html', 'sandbox.html'],
|
||||
['<name>.sandbox.html', '<name>.html` '],
|
||||
['<name>.sandbox/index.html', '<name>.html` '],
|
||||
['<name>.sandbox.html', '<name>.html'],
|
||||
['<name>.sandbox/index.html', '<name>.html'],
|
||||
]"
|
||||
/>
|
||||
|
||||
+3
-1
@@ -2,6 +2,8 @@
|
||||
|
||||
HTML pages that are bundled and shipped with the extension, but are not included in the manifest.
|
||||
|
||||
If you plan on using the page in an iframe, don't forget to add the page to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources).
|
||||
|
||||
### Examples
|
||||
|
||||
- Onboarding
|
||||
@@ -22,7 +24,7 @@ HTML pages that are bundled and shipped with the extension, but are not included
|
||||
Pages are accessible at `'/<name>.html'`:
|
||||
|
||||
```ts
|
||||
const url = browser.runtime.getURL('/<name>.html');
|
||||
const url = browser.runtime.getURL('/<name>.html', '<html></html>');
|
||||
|
||||
console.log(url); // "chrome-extension://<id>/<name>.html"
|
||||
```
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
TypeScript files that are bundled and shipped with the extension, but are not included in the manifest.
|
||||
|
||||
You are responsible for loading/running these scripts where needed.
|
||||
You are responsible for loading/running these scripts where needed. If necessary, don't forget to add the script and/or any related stylesheets to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources).
|
||||
|
||||
## Filenames
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# `<root>/.env`
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -0,0 +1,5 @@
|
||||
# `<srcDir>/hooks`
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -0,0 +1,5 @@
|
||||
# `<root>/.output`
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -0,0 +1,5 @@
|
||||
# `<root>/package.json`
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -0,0 +1,35 @@
|
||||
# `public/`
|
||||
|
||||
Place static files like the extension icon or `_locales/` directory here. These files will be copied over to the output directory without being transformed by Vite.
|
||||
|
||||
```
|
||||
<srcDir>
|
||||
└─ public/
|
||||
├─ icon-16.png
|
||||
├─ icon-32.png
|
||||
├─ icon-48.png
|
||||
├─ icon-96.png
|
||||
└─ icon-128.png
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
You can reference these files by using absolute paths in HTML files or `browser.runtime.getURL` in content scripts.
|
||||
|
||||
:::code-group
|
||||
|
||||
```html [popup.html]
|
||||
<img src="/icon-128.png" />
|
||||
```
|
||||
|
||||
```ts [content.ts]
|
||||
defineContentScript({
|
||||
main() {
|
||||
const image = document.createElement('img');
|
||||
image.src = browser.runtime.getURL('/icon-128.png');
|
||||
document.body.append(image);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
@@ -0,0 +1,5 @@
|
||||
# `public/_locales/`
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -0,0 +1,5 @@
|
||||
# `<root>/tsconfig.json`
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -0,0 +1,5 @@
|
||||
# `<srcDir>/utils`
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -0,0 +1,65 @@
|
||||
# `web-ext.config.ts`
|
||||
|
||||
This file lets you configure the browser startup when running `wxt dev`.
|
||||
|
||||
```ts
|
||||
import { defineRunnerConfig } from 'wxt';
|
||||
|
||||
export default defineRunnerConfig({
|
||||
startUrls: ['https://google.com', 'https://youtube.com'],
|
||||
});
|
||||
```
|
||||
|
||||
There are three places you can customize the runner:
|
||||
|
||||
- `<root>/wxt.config.ts` - Use the `runner` option. Changes here will be committed and shared with everyone developing the project.
|
||||
- `<root>/web-ext.config.ts` - A gitignored file for you to customize the startup behavior to your liking without effecting others
|
||||
- `$HOME/web-ext.config.ts` - Stores system-wide config effecting all projects running on your machine.
|
||||
|
||||
See below examples on how to accomplish common configuration:
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Configuring Binaries
|
||||
|
||||
`web-ext`'s browser discovery is very limited. By default, it only guesses at where Chrome and Firefox are installed. If you've customized your install locations, you may need to tell `web-ext` where the binaries/executables are located using the [`binaries` option](/api/reference/wxt/interfaces/ExtensionRunnerConfig#binaries). For other Chromium based browsers, like Edge or Opera, you'll need to explicitly list them in the `binaries` option as well, otherwise they will open in Chrome by default.
|
||||
|
||||
```ts
|
||||
import { defineRunnerConfig } from 'wxt';
|
||||
|
||||
export default defineRunnerConfig({
|
||||
binaries: {
|
||||
chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome
|
||||
firefox: 'firefoxdeveloperedition', // Use Firefox Developer Edition instead of regular Firefox
|
||||
edge: '/path/to/edge', // Open MS Edge when running "wxt -b edge"
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Disable Opening Browser
|
||||
|
||||
Disabling the browser can be useful if it's difficult to develop your extension with fresh profiles. Maybe you need to sign into a website to see a content script run, and a fresh profile isn't helpful because it doesn't save your login info.
|
||||
|
||||
To disable opening the extension automatically in a new window, just disable the runner:
|
||||
|
||||
```ts
|
||||
export default defineRunnerConfig({
|
||||
disabled: true,
|
||||
});
|
||||
```
|
||||
|
||||
## Profile Customization
|
||||
|
||||
Another option, instead of disabling the runner, to stay logged into websites is to use a custom profile.
|
||||
|
||||
`web-ext` comes with some built-in ways of using an existing profile, but it's not really using the same profile. It copies the profile to a temp directory, and uses that.
|
||||
|
||||
Instead, I've found it's better to pass Chrome's `--user-data-dir` argument. This let's you use a fresh profile initially, and customize it to your liking. You can install devtool extensions, set custom flags, and log into websites. Next time you run the extension in dev mode, all that will be remembered!
|
||||
|
||||
```ts
|
||||
export default defineRunnerConfig({
|
||||
chromiumArgs: ['--user-data-dir=./chrome-data'],
|
||||
});
|
||||
```
|
||||
|
||||
> This only works for Chrome. You'll have to use `firefoxProfile` option instead, which has the same limitations mentioned above, where you won't be signed into websites automatically.
|
||||
@@ -0,0 +1,5 @@
|
||||
# `<root>/wxt.config.ts`
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -0,0 +1,5 @@
|
||||
# `.wxt/`
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -1,141 +0,0 @@
|
||||
# Defining Entrypoints
|
||||
|
||||
An "entrypoint" is any HTML, JS, or CSS file that needs to be bundled and included with the extension.
|
||||
|
||||
Entrypoints may or may not be listed in the extension's `manifest.json`.
|
||||
|
||||
## `/entrypoints` Directory
|
||||
|
||||
In WXT, entrypoints are defined by adding a file to the `entrypoints/` directory.
|
||||
|
||||
For example, a project that looks like this:
|
||||
|
||||
```
|
||||
<rootDir>
|
||||
└─ entrypoints/
|
||||
├─ background.ts
|
||||
├─ content.ts
|
||||
├─ injected.ts
|
||||
└─ popup.html
|
||||
```
|
||||
|
||||
would result in the following `manifest.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
// ...
|
||||
"manifest_version": 3,
|
||||
"action": {
|
||||
// ...
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"background": {
|
||||
// ...
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
// ...
|
||||
"js": ["content-scripts/content.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If a file uses a special name recognized by WXT, it will be added to the manifest. In this case:
|
||||
|
||||
- `popup.html` → `action.default_popup`
|
||||
- `content.ts` → `content_scripts.0.js.0`
|
||||
- `background.ts` → `background.service_worker`
|
||||
|
||||
But not all entrypoints are added to the `manifest.json`. If the filename is not recognized by WXT, they are still built and included in the extension, but they are considered "unlisted" and are not apart of the manifest.
|
||||
|
||||
In this case, `injected.ts` gets output to `<outdir>/injected.js` and is accessible via `browser.runtime.getURL("/injected.js")`.
|
||||
|
||||
:::info
|
||||
See [`/entrypoints` folder](/entrypoints/background) documentation for a full list of recognized entrypoint filenames.
|
||||
:::
|
||||
|
||||
## Entrypoint Options
|
||||
|
||||
Some entrypoints, like content scripts, actions, or the background, can receive additional options.
|
||||
|
||||
In HTML files, options are listed as `meta` tags:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<!-- Defining the popup's "default_icon" field -->
|
||||
<meta name="manifest.default_icon" content="{ '16': '/icon/16.png' }" />
|
||||
</head>
|
||||
</html>
|
||||
```
|
||||
|
||||
In TS files, options are apart of the file's default export:
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
matches: ['*://*.google.com/*'],
|
||||
runAt: 'document_start',
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::info
|
||||
For a full list of entrypoints and each of their options, see the [`/entrypoints` folder](/entrypoints/background) documentation.
|
||||
:::
|
||||
|
||||
### Side Effects
|
||||
|
||||
You cannot use imported variables outside the `main` function is 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,94 +0,0 @@
|
||||
# Extension APIs
|
||||
|
||||
WXT is built on top of [`webextension-polyfill`](https://www.npmjs.com/package/webextension-polyfill), which uses the standard `browser` global.
|
||||
|
||||
If you're used to Chrome's `chrome` global, its a simple switch:
|
||||
|
||||
1. Replace `chrome` with `browser`
|
||||
2. [Replace callbacks with async/await](https://developer.chrome.com/docs/extensions/mv3/promises/)
|
||||
|
||||
And that's it! Your extension now supports Chrome, Firefox, Safari, Edge, and other Chromium browsers.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
The `browser` variable is available globally via [auto-imports](/guide/auto-imports), or it can be imported manually.
|
||||
|
||||
```ts
|
||||
import { browser } from 'wxt/browser';
|
||||
```
|
||||
|
||||
The `wxt/browser` module exports a customized version of `webextension-polyfill`'s browser with improved typing.
|
||||
|
||||
### Example
|
||||
|
||||
Let's save the date the extension was installed. Just like `chrome`, some APIs require the permission is added to your manifest before the API is defined. Here, we need to add the `storage` permission to your manifest.
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['storage'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Then we can use `browser.storage` to save the install date to local storage.
|
||||
|
||||
```ts
|
||||
// background.ts
|
||||
export default defineBackground(() => {
|
||||
browser.runtime.onInstall.addEventListener(({ reason }) => {
|
||||
if (reason === 'install') {
|
||||
browser.storage.local.setItem({ installDate: Date.now() });
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Messaging
|
||||
|
||||
Follow [Chrome's message passing guide](https://developer.chrome.com/docs/extensions/mv3/messaging/) to understand how message passing works in web extensions. In Google's examples, just replace `chrome` with `browser`, and it will work in WXT.
|
||||
|
||||
Here's a basic request/response example:
|
||||
|
||||
```ts
|
||||
// popup/main.ts
|
||||
const res = await browser.runtime.sendMessage('ping');
|
||||
|
||||
console.log(res); // "pong"
|
||||
```
|
||||
|
||||
```ts
|
||||
// background.ts
|
||||
export default defineBackground(() => {
|
||||
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
console.log(message); // "ping"
|
||||
|
||||
// Wait 1 second and respond with "pong"
|
||||
setTimeout(() => sendResponse('pong'), 1000);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
There are a number of message passing libraries you can use to improve the message passing experience.
|
||||
|
||||
Here are some that are compatible with WXT (because they are based off `webextension-polyfill` as well):
|
||||
|
||||
- [`@webext-core/messaging`](https://webext-core.aklinker1.io/guide/messaging/) - "A light-weight, type-safe wrapper around the `browser.runtime` messaging APIs"
|
||||
- [`@webext-core/proxy-service`](https://webext-core.aklinker1.io/guide/proxy-service/) - "Create TRPC-like services that can be called from anywhere but run in the background"
|
||||
- [`webext-bridge`](https://github.com/zikaari/webext-bridge) - "Messaging in Web Extensions made super easy. Out of the box."
|
||||
|
||||
## Browser Differences
|
||||
|
||||
Some APIs are only available on certain browsers or manifest versions. You will have to check if an API exists at runtime if it is not in the `browser` standard.
|
||||
|
||||
```ts
|
||||
if ('session' in browser.storage) {
|
||||
// Do something with the non-standard session storage API
|
||||
}
|
||||
```
|
||||
|
||||
> If you're using TypeScript, knowing what is non-standard is easy! APIs that are not typed are non-standard.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Messaging
|
||||
|
||||
## Overview
|
||||
|
||||
Follow [Chrome's message passing guide](https://developer.chrome.com/docs/extensions/mv3/messaging/) to understand how message passing works in web extensions. In Google's examples, just replace `chrome` with `browser`, and it will work in WXT.
|
||||
|
||||
Here's a basic request/response example:
|
||||
|
||||
```ts
|
||||
// popup/main.ts
|
||||
const res = await browser.runtime.sendMessage('ping');
|
||||
|
||||
console.log(res); // "pong"
|
||||
```
|
||||
|
||||
```ts
|
||||
// background.ts
|
||||
export default defineBackground(() => {
|
||||
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
console.log(message); // "ping"
|
||||
|
||||
// Wait 1 second and respond with "pong"
|
||||
setTimeout(() => sendResponse('pong'), 1000);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Third Party Libraries
|
||||
|
||||
There are a number of message passing libraries you can use to improve the message passing experience.
|
||||
|
||||
- [`@webext-core/messaging`](https://webext-core.aklinker1.io/guide/messaging/) - "A light-weight, type-safe wrapper around the `browser.runtime` messaging APIs"
|
||||
- [`@webext-core/proxy-service`](https://webext-core.aklinker1.io/guide/proxy-service/) - "Create RPC-like services that can be called from anywhere but run in the background"
|
||||
- [`webext-bridge`](https://github.com/zikaari/webext-bridge) - "Messaging in Web Extensions made super easy. Out of the box."
|
||||
- [`trpc-chrome`](https://www.npmjs.com/package/trpc-chrome) - "tRPC adapter for Web Extensions 🧩"
|
||||
@@ -0,0 +1,5 @@
|
||||
# All Other APIs
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -0,0 +1,26 @@
|
||||
# `browser.scripting`
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/api/scripting) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting)
|
||||
|
||||
Refer to the browser docs above for basics on how the API works.
|
||||
|
||||
## Execute Script Return Values
|
||||
|
||||
When using `browser.scripting.executeScript`, you can execute content scripts or unlisted scripts. To return a value, just return a value from the script's main function.
|
||||
|
||||
```ts
|
||||
// entrypoints/background.ts
|
||||
const res = await browser.scripting.executeScript({
|
||||
target: { tabId },
|
||||
files: ['injected.js'],
|
||||
});
|
||||
console.log(res); // "Hello John!"
|
||||
```
|
||||
|
||||
```ts
|
||||
// entrypoints/injected.js
|
||||
export default defineContentScript(() => {
|
||||
console.log('Script was injected!');
|
||||
return 'Hello John!';
|
||||
});
|
||||
```
|
||||
@@ -10,6 +10,21 @@ WXT provides a simplified API to replace the `browser.storage.*` APIs. Use the `
|
||||
import { storage } from 'wxt/storage';
|
||||
```
|
||||
|
||||
:::warning
|
||||
To use the `wxt/storage` API, the `"storage"` permission must be added to the manifest:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['storage'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
More info on permissions [here](/guide/key-concepts/manifest#permissions).
|
||||
:::
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Basic Usage
|
||||
@@ -39,7 +54,7 @@ await storage.watch<number>(
|
||||
await storage.getMeta<{ v: number }>('local:installDate');
|
||||
```
|
||||
|
||||
For a full list of methods available, see the [API reference](/api/wxt/storage/interfaces/WxtStorage).
|
||||
For a full list of methods available, see the [API reference](/api/reference/wxt/storage/interfaces/WxtStorage).
|
||||
|
||||
## Watchers
|
||||
|
||||
@@ -106,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,
|
||||
},
|
||||
);
|
||||
```
|
||||
@@ -117,12 +132,12 @@ Now, instead of using the `storage` variable, you can use the helper functions o
|
||||
await showChangelogOnUpdate.getValue();
|
||||
await showChangelogOnUpdate.setValue(false);
|
||||
await showChangelogOnUpdate.removeValue();
|
||||
const unwatch = showChangelogOnUpdate.watch(() => {
|
||||
const unwatch = showChangelogOnUpdate.watch((newValue) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
For a full list of properties and methods available, see the [API reference](/api/wxt/storage/interfaces/WxtStorageItem).
|
||||
For a full list of properties and methods available, see the [API reference](/api/reference/wxt/storage/interfaces/WxtStorageItem).
|
||||
|
||||
### Versioning
|
||||
|
||||
@@ -138,7 +153,7 @@ type IgnoredWebsiteV1 = string;
|
||||
export const ignoredWebsites = storage.defineItem<IgnoredWebsiteV1[]>(
|
||||
'local:ignoredWebsites',
|
||||
{
|
||||
defaultValue: [],
|
||||
fallback: [],
|
||||
version: 1,
|
||||
},
|
||||
);
|
||||
@@ -158,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 ++]
|
||||
@@ -190,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: {
|
||||
@@ -227,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: [],
|
||||
},
|
||||
);
|
||||
```
|
||||
@@ -247,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 ++]
|
||||
@@ -264,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.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Custom Events
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -0,0 +1,5 @@
|
||||
# Debugging
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -0,0 +1,77 @@
|
||||
# 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.
|
||||
|
||||
There isn't really anything to add here... By default, it should "just work".
|
||||
|
||||
## 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() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,76 @@
|
||||
# Handling Extension Updates
|
||||
|
||||
When releasing an update to your extension, there's a couple of things you need to keep in mind:
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Content Script Cleanup
|
||||
|
||||
Old content scripts are not automatically stopped when an extension updates and reloads. Often, this leads to "Invalidated context" errors in production when a content script from an old version of your extension tries to use an extension API.
|
||||
|
||||
WXT provides a utility for handling this process: `ContentScriptContext`. An instance of this class is provided to you automatically inside the `main` function of each content script.
|
||||
|
||||
When your extension updates or reloads, the context will become invalidated, and will trigger any `ctx.onInvalidated` listeners you add:
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
main(ctx) {
|
||||
ctx.onInvalidated(() => {
|
||||
// Do something
|
||||
});
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
The `ctx` also provides other convenient APIs for stopping your content script without manually calling `onInvalidated` to add a listener:
|
||||
|
||||
1. Setting timers:
|
||||
```ts
|
||||
ctx.setTimeout(() => { ... }, ...);
|
||||
ctx.setInterval(() => { ... }, ...);
|
||||
ctx.requestAnimationFrame(() => { ... });
|
||||
```
|
||||
1. Adding DOM events:
|
||||
```ts
|
||||
ctx.addEventListener(window, "mousemove", (event) => { ... });
|
||||
```
|
||||
1. Implements [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) for canceling standard APIs:
|
||||
```ts
|
||||
fetch('...', {
|
||||
signal: ctx.signal,
|
||||
});
|
||||
```
|
||||
|
||||
Other WXT APIs require a `ctx` object so they can clean themselves up. For example, [`createIntegratedUi`](/guide/key-concepts/content-script-ui#integrated), [`createShadowRootUi`](/guide/key-concepts/content-script-ui#shadow-root), and [`createIframeUi`](/guide/key-concepts/content-script-ui#iframe) automatically unmount and stop a UI when the script is invalidated.
|
||||
|
||||
:::warning
|
||||
When working with content scripts, **you should always use the `ctx` object to stop any async or future work.**
|
||||
|
||||
This prevents old content scripts from interfering with new content scripts, and prevents error messages from being logged to the console in production.
|
||||
:::
|
||||
|
||||
## Testing Permission Changes
|
||||
|
||||
When `permissions`/`host_permissions` change during an update, depending on what exactly changed, the browser will disable your extension until the user accepts the new permissions.
|
||||
|
||||
You can test if your permission changes will result in a disabled extension:
|
||||
|
||||
- Chromium: Use [Google's Extension Update Testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool)
|
||||
- Firefox/Safari: Everyone breaks something in production eventually... 🫡 Good luck soldier
|
||||
|
||||
## Update Event
|
||||
|
||||
You can setup a callback that runs after your extension updates like so:
|
||||
|
||||
```ts
|
||||
browser.runtime.onInstalled.addListener(({ reason }) => {
|
||||
if (reason === 'update') {
|
||||
// Do something
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
If the logic is simple, write a unit test to cover this logic. If you feel the need to manually test this callback, you can either:
|
||||
|
||||
1. In dev mode, remove the `if` statement and reload the extension from `chrome://extensions`
|
||||
2. Use [Google's Extension Update Testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool)
|
||||
@@ -0,0 +1,5 @@
|
||||
# How WXT Works
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -1,10 +1,10 @@
|
||||
# Remote Code
|
||||
|
||||
WXT will automatically download and bundle imports with the `url:` prefix so the extension does not depend of remote code, [a requirement from Google for MV3](https://developer.chrome.com/docs/extensions/migrating/improve-security/#remove-remote-code).
|
||||
WXT will automatically download and bundle imports with the `url:` prefix so the extension does not depend on remote code, [a requirement from Google for MV3](https://developer.chrome.com/docs/extensions/migrating/improve-security/#remove-remote-code).
|
||||
|
||||
## Google Analytics
|
||||
|
||||
For example, you can import google analytics:
|
||||
For example, you can import Google Analytics:
|
||||
|
||||
```ts
|
||||
// utils/google-analytics.ts
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Reusable Modules
|
||||
|
||||
## Overview
|
||||
|
||||
WXT provides a "module" API that lets you modify the build process. This API lets you add entrypoints, inject runtime code, add vite plugins, and more!
|
||||
|
||||
What's more, these modules can be shared on NPM and re-used between projects!
|
||||
|
||||
## Adding a Module
|
||||
|
||||
There are two ways to add a module to your project:
|
||||
|
||||
1. **Local file**: Any file present in the `modules/` directory will be treated as a module and loaded at build-time by WXT. You can use `modules/*.ts` or `modules/*/index.ts`, similar to entrypoints.
|
||||
|
||||
```ts
|
||||
// modules/example.ts
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
|
||||
export default defineWxtModule((wxt) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
2. **NPM package**: By convention, any package on NPM that starts with `@wxt-dev/module-*` or `wxt-module-*` can be installed and included in your project:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
// Add the module to your project
|
||||
modules: ['wxt-module-example'],
|
||||
});
|
||||
```
|
||||
|
||||
## Writing Modules
|
||||
|
||||
Modules contain a setup function that is executed at the beginning of the build process.
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [Function Definition]
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
|
||||
export default defineWxtModule((wxt) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
```ts [Object Definition]
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
|
||||
export default defineWxtModule({
|
||||
// Add metadata...
|
||||
setup(wxt) {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Module Options
|
||||
|
||||
You can define custom options for your module by setting the `configKey`:
|
||||
|
||||
```ts
|
||||
// modules/analytics.ts
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
|
||||
export default defineWxtModule<AnalyticsModuleOptions>({
|
||||
configKey: 'analytics',
|
||||
setup(wxt, options) {
|
||||
console.log(options); // { clientId: "..." }
|
||||
},
|
||||
});
|
||||
|
||||
// Define the option types
|
||||
export interface AnalyticsModuleOptions {
|
||||
clientId: string;
|
||||
}
|
||||
|
||||
// Use "module augmentation" to add types for the new key
|
||||
declare module 'wxt' {
|
||||
export interface InlineConfig {
|
||||
analytics: AnalyticsModuleOptions;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now, when the user provides options to the `analytics` key in their `wxt.config.ts`, those options are passed into the setup function as the second argument.
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
analytics: { clientId: '...' },
|
||||
});
|
||||
```
|
||||
|
||||
### Actually Doing Something
|
||||
|
||||
The first argument of the setup function, `wxt`, provides full access to the current build's context. You can access the resolved configuration via `wxt.config`, or setup hooks to manipulate the build at different steps of the build process.
|
||||
|
||||
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.
|
||||
|
||||
```ts
|
||||
export default defineWxtModule((wxt) => {
|
||||
if (wxt.config.mode === 'development') {
|
||||
// Use the "ready" hook to update wxt.config
|
||||
wxt.hooks.hook('ready', (wxt) => {
|
||||
wxt.config.outDir = wxt.config.outDir.replace('.output', '.output/dev');
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
:::info Async Modules
|
||||
Both the `setup` function and hook callbacks can be async. Don't forget to add `await`!
|
||||
:::
|
||||
|
||||
It's important to understand the basics of how hooks work. Make sure to read the [API reference](/api/reference/wxt/interfaces/WxtHooks.html) for the full list of hooks and what they should be used for. They are the key to modifying your extension.
|
||||
|
||||
### Module Utils
|
||||
|
||||
Additionally, WXT provides several helper functions that setup hooks behind the scenes to streamline common operations.
|
||||
|
||||
For example, if you want to include an entrypoint from inside a module, you can use the `addEntrypoint` util:
|
||||
|
||||
```ts
|
||||
// modules/changelog.ts
|
||||
import { defineWxtModule, addEntrypoint } from 'wxt/modules';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export default defineWxtModule({
|
||||
name: 'changelog',
|
||||
setup(wxt) {
|
||||
addEntrypoint(wxt, {
|
||||
type: 'unlisted-page',
|
||||
name: 'changelog',
|
||||
// Point to the "modules/changelog.html" file
|
||||
inputPath: resolve(__dirname, 'changelog.html'),
|
||||
outputDir: wxt.config.outputDir,
|
||||
options: {},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Refer to the [API reference](/api/reference/wxt/modules/#functions) for the full list of the utilities.
|
||||
|
||||
## Plugins
|
||||
|
||||
Whereas modules are executed at build-time, plugins are executed at runtime. As of now, the only way to add a plugin is with the `addWxtPlugin` helper inside a module.
|
||||
|
||||
Here's a minimal example to execute something at runtime.
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [modules/example/index.ts]
|
||||
import { defineWxtModule, addWxtPlugin } from 'wxt/modules';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export default defineWxtModule((wxt) => {
|
||||
addWxtPlugin(wxt, resolve(__dirname, 'plugin.ts'));
|
||||
});
|
||||
```
|
||||
|
||||
```ts [modules/example/plugin.ts]
|
||||
import { defineWxtPlugin } from 'wxt/sandbox';
|
||||
|
||||
export default defineWxtPlugin(() => {
|
||||
console.log('Executing plugin!');
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::warning Async Plugins
|
||||
Unlike modules, **_plugins cannot be async_**!! If you need to do some async work and expose that result to the rest of the extension, store the result's promise synchronously and await it later on.
|
||||
:::
|
||||
|
||||
## Publishing to NPM
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
@@ -6,7 +6,9 @@ WXT officially supports [Vitest](https://vitest.dev/) for unit tests and either
|
||||
|
||||
For details setting up each testing framework, see the official examples:
|
||||
|
||||
<ExampleList tag="testing" />
|
||||
- [Vitest](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-vitest#readme)
|
||||
- [Playwright](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-playwright#readme)
|
||||
- [Puppeteer](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-puppeteer#readme)
|
||||
|
||||
### Unofficial Frameworks
|
||||
|
||||
@@ -17,9 +19,9 @@ There are other options for unit tests however, like [Jest](https://jestjs.io/),
|
||||
If you want to try to use a different framework for unit tests, you will need to configure the environment manually:
|
||||
|
||||
- **Auto-imports**: Add `unimport` to your test environment or disable them by setting `imports: false` in your `wxt.config.ts` file
|
||||
- **`browser` mock**: Mock the `webextension-polyfill` module globally with `wxt/dist/virtual/mock-browser.js`
|
||||
- **[Remote Code Bundling](/guide/remote-code)**: If you use it, configure your environment to handle the `url:` module prefix
|
||||
- **`browser` mock**: Mock the `webextension-polyfill` module globally with `wxt/dist/virtual/mock-browser.mjs`
|
||||
- **[Remote Code Bundling](/guide/go-further/remote-code)**: If you use it, configure your environment to handle the `url:` module prefix
|
||||
- **Global Variables**: If you consume them, manually define globals provided by WXT (like `import.meta.env.BROWSER`) by adding them to the global scope before accessing them (`import.meta.env.BROWSER = "chrome"`)
|
||||
- **Import paths**: If you use the `@/` or `~/` path aliases, add them to your test environment
|
||||
|
||||
[Here's how Vitest is configured](https://github.com/wxt-dev/wxt/blob/main/src/testing/wxt-vitest-plugin.ts) for reference.
|
||||
[Here's how Vitest is configured](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/src/testing/wxt-vitest-plugin.ts) for reference.
|
||||
@@ -18,9 +18,7 @@ export default defineConfig({
|
||||
|
||||
## Using Plugins
|
||||
|
||||
Plugins can be passed into the `vite` configuration in you `wxt.config.ts` file, just like any other option.
|
||||
|
||||
All plugins should work in WXT, but it is worth pointing out that since WXT orchestrates multiple vite builds to bundle an extension, plugins will be executed multiple times if necessary.
|
||||
Plugins can be passed into the `vite` configuration in your `wxt.config.ts` file, just like any other option.
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
@@ -33,3 +31,9 @@ export default defineConfig({
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
:::warning UNEXPECTED BEHAVIOR
|
||||
Due to the way WXT orchestrates Vite builds, some plugins may not work as expected. Search [GitHub issues](https://github.com/wxt-dev/wxt/issues?q=is%3Aissue+label%3A%22vite+plugin%22) if you run into issues with a specific plugin.
|
||||
|
||||
If one doesn't exist, please open a [new issue](https://github.com/wxt-dev/wxt/issues/new/choose)!
|
||||
:::
|
||||
@@ -0,0 +1,117 @@
|
||||
# Auto-imports
|
||||
|
||||
WXT uses the same tool as Nuxt for auto-imports, [`unimport`](https://github.com/unjs/unimport).
|
||||
|
||||
## WXT Auto-imports
|
||||
|
||||
Some WXT APIs can be used without importing them:
|
||||
|
||||
- [`browser`](/api/reference/wxt/browser/variables/browser) from `wxt/browser`, a small wrapper around `webextension-polyfill`
|
||||
- [`defineContentScript`](/api/reference/wxt/sandbox/functions/defineContentScript) from `wxt/sandbox`
|
||||
- [`defineBackground`](/api/reference/wxt/sandbox/functions/defineBackground) from `wxt/sandbox`
|
||||
- [`defineUnlistedScript`](/api/reference/wxt/sandbox/functions/defineUnlistedScript) from `wxt/sandbox`
|
||||
- [`createIntegratedUi`](/api/reference/wxt/client/functions/createIntegratedUi) from `wxt/client`
|
||||
- [`createShadowRootUi`](/api/reference/wxt/client/functions/createShadowRootUi) from `wxt/client`
|
||||
- [`createIframeUi`](/api/reference/wxt/client/functions/createIframeUi) from `wxt/client`
|
||||
- [`fakeBrowser`](/api/reference/wxt/testing/variables/fakeBrowser) from `wxt/testing`
|
||||
|
||||
And more!
|
||||
|
||||
## Project Auto-imports
|
||||
|
||||
In addition WXT APIs, default and named exports from inside the following directories can be used without listing them in imports.
|
||||
|
||||
- `<srcDir>/components/*`
|
||||
- `<srcDir>/composables/*`
|
||||
- `<srcDir>/hooks/*`
|
||||
- `<srcDir>/utils/*`
|
||||
|
||||
To add auto-imports from subdirectories, like `utils/api/some-file.ts`, re-export them from the base directory:
|
||||
|
||||
```ts
|
||||
// utils/index.ts
|
||||
export * from './api/some-file.ts';
|
||||
```
|
||||
|
||||
Alternatively, you could add the directory to the list of auto-import directories in your config file.
|
||||
|
||||
## TypeScript
|
||||
|
||||
For TypeScript to work, you need to run the `wxt prepare` command. This will ensure types are generated for auto-imports.
|
||||
|
||||
This should be added to your `postinstall` script so your editor has everything it needs to report type errors after installing dependencies:
|
||||
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"scripts": {
|
||||
"postinstall": "wxt prepare" // [!code ++]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
You can override the default auto-import behavior in your `wxt.config.ts` file.
|
||||
|
||||
See [`unimport`'s documentation](https://github.com/unjs/unimport#configurations) for a complete list of options.
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
imports: {
|
||||
// Add auto-imports for vue functions like createApp, ref, computed, watch, toRaw, etc...
|
||||
presets: ['vue'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Disabling Auto-imports
|
||||
|
||||
To disable auto-imports, set `imports: false`
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
imports: false,
|
||||
});
|
||||
```
|
||||
|
||||
## ESLint
|
||||
|
||||
ESLint doesn't know about the auto-imported variables unless they are explicitly defined in the `globals` config. By default, WXT will generate the config if it detects ESLint is installed in your project. If the config isn't generated automatically, you can manually tell WXT to generate it.
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
imports: {
|
||||
eslintrc: {
|
||||
enabled: 8, // Generate ESLint v8 compatible config
|
||||
// or
|
||||
enabled: 9, // Generate ESLint v9 compatible config
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### ESLint 9 and above
|
||||
|
||||
WXT supports the "flat config" file format introduced in ESLint 9. Just import the generated file and add it to the array of config to extend.
|
||||
|
||||
```js
|
||||
// eslint.config.mjs
|
||||
import autoImports from './.wxt/eslint-auto-imports.mjs';
|
||||
|
||||
export default [autoImports];
|
||||
```
|
||||
|
||||
### ESLint 8 and below
|
||||
|
||||
Just extend the generated file:
|
||||
|
||||
```js
|
||||
// .eslintrc.mjs
|
||||
export default {
|
||||
extends: ['./.wxt/eslintrc-auto-import.json'],
|
||||
};
|
||||
```
|
||||
@@ -155,20 +155,20 @@ export default defineContentScript({
|
||||
|
||||
:::
|
||||
|
||||
See the [API Reference](/api/wxt/client/functions/createIntegratedUi) for the complete list of options.
|
||||
See the [API Reference](/api/reference/wxt/client/functions/createIntegratedUi) for the complete list of options.
|
||||
|
||||
You can control how CSS is injected for an integrated content script UI with the [`cssInjectionMode`](/api/wxt/interfaces/BaseContentScriptEntrypointOptions#cssinjectionmode) property. Usually, you'll want to leave it as `"manifest"`, the default, so the UI inherits its style from the website's CSS.
|
||||
You can control how CSS is injected for an integrated content script UI with the [`cssInjectionMode`](/api/reference/wxt/interfaces/BaseContentScriptEntrypointOptions#cssinjectionmode) property. Usually, you'll want to leave it as `"manifest"`, the default, so the UI inherits its style from the website's CSS.
|
||||
|
||||
## Shadow Root
|
||||
|
||||
Often in web extensions, you don't want your content script's CSS affecting the page, or vise-versa. The [`ShadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot) API is ideal for this.
|
||||
|
||||
WXT's [`createShadowRootUi`](/api/wxt/client/functions/createShadowRootUi) abstracts all the `ShadowRoot` setup away, making it easy to create UIs with isolated CSS. It also supports an optional `isolateEvents` parameter to further isolate user interactions.
|
||||
WXT's [`createShadowRootUi`](/api/reference/wxt/client/functions/createShadowRootUi) abstracts all the `ShadowRoot` setup away, making it easy to create UIs with isolated CSS. It also supports an optional `isolateEvents` parameter to further isolate user interactions.
|
||||
|
||||
To use `createShadowRootUi`, follow these steps:
|
||||
|
||||
1. Import your CSS file at the top of your content script
|
||||
2. Set [`cssInjectionMode: "ui"`](/api/wxt/interfaces/BaseContentScriptEntrypointOptions#cssinjectionmode) inside `defineContentScript`
|
||||
2. Set [`cssInjectionMode: "ui"`](/api/reference/wxt/interfaces/BaseContentScriptEntrypointOptions#cssinjectionmode) inside `defineContentScript`
|
||||
3. Define your UI with `createShadowRootUi()`
|
||||
4. Mount the UI so it is visible to users
|
||||
|
||||
@@ -341,7 +341,7 @@ export default defineContentScript({
|
||||
|
||||
:::
|
||||
|
||||
See the [API Reference](/api/wxt/client/functions/createShadowRootUi) for the complete list of options.
|
||||
See the [API Reference](/api/reference/wxt/client/functions/createShadowRootUi) for the complete list of options.
|
||||
|
||||
:::info TailwindCSS
|
||||
`createShadowRootUi` supports TailwindCSS out of the box! When importing the styles, just import the main CSS file containing the `@tailwind` directives, and everything will just work :+1:.
|
||||
@@ -351,7 +351,7 @@ See the [API Reference](/api/wxt/client/functions/createShadowRootUi) for the co
|
||||
|
||||
If you don't need to run your UI in the same frame as the content script, you can use an IFrame to host your UI instead. Since an IFrame just hosts an HTML page, **_HMR is supported_**.
|
||||
|
||||
WXT provides a helper function, [`createIframeUi`](/api/wxt/client/functions/createIframeUi), which simplifies setting up the IFrame.
|
||||
WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/client/functions/createIframeUi), which simplifies setting up the IFrame.
|
||||
|
||||
1. Create an HTML page that will be loaded into your IFrame
|
||||
```html
|
||||
@@ -388,14 +388,14 @@ WXT provides a helper function, [`createIframeUi`](/api/wxt/client/functions/cre
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
|
||||
async main(ctx) {
|
||||
main(ctx) {
|
||||
// Define the UI
|
||||
const ui = await createIframeUi(ctx, {
|
||||
const ui = createIframeUi(ctx, {
|
||||
page: '/example-iframe.html',
|
||||
position: 'inline',
|
||||
onMount: (wrapper, iframe) => {
|
||||
// Add styles to the iframe like width
|
||||
iframe.width = 123;
|
||||
iframe.width = '123';
|
||||
},
|
||||
});
|
||||
|
||||
@@ -405,4 +405,4 @@ WXT provides a helper function, [`createIframeUi`](/api/wxt/client/functions/cre
|
||||
});
|
||||
```
|
||||
|
||||
See the [API Reference](/api/wxt/client/functions/createIframeUi) for the complete list of options.
|
||||
See the [API Reference](/api/reference/wxt/client/functions/createIframeUi) for the complete list of options.
|
||||
@@ -0,0 +1,112 @@
|
||||
# Frontend Frameworks
|
||||
|
||||
## Built-in Modules
|
||||
|
||||
WXT has preconfigured modules for 4 frameworks:
|
||||
|
||||
- [`@wxt-dev/module-react`](https://github.com/wxt-dev/wxt/tree/main/packages/module-react)
|
||||
- [`@wxt-dev/module-vue`](https://github.com/wxt-dev/wxt/tree/main/packages/module-vue)
|
||||
- [`@wxt-dev/module-svelte`](https://github.com/wxt-dev/wxt/tree/main/packages/module-svelte)
|
||||
- [`@wxt-dev/module-solid`](https://github.com/wxt-dev/wxt/tree/main/packages/module-solid)
|
||||
|
||||
Install the module for your framework, then add it to your config:
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [React]
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/module-react'],
|
||||
});
|
||||
```
|
||||
|
||||
```ts [Vue]
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/module-vue'],
|
||||
});
|
||||
```
|
||||
|
||||
```ts [Svelte]
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/module-svelte'],
|
||||
});
|
||||
```
|
||||
|
||||
```ts [Solid]
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/module-solid'],
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Adding Vite Plugins
|
||||
|
||||
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 and it will just work 👍
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
vite: () => ({
|
||||
plugins: [react()],
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
The WXT modules just simplify the configuration and add auto-imports. They're not much different than the above.
|
||||
|
||||
## Multiple Apps
|
||||
|
||||
Since web extensions usually contain multiple UIs as separate HTML files (popup, options, changelog, side panel, etc), you'll need to create individual app instances, one per HTML page.
|
||||
|
||||
Usually, this means each entrypoint should be a directory with it's own files inside it:
|
||||
|
||||
```
|
||||
<root>/
|
||||
├ assets/ <------------------ Put shared assets here
|
||||
│ ├ style.css <------------ Like styles all your pages share
|
||||
│ └ ...
|
||||
├ components/ <-------------- Put shared components here
|
||||
│ └ ...
|
||||
└ entrypoints/
|
||||
├ popup/ <--------------- Use a folder with an index.html file in it
|
||||
│ ├ index.html
|
||||
│ ├ main.tsx <--------- Create and mount your app here
|
||||
│ ├ style.css <-------- Have some global styles to apply?
|
||||
│ └ ... <--------------- Rest of the files can be named whatever
|
||||
└ options/
|
||||
├ pages/ <------------ A good place to put your router pages
|
||||
│ ├ [id]/
|
||||
│ │ └ details.tsx
|
||||
│ ├ index.tsx
|
||||
│ └...
|
||||
├ index.html
|
||||
├ App.vue
|
||||
├ main.ts
|
||||
├ style.css
|
||||
└ router.ts
|
||||
```
|
||||
|
||||
## Configuring Routers
|
||||
|
||||
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`).
|
||||
|
||||
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:
|
||||
|
||||
- [`react-router`](https://reactrouter.com/en/main/routers/create-hash-router)
|
||||
- [`vue-router`](https://router.vuejs.org/guide/essentials/history-mode.html#Hash-Mode)
|
||||
- [`svelte-spa-router`](https://www.npmjs.com/package/svelte-spa-router#hash-based-routing)
|
||||
- [`solid-router`](https://github.com/solidjs/solid-router?tab=readme-ov-file#hash-mode-router)
|
||||
@@ -0,0 +1,277 @@
|
||||
# Manifest
|
||||
|
||||
## Overview
|
||||
|
||||
Sometimes, you'll need to make manual changes to how the `manifest.json` is generated. You can do this by using the `manifest` configuration:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
// Put manual changes here
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Manifest Version Compatibility
|
||||
|
||||
When defining options in the manifest, always define them for MV3 when possible. WXT will either convert them to their MV2 equivalents or remove them from the generated manifest if there is not MV2 equivalent.
|
||||
|
||||
So for fields like `web_accessible_resources` or `content_security_policy`, you just need to list them in their MV3 forms. Other fields, like `side_panel`, which doesn't exist in MV2, will be removed automatically.
|
||||
|
||||
Here's an example `wxt.config.ts` file:
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
action: {
|
||||
default_title: 'Some Title',
|
||||
},
|
||||
web_accessible_resources: [
|
||||
{
|
||||
matches: ['*://*.google.com/*'],
|
||||
resources: ['icon/*.png'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
And here's the different `manifest.json` files generated:
|
||||
|
||||
:::code-group
|
||||
|
||||
```json [MV2]
|
||||
{
|
||||
"manifest_version": 2,
|
||||
// ...
|
||||
"browser_action": {
|
||||
"default_title": "Some Title"
|
||||
},
|
||||
"web_accessible_resources": ["icon/*.png"]
|
||||
}
|
||||
```
|
||||
|
||||
```json [MV3]
|
||||
{
|
||||
"manifest_version": 3,
|
||||
// ...
|
||||
"action": {
|
||||
"default_title": "Some Title"
|
||||
},
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"matches": ["*://*.google.com/*"],
|
||||
"resources": ["icon/*.png"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Name
|
||||
|
||||
If not provided via the `manifest` config, the [manifest's `name`](https://developer.chrome.com/docs/extensions/mv3/manifest/name/) defaults to your `package.json`'s `name` property.
|
||||
|
||||
## Version
|
||||
|
||||
The [manifest's `version` and `version_name`](https://developer.chrome.com/docs/extensions/mv3/manifest/version/) properties are based on the `version` field listed in your `package.json` or `wxt.config.ts`.
|
||||
|
||||
- `version_name` is the exact string listed in your `package.json` or `wxt.config.ts` file
|
||||
- `version` is the string cleaned up, with any invalid suffixes removed
|
||||
|
||||
If a version is not found, a warning is logged and the version defaults to `"0.0.0"`.
|
||||
|
||||
#### Example
|
||||
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"version": "1.3.0-alpha2"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// .output/<dir>/manifest.json
|
||||
{
|
||||
"version": "1.3.0",
|
||||
"version_name": "1.3.0-alpha2"
|
||||
}
|
||||
```
|
||||
|
||||
## `icons`
|
||||
|
||||
By default, WXT will discover icons in your [`public` directory](/guide/directory-structure/public/) and use them for the [manifest's `icons`](https://developer.chrome.com/docs/extensions/mv3/manifest/icons/).
|
||||
|
||||
```
|
||||
public/
|
||||
├─ icon-16.png
|
||||
├─ icon-24.png
|
||||
├─ icon-48.png
|
||||
├─ icon-96.png
|
||||
└─ icon-128.png
|
||||
```
|
||||
|
||||
Icon files need to match the following regex to be automatically included in the manifest. Most design software can output icons in one of these formats
|
||||
|
||||
<<< @/../packages/wxt/src/core/utils/manifest.ts#snippet
|
||||
|
||||
If you prefer to use filenames in a different format, you can add the icons manually in your `wxt.config.ts` file:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
icons: {
|
||||
16: '/extension-icon-16.png',
|
||||
24: '/extension-icon-24.png',
|
||||
48: '/extension-icon-48.png',
|
||||
96: '/extension-icon-96.png',
|
||||
128: '/extension-icon-128.png',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Permissions
|
||||
|
||||
[Permissions](https://developer.chrome.com/docs/extensions/reference/permissions/) must be listed in the manifest config.
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['storage', 'tabs'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Host Permissions
|
||||
|
||||
[Host Permissions](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions#host-permissions) must be listed in the manifest config.
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
host_permissions: ['*://*.google.com/*'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::warning
|
||||
If you use host permissions and target both MV2 and MV3, make sure to only include the required host permissions for each version:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: ({ manifestVersion }) => ({
|
||||
host_permissions: manifestVersion === 2 ? [...] : [...],
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Localization
|
||||
|
||||
Similar to the icon, the [`_locales` directory](https://developer.chrome.com/docs/extensions/reference/i18n/) should be placed inside the the WXT's [`public` directory](/guide/directory-structure/public/).
|
||||
|
||||
```
|
||||
public/
|
||||
└─ _locales/
|
||||
├─ en/
|
||||
│ └─ messages.json
|
||||
├─ es/
|
||||
│ └─ messages.json
|
||||
└─ ko/
|
||||
└─ messages.json
|
||||
```
|
||||
|
||||
Then you'll need to explicitly override the `name` and `description` properties in your config for them to be localized.
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
name: '__MSG_extName__',
|
||||
description: '__MSG_extDescription__',
|
||||
default_locale: 'en',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
See the official localization examples for more details:
|
||||
|
||||
- [I18n](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-i18n#readme)
|
||||
- [Vue I18n](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vue-i18n#readme)
|
||||
|
||||
## Actions
|
||||
|
||||
In MV2, you had two options: [`browser_action`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_action) and [`page_action`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/page_action). In MV3, they were merged into a single [`action`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action) API.
|
||||
|
||||
By default, whenever an action is generated, WXT falls back to `browser_action` when targetting MV2.
|
||||
|
||||
### Action With Popup
|
||||
|
||||
To generate a manifest where a UI appears after clicking the icon, just create a [popup entrypoint](/guide/directory-structure/entrypoints/popup).
|
||||
|
||||
If you want to use a `page_action` for MV2, add the following `meta` tag to the HTML document's head:
|
||||
|
||||
```html
|
||||
<meta name="manifest.type" content="page_action" />
|
||||
```
|
||||
|
||||
### Action Without Popup
|
||||
|
||||
If you want to use the `activeTab` permission or the `browser.action.onClick` event, but don't want to show a popup UI:
|
||||
|
||||
1. Delete the [popup entrypoint](/guide/directory-structure/entrypoints/popup) if it exists
|
||||
2. Add the `action` key to your manifest:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
action: {},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Same as an action with a popup, WXT will fallback on using `browser_action` for MV2. To use a `page_action` instead, add that key as well:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
action: {},
|
||||
page_action: {},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Full Control
|
||||
|
||||
The `manifest` option can also be set equal to a function, letting you use logical statements to determine what should be output.
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
manifest: ({ manifestVersion, browser, mode, command }) => {
|
||||
return { ... }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Or, you can use the `build:manifestGenerated` hook to transform the manifest before it is written to the output directory.
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
hooks: {
|
||||
build: {
|
||||
manifestGenerated(manifest) {
|
||||
// Update the manifest variable by reference
|
||||
manifest.name = 'Overriden name';
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -33,7 +33,7 @@ By default, it will build for `chrome`. When excluding the [manifest version fla
|
||||
| Any other string | 3 |
|
||||
|
||||
:::tip
|
||||
To configure which browser is opened when running dev mode via `wxt -b <browser>`, see the [Development docs](/guide/development#configure-browser-startup) docs.
|
||||
To configure which browser is opened when running dev mode via `wxt -b <browser>`, see the [`web-ext.config.ts` docs](/guide/directory-structure/web-ext-config).
|
||||
:::
|
||||
|
||||
## Target Manifest Version
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user