Compare commits

...

112 Commits

Author SHA1 Message Date
github-actions[bot] 1e3482a878 chore(release): wxt v0.20.14
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-06 15:56:04 +00:00
Aaron 734846843f ci: Update templates when bumping WXT version
This closes #2018
2026-02-06 09:51:10 -06:00
Aaron f1eac66494 chore: Fix markdown lint errors 2026-02-06 09:33:06 -06:00
Aaron b4c3280f58 docs: Add new sections to CONTRIBUTING.md
Added sections on WXT's vision, breaking changes policy, and becoming a maintainer.
2026-02-06 09:31:19 -06:00
Invictus Navarchus b2f2998767 chore: update wxt version to ^0.20.13 in all templates (#2019)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-05 19:36:50 -06:00
blurredart 720eb93c67 docs: add missing import in code sample for configuration (#2057)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-05 17:31:56 -06:00
dareka cdf8611e0b fix: MaxListenersExceededWarning (#2058) 2026-02-05 17:31:44 -06:00
Scan 905d797b65 docs: Add Margin extension to extensions using wxt (#2060)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-05 17:22:09 -06:00
tsubasa-ito 6ca8ccc332 docs: add Lofi BGM Player to example projects (#2056)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-05 17:21:18 -06:00
Nazım Gediz Aydındoğmuş 76496e8865 docs: Added "Teams Chat Exporter" to the homepage (#2050)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-05 17:20:15 -06:00
Muzammil f38fec8588 docs: Added "Capture It - Capture & Edit Screenshots" to the homepage (#2033)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-05 17:18:40 -06:00
Ilya Kubariev 4351ac9657 fix: Add .jsonc support for locale files to match docs (#2066) 2026-02-05 17:17:26 -06:00
Mark f3834ac0e2 fix(content-script-context): handle undefined browser.runtime (#2042) 2026-02-05 17:17:16 -06:00
ariasuni 48891a7ca5 fix: rename and make firefoxPrefs in web-ext.config.ts work (#2068) 2026-02-05 17:16:58 -06:00
Aaron 3b6a4aa635 chore: Refresh lockfile and upgrade subdependencies (#2071) 2026-02-05 17:10:47 -06:00
Aaron b20f0471c2 chore: Upgrade major deps (#2070) 2026-02-05 16:57:49 -06:00
Aaron 0f9e66e1e8 chore: Update dependencies (#2069) 2026-02-05 16:37:13 -06:00
Aaron 6fb4cee43f fix: Remove failing test 2026-02-05 16:13:03 -06:00
aklinker1 0e8afbbf56 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-02-04 00:37:48 +00:00
aklinker1 83decf7bf5 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-01-22 00:35:56 +00:00
aklinker1 5c77cf23b8 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-01-21 00:35:03 +00:00
aklinker1 606fd3164c fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-01-19 00:36:13 +00:00
aklinker1 71308d0d6f fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-01-11 00:37:39 +00:00
Patryk Kuniczak 8bbea7cda4 docs: Fix alignment of 2 last comments of regex patterns (#2012) 2025-12-23 09:42:43 -06:00
Aaron a87d3e4809 chore: Move script-only dev dependencies to top-level package.json (#2007) 2025-12-18 00:28:49 -06:00
Aaron 1f3db58254 chore: Use tsdown to build packages (#2006) 2025-12-18 00:02:19 -06:00
Aaron 43f6c06271 Update out-of-date lockfile 2025-12-17 21:30:07 -06:00
aklinker1 04ae542b13 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2025-12-17 00:32:12 +00:00
bohuslavsemenov f97c81df0d feat: add onBeforeMount callback to createIframeUi options (#1899)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-16 17:13:33 -06:00
github-actions[bot] b7b3fc53eb chore(release): wxt v0.20.13
📼 VHS / Create VHS (push) Cancelled after 0s
2025-12-16 23:10:49 +00:00
rxliuli 4b44fb849d fix: Don't return promises from unlisted scripts that do not have an async main function (#1907)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-16 16:58:46 -06:00
Dan McGee 5d0a266e9c fix: Add another defineItem signature when init function is passed (#1909) 2025-12-16 16:56:24 -06:00
Sebastian Landwehr 24667861db feat: Support .wxtrc config file (#1833)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-16 16:33:37 -06:00
Johan Kiviniemi 25cc403886 refactor: Use script.text instead of innerHTML in injectScript (#1764)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-16 16:17:07 -06:00
Johan Kiviniemi 923312c482 feat: Make injectScript return the created script element (#1838)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-16 16:09:07 -06:00
Johan Kiviniemi adeef6eb73 fix: Make injectScript wait until script is actually loaded (#1763)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-16 15:54:41 -06:00
Johan Kiviniemi 6f84d45960 feat: Add modifyScript option to injectScript (#1762)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-16 15:47:40 -06:00
Johan Kiviniemi 938c25cab4 feat: Remove script element immediately in injectScript (#1761)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-16 15:42:56 -06:00
github-actions[bot] 0404637da8 chore(release): wxt v0.20.12
📼 VHS / Create VHS (push) Cancelled after 0s
2025-12-16 20:57:58 +00:00
Aaron 98d95e4b7f chore(deps): Upgrade node-forge to address CVE-2025-12816, CVE-2025-66031 (#2003) 2025-12-16 14:50:50 -06:00
Aaron 2687a35781 chore(dev-deps): Upgrade happy-dom to address CVE-2025-61927 (#2002) 2025-12-16 14:41:09 -06:00
Aaron 886dae28fa chore: Upgrade dev and non-major prod dependencies (#2000) 2025-12-16 14:33:59 -06:00
Aaron 8c9bc8cdbc chore: Add support for vite-node v5 (#2001) 2025-12-16 11:46:06 -06:00
Jaguar Zhou 5414a0aa34 fix: wxt prepare fails with the error "__vite_ssr_exportName__ is not defined" when using Vite 7 (#1884)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-16 11:04:14 -06:00
K.Utsunomiya 223cf8613b docs: Add note about reviewing project structure in migration guide (#1990) 2025-12-16 08:36:02 -06:00
Alexander Harding 4f1776befc feat: Default to using use_dynamic_url: true for content script css files (#1993)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-15 19:04:07 -06:00
Aaron 38473b9f6c chore: Fix formatting 2025-12-15 18:42:23 -06:00
Manabu Nakazawa 41b428c75f docs: Add "Redirect Web" to homepage (#1995)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-15 18:32:41 -06:00
yiang231 d8d754658f docs: Add "defineConfig" config to startup examples (#1989) 2025-12-15 18:31:43 -06:00
Shawn e52ebf1d53 docs: Change manifest.title to manifest.name in build:manifestGenerated hook example (#1985) 2025-12-15 18:29:10 -06:00
Nguyen Canh Toan 535c7a5f92 docs: add Zen Virtual Piano & Crypto Pulse to example projects (#1977) 2025-12-15 18:27:21 -06:00
TomvdPeet 99b2bd784a Fix comments for includeSources and excludeSources (#1998)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-15 18:19:48 -06:00
Aaron 31ebf966fd chore: Fix other type error after chrome types update 2025-12-15 18:19:25 -06:00
Aaron 6036c6e8e0 chore: Fix type errors after chrome type upgrades 2025-12-15 18:14:29 -06:00
Aaron 597151b5dd ci: Fix browser auto-release workflow 2025-12-15 18:07:44 -06:00
aklinker1 29aeaed223 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2025-12-11 18:41:00 +00:00
Alex Khrisanfov edf87952c7 ci: Fix update-browser-package action (#1992)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-12-11 12:39:18 -06:00
Aaron 78f8434a06 chore: Use linkedom a test instead of happy-dom (#1957) 2025-10-27 18:11:56 -05:00
Aaron 7eaccad683 chore: Update lockfile 2025-10-27 17:49:24 -05:00
Aaron bfa9c1e1c8 chore: Move PNPM settings to pnpm-workspace.yaml folder 2025-10-27 17:39:59 -05:00
Aaron a2d6577579 docs: Fix syntax error 2025-10-27 17:20:59 -05:00
Ethan Lanting 0e8191488f docs: Added "BetterCampus (prev. BetterCanvas)" to the homepage (#1888)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-10-27 17:08:12 -05:00
Shahil Yadav a1510bb081 docs: Added "Leetcode Fonts" to the homepage (#1889)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-10-27 17:07:05 -05:00
Fantasy 1eaca30f97 docs: Added "sitedata" and "TranslateManga" to homepage (#1902)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-10-27 17:05:52 -05:00
yukidaruma bc79987ed5 docs: Added "Livestream Chat Reader" to the homepage (#1910)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-10-27 17:04:46 -05:00
Alexander Harding 36a501506e feat: Remove data-wxt-* attributes (#1913) 2025-10-27 17:03:01 -05:00
dependabot[bot] e1d47f5f63 chore(deps): bump codecov/codecov-action from 5.5.0 to 5.5.1 (#1920)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 16:55:26 -05:00
dependabot[bot] 7faa9b5a64 chore(deps): bump docker/login-action from 3.5.0 to 3.6.0 (#1921)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 16:55:00 -05:00
dependabot[bot] 295bee708c chore(deps): bump actions/github-script from 7.0.1 to 8.0.0 (#1922)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 16:54:32 -05:00
Burak Unver 9e917481c4 docs: Added "ChatGPT Token Counter" to the homepage (#1926)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-10-27 16:53:41 -05:00
anghunk c5a8b9fb3d docs: Add LinuxDo Scripts extension to the list (#1940) 2025-10-27 16:26:46 -05:00
Florian Kühne 3f4e32f8da fix: Optimize splitShadowRootCss (#1934)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-10-27 16:06:01 -05:00
Aaron f630098f1d chore: Upgrade @aklinker1/check to reduce dev dep size 2025-10-27 09:09:20 -05:00
Eli 608d6e2b54 ci: Don't run cron jobs on forks (#1951) 2025-10-26 20:53:35 -05:00
github-actions[bot] fad5ab6c44 chore(release): @wxt-dev/storage v1.2.6
📼 VHS / Create VHS (push) Cancelled after 0s
2025-10-25 16:40:59 +00:00
Aaron d4cb7997fe ci: Publish packages using OIDC with correctly generated package.json
files
2025-10-25 11:34:59 -05:00
Carlos Ortiz 28364b8be1 ci: publish using pnpm instead of npm (#1943) 2025-10-24 21:11:43 -05:00
github-actions[bot] d28a5c7d5e chore(release): @wxt-dev/storage v1.2.5
📼 VHS / Create VHS (push) Cancelled after 0s
2025-10-13 03:09:36 +00:00
Aaron df707aef4c ci: Use correct version of NPM that supports OIDC auth 2025-10-12 22:02:37 -05:00
Aaron 77af2e58ec ci: Log all NPM versions on runner 2025-10-12 21:59:34 -05:00
Aaron f738e56337 ci: Upgrade actions/setup-node to v5 2025-10-12 21:57:24 -05:00
Aaron f2fe2584e0 ci: try out ubuntu-24.04 runner 2025-10-12 21:53:28 -05:00
Aaron 02673ae017 ci: Debug installed NPM versions more 2025-10-12 21:51:37 -05:00
Aaron 8dd294a163 ci: Disable publishing while debugging NPM changes 2025-10-12 21:49:01 -05:00
Aaron 469e735d53 ci: List installed NPM version 2025-10-12 21:47:57 -05:00
github-actions[bot] b02001796e chore(release): @wxt-dev/storage v1.2.4
📼 VHS / Create VHS (push) Cancelled after 0s
2025-10-13 02:46:42 +00:00
Aaron 0d4d2fdd7e ci: Print NPM version 2025-10-12 21:42:15 -05:00
github-actions[bot] b65e5446fa chore(release): @wxt-dev/storage v1.2.3
📼 VHS / Create VHS (push) Cancelled after 0s
2025-10-13 02:00:40 +00:00
Aaron f75fe3ce5b ci: Separate NPM upgrade from publish step 2025-10-12 20:52:54 -05:00
github-actions[bot] 59ba2712cf chore(release): @wxt-dev/storage v1.2.2
📼 VHS / Create VHS (push) Cancelled after 0s
2025-10-13 01:45:12 +00:00
github-actions[bot] fefe601541 chore(release): @wxt-dev/storage v1.2.1
📼 VHS / Create VHS (push) Cancelled after 0s
2025-10-11 17:41:03 +00:00
Aaron 7291c60413 ci: Support NPM OIDC publishing flow and add provenance (#1932) 2025-10-11 12:36:05 -05:00
Jaguar Zhou bb679102b1 fix: wxt normal logs are drowned by dotenv@17.0.0 ads (#1883) 2025-09-09 08:04:18 -05:00
Alexey Karetnikov 16027c8971 chore(templates): Fix typo vite -> wxt (#1885) 2025-09-09 08:02:40 -05:00
github-actions[bot] 84405a33e3 chore(release): wxt v0.20.11
📼 VHS / Create VHS (push) Cancelled after 0s
2025-09-05 13:11:49 +00:00
Aaron bdf795c3f2 chore(deps): Upgrade project subdependencies (#1882) 2025-09-05 08:06:38 -05:00
Qijia Liu ab73084bb5 fix: input_components is supported by mv3 (#1881) 2025-09-05 07:58:09 -05:00
marcellino ornelas e9fb4c5414 fix: Split wxt/testing into separate modules to fix issues with jsdom and happy-dom (#1844)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-09-04 13:56:43 -05:00
github-actions[bot] bc54f525e4 chore(release): @wxt-dev/module-react v1.1.5
📼 VHS / Create VHS (push) Cancelled after 0s
2025-09-04 14:06:52 +00:00
Aaron 9170159bc1 chore(deps): Support @vitejs/plugin-react@5 2025-09-04 09:01:01 -05:00
github-actions[bot] fa657a0366 chore(release): wxt v0.20.10
📼 VHS / Create VHS (push) Cancelled after 0s
2025-09-04 00:03:50 +00:00
Aaron 24920ddd52 chore(deps): publish-browser-extension upgrade (#1880) 2025-09-03 18:59:39 -05:00
Aaron eb0b372113 chore(deps): Upgrade web-ext-run to support CDP (#1879) 2025-09-03 18:51:26 -05:00
Aaron 8f5f4f674e chore(deps): Upgrade non-breaking production dependencies (#1877) 2025-09-03 18:12:54 -05:00
Aaron 38c1dae9c7 ci: Fix fetch depth for notification workflow 2025-09-03 18:08:35 -05:00
Aaron f92bcd1d14 ci: Remove embed from discord notification 2025-09-03 18:07:10 -05:00
Aaron df09b86d23 ci: Fix notification workflow round 4 2025-09-03 18:05:04 -05:00
Aaron bd5cfa10ad ci: Fix notification workflow round 3 2025-09-03 18:00:57 -05:00
github-actions[bot] 94914dd1ad chore(release): wxt v0.20.9
📼 VHS / Create VHS (push) Cancelled after 0s
2025-09-03 22:55:34 +00:00
Aaron dfbb58a2f1 ci: Fix notification workflow round 2 2025-09-03 17:50:13 -05:00
github-actions[bot] 54a1c3576f chore(release): @wxt-dev/storage v1.2.0
📼 VHS / Create VHS (push) Cancelled after 0s
2025-09-03 22:49:56 +00:00
Aaron 9ddf73b4fe ci: Fix notification workflow 2025-09-03 17:44:39 -05:00
91 changed files with 6871 additions and 8114 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ runs:
uses: pnpm/action-setup@f2b2b233b538f500472c7274c7012f57857d8ce0 # v4.1.0
- name: 🛠️ Setup NodeJS
uses: actions/setup-node@b7234cc9fe124f0f4932554b4e5284543083ae7b # v4.4.0
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: 20
cache: pnpm
+12 -10
View File
@@ -7,27 +7,29 @@ on:
jobs:
notify:
runs-on: ubuntu-22.04
if: github.repository_owner == 'wxt-dev'
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
with:
fetch-depth: 0
- name: List Commits
id: list-commits
run: |
OUTPUT="$(./scripts/list-unreleased-commits.sh)"
echo "::set-output name=summary::${OUTPUT}"
./scripts/list-unreleased-commits.sh > summary.txt
- name: Discord notification
uses: Ilshidur/action-discord@d2594079a10f1d6739ee50a2471f0ca57418b554
run: |
PAYLOAD=$(jq -n --arg content "${{ env.MESSAGE }}" '{"content": $content}')
curl -X POST \
-F "payload_json=${PAYLOAD}" \
-F "file1=@summary.txt" \
$DISCORD_WEBHOOK
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_UNRELEASED_COMMITS }}
with:
args: |
```
{{ steps.list-commits.outputs.summary }}
```
If a package needs released, please [run the workflow](https://github.com/wxt-dev/wxt/actions/workflows/release.yml).
MESSAGE: |
If a package needs released, please [run the workflow](<https://github.com/wxt-dev/wxt/actions/workflows/release.yml>).
Before running, consider:
- Are there any breaking changes? If so, prepare a list of breaking changes and update the changelog and release notes after the release.
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
steps:
- name: Post Thank You Comment
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
comment: Thanks for helping make WXT better!
with:
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
uses: ./.github/actions/setup
- name: Login to Docker Registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
+6 -5
View File
@@ -31,9 +31,10 @@ jobs:
publish:
name: Publish
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
permissions:
contents: write
contents: write # Push version changes
id-token: write # OIDC for NPM publishing
needs:
- validate
steps:
@@ -61,9 +62,9 @@ jobs:
- name: Publish to NPM
working-directory: packages/${{ inputs.package }}
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
pnpm build
pnpm publish
pnpm pack
sudo npm i -g npm@latest
/usr/local/bin/npm publish *.tgz
- name: Create GitHub release
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
+9 -4
View File
@@ -11,11 +11,15 @@ jobs:
sync:
name: 'Sync with @types/chrome'
runs-on: ubuntu-latest
if: github.repository_owner == 'wxt-dev'
permissions:
contents: write
contents: write # Push version changes
id-token: write # OIDC for NPM publishing
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
with:
ssh-key: ${{ secrets.DEPLOY_KEY }}
- name: Setup
uses: ./.github/actions/setup
@@ -38,9 +42,10 @@ jobs:
with:
commit_message: 'fix: Upgrade `@wxt-dev/browser` to latest `@types/chrome` version'
- name: Publish Package
- name: Publish to NPM
if: steps.commit.outputs.changes_detected == 'true'
working-directory: packages/browser
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
pnpm publish
pnpm pack
sudo npm i -g npm@latest
/usr/local/bin/npm publish *.tgz
+1 -1
View File
@@ -86,7 +86,7 @@ jobs:
- name: Upload Coverage
if: matrix.coverage
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+32
View File
@@ -6,6 +6,19 @@ If you are changing the docs or fixing a bug, feel free to fork and open a PR.
If you want to add a new feature, please create an issue or discussion first so we can decide if the feature is inline with the vision for WXT.
## WXT's Vision
WXT is two things:
1. A build tool
2. A set of runtime utilities
The long term goal of WXT is provide an opinionated build tool that keeps WXT projects standard, while providing light-weight runtime utils that simplify a lot of the boilerplate/overhead when setting up a new extension.
I also want to provide a way for developers to use either one of those two things independently, and not require them to use both. This is why all of WXT's runtime utils are shipped as their own NPM packages, most of them not bundled inside the core `wxt` package. If you just want to use the packages, they're availalbe, and if you just want to use WXT's built tool, you don't have to import any of WXT's utilities, you can use your own.
> The few runtime utils shipped inside WXT are things that should be used by 90% of extensions. That said, they're also legacy utils left in from before I started creating separate NPM packages, and in the future, they may be removed from the core package.
## Conventional Commits
This project uses [Conventional Commit format](https://www.conventionalcommits.org/en/v1.0.0/) to automatically generate a changelog and better understand the changes in the project
@@ -20,6 +33,19 @@ Here are some examples of conventional commit messages:
The title of your pull request should follow the [conventional commit format](#conventional-commits). When a pull request is merged to the main branch, all changes are going to be squashed into a single commit. The message of this commit will be the title of the pull request. And for every release, the commit messages are used to generate the changelog.
## Breaking Changes Policy
A quick word on WXT's breaking changes policy. I am willing to make breaking changes, but they have to be for a good enough reason - they have to make WXT better as a whole, they can't be based on one opinion.
Breaking changes also require a major release. Major releases have happened once or twice a year, so after you merge your PR, you'll have to wait a little bit before it is released.
To make a breaking change:
1. Make sure you're PR is targeting the `major` branch
2. Add `!` after the conventional commit type (`fix!: ...`, `feat!: ...`, `chore!: ...`, etc) to indicate that it is a breaking change
3. At the top of the PR, provide documentation that will inform developers about the breaking change, why it was done, and how to migrate their extension so nothing breaks.
- This documentation will be put ["Upgrading WXT"](https://wxt.dev/guide/resources/upgrading.html) page in the docs, read through previous breaking change docs for an idea of what is required.
## Setup
WXT uses `pnpm`, so make sure you have it installed.
@@ -188,3 +214,9 @@ Anyone is welcome to submit a blog post on <https://wxt.dev/blog>!
- **English only**: Blog posts should be written in English. Unfortunately, our maintainers don't have the bandwidth right now to translate our docs, let alone blog posts. Sorry 😓
- **AI**: Please only use AI to translate or proof-read your blog post. Don't generate the whole thing... We don't want to publish that.
## Become a Maintainer
If you're interested in becoming a maintainer, send an email to Aaron at <aaronklinker1@gmail.com> with your github username saying you're interested. The process is very informal, I will add you quickly if you've contributed code or answered questions and helped out the community!
Maintainers don't have to just write code - they can manage issues, answer questsions, review PRs, organize and prioritize work - there's lots of ways for you to help out.
@@ -102,6 +102,20 @@ const chromeExtensionIds = [
'ehmoihnjgkdimihkhokkmfjdgomohjgm', // Filmbudd Pro - Simple, private and synced ratings and watch notes across all your devices
'alglchohmdikgdjhafiicilegegieafa', // MultiField CopyCat - Copy, Paste & Autofill Web Forms Instantly
'aamihahiiogceidpbnfgehacgiecephe', // ChatSight - Add Table of Contents to ChatGPT
'cndibmoanboadcifjkjbdpjgfedanolh', // BetterCampus (prev. BetterCanvas)
'hinfimgacobnellbncbcpdlpaapcofaa', // Leetcode Fonts - Change fonts in leetcode effortlessly
'kbkbfefhhabpkibojinapkkgciiacggg', // TranslateManga - Manga Translator & Manga Tracker
'emeakbgdecgmdjgegnejpppcnkcnoaen', // SiteData - Free Website Traffic Checker & Reverse AdSense Tool
'gpnckbhgpnjciklpoehkmligeaebigaa', // Livestream Chat Reader - Text-to-Speech for YouTube/Twitch chat
'fjlalaedpfcojcfpkgkglbjjbbkofgnl', // ChatGPT Token Counter - Count tokens in real time on chatgpt conversation
'fbgblmjbeebanackldpbmpacppflgmlj', // LinuxDo Scripts - 为 linux.do 用户提供了一些增强功能
'dfacnjidgbagicaekenjgclfnhdnjjdi', // Zen Virtual Piano - https://zen-piano.web.app/
'naeibcegmgpofimedkmfgjgphfhfhlab', // Crypto Pulse price tracker - https://get-crypto-pulse.web.app/
'ffglckbhfbfmdkefdmjbhpnffkcmlhdh', // Redirect Web - Automatically redirect pages or open them in another app
'eglpfhbhmelampoihamjomgkeobgdofl', // Capture It - Capture & Edit Screenshots
'jmghclbfbbapimhbgnpffbimphlpolnm', // Teams Chat Exporter
'jdcppdokgfbnhiacbeplahgnciahnhck', // Lofi BGM Player - Free lofi focus music for work & study
'cgpmbiiagnehkikhcbnhiagfomajncpa', // Margin - Annotate and highlight any webpage, with your notes saved to the decentralized AT Protocol.
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
@@ -32,6 +32,8 @@ You can configure browser startup in 3 places:
To set or customize the browser opened during development:
```ts [web-ext.config.ts]
import { defineWebExtConfig } from 'wxt';
export default defineWebExtConfig({
binaries: {
chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome
@@ -41,6 +43,18 @@ export default defineWebExtConfig({
});
```
```ts [wxt.config.ts]
export default defineConfig({
webExt: {
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"
},
},
});
```
By default, WXT will try to automatically discover where Chrome/Firefox are installed. However, if you have chrome installed in a non-standard location, you need to set it manually as shown above.
### Persist Data
@@ -49,11 +63,13 @@ By default, to keep from modifying your browser's existing profiles, `web-ext` c
Right now, Chromium based browsers are the only browsers that support overriding this behavior and persisting data when running the `dev` script multiple times.
To persist data, set the `--user-data-dir` flag:
To persist data, set the `--user-data-dir` flag in any of the config files mentioned above:
:::code-group
```ts [Mac/Linux]
import { defineWebExtConfig } from 'wxt';
export default defineWebExtConfig({
chromiumArgs: ['--user-data-dir=./.wxt/chrome-data'],
});
@@ -61,6 +77,7 @@ export default defineWebExtConfig({
```ts [Windows]
import { resolve } from 'node:path';
import { defineWebExtConfig } from 'wxt';
export default defineWebExtConfig({
// On Windows, the path must be absolute
@@ -82,6 +99,8 @@ You can use any directory you'd like for `--user-data-dir`, the examples above c
If you prefer to load the extension into your browser manually, you can disable the auto-open behavior:
```ts [web-ext.config.ts]
import { defineWebExtConfig } from 'wxt';
export default defineWebExtConfig({
disabled: true,
});
+1 -1
View File
@@ -11,7 +11,7 @@ export default defineConfig({
hooks: {
'build:manifestGenerated': (wxt, manifest) => {
if (wxt.config.mode === 'development') {
manifest.title += ' (DEV)';
manifest.name += ' (DEV)';
}
},
},
+70
View File
@@ -591,6 +591,76 @@ For MV3, `injectScript` is synchronous and the injected script will be evaluated
However for MV2, `injectScript` has to `fetch` the script's text content and create an inline `<script>` block. This means for MV2, your script is injected asynchronously and it will not be evaluated at the same time as your content script's `run_at`.
:::
The `script` element can be modified just before it is added to the DOM by using the `modifyScript` option. This can be used to e.g. modify `script.async`/`script.defer`, add event listeners to the element, or pass data to the script via `script.dataset`. An example:
```ts
// entrypoints/example.content.ts
export default defineContentScript({
matches: ['*://*/*'],
async main() {
await injectScript('/example-main-world.js', {
modifyScript(script) {
script.dataset['greeting'] = 'Hello there';
},
});
},
});
```
```ts
// entrypoints/example-main-world.ts
export default defineUnlistedScript(() => {
console.log(document.currentScript?.dataset['greeting']);
});
```
`injectScript` returns the created script element. It can be used to e.g. send messages to the script in the form of custom events. The script can add an event listener for them via `document.currentScript`. An example of bidirectional communication:
```ts
// entrypoints/example.content.ts
export default defineContentScript({
matches: ['*://*/*'],
async main() {
const { script } = await injectScript('/example-main-world.js', {
modifyScript(script) {
// Add a listener before the injected script is loaded.
script.addEventListener('from-injected-script', (event) => {
if (event instanceof CustomEvent) {
console.log(`${event.type}:`, event.detail);
}
});
},
});
// Send an event after the injected script is loaded.
script.dispatchEvent(
new CustomEvent('from-content-script', {
detail: 'General Kenobi',
}),
);
},
});
```
```ts
// entrypoints/example-main-world.ts
export default defineUnlistedScript(() => {
const script = document.currentScript;
script?.addEventListener('from-content-script', (event) => {
if (event instanceof CustomEvent) {
console.log(`${event.type}:`, event.detail);
}
});
script?.dispatchEvent(
new CustomEvent('from-injected-script', {
detail: 'Hello there',
}),
);
});
```
## Mounting UI to dynamic element
In many cases, you may need to mount a UI to a DOM element that does not exist at the time the web page is initially loaded. To handle this, use the `autoMount` API to automatically mount the UI when the target element appears dynamically and unmount it when the element disappears. In WXT, the `anchor` option is used to target the element, enabling automatic mounting and unmounting based on its appearance and removal.
+2 -2
View File
@@ -9,7 +9,7 @@ WXT provides first class support for Vitest for unit testing:
```ts
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { WxtVitest } from 'wxt/testing';
import { WxtVitest } from 'wxt/testing/vitest-plugin';
export default defineConfig({
plugins: [WxtVitest()],
@@ -36,7 +36,7 @@ This example demonstrates that you don't have to mock `browser.storage` (used by
```ts
import { describe, it, expect } from 'vitest';
import { fakeBrowser } from 'wxt/testing';
import { fakeBrowser } from 'wxt/testing/fake-browser';
const accountStorage = storage.defineItem<Account>('local:account');
+5
View File
@@ -15,6 +15,11 @@ cd path/to/your/project
pnpm dlx wxt@latest init example-wxt --template vanilla
```
:::tip
We recommend reviewing [project structure](/guide/essentials/project-structure.md) before you get started.
You can customize directory names in `wxt.config.ts` to match your project's needs.
:::
In general, you'll need to:
&ensp;<input type="checkbox" /> Install `wxt`<br />
+19 -28
View File
@@ -17,36 +17,38 @@
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
},
"devDependencies": {
"@aklinker1/buildc": "^1.1.5",
"@aklinker1/check": "^2.1.0",
"@commitlint/config-conventional": "^19.8.1",
"@commitlint/types": "^19.8.1",
"@aklinker1/buildc": "^1.1.6",
"@aklinker1/check": "^2.2.0",
"@commitlint/config-conventional": "^20.4.1",
"@commitlint/types": "^20.4.0",
"@types/fs-extra": "^11.0.4",
"@types/semver": "^7.7.1",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/coverage-v8": "^4.0.18",
"changelogen": "^0.6.2",
"consola": "^3.4.2",
"fast-glob": "^3.3.3",
"feed": "^5.1.0",
"fs-extra": "^11.3.1",
"lint-staged": "^16.1.6",
"feed": "^5.2.0",
"fs-extra": "^11.3.3",
"lint-staged": "^16.2.7",
"markdown-it-footnote": "^4.0.0",
"markdownlint-cli": "^0.45.0",
"nano-spawn": "^1.0.2",
"prettier": "^3.6.2",
"semver": "^7.7.2",
"markdownlint-cli": "^0.47.0",
"nano-spawn": "^2.0.0",
"p-map": "^7.0.4",
"prettier": "^3.8.1",
"semver": "^7.7.4",
"simple-git-hooks": "^2.13.1",
"tsx": "4.20.5",
"tsdown": "^0.20.3",
"tsx": "4.21.0",
"typedoc": "^0.25.4",
"typedoc-plugin-frontmatter": "^1.3.0",
"typedoc-plugin-frontmatter": "^1.3.1",
"typedoc-plugin-markdown": "4.0.0-next.23",
"typedoc-vitepress-theme": "1.0.0-next.3",
"typescript": "^5.9.2",
"typescript": "^5.9.3",
"vitepress": "^1.6.4",
"vitepress-knowledge": "^0.4.1",
"vitepress-plugin-group-icons": "^1.6.3",
"vitepress-plugin-group-icons": "^1.7.1",
"vitest-mock-extended": "^3.1.0",
"vue": "^3.5.21",
"vue": "^3.5.27",
"wxt": "workspace:*"
},
"simple-git-hooks": {
@@ -54,16 +56,5 @@
},
"lint-staged": {
"*": "prettier --ignore-unknown --write"
},
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"@algolia/client-search",
"search-insights"
]
},
"patchedDependencies": {
"markdown-it-footnote": "patches/markdown-it-footnote.patch"
}
}
}
-21
View File
@@ -1,21 +0,0 @@
import { defineBuildConfig } from 'unbuild';
import { resolve } from 'node:path';
// Build module and plugins
export default defineBuildConfig({
rootDir: resolve(__dirname, 'modules/analytics'),
outDir: resolve(__dirname, 'dist'),
entries: [
{ input: 'index.ts', name: 'module' },
{ input: 'client.ts', name: 'index' },
'background-plugin.ts',
'types.ts',
'providers/google-analytics-4.ts',
'providers/umami.ts',
],
externals: ['#analytics'],
replace: {
'import.meta.env.NPM': 'true',
},
declaration: true,
});
@@ -253,7 +253,8 @@ function defineStorageItem<T>(
): AnalyticsStorageItem<T> {
return {
getValue: async () =>
(await browser.storage.local.get(key))[key] ?? defaultValue,
(await browser.storage.local.get<Record<string, any>>(key))[key] ??
defaultValue,
setValue: (newValue) => browser.storage.local.set({ [key]: newValue }),
};
}
@@ -22,10 +22,10 @@ export default defineWxtModule({
// Paths
const wxtAnalyticsFolder = resolve(wxt.config.wxtDir, 'analytics');
const wxtAnalyticsIndex = resolve(wxtAnalyticsFolder, 'index.ts');
const clientModuleId = import.meta.env.NPM
const clientModuleId = process.env.NPM
? '@wxt-dev/analytics'
: resolve(wxt.config.modulesDir, 'analytics/client');
const pluginModuleId = import.meta.env.NPM
const pluginModuleId = process.env.NPM
? '@wxt-dev/analytics/background-plugin'
: resolve(wxt.config.modulesDir, 'analytics/background-plugin');
@@ -40,7 +40,7 @@ export default defineWxtModule({
// Generate #analytics module
const wxtAnalyticsCode = [
`import { createAnalytics } from '${
import.meta.env.NPM
process.env.NPM
? clientModuleId
: relative(wxtAnalyticsFolder, clientModuleId)
}';`,
+4 -6
View File
@@ -35,7 +35,7 @@
}
},
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"files": [
"dist"
],
@@ -43,7 +43,7 @@
"dev": "buildc --deps-only -- wxt",
"dev:build": "buildc --deps-only -- wxt build",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"build": "buildc -- tsdown",
"prepack": "pnpm -s build",
"prepare": "buildc --deps-only -- wxt prepare"
},
@@ -51,11 +51,9 @@
"wxt": ">=0.20.0"
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"@types/ua-parser-js": "^0.7.39",
"publint": "^0.3.12",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"wxt": "workspace:*"
},
"dependencies": {
+17
View File
@@ -0,0 +1,17 @@
import { defineConfig } from 'tsdown';
export default defineConfig({
entry: {
index: './modules/analytics/client.ts',
module: './modules/analytics/index.ts',
'background-plugin': './modules/analytics/background-plugin.ts',
types: './modules/analytics/types.ts',
'providers/google-analytics-4':
'./modules/analytics/providers/google-analytics-4.ts',
'providers/umami': './modules/analytics/providers/umami.ts',
},
external: ['#analytics'],
define: {
'process.env.NPM': 'true',
},
});
+7 -9
View File
@@ -27,7 +27,7 @@
"version": "1.1.0",
"type": "module",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"types": "./dist/index.d.mts",
@@ -38,7 +38,7 @@
"dist"
],
"scripts": {
"build": "buildc -- unbuild",
"build": "buildc -- tsdown",
"check": "pnpm build && check"
},
"peerDependencies": {
@@ -46,15 +46,13 @@
},
"dependencies": {
"defu": "^6.1.4",
"fs-extra": "^11.3.1",
"sharp": "^0.34.1"
"fs-extra": "^11.3.3",
"sharp": "^0.34.5"
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"oxlint": "^1.14.0",
"publint": "^0.3.12",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"oxlint": "^1.43.0",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"wxt": "workspace:*"
}
}
+6 -7
View File
@@ -1,7 +1,7 @@
{
"name": "@wxt-dev/browser",
"description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome",
"version": "0.1.4",
"version": "0.1.36",
"type": "module",
"main": "src/index.mjs",
"types": "src/index.d.ts",
@@ -23,12 +23,11 @@
"src"
],
"devDependencies": {
"@types/chrome": "0.1.6",
"fs-extra": "^11.3.1",
"nano-spawn": "^1.0.2",
"tsx": "4.20.5",
"typescript": "^5.9.2",
"vitest": "^3.2.4"
"@types/chrome": "0.1.36",
"fs-extra": "^11.3.3",
"nano-spawn": "^2.0.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"dependencies": {
"@types/filesystem": "*",
+1 -3
View File
@@ -60,9 +60,7 @@ for (const { file, srcPath, destPath } of declarationFileMapping) {
// Done!
console.log(
'\x1b[32m✔\x1b[0m Done in ' + performance.now().toFixed(0) + ' ms',
);
console.log('\x1b[32m✔\x1b[0m Done in ' + performance.now().toFixed(0) + ' ms');
// Transformations
+2712 -2389
View File
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -24,10 +24,9 @@
],
"devDependencies": {
"@types/chrome": "{{chromeTypesVersion}}",
"fs-extra": "^11.3.1",
"nano-spawn": "^1.0.2",
"tsx": "4.19.4",
"typescript": "^5.9.2",
"vitest": "^3.1.2"
"fs-extra": "^11.3.3",
"nano-spawn": "^2.0.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
}
}
+8 -10
View File
@@ -21,13 +21,13 @@
"license": "MIT",
"funding": "https://github.com/sponsors/wxt-dev",
"scripts": {
"build": "buildc -- unbuild",
"build": "buildc -- tsdown",
"check": "buildc --deps-only -- check",
"test": "buildc --deps-only -- vitest"
},
"dependencies": {
"@wxt-dev/browser": "workspace:^",
"chokidar": "^4.0.3",
"chokidar": "^5.0.0",
"confbox": "^0.1.8 || ^0.2.2",
"fast-glob": "^3.3.3"
},
@@ -40,19 +40,17 @@
}
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"@types/node": "^20.17.6",
"oxlint": "^1.14.0",
"publint": "^0.3.12",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"vitest": "^3.2.4",
"vitest-plugin-random-seed": "^1.1.1",
"oxlint": "^1.43.0",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"vitest": "^4.0.18",
"vitest-plugin-random-seed": "^1.1.2",
"wxt": "workspace:*"
},
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"import": {
+2 -2
View File
@@ -44,7 +44,7 @@ export default defineWxtModule<I18nOptions>({
options ?? {};
const getLocalizationFiles = async () => {
const files = await glob('*.{json,json5,yml,yaml,toml}', {
const files = await glob('*.{json,json5,jsonc,yml,yaml,toml}', {
cwd: localesDir,
absolute: true,
});
@@ -88,7 +88,7 @@ export default defineWxtModule<I18nOptions>({
)!;
if (defaultLocaleFile == null) {
throw Error(
`\`[i18n]\` Required localization file does not exist: \`<localesDir>/${wxt.config.manifest.default_locale}.{json|json5|yml|yaml|toml}\``,
`\`[i18n]\` Required localization file does not exist: \`<localesDir>/${wxt.config.manifest.default_locale}.{json|json5|jsonc|yml|yaml|toml}\``,
);
}
+13
View File
@@ -0,0 +1,13 @@
import { defineConfig } from 'tsdown';
const entry = ['src/index.ts', 'src/build.ts', 'src/module.ts'];
export default defineConfig([
{
entry,
},
{
entry,
format: 'cjs',
},
]);
+12
View File
@@ -1,5 +1,17 @@
# Changelog
## v1.1.5
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.4...module-react-v1.1.5)
### 🏡 Chore
- **deps:** Support @vitejs/plugin-react@5 ([9170159b](https://github.com/wxt-dev/wxt/commit/9170159b))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v1.1.4
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.3...module-react-v1.1.4)
-12
View File
@@ -1,12 +0,0 @@
import { defineBuildConfig } from 'unbuild';
import { resolve } from 'node:path';
export default defineBuildConfig({
rootDir: resolve(__dirname, 'modules'),
outDir: resolve(__dirname, 'dist'),
entries: [{ input: 'react.ts', name: 'index' }],
rollup: {
emitCJS: true,
},
declaration: true,
});
+10 -12
View File
@@ -18,11 +18,11 @@
},
"license": "MIT",
"funding": "https://github.com/sponsors/wxt-dev",
"version": "1.1.4",
"version": "1.1.5",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"import": {
@@ -41,24 +41,22 @@
"scripts": {
"dev": "wxt",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"build": "buildc -- tsdown",
"prepare": "buildc --deps-only -- wxt prepare"
},
"peerDependencies": {
"wxt": ">=0.19.16"
},
"dependencies": {
"@vitejs/plugin-react": "^4.4.1"
"@vitejs/plugin-react": "^4.4.1 || ^5.0.0"
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
"publint": "^0.3.12",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"@types/react": "^19.2.13",
"@types/react-dom": "^19.2.3",
"publint": "^0.3.17",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"typescript": "^5.9.3",
"wxt": "workspace:*"
}
}
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from 'tsdown';
const entry = {
index: './modules/react.ts',
};
export default defineConfig([
{
entry,
},
{
entry,
format: 'cjs',
},
]);
-12
View File
@@ -1,12 +0,0 @@
import { defineBuildConfig } from 'unbuild';
import { resolve } from 'node:path';
export default defineBuildConfig({
rootDir: resolve(__dirname, 'modules'),
outDir: resolve(__dirname, 'dist'),
entries: [{ input: 'solid.ts', name: 'index' }],
rollup: {
emitCJS: true,
},
declaration: true,
});
+6 -8
View File
@@ -22,7 +22,7 @@
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"import": {
@@ -41,21 +41,19 @@
"scripts": {
"dev": "wxt",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"build": "buildc -- tsdown",
"prepare": "buildc --deps-only -- wxt prepare"
},
"peerDependencies": {
"wxt": ">=0.19.16"
},
"dependencies": {
"vite-plugin-solid": "^2.11.6"
"vite-plugin-solid": "^2.11.10"
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"publint": "^0.3.12",
"solid-js": "^1.9.9",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"publint": "^0.3.17",
"solid-js": "^1.9.11",
"typescript": "^5.9.3",
"wxt": "workspace:*"
}
}
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from 'tsdown';
const entry = {
index: './modules/solid.ts',
};
export default defineConfig([
{
entry,
},
{
entry,
format: 'cjs',
},
]);
+4 -6
View File
@@ -22,7 +22,7 @@
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"import": {
@@ -39,7 +39,7 @@
"dist"
],
"scripts": {
"build": "buildc -- unbuild",
"build": "buildc -- tsdown",
"check": "pnpm build && check"
},
"peerDependencies": {
@@ -50,10 +50,8 @@
"@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0"
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"publint": "^0.3.12",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"wxt": "workspace:*"
}
}
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from 'tsdown';
const entry = {
index: './src/index.ts',
};
export default defineConfig([
{
entry,
},
{
entry,
format: 'cjs',
},
]);
+4 -6
View File
@@ -22,7 +22,7 @@
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"import": {
@@ -39,7 +39,7 @@
"dist"
],
"scripts": {
"build": "buildc -- unbuild",
"build": "buildc -- tsdown",
"check": "pnpm build && check"
},
"peerDependencies": {
@@ -49,10 +49,8 @@
"@vitejs/plugin-vue": "^5.2.3 || ^6.0.0"
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"publint": "^0.3.12",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"wxt": "workspace:*"
}
}
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from 'tsdown';
const entry = {
index: './src/index.ts',
};
export default defineConfig([
{
entry,
},
{
entry,
format: 'cjs',
},
]);
+6 -10
View File
@@ -24,21 +24,17 @@
"check": "pnpm build && check",
"test": "buildc --deps-only -- vitest",
"dev": "tsx --trace-warnings dev.ts",
"build": "buildc -- unbuild",
"build": "buildc -- tsdown",
"prepublishOnly": "pnpm build"
},
"dependencies": {},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"oxlint": "^1.14.0",
"publint": "^0.3.12",
"tsx": "4.20.5",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"vitest": "^3.2.4"
"oxlint": "^1.43.0",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"main": "dist/index.mjs",
"types": "dist/index.d.ts",
"types": "dist/index.d.mts",
"exports": {
".": {
"types": "./dist/index.d.mts",
+56 -1
View File
@@ -1,5 +1,60 @@
# Changelog
## v1.2.6
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.5...storage-v1.2.6)
## v1.2.5
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.4...storage-v1.2.5)
## v1.2.4
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.3...storage-v1.2.4)
## v1.2.3
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.2...storage-v1.2.3)
## v1.2.2
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.1...storage-v1.2.2)
## v1.2.1
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.0...storage-v1.2.1)
## v1.2.0
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.1.1...storage-v1.2.0)
### 🚀 Enhancements
- **storage:** Add `onMigrationComplete` callback ([#1514](https://github.com/wxt-dev/wxt/pull/1514))
- **storage:** Add `debug` option to enable migration logs ([#1513](https://github.com/wxt-dev/wxt/pull/1513))
### 🩹 Fixes
- Fix typescript error on `defineItem` fallback ([#1601](https://github.com/wxt-dev/wxt/pull/1601))
- Use `@wxt-dev/browser` instead of `@types/chrome` ([#1645](https://github.com/wxt-dev/wxt/pull/1645))
### 🏡 Chore
- **deps:** Update all dependencies ([#1568](https://github.com/wxt-dev/wxt/pull/1568))
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
- Change browser workspace dependency to `^` ([c7335add](https://github.com/wxt-dev/wxt/commit/c7335add))
- **deps:** Upgrade oxlint from 0.16.8 to 1.14.0 ([a01928e0](https://github.com/wxt-dev/wxt/commit/a01928e0))
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Anh71me ([@iyume](https://github.com/iyume))
- Ergou <ma2808203259@hotmail.com>
## v1.1.1
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.1.0...storage-v1.1.1)
@@ -69,4 +124,4 @@ It's still shipped inside WXT and accessible via `wxt/storage`, but now:
[Read the docs](https://wxt.dev/storage.html) for more details.
> This is apart of the v1.0 initiative for WXT.
> This is apart of the v1.0 initiative for WXT.
+8 -10
View File
@@ -1,7 +1,7 @@
{
"name": "@wxt-dev/storage",
"description": "Web extension storage API provided by WXT, supports all browsers.",
"version": "1.1.1",
"version": "1.2.6",
"type": "module",
"repository": {
"type": "git",
@@ -25,7 +25,7 @@
"license": "MIT",
"funding": "https://github.com/sponsors/wxt-dev",
"scripts": {
"build": "buildc -- unbuild",
"build": "buildc -- tsdown",
"check": "buildc --deps-only -- check",
"test": "buildc --deps-only -- vitest"
},
@@ -35,16 +35,14 @@
"dequal": "^2.0.3"
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"@webext-core/fake-browser": "^1.3.2",
"oxlint": "^1.14.0",
"publint": "^0.3.12",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"vitest": "^3.2.4"
"@webext-core/fake-browser": "^1.3.4",
"oxlint": "^1.43.0",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"types": "./dist/index.d.mts",
@@ -1315,6 +1315,18 @@ describe('Storage Utils', () => {
});
expectTypeOf(item).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
});
it('should define a non-null value when options are passed with a non-null init function', () => {
const item = storage.defineItem(`local:test`, {
init: () => 123,
});
expectTypeOf(item).toEqualTypeOf<WxtStorageItem<number, {}>>();
const item2 = storage.defineItem(`local:test`, {
init: () => Promise.resolve(123),
});
expectTypeOf(item2).toEqualTypeOf<WxtStorageItem<number, {}>>();
});
});
});
+11 -2
View File
@@ -519,7 +519,7 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
const watchListeners = new Set<(changes: StorageAreaChanges) => void>();
return {
getItem: async (key) => {
const res = await getStorageArea().get(key);
const res = await getStorageArea().get<Record<string, any>>(key);
return res[key];
},
getItems: async (keys) => {
@@ -560,7 +560,10 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
},
watch(key, cb) {
const listener = (changes: StorageAreaChanges) => {
const change = changes[key];
const change = changes[key] as {
newValue?: any;
oldValue?: any | null;
} | null;
if (change == null) return;
if (dequal(change.newValue, change.oldValue)) return;
cb(change.newValue ?? null, change.oldValue ?? null);
@@ -749,6 +752,12 @@ export interface WxtStorage {
key: StorageItemKey,
options: WxtStorageItemOptions<TValue> & { defaultValue: TValue },
): WxtStorageItem<TValue, TMetadata>;
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
key: StorageItemKey,
options: WxtStorageItemOptions<TValue> & {
init: () => TValue | Promise<TValue>;
},
): WxtStorageItem<TValue, TMetadata>;
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
key: StorageItemKey,
options: WxtStorageItemOptions<TValue>,
+5 -7
View File
@@ -21,7 +21,7 @@
"license": "MIT",
"type": "module",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"types": "./dist/index.d.mts",
@@ -32,7 +32,7 @@
"dist"
],
"scripts": {
"build": "buildc -- unbuild",
"build": "buildc -- tsdown",
"check": "buildc --deps-only -- check"
},
"peerDependencies": {
@@ -40,11 +40,9 @@
"wxt": ">=0.19.0"
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"oxlint": "^1.14.0",
"publint": "^0.3.12",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"oxlint": "^1.43.0",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"unocss": "^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0",
"wxt": "workspace:*"
},
@@ -1,15 +0,0 @@
import { defineBuildConfig } from 'unbuild';
import { resolve } from 'node:path';
export default defineBuildConfig({
rootDir: resolve(__dirname, 'modules/webextension-polyfill'),
outDir: resolve(__dirname, 'dist'),
entries: [
{ input: 'index.ts', name: 'index' },
{ input: 'browser.ts', name: 'browser' },
],
replace: {
'process.env.NPM': 'true',
},
declaration: true,
});
+5 -7
View File
@@ -20,7 +20,7 @@
"version": "1.0.0",
"type": "module",
"main": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"types": "./dist/index.d.mts",
@@ -37,7 +37,7 @@
"scripts": {
"dev": "wxt",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"build": "buildc -- tsdown",
"prepare": "buildc --deps-only -- wxt prepare"
},
"peerDependencies": {
@@ -45,11 +45,9 @@
"wxt": ">=0.20.0"
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"@types/webextension-polyfill": "^0.12.3",
"publint": "^0.3.12",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"@types/webextension-polyfill": "^0.12.4",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"webextension-polyfill": "^0.12.0",
"wxt": "workspace:*"
}
@@ -0,0 +1,11 @@
import { defineConfig } from 'tsdown';
export default defineConfig({
entry: {
index: 'modules/webextension-polyfill/index.ts',
browser: 'modules/webextension-polyfill/browser.ts',
},
define: {
'process.env.NPM': 'true',
},
});
+8 -8
View File
@@ -18,19 +18,19 @@
},
"dependencies": {
"@wxt-dev/i18n": "workspace:*",
"react": "^19.1.1",
"react-dom": "^19.1.1"
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
"devDependencies": {
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
"@types/react": "^19.2.13",
"@types/react-dom": "^19.2.3",
"@wxt-dev/auto-icons": "workspace:*",
"@wxt-dev/unocss": "workspace:*",
"sass": "^1.92.0",
"typescript": "^5.9.2",
"sass": "^1.97.3",
"typescript": "^5.9.3",
"unocss": "^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0",
"vitest": "^3.2.4",
"vitest-plugin-random-seed": "^1.1.1",
"vitest": "^4.0.18",
"vitest-plugin-random-seed": "^1.1.2",
"wxt": "workspace:*"
},
"buildc": {
+150
View File
@@ -1,5 +1,155 @@
# Changelog
## v0.20.14
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.13...wxt-v0.20.14)
### 🚀 Enhancements
- Add `onBeforeMount` callback to `createIframeUi` options ([#1899](https://github.com/wxt-dev/wxt/pull/1899))
### 🩹 Fixes
- Remove failing test ([6fb4cee4](https://github.com/wxt-dev/wxt/commit/6fb4cee4))
- Rename and make firefoxPrefs in web-ext.config.ts work ([#2068](https://github.com/wxt-dev/wxt/pull/2068))
- **content-script-context:** Handle undefined browser.runtime ([#2042](https://github.com/wxt-dev/wxt/pull/2042))
- `MaxListenersExceededWarning` ([#2058](https://github.com/wxt-dev/wxt/pull/2058))
### 📖 Documentation
- Fix alignment of 2 last comments of regex patterns ([#2012](https://github.com/wxt-dev/wxt/pull/2012))
### 🏡 Chore
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
- Upgrade major deps ([#2070](https://github.com/wxt-dev/wxt/pull/2070))
- Refresh lockfile and upgrade subdependencies ([#2071](https://github.com/wxt-dev/wxt/pull/2071))
### ❤️ Contributors
- Dareka ([@darekadareka](https://github.com/darekadareka))
- Mark ([@mjfaga](https://github.com/mjfaga))
- Ariasuni <aria@ariasuni.eu>
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
- Bohuslavsemenov ([@bohuslavsemenov](https://github.com/bohuslavsemenov))
## v0.20.13
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.12...wxt-v0.20.13)
### 🚀 Enhancements
- Remove script element immediately in injectScript ([#1761](https://github.com/wxt-dev/wxt/pull/1761))
- Add `modifyScript` option to `injectScript` ([#1762](https://github.com/wxt-dev/wxt/pull/1762))
- Make `injectScript` return the created script element ([#1838](https://github.com/wxt-dev/wxt/pull/1838))
- Support `.wxtrc` config file ([#1833](https://github.com/wxt-dev/wxt/pull/1833))
### 🩹 Fixes
- Make `injectScript` wait until script is actually loaded ([#1763](https://github.com/wxt-dev/wxt/pull/1763))
- Don't return promises from unlisted scripts that do not have an async `main` function ([#1907](https://github.com/wxt-dev/wxt/pull/1907))
### 💅 Refactors
- Use `script.text` instead of `innerHTML` in `injectScript` ([#1764](https://github.com/wxt-dev/wxt/pull/1764))
### ❤️ Contributors
- Rxliuli ([@rxliuli](https://github.com/rxliuli))
- Sebastian Landwehr <info@sebastianlandwehr.com>
- Johan Kiviniemi ([@ion1](https://github.com/ion1))
## v0.20.12
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.11...wxt-v0.20.12)
### 🚀 Enhancements
- Remove `data-wxt-*` attributes ([#1913](https://github.com/wxt-dev/wxt/pull/1913))
- Default to using `use_dynamic_url: true` for content script css files ([#1993](https://github.com/wxt-dev/wxt/pull/1993))
### 🩹 Fixes
- Wxt normal logs are drowned by `dotenv@17.0.0` ads ([#1883](https://github.com/wxt-dev/wxt/pull/1883))
- Optimize `splitShadowRootCss` ([#1934](https://github.com/wxt-dev/wxt/pull/1934))
- `wxt prepare` fails with the error "__vite_ssr_exportName__ is not defined" when using Vite 7 ([#1884](https://github.com/wxt-dev/wxt/pull/1884))
### 🏡 Chore
- Use `linkedom` a test instead of `happy-dom` ([#1957](https://github.com/wxt-dev/wxt/pull/1957))
- Add support for `vite-node` v5 ([#2001](https://github.com/wxt-dev/wxt/pull/2001))
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
- **dev-deps:** Upgrade `happy-dom` to address CVE-2025-61927 ([#2002](https://github.com/wxt-dev/wxt/pull/2002))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Jaguar Zhou ([@aiktb](https://github.com/aiktb))
- Alexander Harding <noreply@harding.dev>
- Florian Kühne ([@ZerGo0](https://github.com/ZerGo0))
## v0.20.11
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.10...wxt-v0.20.11)
### 🩹 Fixes
- Split `wxt/testing` into separate modules to fix issues with `jsdom` and `happy-dom` ([#1844](https://github.com/wxt-dev/wxt/pull/1844))
- `input_components` is supported by mv3 ([#1881](https://github.com/wxt-dev/wxt/pull/1881))
### 🏡 Chore
- **deps:** Upgrade project subdependencies ([#1882](https://github.com/wxt-dev/wxt/pull/1882))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Qijia Liu <liumeo@pku.edu.cn>
- Marcellino Ornelas ([@marcellino-ornelas](https://github.com/marcellino-ornelas))
## v0.20.10
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.9...wxt-v0.20.10)
### 🏡 Chore
- **deps:** Upgrade non-breaking production dependencies ([#1877](https://github.com/wxt-dev/wxt/pull/1877))
- **deps:** Upgrade web-ext-run to support CDP ([#1879](https://github.com/wxt-dev/wxt/pull/1879))
- **deps:** `publish-browser-extension` upgrade ([#1880](https://github.com/wxt-dev/wxt/pull/1880))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.20.9
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.8...wxt-v0.20.9)
### 🩹 Fixes
- **types:** Use TType for DocumentEventMap key in ctx.addEventListener ([#1863](https://github.com/wxt-dev/wxt/pull/1863))
- Prevent Unlisted CSS from being excluded in the build when using CSS preprocessor ([#1590](https://github.com/wxt-dev/wxt/pull/1590))
### 🏡 Chore
- **deps:** Upgrade oxlint from 0.16.8 to 1.14.0 ([a01928e0](https://github.com/wxt-dev/wxt/commit/a01928e0))
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
- Upgrade nano-spawn to v1 ([5fefd8e0](https://github.com/wxt-dev/wxt/commit/5fefd8e0))
- Upgrade faker to v10 ([984568e0](https://github.com/wxt-dev/wxt/commit/984568e0))
- Upgrade dotenv to v17.2.2 ([380a9630](https://github.com/wxt-dev/wxt/commit/380a9630))
- Upgrade happy-dom to v18.0.1 ([c1c3d3b7](https://github.com/wxt-dev/wxt/commit/c1c3d3b7))
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Kim Gyeongjae ([@PortalCube](https://github.com/PortalCube))
- Atomie CHEN <atomic_cwh@163.com>
## v0.20.8
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.7...wxt-v0.20.8)
-46
View File
@@ -1,46 +0,0 @@
import { defineBuildConfig } from 'unbuild';
import { version, exports } from './package.json';
import { readFile, writeFile } from 'fs/promises';
import {
virtualEntrypointModuleNames,
virtualModuleNames,
} from './src/core/utils/virtual-modules';
export default defineBuildConfig([
// Non-virtual modules can be transpiled with mkdist
{
entries: [
{
builder: 'mkdist',
input: 'src',
pattern: ['**/*', '!**/__tests__', '!**/*.md', '!virtual', '!@types'],
declaration: true,
},
],
hooks: {
async 'build:done'() {
// Replace any template variables in output files
await replaceVars('dist/version.mjs', { version });
},
},
},
// Virtual modules must be bundled individually
...virtualModuleNames.map((moduleName) => ({
entries: [`src/virtual/${moduleName}.ts`],
externals: [
...virtualEntrypointModuleNames.map((name) => `virtual:user-${name}`),
'virtual:wxt-plugins',
'virtual:app-config',
...Object.keys(exports).map((path) => 'wxt' + path.slice(1)), // ./utils/storage => wxt/utils/storage
],
})),
]);
async function replaceVars(file: string, vars: Record<string, string>) {
let text = await readFile(file, 'utf8');
Object.entries(vars).forEach(([name, value]) => {
text = text.replaceAll(`{{${name}}}`, value);
});
await writeFile(file, text, 'utf8');
}
+16 -30
View File
@@ -17,22 +17,22 @@ describe('Auto Imports', () => {
// Generated by wxt
export {}
declare global {
const ContentScriptContext: typeof import('wxt/utils/content-script-context')['ContentScriptContext']
const InvalidMatchPattern: typeof import('wxt/utils/match-patterns')['InvalidMatchPattern']
const MatchPattern: typeof import('wxt/utils/match-patterns')['MatchPattern']
const browser: typeof import('wxt/browser')['browser']
const createIframeUi: typeof import('wxt/utils/content-script-ui/iframe')['createIframeUi']
const createIntegratedUi: typeof import('wxt/utils/content-script-ui/integrated')['createIntegratedUi']
const createShadowRootUi: typeof import('wxt/utils/content-script-ui/shadow-root')['createShadowRootUi']
const defineAppConfig: typeof import('wxt/utils/define-app-config')['defineAppConfig']
const defineBackground: typeof import('wxt/utils/define-background')['defineBackground']
const defineContentScript: typeof import('wxt/utils/define-content-script')['defineContentScript']
const defineUnlistedScript: typeof import('wxt/utils/define-unlisted-script')['defineUnlistedScript']
const defineWxtPlugin: typeof import('wxt/utils/define-wxt-plugin')['defineWxtPlugin']
const fakeBrowser: typeof import('wxt/testing')['fakeBrowser']
const injectScript: typeof import('wxt/utils/inject-script')['injectScript']
const storage: typeof import('wxt/utils/storage')['storage']
const useAppConfig: typeof import('wxt/utils/app-config')['useAppConfig']
const ContentScriptContext: typeof import('wxt/utils/content-script-context').ContentScriptContext
const InvalidMatchPattern: typeof import('wxt/utils/match-patterns').InvalidMatchPattern
const MatchPattern: typeof import('wxt/utils/match-patterns').MatchPattern
const browser: typeof import('wxt/browser').browser
const createIframeUi: typeof import('wxt/utils/content-script-ui/iframe').createIframeUi
const createIntegratedUi: typeof import('wxt/utils/content-script-ui/integrated').createIntegratedUi
const createShadowRootUi: typeof import('wxt/utils/content-script-ui/shadow-root').createShadowRootUi
const defineAppConfig: typeof import('wxt/utils/define-app-config').defineAppConfig
const defineBackground: typeof import('wxt/utils/define-background').defineBackground
const defineContentScript: typeof import('wxt/utils/define-content-script').defineContentScript
const defineUnlistedScript: typeof import('wxt/utils/define-unlisted-script').defineUnlistedScript
const defineWxtPlugin: typeof import('wxt/utils/define-wxt-plugin').defineWxtPlugin
const fakeBrowser: typeof import('wxt/testing').fakeBrowser
const injectScript: typeof import('wxt/utils/inject-script').injectScript
const storage: typeof import('wxt/utils/storage').storage
const useAppConfig: typeof import('wxt/utils/app-config').useAppConfig
}
// for type re-export
declare global {
@@ -288,20 +288,6 @@ describe('Auto Imports', () => {
);
});
it('should NOT output an ESlint config file by default', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', `<html></html>`);
await project.prepare();
expect(await project.fileExists('.wxt/eslint-auto-imports.mjs')).toBe(
false,
);
expect(await project.fileExists('.wxt/eslintrc-auto-import.json')).toBe(
false,
);
});
it('should allow customizing the output', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', `<html></html>`);
+1 -1
View File
@@ -298,7 +298,7 @@ describe('Module Helpers', () => {
await expect(
project.serializeFile('.wxt/types/imports.d.ts'),
).resolves.toContain("const ref: typeof import('vue')['ref']");
).resolves.toContain("const ref: typeof import('vue').ref");
});
});
});
+73 -83
View File
@@ -343,45 +343,40 @@ describe('Output Directory Structure', () => {
expect(await project.serializeFile('.output/chrome-mv3/background.js'))
.toMatchInlineSnapshot(`
".output/chrome-mv3/background.js
----------------------------------------
var _a, _b;
import { l as logHello, i as initPlugins } from "./chunks/_virtual_wxt-plugins-OjKtWpmY.js";
function defineBackground(arg) {
if (arg == null || typeof arg === "function") return { main: arg };
return arg;
}
const definition = defineBackground({
type: "module",
main() {
logHello("background");
".output/chrome-mv3/background.js
----------------------------------------
import { l as logHello, i as initPlugins } from "./chunks/_virtual_wxt-plugins-OjKtWpmY.js";
function defineBackground(arg) {
if (arg == null || typeof arg === "function") return { main: arg };
return arg;
}
});
((_b = (_a = globalThis.browser) == null ? void 0 : _a.runtime) == null ? void 0 : _b.id) ? globalThis.browser : globalThis.chrome;
function print(method, ...args) {
return;
}
const logger = {
debug: (...args) => print(console.debug, ...args),
log: (...args) => print(console.log, ...args),
warn: (...args) => print(console.warn, ...args),
error: (...args) => print(console.error, ...args)
};
let result;
try {
initPlugins();
result = definition.main();
if (result instanceof Promise) {
console.warn(
"The background's main() function return a promise, but it must be synchronous"
);
const definition = defineBackground({
type: "module",
main() {
logHello("background");
}
});
globalThis.browser?.runtime?.id ? globalThis.browser : globalThis.chrome;
function print(method, ...args) {
return;
}
} catch (err) {
logger.error("The background crashed on startup!");
throw err;
}
"
`);
const logger = {
debug: (...args) => print(console.debug, ...args),
log: (...args) => print(console.log, ...args),
warn: (...args) => print(console.warn, ...args),
error: (...args) => print(console.error, ...args)
};
let result;
try {
initPlugins();
result = definition.main();
if (result instanceof Promise) console.warn("The background's main() function return a promise, but it must be synchronous");
} catch (err) {
logger.error("The background crashed on startup!");
throw err;
}
"
`);
});
it('should generate IIFE background script when type=undefined', async () => {
@@ -421,52 +416,47 @@ describe('Output Directory Structure', () => {
expect(await project.serializeFile('.output/chrome-mv3/background.js'))
.toMatchInlineSnapshot(`
".output/chrome-mv3/background.js
----------------------------------------
var background = function() {
"use strict";
var _a, _b;
function defineBackground(arg) {
if (arg == null || typeof arg === "function") return { main: arg };
return arg;
".output/chrome-mv3/background.js
----------------------------------------
var background = (function() {
"use strict";
function defineBackground(arg) {
if (arg == null || typeof arg === "function") return { main: arg };
return arg;
}
function logHello(name) {
console.log(\`Hello \${name}!\`);
}
const definition = defineBackground({
main() {
logHello("background");
}
function logHello(name) {
console.log(\`Hello \${name}!\`);
}
const definition = defineBackground({
main() {
logHello("background");
}
});
function initPlugins() {
}
((_b = (_a = globalThis.browser) == null ? void 0 : _a.runtime) == null ? void 0 : _b.id) ? globalThis.browser : globalThis.chrome;
function print(method, ...args) {
return;
}
const logger = {
debug: (...args) => print(console.debug, ...args),
log: (...args) => print(console.log, ...args),
warn: (...args) => print(console.warn, ...args),
error: (...args) => print(console.error, ...args)
};
let result;
try {
initPlugins();
result = definition.main();
if (result instanceof Promise) {
console.warn(
"The background's main() function return a promise, but it must be synchronous"
);
}
} catch (err) {
logger.error("The background crashed on startup!");
throw err;
}
const result$1 = result;
return result$1;
}();
"
`);
});
function initPlugins() {
}
globalThis.browser?.runtime?.id ? globalThis.browser : globalThis.chrome;
function print(method, ...args) {
return;
}
const logger = {
debug: (...args) => print(console.debug, ...args),
log: (...args) => print(console.log, ...args),
warn: (...args) => print(console.warn, ...args),
error: (...args) => print(console.error, ...args)
};
let result;
try {
initPlugins();
result = definition.main();
if (result instanceof Promise) console.warn("The background's main() function return a promise, but it must be synchronous");
} catch (err) {
logger.error("The background crashed on startup!");
throw err;
}
var background_entrypoint_default = result;
return background_entrypoint_default;
})();
"
`);
});
});
+1 -1
View File
@@ -16,7 +16,7 @@ describe('Remote Code', () => {
const output = await project.serializeFile('.output/chrome-mv3/popup.js');
expect(output).toContain(
// Some text that will hopefully be in future versions of this script
'lodash.com',
'__lodash_placeholder__',
);
expect(output).not.toContain(url);
expect(
+3 -5
View File
@@ -139,17 +139,15 @@ describe('User Config', () => {
).toBe(true);
});
it('should throw error when config file not exist', async () => {
it('should not throw error when config file not exist', async () => {
const project = new TestProject();
project.addFile(
'src/entrypoints/background.ts',
'entrypoints/background.ts',
`export default defineBackground(
() => console.log('Hello background'),
);`,
);
await expect(
project.build({ configFile: 'foo.config.ts' }),
).rejects.toThrowError(/not found/);
await project.build({ configFile: 'foo.config.ts' });
});
});
+62 -56
View File
@@ -1,12 +1,12 @@
{
"name": "wxt",
"type": "module",
"version": "0.20.8",
"version": "0.20.14",
"description": "⚡ Next-gen Web Extension Framework",
"license": "MIT",
"scripts": {
"wxt": "tsx src/cli/index.ts",
"build": "buildc -- unbuild",
"build": "buildc -- tsdown --config-loader unrun",
"check": "pnpm build && pnpm run --reporter-hide-prefix /^check:.*/",
"check:default": "check",
"check:tsc-virtual": "tsc --noEmit -p src/virtual",
@@ -18,67 +18,65 @@
"dependencies": {
"@1natsu/wait-element": "^4.1.2",
"@aklinker1/rollup-plugin-visualizer": "5.12.0",
"@webext-core/fake-browser": "^1.3.2",
"@webext-core/isolated-element": "^1.1.2",
"@webext-core/fake-browser": "^1.3.4",
"@webext-core/isolated-element": "^1.1.3",
"@webext-core/match-patterns": "^1.0.3",
"@wxt-dev/browser": "workspace:^",
"@wxt-dev/storage": "workspace:^1.0.0",
"async-mutex": "^0.5.0",
"c12": "^3.0.3",
"c12": "^3.3.3",
"cac": "^6.7.14",
"chokidar": "^4.0.3",
"ci-info": "^4.2.0",
"chokidar": "^5.0.0",
"ci-info": "^4.4.0",
"consola": "^3.4.2",
"defu": "^6.1.4",
"dotenv": "^17.2.2",
"dotenv-expand": "^12.0.2",
"esbuild": "^0.25.0",
"dotenv": "^17.2.4",
"dotenv-expand": "^12.0.3",
"esbuild": "^0.27.1",
"fast-glob": "^3.3.3",
"filesize": "^10.1.6",
"fs-extra": "^11.3.1",
"get-port-please": "^3.1.2",
"giget": "^1.2.3 || ^2.0.0",
"hookable": "^5.5.3",
"import-meta-resolve": "^4.1.0",
"filesize": "^11.0.13",
"fs-extra": "^11.3.3",
"get-port-please": "^3.2.0",
"giget": "^1.2.3 || ^2.0.0 || ^3.0.0",
"hookable": "^6.0.1",
"import-meta-resolve": "^4.2.0",
"is-wsl": "^3.1.0",
"json5": "^2.2.3",
"jszip": "^3.10.1",
"linkedom": "^0.18.10",
"magicast": "^0.3.5",
"minimatch": "^10.0.1",
"nano-spawn": "^1.0.2",
"linkedom": "^0.18.12",
"magicast": "^0.5.2",
"minimatch": "^10.1.2",
"nano-spawn": "^2.0.0",
"normalize-path": "^3.0.0",
"nypm": "^0.6.0",
"nypm": "^0.6.5",
"ohash": "^2.0.11",
"open": "^10.1.2",
"ora": "^8.2.0",
"perfect-debounce": "^1.0.0",
"open": "^11.0.0",
"ora": "^9.3.0",
"perfect-debounce": "^2.1.0",
"picocolors": "^1.1.1",
"prompts": "^2.4.2",
"publish-browser-extension": "^2.3.0 || ^3.0.0",
"publish-browser-extension": "^2.3.0 || ^3.0.2 || ^4.0.0",
"scule": "^1.3.0",
"unimport": "^3.13.1 || ^4.0.0 || ^5.0.0",
"vite": "^5.4.19 || ^6.3.4 || ^7.0.0",
"vite-node": "^2.1.4 || ^3.1.2",
"web-ext-run": "^0.2.3"
"vite-node": "^3.2.4 || ^5.0.0",
"web-ext-run": "^0.2.4"
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"@faker-js/faker": "^10.0.0",
"@faker-js/faker": "^10.2.0",
"@types/fs-extra": "^11.0.4",
"@types/lodash.merge": "^4.6.9",
"@types/node": "^20.17.6",
"@types/normalize-path": "^3.0.2",
"@types/prompts": "^2.4.9",
"extract-zip": "^2.0.1",
"happy-dom": "^18.0.1",
"happy-dom": "^20.5.0",
"lodash.merge": "^4.6.2",
"oxlint": "^1.14.0",
"publint": "^0.3.12",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"vitest": "^3.2.4",
"vitest-plugin-random-seed": "^1.1.1"
"oxlint": "^1.43.0",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"vitest": "^4.0.18",
"vitest-plugin-random-seed": "^1.1.2"
},
"peerDependenciesMeta": {},
"repository": {
@@ -109,85 +107,93 @@
"wxt-publish-extension": "./bin/wxt-publish-extension.cjs"
},
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"./utils/app-config": {
"types": "./dist/utils/app-config.d.ts",
"types": "./dist/utils/app-config.d.mts",
"default": "./dist/utils/app-config.mjs"
},
"./utils/inject-script": {
"types": "./dist/utils/inject-script.d.ts",
"types": "./dist/utils/inject-script.d.mts",
"default": "./dist/utils/inject-script.mjs"
},
"./utils/content-script-context": {
"types": "./dist/utils/content-script-context.d.ts",
"types": "./dist/utils/content-script-context.d.mts",
"default": "./dist/utils/content-script-context.mjs"
},
"./utils/content-script-ui/types": {
"types": "./dist/utils/content-script-ui/types.d.ts",
"types": "./dist/utils/content-script-ui/types.d.mts",
"default": "./dist/utils/content-script-ui/types.mjs"
},
"./utils/content-script-ui/integrated": {
"types": "./dist/utils/content-script-ui/integrated.d.ts",
"types": "./dist/utils/content-script-ui/integrated.d.mts",
"default": "./dist/utils/content-script-ui/integrated.mjs"
},
"./utils/content-script-ui/shadow-root": {
"types": "./dist/utils/content-script-ui/shadow-root.d.ts",
"types": "./dist/utils/content-script-ui/shadow-root.d.mts",
"default": "./dist/utils/content-script-ui/shadow-root.mjs"
},
"./utils/content-script-ui/iframe": {
"types": "./dist/utils/content-script-ui/iframe.d.ts",
"types": "./dist/utils/content-script-ui/iframe.d.mts",
"default": "./dist/utils/content-script-ui/iframe.mjs"
},
"./utils/define-app-config": {
"types": "./dist/utils/define-app-config.d.ts",
"types": "./dist/utils/define-app-config.d.mts",
"default": "./dist/utils/define-app-config.mjs"
},
"./utils/define-background": {
"types": "./dist/utils/define-background.d.ts",
"types": "./dist/utils/define-background.d.mts",
"default": "./dist/utils/define-background.mjs"
},
"./utils/define-content-script": {
"types": "./dist/utils/define-content-script.d.ts",
"types": "./dist/utils/define-content-script.d.mts",
"default": "./dist/utils/define-content-script.mjs"
},
"./utils/define-unlisted-script": {
"types": "./dist/utils/define-unlisted-script.d.ts",
"types": "./dist/utils/define-unlisted-script.d.mts",
"default": "./dist/utils/define-unlisted-script.mjs"
},
"./utils/define-wxt-plugin": {
"types": "./dist/utils/define-wxt-plugin.d.ts",
"types": "./dist/utils/define-wxt-plugin.d.mts",
"default": "./dist/utils/define-wxt-plugin.mjs"
},
"./utils/match-patterns": {
"types": "./dist/utils/match-patterns.d.ts",
"types": "./dist/utils/match-patterns.d.mts",
"default": "./dist/utils/match-patterns.mjs"
},
"./utils/split-shadow-root-css": {
"types": "./dist/utils/split-shadow-root-css.d.ts",
"types": "./dist/utils/split-shadow-root-css.d.mts",
"default": "./dist/utils/split-shadow-root-css.mjs"
},
"./utils/storage": {
"types": "./dist/utils/storage.d.ts",
"types": "./dist/utils/storage.d.mts",
"default": "./dist/utils/storage.mjs"
},
"./browser": {
"types": "./dist/browser.d.ts",
"types": "./dist/browser.d.mts",
"default": "./dist/browser.mjs"
},
"./testing/fake-browser": {
"types": "./dist/testing/fake-browser.d.mts",
"default": "./dist/testing/fake-browser.mjs"
},
"./testing/vitest-plugin": {
"types": "./dist/testing/wxt-vitest-plugin.d.mts",
"default": "./dist/testing/wxt-vitest-plugin.mjs"
},
"./testing": {
"types": "./dist/testing/index.d.ts",
"types": "./dist/testing/index.d.mts",
"default": "./dist/testing/index.mjs"
},
"./vite-builder-env": {
"types": "./dist/vite-builder-env.d.ts"
},
"./modules": {
"types": "./dist/modules.d.ts",
"types": "./dist/modules.d.mts",
"default": "./dist/modules.mjs"
}
}
@@ -1,5 +1,4 @@
import { describe, expect, it } from 'vitest';
import { Window } from 'happy-dom';
import { pointToDevServer } from '../devHtmlPrerender';
import {
fakeDevServer,
@@ -7,6 +6,7 @@ import {
} from '../../../../utils/testing/fake-objects';
import { normalizePath } from '../../../../utils/paths';
import { resolve } from 'node:path';
import { parseHTML } from 'linkedom';
describe('Dev HTML Prerender Plugin', () => {
describe('pointToDevServer', () => {
@@ -32,9 +32,7 @@ describe('Dev HTML Prerender Plugin', () => {
// URLs should not be changed
['https://example.com/style.css', 'https://example.com/style.css'],
])('should transform "%s" into "%s"', (input, expected) => {
const { document } = new Window({
url: 'http://localhost',
});
const { document } = parseHTML('<html></html>');
const root = '/some/root';
const config = fakeResolvedConfig({
root,
@@ -55,7 +53,7 @@ describe('Dev HTML Prerender Plugin', () => {
const id = root + '/entrypoints/popup/index.html';
document.head.innerHTML = `<link rel="stylesheet" href="${input}" />`;
pointToDevServer(config, server, id, document as any, 'link', 'href');
pointToDevServer(config, server, id, document, 'link', 'href');
const actual = document.querySelector('link')!;
expect(actual.getAttribute('href')).toBe(expected);
@@ -43,7 +43,7 @@ export function extensionApiMock(config: ResolvedConfig): vite.PluginOption {
const setupTemplate = `
import { vi } from 'vitest';
import { fakeBrowser } from 'wxt/testing';
import { fakeBrowser } from 'wxt/testing/fake-browser';
vi.stubGlobal("chrome", fakeBrowser);
vi.stubGlobal("browser", fakeBrowser);
@@ -26,6 +26,8 @@ export function createKeyboardShortcuts(
return {
start() {
this.stop();
rl ??= readline.createInterface({
input: process.stdin,
terminal: false, // Don't intercept ctrl+C, ctrl+Z, etc
-4
View File
@@ -50,11 +50,7 @@ export async function resolveConfig(
configFile: inlineConfig.configFile,
name: 'wxt',
cwd: inlineConfig.root ?? process.cwd(),
rcFile: false,
});
if (inlineConfig.configFile && metadata.layers?.length === 0) {
throw Error(`Config file "${inlineConfig.configFile}" not found`);
}
userConfig = loadedConfig ?? {};
userConfigMetadata = metadata;
}
+1 -2
View File
@@ -44,7 +44,7 @@ export function createWebExtRunner(): ExtensionRunner {
? {
firefox: wxtUserConfig?.binaries?.firefox,
firefoxProfile: wxtUserConfig?.firefoxProfile,
prefs: wxtUserConfig?.firefoxPrefs,
pref: wxtUserConfig?.firefoxPref,
args: wxtUserConfig?.firefoxArgs,
}
: {
@@ -56,7 +56,6 @@ export function createWebExtRunner(): ExtensionRunner {
),
args: [
'--unsafely-disable-devtools-self-xss-warnings',
'--disable-features=DisableLoadExtensionCommandLineSwitch',
...(wxtUserConfig?.chromiumArgs ?? []),
],
}),
@@ -747,6 +747,7 @@ describe('Manifest Utils', () => {
{
matches: ['*://google.com/*'],
resources: ['content-scripts/one.css'],
use_dynamic_url: true,
},
]);
});
@@ -830,6 +831,7 @@ describe('Manifest Utils', () => {
{
matches: ['*://play.google.com/*'],
resources: ['content-scripts/one.css'],
use_dynamic_url: true,
},
]);
});
@@ -993,6 +995,7 @@ describe('Manifest Utils', () => {
{
resources: ['content-scripts/one.css'],
matches: ['*://google.com/*'],
use_dynamic_url: true,
},
]);
});
@@ -1351,7 +1354,6 @@ describe('Manifest Utils', () => {
event_rules: {},
file_browser_handlers: {},
file_system_provider_capabilities: {},
input_components: {},
nacl_modules: {},
natively_connectable: {},
offline_enabled: {},
+1
View File
@@ -8,6 +8,7 @@ import type { TargetBrowser } from '../../types';
export function loadEnv(mode: string, browser: TargetBrowser) {
return expand(
config({
quiet: true,
// Files on top override files below
path: [
`.env.${mode}.${browser}.local`,
+3 -3
View File
@@ -436,8 +436,8 @@ function discoverIcons(
/^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
/^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
@@ -553,6 +553,7 @@ export function getContentScriptCssWebAccessibleResources(
resources.push({
resources: [cssFile],
use_dynamic_url: true,
matches:
script.options.matches?.map((matchPattern) =>
stripPathFromMatchPattern(matchPattern),
@@ -716,7 +717,6 @@ const mv2OnlyKeys = [
'event_rules',
'file_browser_handlers',
'file_system_provider_capabilities',
'input_components',
'nacl_modules',
'natively_connectable',
'offline_enabled',
+10
View File
@@ -1 +1,11 @@
/**
* The fake browser is automatically used as a mock for the `wxt/browser` import
* when using `wxt/testing/vitest-plugin` with Vitest. It is also setup to
* reset all state before each test.
*
* This module is just a re-export of [@webext-core/fake-browser](https://webext-core.aklinker1.io/fake-browser/triggering-events).
*
* @module wxt/testing/fake-browser
*/
export { fakeBrowser, type FakeBrowser } from '@webext-core/fake-browser';
+4
View File
@@ -1,5 +1,9 @@
/**
* Utilities for unit testing WXT extensions.
*
* @deprecated Use `wxt/testing/*` instead to prevent issues with JSDOM or
* HappyDOM environments. Will be removed in the next major version of WXT.
*
* @module wxt/testing
*/
export * from './fake-browser';
@@ -1,3 +1,10 @@
/**
* Contains a Vitest plugin that configures your test environment to work with
* WXT projects.
*
* @module wxt/testing/vitest
*/
import type * as vite from 'vite';
import {
download,
@@ -16,7 +23,7 @@ import { registerWxt, wxt } from '../core/wxt';
* ```ts
* // vitest.config.ts
* import { defineConfig } from 'vitest/config';
* import { WxtVitest } from 'wxt/testing';
* import { WxtVitest } from 'wxt/testing/vitest-plugin';
*
* export default defineConfig({
* plugins: [WxtVitest()],
+12 -17
View File
@@ -200,7 +200,7 @@ export interface InlineConfig {
*
* @example
* [
* "coverage", // Ignore the coverage directory in the `sourcesRoot`
* "coverage", // Include the coverage directory in the `sourcesRoot`
* ]
*/
includeSources?: string[];
@@ -213,7 +213,7 @@ export interface InlineConfig {
*
* @example
* [
* "coverage", // Include the coverage directory in the `sourcesRoot`
* "coverage", // Ignore the coverage directory in the `sourcesRoot`
* ]
*/
excludeSources?: string[];
@@ -462,8 +462,7 @@ export interface BuildStepOutput {
}
export interface WxtDevServer
extends Omit<WxtBuilderServer, 'listen' | 'close'>,
ServerInfo {
extends Omit<WxtBuilderServer, 'listen' | 'close'>, ServerInfo {
/**
* Stores the current build output of the server.
*/
@@ -567,8 +566,7 @@ export interface BackgroundEntrypointOptions extends BaseEntrypointOptions {
type?: PerBrowserOption<'module'>;
}
export interface BaseContentScriptEntrypointOptions
extends BaseEntrypointOptions {
export interface BaseContentScriptEntrypointOptions extends BaseEntrypointOptions {
matches?: PerBrowserOption<NonNullable<ManifestContentScript['matches']>>;
/**
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
@@ -635,16 +633,14 @@ export interface BaseContentScriptEntrypointOptions
registration?: PerBrowserOption<'manifest' | 'runtime'>;
}
export interface MainWorldContentScriptEntrypointOptions
extends BaseContentScriptEntrypointOptions {
export interface MainWorldContentScriptEntrypointOptions extends BaseContentScriptEntrypointOptions {
/**
* See https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world
*/
world: 'MAIN';
}
export interface IsolatedWorldContentScriptEntrypointOptions
extends BaseContentScriptEntrypointOptions {
export interface IsolatedWorldContentScriptEntrypointOptions extends BaseContentScriptEntrypointOptions {
/**
* See https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts#isolated_world
* @default "ISOLATED"
@@ -780,8 +776,7 @@ export type EntrypointGroup = Entrypoint | Entrypoint[];
export type OnContentScriptStopped = (cb: () => void) => void;
export interface IsolatedWorldContentScriptDefinition
extends IsolatedWorldContentScriptEntrypointOptions {
export interface IsolatedWorldContentScriptDefinition extends IsolatedWorldContentScriptEntrypointOptions {
/**
* Main function executed when the content script is loaded.
*
@@ -792,8 +787,7 @@ export interface IsolatedWorldContentScriptDefinition
main(ctx: ContentScriptContext): any | Promise<any>;
}
export interface MainWorldContentScriptDefinition
extends MainWorldContentScriptEntrypointOptions {
export interface MainWorldContentScriptDefinition extends MainWorldContentScriptEntrypointOptions {
/**
* Main function executed when the content script is loaded.
*
@@ -998,7 +992,7 @@ export interface WebExtConfig {
/**
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#pref
*/
firefoxPrefs?: Record<string, string>;
firefoxPref?: Record<string, boolean | number | string>;
/**
* @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#args
*/
@@ -1544,8 +1538,9 @@ export interface WxtModule<TOptions extends WxtModuleOptions> {
setup?: WxtModuleSetup<TOptions>;
}
export interface WxtModuleWithMetadata<TOptions extends WxtModuleOptions>
extends WxtModule<TOptions> {
export interface WxtModuleWithMetadata<
TOptions extends WxtModuleOptions,
> extends WxtModule<TOptions> {
type: 'local' | 'node_module';
id: string;
}
@@ -32,6 +32,25 @@ describe('Content Script Context', () => {
expect(isValid).toBe(false);
});
it('should not throw when browser.runtime is undefined (extension context fully invalidated)', () => {
const ctx = new ContentScriptContext('test');
const onInvalidated = vi.fn();
ctx.onInvalidated(onInvalidated);
// Simulate complete extension context invalidation where browser.runtime becomes undefined
const originalRuntime = fakeBrowser.runtime;
// @ts-ignore
fakeBrowser.runtime = undefined;
// Should not throw, and should mark as invalid
expect(() => ctx.isInvalid).not.toThrow();
expect(ctx.isInvalid).toBe(true);
expect(onInvalidated).toBeCalled();
// Restore for other tests
fakeBrowser.runtime = originalRuntime;
});
it('should invalidate the current content script when a new context is created', async () => {
const name = 'test';
const onInvalidated = vi.fn();
@@ -21,4 +21,22 @@ describe('defineUnlistedScript', () => {
expect(actual).toEqual({ main });
});
it('should return the result without awaiting for synchronous main functions', () => {
const main = vi.fn(() => 'test');
const actual = defineUnlistedScript(main);
expect(actual).toEqual({ main });
expect(actual.main()).eq('test');
});
it('should return a promise of a result for async main functions', async () => {
const main = vi.fn(() => Promise.resolve('test'));
const actual = defineUnlistedScript(main);
expect(actual).toEqual({ main });
await expect(actual.main()).resolves.toEqual('test');
});
});
@@ -71,7 +71,7 @@ export class ContentScriptContext implements AbortController {
}
get isInvalid(): boolean {
if (browser.runtime.id == null) {
if (browser.runtime?.id == null) {
this.notifyInvalidated(); // Sets `signal.aborted` to true
}
return this.signal.aborted;
@@ -63,9 +63,7 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(
document.querySelector('div[data-wxt-integrated]'),
).not.toBeNull();
expect(document.body.children).toContain(ui.wrapper);
expect(document.querySelector('app')).not.toBeNull();
});
@@ -77,9 +75,7 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(
document.querySelector('pre[data-wxt-integrated]'),
).not.toBeNull();
expect(document.querySelector('pre')).toBe(ui.wrapper);
expect(document.querySelector('app')).not.toBeNull();
});
});
@@ -92,7 +88,7 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(document.querySelector('div[data-wxt-iframe]')).toBeDefined();
expect(document.body.children).toContain(ui.wrapper);
expect(document.querySelector('iframe')).toBeDefined();
});
});
@@ -108,9 +104,7 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(
document.querySelector('test-component[data-wxt-shadow-root]'),
).not.toBeNull();
expect(document.querySelector('test-component')).toBe(ui.shadowHost);
expect(ui.shadow.querySelector('app')).not.toBeNull();
});
@@ -143,7 +137,7 @@ describe('Content Script UIs', () => {
});
expect(ui.wrapper.outerHTML).toMatchInlineSnapshot(
`"<div data-wxt-iframe=""><iframe src="chrome-extension://test-extension-id/page.html"></iframe></div>"`,
`"<div><iframe src="chrome-extension://test-extension-id/page.html"></iframe></div>"`,
);
});
});
@@ -157,7 +151,7 @@ describe('Content Script UIs', () => {
ui.mount();
expect(ui.wrapper.outerHTML).toMatchInlineSnapshot(
`"<div data-wxt-iframe="" style="overflow: visible; position: relative; width: 0px; height: 0px; display: block;"><iframe src="chrome-extension://test-extension-id/page.html" style="position: absolute; top: 0px; left: 0px;"></iframe></div>"`,
`"<div style="overflow: visible; position: relative; width: 0px; height: 0px; display: block;"><iframe src="chrome-extension://test-extension-id/page.html" style="position: absolute; top: 0px; left: 0px;"></iframe></div>"`,
);
});
@@ -170,7 +164,7 @@ describe('Content Script UIs', () => {
ui.mount();
expect(ui.wrapper.outerHTML).toMatchInlineSnapshot(
`"<div data-wxt-iframe="" style="overflow: visible; position: relative; width: 0px; height: 0px; display: block;"><iframe src="chrome-extension://test-extension-id/page.html" style="position: absolute; top: 0px; left: 0px;"></iframe></div>"`,
`"<div style="overflow: visible; position: relative; width: 0px; height: 0px; display: block;"><iframe src="chrome-extension://test-extension-id/page.html" style="position: absolute; top: 0px; left: 0px;"></iframe></div>"`,
);
});
@@ -183,7 +177,7 @@ describe('Content Script UIs', () => {
ui.mount();
expect(ui.wrapper.outerHTML).toMatchInlineSnapshot(
`"<div data-wxt-iframe="" style="overflow: visible; position: relative; width: 0px; height: 0px; display: block;"><iframe src="chrome-extension://test-extension-id/page.html" style="position: absolute; top: 0px; right: 0px;"></iframe></div>"`,
`"<div style="overflow: visible; position: relative; width: 0px; height: 0px; display: block;"><iframe src="chrome-extension://test-extension-id/page.html" style="position: absolute; top: 0px; right: 0px;"></iframe></div>"`,
);
});
@@ -196,7 +190,7 @@ describe('Content Script UIs', () => {
ui.mount();
expect(ui.wrapper.outerHTML).toMatchInlineSnapshot(
`"<div data-wxt-iframe="" style="overflow: visible; position: relative; width: 0px; height: 0px; display: block;"><iframe src="chrome-extension://test-extension-id/page.html" style="position: absolute; bottom: 0px; right: 0px;"></iframe></div>"`,
`"<div style="overflow: visible; position: relative; width: 0px; height: 0px; display: block;"><iframe src="chrome-extension://test-extension-id/page.html" style="position: absolute; bottom: 0px; right: 0px;"></iframe></div>"`,
);
});
@@ -209,7 +203,7 @@ describe('Content Script UIs', () => {
ui.mount();
expect(ui.wrapper.outerHTML).toMatchInlineSnapshot(
`"<div data-wxt-iframe="" style="overflow: visible; position: relative; width: 0px; height: 0px; display: block;"><iframe src="chrome-extension://test-extension-id/page.html" style="position: absolute; bottom: 0px; left: 0px;"></iframe></div>"`,
`"<div style="overflow: visible; position: relative; width: 0px; height: 0px; display: block;"><iframe src="chrome-extension://test-extension-id/page.html" style="position: absolute; bottom: 0px; left: 0px;"></iframe></div>"`,
);
});
@@ -235,7 +229,7 @@ describe('Content Script UIs', () => {
ui.mount();
expect(ui.wrapper.outerHTML).toMatchInlineSnapshot(
`"<div data-wxt-iframe="" style="overflow: visible; position: relative; width: 0px; height: 0px; display: block;"><iframe src="chrome-extension://test-extension-id/page.html" style="position: fixed; top: 0px; bottom: 0px; left: 0px; right: 0px;"></iframe></div>"`,
`"<div style="overflow: visible; position: relative; width: 0px; height: 0px; display: block;"><iframe src="chrome-extension://test-extension-id/page.html" style="position: fixed; top: 0px; bottom: 0px; left: 0px; right: 0px;"></iframe></div>"`,
);
});
@@ -262,9 +256,7 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(
document.querySelector('body > div[data-wxt-integrated]'),
).not.toBeNull();
expect(document.body.children).toContain(ui.wrapper);
});
});
@@ -277,9 +269,9 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(
document.querySelector('#parent > div[data-wxt-integrated]'),
).not.toBeNull();
expect(document.querySelector('#parent')!.children).toContain(
ui.wrapper,
);
});
it('should append the element using an XPath string', () => {
@@ -295,9 +287,7 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(
document.querySelector('#three > div[data-wxt-integrated]'),
).not.toBeNull();
expect(document.querySelector('#three')!.children[0]).toBe(ui.wrapper);
});
});
@@ -310,9 +300,9 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(
document.querySelector('#parent > div[data-wxt-integrated]'),
).not.toBeNull();
expect(document.querySelector('#parent')!.children).toContain(
ui.wrapper,
);
});
});
@@ -325,9 +315,9 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(
document.querySelector('#parent > div[data-wxt-integrated]'),
).not.toBeNull();
expect(document.querySelector('#parent')!.children).toContain(
ui.wrapper,
);
});
});
@@ -354,9 +344,7 @@ describe('Content Script UIs', () => {
ui.mount();
expect(
document.querySelector(
'#parent > div[data-wxt-integrated]:last-child',
),
document.querySelector('#parent')!.lastElementChild,
).not.toBeNull();
});
});
@@ -372,9 +360,7 @@ describe('Content Script UIs', () => {
ui.mount();
expect(
document.querySelector(
'#parent > div[data-wxt-integrated]:first-child',
),
document.querySelector('#parent')!.children[0].firstElementChild,
).not.toBeNull();
});
});
@@ -389,9 +375,7 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(
document.querySelector('body > div[data-wxt-integrated]'),
).not.toBeNull();
expect(document.body.children).toContain(ui.wrapper);
expect(document.querySelector('#parent')).toBeNull();
});
});
@@ -406,11 +390,9 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(
document.querySelector(
'#parent > div[data-wxt-integrated]:first-child',
),
).not.toBeNull();
expect(document.querySelector('#parent')!.firstElementChild).toBe(
ui.wrapper,
);
});
});
@@ -424,11 +406,9 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(
document.querySelector(
'#parent > div[data-wxt-integrated]:last-child',
),
).not.toBeNull();
expect(document.querySelector('#parent')!.lastElementChild).toBe(
ui.wrapper,
);
});
});
@@ -444,9 +424,8 @@ describe('Content Script UIs', () => {
});
ui.mount();
expect(
document.querySelector('body > div[data-wxt-integrated]'),
).not.toBeNull();
expect(document.body.children).toContain(ui.wrapper);
expect(document.querySelector('#parent')).toBeNull();
});
});
@@ -529,23 +508,29 @@ describe('Content Script UIs', () => {
{
name: 'integrated',
createUiFunction: createIntegratedUi,
uiSelector: 'div[data-wxt-integrated]',
uiSelector: '[data-wrapper-name="integrated"]',
},
{
name: 'iframe',
createUiFunction: createIframeUi,
uiSelector: 'div[data-wxt-iframe]',
uiSelector: `[data-wrapper-name="iframe"]`,
},
{
name: 'shadow-root',
createUiFunction: createShadowRootUi,
uiSelector: 'test-component[data-wxt-shadow-root]',
uiSelector: 'test-component',
},
] as const)(
'built-in UI type: $name',
({ name, createUiFunction, uiSelector }) => {
const onMount = vi.fn((wrapper: HTMLElement) => {
if (name === 'shadow-root') return;
wrapper.setAttribute('data-wrapper-name', name);
appendTestApp(wrapper);
});
it('should mount if an anchor already exists at the initialization', async () => {
const onMount = vi.fn(appendTestApp);
ui = await createUiFunction(ctx, {
position: 'inline',
onMount,
@@ -555,6 +540,7 @@ describe('Content Script UIs', () => {
});
appendTestElement({ id: DYNAMIC_CHILD_ID });
ui.autoMount();
await runMicrotasks();
@@ -564,7 +550,6 @@ describe('Content Script UIs', () => {
});
it('should mount when an anchor is dynamically added and unmount when an anchor is removed', async () => {
const onMount = vi.fn(appendTestApp);
const onRemove = vi.fn();
ui = await createUiFunction(ctx, {
position: 'inline',
@@ -599,7 +584,6 @@ describe('Content Script UIs', () => {
describe('options', () => {
it('should auto-mount only once mount and remove when the `once` option is true', async () => {
const onMount = vi.fn(appendTestApp);
const onRemove = vi.fn();
ui = await createUiFunction(ctx, {
position: 'inline',
@@ -644,7 +628,7 @@ describe('Content Script UIs', () => {
it('should throw when anchor is set as type Element', async () => {
ui = await createUiFunction(ctx, {
position: 'inline',
onMount: appendTestApp,
onMount,
anchor: document.documentElement,
page: name === 'iframe' ? '/page.html' : undefined,
name: 'test-component',
@@ -657,7 +641,7 @@ describe('Content Script UIs', () => {
it('should throw when anchor is set as type `() => Element`', async () => {
ui = await createUiFunction(ctx, {
position: 'inline',
onMount: appendTestApp,
onMount,
anchor: () => document.documentElement,
page: name === 'iframe' ? '/page.html' : undefined,
name: 'test-component',
@@ -670,7 +654,6 @@ describe('Content Script UIs', () => {
describe('StopAutoMount', () => {
it('should stop auto-mounting and remove ui when `ui.remove` is called', async () => {
const onMount = vi.fn(appendTestApp);
const onRemove = vi.fn();
ui = await createUiFunction(ctx, {
position: 'inline',
@@ -705,7 +688,6 @@ describe('Content Script UIs', () => {
});
it('should call internal StopAutoMount when `ui.remove` is called', async () => {
const onMount = vi.fn(appendTestApp);
const onRemove = vi.fn();
const onStop = vi.fn();
ui = await createUiFunction(ctx, {
@@ -723,7 +705,6 @@ describe('Content Script UIs', () => {
});
it('should allow calling automount again after internal StopAutoMount is called', async () => {
const onMount = vi.fn(appendTestApp);
ui = await createUiFunction(ctx, {
position: 'inline',
onMount,
@@ -14,7 +14,6 @@ export function createIframeUi<TMounted>(
options: IframeContentScriptUiOptions<TMounted>,
): IframeContentScriptUi<TMounted> {
const wrapper = document.createElement('div');
wrapper.setAttribute('data-wxt-iframe', '');
const iframe = document.createElement('iframe');
// @ts-expect-error: getURL is defined per-project, but not inside the package
iframe.src = browser.runtime.getURL(options.page);
@@ -23,6 +22,7 @@ export function createIframeUi<TMounted>(
let mounted: TMounted | undefined = undefined;
const mount = () => {
applyPosition(wrapper, iframe, options);
options.onBeforeMount?.(wrapper, iframe);
mountUi(wrapper, options);
mounted = options.onMount?.(wrapper, iframe);
};
@@ -46,8 +46,9 @@ export function createIframeUi<TMounted>(
};
}
export interface IframeContentScriptUi<TMounted>
extends ContentScriptUi<TMounted> {
export interface IframeContentScriptUi<
TMounted,
> extends ContentScriptUi<TMounted> {
/**
* The iframe added to the DOM.
*/
@@ -73,4 +74,9 @@ export type IframeContentScriptUiOptions<TMounted> =
* Optionally return a value that can be accessed at `ui.mounted` or in the `onRemove` callback.
*/
onMount?: (wrapper: HTMLElement, iframe: HTMLIFrameElement) => TMounted;
/**
* Callback executed before mounting the UI. Use this function to customize the iframe or wrapper
* elements before they are injected into the DOM. It is called every time `ui.mount()` is called.
*/
onBeforeMount?: (wrapper: HTMLElement, iframe: HTMLIFrameElement) => void;
};
@@ -13,7 +13,6 @@ export function createIntegratedUi<TMounted>(
options: IntegratedContentScriptUiOptions<TMounted>,
): IntegratedContentScriptUi<TMounted> {
const wrapper = document.createElement(options.tag || 'div');
wrapper.setAttribute('data-wxt-integrated', '');
let mounted: TMounted | undefined = undefined;
const mount = () => {
@@ -51,8 +50,9 @@ export function createIntegratedUi<TMounted>(
* Shared types for the different `wxt/utils/content-script-ui/*` modules.
* @module wxt/utils/content-script-ui/types
*/
export interface IntegratedContentScriptUi<TMounted>
extends ContentScriptUi<TMounted> {
export interface IntegratedContentScriptUi<
TMounted,
> extends ContentScriptUi<TMounted> {
/**
* A wrapper div that assists in positioning.
*/
@@ -48,7 +48,6 @@ export async function createShadowRootUi<TMounted>(
mode: options.mode ?? 'open',
isolateEvents: options.isolateEvents,
});
shadowHost.setAttribute('data-wxt-shadow-root', '');
let mounted: TMounted | undefined;
@@ -135,8 +134,9 @@ async function loadCss(): Promise<string> {
}
}
export interface ShadowRootContentScriptUi<TMounted>
extends ContentScriptUi<TMounted> {
export interface ShadowRootContentScriptUi<
TMounted,
> extends ContentScriptUi<TMounted> {
/**
* The `HTMLElement` hosting the shadow root used to isolate the UI's styles. This is the element
* that get's added to the DOM. This element's style is not isolated from the webpage.
+56 -5
View File
@@ -15,28 +15,62 @@ export type ScriptPublicPath = Extract<
*
* Make sure to add the injected script to your manifest's
* `web_accessible_resources`.
*
* @returns A result object containing the created script element.
*/
export async function injectScript(
path: ScriptPublicPath,
options?: InjectScriptOptions,
): Promise<void> {
): Promise<InjectScriptResult> {
// @ts-expect-error: getURL is defined per-project, but not inside the package
const url = browser.runtime.getURL(path);
const script = document.createElement('script');
if (browser.runtime.getManifest().manifest_version === 2) {
// MV2 requires using an inline script
script.innerHTML = await fetch(url).then((res) => res.text());
script.text = await fetch(url).then((res) => res.text());
} else {
// MV3 requires using src
script.src = url;
}
if (!options?.keepInDom) {
script.onload = () => script.remove();
}
const loadedPromise = makeLoadedPromise(script);
await options?.modifyScript?.(script);
(document.head ?? document.documentElement).append(script);
if (!options?.keepInDom) {
script.remove();
}
await loadedPromise;
return {
script,
};
}
function makeLoadedPromise(script: HTMLScriptElement): Promise<void> {
return new Promise((resolve, reject) => {
const onload = () => {
resolve();
cleanup();
};
const onerror = () => {
reject(new Error(`Failed to load script: ${script.src}`));
cleanup();
};
const cleanup = () => {
script.removeEventListener('load', onload);
script.removeEventListener('error', onerror);
};
script.addEventListener('load', onload);
script.addEventListener('error', onerror);
});
}
export interface InjectScriptOptions {
@@ -45,4 +79,21 @@ export interface InjectScriptOptions {
* injected. To disable this behavior, set this flag to true.
*/
keepInDom?: boolean;
/**
* Modify the script element just before it is added to the DOM.
*
* It can be used to e.g. modify `script.async`/`script.defer`, add event
* listeners to the element, or pass data to the script via `script.dataset`
* (which can be accessed by the script via `document.currentScript`).
*/
modifyScript?: (script: HTMLScriptElement) => Promise<void> | void;
}
export interface InjectScriptResult {
/**
* The created script element. It can be used to e.g. send messages to the
* script in the form of custom events. The script can add an event listener
* for them via `document.currentScript`.
*/
script: HTMLScriptElement;
}
@@ -1,5 +1,7 @@
/** @module wxt/utils/split-shadow-root-css */
const AT_RULE_BLOCKS = /(\s*@(property|font-face)[\s\S]*?{[\s\S]*?})/gm;
/**
* Given a CSS string that will be loaded into a shadow root, split it into two parts:
* - `documentCss`: CSS that needs to be applied to the document (like `@property`)
@@ -10,18 +12,13 @@ export function splitShadowRootCss(css: string): {
documentCss: string;
shadowCss: string;
} {
let shadowCss = css;
let documentCss = '';
const rulesRegex = /(\s*@(property|font-face)[\s\S]*?{[\s\S]*?})/gm;
let match;
while ((match = rulesRegex.exec(css)) !== null) {
documentCss += match[1];
shadowCss = shadowCss.replace(match[1], '');
}
const documentCss = Array.from(css.matchAll(AT_RULE_BLOCKS), (m) => m[0])
.join('')
.trim();
const shadowCss = css.replace(AT_RULE_BLOCKS, '').trim();
return {
documentCss: documentCss.trim(),
shadowCss: shadowCss.trim(),
documentCss: documentCss,
shadowCss: shadowCss,
};
}
@@ -2,10 +2,29 @@ import definition from 'virtual:user-unlisted-script-entrypoint';
import { logger } from '../utils/internal/logger';
import { initPlugins } from 'virtual:wxt-plugins';
const result = (async () => {
const result = (() => {
try {
initPlugins();
return await definition.main();
} catch (err) {
logger.error(
`Failed to initialize plugins for "${import.meta.env.ENTRYPOINT}"`,
err,
);
throw err;
}
let result;
try {
result = definition.main();
if (result instanceof Promise) {
result = (result as Promise<any>).catch((err) => {
logger.error(
`The unlisted script "${import.meta.env.ENTRYPOINT}" crashed on startup!`,
err,
);
throw err;
});
}
} catch (err) {
logger.error(
`The unlisted script "${import.meta.env.ENTRYPOINT}" crashed on startup!`,
@@ -13,6 +32,7 @@ const result = (async () => {
);
throw err;
}
return result;
})();
// Return the main function's result to the background when executed via the
+62
View File
@@ -0,0 +1,62 @@
import { defineConfig, UserConfig } from 'tsdown';
import pkgJson from './package.json' with { type: 'json' };
import { readFile, writeFile } from 'node:fs/promises';
import {
virtualEntrypointModuleNames,
virtualModuleNames,
} from './src/core/utils/virtual-modules';
export default defineConfig([
// Non-virtual modules can be transpiled in-place to make debugging in node_modules easier
{
entry: [
// Exports
...Object.values(pkgJson.exports)
.filter((ex: any) => ex.default)
.map((ex: any) =>
ex.default.replace('./dist', 'src').replace('.mjs', '.ts'),
),
// CLI
'src/cli/index.ts',
],
unbundle: true,
external: ['wxt/browser', 'virtual:app-config'],
copy: [
// If tsdown bundles this file, it removes the triple-slash reference, so
// we need to copy it into the out dir manually instead of building it.
'src/vite-builder-env.d.ts',
],
onSuccess: async () => {
// Don't rely on importing the package.json file at runtime, hardcode the
// version to avoid issues with different runtimes handling JSON imports
// differently.
await replaceVars('dist/version.mjs', { version: pkgJson.version });
},
},
// Virtual modules must be bundled individually
...virtualModuleNames.map(
(moduleName): UserConfig => ({
entry: `src/virtual/${moduleName}.ts`,
outDir: 'dist/virtual',
external: [
...virtualEntrypointModuleNames.map((name) => `virtual:user-${name}`),
'virtual:wxt-plugins',
'virtual:app-config',
...Object.keys(pkgJson.exports).map((path) => 'wxt' + path.slice(1)), // ./utils/storage => wxt/utils/storage
],
}),
),
]);
async function replaceVars(
file: string,
vars: Record<string, string>,
): Promise<void> {
let text = await readFile(file, 'utf8');
Object.entries(vars).forEach(([name, value]) => {
text = text.replaceAll(`{{${name}}}`, value);
});
await writeFile(file, text, 'utf8');
}
+2
View File
@@ -17,6 +17,8 @@
"src/utils/split-shadow-root-css.ts",
"src/utils/storage.ts",
"src/testing/index.ts",
"src/testing/fake-browser.ts",
"src/testing/wxt-vitest-plugin.ts",
"src/modules.ts"
]
}
+3004 -5103
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -8,3 +8,11 @@ onlyBuiltDependencies:
- sharp
- simple-git-hooks
- spawn-sync
autoInstallPeers: false
patchedDependencies:
markdown-it-footnote: patches/markdown-it-footnote.patch
peerDependencyRules:
ignoreMissing:
- '@algolia/client-search'
- search-insights
- svelte
+30 -1
View File
@@ -9,6 +9,7 @@ import spawn from 'nano-spawn';
import { getPkgTag, grabPackageDetails, listCommitsInDir } from './git';
import { consola } from 'consola';
import fs from 'fs-extra';
import path from 'node:path';
const pkg = process.argv[2];
if (pkg == null) {
@@ -81,8 +82,36 @@ const newChangelog =
await fs.writeFile(changelogPath, newChangelog, 'utf8');
consola.success('Updated changelog');
// Update WXT version in templates when releasing wxt package
const templatePkgJsonPaths: string[] = [];
if (pkg === 'wxt') {
const templatesDir = 'templates';
const templateDirs = await fs.readdir(templatesDir);
for (const templateDir of templateDirs) {
const templatePkgJsonPath = path.join(
templatesDir,
templateDir,
'package.json',
);
if (await fs.pathExists(templatePkgJsonPath)) {
const templatePkgJson = await fs.readJson(templatePkgJsonPath);
if (templatePkgJson.devDependencies?.wxt) {
templatePkgJson.devDependencies.wxt = `^${newVersion}`;
await fs.writeJson(templatePkgJsonPath, templatePkgJson, { spaces: 2 });
templatePkgJsonPaths.push(templatePkgJsonPath);
consola.success(`Updated wxt version in ${templatePkgJsonPath}`);
}
}
}
}
// Commit changes
await spawn('git', ['add', pkgJsonPath, changelogPath]);
await spawn('git', [
'add',
pkgJsonPath,
changelogPath,
...templatePkgJsonPaths,
]);
await spawn('git', [
'commit',
'-m',
+9 -8
View File
@@ -4,6 +4,7 @@ import * as semver from 'semver';
import { dirname } from 'node:path';
import consola from 'consola';
import spawn from 'nano-spawn';
import pMap from 'p-map';
const HELP_MESSAGE = `
Upgrades dependencies throughout WXT using custom rules.
@@ -177,7 +178,7 @@ function validateNoMultipleVersions(
async function fetchPackageInfo(name: string): Promise<PackageInfo> {
// Use PNPM instead of API in case dependencies don't come from NPM
const res = await spawn('pnpm', ['view', name, '--json']);
return JSON.parse(res.output);
return JSON.parse(res.stdout);
}
type PackageInfo = {
@@ -203,12 +204,13 @@ type DependencyInfo = {
async function fetchAllPackageInfos(
deps: DependencyVersionMap,
): Promise<DependencyInfo[]> {
const infos = await Promise.all(
Object.entries(deps).map(async ([name, currentVersionRange]) => ({
name,
currentVersionRange,
info: await fetchPackageInfo(name),
})),
const infos: DependencyInfo[] = await pMap(
Object.entries(deps),
async ([name, currentVersionRange]) => {
const info = await fetchPackageInfo(name);
return { name, currentVersionRange, info };
},
{ concurrency: 20 },
);
return infos.toSorted((a, b) => a.name.localeCompare(b.name));
}
@@ -253,7 +255,6 @@ async function detectUpgrades(
const latestVersion = dep.info['dist-tags'].latest;
const latestVersionReleasedAt = new Date(dep.info.time[latestVersion]);
semver.RELEASE_TYPES;
const upgradeToVersion = isMajor
? // Always use the latest version for major upgrades
latestVersion
+7 -7
View File
@@ -15,14 +15,14 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"react": "^19.1.1",
"react-dom": "^19.1.1"
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
"devDependencies": {
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
"@wxt-dev/module-react": "^1.1.3",
"typescript": "^5.9.2",
"wxt": "^0.20.6"
"@types/react": "^19.2.13",
"@types/react-dom": "^19.2.3",
"@wxt-dev/module-react": "^1.1.5",
"typescript": "^5.9.3",
"wxt": "^0.20.14"
}
}
+4 -4
View File
@@ -15,11 +15,11 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"solid-js": "^1.9.9"
"solid-js": "^1.9.11"
},
"devDependencies": {
"@wxt-dev/module-solid": "^1.1.3",
"typescript": "^5.9.2",
"wxt": "^0.20.6"
"@wxt-dev/module-solid": "^1.1.4",
"typescript": "^5.9.3",
"wxt": "^0.20.14"
}
}
+6 -6
View File
@@ -15,12 +15,12 @@
"postinstall": "wxt prepare"
},
"devDependencies": {
"@tsconfig/svelte": "^5.0.5",
"@wxt-dev/module-svelte": "^2.0.3",
"svelte": "^5.38.6",
"svelte-check": "^4.3.1",
"@tsconfig/svelte": "^5.0.7",
"@wxt-dev/module-svelte": "^2.0.4",
"svelte": "^5.49.2",
"svelte-check": "^4.3.6",
"tslib": "^2.8.1",
"typescript": "^5.9.2",
"wxt": "^0.20.6"
"typescript": "^5.9.3",
"wxt": "^0.20.14"
}
}
+2 -2
View File
@@ -1,12 +1,12 @@
import './style.css';
import typescriptLogo from '@/assets/typescript.svg';
import viteLogo from '/wxt.svg';
import wxtLogo from '/wxt.svg';
import { setupCounter } from '@/components/counter';
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<div>
<a href="https://wxt.dev" target="_blank">
<img src="${viteLogo}" class="logo" alt="WXT logo" />
<img src="${wxtLogo}" class="logo" alt="WXT logo" />
</a>
<a href="https://www.typescriptlang.org/" target="_blank">
<img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" />
+2 -2
View File
@@ -15,7 +15,7 @@
"postinstall": "wxt prepare"
},
"devDependencies": {
"typescript": "^5.9.2",
"wxt": "^0.20.6"
"typescript": "^5.9.3",
"wxt": "^0.20.14"
}
}
+5 -5
View File
@@ -15,12 +15,12 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"vue": "^3.5.13"
"vue": "^3.5.27"
},
"devDependencies": {
"@wxt-dev/module-vue": "^1.0.2",
"typescript": "^5.9.2",
"vue-tsc": "^3.0.6",
"wxt": "^0.20.6"
"@wxt-dev/module-vue": "^1.0.3",
"typescript": "^5.9.3",
"vue-tsc": "^3.2.4",
"wxt": "^0.20.14"
}
}