Compare commits

..

77 Commits

Author SHA1 Message Date
GitHub Actions 03c8ab4d6f chore(release): v0.10.3 2023-11-28 18:18:59 +00:00
Aaron df13b9705c fix(auto-imports): Don't add imports to node_module dependencies (#247) 2023-11-28 12:15:05 -06:00
Aaron 762ba0080d chore(deps): Upgrade to typescript 5.3 (#245) 2023-11-27 10:03:03 -06:00
Aaron Klinker 317b1b6dcc docs: Fix typo 2023-11-27 09:57:09 -06:00
Aaron 93175a6477 chore: Use defu for merging some config objects (#243) 2023-11-26 12:23:10 -06:00
Aaron 60d6707b11 chore: Use normalize-path instead of vite.normalizePath (#244) 2023-11-26 11:36:42 -06:00
Aaron Klinker 97cbda3dab ci: only print response headers from docs webhook 2023-11-25 14:57:04 -06:00
Aaron Klinker 1611c1dba6 ci: Publish docs on push to main 2023-11-25 14:51:30 -06:00
Aaron Klinker b59252284a Print verbose webhook 2023-11-25 14:48:05 -06:00
Aaron Klinker 742b99657a chore: Trigger docs upgrade via webhook 2023-11-25 14:45:41 -06:00
Aaron Klinker e2997a43e0 Fix docker push command 2023-11-24 14:18:27 -06:00
Aaron Klinker 21dead60fd Self-host docs 2023-11-24 14:16:18 -06:00
GitHub Actions 3d1bc0a12c chore(release): v0.10.2 2023-11-20 19:52:55 +00:00
Aaron Klinker 4b24bee1fb Update demo app logs 2023-11-20 13:47:31 -06:00
Aaron Klinker 82ed821eb5 fix: Apply mode option to build steps correctly 2023-11-20 13:47:18 -06:00
Aaron 60625280c2 chore: Upgrade templates to v0.10 (#239) 2023-11-16 16:50:30 -06:00
GitHub Actions 083792b5f0 chore(release): v0.10.1 2023-11-16 17:46:36 +00:00
Aaron Klinker 3da3e07bda fix: Remove WXT global to remove unused modules from production builds 2023-11-16 11:35:20 -06:00
Aaron Klinker 64d61eb3f1 Update changelog 2023-11-14 23:26:27 -06:00
GitHub Actions aa6009e476 chore(release): v0.10.0 2023-11-15 05:21:53 +00:00
Aaron d2cb8f9416 feat: Add wxt/storage API (#234) 2023-11-14 23:07:45 -06:00
Nenad Novaković 046a4809d3 feat!: Allow plural directory and only png's for manifest icons (#237)
BREAKING CHANGE: No longer discover icons with extensions other than `.png`. If you previously used `.jpg`, `.jpeg`, `.bmp`, or `.svg`, you'll need to convert your icons to `.png` files or manually add them to the manifest inside your `wxt.config.ts` file.
2023-11-14 23:07:09 -06:00
Aaron Klinker 207b750d4e docs: Update main README links 2023-11-12 10:37:56 -06:00
Aaron 077fa9939e feat: List bun as an experimental option in wxt init (#233) 2023-11-11 15:45:29 -06:00
Aaron 44d715a29e fix: Don't use bun to load entrypoint config (#232) 2023-11-11 14:46:13 -06:00
GitHub Actions 3ab9fe4ae3 chore(release): v0.9.2 2023-11-11 19:57:25 +00:00
Aaron 91b28c2cb9 feat: Experimental option to exclude webextension-polyfill (#231) 2023-11-11 13:53:47 -06:00
Aaron Klinker d1b523061f ci: Fix sync-release workflow 2023-11-11 12:49:40 -06:00
Aaron Klinker c69ea3967f Fix changelog 2023-11-11 12:47:32 -06:00
GitHub Actions 41527a2200 chore(release): v0.9.1 2023-11-11 18:27:09 +00:00
Aaron 281f28192d fix: Generate valid manifest for Firefox MV3 (#229) 2023-11-11 12:22:46 -06:00
Aaron 67ffa44f9c docs: Update feature comparison 2023-11-11 08:53:59 -06:00
Aaron Klinker 421c0e412d docs: Add StayFree to homepage (#221) 2023-11-08 10:51:42 -06:00
Aaron d21ee08eb6 docs: Add UltraWideo to homepage (#193) 2023-11-08 09:26:51 -06:00
Aaron 7fa150dfae docs: Improve the "Used By" section on homepage (#220) 2023-11-08 08:27:58 -06:00
Aaron 7ba52b18fd ci: Automatically sync GitHub releases with CHANGELOG.md on push (#218) 2023-11-07 13:08:46 -06:00
Aaron 66e5079b84 feat: Add alias config for customizing path aliases (#216) 2023-11-07 11:38:48 -06:00
Aaron Klinker f1e8084be8 chore: Update Vite dependency range to ^4.0.0 || ^5.0.0-0 2023-11-07 10:42:36 -06:00
Aaron Klinker 609ae2ae37 fix: Move webextension-polyfill from peer to regular dependencies
This closes https://github.com/wxt-dev/wxt/issues/215
2023-11-07 10:38:24 -06:00
Aaron Klinker c81dfff37c docs: Add examples 2023-11-05 11:35:59 -06:00
Aaron 9c27820add chore: Upgrade templates to wxt@^0.9.0 (#214) 2023-11-04 13:01:05 -05:00
Aaron 22f8e10918 chore: Remove whitespace from genearted .wxt files (#211) 2023-11-04 12:28:31 -05:00
Aaron Klinker 96d41f8b7c Update changelog 2023-11-04 11:17:32 -05:00
GitHub Actions c4ce44e001 chore(release): v0.9.0 2023-11-04 15:56:43 +00:00
yyyanghj 6641ffaeed fix!: Remove lib from .wxt/tsconfig.json (#209)
Co-authored-by: Aaron Klinker <aaronklinker1@gmail.com>
2023-11-04 10:50:52 -05:00
Aaron 450dc0975f Add CLI screenshot back to README 2023-11-04 09:12:06 -05:00
Aaron d343555f41 Update MP4 title 2023-11-04 01:34:25 -05:00
Aaron 7993908c6c docs: Add demo video (#208) 2023-11-04 01:32:19 -05:00
yyyanghj 94e1dbce24 chore: Fix Svelte and React template READMEs (#207) 2023-11-03 12:59:35 -05:00
Aaron d14215946a Cleanup README badge links 2023-11-03 11:46:14 -05:00
Aaron Klinker 345406f02a docs: Fix heading 2023-11-03 09:39:09 -05:00
GitHub Actions 6b689bab23 chore(release): v0.8.7 2023-11-03 14:36:36 +00:00
Aaron fcdf0dcbe9 feat: createContentScriptIframe utility (#206) 2023-11-03 09:31:16 -05:00
Aaron bd35acdae1 chore: Run E2E tests in parallel (#204) 2023-11-02 11:15:07 -05:00
Aaron 21c35ee593 ci: Separate validation into multiple jobs (#203) 2023-11-02 11:08:19 -05:00
Aaron Klinker 0f09cbee0e chore: Improve prettier git hook 2023-11-02 10:31:54 -05:00
Aaron Klinker 183bb02e29 chore(deps-dev): Update sass to 1.69.5 2023-11-02 10:26:14 -05:00
Aaron 0bf4ea0897 chore(deps): Upgrade vite to 4.5.0 (#202) 2023-11-02 10:24:50 -05:00
Aaron Klinker cd4d00e23e chore(deps-dev): Update @type/* packages for demo 2023-11-02 10:16:52 -05:00
Aaron Klinker 5de18e5371 chore(deps-dev): Upgrade vitepress to 1.0.0-rc.24 2023-11-02 10:04:33 -05:00
Aaron af823418ee chore(deps): Upgrade giget to ^1.1.3 (#199) 2023-11-02 09:11:17 -05:00
Aaron Klinker 0b8d101c33 chore(deps-dev): Upgrade vue to 3.3.7 2023-11-02 08:58:25 -05:00
Aaron b587849126 chore(deps-dev): Upgrade typedoc to ^0.25.3 (#201) 2023-11-02 08:57:16 -05:00
Aaron 2a35ce0a1e chore(deps-dev): Upgrade execa to ^8.0.1 (#200) 2023-11-02 08:57:06 -05:00
Aaron Klinker c390b70f76 chore(deps): Upgrade unimport to 3.4.0 2023-11-02 08:54:24 -05:00
Aaron Klinker c9fd739128 chore(deps): Upgrade linkedom to 0.16.1 2023-11-02 08:52:22 -05:00
Aaron Klinker 0806c06042 chore(deps): Upgrade jiti to 1.21.0 2023-11-02 08:51:14 -05:00
Aaron Klinker 5f74a544a4 chore(deps-dev): Upgrade lint-staged to ^15.0.2 2023-11-02 08:47:53 -05:00
Aaron Klinker 121778894c chore(deps): Upgrade immer to ^10.0.3 2023-11-02 08:46:25 -05:00
Aaron Klinker 102c72a03c chore(deps): Upgrade esbuild to ^0.19.5 2023-11-02 08:42:16 -05:00
Aaron Klinker a0507866db chore(deps): Upgrade c12 to ^1.5.1 2023-11-02 08:41:05 -05:00
Aaron Klinker f3874da780 chore: Upgrade types 2023-11-02 08:39:47 -05:00
dependabot[bot] cc5d24ec92 chore(deps): bump @types/webextension-polyfill from 0.10.0 to 0.10.5 (#196)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-02 08:05:50 -05:00
dependabot[bot] 08760015da chore(deps-dev): bump tsx from 3.12.8 to 3.14.0 (#198)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-02 08:02:53 -05:00
dependabot[bot] 5368371020 chore(deps): bump filesize from 10.0.12 to 10.1.0 (#197)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-02 08:01:50 -05:00
dependabot[bot] 08d62a437a chore(deps): bump is-wsl from 3.0.0 to 3.1.0 (#195)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-02 08:00:52 -05:00
dependabot[bot] 37e2348d79 chore(deps-dev): bump happy-dom from 12.4.0 to 12.10.3 (#194)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-02 07:59:55 -05:00
71 changed files with 2595 additions and 1061 deletions
+17
View File
@@ -0,0 +1,17 @@
name: Basic Setup
description: Install PNPM, Node, and dependencies
runs:
using: composite
steps:
- name: Setup PNPM
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup NodeJS
uses: actions/setup-node@v4
with:
node-version: 18
cache: pnpm
- name: Install Dependencies
shell: bash
run: pnpm install
+27
View File
@@ -0,0 +1,27 @@
name: Publish Docs
on:
push:
branches:
- main
workflow_dispatch:
inputs:
tag:
description: Docker Image Tag
required: true
default: latest
jobs:
publish:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: docker/login-action@v3
with:
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
- run: pnpm docs:build
- run: docker build docs/.vitepress -t ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
- run: docker push ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
- run: curl -X POST -i ${{ secrets.UPDATE_DOCS_WEBHOOK }}
+2 -15
View File
@@ -11,24 +11,11 @@ jobs:
needs:
- validate
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup PNPM
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup NodeJS
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- uses: ./.github/actions/setup
- name: Bump and Tag
run: |
+22
View File
@@ -0,0 +1,22 @@
name: Sync Releases
on:
workflow_dispatch:
push:
branches:
- main
paths:
- CHANGELOG.md
jobs:
sync:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 18
cache: pnpm
- run: pnpm sync-releases all --token ${{ secrets.GITHUB_TOKEN }}
+56 -127
View File
@@ -7,141 +7,70 @@ on:
- main
jobs:
checks:
name: Checks
formatting:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PNPM
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup NodeJS
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Formatting
run: pnpm format:check
- name: Type Check
run: pnpm compile
- name: Build Demo
run: |
pnpm build:all:chrome-mv2
pnpm build:all:chrome-mv3
pnpm build:all:firefox-mv2
pnpm build:all:firefox-mv3
pnpm tsc --noEmit
pnpm wxt zip
pnpm vitest run
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: pnpm format:check
type-check:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: pnpm compile
validate-demo:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: pnpm build:all
working-directory: demo
- name: Tests
- run: pnpm tsc --noEmit
working-directory: demo
- run: pnpm wxt zip
working-directory: demo
- run: pnpm vitest run
working-directory: demo
tests:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- name: pnpm test:coverage
run: pnpm test:coverage --reporter=default --reporter=hanging-process
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
windows-tests:
name: Windows Tests
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PNPM
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup NodeJS
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Tests
run: pnpm test:coverage --reporter=default --reporter=hanging-process
project-templates:
name: Project Templates
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: pnpm test run
template:
runs-on: ubuntu-22.04
strategy:
matrix:
template:
- react
- solid
- svelte
- vanilla
- vue
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PNPM
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup NodeJS
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Build Local Tarball
run: pnpm pack
- name: Validate Vanilla
working-directory: templates/vanilla
run: |
npm i
npm i -D ../../wxt-*.tgz
npm ls vite
npm run build
npm run compile
- name: Validate Vue
working-directory: templates/vue
run: |
npm i
npm i -D ../../wxt-*.tgz
npm ls vite
npm run build
npm run compile
- name: Validate React
working-directory: templates/react
run: |
npm i
npm i -D ../../wxt-*.tgz
npm ls vite
npm run build
npm run compile
- name: Validate Svelte
working-directory: templates/svelte
run: |
npm i
npm i -D ../../wxt-*.tgz
npm ls vite
npm run build
npm run check
- name: Validate Solid
working-directory: templates/solid
run: |
npm i
npm i -D ../../wxt-*.tgz
npm ls vite
npm run build
npm run compile
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: pnpm pack
- run: npm i
working-directory: templates/${{ matrix.template }}
- run: npm i -D ../../wxt-*.tgz
working-directory: templates/${{ matrix.template }}
- run: npm run compile
if: matrix.template != 'svelte'
working-directory: templates/${{ matrix.template }}
- run: npm run check
if: matrix.template == 'svelte'
working-directory: templates/${{ matrix.template }}
- run: npm run build
working-directory: templates/${{ matrix.template }}
-9
View File
@@ -5,12 +5,3 @@ dist
docs/.vitepress/cache
pnpm-lock.yaml
CHANGELOG.md
.gitattributes
.gitignore
.prettierignore
*.png
*.svg
*.txt
_gitignore
_redirects
*.svelte
+178 -2
View File
@@ -1,5 +1,181 @@
# Changelog
## v0.10.3
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.10.2...v0.10.3)
### 🩹 Fixes
- **auto-imports:** Don't add imports to `node_module` dependencies ([#247](https://github.com/wxt-dev/wxt/pull/247))
### 📖 Documentation
- Fix typo ([317b1b6](https://github.com/wxt-dev/wxt/commit/317b1b6))
### 🏡 Chore
- Trigger docs upgrade via webhook ([742b996](https://github.com/wxt-dev/wxt/commit/742b996))
- Use `normalize-path` instead of `vite.normalizePath` ([#244](https://github.com/wxt-dev/wxt/pull/244))
- Use `defu` for merging some config objects ([#243](https://github.com/wxt-dev/wxt/pull/243))
### 🤖 CI
- Publish docs on push to main ([1611c1d](https://github.com/wxt-dev/wxt/commit/1611c1d))
- Only print response headers from docs webhook ([97cbda3](https://github.com/wxt-dev/wxt/commit/97cbda3))
## v0.10.2
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.10.1...v0.10.2)
### 🩹 Fixes
- Apply `mode` option to build steps correctly ([82ed821](https://github.com/wxt-dev/wxt/commit/82ed821))
### 🏡 Chore
- Upgrade templates to v0.10 ([#239](https://github.com/wxt-dev/wxt/pull/239))
## v0.10.1
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.10.0...v0.10.1)
### 🩹 Fixes
- Remove WXT global to remove unused modules from production builds ([3da3e07](https://github.com/wxt-dev/wxt/commit/3da3e07))
## v0.10.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.9.2...v0.10.0)
### 🚀 Enhancements
- List `bun` as an experimental option in `wxt init` ([#233](https://github.com/wxt-dev/wxt/pull/233))
- ⚠️ Allow plural directory and only png's for manifest icons ([#237](https://github.com/wxt-dev/wxt/pull/237))
- Add `wxt/storage` API ([#234](https://github.com/wxt-dev/wxt/pull/234))
### 🩹 Fixes
- Don't use `bun` to load entrypoint config ([#232](https://github.com/wxt-dev/wxt/pull/232))
### 📖 Documentation
- Update main README links ([207b750](https://github.com/wxt-dev/wxt/commit/207b750))
#### ⚠️ Breaking Changes
- ⚠️ No longer discover icons with extensions other than `.png`. If you previously used `.jpg`, `.jpeg`, `.bmp`, or `.svg`, you'll need to convert your icons to `.png` files or manually add them to the manifest inside your `wxt.config.ts` file ([#237](https://github.com/wxt-dev/wxt/pull/237))
### ❤️ Contributors
- Nenad Novaković ([@dvlden](https://github.com/dvlden))
## v0.9.2
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.9.1...v0.9.2)
### 🚀 Enhancements
- Experimental option to exclude `webextension-polyfill` ([#231](https://github.com/wxt-dev/wxt/pull/231))
### 🤖 CI
- Fix sync-release workflow ([d1b5230](https://github.com/wxt-dev/wxt/commit/d1b5230))
## v0.9.1
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.9.0...v0.9.1)
### 🚀 Enhancements
- Add `alias` config for customizing path aliases ([#216](https://github.com/wxt-dev/wxt/pull/216))
### 🩹 Fixes
- Move `webextension-polyfill` from peer to regular dependencies ([609ae2a](https://github.com/wxt-dev/wxt/commit/609ae2a))
- Generate valid manifest for Firefox MV3 ([#229](https://github.com/wxt-dev/wxt/pull/229))
### 📖 Documentation
- Add examples ([c81dfff](https://github.com/wxt-dev/wxt/commit/c81dfff))
- Improve the "Used By" section on homepage ([#220](https://github.com/wxt-dev/wxt/pull/220))
- Add UltraWideo to homepage ([#193](https://github.com/wxt-dev/wxt/pull/193))
- Add StayFree to homepage ([#221](https://github.com/wxt-dev/wxt/pull/221))
- Update feature comparison ([67ffa44](https://github.com/wxt-dev/wxt/commit/67ffa44))
### 🏡 Chore
- Remove whitespace from genearted `.wxt` files ([#211](https://github.com/wxt-dev/wxt/pull/211))
- Upgrade templates to `wxt@^0.9.0` ([#214](https://github.com/wxt-dev/wxt/pull/214))
- Update Vite dependency range to `^4.0.0 || ^5.0.0-0` ([f1e8084](https://github.com/wxt-dev/wxt/commit/f1e8084be89e512dde441b9197a99183c497f67d))
### 🤖 CI
- Automatically sync GitHub releases with `CHANGELOG.md` on push ([#218](https://github.com/wxt-dev/wxt/pull/218))
### ❤️ Contributors
- Aaron Klinker ([@aaronklinker-st](http://github.com/aaronklinker-st))
## v0.9.0
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.8.7...v0.9.0)
### 🩹 Fixes
- ⚠️ Remove `lib` from `.wxt/tsconfig.json` ([#209](https://github.com/wxt-dev/wxt/pull/209))
### 📖 Documentation
- Fix heading ([345406f](https://github.com/wxt-dev/wxt/commit/345406f))
- Add demo video ([#208](https://github.com/wxt-dev/wxt/pull/208))
### 🏡 Chore
- Fix Svelte and React template READMEs ([#207](https://github.com/wxt-dev/wxt/pull/207))
#### ⚠️ Breaking Changes
- ⚠️ Removed [`"WebWorker"` types](https://www.typescriptlang.org/tsconfig/lib.html) from `.wxt/tsconfig.json` ([#209](https://github.com/wxt-dev/wxt/pull/209)). These types are useful for MV3 projects using a service worker. To add them back to your project, add the following to your project's TSConfig:
```diff
{
"extends": "./.wxt/tsconfig.json",
+ "compilerOptions": {
+ "lib": ["ESNext", "DOM", "WebWorker"]
+ }
}
```
### ❤️ Contributors
- yyyanghj ([@yyyanghj](https://github.com/yyyanghj))
## v0.8.7
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.8.6...v0.8.7)
### 🚀 Enhancements
- `createContentScriptIframe` utility ([#206](https://github.com/wxt-dev/wxt/pull/206))
### 🏡 Chore
- **deps-dev:** Bump happy-dom from 12.4.0 to 12.10.3 ([#194](https://github.com/wxt-dev/wxt/pull/194))
- **deps-dev:** Bump tsx from 3.12.8 to 3.14.0 ([#198](https://github.com/wxt-dev/wxt/pull/198))
- Upgrade types ([f3874da](https://github.com/wxt-dev/wxt/commit/f3874da))
- **deps-dev:** Upgrade `lint-staged` to `^15.0.2` ([5f74a54](https://github.com/wxt-dev/wxt/commit/5f74a54))
- **deps-dev:** Upgrade `execa` to `^8.0.1` ([#200](https://github.com/wxt-dev/wxt/pull/200))
- **deps-dev:** Upgrade `typedoc` to `^0.25.3` ([#201](https://github.com/wxt-dev/wxt/pull/201))
- **deps-dev:** Upgrade `vue` to `3.3.7` ([0b8d101](https://github.com/wxt-dev/wxt/commit/0b8d101))
- **deps-dev:** Upgrade `vitepress` to `1.0.0-rc.24` ([5de18e5](https://github.com/wxt-dev/wxt/commit/5de18e5))
- **deps-dev:** Update `@type/*` packages for demo ([cd4d00e](https://github.com/wxt-dev/wxt/commit/cd4d00e))
- **deps-dev:** Update `sass` to `1.69.5` ([183bb02](https://github.com/wxt-dev/wxt/commit/183bb02))
- Improve prettier git hook ([0f09cbe](https://github.com/wxt-dev/wxt/commit/0f09cbe))
- Run E2E tests in parallel ([#204](https://github.com/wxt-dev/wxt/pull/204))
### 🤖 CI
- Separate validation into multiple jobs ([#203](https://github.com/wxt-dev/wxt/pull/203))
## v0.8.6
[compare changes](https://github.com/wxt-dev/wxt/compare/v0.8.5...v0.8.6)
@@ -33,7 +209,7 @@
### ❤️ Contributors
- Nenad Novaković
- Nenad Novaković ([@dvlden](https://github.com/dvlden))
## v0.8.4
@@ -98,7 +274,7 @@
### ❤️ Contributors
- 渣渣120 <WOSHIZHAZHA120@qq.com>
- 渣渣120 [@WOSHIZHAZHA120](https://github.com/WOSHIZHAZHA120)
## v0.7.5
+1 -1
View File
@@ -86,7 +86,7 @@ Note that templates are hardcoded to a specific version of `wxt` from NPM, they
```diff
"devDependencies": {
"typescript": "^5.1.6",
"typescript": "^5.3.2",
"vite-plugin-solid": "^2.7.0",
- "wxt": "^0.8.0"
+ "wxt": "../.."
+10 -10
View File
@@ -4,18 +4,14 @@
</h1>
<p align="center">
<a href="https://www.npmjs.com/package/wxt" target="_blank">
<img alt="npm" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048">
</a>
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<img alt="npm" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048">
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<img alt="NPM" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048">
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="NPM" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
<!-- Hide code coverage while it's broken -->
<!-- <span> </span>
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank">
<img alt="Codecov" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048">
</a> -->
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a> -->
</p>
<p align="center">
@@ -31,13 +27,17 @@
&bull;
<a href="https://wxt.dev/guide/installation.html" target="_blank">Installation</a>
&bull;
<a href="https://wxt.dev/entrypoints/background.html" target="_blank">Entrypoints</a>
&bull;
<a href="https://wxt.dev/api/config.html" target="_blank">Configuration</a>
&bull;
<a href="https://wxt.dev/examples.html" target="_blank">Examples</a>
</p>
![Example CLI Output](./docs/assets/cli-output.png)
## Demo
https://github.com/wxt-dev/wxt/assets/10101283/07359e53-f491-43b6-8e8f-fae94aec8063
## Quick Start
Bootstrap a new project:
+6 -6
View File
@@ -1,12 +1,12 @@
{
"name": "WXT Demo",
"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": "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",
@@ -23,10 +23,10 @@
"webextension-polyfill": "^0.10.0"
},
"devDependencies": {
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@types/webextension-polyfill": "^0.10.0",
"sass": "^1.64.0",
"@types/react": "^18.2.34",
"@types/react-dom": "^18.2.14",
"@types/webextension-polyfill": "^0.10.5",
"sass": "^1.69.5",
"wxt": "workspace:*"
}
}
+10 -1
View File
@@ -1,3 +1,5 @@
import messages from 'public/_locales/en/messages.json';
export default defineBackground(() => {
console.log(browser.runtime.id);
logId();
@@ -6,6 +8,7 @@ export default defineBackground(() => {
chrome: __IS_CHROME__,
firefox: __IS_FIREFOX__,
manifestVersion: __MANIFEST_VERSION__,
messages,
});
// @ts-expect-error: should only accept entrypoints or public assets
@@ -20,5 +23,11 @@ export default defineBackground(() => {
browser.i18n.getMessage('bye', ['Aaron']);
browser.i18n.getMessage('@@extension_id');
console.log('WXT MODE:', import.meta.env.MODE);
console.log('WXT MODE:', {
MODE: import.meta.env.MODE,
DEV: import.meta.env.DEV,
PROD: import.meta.env.PROD,
});
storage.setItem('session:startTime', Date.now());
});
+5 -1
View File
@@ -6,7 +6,11 @@ export default defineContentScript({
console.log(browser.runtime.id);
logId();
console.log('WXT MODE:', import.meta.env.MODE);
console.log('WXT MODE:', {
MODE: import.meta.env.MODE,
DEV: import.meta.env.DEV,
PROD: import.meta.env.PROD,
});
const n = (Math.random() * 100).toFixed(1);
ctx.setInterval(() => {
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Iframe Example</title>
</head>
<body>
<p>Hello iframe page!</p>
<script type="module" src="./main.ts"></script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
console.log('iframe 2');
+13
View File
@@ -0,0 +1,13 @@
export default defineContentScript({
matches: ['*://*.google.com/*'],
main(ctx) {
const ui = createContentScriptIframe(ctx, {
page: '/iframe-src.html',
type: 'overlay',
anchor: 'form[action="/search"]',
});
ui.mount();
console.log('Mounted iframe');
},
});
+5 -1
View File
@@ -4,4 +4,8 @@ console.log(browser.runtime.id);
logId();
console.log(2);
console.log('WXT MODE:', import.meta.env.MODE);
console.log('WXT MODE:', {
MODE: import.meta.env.MODE,
DEV: import.meta.env.DEV,
PROD: import.meta.env.PROD,
});
+10
View File
@@ -3,6 +3,16 @@ import { defineConfig } from 'wxt';
export default defineConfig({
srcDir: 'src',
manifest: {
permissions: ['storage'],
default_locale: 'en',
web_accessible_resources: [
{
resources: ['/iframe-src.html'],
matches: ['*://*.google.com/*'],
},
],
},
alias: {
public: 'src/public',
},
});
+2
View File
@@ -0,0 +1,2 @@
FROM lipanski/docker-static-website:latest
COPY dist .
+42 -17
View File
@@ -8,14 +8,22 @@ import useListExtensionDetails, {
const chromeExtensionIds = [
'ocfdgncpifmegplaglcnglhioflaimkd', // GitHub: Better Line Counts
'mgmdkjcljneegjfajchedjpdhbadklcf', // Anime Skip Player
'bfbnagnphiehemkdgmmficmjfddgfhpl', // UltraWideo
'elfaihghhjjoknimpccccmkioofjjfkf', // StayFree - Website Blocker & Web Analytics
];
const { data } = useListExtensionDetails(chromeExtensionIds);
const sortedExtensions = computed(() =>
!data.value
? undefined
: [...data.value].sort((l, r) => r.weeklyActiveUsers - l.weeklyActiveUsers),
);
const sortedExtensions = computed(() => {
if (!data.value?.length) return [];
return [...data.value]
.map((item) => ({
...item,
// Sort based on the user count weighted by the rating
sortKey: ((item.rating ?? 5) / 5) * item.weeklyActiveUsers,
}))
.sort((l, r) => r.sortKey - l.sortKey);
});
function getStoreUrl(extension: ChromeExtension) {
const url = new URL(extension.storeUrl);
@@ -27,32 +35,41 @@ function getStoreUrl(extension: ChromeExtension) {
<template>
<section class="vp-doc">
<div class="container">
<h2>Who's Using WXT?</h2>
<h2 id="whos-using-wxt">Who's Using WXT?</h2>
<p>
Battle tested and ready for production. Explore chrome extensions made
with WXT.
</p>
<ul>
<li v-for="extension of sortedExtensions">
<li
v-for="extension of sortedExtensions"
:key="extension.id"
class="relative"
>
<img
:src="extension.iconUrl"
:alt="`${extension.name} icon`"
referrerpolicy="no-referrer"
/>
<div>
<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>
<p class="user-count">
{{ extension.weeklyActiveUsers.toLocaleString() }} users
</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">
@@ -121,7 +138,8 @@ li {
background-color: var(--vp-c-bg-soft);
border-radius: 12px;
flex: 1;
gap: 16px;
gap: 24px;
align-items: center;
}
.centered {
@@ -136,9 +154,10 @@ li .description {
}
li .user-count {
opacity: 70%;
text-align: right;
width: 100%;
font-size: small;
position: absolute;
bottom: 12px;
right: 16px;
}
li a {
@@ -157,8 +176,6 @@ li a:hover {
li div {
flex: 1;
display: flex;
flex-direction: column;
}
li .description {
@@ -167,10 +184,18 @@ li .description {
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
flex-grow: 1;
margin-bottom: 16px;
}
li .extension-name {
font-size: large;
}
.pr {
opacity: 70%;
}
.relative {
position: relative;
}
</style>
@@ -7,6 +7,7 @@ export interface ChromeExtension {
weeklyActiveUsers: number;
shortDescription: string;
storeUrl: string;
rating: number | undefined;
}
const operationName = 'WxtDocsUsedBy';
@@ -18,6 +19,7 @@ const query = `query ${operationName}($ids:[String!]!) {
weeklyActiveUsers
shortDescription
storeUrl
rating
}
}`;
+2
View File
@@ -71,6 +71,7 @@ export default defineConfig({
nav: [
{ text: 'Guide', link: '/guide/installation.md' },
{ text: 'Entrypoints', link: '/entrypoints/background.md' },
{ text: 'Examples', link: '/examples.md' },
{ text: 'API', link: '/api/cli.md' },
],
@@ -89,6 +90,7 @@ export default defineConfig({
{ text: 'Auto-imports', link: '/guide/auto-imports.md' },
{ text: 'Manifest.json', link: '/guide/manifest.md' },
{ text: 'Extension APIs', link: '/guide/extension-apis.md' },
{ text: 'Storage', link: '/guide/storage.md' },
{ text: 'Remote Code', link: '/guide/remote-code.md' },
{ text: 'Development', link: '/guide/development.md' },
{ text: 'Testing', link: '/guide/testing.md' },
+45 -2
View File
@@ -153,6 +153,8 @@ const anchor = document.querySelector('#anchor-selector');
anchor.append(ui);
```
You can try out [`createContentScriptIframe`](#createcontentscriptiframe) as an alternative solution
:::
### Usage
@@ -162,7 +164,7 @@ To use `createContentScriptUi`, follow these steps:
1. Import your CSS file at the top of your content script
2. Set `cssInjectionMode: "ui"` inside `defineContentScript`
3. Call `createContentScriptUi`
4. Call `ui.mount()` to add the UI to the webpage
4. Call `mount` to add the UI to the webpage
Here's a basic example:
@@ -185,7 +187,7 @@ export default defineContentScript({
},
});
// Yoy must call `mount` to add the UI to the page.
// You must call `mount` to add the UI to the page.
ui.mount();
},
});
@@ -290,3 +292,44 @@ There are 3 types of UI's you can mount.
Because the overlay UI type results in a 0px by 0px container being added to the webpage, the `alignment` option allows you to configure which corner of your UI is aligned with the 0x0 element.
> TODO: Add visualization of the different alignments.
## IFrame
WXT provides a utility function, `createContentScriptIframe` to simplify mounting a UI from a content script. It creates an iframe to an unlisted HTML page. Unlike `createContentScriptUi`, this API support HMR.
`createContentScriptIframe` requires a `ContentScriptContext` so that when the context is invalidated, the UI is automatically removed from the webpage.
### Usage
To use `createContentScriptIframe`, follow these steps:
1. Create an unlisted HTML page that will be loaded into your iframe
1. Add unlisted page to the manifest's `web_accessible_resouces`
1. Call `createContentScriptUi`
1. Call `mount` to add the UI to the webpage
Here's a basic example:
```ts
export default defineContentScript({
// ...
async main(ctx) {
const ui = await createContentScriptIframe(ctx, {
page: '/your-unlisted-page.html',
type: 'inline',
anchor: '#some-element',
append: 'after',
});
// You must call `mount` to add the UI to the page.
ui.mount();
},
});
```
The options, other than `page`, are the same as [`createContentScriptUi`](#anchor).
### `page`
The HTML page you want to load inside the iframe. This string will be passed into `browser.runtime.getURL` to resolve the full path of to your HTML page.
+27
View File
@@ -0,0 +1,27 @@
# Examples
Simple walkthroughs to accomplish common tasks or patterns with WXT.
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
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();
})
</script>
<ul>
<li v-if="examples == null">
Loading...
</li>
<template v-else>
<li v-for="example of examples">
<a :href="example.url" target="_blank">{{ example.name }}</a>
</li>
</template>
</ul>
> Full code available at [`wxt-dev/wxt-examples`](https://github.com/wxt-dev/wxt-examples)
+3 -3
View File
@@ -27,9 +27,9 @@ Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework),
| Reload Content Scripts on Change | ✅ | 🟡 Reloads entire extension |
| Reload Background on Change | 🟡 Reloads entire extension | 🟡 Reloads entire extension |
| <strong style="opacity: 50%">Built-in Utils</strong> | | |
| Storage | | ✅ |
| Messaging | | ✅ |
| Content Script UI | | ✅ |
| Storage | | ✅ |
| Messaging | 🟡 Coming soon | ✅ |
| Content Script UI | | ✅ |
## Dev Mode
+1 -1
View File
@@ -2,7 +2,7 @@
The manifest.json is generated at build-time based on files in your `entrypoints` directory and your `wxt.config.ts`.
## Confiuration
## Configuration
While entrypoints are generated and added to the manifest at build-time, you can customize or add to your `manifest.json` in the config file.
+36
View File
@@ -0,0 +1,36 @@
# Storage API
WXT's storage API is powered by `unstorage`. See [their docs](https://unstorage.unjs.io/usage#usage-1) for more details.
## Overview
:::code-group
```ts [native]
const { installDate } = await browser.storage.local.get('installDate');
await browser.storage.local.set({ key: 'value' });
```
```ts [wxt/browser]
const installDate = await storage.get('local:installDate');
await storage.setItem('key', 'value');
```
:::
Use the `"local:"`, `"session:"`, `"sync:"`, and `"managed:"` prefixes to specify which storage area to use.
## Customization
WXT also provides a driver for `unstorage`. To customize the `storage` object's setup, like removing the prefixes and using a single storage area, you can create your own storage:
```ts
// storage.ts
export default createStorage({
driver: webExtensionDriver({ storageArea: 'local' }),
});
```
:::note
`wxt/browser` re-exports all of `unstorage`, which is where `createStorage` comes from.
:::
+3 -2
View File
@@ -76,8 +76,9 @@ features:
<p>
WXT's simplifies the chrome extension development process by providing tools for zipping and publishing, the best-in-class dev mode, an opinionated project structure, and more. Iterate faster, develop features not build scripts, and use everything the JS ecosystem has to offer.
</p>
<div style="margin: auto; width: 100%; max-width: 700px; text-align: center">
<img src="./assets/cli-output.png" alt="Screenshot of WXT's build output" />
<div style="margin: auto; width: 100%; max-width: 900px; text-align: center">
<video src="https://github.com/wxt-dev/wxt/assets/10101283/b32e6766-ec11-45a4-9677-226ee4718e1c" controls></video>
<br />
<small>
And who doesn't appreciate a beautiful CLI?
</small>
+1
View File
@@ -4,6 +4,7 @@
"../src/client",
"../src/browser.ts",
"../src/sandbox",
"../src/storage.ts",
"../src/testing"
],
"plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme"],
+12
View File
@@ -18,12 +18,24 @@ describe('Auto Imports', () => {
declare global {
const ContentScriptContext: typeof import('wxt/client')['ContentScriptContext']
const browser: typeof import('wxt/browser')['browser']
const builtinDrivers: typeof import('wxt/storage')['builtinDrivers']
const createContentScriptIframe: typeof import('wxt/client')['createContentScriptIframe']
const createContentScriptUi: typeof import('wxt/client')['createContentScriptUi']
const createStorage: typeof import('wxt/storage')['createStorage']
const defineBackground: typeof import('wxt/client')['defineBackground']
const defineConfig: typeof import('wxt')['defineConfig']
const defineContentScript: typeof import('wxt/client')['defineContentScript']
const defineDriver: typeof import('wxt/storage')['defineDriver']
const defineUnlistedScript: typeof import('wxt/sandbox')['defineUnlistedScript']
const fakeBrowser: typeof import('wxt/testing')['fakeBrowser']
const joinKeys: typeof import('wxt/storage')['joinKeys']
const normalizeBaseKey: typeof import('wxt/storage')['normalizeBaseKey']
const normalizeKey: typeof import('wxt/storage')['normalizeKey']
const prefixStorage: typeof import('wxt/storage')['prefixStorage']
const restoreSnapshot: typeof import('wxt/storage')['restoreSnapshot']
const snapshot: typeof import('wxt/storage')['snapshot']
const storage: typeof import('wxt/storage')['storage']
const webExtensionDriver: typeof import('wxt/storage')['webExtensionDriver']
}
"
`);
+62 -24
View File
@@ -104,26 +104,64 @@ describe('Manifest Content', () => {
})
`;
it('should include a background script for mv2', async () => {
it.each(['chrome', 'safari'])(
'should include scripts and persistent for %s mv2',
async (browser) => {
const project = new TestProject();
project.addFile('entrypoints/background.ts', backgroundContent);
await project.build({ browser, manifestVersion: 2 });
const manifest = await project.getOutputManifest(
`.output/${browser}-mv2/manifest.json`,
);
expect(manifest.background).toEqual({
persistent: true,
scripts: ['background.js'],
});
},
);
it.each(['chrome', 'safari'])(
'should include a service worker and type for %s mv3',
async (browser) => {
const project = new TestProject();
project.addFile('entrypoints/background.ts', backgroundContent);
await project.build({ browser, manifestVersion: 3 });
const manifest = await project.getOutputManifest(
`.output/${browser}-mv3/manifest.json`,
);
expect(manifest.background).toEqual({
type: 'module',
service_worker: 'background.js',
});
},
);
it('should include a background script and type for firefox mv3', async () => {
const project = new TestProject();
project.addFile('entrypoints/background.ts', backgroundContent);
await project.build();
const manifest = await project.getOutputManifest();
await project.build({ browser: 'firefox', manifestVersion: 3 });
const manifest = await project.getOutputManifest(
'.output/firefox-mv3/manifest.json',
);
expect(manifest.background).toEqual({
type: 'module',
service_worker: 'background.js',
scripts: ['background.js'],
});
});
it('should include a options_ui and browser_style for firefox', async () => {
it('should include a background script and persistent for firefox mv2', async () => {
const project = new TestProject();
project.addFile('entrypoints/background.ts', backgroundContent);
await project.build({ manifestVersion: 2 });
await project.build({ browser: 'firefox', manifestVersion: 2 });
const manifest = await project.getOutputManifest(
'.output/chrome-mv2/manifest.json',
'.output/firefox-mv2/manifest.json',
);
expect(manifest.background).toEqual({
@@ -137,22 +175,22 @@ describe('Manifest Content', () => {
it('should auto-discover icons with the correct name', async () => {
const project = new TestProject();
project.addFile('public/icon-16.png');
project.addFile('public/icon/32.jpeg');
project.addFile('public/icon@48w.jpg');
project.addFile('public/icon-64x64.gif');
project.addFile('public/icon@96.bmp');
project.addFile('public/icon/128x128.ico');
project.addFile('public/icon/32.png');
project.addFile('public/icon@48w.png');
project.addFile('public/icon-64x64.png');
project.addFile('public/icon@96.png');
project.addFile('public/icons/128x128.png');
await project.build();
const manifest = await project.getOutputManifest();
expect(manifest.icons).toEqual({
'16': 'icon-16.png',
'32': 'icon/32.jpeg',
'48': 'icon@48w.jpg',
'64': 'icon-64x64.gif',
'96': 'icon@96.bmp',
'128': 'icon/128x128.ico',
'32': 'icon/32.png',
'48': 'icon@48w.png',
'64': 'icon-64x64.png',
'96': 'icon@96.png',
'128': 'icons/128x128.png',
});
});
@@ -320,7 +358,7 @@ describe('Manifest Content', () => {
project.addFile(
'entrypoints/content/index.ts',
`import "./style.css";
export default defineContentScript({
matches: ["https://*.google.com/*"],
@@ -346,7 +384,7 @@ describe('Manifest Content', () => {
project.addFile(
'entrypoints/content/index.ts',
`import "./style.css";
export default defineContentScript({
matches: ["https://*.google.com/*"],
cssInjectionMode: "manifest",
@@ -373,7 +411,7 @@ describe('Manifest Content', () => {
project.addFile(
'entrypoints/content/index.ts',
`import "./style.css";
export default defineContentScript({
matches: ["https://*.google.com/*"],
cssInjectionMode: "manual",
@@ -400,7 +438,7 @@ describe('Manifest Content', () => {
project.addFile(
'entrypoints/content/index.ts',
`import "./style.css";
export default defineContentScript({
matches: ["https://*.google.com/*"],
cssInjectionMode: "ui",
@@ -429,7 +467,7 @@ describe('Manifest Content', () => {
project.addFile(
'entrypoints/content/index.ts',
`import "./style.css";
export default defineContentScript({
matches: ["https://*.google.com/*"],
cssInjectionMode: "ui",
@@ -459,7 +497,7 @@ describe('Manifest Content', () => {
project.addFile(
'entrypoints/content/index.ts',
`import "./style.css";
export default defineContentScript({
matches: ["https://*.google.com/*"],
cssInjectionMode: "ui",
@@ -489,7 +527,7 @@ describe('Manifest Content', () => {
project.addFile(
'wxt.config.ts',
`import { defineConfig } from 'wxt';
export default defineConfig({
transformManifest(manifest) {
manifest.author = "Custom Author"
+59 -12
View File
@@ -44,7 +44,7 @@ describe('TypeScript Project', () => {
import \\"wxt/browser\\";
declare module \\"wxt/browser\\" {
type PublicPath =
export type PublicPath =
| \\"/options.html\\"
| \\"/popup.html\\"
| \\"/sandbox.html\\"
@@ -120,7 +120,7 @@ describe('TypeScript Project', () => {
/**
* The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even unlocalized extensions can use this message.
Note: You can't use this message in a manifest file.
*
*
* \\"<browser.runtime.id>\\"
*/
getMessage(
@@ -130,7 +130,7 @@ describe('TypeScript Project', () => {
): string;
/**
*
*
*
* \\"<browser.i18n.getUiLocale()>\\"
*/
getMessage(
@@ -140,7 +140,7 @@ describe('TypeScript Project', () => {
): string;
/**
* The text direction for the current locale, either \\"ltr\\" for left-to-right languages such as English or \\"rtl\\" for right-to-left languages such as Japanese.
*
*
* \\"<ltr|rtl>\\"
*/
getMessage(
@@ -150,7 +150,7 @@ describe('TypeScript Project', () => {
): string;
/**
* If the @@bidi_dir is \\"ltr\\", then this is \\"rtl\\"; otherwise, it's \\"ltr\\".
*
*
* \\"<rtl|ltr>\\"
*/
getMessage(
@@ -160,7 +160,7 @@ describe('TypeScript Project', () => {
): string;
/**
* If the @@bidi_dir is \\"ltr\\", then this is \\"left\\"; otherwise, it's \\"right\\".
*
*
* \\"<left|right>\\"
*/
getMessage(
@@ -170,7 +170,7 @@ describe('TypeScript Project', () => {
): string;
/**
* If the @@bidi_dir is \\"ltr\\", then this is \\"right\\"; otherwise, it's \\"left\\".
*
*
* \\"<right|left>\\"
*/
getMessage(
@@ -180,7 +180,7 @@ describe('TypeScript Project', () => {
): string;
/**
* Ask for the user's name
*
*
* \\"What's your name?\\"
*/
getMessage(
@@ -190,7 +190,7 @@ describe('TypeScript Project', () => {
): string;
/**
* Greet the user
*
*
* \\"Hello, $USER$\\"
*/
getMessage(
@@ -200,7 +200,7 @@ describe('TypeScript Project', () => {
): string;
/**
* Say goodbye to the user
*
*
* \\"Goodbye, $USER$. Come back to $OUR_SITE$ soon!\\"
*/
getMessage(
@@ -252,7 +252,6 @@ describe('TypeScript Project', () => {
\\"forceConsistentCasingInFileNames\\": true,
\\"resolveJsonModule\\": true,
\\"strict\\": true,
\\"lib\\": [\\"DOM\\", \\"WebWorker\\"],
\\"skipLibCheck\\": true,
\\"paths\\": {
\\"@\\": [\\"..\\"],
@@ -296,7 +295,6 @@ describe('TypeScript Project', () => {
\\"forceConsistentCasingInFileNames\\": true,
\\"resolveJsonModule\\": true,
\\"strict\\": true,
\\"lib\\": [\\"DOM\\", \\"WebWorker\\"],
\\"skipLibCheck\\": true,
\\"paths\\": {
\\"@\\": [\\"../src\\"],
@@ -317,4 +315,53 @@ describe('TypeScript Project', () => {
}"
`);
});
it('should add additional path aliases listed in the alias config, preventing defaults from being overridden', async () => {
const project = new TestProject();
project.setConfigFileConfig({
srcDir: 'src',
alias: {
example: 'example',
'@': 'ignored-path',
},
});
await project.build();
const output = await project.serializeFile('.wxt/tsconfig.json');
expect(output).toMatchInlineSnapshot(`
".wxt/tsconfig.json
----------------------------------------
{
\\"compilerOptions\\": {
\\"target\\": \\"ESNext\\",
\\"module\\": \\"ESNext\\",
\\"moduleResolution\\": \\"Bundler\\",
\\"noEmit\\": true,
\\"esModuleInterop\\": true,
\\"forceConsistentCasingInFileNames\\": true,
\\"resolveJsonModule\\": true,
\\"strict\\": true,
\\"skipLibCheck\\": true,
\\"paths\\": {
\\"example\\": [\\"../example\\"],
\\"example/*\\": [\\"../example/*\\"],
\\"@\\": [\\"../src\\"],
\\"@/*\\": [\\"../src/*\\"],
\\"~\\": [\\"../src\\"],
\\"~/*\\": [\\"../src/*\\"],
\\"@@\\": [\\"..\\"],
\\"@@/*\\": [\\"../*\\"],
\\"~~\\": [\\"..\\"],
\\"~~/*\\": [\\"../*\\"]
}
},
\\"include\\": [
\\"../**/*\\",
\\"./wxt.d.ts\\"
],
\\"exclude\\": [\\"../.output\\"]
}"
`);
});
});
+21
View File
@@ -1,5 +1,6 @@
import { describe, it, expect } from 'vitest';
import { TestProject } from '../utils';
import { InlineConfig } from '~/types';
describe('User Config', () => {
// Root directory is tested with all tests.
@@ -85,4 +86,24 @@ describe('User Config', () => {
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"example_customization\\":[\\"production\\",\\"chrome\\",\\"3\\",\\"build\\"]}"
`);
});
it('should exclude the polyfill when the experimental setting is set to false', async () => {
const buildBackground = async (config?: InlineConfig) => {
const background = `export default defineBackground(() => console.log(browser.runtime.id));`;
const projectWithPolyfill = new TestProject();
projectWithPolyfill.addFile('entrypoints/background.ts', background);
await projectWithPolyfill.build(config);
return await projectWithPolyfill.serializeFile(
'.output/chrome-mv3/background.js',
);
};
const withPolyfill = await buildBackground();
const withoutPolyfill = await buildBackground({
experimental: {
includeBrowserPolyfill: false,
},
});
expect(withoutPolyfill).not.toBe(withPolyfill);
});
});
+32 -27
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.8.6",
"version": "0.10.3",
"description": "Next gen framework for developing web extensions",
"engines": {
"node": ">=18",
@@ -56,6 +56,11 @@
"require": "./dist/testing.cjs",
"import": "./dist/testing.js",
"types": "./dist/testing.d.ts"
},
"./storage": {
"require": "./dist/storage.cjs",
"import": "./dist/storage.js",
"types": "./dist/storage.d.ts"
}
},
"scripts": {
@@ -75,46 +80,50 @@
"sync-releases": "pnpx changelogen@latest gh release"
},
"dependencies": {
"@types/webextension-polyfill": "^0.10.1",
"@webext-core/fake-browser": "^1.2.2",
"@types/webextension-polyfill": "^0.10.5",
"@webext-core/fake-browser": "^1.3.1",
"@webext-core/isolated-element": "^1.0.4",
"@webext-core/match-patterns": "^1.0.2",
"async-mutex": "^0.4.0",
"c12": "^1.4.2",
"c12": "^1.5.1",
"cac": "^6.7.14",
"consola": "^3.2.3",
"esbuild": "^0.19.4",
"defu": "^6.1.3",
"esbuild": "^0.19.5",
"fast-glob": "^3.3.1",
"filesize": "^10.0.8",
"fs-extra": "^11.1.1",
"get-port": "^7.0.0",
"giget": "^1.1.2",
"immer": "^10.0.2",
"giget": "^1.1.3",
"immer": "^10.0.3",
"is-wsl": "^3.0.0",
"jiti": "^1.19.1",
"jiti": "^1.21.0",
"json5": "^2.2.3",
"linkedom": "^0.15.1",
"linkedom": "^0.16.1",
"minimatch": "^9.0.3",
"normalize-path": "^3.0.0",
"ora": "^7.0.1",
"picocolors": "^1.0.0",
"prompts": "^2.4.2",
"rollup-plugin-visualizer": "^5.9.2",
"unimport": "^3.1.0",
"vite": "^4.4.11",
"unimport": "^3.4.0",
"unstorage": "^1.9.0",
"vite": "^4.0.0 || ^5.0.0-0",
"web-ext-run": "^0.1.0",
"webextension-polyfill": "^0.10.0",
"zip-dir": "^2.0.0"
},
"devDependencies": {
"@faker-js/faker": "^8.0.2",
"@types/fs-extra": "^11.0.1",
"@types/lodash.merge": "^4.6.7",
"@types/node": "^20.8.6",
"@types/prompts": "^2.4.4",
"@faker-js/faker": "^8.2.0",
"@types/fs-extra": "^11.0.3",
"@types/lodash.merge": "^4.6.8",
"@types/node": "^20.8.10",
"@types/normalize-path": "^3.0.2",
"@types/prompts": "^2.4.7",
"@vitest/coverage-v8": "^0.34.6",
"execa": "^7.2.0",
"execa": "^8.0.1",
"happy-dom": "^12.4.0",
"lint-staged": "^14.0.0",
"lint-staged": "^15.0.2",
"lodash.merge": "^4.6.2",
"npm-run-all": "^4.1.5",
"p-map": "^6.0.0",
@@ -122,25 +131,21 @@
"simple-git-hooks": "^2.9.0",
"tsup": "^7.2.0",
"tsx": "^3.12.7",
"typedoc": "^0.25.2",
"typedoc": "^0.25.4",
"typedoc-plugin-markdown": "4.0.0-next.23",
"typedoc-vitepress-theme": "1.0.0-next.3",
"typescript": "^5.2.2",
"vitepress": "1.0.0-rc.10",
"typescript": "^5.3.2",
"vitepress": "1.0.0-rc.24",
"vitest": "^0.34.6",
"vitest-mock-extended": "^1.3.1",
"vue": "^3.3.4",
"webextension-polyfill": "^0.10.0"
},
"peerDependencies": {
"webextension-polyfill": ">=0.10.0"
"vue": "^3.3.7"
},
"packageManager": "pnpm@8.6.3",
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": "prettier --write"
"*": "prettier --ignore-unknown --write"
},
"changelog": {
"excludeAuthors": [
+971 -567
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -57,6 +57,7 @@ const config: tsup.Options[] = [
entry: {
index: 'src/index.ts',
testing: 'src/testing/index.ts',
storage: 'src/storage.ts',
},
format: ['cjs', 'esm'],
clean: true,
+70
View File
@@ -0,0 +1,70 @@
import { fakeBrowser } from '@webext-core/fake-browser';
import { describe, it, expect, beforeEach } from 'vitest';
import { browser } from '~/browser';
import { storage } from '~/storage';
describe('Storage Utils', () => {
beforeEach(() => {
fakeBrowser.reset();
});
describe.each(['local', 'sync', 'managed', 'session'] as const)(
'storage - %s',
(storageArea) => {
describe('getItem', () => {
it('should return the value from the correct storage area', async () => {
const expected = 123;
await fakeBrowser.storage[storageArea].set({ count: expected });
expect(await storage.getItem(`${storageArea}:count`)).toBe(expected);
});
it("should return null if the value doesn't exist", async () => {
expect(await storage.getItem(`${storageArea}:count`)).toBeNull();
});
});
describe('getItems', () => {
it('should return an array of values in the same order as the keys passed in', async () => {
const expected = [
{ key: `${storageArea}:count`, value: 234 },
{ key: `${storageArea}:installDate`, value: null },
];
const keys = expected.map((item) => item.key);
await fakeBrowser.storage[storageArea].set({
count: expected[0].value,
});
const actual = await storage.getItems(keys);
expect(actual).toEqual(expected);
});
});
describe('setItem', () => {
it('should set the value in the correct storage area', () => {});
it.each([undefined, null])(
'should remove the item from storage when setting the value to %s',
async (value) => {
await fakeBrowser.storage[storageArea].set({ count: 345 });
await storage.setItem(`${storageArea}:count`, value as null);
// For some reason storage sets the value to "null" instead of deleting it. So using
// fakeBrowser during the expect fails. Using storage works. I've confirmed that this
// doesn't happen in a real extension environment.
expect(await storage.getItem(`${storageArea}:count`)).toBeNull();
},
);
});
describe('removeItem', () => {
it('should remove the key from storage', async () => {
await fakeBrowser.storage[storageArea].set({ count: 456 });
await storage.removeItem(`${storageArea}:count`);
expect(await browser.storage[storageArea].get()).toEqual({});
});
});
},
);
});
@@ -0,0 +1,214 @@
/** @vitest-environment happy-dom */
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createContentScriptIframe } from '~/client/content-scripts/content-script-iframe';
import { ContentScriptContext } from '~/client/content-scripts/content-script-context';
const createCtx = () => new ContentScriptContext('test');
const fetch = vi.fn();
describe('createContentScriptIframe', () => {
beforeEach(() => {
document.body.innerHTML = `
<div id="parent">
<p id="one">one</p>
<p id="two">two</p>
<p id="three"></p>
</div>
`;
window.fetch = fetch;
fetch.mockResolvedValue({ text: () => Promise.resolve('') });
});
describe('mount', () => {
describe('append option', () => {
it.each([undefined, 'last' as const])(
'should append the element as the last child to the anchor when append=%s',
async (append) => {
const ui = await createContentScriptIframe(createCtx(), {
page: '/test.html',
type: 'inline',
anchor: '#parent',
append,
});
ui.mount();
expect(
document.querySelector('#parent > :nth-child(4)')?.tagName,
).toEqual('DIV');
},
);
it('should append the element as the first child to the anchor when append=first', async () => {
const ui = await createContentScriptIframe(createCtx(), {
page: '/test.html',
type: 'inline',
anchor: '#parent',
append: 'first',
});
ui.mount();
expect(
document.querySelector('#parent > :nth-child(1)')?.tagName,
).toEqual('DIV');
});
it('should append the element normally when append=first but there are no other children', async () => {
const ui = await createContentScriptIframe(createCtx(), {
page: '/test.html',
type: 'inline',
anchor: '#three',
append: 'first',
});
ui.mount();
expect(
document.querySelector('#three > :nth-child(1)')?.tagName,
).toEqual('DIV');
});
it('should replace the anchor with the element when append=replace', async () => {
const ui = await createContentScriptIframe(createCtx(), {
page: '/test.html',
type: 'inline',
anchor: '#two',
append: 'replace',
});
ui.mount();
expect(
document.querySelector('#parent > :nth-child(2)')?.tagName,
).toEqual('DIV');
});
it('should append the element after the anchor when append=before', async () => {
const ui = await createContentScriptIframe(createCtx(), {
page: '/test.html',
type: 'inline',
anchor: '#two',
append: 'before',
});
ui.mount();
expect(
document.querySelector('#parent > :nth-child(2)')?.tagName,
).toEqual('DIV');
});
it('should append the element after the anchor when append=after', async () => {
const ui = await createContentScriptIframe(createCtx(), {
page: '/test.html',
type: 'inline',
anchor: '#two',
append: 'after',
});
ui.mount();
// Happy DOM doesn't work in this case, so we just make sure the element is added.
// expect(
// document.querySelector('#parent > :nth-child(3)')?.tagName,
// ).toEqual('DIV');
expect(document.querySelector('DIV')).toBeDefined();
});
it('should apply a custom function', async () => {
const ui = await createContentScriptIframe(createCtx(), {
page: '/test.html',
type: 'inline',
anchor: '#three',
append: (anchor, ui) => anchor.replaceWith(ui),
});
ui.mount();
expect(
document.querySelector('#parent > :nth-child(3)')?.tagName,
).toEqual('DIV');
});
});
it('should default the anchor to the body when unset', async () => {
const ui = await createContentScriptIframe(createCtx(), {
page: '/test.html',
type: 'inline',
});
ui.mount();
expect(document.querySelector('body > :nth-child(2)')?.tagName).toEqual(
'DIV',
);
});
it.each(['#four', () => document.querySelector('#four')])(
"should throw an error if the anchor doesn't exist",
async (anchor) => {
const ui = await createContentScriptIframe(createCtx(), {
page: '/test.html',
type: 'inline',
anchor,
});
expect(ui.mount).toThrow(
'Failed to mount content script UI: could not find anchor element',
);
},
);
});
describe('remove', () => {
it("should not fail if the ui hasn't been mounted", async () => {
const ui = await createContentScriptIframe(createCtx(), {
page: '/test.html',
type: 'inline',
});
ui.remove();
});
it('should automatically remove the UI when the context is invalidated', async () => {
const ctx = createCtx();
const ui = await createContentScriptIframe(ctx, {
page: '/test.html',
type: 'inline',
});
ui.mount();
expect(document.querySelector('.wxt-iframe-wrapper')).toBeDefined();
ctx.abort();
expect(document.querySelector('.wxt-iframe-wrapper')).toBeNull();
});
});
describe('type', () => {
it.each(['inline', 'overlay', 'modal'] as const)(
'should render type=%s',
async (type) => {
const ui = await createContentScriptIframe(createCtx(), {
page: '/test.html',
type,
});
ui.mount();
expect(document.querySelector('DIV')).toBeDefined();
},
);
});
describe('anchor', () => {
it.each([
() => '#two',
() => () => '#two',
() => document.querySelector('#two'),
() => () => document.querySelector('#two'),
])('should render anchor=%s', async (getAnchor) => {
const ui = await createContentScriptIframe(createCtx(), {
page: '/test.html',
type: 'inline',
anchor: getAnchor(),
});
ui.mount();
expect(document.querySelector('DIV')).toBeDefined();
});
});
});
@@ -266,7 +266,7 @@ describe('createContentScriptUi', () => {
});
expect(ui.mount).toThrow(
'Failed to mount content script ui: could not find anchor element',
'Failed to mount content script UI: could not find anchor element',
);
},
);
@@ -0,0 +1,83 @@
import browser from 'webextension-polyfill';
import {
ContentScriptAnchoredOptions,
ContentScriptPositioningOptions,
applyContentScriptUiPosition,
mountContentScriptUiRoot,
} from '../utils/content-script-ui';
import { ContentScriptContext } from './content-script-context';
/**
* Utility for mounting a content script UI inside an iframe. Automatically removed from the DOM
* when the content script's context is invalidated.
*
* See https://wxt.dev/entrypoints/content-scripts.html#iframe for full documentation.
*
* @example
* export default defineContentScript({
* matches: ["*://*.google.com/*"],
*
* main(ctx) {
* const ui = await createContentScriptIframe(ctx, {
* page: "/content-script-overlay.html",
* type: "modal",
* })
* ui.mount();
* }
* })
*/
export function createContentScriptIframe(
ctx: ContentScriptContext,
options: ContentScriptIframeOptions,
): ContentScriptIframe {
const wrapper = document.createElement('div');
wrapper.classList.add('wxt-iframe-wrapper');
const iframe = document.createElement('iframe');
iframe.src = browser.runtime.getURL(options.page);
wrapper.appendChild(iframe);
const mount = () => {
applyContentScriptUiPosition(wrapper, iframe, options);
mountContentScriptUiRoot(wrapper, options);
};
const remove = () => {
wrapper.remove();
};
ctx.onInvalidated(remove);
return {
iframe,
wrapper,
mount,
remove,
};
}
export interface ContentScriptIframe {
/**
* The iframe added to the DOM.
*/
iframe: HTMLIFrameElement;
/**
* A wrapper div that assists in positioning.
*/
wrapper: HTMLDivElement;
/**
* Function that mounts or remounts the UI on the page.
*/
mount: () => void;
/**
* Function that removes the UI from the webpage.
*/
remove: () => void;
}
export type ContentScriptIframeOptions = ContentScriptPositioningOptions &
ContentScriptAnchoredOptions & {
/**
* The path to the unlisted HTML file to display in the iframe.
*/
page: import('wxt/browser').PublicPath;
};
+31 -159
View File
@@ -2,6 +2,12 @@ import { createIsolatedElement } from '@webext-core/isolated-element';
import { browser } from '~/browser';
import { logger } from '~/client/utils/logger';
import { ContentScriptContext } from './content-script-context';
import {
ContentScriptAnchoredOptions,
ContentScriptPositioningOptions,
applyContentScriptUiPosition,
mountContentScriptUiRoot,
} from '../utils/content-script-ui';
/**
* Utility for mounting content script UI's with isolated styles. Automatically removed from the DOM
@@ -52,83 +58,19 @@ export async function createContentScriptUi<TApp>(
mode: 'open',
});
const getAnchor = (): Element | undefined => {
if (options.anchor == null) return document.body;
let resolved =
typeof options.anchor === 'function' ? options.anchor() : options.anchor;
if (typeof resolved === 'string')
return document.querySelector<Element>(resolved) ?? undefined;
return resolved ?? undefined;
};
let mounted: TApp;
const mount = () => {
const anchor = getAnchor();
if (anchor == null)
throw Error(
'Failed to mount content script ui: could not find anchor element',
);
// Mount UI inside shadow root
mounted = options.mount(uiContainer);
// Add shadow root element to DOM
switch (options.append) {
case undefined:
case 'last':
anchor.append(shadowHost);
break;
case 'first':
if (anchor.firstChild) {
anchor.insertBefore(shadowHost, anchor.firstChild);
} else {
anchor.append(shadowHost);
}
break;
case 'replace':
anchor.replaceWith(shadowHost);
break;
case 'after':
anchor.replaceWith(anchor, shadowHost);
break;
case 'before':
anchor.replaceWith(shadowHost, anchor);
break;
default:
options.append(anchor, shadowHost);
break;
}
// Apply types
if (options.type !== 'inline') {
if (options.zIndex != null)
shadowHost.style.zIndex = String(options.zIndex);
shadowHost.style.overflow = 'visible';
shadowHost.style.position = 'relative';
shadowHost.style.width = '0';
shadowHost.style.height = '0';
shadowHost.style.display = 'block';
const html = shadow.querySelector('html')!;
// HTML doesn't exist in tests
if (options.type === 'overlay') {
html.style.position = 'absolute';
if (options.alignment?.startsWith('bottom-')) html.style.bottom = '0';
else html.style.top = '0';
if (options.alignment?.endsWith('-right')) html.style.right = '0';
else html.style.left = '0';
} else {
html.style.position = 'fixed';
html.style.top = '0';
html.style.bottom = '0';
html.style.left = '0';
html.style.right = '0';
}
}
mountContentScriptUiRoot(shadowHost, options);
applyContentScriptUiPosition(
shadowHost,
shadow.querySelector('html'),
options,
);
};
const remove = () => {
@@ -202,100 +144,30 @@ export interface ContentScriptUi<TApp> {
remove: () => void;
}
export interface BaseContentScriptUiOptions<TApp> {
/**
* The name of the custom component used to host the ShadowRoot. Must be kebab-case.
*/
name: string;
/**
* In combination with `anchor`, decide how to add the UI to the DOM.
*
* - `"last"` (default) - Add the UI as the last child of the `anchor` element
* - `"first"` - Add the UI as the last child of the `anchor` element
* - `"replace"` - Replace the `anchor` element with the UI.
* - `"before"` - Add the UI as the sibling before the `anchor` element
* - `"after"` - Add the UI as the sibling after the `anchor` element
* - `(anchor, ui) => void` - Customizable function that let's you add the UI to the DOM
*/
append?: ContentScriptAppendMode | ((anchor: Element, ui: Element) => void);
/**
* A CSS selector, element, or function that returns one of the two. Along with `append`, the
* `anchor` dictates where in the page the UI will be added.
*/
anchor?:
| string
| Element
| null
| undefined
| (() => string | Element | null | undefined);
/**
* Callback executed when mounting the UI. This function should create and append the UI to the
* `container` element. It is called every time `ui.mount()` is called
*
* Optionally return a value that can be accessed at `ui.mounted` or in the `onRemove` callback.
*/
mount: (container: Element) => TApp;
/**
* Callback called when the UI is removed from the webpage. Use to cleanup your UI, like
* unmounting your vue or react apps.
*/
onRemove?: (mounted: TApp) => void;
/**
* Custom CSS text to apply to the UI. If your content script imports/generates CSS and you've
* set `cssInjectionMode: "ui"`, the imported CSS will be included automatically. You do not need
* to pass those styles in here. This is for any additional styles not in the imported CSS.
*
* See https://wxt.dev/entrypoints/content-scripts.html#ui for more info.
*/
css?: string;
}
export type OverlayContentScriptUiOptions<TApp> =
BaseContentScriptUiOptions<TApp> & {
type: 'overlay';
export type ContentScriptUiOptions<TApp> = ContentScriptPositioningOptions &
ContentScriptAnchoredOptions & {
/**
* When using `type: "overlay"`, the mounted element is 0px by 0px in size. Alignment specifies
* which corner is aligned with that 0x0 pixel space.
* The name of the custom component used to host the ShadowRoot. Must be kebab-case.
*/
name: string;
/**
* Callback executed when mounting the UI. This function should create and append the UI to the
* `container` element. It is called every time `ui.mount()` is called
*
* @default "top-left"
* Optionally return a value that can be accessed at `ui.mounted` or in the `onRemove` callback.
*/
alignment?: ContentScriptUiOverlayAlignment;
mount: (container: Element) => TApp;
/**
* The `z-index` used on the `shadowHost`. Set to a positive number to show your UI over website
* content.
* Callback called when the UI is removed from the webpage. Use to cleanup your UI, like
* unmounting your vue or react apps.
*/
zIndex?: number;
};
export type ModalContentScriptUiOptions<TApp> =
BaseContentScriptUiOptions<TApp> & {
type: 'modal';
onRemove?: (mounted: TApp) => void;
/**
* The `z-index` used on the `shadowHost`. Set to a positive number to show your UI over website
* content.
* Custom CSS text to apply to the UI. If your content script imports/generates CSS and you've
* set `cssInjectionMode: "ui"`, the imported CSS will be included automatically. You do not need
* to pass those styles in here. This is for any additional styles not in the imported CSS.
*
* See https://wxt.dev/entrypoints/content-scripts.html#ui for more info.
*/
zIndex?: number;
css?: string;
};
export type InlineContentScriptUiOptions<TApp> =
BaseContentScriptUiOptions<TApp> & {
type: 'inline';
};
export type ContentScriptUiOverlayAlignment =
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right';
export type ContentScriptAppendMode =
| 'last'
| 'first'
| 'replace'
| 'before'
| 'after';
export type ContentScriptUiOptions<TApp> =
| OverlayContentScriptUiOptions<TApp>
| ModalContentScriptUiOptions<TApp>
| InlineContentScriptUiOptions<TApp>;
+1
View File
@@ -1,3 +1,4 @@
export * from './content-script-context';
export * from './content-script-ui';
export * from './content-script-iframe';
export * from './define-content-script';
+145
View File
@@ -0,0 +1,145 @@
export type ContentScriptOverlayAlignment =
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right';
export type ContentScriptAppendMode =
| 'last'
| 'first'
| 'replace'
| 'before'
| 'after'
| ((anchor: Element, ui: Element) => void);
export function mountContentScriptUiRoot(
root: HTMLElement,
options: ContentScriptAnchoredOptions,
): void {
const anchor = getAnchor(options);
if (anchor == null)
throw Error(
'Failed to mount content script UI: could not find anchor element',
);
switch (options.append) {
case undefined:
case 'last':
anchor.append(root);
break;
case 'first':
if (anchor.firstChild) {
anchor.insertBefore(root, anchor.firstChild);
} else {
anchor.append(root);
}
break;
case 'replace':
anchor.replaceWith(root);
break;
case 'after':
anchor.replaceWith(anchor, root);
break;
case 'before':
anchor.replaceWith(root, anchor);
break;
default:
options.append(anchor, root);
break;
}
}
export type ContentScriptPositioningOptions =
| { type: 'inline' }
| {
type: 'overlay';
/**
* The `z-index` used on the `shadowHost`. Set to a positive number to show your UI over website
* content.
*/
zIndex?: number;
/**
* When using `type: "overlay"`, the mounted element is 0px by 0px in size. Alignment specifies
* which corner is aligned with that 0x0 pixel space.
*
* @default "top-left"
*/
alignment?: ContentScriptOverlayAlignment;
}
| {
type: 'modal';
/**
* The `z-index` used on the `shadowHost`. Set to a positive number to show your UI over website
* content.
*/
zIndex?: number;
};
export function applyContentScriptUiPosition(
root: HTMLElement,
positionedElement: HTMLElement | undefined | null,
options: ContentScriptPositioningOptions,
): void {
if (options.type !== 'inline') {
if (options.zIndex != null) root.style.zIndex = String(options.zIndex);
root.style.overflow = 'visible';
root.style.position = 'relative';
root.style.width = '0';
root.style.height = '0';
root.style.display = 'block';
if (positionedElement) {
if (options.type === 'overlay') {
positionedElement.style.position = 'absolute';
if (options.alignment?.startsWith('bottom-'))
positionedElement.style.bottom = '0';
else positionedElement.style.top = '0';
if (options.alignment?.endsWith('-right'))
positionedElement.style.right = '0';
else positionedElement.style.left = '0';
} else {
positionedElement.style.position = 'fixed';
positionedElement.style.top = '0';
positionedElement.style.bottom = '0';
positionedElement.style.left = '0';
positionedElement.style.right = '0';
}
}
}
}
function getAnchor(options: ContentScriptAnchoredOptions): Element | undefined {
if (options.anchor == null) return document.body;
let resolved =
typeof options.anchor === 'function' ? options.anchor() : options.anchor;
if (typeof resolved === 'string')
return document.querySelector<Element>(resolved) ?? undefined;
return resolved ?? undefined;
}
export interface ContentScriptAnchoredOptions {
/**
* A CSS selector, element, or function that returns one of the two. Along with `append`, the
* `anchor` dictates where in the page the UI will be added.
*/
anchor?:
| string
| Element
| null
| undefined
| (() => string | Element | null | undefined);
/**
* In combination with `anchor`, decide how to add the UI to the DOM.
*
* - `"last"` (default) - Add the UI as the last child of the `anchor` element
* - `"first"` - Add the UI as the last child of the `anchor` element
* - `"replace"` - Replace the `anchor` element with the UI.
* - `"before"` - Add the UI as the sibling before the `anchor` element
* - `"after"` - Add the UI as the sibling after the `anchor` element
* - `(anchor, ui) => void` - Customizable function that let's you add the UI to the DOM
*/
append?: ContentScriptAppendMode | ((anchor: Element, ui: Element) => void);
}
+7 -3
View File
@@ -41,9 +41,13 @@ export async function initialize(options: {
type: () => (options.packageManager == null ? 'select' : undefined),
message: 'Package Manager',
choices: [
{ title: 'npm', value: 'npm' },
{ title: 'pnpm', value: 'pnpm' },
{ title: 'yarn', value: 'yarn' },
{ title: pc.red('npm'), value: 'npm' },
{ title: pc.yellow('pnpm'), value: 'pnpm' },
{ title: pc.cyan('yarn'), value: 'yarn' },
{
title: `${pc.magenta('bun')}${pc.gray(' (experimental)')}`,
value: 'bun',
},
],
},
],
+6
View File
@@ -11,6 +11,12 @@ export function createWebExtRunner(): ExtensionRunner {
async openBrowser(config) {
config.logger.info('Opening browser...');
if (config.browser === 'firefox' && config.manifestVersion === 3) {
throw Error(
'Dev mode does not support Firefox MV3. For alternatives, see https://github.com/wxt-dev/wxt/issues/230#issuecomment-1806881653',
);
}
// Use the plugin's logger instead of web-ext's built-in one.
const webExtLogger = await import('web-ext-run/util/logger');
webExtLogger.consoleStream.write = ({ level, msg, name }) => {
+24
View File
@@ -0,0 +1,24 @@
import { describe, expect, it } from 'vitest';
import { normalizePath } from '../paths';
describe('Path Utils', () => {
describe('normalizePath', () => {
it.each([
// Relative paths
['../test.sh', '../test.sh'],
['..\\test.sh', '../test.sh'],
['test.png', 'test.png'],
// Absolute paths
['C:\\\\path\\to\\file', 'C:/path/to/file'],
['/path/to/file', '/path/to/file'],
// Strip trailing slash
['C:\\\\path\\to\\folder\\', 'C:/path/to/folder'],
['/path/to/folder/', '/path/to/folder'],
// Dedupe slashes
['path\\\\\\file', 'path/file'],
['path//file', 'path/file'],
])('should normalize "%s" to "%s"', (input, expected) => {
expect(normalizePath(input)).toBe(expected);
});
});
});
@@ -71,6 +71,7 @@ async function buildSingleEntrypoint(
}
const libMode: vite.UserConfig = {
mode: config.mode,
plugins,
build: {
lib: {
@@ -128,6 +129,7 @@ async function buildMultipleEntrypoints(
config: InternalConfig,
): Promise<BuildStepOutput> {
const multiPage: vite.UserConfig = {
mode: config.mode,
plugins: [wxtPlugins.multipageMove(entrypoints, config)],
build: {
rollupOptions: {
+17 -16
View File
@@ -77,7 +77,7 @@ async function writePathsDeclarationFile(
import "wxt/browser";
declare module "wxt/browser" {
type PublicPath =
export type PublicPath =
{{ union }}
export interface WxtRuntime extends Runtime.Static {
getURL(path: PublicPath): string;
@@ -135,7 +135,7 @@ declare module "wxt/browser" {
const overrides = messages.map((message) => {
return ` /**
* ${message.description ?? 'No message description.'}
*
*
* "${message.message}"
*/
getMessage(
@@ -195,8 +195,17 @@ async function writeTsConfigFile(
config: InternalConfig,
) {
const dir = config.wxtDir;
const rootPath = normalizePath(relative(dir, config.root));
const srcPath = normalizePath(relative(dir, config.srcDir));
const getTsconfigPath = (path: string) => normalizePath(relative(dir, path));
const paths = Object.entries(config.alias)
.flatMap(([alias, absolutePath]) => {
const aliasPath = getTsconfigPath(absolutePath);
return [
` "${alias}": ["${aliasPath}"]`,
` "${alias}/*": ["${aliasPath}/*"]`,
];
})
.join(',\n');
await writeFileIfDifferent(
resolve(dir, 'tsconfig.json'),
`{
@@ -209,24 +218,16 @@ async function writeTsConfigFile(
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"strict": true,
"lib": ["DOM", "WebWorker"],
"skipLibCheck": true,
"paths": {
"@": ["${srcPath}"],
"@/*": ["${srcPath}/*"],
"~": ["${srcPath}"],
"~/*": ["${srcPath}/*"],
"@@": ["${rootPath}"],
"@@/*": ["${rootPath}/*"],
"~~": ["${rootPath}"],
"~~/*": ["${rootPath}/*"]
${paths}
}
},
"include": [
"${normalizePath(relative(dir, config.root))}/**/*",
"./${normalizePath(relative(dir, mainReference))}"
"${getTsconfigPath(config.root)}/**/*",
"./${getTsconfigPath(mainReference)}"
],
"exclude": ["${normalizePath(relative(dir, config.outBaseDir))}"]
"exclude": ["${getTsconfigPath(config.outBaseDir)}"]
}`,
);
}
+30 -4
View File
@@ -14,6 +14,7 @@ import * as vite from 'vite';
import { createFsCache } from '~/core/utils/cache';
import consola, { LogLevels } from 'consola';
import * as plugins from '~/core/vite-plugins';
import defu from 'defu';
/**
* Given an inline config, discover the config file if necessary, merge the results, resolve any
@@ -80,6 +81,16 @@ export async function getInternalConfig(
overrides: inlineConfig.runner,
defaults: userConfig.runner,
});
// Make sure alias are absolute
const alias = Object.fromEntries(
Object.entries({
...mergedConfig.alias,
'@': srcDir,
'~': srcDir,
'@@': root,
'~~': root,
}).map(([key, value]) => [key, path.resolve(root, value)]),
);
const finalConfig: InternalConfig = {
browser,
@@ -112,6 +123,11 @@ export async function getInternalConfig(
template: mergedConfig.analysis?.template ?? 'treemap',
},
userConfigMetadata: userConfigMetadata ?? {},
alias,
experimental: {
includeBrowserPolyfill:
mergedConfig.experimental?.includeBrowserPolyfill ?? true,
},
};
finalConfig.vite = (env) =>
@@ -157,13 +173,13 @@ function mergeInlineConfig(
const inline = await inlineConfig.vite?.(env);
return vite.mergeConfig(user ?? {}, inline ?? {});
};
const runner: InlineConfig['runner'] = vite.mergeConfig(
userConfig.runner ?? {},
const runner: InlineConfig['runner'] = defu(
inlineConfig.runner ?? {},
userConfig.runner ?? {},
);
const zip: InlineConfig['zip'] = vite.mergeConfig(
userConfig.zip ?? {},
const zip: InlineConfig['zip'] = defu(
inlineConfig.zip ?? {},
userConfig.zip ?? {},
);
return {
@@ -187,6 +203,14 @@ function mergeInlineConfig(
template:
inlineConfig.analysis?.template ?? userConfig.analysis?.template,
},
alias: {
...userConfig.alias,
...inlineConfig.alias,
},
experimental: {
...userConfig.experimental,
...inlineConfig.experimental,
},
};
}
@@ -252,5 +276,7 @@ async function resolveInternalViteConfig(
}
internalVite.plugins.push(plugins.globals(finalConfig));
internalVite.plugins.push(plugins.excludeBrowserPolyfill(finalConfig));
return internalVite;
}
@@ -55,6 +55,9 @@ export async function importEntrypointFile<T>(
'node_modules/wxt/dist/virtual/mock-browser.js',
),
},
// Continue using node to load TS files even if `bun run --bun` is detected. Jiti does not
// respect the custom transform function when using it's native bun option.
experimentalBun: false,
// List of extensions to transform with esbuild
extensions: ['.ts', '.cts', '.mts', '.tsx', '.js', '.cjs', '.mjs', '.jsx'],
transform(opts) {
+16 -11
View File
@@ -23,7 +23,7 @@ import { getPackageJson } from './package';
import { normalizePath } from './paths';
import { writeFileIfDifferent } from './fs';
import { produce } from 'immer';
import * as vite from 'vite';
import defu from 'defu';
/**
* Writes the manifest to the output directory and the build output.
@@ -78,9 +78,9 @@ export async function generateMainfest(
};
const userManifest = config.manifest;
const manifest = vite.mergeConfig(
baseManifest,
const manifest = defu(
userManifest,
baseManifest,
) as Manifest.WebExtensionManifest;
addEntrypoints(manifest, entrypoints, buildOutput, config);
@@ -154,7 +154,12 @@ function addEntrypoints(
if (background) {
const script = getEntrypointBundlePath(background, config.outDir, '.js');
if (manifest.manifest_version === 3) {
if (config.browser === 'firefox' && config.manifestVersion === 3) {
manifest.background = {
type: background.options.type,
scripts: [script],
};
} else if (config.manifestVersion === 3) {
manifest.background = {
type: background.options.type,
service_worker: script,
@@ -360,13 +365,13 @@ function discoverIcons(
// prettier-ignore
// #region snippet
const iconRegex = [
/^icon-([0-9]+)\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon-16.png
/^icon-([0-9]+)x[0-9]+\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon-16x16.png
/^icon@([0-9]+)w\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon@16w.png
/^icon@([0-9]+)h\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon@16h.png
/^icon@([0-9]+)\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon@16.png
/^icon[\/\\]([0-9]+)\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon/16.png
/^icon[\/\\]([0-9]+)x[0-9]+\.(png|bmp|jpeg|jpg|ico|gif)$/, // icon/16x16.png
/^icon-([0-9]+)\.png$/, // icon-16.png
/^icon-([0-9]+)x[0-9]+\.png$/, // icon-16x16.png
/^icon@([0-9]+)w\.png$/, // icon@16w.png
/^icon@([0-9]+)h\.png$/, // icon@16h.png
/^icon@([0-9]+)\.png$/, // icon@16.png
/^icons?[\/\\]([0-9]+)\.png$/, // icon/16.png | icons/16.png
/^icons?[\/\\]([0-9]+)x[0-9]+\.png$/, // icon/16x16.png | icons/16x16.png
];
// #endregion snippet
+4 -4
View File
@@ -1,19 +1,19 @@
import nodePath from 'node:path';
import * as vite from 'vite';
import systemPath from 'node:path';
import normalize from 'normalize-path';
/**
* Converts system paths to normalized bundler path. On windows and unix, this returns paths with /
* instead of \.
*/
export function normalizePath(path: string): string {
return vite.normalizePath(path);
return normalize(path);
}
/**
* Given a normalized path, convert it to the system path style. On Windows, switch to \, otherwise use /.
*/
export function unnormalizePath(path: string): string {
return nodePath.normalize(path);
return systemPath.normalize(path);
}
export const CSS_EXTENSIONS = ['css', 'scss', 'sass', 'less', 'styl', 'stylus'];
+5
View File
@@ -164,6 +164,7 @@ export const fakeRollupOutputChunk = fakeObjectCreator<Rollup.OutputChunk>(
importedCss: new Set(),
},
preliminaryFileName: faker.string.alphanumeric(),
sourcemapFileName: null,
}),
);
@@ -243,5 +244,9 @@ export const fakeInternalConfig = fakeObjectCreator<InternalConfig>(() => {
},
transformManifest: () => {},
userConfigMetadata: {},
alias: {},
experimental: {
includeBrowserPolyfill: true,
},
};
});
+1
View File
@@ -17,6 +17,7 @@ export function getUnimportOptions(
{ package: 'wxt/client' },
{ package: 'wxt/browser' },
{ package: 'wxt/sandbox' },
{ package: 'wxt/storage' },
],
warn: config.logger.warn,
dirs: ['components', 'composables', 'hooks', 'utils'],
@@ -0,0 +1,33 @@
import { InternalConfig } from '~/types';
import * as vite from 'vite';
/**
* Apply the experimental config for disabling the polyfill. It works by aliasing the
* `webextension-polyfill` module to a virtual module and exporting the `chrome` global from the
* virtual module.
*/
export function excludeBrowserPolyfill(config: InternalConfig): vite.Plugin {
const virtualId = 'virtual:wxt-webextension-polyfill-disabled';
return {
name: 'wxt:exclude-browser-polyfill',
config() {
// Only apply the config if we're disabling the polyfill
if (config.experimental.includeBrowserPolyfill) return;
return {
resolve: {
alias: {
'webextension-polyfill': virtualId,
},
},
};
},
load(id) {
if (id === virtualId) {
// Use chrome instead of the polyfill when disabled.
return 'export default chrome';
}
},
};
}
+1
View File
@@ -11,3 +11,4 @@ export * from './bundleAnalysis';
export * from './globals';
export * from './webextensionPolyfillAlias';
export * from './webextensionPolyfillInlineDeps';
export * from './excludeBrowserPolyfill';
+1 -6
View File
@@ -7,12 +7,7 @@ export function tsconfigPaths(config: InternalConfig): vite.Plugin {
async config() {
return {
resolve: {
alias: {
'@@': config.root,
'~~': config.root,
'@': config.srcDir,
'~': config.srcDir,
},
alias: config.alias,
},
};
},
+15 -10
View File
@@ -4,14 +4,14 @@ import { getUnimportOptions } from '~/core/utils/unimport';
import * as vite from 'vite';
import { extname } from 'path';
const ENABLED_EXTENSIONS: Record<string, boolean | undefined> = {
'.js': true,
'.jsx': true,
'.ts': true,
'.tsx': true,
'.vue': true,
'.svelte': true,
};
const ENABLED_EXTENSIONS = new Set([
'.js',
'.jsx',
'.ts',
'.tsx',
'.vue',
'.svelte',
]);
/**
* Inject any global imports defined by unimport
@@ -28,8 +28,13 @@ export function unimport(config: InternalConfig): vite.PluginOption {
await unimport.scanImportsFromDir(undefined, { cwd: config.srcDir });
},
async transform(code, id) {
const ext = extname(id);
if (ENABLED_EXTENSIONS[ext]) return unimport.injectImports(code, id);
// Don't transform dependencies
if (id.includes('node_modules')) return;
// Don't transform non-js files
if (!ENABLED_EXTENSIONS.has(extname(id))) return;
return unimport.injectImports(code, id);
},
};
}
+118
View File
@@ -0,0 +1,118 @@
/**
* @module wxt/storage
*/
import {
Driver,
WatchCallback,
createStorage,
defineDriver,
Storage,
} from 'unstorage';
import browser, { Storage as BrowserStorage } from 'webextension-polyfill';
export interface WebExtensionDriverOptions {
storageArea: 'sync' | 'local' | 'managed' | 'session';
}
export const webExtensionDriver: (opts: WebExtensionDriverOptions) => Driver =
defineDriver((opts) => {
const checkPermission = () => {
if (browser.storage == null)
throw Error(
"You must request the 'storage' permission to use webExtensionDriver",
);
};
const _storageListener: (
changes: BrowserStorage.StorageAreaSyncOnChangedChangesType,
) => void = (changes) => {
Object.entries(changes).forEach(([key, { newValue }]) => {
_listeners.forEach((callback) => {
callback(newValue ? 'update' : 'remove', key);
});
});
};
const _listeners = new Set<WatchCallback>();
return {
name: 'web-extension:' + opts.storageArea,
async hasItem(key) {
checkPermission();
const res = await browser.storage[opts.storageArea].get(key);
return res[key] != null;
},
async getItem(key) {
checkPermission();
const res = await browser.storage[opts.storageArea].get(key);
return res[key] ?? null;
},
async getItems(items) {
checkPermission();
const res = await browser.storage[opts.storageArea].get(
items.map((item) => item.key),
);
return items.map((item) => ({
key: item.key,
value: res[item.key] ?? null,
}));
},
async setItem(key, value) {
checkPermission();
await browser.storage[opts.storageArea].set({ [key]: value ?? null });
},
async setItems(items) {
checkPermission();
const map = items.reduce<Record<string, any>>((map, item) => {
map[item.key] = item.value ?? null;
return map;
}, {});
await browser.storage[opts.storageArea].set(map);
},
async removeItem(key) {
checkPermission();
await browser.storage[opts.storageArea].remove(key);
},
async getKeys() {
checkPermission();
const all = await browser.storage[opts.storageArea].get();
return Object.keys(all);
},
async clear() {
checkPermission();
await browser.storage[opts.storageArea].clear();
},
watch(callback) {
checkPermission();
_listeners.add(callback);
if (_listeners.size === 1) {
browser.storage[opts.storageArea].onChanged.addListener(
_storageListener,
);
}
return () => {
_listeners.delete(callback);
if (_listeners.size === 0) {
browser.storage[opts.storageArea].onChanged.removeListener(
_storageListener,
);
}
};
},
};
});
function createWebExtensionStorage() {
const storage = createStorage();
storage.mount('local', webExtensionDriver({ storageArea: 'local' }));
storage.mount('session', webExtensionDriver({ storageArea: 'session' }));
storage.mount('sync', webExtensionDriver({ storageArea: 'sync' }));
storage.mount('managed', webExtensionDriver({ storageArea: 'managed' }));
return storage;
}
export type StorageValue = null | string | number | boolean | object;
export const storage: Storage<StorageValue> = createWebExtensionStorage();
export * from 'unstorage';
+42 -1
View File
@@ -190,6 +190,47 @@ export interface InlineConfig {
*/
template?: PluginVisualizerOptions['template'];
};
/**
* Add additional paths to the `.wxt/tsconfig.json`. Use this instead of overwriting the `paths`
* in the root `tsconfig.json` if you want to add new paths.
*
* Passed into Vite's
* [`resolve.alias`](https://vitejs.dev/config/shared-options.html#resolve-alias) option and used
* to generate the `.wxt/tsconfig.json`.
*
* The key is the import alias and the value is either a relative path to the root directory or an absolute path.
*
* @example
* {
* "testing": "src/utils/testing.ts"
* }
*/
alias?: Record<string, string>;
/**
* Experimental settings - use with caution.
*/
experimental?: {
/**
* Whether to use [`webextension-polyfill`](https://www.npmjs.com/package/webextension-polyfill)
* when importing `browser` from `wxt/browser`.
*
* When set to `false`, WXT will export the chrome global instead of the polyfill from
* `wxt/browser`.
*
* You should use `browser` to access the web extension APIs.
*
* @experimental This option will remain experimental until Manifest V2 is dead.
*
* @default true
* @example
* export default defineConfig({
* experimental: {
* includeBrowserPolyfill: false
* }
* })
*/
includeBrowserPolyfill?: boolean;
};
}
export interface WxtInlineViteConfig
@@ -286,7 +327,7 @@ export interface BaseEntrypoint {
* - `named.sandbox.html` &rarr; `named`
* - `named.sandbox/index.html` &rarr; `named`
* - `sandbox.html` &rarr; `sandbox`
* - `sandbox.index.html` &rarr; `sandbox`
* - `sandbox/index.html` &rarr; `sandbox`
* - `overlay.content.ts` &rarr; `overlay`
* - `overlay.content/index.ts` &rarr; `overlay`
*
+7
View File
@@ -49,6 +49,13 @@ export interface InternalConfig {
template: NonNullable<PluginVisualizerOptions['template']>;
};
userConfigMetadata: Omit<ResolvedConfig<UserConfig>, 'config'>;
/**
* Import aliases to absolute paths.
*/
alias: Record<string, string>;
experimental: {
includeBrowserPolyfill: boolean;
};
}
export interface FsCache {
+5
View File
@@ -49,3 +49,8 @@ declare module 'web-ext-run/util/logger' {
}
export const consoleStream: IConsoleStream;
}
declare module 'wxt/browser' {
// Overridden when types are generated per project
export type PublicPath = string;
}
+3
View File
@@ -0,0 +1,3 @@
// Types generated in the .wxt directory available after wxt prepare
declare type PublicPath = string;
+2 -6
View File
@@ -1,7 +1,3 @@
# WXT + Vue 3
# WXT + React
This template should help get you started developing with Vue 3 in WXT.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
This template should help get you started developing with React in WXT.
+2 -2
View File
@@ -22,7 +22,7 @@
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@vitejs/plugin-react": "^4.0.3",
"typescript": "^5.1.6",
"wxt": "^0.8.0"
"typescript": "^5.3.2",
"wxt": "^0.10.0"
}
}
+2 -2
View File
@@ -18,8 +18,8 @@
"solid-js": "^1.7.8"
},
"devDependencies": {
"typescript": "^5.1.6",
"typescript": "^5.3.2",
"vite-plugin-solid": "^2.7.0",
"wxt": "^0.8.0"
"wxt": "^0.10.0"
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
# Svelte + Vite
# WXT + Svelte
This template should help get you started developing with Svelte in WXT.
+2 -2
View File
@@ -19,7 +19,7 @@
"svelte": "^4.0.5",
"svelte-check": "^3.4.6",
"tslib": "^2.6.0",
"typescript": "^5.1.6",
"wxt": "^0.8.0"
"typescript": "^5.3.2",
"wxt": "^0.10.0"
}
}
+2 -2
View File
@@ -15,7 +15,7 @@
"postinstall": "wxt prepare"
},
"devDependencies": {
"typescript": "^5.1.6",
"wxt": "^0.8.0"
"typescript": "^5.3.2",
"wxt": "^0.10.0"
}
}
+2 -2
View File
@@ -19,8 +19,8 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"typescript": "^5.1.6",
"typescript": "^5.3.2",
"vue-tsc": "^1.8.4",
"wxt": "^0.8.0"
"wxt": "^0.10.0"
}
}
-1
View File
@@ -41,7 +41,6 @@ export default defineWorkspace([
test: {
name: 'e2e',
dir: 'e2e',
singleThread: true,
testTimeout: 120e3,
},
plugins: [testSeed()],