Compare commits

...

212 Commits

Author SHA1 Message Date
github-actions[bot] 65fc0fadb3 chore(release): wxt v0.20.20
📼 VHS / Create VHS (push) Cancelled after 0s
2026-03-17 13:21:45 +00:00
Aaron 64b686713b chore(deps): Add vite-node 6 support 2026-03-17 08:16:33 -05:00
Joseph Hu 9c6d3e37d4 docs: Add Extension Rank Checker to the list of extensions (#2193)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-03-17 08:00:23 -05:00
FJRG2007 ツ 37abe9db39 docs: Add Dymo extension ID to UsingWxtSection.vue (#2187)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-03-17 12:43:08 +00:00
Yue Yang 9ebef4a268 chore: remove useDefineForClassFields from svelte/tsconfig.json (#2190) 2026-03-17 07:37:38 -05:00
Hampus Tågerud e60f7c7e77 fix: unlisted script return values broken with Vite 8 sourcemaps (#2197) 2026-03-17 12:35:22 +00:00
aklinker1 36aadd0604 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-03-17 00:39:30 +00:00
github-actions[bot] e4598a5e37 chore(release): @wxt-dev/module-react v1.2.2
📼 VHS / Create VHS (push) Cancelled after 0s
2026-03-14 23:48:01 +00:00
github-actions[bot] db5c22809c chore(release): wxt v0.20.19
📼 VHS / Create VHS (push) Cancelled after 0s
2026-03-14 23:43:04 +00:00
Aaron c61fa8f858 fix: Add @vitejs/plugin-react v6 support 2026-03-14 18:38:33 -05:00
Florian Metz 5fe4681620 chore: replace fs-extra with node:fs/promises (#2174)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-03-06 09:29:49 -06:00
Aaron 80d384361b chore: Update nano-staged precommit command 2026-03-06 09:17:22 -06:00
dagangtj a1e72c0749 chore: replace lint-staged with nano-staged (#2179)
Co-authored-by: Dean <dagangtj@gmail.com>
2026-03-06 15:13:41 +00:00
Aaron e3a80b8247 ci: Re-enable react template validation 2026-03-06 08:54:08 -06:00
github-actions[bot] c0013acadc chore(release): @wxt-dev/module-react v1.2.1
📼 VHS / Create VHS (push) Cancelled after 0s
2026-03-06 14:51:43 +00:00
Aaron f472c53da7 ci: Temporarily disable react template validation 2026-03-06 08:47:58 -06:00
Aaron 032f7931e8 chore: Add prepack script to all packages 2026-03-06 08:43:51 -06:00
github-actions[bot] 57a12c1711 chore(release): @wxt-dev/module-react v1.2.0
📼 VHS / Create VHS (push) Cancelled after 0s
2026-03-06 14:24:19 +00:00
Aaron d13ca3983f chore(deps): Upgdate fs-extra in browser package 2026-03-06 08:19:54 -06:00
aklinker1 9769ce4ac9 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-03-06 00:44:23 +00:00
Aaron 55dc263dc9 fix: List eslint 10 as supported 2026-03-05 10:08:02 -06:00
Aaron e56ae23747 chore(deps): Upgrade deps (#2175) 2026-03-05 10:00:41 -06:00
Aaron bfd4af5d69 feat: Vite 8 support 2026-03-05 08:59:58 -06:00
Patryk Kuniczak 9673604878 perf: Use filter to improve plugin performance with rolldown (#1787)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-03-05 08:55:36 -06:00
Florian Metz ae55aecb0d chore: replace fast-glob and ora with lighter alternatives (#2173) 2026-03-04 14:26:11 -06:00
Martin Broder a14d0b4f2f feat: add vitePluginsBefore option (#2170) 2026-03-04 11:39:00 -06:00
Aaron 301e4a17da chore: Add prettier-plugin-jsdoc to project (#2171) 2026-03-03 08:43:04 -06:00
etoome 19cebbf673 docs: Added "isTrust" to the homepage (#2161) 2026-02-27 00:49:06 +00:00
Kuba ef73c07dfe docs: Added "Scrape Similar" to the homepage (#2158) 2026-02-23 13:57:08 +00:00
Aaron ca5ad7e51b chore: Update codeowners 2026-02-22 18:56:12 -06:00
github-actions[bot] 5dab944abc chore(release): wxt v0.20.18
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-22 23:45:06 +00:00
Aaron adaae6e1a4 fix: Fix submit command (#2157) 2026-02-22 17:29:53 -06:00
Aaron 4524cbdf76 ci: Fix VHS workflow 2026-02-22 17:29:53 -06:00
github-actions[bot] 1171cf52f0 chore(release): @wxt-dev/analytics v0.5.4
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-22 17:29:53 -06:00
Aaron c5978b1072 fix: Continue using useAppConfig to support older versions of WXT 2026-02-22 17:29:53 -06:00
Aaron e5ef9367ed ci: Fix VHS action (#2155) 2026-02-22 17:29:53 -06:00
github-actions[bot] 0ca908a6cc chore(release): @wxt-dev/analytics v0.5.3
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-22 17:29:53 -06:00
github-actions[bot] e20c012eb3 chore(release): @wxt-dev/storage v1.2.8
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-22 17:29:53 -06:00
github-actions[bot] e88df26d7c chore(release): @wxt-dev/auto-icons v1.1.1
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-22 17:29:53 -06:00
github-actions[bot] 9491e6845e chore(release): @wxt-dev/i18n v0.2.5
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-22 17:29:53 -06:00
Aaron e4433c4212 ci: Fix scripts/list_unreleased_commits.sh (#2153) 2026-02-22 17:29:53 -06:00
Jonathan Viney c6fdc5a5d5 feat: Add globalName entrypoint option. (#2017) 2026-02-22 17:29:53 -06:00
Aaron 68dd3fc027 docs: Update section in SECURITY.md around reporting vulnerabilities (#2147) 2026-02-22 17:29:53 -06:00
omerfardemir cd7aec6752 fix: auto icons override default icons (#1616)
Co-authored-by: Florian Metz <me@timeraa.dev>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-22 17:29:53 -06:00
Smit b80556498d fix: Improve background script detection logic for analytics package (#1808)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-22 17:29:53 -06:00
Aaron 38530cdb39 feat: New @wxt-dev/is-background package (#2152)
Co-authored-by: Smit <sm17p@users.noreply.github.com>
2026-02-22 17:29:53 -06:00
Tam Dang 078fd5990e fix(types): include CSS entrypoints in PublicPath generation (#2150)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-22 17:29:53 -06:00
Aaron b4be5feaa6 ci: Fix auto-labeling workflow for forks 2026-02-22 17:29:53 -06:00
Aaron d982f97bd8 feat(modules): Add support for augumenting entrypoint options (#2149) 2026-02-22 17:29:53 -06:00
Aaron 653608c98c docs: Add deep outline to modules page to show recipes 2026-02-22 17:29:53 -06:00
Aaron d63be2d6f3 ci: Create workflow for labeling and adding authors to PRs (#2148) 2026-02-22 17:29:53 -06:00
Nick Doan 4500ca577f fix(types): Add type safety to browser.runtime.executeScript files option (#2142)
Co-authored-by: nickbar01234 <nickbar01234gmail.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-22 17:29:53 -06:00
Aaron cc935daec0 fix: Allow userId option to return undefined 2026-02-22 17:29:53 -06:00
Patryk Kuniczak 51ff46ec58 chore: Created new types, instead of any for analytics (#2119) 2026-02-22 17:29:53 -06:00
Patryk Kuniczak 206b7731cf chore: Remove @ts-expect-error from manifest.test.ts and fix typo (#2123) 2026-02-22 17:29:53 -06:00
Patryk Kuniczak d16f59d33d chore: Simplify imports in wxt/e2e (#2122) 2026-02-22 17:29:53 -06:00
Patryk Kuniczak a5ec046346 chore: Rename zipdir to zipDir (#2126) 2026-02-22 17:29:49 -06:00
Patryk Kuniczak 51dd803115 chore: Remove unnecessary @ts-ignore of builders/vite index.ts (#2127) 2026-02-21 10:47:39 -06:00
Patryk Kuniczak 6e39424a30 chore: Make type for __ENTRYPOINT__ of vitest.globalSetup.ts (#2128) 2026-02-21 10:46:36 -06:00
Anthony Ciccarello 6ca84782aa fix: enable mv3 dev mode with firefox 147 (#2135) 2026-02-21 16:45:04 +00:00
Henrik Wenz ee1ba6369e docs: Add wxt-local-analytics to community resources (#2141) 2026-02-21 16:41:13 +00:00
Aaron f35d70e6fe fix: Correctly update version metadata when setting a value for the first time (#2139)
Co-authored-by: willow <42willow@pm.me>
Co-authored-by: absdjfh <absdjfh@users.noreply.github.com>
2026-02-21 10:39:21 -06:00
sheng zhang 430a3acfeb fix: Add getAppConfig as an alias to useAppConfig (#2144)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-21 15:38:42 +00:00
aklinker1 395c5841f9 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-02-21 00:39:02 +00:00
Oleksii 173161ca4d feat: use Navigation API for location change detection with polling fallback (#2136) 2026-02-19 01:21:48 +00:00
Patryk Kuniczak a1a8e14368 feat: Create tests for all func of network.ts (#2132) 2026-02-18 19:14:36 -06:00
qixing-jk 2379806c61 docs: add All API Hub to showcase extensions (#2137) 2026-02-19 00:58:20 +00:00
Muzammil f604208a8b docs: Added "Glossy New Tab" to the homepage (#2133) 2026-02-15 03:48:59 +00:00
Patryk Kuniczak cf31fbec5d chore: add missing async to network.ts (#2107) 2026-02-13 12:18:37 +00:00
github-actions[bot] 279be8deb7 chore(release): @wxt-dev/storage v1.2.7
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-13 00:01:40 +00:00
github-actions[bot] 7366d8aab7 chore(release): wxt v0.20.17
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-12 22:35:05 +00:00
Aaron a7ab409d48 fix: Update left-over CJS require of publish-browser-extension to ESM (#2120) 2026-02-12 16:30:47 -06:00
Aaron 9dea0cf3ae chore: Cleanup type tests 2026-02-12 15:53:41 -06:00
Patryk Kuniczak 50d7c1ec80 chore: Change browser tests to fix deprecated warning (#2117)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-12 21:35:27 +00:00
github-actions[bot] f32cdafdf6 chore(release): @wxt-dev/analytics v0.5.2
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-12 21:08:03 +00:00
Honwhy Wang 2e2272d07a fix: Allow custom API URL in Google Analytics 4 provider options (#1653)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-12 13:34:05 -06:00
github-actions[bot] 27ffff7e0a chore(release): wxt v0.20.16
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-12 19:16:33 +00:00
Aaron 305cebe0d0 fix: Revert "rename name to names prop of rollup to fix deprecated warning (#2106)"
This reverts commit 9470d2b696.
2026-02-12 13:11:36 -06:00
Aaron 4602a068f8 Update contributor in changelog 2026-02-12 13:06:06 -06:00
Aaron 3a947d72c6 chore: Include doc updates in wxt changelog 2026-02-12 13:03:58 -06:00
Aaron 1940e3530f chore: Speed up E2E tests by skipping pnpm install when possible (#2113) 2026-02-11 12:24:36 -06:00
github-actions[bot] dd549dc686 chore(release): wxt v0.20.15
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-11 07:13:31 +00:00
Nick Doan 3417bd3ed2 feat: Add log --level cli option (#1973)
Co-authored-by: nickbar01234 <nickbar01234gmail.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-11 04:57:27 +00:00
Patryk Kuniczak 9470d2b696 fix: rename name to names prop of rollup to fix deprecated warning (#2106) 2026-02-10 12:23:19 -06:00
Aaron f523a7d31d docs: Add notes about chrome services and feature flags (#2110) 2026-02-10 12:22:18 -06:00
Patryk Kuniczak ea4c2b035b fix: Remove prepublish script of root package.json (#2109) 2026-02-10 10:11:47 +01:00
willow c5bfba837f fix(storage): Set version number on init (#1996) 2026-02-10 01:41:00 +00:00
aklinker1 c5ad957b25 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-02-10 00:47:54 +00:00
Patryk Kuniczak cc849e7cea docs: rename keys name of getMetas() to be proper (#2105) 2026-02-09 22:28:49 +01:00
Patryk Kuniczak 22428da54d chore: simplify imports of cli/__tests__/index.ts (#2104) 2026-02-09 20:59:46 +00:00
Patryk Kuniczak f6071f7e2d fix: Change deprecated function presetUno to presetWind3 (#2103) 2026-02-09 21:55:28 +01:00
Aaron 08e8560865 chore: Upgrade PNPM to 10.29.2 (#2102) 2026-02-09 12:46:47 -06:00
Aaron 75f01db543 refactor: Simplify import paths (#2093)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-09 02:09:51 -06:00
Aaron 5a028d2551 chore: Remove unused code (#2087)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-09 01:45:11 -06:00
Aaron 07a4ba8bc5 fix: Make ExtensionRunner#closeBrowser optional (#2092)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-09 01:44:35 -06:00
Aaron f84921f735 chore: Fix typos (#2086)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-09 01:39:46 -06:00
Aaron 872494e490 chore: Update JSDoc to use @internal (#2091)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-09 01:39:07 -06:00
Aaron d0848b0be5 chore: Cleanup @ts-* comments (#2094)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-09 01:38:42 -06:00
Aaron c848956777 chore: Cleanup comment line lengths (#2090)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-09 01:38:09 -06:00
Aaron 296997d10e refactor: Cleanup unnecssary checks (#2089)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-09 01:37:47 -06:00
Aaron ffdb22238c refactor: async/await cleanup (#2088)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-09 01:37:16 -06:00
Aaron a63c320403 refactor: Code cleanup for storage package (#2085)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-09 01:36:40 -06:00
Aaron aa86ed78a3 refactor: Code cleanup in analytics package (#2084)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-09 01:36:15 -06:00
wotan-allfather 19456ec855 feat(popup): Add Firefox default_area and theme_icons support (#2097)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-09 07:28:26 +00:00
Aaron 2c57840c44 revert: Use uiContainer to apply position when creating shadow root ui (#2036)
This reverts commit 26b7927f62.
2026-02-09 00:44:07 -06:00
Namu 26b7927f62 fix: Use uiContainer to apply position when creating shadow root ui (#2036)
Co-authored-by: Namu <535248+namukang@users.noreply.github.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-09 06:32:35 +00:00
Eli d4267af725 refactor: cleanup esm (#1950)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-09 04:27:51 +00:00
ariasuni 16a2aedcd0 docs: Small improvements for hooks (#2095)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-09 02:46:40 +00:00
Puzant 755f04f777 docs: Added KeyFloat (#2082)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-09 02:43:58 +00:00
Namu cf9539ddab fix: content script is incorrectly invalidated when injected multiple times (#2035)
Co-authored-by: Namu <535248+namukang@users.noreply.github.com>
2026-02-09 02:38:54 +00:00
Aaron 99d558f33c refactor: Standardize file existence checks to pathExists (#2083)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-08 15:21:45 -06:00
Patryk Kuniczak 1fe073004b fix: normalize path for createAnalytics of analytics/index.ts (#2013) 2026-02-08 08:05:09 -06:00
Aaron 6ae5fb5edb docs: Fix JSDoc 2026-02-08 08:03:46 -06:00
Patryk Kuniczak 3a3953edf6 feat: Export normalizePath from wxt module (#2080) 2026-02-08 08:18:49 +00:00
rxliuli c8e788eaf8 docs: Add blog post resource for building browser extensions (#2077) 2026-02-07 13:40:14 +00:00
Aaron 088a44496d docs: Add llms.txt to documentation site (#1725) 2026-02-06 16:38:37 -06:00
Aaron 60a57c5abc chore: Fix markdownlint issues 2026-02-06 16:34:08 -06:00
Aaron 482466bbbf docs: Update MAINTAINERS.md
Added an example of how to ask for a bug reproduction.
2026-02-06 15:10:11 -06:00
Aaron 65deb7317b chore: Upgrade github actions (#2073) 2026-02-06 14:14:19 -06:00
Patryk Kuniczak 00ae061822 chore: fix recognize lack of argument for scripts (#2015) 2026-02-06 14:04:14 -06:00
Aaron 9bd7fc1bd5 chore: Don't report unreleased chore/doc commits 2026-02-06 13:36:04 -06:00
Aaron 98ddb83f1d docs: Add PR guidelines for small and targeted changes 2026-02-06 10:55:00 -06:00
Aaron 6dd8b9d072 docs: Add MAINTAINERS.md file for notes specific to maintainers 2026-02-06 10:41:57 -06:00
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
234 changed files with 12430 additions and 8592 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# Set default
* @aklinker1 @Timeraa
* @aklinker1
# Secure Directories
/.github/ @aklinker1
+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
+111
View File
@@ -0,0 +1,111 @@
name: ✨ Auto-label PR
on:
pull_request_target:
types: [opened, synchronized, reopened]
jobs:
update-pr:
name: Update PR
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Gather Info
id: check
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number
});
// Check if PR has assignees
const hasAssignees = pr.assignees && pr.assignees.length > 0;
core.setOutput('has_assignees', hasAssignees);
core.setOutput('author', pr.user.login);
// Get list of changed files
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number
});
// Find all packages that were modified
const packagesRegex = /^packages\/([^\/]+)\//;
const affectedPackages = new Set();
for (const file of files) {
const match = file.filename.match(packagesRegex);
if (match) {
affectedPackages.add(match[1]);
}
}
const labels = Array.from(affectedPackages).map(pkg => `pkg/${pkg}`);
core.setOutput('labels', JSON.stringify(labels));
console.log('Detected package labels:', labels);
// Get current labels on the PR that match pkg/* pattern
const currentPkgLabels = pr.labels
.map(label => label.name)
.filter(name => name.startsWith('pkg/'));
core.setOutput('current_pkg_labels', JSON.stringify(currentPkgLabels));
console.log('Current pkg labels:', currentPkgLabels);
- name: Sync Author
if: steps.check.outputs.has_assignees == 'false'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
assignees: ['${{ steps.check.outputs.author }}']
});
console.log('Assigned PR author: ${{ steps.check.outputs.author }}');
- name: Sync Labels
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const newLabels = ${{ steps.check.outputs.labels }};
const currentLabels = ${{ steps.check.outputs.current_pkg_labels }};
// Find labels to add (in newLabels but not in currentLabels)
const labelsToAdd = newLabels.filter(label => !currentLabels.includes(label));
// Find labels to remove (in currentLabels but not in newLabels)
const labelsToRemove = currentLabels.filter(label => !newLabels.includes(label));
// Add new labels
if (labelsToAdd.length > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: labelsToAdd
});
console.log('Added labels:', labelsToAdd);
}
// Remove obsolete labels
for (const label of labelsToRemove) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
name: label
});
console.log('Removed label:', label);
}
if (labelsToAdd.length === 0 && labelsToRemove.length === 0) {
console.log('No label changes needed');
}
@@ -7,9 +7,10 @@ on:
jobs:
notify:
runs-on: ubuntu-22.04
if: github.repository_owner == 'wxt-dev'
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
if: ${{ github.repository == 'wxt-dev/wxt' }}
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
+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
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# Only fetch the config file from the repository
sparse-checkout-cone-mode: false
+2 -2
View File
@@ -23,13 +23,13 @@ jobs:
contents: write
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
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 }}
+5 -3
View File
@@ -10,6 +10,7 @@ on:
- analytics
- auto-icons
- i18n
- is-background
- module-react
- module-solid
- module-svelte
@@ -31,7 +32,7 @@ jobs:
publish:
name: Publish
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
permissions:
contents: write # Push version changes
id-token: write # OIDC for NPM publishing
@@ -39,7 +40,7 @@ jobs:
- validate
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
ssh-key: ${{ secrets.DEPLOY_KEY }} # https://github.com/sbellone/release-workflow-example
@@ -62,8 +63,9 @@ jobs:
- name: Publish to NPM
working-directory: packages/${{ inputs.package }}
run: |
pnpm pack
sudo npm i -g npm@latest
npm publish
/usr/local/bin/npm publish *.tgz
- name: Create GitHub release
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
+2 -1
View File
@@ -10,6 +10,7 @@ on:
- analytics
- auto-icons
- i18n
- is-background
- module-react
- module-solid
- module-svelte
@@ -30,7 +31,7 @@ jobs:
contents: write
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
+8 -4
View File
@@ -11,12 +11,15 @@ jobs:
sync:
name: 'Sync with @types/chrome'
runs-on: ubuntu-latest
if: github.repository_owner == 'wxt-dev'
permissions:
contents: write # Push version changes
id-token: write # OIDC for NPM publishing
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ssh-key: ${{ secrets.DEPLOY_KEY }}
- name: Setup
uses: ./.github/actions/setup
@@ -33,15 +36,16 @@ jobs:
- name: Commit Changes
id: commit
uses: stefanzweifel/git-auto-commit-action@01d77ca6cb089da1360e540865f7d035c95aa199 # v6.0.1
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
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: |
pnpm pack
sudo npm i -g npm@latest
npm publish
/usr/local/bin/npm publish *.tgz
+7 -7
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
@@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
@@ -41,7 +41,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
@@ -68,13 +68,13 @@ jobs:
coverage: false
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
- name: Setup Bun
uses: oven-sh/setup-bun@22457c87c1b161cf7dde222c3e82b2b5f8d2bed2 # v2.0.2
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
- name: Run Tests
if: ${{ ! matrix.coverage }}
@@ -86,7 +86,7 @@ jobs:
- name: Upload Coverage
if: matrix.coverage
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
@@ -104,7 +104,7 @@ jobs:
- vue
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
+17 -6
View File
@@ -11,19 +11,26 @@ permissions:
jobs:
vhs:
name: Create VHS
runs-on: ubuntu-22.04
runs-on: macos-latest
if: ${{ github.repository == 'wxt-dev/wxt' }}
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.ref }}
- name: Setup
uses: ./.github/actions/setup
with:
install: false
- name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
with:
go-version: '1.25.1'
# This prevents pnpm dlx from downloading WXT in the video
- name: Pre-install WXT
run: |
@@ -31,15 +38,19 @@ jobs:
pnpm dlx wxt@latest --version
- name: Record VHS
uses: charmbracelet/vhs-action@59641cdc7fadf3978db65eb8c6937ea2752f4ec3 # v2.1.0
with:
path: 'docs/tapes/init-demo.tape'
run: |
brew install ttyd ffmpeg
go install github.com/charmbracelet/vhs@517bcda0faf416728bcf6b7fe489eb0e2469d9b5 # v0.10.0
vhs docs/tapes/init-demo.tape
- name: Save recorded GIF
uses: stefanzweifel/git-auto-commit-action@01d77ca6cb089da1360e540865f7d035c95aa199 # v6.0.1
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commit_message: 'docs: Update `wxt init` GIF'
commit_user_name: github-actions[bot]
commit_user_email: github-actions[bot]@users.noreply.github.com
commit_author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
# https://github.com/charmbracelet/vhs#output
file_pattern: 'docs/assets/*.gif'
+2
View File
@@ -1,2 +1,4 @@
singleQuote: true
endOfLine: lf
plugins:
- prettier-plugin-jsdoc
+2 -1
View File
@@ -10,5 +10,6 @@
// Additional guidelines for Copilot
"github.copilot.chat.codeGeneration.instructions": [
{ "file": "CONTRIBUTING.md" }
]
],
"oxc.enable": true
}
+32 -7
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.
@@ -137,13 +163,6 @@ cp -r templates/vanilla templates/<new-template-name>
That's it. Once your template is merged, it will be available inside `wxt init` immediately. You don't need to release a new version of WXT to release a new template.
## Releasing Updates
Releases are done with GitHub actions:
- Use the [Release workflow](https://github.com/wxt-dev/wxt/actions/workflows/release.yml) to release a single package in the monorepo. This automatically detects the version change with conventional commits, builds and uploads the package to NPM, and creates a GitHub release.
- Use the [Sync Releases workflow](https://github.com/wxt-dev/wxt/actions/workflows/sync-releases.yml) to sync the GitHub releases with changes to the changelog. To change a release, update the `CHANGELOG.md` file and run the workflow. It will sync the releases of a single package in the monorepo.
## Upgrading Dependencies
WXT has custom rules around what dependencies can be upgraded. Use the `scripts/upgrade-deps.ts` script to upgrade dependencies and follow these rules.
@@ -188,3 +207,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.
+90
View File
@@ -0,0 +1,90 @@
# Maintainers
A couple of things for you to consider before merging a PR or giving the go-ahead on a feature proposal.
## Be picky about new features and packages
We are responsible for maintaining them and fixing related bugs after the PR is merged. The community can always release their own WXT modules or packages, not everything needs to be built into WXT.
## Prefer standards over customization
Don't merge PRs that just add another way to do something, like [this one](https://github.com/wxt-dev/wxt/pull/2053#issuecomment-3857010196).
WXT is opinionated, if you have questions about what is WXT's opinion or we need to create a new one, create an issue and @ me to discuss.
## PRs should be small and targeted
A PR should make one change. They should not make any unrelated changes outside of accomplishing the one thing. This makes PRs easier to review and they get merged more quickly - a win-win for everyone.
## `@wxt-dev/*` packages are separate
We can't make changes to these packages assuming people are using them only with WXT. For example, I almost missed [this PR](https://github.com/wxt-dev/wxt/pull/2049#issuecomment-3861251599).
I want these packages to be usable on their own so if people don't like WXT's build tool, they can still use our other awesome packages.
## Minimize dependencies
I don't like how heavy lots of frameworks are. It's unavoidable to a certain extent, but if you can do something without another dependency, try to.
If you need a dependency, look for one with zero dependencies and that's respectable.
## Look at milestones
I've organized WXT's long term plans into [milestones](https://github.com/wxt-dev/wxt/milestones), check those out to get an idea of my priorities for some of the larger features.
## Require tests
If someone opens a PR to fix a bug but doesn't include tests, don't merge the PR. Tests are required to prevent regressions and maintain a codebase long term.
## Ask for reproductions for bugs
You don't need to triage bugs if someone doesn't give you enough information. You can always ask for a repo with a reproduction or wait for more details.
If there's not an easy way to reproduce a bug, you're wasting your time triaging it. Just be mindful of your own time!
Here's an example of how to ask for a reproduction: <https://github.com/wxt-dev/wxt/issues/2064#issuecomment-3862579110>. You could be more blunt than this.
## Add yourself as a code owner
If you want to be responsible for a specific package or directory, add yourself to the [`.github/CODEOWNERS`](https://github.com/wxt-dev/wxt/blob/main/.github/CODEOWNERS) file to get added as a reviewer to PRs automatically. You can also add yourself to the default list to be added as a reviewer on all PRs.
## Releasing Package Updates
Releases are done with GitHub actions:
- Use the [Release workflow](https://github.com/wxt-dev/wxt/actions/workflows/release.yml) to release a single package in the monorepo. This automatically detects the version change with conventional commits, builds and uploads the package to NPM, and creates a GitHub release.
- Use the [Sync Releases workflow](https://github.com/wxt-dev/wxt/actions/workflows/sync-releases.yml) to sync the GitHub releases with changes to the changelog. To change a release, update the `CHANGELOG.md` file and run the workflow. It will sync the releases of a single package in the monorepo.
## Creating New Packages
Example PR: <https://github.com/wxt-dev/wxt/pull/2152>
1. Create the package.
2. Update CI workflow inputs.
3. Add docs page and version for "Other Packages" dropdown.
4. Merge the PR.
5. Tag the commit (look at other tags for pattern):
```sh
git tag <dir-name>-v<version>
git push --tags
```
6. Publish the package to NPM:
```sh
cd packages/<dir-name>
pnpm publish --access public
```
7. Create a basic release on GitHub mentioning the new package is available.
A couple of things to note:
- pkg.pr.new will fail on the original PR. It's fine to ignore and merge your PR as long as it fails due to your new package not being published to NPM yet.
- The regular release workflow DOES NOT WORK for new packages. You have to have at least one `<dir-name>-v<version>` tag created before you can run that workflow for your new package.
- You don't need to create a CHANGELOG.md file for the package, it will be created automatically after future changes are released via the normal release workflow.
+2 -6
View File
@@ -4,12 +4,8 @@ While WXT is in prerelease, only the latest version will receive security update
<img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048">
<!--
## Reporting a Vulnerability
Use this section to tell people how to report a vulnerability.
If you discover a security vulnerability, please email me at <aaronklinker1@gmail.com>. I will respond within a few days to acknowledge receipt of your report.
Tell them where to go, how often they can expect to get an update on a
reported vulnerability, what to expect if the vulnerability is accepted or
declined, etc.
-->
If the vulnerability is accepted, I will open a public issue to track the fix. If the vulnerability is not accepted, no further action will be taken.
@@ -102,6 +102,27 @@ 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.
'mfjdonmgmgcijagclnkfhmjiblbfjaid', // KeyFloat - Floating multilingual keyboard with native key mappings, drag, dark mode, sounds, and dynamic layouts for macOS & Windows
'dhiekgdaipindoapjmcnpompdknjeijf', // Glossy New Tab - Say Goodbye to Boring Tabs with live wallpapers
'lapnciffpekdengooeolaienkeoilfeo', // All API Hub AI Relay & New API Manager - https://github.com/qixing-jk/all-api-hub
'bhgobenflkkhfcgkikejaaejenoddcmo', // Scrape Similar - Extract data from websites into spreadsheets - https://github.com/zizzfizzix/scrape-similar
'kinlknncggaihnhdcalijdmpbhbflalm', // isTrust - https://github.com/Internet-Society-Belgium/isTrust/
'ojpakgiekphppgkcdihbjpafobhnhlkp', // Dymo
'pmgehhllikbjmadpenhabejhpemplhmd', // Extension Rank Checker - Extension Ranker
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
+4
View File
@@ -16,6 +16,7 @@ import { version as unocssVersion } from '../../packages/unocss/package.json';
import { version as storageVersion } from '../../packages/storage/package.json';
import { version as analyticsVersion } from '../../packages/analytics/package.json';
import { version as runnerVersion } from '../../packages/runner/package.json';
import { version as isBackgroundVersion } from '../../packages/is-background/package.json';
import addKnowledge from 'vitepress-knowledge';
import {
groupIconMdPlugin,
@@ -25,6 +26,7 @@ import {
import { Feed } from 'feed';
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import llmstxt from 'vitepress-plugin-llms';
const origin = 'https://wxt.dev';
@@ -43,6 +45,7 @@ const otherPackages = {
storage: storageVersion,
unocss: unocssVersion,
runner: runnerVersion,
'is-background': isBackgroundVersion,
};
const knowledge = addKnowledge<DefaultTheme.Config>({
@@ -71,6 +74,7 @@ export default defineConfig({
vite: {
clearScreen: false,
plugins: [
llmstxt(),
groupIconVitePlugin({
customIcon: {
'wxt.config.ts': localIconLoader(
+1 -3
View File
@@ -77,9 +77,7 @@ export function menuItem(
return { text, link };
}
/**
* Clean up and add badges to typedoc leaf sections
*/
/** Clean up and add badges to typedoc leaf sections */
export function prepareTypedocSidebar(items: SidebarItem[]) {
// skip contents file
const filtered = items.slice(1);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 136 KiB

@@ -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,7 +99,30 @@ 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,
});
```
### Enabling Chrome Features
Some APIs are disabled in Chrome during development because of the default flags `web-ext` uses to launch Chrome, like the [Prompt API](https://developer.chrome.com/docs/ai/prompt-api).
If your extension depends on new features or services, you can enable them via `chromiumArgs`:
```ts
import { defineWebExtConfig } from 'wxt';
export default defineWebExtConfig({
chromiumArgs: [
// For example, this flag enables the Prompt API
'--disable-features=DisableLoadExtensionCommandLineSwitch',
],
});
```
There is no comprehensive list of what feature flags enable what APIs and services.
Alternatively, if you can't find a flag that enables a feature you're looking for, [disable the opening the browser during development](#disable-opening-browser) and use your regular chrome profile for development.
+3 -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)';
}
},
},
@@ -20,6 +20,8 @@ export default defineConfig({
Most hooks provide the `wxt` object as the first argument. It contains the resolved config and other info about the current build. The other arguments can be modified by reference to change different parts of the build system.
You can find the [list of all available hooks](/api/reference/wxt/interfaces/WxtHooks) in the API reference.
Putting one-off hooks like this in your config file is simple, but if you find yourself writing lots of hooks, you should extract them into [WXT Modules](/guide/essentials/wxt-modules) instead.
## Execution Order
+3 -3
View File
@@ -23,12 +23,12 @@ export default defineAppConfig({
This file is committed to the repo, so don't put any secrets here. Instead, use [Environment Variables](/guide/essentials/config/environment-variables)
:::
To access runtime config, WXT provides the `useAppConfig` function:
To access runtime config, WXT provides the `getAppConfig` function:
```ts
import { useAppConfig } from '#imports';
import { getAppConfig } from '#imports';
console.log(useAppConfig()); // { theme: "dark" }
console.log(getAppConfig()); // { theme: "dark" }
```
## Environment Variables in App Config
+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.
+13
View File
@@ -470,6 +470,19 @@ When you define a Newtab entrypoint, WXT will automatically update the manifest
/>
<meta name="manifest.type" content="page_action|browser_action" />
<meta name="manifest.browser_style" content="true|false" />
<!-- Firefox only: where to place the action button -->
<meta
name="manifest.default_area"
content="navbar|menupanel|tabstrip|personaltoolbar"
/>
<!-- Firefox only: icons for light/dark themes -->
<meta
name="manifest.theme_icons"
content="[
{ light: '/icon-light-16.png', dark: '/icon-dark-16.png', size: 16 },
{ light: '/icon-light-32.png', dark: '/icon-dark-32.png', size: 32 }
]"
/>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
+59 -3
View File
@@ -1,3 +1,7 @@
---
outline: deep
---
# WXT Modules
WXT provides a "module system" that let's you run code at different steps in the build process to modify it.
@@ -66,7 +70,7 @@ Each module's setup function is executed after the `wxt.config.ts` file is loade
Refer to the [API reference](/api/reference/wxt/interfaces/Wxt) for a complete list of properties and functions available.
Also to make sure and read about all the [hooks that are available](https://wxt.dev/api/reference/wxt/interfaces/WxtHooks) - they are essential to writing modules.
Also make sure to read about [all the hooks that are available](/api/reference/wxt/interfaces/WxtHooks) - they are essential to writing modules.
### Recipes
@@ -131,12 +135,64 @@ declare module 'wxt/utils/define-app-config' {
Runtime options are returned when calling
```ts
const config = useAppConfig();
const config = getAppConfig();
console.log(config.myModule);
```
This is very useful when [generating runtime code](#generate-runtime-module).
#### Add custom entrypoint options
Modules can add custom options to entrypoints by augmenting the entrypoint options types. This allows you to add custom configuration that can be accessed during the build process.
```ts
import { defineWxtModule } from 'wxt/modules';
import 'wxt';
declare module 'wxt' {
export interface BackgroundEntrypointOptions {
// Add custom options to the background entrypoint
myCustomOption?: string;
}
}
export default defineWxtModule({
setup(wxt) {
wxt.hook('entrypoints:resolved', (_, entrypoints) => {
const background = entrypoints.find((e) => e.type === 'background');
if (background) {
console.log('Custom option:', background.options.myCustomOption);
}
});
},
});
```
Now users can set the custom option in their entrypoint:
```ts [entrypoints/background.ts]
export default defineBackground({
myCustomOption: 'custom value',
main() {
// ...
},
});
```
This works for all other JS and HTML entrypoints, here's an example of how to pass a custom option from an HTML file.
```html [entrypoints/popup.html]
<html>
<head>
<meta name="wxt.myHtmlOption" content="custom value" />
<title>Popup</title>
</head>
<body>
<!-- ... -->
</body>
</html>
```
#### Generate output file
```ts
@@ -220,7 +276,7 @@ export default defineWxtModule({
const analyticsModuleCode = `
import { createAnalytics } from 'some-module';
export const analytics = createAnalytics(useAppConfig().analytics);
export const analytics = createAnalytics(getAppConfig().analytics);
export const { reportEvent, reportPageView } = analytics;
`;
+2
View File
@@ -7,8 +7,10 @@ This page is dedicated to all the awesome people how have made something for WXT
## Blog Posts
- [Building Modern Cross Browser Web Extensions](https://aabidk.dev/tags/wxt/) by Aabid ([@aabidk20](https://github.com/aabidk20))
- [Building Browser Extensions with WXT](https://rxliuli.com/blog/browser-extension-dev-01-introduction-to-basic-concepts/) by rxliuli ([@rxliuli](https://github.com/rxliuli))
## NPM Packages
- [`@webext-core/*`](https://webext-core.aklinker1.io/): Easy-to-use utilities for writing and testing web extensions that work on all browsers.
- [`Comctx`](https://github.com/molvqingtai/comctx): Cross-context RPC solution with type safety and flexible adapters.
- [`wxt-local-analytics`](https://github.com/HaNdTriX/wxt-local-analytics): Local analytics provider for [`@wxt-dev/analytics`](https://wxt.dev/analytics)
+34
View File
@@ -164,6 +164,10 @@ Both issues have the same cause: the library puts something outside the `ShadowR
Both issues have the same fix: tell the library to put elements inside the `ShadowRoot`, not outside it. See the details above for more information and example fixes for each problem.
## Does WXT provide docs for LLMs?
Yes, WXT's documentation provides markdown files based on the [the /llms.txt proposal](https://llmstxt.org/).
## Is there an LLM trained on WXT's docs that I chat with?
Yes! There's a "Ask AI" button in the bottom right of the page, try it out! Or visit <https://knowledge.wxt.dev/> for a fullscreen experience.
@@ -186,3 +190,33 @@ To run the WXT dev server in a devcontainer, but load the dev build of your exte
3. **Tell WXT to listen on all network interfaces**
To enable hot-reloading, your extension has to connect to the WXT dev server running inside your container. WXT will only listen on `localhost` by default, which prevents connections from outside the devcontainer. To fix this you can instruct WXT to listen on all interfaces with `wxt --host 0.0.0.0`.
## How do I use the new Prompt API in Chrome?
The service responsible for the [Prompt API](https://developer.chrome.com/docs/ai/prompt-api) is not enabled by default if you let WXT open the browser during dev mode. When checking `LanguageModel.availability`, you will always receive "unavailable".
You have two options:
1. Pass the `--disable-features=DisableLoadExtensionCommandLineSwitch` feature flag to enable the service in the browser WXT opens:
```ts
// wxt.config.ts
export default defineConfig({
webExt: {
chromiumArgs: [
'--disable-features=DisableLoadExtensionCommandLineSwitch',
],
},
});
```
2. Disable the runner and install your extension in your regular chrome profile manually:
```ts
// wxt.config.ts
export default defineConfig({
webExt: {
disabled: true,
},
});
```
+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 />
+2 -2
View File
@@ -177,7 +177,7 @@ import { defineContentScript } from 'wxt/sandbox'; // [!code --]
import { ContentScriptContext, useAppConfig } from 'wxt/client'; // [!code --]
import { storage } from '#imports'; // [!code ++]
import { defineContentScript } from '#imports'; // [!code ++]
import { ContentScriptContext, useAppConfig } from '#imports'; // [!code ++]
import { ContentScriptContext, getAppConfig } from '#imports'; // [!code ++]
```
You can combine the imports into a single import statement, but it's easier to just find/replace each statement.
@@ -191,7 +191,7 @@ import {
storage, // [!code ++]
defineContentScript, // [!code ++]
ContentScriptContext, // [!code ++]
useAppConfig, // [!code ++]
getAppConfig, // [!code ++]
} from '#imports'; // [!code ++]
```
+1
View File
@@ -0,0 +1 @@
<!--@include: ../packages/is-background/README.md-->
+24 -34
View File
@@ -4,66 +4,56 @@
"engines": {
"node": ">=18.20.3"
},
"packageManager": "pnpm@10.15.1",
"packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8",
"scripts": {
"check": "check && pnpm -r --sequential run check",
"test": "pnpm -r --sequential run test run",
"test:coverage": "pnpm -r --sequential run test:coverage",
"prepare": "simple-git-hooks",
"prepublish": "pnpm -s build",
"docs:gen": "typedoc --options docs/typedoc.json",
"docs:dev": "pnpm -s docs:gen && vitepress dev docs",
"docs:build": "pnpm -s docs:gen && vitepress build docs",
"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",
"@types/fs-extra": "^11.0.4",
"@aklinker1/buildc": "^1.1.7",
"@aklinker1/check": "^2.2.0",
"@commitlint/config-conventional": "^20.4.3",
"@commitlint/types": "^20.4.3",
"@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",
"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.48.0",
"nano-spawn": "^2.0.0",
"nano-staged": "^0.8.0",
"p-map": "^7.0.4",
"prettier": "^3.8.1",
"prettier-plugin-jsdoc": "^1.8.0",
"semver": "^7.7.4",
"simple-git-hooks": "^2.13.1",
"tsx": "4.20.5",
"tinyglobby": "^0.2.15",
"tsdown": "^0.21.0",
"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",
"vitepress-plugin-llms": "^1.11.0",
"vitest-mock-extended": "^3.1.0",
"vue": "^3.5.21",
"vue": "^3.5.29",
"wxt": "workspace:*"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
"pre-commit": "./node_modules/.bin/nano-staged"
},
"lint-staged": {
"nano-staged": {
"*": "prettier --ignore-unknown --write"
},
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"@algolia/client-search",
"search-insights"
]
},
"patchedDependencies": {
"markdown-it-footnote": "patches/markdown-it-footnote.patch"
}
}
}
+60
View File
@@ -1,5 +1,65 @@
# Changelog
## v0.5.4
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.3...analytics-v0.5.4)
### 🩹 Fixes
- Continue using `useAppConfig` to support older versions of WXT ([bfd94556](https://github.com/wxt-dev/wxt/commit/bfd94556))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.5.3
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.2...analytics-v0.5.3)
### 🩹 Fixes
- Add `getAppConfig` as an alias to `useAppConfig` ([#2144](https://github.com/wxt-dev/wxt/pull/2144))
- Allow `userId` option to return `undefined` ([636cf1f8](https://github.com/wxt-dev/wxt/commit/636cf1f8))
- Improve background script detection logic for analytics package ([#1808](https://github.com/wxt-dev/wxt/pull/1808))
### 🏡 Chore
- Created new types, instead of `any` for `analytics` ([#2119](https://github.com/wxt-dev/wxt/pull/2119))
### ❤️ Contributors
- Smit ([@sm17p](https://github.com/sm17p))
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
- Sheng Zhang ([@Arktomson](https://github.com/Arktomson))
## v0.5.2
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.1...analytics-v0.5.2)
### 🩹 Fixes
- Normalize path for createAnalytics of analytics/index.ts ([#2013](https://github.com/wxt-dev/wxt/pull/2013))
- Allow custom API URL in Google Analytics 4 provider options ([#1653](https://github.com/wxt-dev/wxt/pull/1653))
### 💅 Refactors
- Code cleanup in analytics package ([#2084](https://github.com/wxt-dev/wxt/pull/2084))
### 🏡 Chore
- Fix other type error after `chrome` types update ([31ebf966](https://github.com/wxt-dev/wxt/commit/31ebf966))
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
- 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))
### ❤️ Contributors
- Honwhy Wang <honwhy.wang@gmail.com>
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
## v0.5.1
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.0...analytics-v0.5.1)
-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,
});
+63 -48
View File
@@ -2,17 +2,49 @@ import { UAParser } from 'ua-parser-js';
import type {
Analytics,
AnalyticsConfig,
AnalyticsEventMetadata,
AnalyticsPageViewEvent,
AnalyticsProvider,
AnalyticsStorageItem,
AnalyticsTrackEvent,
BaseAnalyticsEvent,
AnalyticsEventMetadata,
AnalyticsProvider,
} from './types';
import { browser } from '@wxt-dev/browser';
import { isBackground } from '@wxt-dev/is-background';
type AnalyticsMessage = {
[K in keyof Analytics]: {
fn: K;
args: Parameters<Analytics[K]>;
};
}[keyof Analytics];
type AnalyticsMethod =
| ((...args: Parameters<Analytics[keyof Analytics]>) => void)
| undefined;
type MethodForwarder = <K extends keyof Analytics>(
fn: K,
) => (...args: Parameters<Analytics[K]>) => void;
const ANALYTICS_PORT = '@wxt-dev/analytics';
const INTERACTIVE_TAGS = new Set([
'A',
'BUTTON',
'INPUT',
'SELECT',
'TEXTAREA',
]);
const INTERACTIVE_ROLES = new Set([
'button',
'link',
'checkbox',
'menuitem',
'tab',
'radio',
]);
export function createAnalytics(config?: AnalyticsConfig): Analytics {
if (!browser?.runtime?.id)
throw Error(
@@ -24,16 +56,14 @@ export function createAnalytics(config?: AnalyticsConfig): Analytics {
);
}
// TODO: This only works for standard WXT extensions, add a more generic
// background script detector that works with non-WXT projects.
if (location.pathname === '/background.js')
return createBackgroundAnalytics(config);
if (isBackground()) return createBackgroundAnalytics(config);
return createFrontendAnalytics();
}
/**
* Creates an analytics client in the background responsible for uploading events to the server to avoid CORS errors.
* Creates an analytics client in the background responsible for uploading
* events to the server to avoid CORS errors.
*/
function createBackgroundAnalytics(
config: AnalyticsConfig | undefined,
@@ -62,8 +92,8 @@ function createBackgroundAnalytics(
const getBackgroundMeta = () => ({
timestamp: Date.now(),
// Don't track sessions for the background, it can be running
// indefinitely, and will inflate session duration stats.
// Don't track sessions for the background, it can be running indefinitely
// and will inflate session duration stats.
sessionId: undefined,
language: navigator.language,
referrer: undefined,
@@ -75,7 +105,7 @@ function createBackgroundAnalytics(
const getBaseEvent = async (
meta: AnalyticsEventMetadata,
): Promise<BaseAnalyticsEvent> => {
const platform = await platformInfo;
const { arch, os } = await platformInfo;
return {
meta,
user: {
@@ -84,8 +114,8 @@ function createBackgroundAnalytics(
version: config?.version ?? manifest.version_name ?? manifest.version,
wxtMode: import.meta.env.MODE,
wxtBrowser: import.meta.env.BROWSER,
arch: platform.arch,
os: platform.os,
arch,
os,
browser: userAgent.browser.name,
browserVersion: userAgent.browser.version,
...(await userProperties),
@@ -147,7 +177,7 @@ function createBackgroundAnalytics(
},
track: async (
eventName: string,
eventProperties?: Record<string, string>,
eventProperties?: Record<string, string | undefined>,
meta: AnalyticsEventMetadata = getBackgroundMeta(),
) => {
const baseEvent = await getBaseEvent(meta);
@@ -181,9 +211,8 @@ function createBackgroundAnalytics(
// Listen for messages from the rest of the extension
browser.runtime.onConnect.addListener((port) => {
if (port.name === ANALYTICS_PORT) {
port.onMessage.addListener(({ fn, args }) => {
// @ts-expect-error: Untyped fn key
void analytics[fn]?.(...args);
port.onMessage.addListener(({ fn, args }: AnalyticsMessage) => {
void (analytics[fn] as AnalyticsMethod)?.(...args);
});
}
});
@@ -191,9 +220,7 @@ function createBackgroundAnalytics(
return analytics;
}
/**
* Creates an analytics client for non-background contexts.
*/
/** Creates an analytics client for non-background contexts. */
function createFrontendAnalytics(): Analytics {
const port = browser.runtime.connect({ name: ANALYTICS_PORT });
const sessionId = Date.now();
@@ -201,17 +228,15 @@ function createFrontendAnalytics(): Analytics {
sessionId,
timestamp: Date.now(),
language: navigator.language,
referrer: globalThis.document?.referrer || undefined,
screen: globalThis.window
? `${globalThis.window.screen.width}x${globalThis.window.screen.height}`
: undefined,
referrer: document.referrer || undefined,
screen: `${window.screen.width}x${window.screen.height}`,
url: location.href,
title: document.title || undefined,
});
const methodForwarder =
(fn: string) =>
(...args: any[]) => {
const methodForwarder: MethodForwarder =
(fn) =>
(...args) => {
port.postMessage({ fn, args: [...args, getFrontendMetadata()] });
};
@@ -222,11 +247,11 @@ function createFrontendAnalytics(): Analytics {
setEnabled: methodForwarder('setEnabled'),
autoTrack: (root) => {
const onClick = (event: Event) => {
const element = event.target as any;
const element = event.target as HTMLElement | null;
if (
!element ||
(!INTERACTIVE_TAGS.has(element.tagName) &&
!INTERACTIVE_ROLES.has(element.getAttribute('role')))
!INTERACTIVE_ROLES.has(element.getAttribute('role') ?? ''))
)
return;
@@ -235,7 +260,7 @@ function createFrontendAnalytics(): Analytics {
id: element.id || undefined,
className: element.className || undefined,
textContent: element.textContent?.substring(0, 50) || undefined, // Limit text content length
href: element.href,
href: (element as HTMLAnchorElement).href,
});
};
root.addEventListener('click', onClick, { capture: true, passive: true });
@@ -247,33 +272,23 @@ function createFrontendAnalytics(): Analytics {
return analytics;
}
function defineStorageItem<T>(key: string): AnalyticsStorageItem<T | undefined>;
function defineStorageItem<T>(
key: string,
defaultValue?: NonNullable<T>,
): AnalyticsStorageItem<T> {
defaultValue: T,
): AnalyticsStorageItem<T>;
function defineStorageItem(
key: string,
defaultValue?: unknown,
): AnalyticsStorageItem<unknown> {
return {
getValue: async () =>
(await browser.storage.local.get(key))[key] ?? defaultValue,
(await browser.storage.local.get<Record<string, unknown>>(key))[key] ??
defaultValue,
setValue: (newValue) => browser.storage.local.set({ [key]: newValue }),
};
}
const INTERACTIVE_TAGS = new Set([
'A',
'BUTTON',
'INPUT',
'SELECT',
'TEXTAREA',
]);
const INTERACTIVE_ROLES = new Set([
'button',
'link',
'checkbox',
'menuitem',
'tab',
'radio',
]);
export function defineAnalyticsProvider<T = never>(
definition: (
/** The analytics object. */
+12 -13
View File
@@ -8,6 +8,7 @@ import {
} from 'wxt/modules';
import { relative, resolve } from 'node:path';
import type { AnalyticsConfig } from './types';
import { normalizePath } from 'wxt';
declare module 'wxt/utils/define-app-config' {
export interface WxtAppConfig {
@@ -22,10 +23,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');
@@ -38,17 +39,15 @@ export default defineWxtModule({
});
// Generate #analytics module
const wxtAnalyticsCode = [
`import { createAnalytics } from '${
import.meta.env.NPM
? clientModuleId
: relative(wxtAnalyticsFolder, clientModuleId)
}';`,
`import { useAppConfig } from '#imports';`,
``,
`export const analytics = createAnalytics(useAppConfig().analytics);`,
``,
].join('\n');
const wxtAnalyticsCode = `import { createAnalytics } from '${
process.env.NPM
? clientModuleId
: normalizePath(relative(wxtAnalyticsFolder, clientModuleId))
}';
import { useAppConfig } from '#imports';
export const analytics = createAnalytics(useAppConfig().analytics);
`;
addAlias(wxt, '#analytics', wxtAnalyticsIndex);
wxt.hook('prepare:types', async (_, entries) => {
entries.push({
@@ -4,6 +4,7 @@ import type { BaseAnalyticsEvent } from '../types';
const DEFAULT_ENGAGEMENT_TIME_IN_MSEC = 100;
export interface GoogleAnalytics4ProviderOptions {
apiUrl?: string;
apiSecret: string;
measurementId: string;
}
@@ -18,7 +19,7 @@ export const googleAnalytics4 =
): Promise<void> => {
const url = new URL(
config?.debug ? '/debug/mp/collect' : '/mp/collect',
'https://www.google-analytics.com',
options.apiUrl ?? 'https://www.google-analytics.com',
);
if (options.apiSecret)
url.searchParams.set('api_secret', options.apiSecret);
+19 -13
View File
@@ -2,39 +2,45 @@ export interface Analytics {
/** Report a page change. */
page: (url: string) => void;
/** Report a custom event. */
track: (eventName: string, eventProperties?: Record<string, string>) => void;
track: (
eventName: string,
eventProperties?: Record<string, string | undefined>,
) => void;
/** Save information about the user. */
identify: (userId: string, userProperties?: Record<string, string>) => void;
/** Automatically setup and track user interactions, returning a function to remove any listeners that were setup. */
/**
* Automatically setup and track user interactions, returning a function to
* remove any listeners that were setup.
*/
autoTrack: (root: Document | ShadowRoot | Element) => () => void;
/** Calls `config.enabled.setValue`. */
setEnabled: (enabled: boolean) => void;
}
export interface AnalyticsConfig {
/**
* Array of providers to send analytics to.
*/
/** Array of providers to send analytics to. */
providers: AnalyticsProvider[];
/**
* Enable debug logs and other provider-specific debugging features.
*/
/** Enable debug logs and other provider-specific debugging features. */
debug?: boolean;
/**
* Your extension's version, reported alongside events.
*
* @default browser.runtime.getManifest().version`.
*/
version?: string;
/**
* Configure how the enabled flag is persisted. Defaults to using `browser.storage.local`.
* Configure how the enabled flag is persisted. Defaults to using
* `browser.storage.local`.
*/
enabled?: AnalyticsStorageItem<boolean>;
/**
* Configure how the user Id is persisted. Defaults to using `browser.storage.local`.
* Configure how the user Id is persisted. Defaults to using
* `browser.storage.local`.
*/
userId?: AnalyticsStorageItem<string>;
userId?: AnalyticsStorageItem<string | undefined>;
/**
* Configure how user properties are persisted. Defaults to using `browser.storage.local`.
* Configure how user properties are persisted. Defaults to using
* `browser.storage.local`.
*/
userProperties?: AnalyticsStorageItem<Record<string, string>>;
}
@@ -94,6 +100,6 @@ export interface AnalyticsPageViewEvent extends BaseAnalyticsEvent {
export interface AnalyticsTrackEvent extends BaseAnalyticsEvent {
event: {
name: string;
properties?: Record<string, string>;
properties?: Record<string, string | undefined>;
};
}
+26 -27
View File
@@ -1,14 +1,36 @@
{
"name": "@wxt-dev/analytics",
"version": "0.5.1",
"version": "0.5.4",
"description": "Add analytics to your web extension",
"type": "module",
"scripts": {
"dev": "buildc --deps-only -- wxt",
"dev:build": "buildc --deps-only -- wxt build",
"check": "pnpm build && check",
"build": "buildc -- tsdown",
"prepack": "pnpm build",
"prepare": "buildc --deps-only -- wxt prepare"
},
"dependencies": {
"@wxt-dev/browser": "workspace:^",
"@wxt-dev/is-background": "workspace:^",
"ua-parser-js": "^1.0.40"
},
"peerDependencies": {
"wxt": ">=0.20.0"
},
"devDependencies": {
"@types/ua-parser-js": "^0.7.39",
"publint": "^0.3.18",
"typescript": "^5.9.3",
"wxt": "workspace:*"
},
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/analytics"
},
"license": "MIT",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.mts",
@@ -35,31 +57,8 @@
}
},
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"files": [
"dist"
],
"scripts": {
"dev": "buildc --deps-only -- wxt",
"dev:build": "buildc --deps-only -- wxt build",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"prepack": "pnpm -s build",
"prepare": "buildc --deps-only -- wxt prepare"
},
"peerDependencies": {
"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",
"wxt": "workspace:*"
},
"dependencies": {
"@wxt-dev/browser": "workspace:^",
"ua-parser-js": "^1.0.40"
}
]
}
+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',
},
});
+29
View File
@@ -1,5 +1,34 @@
# Changelog
## v1.1.1
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.1.0...auto-icons-v1.1.1)
### 🩹 Fixes
- Auto icons override default icons ([#1616](https://github.com/wxt-dev/wxt/pull/1616))
### 💅 Refactors
- Standardize file existence checks to `pathExists` ([#2083](https://github.com/wxt-dev/wxt/pull/2083))
### 🏡 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))
- 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))
- **deps:** Upgrade non-breaking production dependencies ([#1877](https://github.com/wxt-dev/wxt/pull/1877))
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
- 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))
### ❤️ Contributors
- Omerfardemir <od080624@gmail.com>
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v1.1.0
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.2...auto-icons-v1.1.0)
+11 -11
View File
@@ -24,10 +24,10 @@
}
],
"license": "MIT",
"version": "1.1.0",
"version": "1.1.1",
"type": "module",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"types": "./dist/index.d.mts",
@@ -38,23 +38,23 @@
"dist"
],
"scripts": {
"build": "buildc -- unbuild",
"check": "pnpm build && check"
"build": "buildc -- tsdown",
"check": "pnpm build && check",
"test": "buildc --deps-only -- vitest",
"prepack": "pnpm build"
},
"peerDependencies": {
"wxt": ">=0.19.0"
},
"dependencies": {
"defu": "^6.1.4",
"fs-extra": "^11.3.1",
"sharp": "^0.34.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.51.0",
"publint": "^0.3.18",
"typescript": "^5.9.3",
"vitest": "^4.0.18",
"wxt": "workspace:*"
}
}
@@ -0,0 +1,630 @@
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
import { resolve } from 'node:path';
import * as fsPromises from 'node:fs/promises';
import sharp from 'sharp';
import type { Wxt, UserManifest } from 'wxt';
// Import the actual module
import autoIconsModule from '../index';
import type { AutoIconsOptions } from '../index';
// Mock dependencies
vi.mock('node:fs/promises', () => ({
mkdir: vi.fn(),
access: vi.fn(),
}));
vi.mock('sharp', () => ({
default: vi.fn(),
}));
// Type definitions for better type safety
interface MockWxt {
config: {
srcDir: string;
outDir: string;
mode: 'development' | 'production';
};
logger: {
warn: Mock;
};
hooks: {
hook: Mock;
};
}
interface PublicAsset {
type: string;
fileName: string;
}
interface BuildOutput {
publicAssets: PublicAsset[];
}
describe('auto-icons module', () => {
const mockWxt: MockWxt = {
config: {
srcDir: '/mock/src',
outDir: '/mock/dist',
mode: 'development',
},
logger: {
warn: vi.fn(),
},
hooks: {
hook: vi.fn(),
},
};
const createMockSharpInstance = () => {
const instance = {
png: vi.fn(),
grayscale: vi.fn(),
resize: vi.fn(),
toFile: vi.fn().mockResolvedValue(undefined),
};
// Make methods chainable
instance.png.mockReturnValue(instance);
instance.grayscale.mockReturnValue(instance);
instance.resize.mockImplementation(() => {
// Create a new instance for each resize to simulate real sharp behavior
const resizedInstance = { ...instance };
resizedInstance.toFile = vi.fn().mockResolvedValue(undefined);
return resizedInstance;
});
return instance;
};
let mockSharpInstance: ReturnType<typeof createMockSharpInstance>;
beforeEach(() => {
vi.clearAllMocks();
mockSharpInstance = createMockSharpInstance();
vi.mocked(sharp).mockReturnValue(
mockSharpInstance as unknown as sharp.Sharp,
);
vi.mocked(fsPromises.access).mockResolvedValue(undefined);
vi.mocked(fsPromises.mkdir).mockResolvedValue(undefined as any);
});
describe('module setup', () => {
it('should have correct module metadata', () => {
expect(autoIconsModule.name).toBe('@wxt-dev/auto-icons');
expect(autoIconsModule.configKey).toBe('autoIcons');
expect(typeof autoIconsModule.setup).toBe('function');
});
});
describe('options handling', () => {
it('should use default options when not provided', async () => {
const options: AutoIconsOptions = {};
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
// Verify that the module was set up (hooks were registered)
expect(mockWxt.hooks.hook).toHaveBeenCalledWith(
'build:manifestGenerated',
expect.any(Function),
);
expect(mockWxt.hooks.hook).toHaveBeenCalledWith(
'build:done',
expect.any(Function),
);
expect(mockWxt.hooks.hook).toHaveBeenCalledWith(
'prepare:publicPaths',
expect.any(Function),
);
});
it('should merge custom options with defaults', async () => {
const options: AutoIconsOptions = {
sizes: [64, 32],
grayscaleOnDevelopment: false,
};
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
// Verify that the module was set up with custom options
expect(mockWxt.hooks.hook).toHaveBeenCalledWith(
'build:manifestGenerated',
expect.any(Function),
);
expect(mockWxt.hooks.hook).toHaveBeenCalledWith(
'build:done',
expect.any(Function),
);
expect(mockWxt.hooks.hook).toHaveBeenCalledWith(
'prepare:publicPaths',
expect.any(Function),
);
});
});
describe('error handling', () => {
it('should warn when disabled', async () => {
const options: AutoIconsOptions = {
enabled: false,
};
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
expect(mockWxt.logger.warn).toHaveBeenCalledWith(
'`[auto-icons]` @wxt-dev/auto-icons disabled',
);
expect(mockWxt.hooks.hook).not.toHaveBeenCalled();
});
it('should warn when base icon not found', async () => {
vi.mocked(fsPromises.access).mockRejectedValue(new Error('ENOENT'));
const options: AutoIconsOptions = {
enabled: true,
baseIconPath: 'assets/missing-icon.png',
};
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
expect(mockWxt.logger.warn).toHaveBeenCalledWith(
expect.stringContaining(
'Skipping icon generation, no base icon found at',
),
);
expect(mockWxt.hooks.hook).not.toHaveBeenCalled();
});
});
describe('manifest generation hook', () => {
it('should update manifest with default icons when no custom sizes provided', async () => {
const options: AutoIconsOptions = {
enabled: true,
};
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
const manifestHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:manifestGenerated')?.[1];
expect(manifestHook).toBeDefined();
const manifest: UserManifest = {};
if (manifestHook) {
await manifestHook(mockWxt as unknown as Wxt, manifest);
}
// Should use default sizes: [128, 48, 32, 16]
expect(manifest.icons).toEqual({
128: 'icons/128.png',
48: 'icons/48.png',
32: 'icons/32.png',
16: 'icons/16.png',
});
});
it('should merge custom sizes with defaults', async () => {
const options: AutoIconsOptions = {
enabled: true,
sizes: [96, 64], // These will be merged with defaults
};
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
const manifestHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:manifestGenerated')?.[1];
expect(manifestHook).toBeDefined();
const manifest: UserManifest = {};
if (manifestHook) {
await manifestHook(mockWxt as unknown as Wxt, manifest);
}
// defu merges arrays, so we get both custom and default sizes
expect(manifest.icons).toEqual({
96: 'icons/96.png',
64: 'icons/64.png',
128: 'icons/128.png',
48: 'icons/48.png',
32: 'icons/32.png',
16: 'icons/16.png',
});
});
it('should warn when overwriting existing icons in manifest', async () => {
const options: AutoIconsOptions = {
enabled: true,
};
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
const manifestHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:manifestGenerated')?.[1];
const manifest: UserManifest = {
icons: {
128: 'existing-icon.png',
},
};
if (manifestHook) {
await manifestHook(mockWxt as unknown as Wxt, manifest);
}
expect(mockWxt.logger.warn).toHaveBeenCalledWith(
'`[auto-icons]` icons property found in manifest, overwriting with auto-generated icons',
);
});
});
describe('icon generation hook', () => {
it('should generate icons with default sizes', async () => {
const options: AutoIconsOptions = {
enabled: true,
};
const output: BuildOutput = {
publicAssets: [],
};
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
const buildHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
expect(buildHook).toBeDefined();
if (buildHook) {
await buildHook(mockWxt as unknown as Wxt, output);
}
expect(sharp).toHaveBeenCalledWith(
resolve('/mock/src', 'assets/icon.png'),
);
expect(mockSharpInstance.png).toHaveBeenCalled();
// Should resize to default sizes
expect(mockSharpInstance.resize).toHaveBeenCalledWith(128);
expect(mockSharpInstance.resize).toHaveBeenCalledWith(48);
expect(mockSharpInstance.resize).toHaveBeenCalledWith(32);
expect(mockSharpInstance.resize).toHaveBeenCalledWith(16);
expect(fsPromises.mkdir).toHaveBeenCalledWith(
resolve('/mock/dist', 'icons'),
{ recursive: true },
);
expect(output.publicAssets).toEqual([
{ type: 'asset', fileName: 'icons/128.png' },
{ type: 'asset', fileName: 'icons/48.png' },
{ type: 'asset', fileName: 'icons/32.png' },
{ type: 'asset', fileName: 'icons/16.png' },
]);
});
it('should generate icons with custom sizes merged with defaults', async () => {
const options: AutoIconsOptions = {
enabled: true,
sizes: [96, 64],
};
const output: BuildOutput = {
publicAssets: [],
};
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
const buildHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
expect(buildHook).toBeDefined();
if (buildHook) {
await buildHook(mockWxt as unknown as Wxt, output);
}
// Should include both custom and default sizes
expect(mockSharpInstance.resize).toHaveBeenCalledWith(96);
expect(mockSharpInstance.resize).toHaveBeenCalledWith(64);
expect(mockSharpInstance.resize).toHaveBeenCalledWith(128);
expect(mockSharpInstance.resize).toHaveBeenCalledWith(48);
expect(mockSharpInstance.resize).toHaveBeenCalledWith(32);
expect(mockSharpInstance.resize).toHaveBeenCalledWith(16);
expect(output.publicAssets).toEqual([
{ type: 'asset', fileName: 'icons/96.png' },
{ type: 'asset', fileName: 'icons/64.png' },
{ type: 'asset', fileName: 'icons/128.png' },
{ type: 'asset', fileName: 'icons/48.png' },
{ type: 'asset', fileName: 'icons/32.png' },
{ type: 'asset', fileName: 'icons/16.png' },
]);
});
it('should apply grayscale in development mode', async () => {
const options: AutoIconsOptions = {
enabled: true,
grayscaleOnDevelopment: true,
sizes: [128],
};
const output: BuildOutput = { publicAssets: [] };
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
const buildHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
if (buildHook) {
await buildHook(mockWxt as unknown as Wxt, output);
}
expect(mockSharpInstance.grayscale).toHaveBeenCalled();
});
it('should not apply grayscale in production mode', async () => {
const prodMockWxt = {
...mockWxt,
config: {
...mockWxt.config,
mode: 'production' as const,
},
};
const options: AutoIconsOptions = {
enabled: true,
grayscaleOnDevelopment: true,
sizes: [128],
};
const output: BuildOutput = { publicAssets: [] };
await autoIconsModule.setup!(prodMockWxt as unknown as Wxt, options);
const buildHook = vi
.mocked(prodMockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
if (buildHook) {
await buildHook(prodMockWxt as unknown as Wxt, output);
}
expect(mockSharpInstance.grayscale).not.toHaveBeenCalled();
});
it('should not apply grayscale when disabled', async () => {
const options: AutoIconsOptions = {
enabled: true,
grayscaleOnDevelopment: false,
sizes: [128],
};
const output: BuildOutput = { publicAssets: [] };
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
const buildHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
if (buildHook) {
await buildHook(mockWxt as unknown as Wxt, output);
}
expect(mockSharpInstance.grayscale).not.toHaveBeenCalled();
});
});
describe('public paths hook', () => {
it('should add default icon paths to public paths', async () => {
const options: AutoIconsOptions = {
enabled: true,
};
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
const pathsHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'prepare:publicPaths')?.[1];
expect(pathsHook).toBeDefined();
const paths: string[] = [];
if (pathsHook) {
pathsHook(mockWxt as unknown as Wxt, paths);
}
expect(paths).toEqual([
'icons/128.png',
'icons/48.png',
'icons/32.png',
'icons/16.png',
]);
});
});
describe('edge cases and error handling', () => {
it('should handle empty sizes array', async () => {
const options: AutoIconsOptions = {
enabled: true,
sizes: [], // Empty array should still merge with defaults
};
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
const manifestHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:manifestGenerated')?.[1];
const manifest: UserManifest = {};
if (manifestHook) {
await manifestHook(mockWxt as unknown as Wxt, manifest);
}
// Should still have default sizes due to defu merge
expect(manifest.icons).toEqual({
128: 'icons/128.png',
48: 'icons/48.png',
32: 'icons/32.png',
16: 'icons/16.png',
});
});
it('should handle sharp processing errors gracefully', async () => {
const options: AutoIconsOptions = {
enabled: true,
};
// Make toFile throw an error - need to properly chain resize -> png -> toFile
const errorInstance = {
toFile: vi.fn().mockRejectedValue(new Error('Sharp processing failed')),
grayscale: vi.fn(),
composite: vi.fn(),
};
errorInstance.grayscale.mockReturnValue(errorInstance);
errorInstance.composite.mockReturnValue(errorInstance);
mockSharpInstance.resize = vi.fn().mockImplementation(() => ({
png: vi.fn().mockReturnValue(errorInstance),
}));
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
const buildHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
const output: BuildOutput = { publicAssets: [] };
// Should throw the sharp error
if (buildHook) {
await expect(
buildHook(mockWxt as unknown as Wxt, output),
).rejects.toThrow('Sharp processing failed');
}
});
it('should handle file system errors during directory creation', async () => {
const options: AutoIconsOptions = {
enabled: true,
};
// Make ensureDir throw an error
vi.mocked(fsPromises.mkdir).mockRejectedValue(
new Error('Directory creation failed'),
);
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
const buildHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
const output: BuildOutput = { publicAssets: [] };
// The module doesn't await ensureDir, so it won't throw
if (buildHook) {
await buildHook(mockWxt as unknown as Wxt, output);
// But ensureDir should have been called
expect(fsPromises.mkdir).toHaveBeenCalled();
}
});
it('should handle custom base icon path correctly', async () => {
const customPath = 'custom/icon.png';
const options: AutoIconsOptions = {
enabled: true,
baseIconPath: customPath,
};
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
const buildHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
const output: BuildOutput = { publicAssets: [] };
if (buildHook) {
await buildHook(mockWxt as unknown as Wxt, output);
}
// Should resolve the path relative to srcDir
expect(sharp).toHaveBeenCalledWith(resolve('/mock/src', customPath));
});
});
describe('integration test', () => {
it('should handle full workflow correctly', async () => {
const options: AutoIconsOptions = {
enabled: true,
baseIconPath: 'assets/custom-icon.png',
sizes: [96], // Will be merged with defaults
grayscaleOnDevelopment: false,
};
const manifest: UserManifest = {};
const output: BuildOutput = { publicAssets: [] };
const paths: string[] = [];
// Setup the module
await autoIconsModule.setup!(mockWxt as unknown as Wxt, options);
// Execute all hooks
const manifestHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:manifestGenerated')?.[1];
const buildHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'build:done')?.[1];
const pathsHook = vi
.mocked(mockWxt.hooks.hook)
.mock.calls.find((call) => call[0] === 'prepare:publicPaths')?.[1];
if (manifestHook) {
await manifestHook(mockWxt as unknown as Wxt, manifest);
}
if (buildHook) {
await buildHook(mockWxt as unknown as Wxt, output);
}
if (pathsHook) {
pathsHook(mockWxt as unknown as Wxt, paths);
}
// Verify results - defu merges arrays
expect(manifest.icons).toEqual({
96: 'icons/96.png',
128: 'icons/128.png',
48: 'icons/48.png',
32: 'icons/32.png',
16: 'icons/16.png',
});
expect(output.publicAssets).toEqual([
{ type: 'asset', fileName: 'icons/96.png' },
{ type: 'asset', fileName: 'icons/128.png' },
{ type: 'asset', fileName: 'icons/48.png' },
{ type: 'asset', fileName: 'icons/32.png' },
{ type: 'asset', fileName: 'icons/16.png' },
]);
expect(paths).toEqual([
'icons/96.png',
'icons/128.png',
'icons/48.png',
'icons/32.png',
'icons/16.png',
]);
expect(sharp).toHaveBeenCalledWith(
resolve('/mock/src', 'assets/custom-icon.png'),
);
expect(mockSharpInstance.grayscale).not.toHaveBeenCalled();
});
});
});
+19 -13
View File
@@ -3,7 +3,7 @@ import { defineWxtModule } from 'wxt/modules';
import { resolve, relative } from 'node:path';
import defu from 'defu';
import sharp from 'sharp';
import { ensureDir, exists } from 'fs-extra';
import { access, mkdir } from 'node:fs/promises';
export default defineWxtModule<AutoIconsOptions>({
name: '@wxt-dev/auto-icons',
@@ -37,7 +37,11 @@ export default defineWxtModule<AutoIconsOptions>({
if (!parsedOptions.enabled)
return wxt.logger.warn(`\`[auto-icons]\` ${this.name} disabled`);
if (!(await exists(resolvedPath))) {
const iconExists = await access(resolvedPath).then(
() => true,
() => false,
);
if (!iconExists) {
return wxt.logger.warn(
`\`[auto-icons]\` Skipping icon generation, no base icon found at ${relative(process.cwd(), resolvedPath)}`,
);
@@ -45,7 +49,7 @@ export default defineWxtModule<AutoIconsOptions>({
wxt.hooks.hook('build:manifestGenerated', async (wxt, manifest) => {
if (manifest.icons)
return wxt.logger.warn(
wxt.logger.warn(
'`[auto-icons]` icons property found in manifest, overwriting with auto-generated icons',
);
@@ -91,7 +95,7 @@ export default defineWxtModule<AutoIconsOptions>({
}
}
ensureDir(resolve(outputFolder, 'icons'));
mkdir(resolve(outputFolder, 'icons'), { recursive: true });
await resizedImage.toFile(resolve(outputFolder, `icons/${size}.png`));
output.publicAssets.push({
@@ -109,12 +113,11 @@ export default defineWxtModule<AutoIconsOptions>({
},
});
/**
* Options for the auto-icons module
*/
/** Options for the auto-icons module */
export interface AutoIconsOptions {
/**
* Enable auto-icons generation
*
* @default true
*/
enabled?: boolean;
@@ -122,27 +125,30 @@ export interface AutoIconsOptions {
* Path to the image to use.
*
* Path is relative to the project's src directory.
* @default "<srcDir>/assets/icon.png"
*
* @default '<srcDir>/assets/icon.png'
*/
baseIconPath?: string;
/**
* Apply a visual indicator to the icon when running in development mode.
*
* "grayscale" converts the icon to grayscale.
* "overlay" covers the bottom half with a yellow rectangle and writes "DEV" in black text.
* Set to `false` to disable any indicator.
* "grayscale" converts the icon to grayscale. "overlay" covers the bottom
* half with a yellow rectangle and writes "DEV" in black text. Set to `false`
* to disable any indicator.
*
* @default "grayscale"
* @default 'grayscale'
*/
developmentIndicator?: 'grayscale' | 'overlay' | false;
/**
* Grayscale the image when in development mode to indicate development
* @default true
*
* @deprecated Use `developmentIndicator` instead
* @default true
*/
grayscaleOnDevelopment?: boolean;
/**
* Sizes to generate icons for
*
* @default [128, 48, 32, 16]
*/
sizes?: number[];
+8
View File
@@ -0,0 +1,8 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
mockReset: true,
restoreMocks: true,
},
});
+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.38",
"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.38",
"@types/node": "^20.0.0",
"nano-spawn": "^2.0.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"dependencies": {
"@types/filesystem": "*",
+9 -11
View File
@@ -1,5 +1,5 @@
import spawn from 'nano-spawn';
import fs from 'fs-extra';
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve, sep } from 'node:path';
import { sep as posixSep } from 'node:path/posix';
@@ -17,8 +17,8 @@ const pkgJsonPath = fileURLToPath(
import.meta.resolve('@types/chrome/package.json'),
);
const pkgDir = dirname(pkgJsonPath);
const pkgJson = await fs.readJson(pkgJsonPath);
const pkgJsonTemplate = await fs.readFile('templates/package.json', 'utf8');
const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8'));
const pkgJsonTemplate = await readFile('templates/package.json', 'utf8');
const newPkgJson = JSON.parse(
pkgJsonTemplate.replaceAll('{{chromeTypesVersion}}', pkgJson.version),
);
@@ -27,7 +27,7 @@ newPkgJson.peerDependencies = pkgJson.peerDependencies;
newPkgJson.peerDependenciesMeta = pkgJson.peerDependenciesMeta;
const outPkgJsonPath = resolve('package.json');
await fs.writeJson(outPkgJsonPath, newPkgJson);
await writeFile(outPkgJsonPath, JSON.stringify(newPkgJson, null, 2));
await spawn('pnpm', ['-w', 'prettier', '--write', outPkgJsonPath]);
// Generate declaration files
@@ -35,7 +35,7 @@ await spawn('pnpm', ['-w', 'prettier', '--write', outPkgJsonPath]);
console.log('Generating declaration files');
const outDir = resolve('src/gen');
const declarationFileMapping = (
await fs.readdir(pkgDir, {
await readdir(pkgDir, {
recursive: true,
encoding: 'utf8',
})
@@ -50,19 +50,17 @@ const declarationFileMapping = (
}));
for (const { file, srcPath, destPath } of declarationFileMapping) {
const content = await fs.readFile(srcPath, 'utf8');
const content = await readFile(srcPath, 'utf8');
const transformedContent = transformFile(file, content);
const destDir = dirname(destPath);
await fs.mkdir(destDir, { recursive: true });
await fs.writeFile(destPath, transformedContent);
await mkdir(destDir, { recursive: true });
await writeFile(destPath, transformedContent);
console.log(` \x1b[2m-\x1b[0m \x1b[36m${file}\x1b[0m`);
}
// 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
+7 -7
View File
@@ -5,19 +5,19 @@ import { browser, type Browser } from '../index';
describe('browser', () => {
describe('types', () => {
it('should provide types via the Browser import', () => {
expectTypeOf<Browser.runtime.MessageSender>().toMatchTypeOf<chrome.runtime.MessageSender>();
expectTypeOf<Browser.storage.AreaName>().toMatchTypeOf<chrome.storage.AreaName>();
expectTypeOf<Browser.i18n.LanguageDetectionResult>().toMatchTypeOf<chrome.i18n.LanguageDetectionResult>();
expectTypeOf<Browser.runtime.MessageSender>().toEqualTypeOf<chrome.runtime.MessageSender>();
expectTypeOf<Browser.storage.AreaName>().toEqualTypeOf<chrome.storage.AreaName>();
expectTypeOf<Browser.i18n.LanguageDetectionResult>().toEqualTypeOf<chrome.i18n.LanguageDetectionResult>();
});
it('should provide values via the browser import', () => {
expectTypeOf(browser.runtime.id).toMatchTypeOf<string>();
expectTypeOf(browser.runtime.id).toEqualTypeOf<string>();
expectTypeOf(
browser.storage.local,
).toMatchTypeOf<Browser.storage.StorageArea>();
).toEqualTypeOf<Browser.storage.LocalStorageArea>();
expectTypeOf(
browser.i18n.detectLanguage('Hello, world!'),
).resolves.toMatchTypeOf<chrome.i18n.LanguageDetectionResult>();
browser.i18n.detectLanguage,
).returns.resolves.toEqualTypeOf<chrome.i18n.LanguageDetectionResult>();
});
});
});
+2768 -2395
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"
"@types/node": "^20.0.0",
"nano-spawn": "^2.0.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
}
}
+26
View File
@@ -1,5 +1,31 @@
# Changelog
## v0.2.5
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.4...i18n-v0.2.5)
### 🩹 Fixes
- Add `.jsonc` support for locale files to match docs ([#2066](https://github.com/wxt-dev/wxt/pull/2066))
### 🏡 Chore
- Manually fix markdownlint errors ([#1711](https://github.com/wxt-dev/wxt/pull/1711))
- **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))
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
- 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))
### ❤️ Contributors
- Ilya Kubariev ([@gymnasy55](https://github.com/gymnasy55))
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.2.4
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.3...i18n-v0.2.4)
+13 -14
View File
@@ -1,7 +1,7 @@
{
"name": "@wxt-dev/i18n",
"description": "Type-safe wrapper around browser.i18n.getMessage with additional features",
"version": "0.2.4",
"version": "0.2.5",
"type": "module",
"repository": {
"type": "git",
@@ -21,15 +21,16 @@
"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"
"test": "buildc --deps-only -- vitest",
"prepack": "pnpm build"
},
"dependencies": {
"@wxt-dev/browser": "workspace:^",
"chokidar": "^4.0.3",
"confbox": "^0.1.8 || ^0.2.2",
"fast-glob": "^3.3.3"
"chokidar": "^5.0.0",
"confbox": "^0.2.4",
"tinyglobby": "^0.2.15"
},
"peerDependencies": {
"wxt": ">=0.19.7"
@@ -40,19 +41,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.51.0",
"publint": "^0.3.18",
"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": {
+5 -10
View File
@@ -51,7 +51,8 @@ export type MessageFormat = 'JSON5' | 'YAML' | 'TOML';
//
/**
* See https://developer.chrome.com/docs/extensions/reference/api/i18n#overview-predefined
* See
* https://developer.chrome.com/docs/extensions/reference/api/i18n#overview-predefined
*/
const PREDEFINED_MESSAGES: Record<string, ChromeMessage> = {
'@@extension_id': {
@@ -110,9 +111,7 @@ const ALLOWED_CHROME_MESSAGE_KEYS: Set<string> = new Set<keyof ChromeMessage>([
// PARSING
//
/**
* Parse a messages file, extract the messages. Supports JSON, JSON5, and YAML.
*/
/** Parse a messages file, extract the messages. Supports JSON, JSON5, and YAML. */
export async function parseMessagesFile(
file: string,
): Promise<ParsedMessage[]> {
@@ -121,9 +120,7 @@ export async function parseMessagesFile(
return parseMessagesText(text, EXT_FORMATS_MAP[ext] ?? 'JSON5');
}
/**
* Parse a string, extracting the messages. Supports JSON, JSON5, and YAML.
*/
/** Parse a string, extracting the messages. Supports JSON, JSON5, and YAML. */
export function parseMessagesText(
text: string,
format: 'JSON5' | 'YAML' | 'TOML',
@@ -131,9 +128,7 @@ export function parseMessagesText(
return parseMessagesObject(PARSERS[format](text));
}
/**
* Given the JS object form of a raw messages file, extract the messages.
*/
/** Given the JS object form of a raw messages file, extract the messages. */
export function parseMessagesObject(object: any): ParsedMessage[] {
return _parseMessagesObject(
[],
+1 -3
View File
@@ -1,6 +1,4 @@
/**
* @module @wxt-dev/i18n
*/
/** @module @wxt-dev/i18n */
import {
I18nStructure,
DefaultI18nStructure,
+8 -8
View File
@@ -3,7 +3,7 @@
*
* ```ts
* export default defineConfig({
* modules: ["@wxt-dev/i18n/module"],
* modules: ['@wxt-dev/i18n/module'],
* });
* ```
*
@@ -18,7 +18,7 @@ import {
generateTypeText,
SUPPORTED_LOCALES,
} from './build';
import glob from 'fast-glob';
import { glob } from 'tinyglobby';
import { basename, extname, join, resolve } from 'node:path';
import { watch } from 'chokidar';
import { GeneratedPublicFile, WxtDirFileEntry } from 'wxt';
@@ -44,9 +44,10 @@ 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,
expandDirectories: false,
});
const unsupportedLocales: string[] = [];
@@ -88,7 +89,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}\``,
);
}
@@ -180,13 +181,12 @@ export { type GeneratedI18nStructure }
},
});
/**
* Options for the i18n module
*/
/** Options for the i18n module */
export interface I18nOptions {
/**
* Directory containing files that define the translations.
* @default "${config.srcDir}/locales"
*
* @default '${config.srcDir}/locales'
*/
localesDir?: string;
}
+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',
},
]);
+17
View File
@@ -0,0 +1,17 @@
# `@wxt-dev/is-background`
Exports a getter to determine if the current JS context is the background or not.
## Installation
```sh
pnpm add @wxt-dev/is-background
```
## Usage
```ts
import { isBackground } from '@wxt-dev/is-background';
isBackground(); // true | false
```
+56
View File
@@ -0,0 +1,56 @@
{
"name": "@wxt-dev/is-background",
"type": "module",
"version": "1.0.0",
"description": "Check if the current context is the background or not.",
"license": "MIT",
"scripts": {
"build": "buildc -- tsdown",
"check": "pnpm build && check",
"test": "buildc --deps-only -- vitest",
"test:coverage": "pnpm test run --coverage",
"prepack": "pnpm build"
},
"dependencies": {
"@wxt-dev/browser": "workspace:^"
},
"devDependencies": {
"oxlint": "^1.51.0",
"publint": "^0.3.18",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/is-background"
},
"keywords": [
"wxt",
"chrome",
"web",
"extension",
"is",
"background",
"script",
"page",
"service",
"worker"
],
"author": {
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
},
"funding": "https://github.com/sponsors/wxt-dev",
"files": [
"dist"
],
"types": "./dist/index.d.mts",
"module": "./dist/index.mjs",
"exports": {
".": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
}
}
}
@@ -0,0 +1,145 @@
import { describe, it, vi, expect } from 'vitest';
import { getIsBackground } from '../getter';
let mockBrowser: any;
vi.mock('@wxt-dev/browser', () => ({
get browser() {
return mockBrowser;
},
}));
const backgroundWindow = Symbol('Background');
const otherWindow = Symbol('Other');
function setupEnv(options: {
window: symbol | undefined;
hasExtensionApis: boolean;
hasGetBackgroundPage: boolean;
hasServiceWorkerGlobalScope: boolean;
}): void {
vi.unstubAllGlobals();
mockBrowser = undefined;
if (options.window) {
vi.stubGlobal('window', options.window);
}
if (options.hasExtensionApis) {
mockBrowser = {
runtime: {
id: 'test',
},
...(options.hasGetBackgroundPage && {
extension: {
getBackgroundPage: () => backgroundWindow,
},
}),
};
}
if (options.hasServiceWorkerGlobalScope) {
class ServiceWorkerGlobalScope {}
vi.stubGlobal('ServiceWorkerGlobalScope', ServiceWorkerGlobalScope);
vi.stubGlobal('self', new ServiceWorkerGlobalScope());
}
}
describe('isBackground Getter', () => {
describe('Non-extension contexts', () => {
it('should return false', () => {
setupEnv({
hasServiceWorkerGlobalScope: false,
hasExtensionApis: false,
hasGetBackgroundPage: false,
window: otherWindow,
});
expect(getIsBackground()).toBe(false);
});
});
describe('Chromium & Safari', () => {
const hasExtensionApis = true;
describe('MV2', () => {
const hasServiceWorkerGlobalScope = false;
const hasGetBackgroundPage = true;
it('should return true inside the background page', () => {
setupEnv({
hasServiceWorkerGlobalScope,
hasExtensionApis,
hasGetBackgroundPage,
window: backgroundWindow,
});
expect(getIsBackground()).toBe(true);
});
it('should return false outside the background page', () => {
setupEnv({
hasServiceWorkerGlobalScope,
hasExtensionApis,
hasGetBackgroundPage,
window: otherWindow,
});
expect(getIsBackground()).toBe(false);
});
});
describe('MV3', () => {
const hasGetBackgroundPage = false;
it('should return true inside the service worker', () => {
setupEnv({
hasServiceWorkerGlobalScope: true,
hasExtensionApis,
hasGetBackgroundPage,
window: undefined,
});
expect(getIsBackground()).toBe(true);
});
it('should return false outside the service worker', () => {
setupEnv({
hasServiceWorkerGlobalScope: false,
hasExtensionApis,
hasGetBackgroundPage,
window: otherWindow,
});
expect(getIsBackground()).toBe(false);
});
});
});
describe('Firefox, MV2 & MV3', () => {
const hasServiceWorkerGlobalScope = false;
const hasExtensionApis = true;
const hasGetBackgroundPage = true;
it('should return true inside the background page', () => {
setupEnv({
hasServiceWorkerGlobalScope,
hasExtensionApis,
hasGetBackgroundPage,
window: backgroundWindow,
});
expect(getIsBackground()).toBe(true);
});
it('should return false outside the background page', () => {
setupEnv({
hasServiceWorkerGlobalScope,
hasExtensionApis,
hasGetBackgroundPage,
window: otherWindow,
});
expect(getIsBackground()).toBe(false);
});
});
});
+32
View File
@@ -0,0 +1,32 @@
import { browser } from '@wxt-dev/browser';
declare class ServiceWorkerGlobalScope {}
export function getIsBackground(): boolean {
// Are we in an extension context?
if (!browser?.runtime?.id) return false;
// Is this a true MV3 service worker?
//
// - ✅ Chromium MV3
// - ❌ Firefox MV3 - Uses a non-persistent HTML page instead of a service worker.
// - ✅ Safari MV3
if (
typeof ServiceWorkerGlobalScope !== 'undefined' &&
self instanceof ServiceWorkerGlobalScope
) {
return true;
}
// Is this the background page?
//
// - ✅ Chromium MV2
// - ✅ Firefox MV2
// - ✅ Firefox MV3 - Works with the non-persistent HTML page
// - ✅ Safari MV2
return (
typeof window !== 'undefined' &&
typeof browser.extension?.getBackgroundPage === 'function' &&
browser.extension.getBackgroundPage() === window
);
}
+31
View File
@@ -0,0 +1,31 @@
/**
* This module uses a lazy getter function so the logic isn't ran until it's
* needed.
*
* This has a few benefits:
*
* 1. Easier to mock in tests
* 2. Safe to import in NodeJS environments (but it should be safe to run just
* in-case)
* 3. Keeps startup fast by waiting to run the slow functions (`instanceof` or
* `browser.extension.getBackgroundPage`) until needed
*
* @module @wxt-dev/is-background
*/
import { getIsBackground } from './getter';
let cached: boolean | undefined;
/**
* Getter that returns if the current context is apart of an extension's
* background or not.
*
* > This function caches the result when called for the first time so it doesn't
* > have to recalculate.
*
* @returns True when in a background page or service worker.
*/
export function isBackground(): boolean {
if (cached == null) cached = getIsBackground();
return cached;
}
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"exclude": ["node_modules/**", "dist/**"]
}
+46
View File
@@ -1,5 +1,51 @@
# Changelog
## v1.2.2
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.2.1...module-react-v1.2.2)
### 🩹 Fixes
- Add `@vitejs/plugin-react` v6 support ([c61fa8f8](https://github.com/wxt-dev/wxt/commit/c61fa8f8))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v1.2.1
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.2.0...module-react-v1.2.1)
### 🏡 Chore
- Add prepack script to all packages ([032f7931](https://github.com/wxt-dev/wxt/commit/032f7931))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v1.2.0
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.5...module-react-v1.2.0)
### 🚀 Enhancements
- Add `vitePluginsBefore` option ([#2170](https://github.com/wxt-dev/wxt/pull/2170))
- Vite 8 support ([bfd4af5d](https://github.com/wxt-dev/wxt/commit/bfd4af5d))
### 🏡 Chore
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
- 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))
- **deps:** Upgrade deps ([#2175](https://github.com/wxt-dev/wxt/pull/2175))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Martin Broder <hello@martinbroder.com>
## v1.1.5
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.4...module-react-v1.1.5)
-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,
});
+9 -2
View File
@@ -1,15 +1,16 @@
import 'wxt';
import { addImportPreset, addViteConfig, defineWxtModule } from 'wxt/modules';
import react, { Options as PluginOptions } from '@vitejs/plugin-react';
import type { PluginOption } from 'vite';
export default defineWxtModule<ReactModuleOptions>({
name: '@wxt-dev/module-react',
configKey: 'react',
setup(wxt, options) {
const { vite } = options ?? {};
const { vite, vitePluginsBefore } = options ?? {};
addViteConfig(wxt, () => ({
plugins: [react(vite)],
plugins: [...(vitePluginsBefore ?? []), react(vite)],
}));
addImportPreset(wxt, 'react');
@@ -30,6 +31,12 @@ export default defineWxtModule<ReactModuleOptions>({
export interface ReactModuleOptions {
vite?: PluginOptions;
/**
* Vite plugins to add before the `react()` plugin. Some plugins like the
* `@tanstack/router-plugin` need to be added before `react()` to work
* correctly.
*/
vitePluginsBefore?: PluginOption[];
}
declare module 'wxt' {
+14 -13
View File
@@ -18,11 +18,11 @@
},
"license": "MIT",
"funding": "https://github.com/sponsors/wxt-dev",
"version": "1.1.5",
"version": "1.2.2",
"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,25 @@
"scripts": {
"dev": "wxt",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"prepare": "buildc --deps-only -- wxt prepare"
"build": "buildc -- tsdown",
"prepare": "buildc --deps-only -- wxt prepare",
"prepack": "pnpm build"
},
"peerDependencies": {
"vite": "^5.4.19 || ^6.3.4 || ^7.0.0 || ^8.0.0-0",
"wxt": ">=0.19.16"
},
"dependencies": {
"@vitejs/plugin-react": "^4.4.1 || ^5.0.0"
"@vitejs/plugin-react": "^4.4.1 || ^5.0.0 || ^6.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.14",
"@types/react-dom": "^19.2.3",
"publint": "^0.3.18",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"typescript": "^5.9.3",
"vite": "^7.0.0",
"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,
});
+8 -9
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,20 @@
"scripts": {
"dev": "wxt",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"prepare": "buildc --deps-only -- wxt prepare"
"build": "buildc -- tsdown",
"prepare": "buildc --deps-only -- wxt prepare",
"prepack": "pnpm build"
},
"peerDependencies": {
"wxt": ">=0.19.16"
},
"dependencies": {
"vite-plugin-solid": "^2.11.8"
"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.18",
"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',
},
]);
+6 -7
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,8 +39,9 @@
"dist"
],
"scripts": {
"build": "buildc -- unbuild",
"check": "pnpm build && check"
"build": "buildc -- tsdown",
"check": "pnpm build && check",
"prepack": "pnpm build"
},
"peerDependencies": {
"wxt": ">=0.18.6",
@@ -50,10 +51,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.18",
"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 -7
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,8 +39,9 @@
"dist"
],
"scripts": {
"build": "buildc -- unbuild",
"check": "pnpm build && check"
"build": "buildc -- tsdown",
"check": "pnpm build && check",
"prepack": "pnpm build"
},
"peerDependencies": {
"wxt": ">=0.19.16"
@@ -49,10 +50,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.18",
"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',
},
]);
+7 -11
View File
@@ -24,21 +24,17 @@
"check": "pnpm build && check",
"test": "buildc --deps-only -- vitest",
"dev": "tsx --trace-warnings dev.ts",
"build": "buildc -- unbuild",
"prepublishOnly": "pnpm build"
"build": "buildc -- tsdown",
"prepack": "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.51.0",
"publint": "^0.3.18",
"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",
+2 -1
View File
@@ -124,7 +124,8 @@ export const KNOWN_BROWSER_PATHS: Record<
};
/**
* When targeting a browser, this map contains the other targets to fall back on when a binary could not be found for the primary target.
* When targeting a browser, this map contains the other targets to fall back on
* when a binary could not be found for the primary target.
*/
export const FALLBACK_TARGETS: Partial<Record<KnownTarget, KnownTarget[]>> = {
chrome: [
+7 -3
View File
@@ -4,8 +4,11 @@ import { createCdpConnection } from './cdp';
/**
* Install an extension to an already running instance of Firefox.
* @param debuggerUrl The URL of the Firefox BiDi server (ex: `ws://127.0.0.1:45912`).
* @param extensionDir Absolute path to the directory containing the extension to be installed.
*
* @param debuggerUrl The URL of the Firefox BiDi server (ex:
* `ws://127.0.0.1:45912`).
* @param extensionDir Absolute path to the directory containing the extension
* to be installed.
*/
export async function installFirefox(
debuggerUrl: string,
@@ -33,7 +36,8 @@ export type BidiWebExtensionInstallResponse = {
};
/**
* Given a child process of Chrome, install an extension. The process must be started with the following flags:
* Given a child process of Chrome, install an extension. The process must be
* started with the following flags:
*
* - `--remote-debugging-pipe`
* - `--user-data-dir=...`
+38 -12
View File
@@ -17,21 +17,46 @@ export type Target = KnownTarget | UnknownTarget;
export type RunOptions = {
/** Paths to binaries to use for each target. */
browserBinaries?: Record<string, string>;
/** Customize the arguments passed to the chromium binary. Conflicting arguments with required ones to install extensions are ignored. */
/**
* Customize the arguments passed to the chromium binary. Conflicting
* arguments with required ones to install extensions are ignored.
*/
chromiumArgs?: string[];
/** Control how data is persisted between launches. Either save data at a user level, project level, or don't persist data at all. Defaults to `project`. */
/**
* Control how data is persisted between launches. Either save data at a user
* level, project level, or don't persist data at all. Defaults to `project`.
*/
dataPersistence?: 'user' | 'project' | 'none';
/** Customize where your profile's data is stored when using `dataPersistence: 'project'`. Can be absolute or relative to the current working directory. */
/**
* Customize where your profile's data is stored when using `dataPersistence:
* 'project'`. Can be absolute or relative to the current working directory.
*/
projectDataDir?: string;
/** Customize the port Chrome's debugger is listening on. Defaults to a random open port. */
/**
* Customize the port Chrome's debugger is listening on. Defaults to a random
* open port.
*/
chromiumRemoteDebuggingPort?: number;
/** Directory where the extension will be installed from. Should contain a `manifest.json` file. Can be relative to the current working directory. Defaults to the current working directory. */
/**
* Directory where the extension will be installed from. Should contain a
* `manifest.json` file. Can be relative to the current working directory.
* Defaults to the current working directory.
*/
extensionDir?: string;
/** Customize the arguments passed to the firefox binary. Conflicting arguments with required ones to install extensions are ignored. */
/**
* Customize the arguments passed to the firefox binary. Conflicting arguments
* with required ones to install extensions are ignored.
*/
firefoxArgs?: string[];
/** Customize the port Firefox's debugger is listening on. Defaults to a random open port. */
/**
* Customize the port Firefox's debugger is listening on. Defaults to a random
* open port.
*/
firefoxRemoteDebuggingPort?: number;
/** Specify the browser to open. Defaults to `"chrome"`, but you can pass any string. */
/**
* Specify the browser to open. Defaults to `"chrome"`, but you can pass any
* string.
*/
target?: Target;
};
@@ -114,7 +139,7 @@ async function findBrowserBinary(target: string): Promise<string | undefined> {
for (const target of targets) {
const potentialPaths = KNOWN_BROWSER_PATHS[target]?.[platform] ?? [];
for (const path of potentialPaths) {
if (await exists(path)) return path;
if (await pathExists(path)) return path;
}
}
}
@@ -207,7 +232,7 @@ function deduplicateArgs(
return args;
}
async function exists(path: string): Promise<boolean> {
async function pathExists(path: string): Promise<boolean> {
try {
await open(path, 'r');
return true;
@@ -219,8 +244,9 @@ async function exists(path: string): Promise<boolean> {
}
/**
* Copied from https://github.com/GoogleChrome/chrome-launcher/blob/main/src/flags.ts
* with some flags commented out. Run tests after updating to compare.
* Copied from
* https://github.com/GoogleChrome/chrome-launcher/blob/main/src/flags.ts with
* some flags commented out. Run tests after updating to compare.
*/
const CHROME_LAUNCHER_DEFAULT_FLAGS = [
'--disable-features=' +
+60
View File
@@ -1,5 +1,65 @@
# Changelog
## v1.2.8
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.7...storage-v1.2.8)
### 🩹 Fixes
- Correctly update version metadata when setting a value for the first time ([#2139](https://github.com/wxt-dev/wxt/pull/2139))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v1.2.7
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.6...storage-v1.2.7)
### 🩹 Fixes
- Add another `defineItem` signature when `init` function is passed ([#1909](https://github.com/wxt-dev/wxt/pull/1909))
- **storage:** Set version number on init ([#1996](https://github.com/wxt-dev/wxt/pull/1996))
### 💅 Refactors
- Code cleanup in analytics package ([#2084](https://github.com/wxt-dev/wxt/pull/2084))
### 📖 Documentation
- Rename keys name of getMetas() to be proper ([#2105](https://github.com/wxt-dev/wxt/pull/2105))
### 🏡 Chore
- Fix type errors after `chrome` type upgrades ([6036c6e8](https://github.com/wxt-dev/wxt/commit/6036c6e8))
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
- 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))
### ❤️ Contributors
- Willow ([@42willow](https://github.com/42willow))
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Dan McGee <dpmcgee@gmail.com>
## 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)
+10 -11
View File
@@ -1,7 +1,7 @@
{
"name": "@wxt-dev/storage",
"description": "Web extension storage API provided by WXT, supports all browsers.",
"version": "1.2.2",
"version": "1.2.8",
"type": "module",
"repository": {
"type": "git",
@@ -25,9 +25,10 @@
"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"
"test": "buildc --deps-only -- vitest",
"prepack": "pnpm build"
},
"dependencies": {
"@wxt-dev/browser": "workspace:^",
@@ -35,16 +36,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.51.0",
"publint": "^0.3.18",
"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",
+117 -4
View File
@@ -1,11 +1,11 @@
import { fakeBrowser } from '@webext-core/fake-browser';
import { describe, it, expect, beforeEach, vi, expectTypeOf } from 'vitest';
import { MigrationError, type WxtStorageItem, storage } from '../index';
import { browser } from '@wxt-dev/browser';
import { beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest';
import { MigrationError, type WxtStorageItem, storage } from '../index';
/**
* This works because fakeBrowser is synchronous, and is will finish any number of chained
* calls within a single tick of the event loop, ie: a timeout of 0.
* This works because fakeBrowser is synchronous, and is will finish any number
* of chained calls within a single tick of the event loop, ie: a timeout of 0.
*/
async function waitForMigrations() {
return new Promise((res) => setTimeout(res));
@@ -23,6 +23,9 @@ describe('Storage Utils', () => {
beforeEach(() => {
fakeBrowser.reset();
storage.unwatch();
// Setup a spy to check for excessive calls
fakeBrowser.storage.local.set = vi.spyOn(fakeBrowser.storage.local, 'set');
});
describe.each(['local', 'sync', 'managed', 'session'] as const)(
@@ -706,6 +709,47 @@ describe('Storage Utils', () => {
expect(migrateToV3).toBeCalledWith(4);
});
it('should set the version without running migrations for empty storage items', async () => {
const migrate = vi.fn((n: number) => n * 2);
const item = storage.defineItem<number>('local:key', {
init: () => 1,
version: 2,
migrations: {
2: migrate,
},
});
const value = await item.getValue();
const meta = await item.getMeta();
expect(value).toEqual(1);
expect(meta).toEqual({ v: 2 });
expect(migrate).toBeCalledTimes(0);
});
it('should set the version and run migrations if a storage value already exists and v2 is added', async () => {
const key = 'local:key';
// Mimic as if an initial value was set by a previous version. We expect
// this value to be migrated and doubled to V2.
await storage.setItem(key, 1);
const migrate = vi.fn((n: number) => n * 2);
const item = storage.defineItem<number>(key, {
init: () => 1,
version: 2,
migrations: {
2: migrate,
},
});
const value = await item.getValue();
const meta = await item.getMeta();
expect(value).toEqual(2);
expect(meta).toEqual({ v: 2 });
expect(migrate).toBeCalledTimes(1);
});
it('should call onMigrationComplete callback function if defined', async () => {
await fakeBrowser.storage.local.set({
count: 2,
@@ -908,6 +952,7 @@ describe('Storage Utils', () => {
{ migratedValue: expect.any(Number) },
);
});
it('should not print migration logs if debug option is undefined or false', async () => {
await fakeBrowser.storage.local.set({
count: 2,
@@ -939,6 +984,62 @@ describe('Storage Utils', () => {
await waitForMigrations();
expect(consoleSpy).toHaveBeenCalledTimes(0);
});
describe('calling setValue', () => {
const migrateToV2 = vi.fn((v1) => v1);
const defineTestItem = () =>
storage.defineItem<number>('local:count', {
version: 2,
migrations: {
2: migrateToV2,
},
});
it('should set the version metadata when setting the value for the first time', async () => {
const item = defineTestItem();
expect(await item.getValue()).toBeNull();
expect(await item.getMeta()).toEqual({});
expect(fakeBrowser.storage.local.set).toBeCalledTimes(0);
await item.setValue(1);
expect(await item.getValue()).toBe(1);
expect(await item.getMeta()).toEqual({ v: 2 });
// Called twice, once for setting the value, once for updating the metadata.
expect(fakeBrowser.storage.local.set).toBeCalledTimes(2);
expect(fakeBrowser.storage.local.set).toBeCalledWith({ count: 1 });
expect(fakeBrowser.storage.local.set).toBeCalledWith({
count$: { v: 2 },
});
await item.setValue(2);
expect(await item.getValue()).toBe(2);
expect(await item.getMeta()).toEqual({ v: 2 });
// Only called one more time, just for setting the value
expect(fakeBrowser.storage.local.set).toBeCalledTimes(3);
expect(fakeBrowser.storage.local.set).toBeCalledWith({ count: 2 });
// Migration function never called throughout the whole test
expect(migrateToV2).not.toBeCalled();
});
it('should not set the version metadata when a value is already in storage', async () => {
await fakeBrowser.storage.local.set({ count: 1, count$: { v: 2 } });
vi.mocked(fakeBrowser.storage.local.set).mockClear();
const item = defineTestItem();
await item.setValue(2);
expect(await item.getValue()).toEqual(2);
expect(await item.getMeta()).toEqual({ v: 2 });
expect(fakeBrowser.storage.local.set).toBeCalledTimes(1);
expect(fakeBrowser.storage.local.set).toBeCalledWith({ count: 2 });
expect(migrateToV2).not.toBeCalled();
});
});
});
describe('getValue', () => {
@@ -1315,6 +1416,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, {}>>();
});
});
});
+230 -126
View File
@@ -1,12 +1,14 @@
/**
* Simplified storage APIs with support for versioned fields, snapshots, metadata, and item definitions.
* Simplified storage APIs with support for versioned fields, snapshots,
* metadata, and item definitions.
*
* See [the guide](https://wxt.dev/storage.html) for more information.
*
* @module @wxt-dev/storage
*/
import { dequal } from 'dequal/lite';
import { Mutex } from 'async-mutex';
import { browser, type Browser } from '@wxt-dev/browser';
import { Mutex } from 'async-mutex';
import { dequal } from 'dequal/lite';
export const storage = createStorage();
@@ -17,6 +19,7 @@ function createStorage(): WxtStorage {
sync: createDriver('sync'),
managed: createDriver('managed'),
};
const getDriver = (area: StorageArea) => {
const driver = drivers[area];
if (driver == null) {
@@ -25,14 +28,17 @@ function createStorage(): WxtStorage {
}
return driver;
};
const resolveKey = (key: StorageItemKey) => {
const deliminatorIndex = key.indexOf(':');
const driverArea = key.substring(0, deliminatorIndex) as StorageArea;
const driverKey = key.substring(deliminatorIndex + 1);
if (driverKey == null)
if (driverKey == null) {
throw Error(
`Storage key should be in the form of "area:key", but received "${key}"`,
);
}
return {
driverArea,
@@ -40,17 +46,23 @@ function createStorage(): WxtStorage {
driver: getDriver(driverArea),
};
};
const getMetaKey = (key: string) => key + '$';
const mergeMeta = (oldMeta: any, newMeta: any): any => {
const newFields = { ...oldMeta };
Object.entries(newMeta).forEach(([key, value]) => {
if (value == null) delete newFields[key];
else newFields[key] = value;
});
return newFields;
};
const getValueOrFallback = (value: any, fallback: any) =>
value ?? fallback ?? null;
const getMetaValue = (properties: any) =>
typeof properties === 'object' && !Array.isArray(properties)
? properties
@@ -64,11 +76,13 @@ function createStorage(): WxtStorage {
const res = await driver.getItem<any>(driverKey);
return getValueOrFallback(res, opts?.fallback ?? opts?.defaultValue);
};
const getMeta = async (driver: WxtStorageDriver, driverKey: string) => {
const metaKey = getMetaKey(driverKey);
const res = await driver.getItem<any>(metaKey);
return getMetaValue(res);
};
const setItem = async (
driver: WxtStorageDriver,
driverKey: string,
@@ -76,6 +90,7 @@ function createStorage(): WxtStorage {
) => {
await driver.setItem(driverKey, value ?? null);
};
const setMeta = async (
driver: WxtStorageDriver,
driverKey: string,
@@ -85,23 +100,27 @@ function createStorage(): WxtStorage {
const existingFields = getMetaValue(await driver.getItem(metaKey));
await driver.setItem(metaKey, mergeMeta(existingFields, properties));
};
const removeItem = async (
driver: WxtStorageDriver,
driverKey: string,
opts: RemoveItemOptions | undefined,
) => {
await driver.removeItem(driverKey);
if (opts?.removeMeta) {
const metaKey = getMetaKey(driverKey);
await driver.removeItem(metaKey);
}
};
const removeMeta = async (
driver: WxtStorageDriver,
driverKey: string,
properties: string | string[] | undefined,
) => {
const metaKey = getMetaKey(driverKey);
if (properties == null) {
await driver.removeItem(metaKey);
} else {
@@ -110,19 +129,19 @@ function createStorage(): WxtStorage {
await driver.setItem(metaKey, newFields);
}
};
const watch = (
driver: WxtStorageDriver,
driverKey: string,
cb: WatchCallback<any>,
) => {
return driver.watch(driverKey, cb);
};
) => driver.watch(driverKey, cb);
const storage: WxtStorage = {
return {
getItem: async (key, opts) => {
const { driver, driverKey } = resolveKey(key);
return await getItem(driver, driverKey, opts);
},
getItems: async (keys) => {
const areaToKeyMap = new Map<StorageArea, string[]>();
const keyToOptsMap = new Map<string, GetItemOptions<any> | undefined>();
@@ -131,6 +150,7 @@ function createStorage(): WxtStorage {
keys.forEach((key) => {
let keyStr: StorageItemKey;
let opts: GetItemOptions<any> | undefined;
if (typeof key === 'string') {
// key: string
keyStr = key;
@@ -143,9 +163,11 @@ function createStorage(): WxtStorage {
keyStr = key.key;
opts = key.options;
}
orderedKeys.push(keyStr);
const { driverArea, driverKey } = resolveKey(keyStr);
const areaKeys = areaToKeyMap.get(driverArea) ?? [];
areaToKeyMap.set(driverArea, areaKeys.concat(driverKey));
keyToOptsMap.set(keyStr, opts);
});
@@ -154,6 +176,7 @@ function createStorage(): WxtStorage {
await Promise.all(
Array.from(areaToKeyMap.entries()).map(async ([driverArea, keys]) => {
const driverResults = await drivers[driverArea].getItems(keys);
driverResults.forEach((driverResult) => {
const key = `${driverArea}:${driverResult.key}` as StorageItemKey;
const opts = keyToOptsMap.get(key);
@@ -161,6 +184,7 @@ function createStorage(): WxtStorage {
driverResult.value,
opts?.fallback ?? opts?.defaultValue,
);
resultsMap.set(key, value);
});
}),
@@ -171,14 +195,17 @@ function createStorage(): WxtStorage {
value: resultsMap.get(key),
}));
},
getMeta: async (key) => {
const { driver, driverKey } = resolveKey(key);
return await getMeta(driver, driverKey);
},
getMetas: async (args) => {
const keys = args.map((arg) => {
const key = typeof arg === 'string' ? arg : arg.key;
const { driverArea, driverKey } = resolveKey(key);
return {
key,
driverArea,
@@ -186,6 +213,7 @@ function createStorage(): WxtStorage {
driverMetaKey: getMetaKey(driverKey),
};
});
const areaToDriverMetaKeysMap = keys.reduce<
Partial<Record<StorageArea, (typeof keys)[number][]>>
>((map, key) => {
@@ -211,10 +239,12 @@ function createStorage(): WxtStorage {
meta: resultsMap[key.key],
}));
},
setItem: async (key, value) => {
const { driver, driverKey } = resolveKey(key);
await setItem(driver, driverKey, value);
},
setItems: async (items) => {
const areaToKeyValueMap: Partial<
Record<StorageArea, Array<{ key: string; value: any }>>
@@ -229,6 +259,7 @@ function createStorage(): WxtStorage {
value: item.value,
});
});
await Promise.all(
Object.entries(areaToKeyValueMap).map(async ([driverArea, values]) => {
const driver = getDriver(driverArea as StorageArea);
@@ -236,10 +267,12 @@ function createStorage(): WxtStorage {
}),
);
},
setMeta: async (key, properties) => {
const { driver, driverKey } = resolveKey(key);
await setMeta(driver, driverKey, properties);
},
setMetas: async (items) => {
const areaToMetaUpdatesMap: Partial<
Record<StorageArea, { key: string; properties: any }[]>
@@ -278,16 +311,19 @@ function createStorage(): WxtStorage {
),
);
},
removeItem: async (key, opts) => {
const { driver, driverKey } = resolveKey(key);
await removeItem(driver, driverKey, opts);
},
removeItems: async (keys) => {
const areaToKeysMap: Partial<Record<StorageArea, string[]>> = {};
keys.forEach((key) => {
let keyStr: StorageItemKey;
let opts: RemoveItemOptions | undefined;
if (typeof key === 'string') {
// key: string
keyStr = key;
@@ -303,9 +339,11 @@ function createStorage(): WxtStorage {
keyStr = key.key;
opts = key.options;
}
const { driverArea, driverKey } = resolveKey(keyStr);
areaToKeysMap[driverArea] ??= [];
areaToKeysMap[driverArea].push(driverKey);
if (opts?.removeMeta) {
areaToKeysMap[driverArea].push(getMetaKey(driverKey));
}
@@ -318,36 +356,45 @@ function createStorage(): WxtStorage {
}),
);
},
clear: async (base) => {
const driver = getDriver(base);
await driver.clear();
},
removeMeta: async (key, properties) => {
const { driver, driverKey } = resolveKey(key);
await removeMeta(driver, driverKey, properties);
},
snapshot: async (base, opts) => {
const driver = getDriver(base);
const data = await driver.snapshot();
opts?.excludeKeys?.forEach((key) => {
delete data[key];
delete data[getMetaKey(key)];
});
return data;
},
restoreSnapshot: async (base, data) => {
const driver = getDriver(base);
await driver.restoreSnapshot(data);
},
watch: (key, cb) => {
const { driver, driverKey } = resolveKey(key);
return watch(driver, driverKey, cb);
},
unwatch() {
Object.values(drivers).forEach((driver) => {
driver.unwatch();
});
},
defineItem: (key, opts?: WxtStorageItemOptions<any>) => {
const { driver, driverKey } = resolveKey(key);
@@ -357,17 +404,25 @@ function createStorage(): WxtStorage {
onMigrationComplete,
debug = false,
} = opts ?? {};
if (targetVersion < 1) {
throw Error(
'Storage item version cannot be less than 1. Initial versions should be set to 1, not 0.',
);
}
const migrate = async () => {
let needsVersionSet = false;
const migrate: WxtStorageItem<any, any>['migrate'] = async () => {
const driverMetaKey = getMetaKey(driverKey);
const [{ value }, { value: meta }] = await driver.getItems([
driverKey,
driverMetaKey,
]);
// Used in setValue to also set the version when needed
needsVersionSet = value == null && meta?.v == null && !!targetVersion;
if (value == null) return;
const currentVersion = meta?.v ?? 1;
@@ -376,11 +431,12 @@ function createStorage(): WxtStorage {
`Version downgrade detected (v${currentVersion} -> v${targetVersion}) for "${key}"`,
);
}
if (currentVersion === targetVersion) {
return;
}
if (debug === true) {
if (debug) {
console.debug(
`[@wxt-dev/storage] Running storage migration for ${key}: v${currentVersion} -> v${targetVersion}`,
);
@@ -395,7 +451,7 @@ function createStorage(): WxtStorage {
migratedValue =
(await migrations?.[migrateToVersion]?.(migratedValue)) ??
migratedValue;
if (debug === true) {
if (debug) {
console.debug(
`[@wxt-dev/storage] Storage migration processed for version: v${migrateToVersion}`,
);
@@ -411,14 +467,16 @@ function createStorage(): WxtStorage {
{ key: driverMetaKey, value: { ...meta, v: targetVersion } },
]);
if (debug === true) {
if (debug) {
console.debug(
`[@wxt-dev/storage] Storage migration completed for ${key} v${targetVersion}`,
{ migratedValue },
);
}
onMigrationComplete?.(migratedValue, targetVersion);
};
const migrationsDone =
opts?.migrations == null
? Promise.resolve()
@@ -441,6 +499,9 @@ function createStorage(): WxtStorage {
const newValue = await opts.init();
await driver.setItem<any>(driverKey, newValue);
if (value == null && targetVersion > 1) {
await setMeta(driver, driverKey, { v: targetVersion });
}
return newValue;
});
@@ -449,62 +510,86 @@ function createStorage(): WxtStorage {
return {
key,
get defaultValue() {
return getFallback();
},
get fallback() {
return getFallback();
},
getValue: async () => {
await migrationsDone;
if (opts?.init) {
return await getOrInitValue();
} else {
return await getItem(driver, driverKey, opts);
}
},
getMeta: async () => {
await migrationsDone;
return await getMeta(driver, driverKey);
},
setValue: async (value) => {
await migrationsDone;
return await setItem(driver, driverKey, value);
if (needsVersionSet) {
needsVersionSet = false;
await Promise.all([
// Note: These calls cannot be done in a single `setItems` call;
// metadata needs to be merged together with existing data and
// setItems overwrites the whole value without merging.
setItem(driver, driverKey, value),
setMeta(driver, driverKey, { v: targetVersion }),
]);
} else {
await setItem(driver, driverKey, value);
}
},
setMeta: async (properties) => {
await migrationsDone;
return await setMeta(driver, driverKey, properties);
},
removeValue: async (opts) => {
await migrationsDone;
return await removeItem(driver, driverKey, opts);
},
removeMeta: async (properties) => {
await migrationsDone;
return await removeMeta(driver, driverKey, properties);
},
watch: (cb) =>
watch(driver, driverKey, (newValue, oldValue) =>
cb(newValue ?? getFallback(), oldValue ?? getFallback()),
),
migrate,
};
},
};
return storage;
}
function createDriver(storageArea: StorageArea): WxtStorageDriver {
const getStorageArea = () => {
if (browser.runtime == null) {
throw Error(
[
"'wxt/storage' must be loaded in a web extension environment",
'\n - If thrown during a build, see https://github.com/wxt-dev/wxt/issues/371',
" - If thrown during tests, mock 'wxt/browser' correctly. See https://wxt.dev/guide/go-further/testing.html\n",
].join('\n'),
);
throw Error(`'wxt/storage' must be loaded in a web extension environment
- If thrown during a build, see https://github.com/wxt-dev/wxt/issues/371
- If thrown during tests, mock 'wxt/browser' correctly. See https://wxt.dev/guide/go-further/testing.html
`);
}
if (browser.storage == null) {
throw Error(
"You must add the 'storage' permission to your manifest to use 'wxt/storage'",
@@ -514,18 +599,23 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
const area = browser.storage[storageArea];
if (area == null)
throw Error(`"browser.storage.${storageArea}" is undefined`);
return area;
};
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) => {
const result = await getStorageArea().get(keys);
return keys.map((key) => ({ key, value: result[key] ?? null }));
},
setItem: async (key, value) => {
if (value == null) {
await getStorageArea().remove(key);
@@ -533,6 +623,7 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
await getStorageArea().set({ [key]: value });
}
},
setItems: async (values) => {
const map = values.reduce<Record<string, unknown>>(
(map, { key, value }) => {
@@ -541,37 +632,51 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
},
{},
);
await getStorageArea().set(map);
},
removeItem: async (key) => {
await getStorageArea().remove(key);
},
removeItems: async (keys) => {
await getStorageArea().remove(keys);
},
clear: async () => {
await getStorageArea().clear();
},
snapshot: async () => {
return await getStorageArea().get();
},
restoreSnapshot: async (data) => {
await getStorageArea().set(data);
},
watch(key, cb) {
const listener = (changes: StorageAreaChanges) => {
const change = changes[key];
if (change == null) return;
if (dequal(change.newValue, change.oldValue)) return;
const change = changes[key] as {
newValue?: any;
oldValue?: any | null;
} | null;
if (change == null || dequal(change.newValue, change.oldValue)) return;
cb(change.newValue ?? null, change.oldValue ?? null);
};
getStorageArea().onChanged.addListener(listener);
watchListeners.add(listener);
return () => {
getStorageArea().onChanged.removeListener(listener);
watchListeners.delete(listener);
};
},
unwatch() {
watchListeners.forEach((listener) => {
getStorageArea().onChanged.removeListener(listener);
@@ -586,7 +691,7 @@ export interface WxtStorage {
* Get an item from storage, or return `null` if it doesn't exist.
*
* @example
* await storage.getItem<number>("local:installDate");
* await storage.getItem<number>('local:installDate');
*/
getItem<TValue>(
key: StorageItemKey,
@@ -599,11 +704,11 @@ export interface WxtStorage {
): Promise<TValue | null>;
/**
* Get multiple items from storage. The return order is guaranteed to be the same as the order
* requested.
* Get multiple items from storage. The return order is guaranteed to be the
* same as the order requested.
*
* @example
* await storage.getItems(["local:installDate", "session:someCounter"]);
* await storage.getItems(['local:installDate', 'session:someCounter']);
*/
getItems(
keys: Array<
@@ -612,39 +717,44 @@ export interface WxtStorage {
| { key: StorageItemKey; options?: GetItemOptions<any> }
>,
): Promise<Array<{ key: StorageItemKey; value: any }>>;
/**
* Return an object containing metadata about the key. Object is stored at `key + "$"`. If value
* is not an object, it returns an empty object.
* Return an object containing metadata about the key. Object is stored at
* `key + "$"`. If value is not an object, it returns an empty object.
*
* @example
* await storage.getMeta("local:installDate");
* await storage.getMeta('local:installDate');
*/
getMeta<T extends Record<string, unknown>>(key: StorageItemKey): Promise<T>;
/**
* Get the metadata of multiple storage items.
*
* @param items List of keys or items to get the metadata of.
* @param keys List of keys or items to get the metadata of.
* @returns An array containing storage keys and their metadata.
*/
getMetas(
keys: Array<StorageItemKey | WxtStorageItem<any, any>>,
): Promise<Array<{ key: StorageItemKey; meta: any }>>;
/**
* Set a value in storage. Setting a value to `null` or `undefined` is equivalent to calling
* `removeItem`.
* Set a value in storage. Setting a value to `null` or `undefined` is
* equivalent to calling `removeItem`.
*
* @example
* await storage.setItem<number>("local:installDate", Date.now());
* await storage.setItem<number>('local:installDate', Date.now());
*/
setItem<T>(key: StorageItemKey, value: T | null): Promise<void>;
/**
* Set multiple values in storage. If a value is set to `null` or `undefined`, the key is removed.
* Set multiple values in storage. If a value is set to `null` or `undefined`,
* the key is removed.
*
* @example
* await storage.setItem([
* await storage.setItem([
* { key: "local:installDate", value: Date.now() },
* { key: "session:someCounter, value: 5 },
* ]);
* ]);
*/
setItems(
values: Array<
@@ -652,21 +762,23 @@ export interface WxtStorage {
| { item: WxtStorageItem<any, any>; value: any }
>,
): Promise<void>;
/**
* Sets metadata properties. If some properties are already set, but are not included in the
* `properties` parameter, they will not be removed.
* Sets metadata properties. If some properties are already set, but are not
* included in the `properties` parameter, they will not be removed.
*
* @example
* await storage.setMeta("local:installDate", { appVersion });
* await storage.setMeta('local:installDate', { appVersion });
*/
setMeta<T extends Record<string, unknown>>(
key: StorageItemKey,
properties: T | null,
): Promise<void>;
/**
* Set the metadata of multiple storage items.
*
* @param items List of storage keys or items and metadata to set for each.
* @param metas List of storage keys or items and metadata to set for each.
*/
setMetas(
metas: Array<
@@ -674,16 +786,16 @@ export interface WxtStorage {
| { item: WxtStorageItem<any, any>; meta: Record<string, any> }
>,
): Promise<void>;
/**
* Removes an item from storage.
*
* @example
* await storage.removeItem("local:installDate");
* await storage.removeItem('local:installDate');
*/
removeItem(key: StorageItemKey, opts?: RemoveItemOptions): Promise<void>;
/**
* Remove a list of keys from storage.
*/
/** Remove a list of keys from storage. */
removeItems(
keys: Array<
| StorageItemKey
@@ -693,44 +805,41 @@ export interface WxtStorage {
>,
): Promise<void>;
/**
* Removes all items from the provided storage area.
*/
/** Removes all items from the provided storage area. */
clear(base: StorageArea): Promise<void>;
/**
* Remove the entire metadata for a key, or specific properties by name.
*
* @example
* // Remove all metadata properties from the item
* await storage.removeMeta("local:installDate");
* // Remove all metadata properties from the item
* await storage.removeMeta('local:installDate');
*
* // Remove only specific the "v" field
* await storage.removeMeta("local:installDate", "v")
* // Remove only specific the "v" field
* await storage.removeMeta('local:installDate', 'v');
*/
removeMeta(
key: StorageItemKey,
properties?: string | string[],
): Promise<void>;
/**
* Return all the items in storage.
*/
/** Return all the items in storage. */
snapshot(
base: StorageArea,
opts?: SnapshotOptions,
): Promise<Record<string, unknown>>;
/**
* Restores the results of `snapshot`. If new properties have been saved since the snapshot, they are
* not overridden. Only values existing in the snapshot are overridden.
* Restores the results of `snapshot`. If new properties have been saved since
* the snapshot, they are not overridden. Only values existing in the snapshot
* are overridden.
*/
restoreSnapshot(base: StorageArea, data: any): Promise<void>;
/**
* Watch for changes to a specific key in storage.
*/
/** Watch for changes to a specific key in storage. */
watch<T>(key: StorageItemKey, cb: WatchCallback<T | null>): Unwatch;
/**
* Remove all watch listeners.
*/
/** Remove all watch listeners. */
unwatch(): void;
/**
@@ -749,6 +858,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>,
@@ -773,51 +888,42 @@ export interface WxtStorageItem<
TValue,
TMetadata extends Record<string, unknown>,
> {
/**
* The storage key passed when creating the storage item.
*/
/** The storage key passed when creating the storage item. */
key: StorageItemKey;
/**
* @deprecated Renamed to fallback, use it instead.
*/
/** @deprecated Renamed to fallback, use it instead. */
defaultValue: TValue;
/**
* The value provided by the `fallback` option.
*/
/** The value provided by the `fallback` option. */
fallback: TValue;
/**
* Get the latest value from storage.
*/
/** Get the latest value from storage. */
getValue(): Promise<TValue>;
/**
* Get metadata.
*/
/** Get metadata. */
getMeta(): Promise<NullablePartial<TMetadata>>;
/**
* Set the value in storage.
*/
/** Set the value in storage. */
setValue(value: TValue): Promise<void>;
/**
* Set metadata properties.
*/
/** Set metadata properties. */
setMeta(properties: NullablePartial<TMetadata>): Promise<void>;
/**
* Remove the value from storage.
*/
/** Remove the value from storage. */
removeValue(opts?: RemoveItemOptions): Promise<void>;
/**
* Remove all metadata or certain properties from metadata.
*/
/** Remove all metadata or certain properties from metadata. */
removeMeta(properties?: string[]): Promise<void>;
/**
* Listen for changes to the value in storage.
*/
/** Listen for changes to the value in storage. */
watch(cb: WatchCallback<TValue>): Unwatch;
/**
* If there are migrations defined on the storage item, migrate to the latest version.
* If there are migrations defined on the storage item, migrate to the latest
* version.
*
* **This function is ran automatically whenever the extension updates**, so you don't have to call it
* manually.
* **This function is ran automatically whenever the extension updates**, so
* you don't have to call it manually.
*/
migrate(): Promise<void>;
}
@@ -826,13 +932,10 @@ export type StorageArea = 'local' | 'session' | 'sync' | 'managed';
export type StorageItemKey = `${StorageArea}:${string}`;
export interface GetItemOptions<T> {
/**
* @deprecated Renamed to `fallback`, use it instead.
*/
/** @deprecated Renamed to `fallback`, use it instead. */
defaultValue?: T;
/**
* Default value returned when `getItem` would otherwise return `null`.
*/
/** Default value returned when `getItem` would otherwise return `null`. */
fallback?: T;
}
@@ -847,21 +950,19 @@ export interface RemoveItemOptions {
export interface SnapshotOptions {
/**
* Exclude a list of keys. The storage area prefix should be removed since the snapshot is for a
* specific storage area already.
* Exclude a list of keys. The storage area prefix should be removed since the
* snapshot is for a specific storage area already.
*/
excludeKeys?: string[];
}
export interface WxtStorageItemOptions<T> {
/**
* @deprecated Renamed to `fallback`, use it instead.
*/
/** @deprecated Renamed to `fallback`, use it instead. */
defaultValue?: T;
/**
* Default value returned when `getValue` would otherwise return `null`.
*/
/** Default value returned when `getValue` would otherwise return `null`. */
fallback?: T;
/**
* If passed, a value in storage will be initialized immediately after
* defining the storage item. This function returns the value that will be
@@ -869,23 +970,28 @@ export interface WxtStorageItemOptions<T> {
* already exist.
*/
init?: () => T | Promise<T>;
/**
* Provide a version number for the storage item to enable migrations. When changing the version
* in the future, migration functions will be ran on application startup.
* Provide a version number for the storage item to enable migrations. When
* changing the version in the future, migration functions will be ran on
* application startup.
*/
version?: number;
/**
* A map of version numbers to the functions used to migrate the data to that version.
* A map of version numbers to the functions used to migrate the data to that
* version.
*/
migrations?: Record<number, (oldValue: any) => any>;
/**
* Print debug logs, such as migration process.
*
* @default false
*/
debug?: boolean;
/**
* A callback function that runs on migration complete.
*/
/** A callback function that runs on migration complete. */
onMigrationComplete?: (migratedValue: T, targetVersion: number) => void;
}
@@ -894,19 +1000,17 @@ export type StorageAreaChanges = {
};
/**
* Same as `Partial`, but includes `| null`. It makes all the properties of an object optional and
* nullable.
* Same as `Partial`, but includes `| null`. It makes all the properties of an
* object optional and nullable.
*/
type NullablePartial<T> = {
[key in keyof T]+?: T[key] | undefined | null;
};
/**
* Callback called when a value in storage is changed.
*/
/** Callback called when a value in storage is changed. */
export type WatchCallback<T> = (newValue: T, oldValue: T) => void;
/**
* Call to remove a watch listener
*/
/** Call to remove a watch listener */
export type Unwatch = () => void;
export class MigrationError extends Error {
+8 -9
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,24 +32,23 @@
"dist"
],
"scripts": {
"build": "buildc -- unbuild",
"check": "buildc --deps-only -- check"
"build": "buildc -- tsdown",
"check": "buildc --deps-only -- check",
"prepack": "pnpm build"
},
"peerDependencies": {
"unocss": ">=0.60.0",
"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.51.0",
"publint": "^0.3.18",
"typescript": "^5.9.3",
"unocss": "^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0",
"wxt": "workspace:*"
},
"dependencies": {
"defu": "^6.1.4",
"fast-glob": "^3.3.3"
"tinyglobby": "^0.2.15"
}
}
+10 -6
View File
@@ -38,12 +38,11 @@ export default defineWxtModule<UnoCSSOptions>({
},
});
/**
* Options for the UnoCSS module
*/
/** Options for the UnoCSS module */
export interface UnoCSSOptions<Theme extends object = object> {
/**
* Enable UnoCSS
*
* @default true
*/
enabled?: boolean;
@@ -51,12 +50,17 @@ export interface UnoCSSOptions<Theme extends object = object> {
* List of entrypoint names that UnoCSS is not used in. By default, the UnoCSS
* vite plugin is added to all build steps, but this option is used to exclude
* it from specific builds.
* @example ["popup", "options"]
* @default []
*
* @example
* {undefined} ('popup',
* 'options');
*
* @default [ ]
*/
excludeEntrypoints?: string[];
/**
* The path to your `unocss.config.ts` file, relative to <rootDir>, or inline configuration.
* The path to your `unocss.config.ts` file, relative to <rootDir>, or inline
* configuration.
*/
configOrPath?: Parameters<typeof UnoCSS<Theme>>[0];
}
@@ -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,
});
+7 -8
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,19 +37,18 @@
"scripts": {
"dev": "wxt",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"prepare": "buildc --deps-only -- wxt prepare"
"build": "buildc -- tsdown",
"prepare": "buildc --deps-only -- wxt prepare",
"prepack": "pnpm build"
},
"peerDependencies": {
"webextension-polyfill": "*",
"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.5",
"publint": "^0.3.18",
"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.14",
"@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": {
@@ -12,7 +12,7 @@ export default defineBackground({
manifestVersion: import.meta.env.MANIFEST_VERSION,
});
console.log(useAppConfig());
console.log(getAppConfig());
browser.runtime.getURL('/');
browser.runtime.getURL('/background.js');
@@ -43,3 +43,14 @@ export default defineBackground({
storage.setItem('session:startTime', Date.now());
},
});
function _otherTypeChecksNotEvaluated() {
browser.scripting.executeScript({
target: { tabId: 1 },
files: [
'/background.js',
// @ts-expect-error: Should error for non-existing paths
'/other.js',
],
});
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { defineConfig } from 'wxt';
import { presetUno } from 'unocss';
import { presetWind3 } from 'unocss';
export default defineConfig({
srcDir: 'src',
@@ -53,7 +53,7 @@ export default defineConfig({
],
},
},
presets: [presetUno()],
presets: [presetWind3()],
},
},
});
+280
View File
@@ -1,5 +1,285 @@
# Changelog
## v0.20.20
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.19...wxt-v0.20.20)
### 🩹 Fixes
- Unlisted script return values broken with Vite 8 sourcemaps ([#2197](https://github.com/wxt-dev/wxt/pull/2197))
### 📖 Documentation
- Add Dymo extension ID to UsingWxtSection.vue ([#2187](https://github.com/wxt-dev/wxt/pull/2187))
- Add Extension Rank Checker to the list of extensions ([#2193](https://github.com/wxt-dev/wxt/pull/2193))
### 🏡 Chore
- **deps:** Add vite-node 6 support ([64b68671](https://github.com/wxt-dev/wxt/commit/64b68671))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Joseph Hu ([@KiJO94GO](https://github.com/KiJO94GO))
- FJRG2007 ツ ([@FJRG2007](https://github.com/FJRG2007))
- Hampus Tågerud ([@hampustagerud](https://github.com/hampustagerud))
## v0.20.19
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.18...wxt-v0.20.19)
### 🚀 Enhancements
- Vite 8 support ([bfd4af5d](https://github.com/wxt-dev/wxt/commit/bfd4af5d))
### 🔥 Performance
- Use filter to improve plugin performance with rolldown ([#1787](https://github.com/wxt-dev/wxt/pull/1787))
### 🩹 Fixes
- List eslint 10 as supported ([55dc263d](https://github.com/wxt-dev/wxt/commit/55dc263d))
### 📖 Documentation
- Added "Scrape Similar" to the homepage ([#2158](https://github.com/wxt-dev/wxt/pull/2158))
- Added "isTrust" to the homepage ([#2161](https://github.com/wxt-dev/wxt/pull/2161))
### 🏡 Chore
- Add `prettier-plugin-jsdoc` to project ([#2171](https://github.com/wxt-dev/wxt/pull/2171))
- Replace fast-glob and ora with lighter alternatives ([#2173](https://github.com/wxt-dev/wxt/pull/2173))
- **deps:** Upgrade deps ([#2175](https://github.com/wxt-dev/wxt/pull/2175))
- Replace `fs-extra` with `node:fs/promises` ([#2174](https://github.com/wxt-dev/wxt/pull/2174))
### ❤️ Contributors
- Florian Metz ([@Timeraa](https://github.com/Timeraa))
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
- Etoome ([@etoome](https://github.com/etoome))
- Kuba ([@zizzfizzix](https://github.com/zizzfizzix))
## v0.20.18
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.17...wxt-v0.20.18)
### 🚀 Enhancements
- Create tests for all func of `network.ts` ([#2132](https://github.com/wxt-dev/wxt/pull/2132))
- Use Navigation API for location change detection with polling fallback ([#2136](https://github.com/wxt-dev/wxt/pull/2136))
- **modules:** Add support for augumenting entrypoint options ([#2149](https://github.com/wxt-dev/wxt/pull/2149))
- New `@wxt-dev/is-background` package ([#2152](https://github.com/wxt-dev/wxt/pull/2152))
- Add `globalName` entrypoint option. ([#2017](https://github.com/wxt-dev/wxt/pull/2017))
### 🩹 Fixes
- Add `getAppConfig` as an alias to `useAppConfig` ([#2144](https://github.com/wxt-dev/wxt/pull/2144))
- Enable mv3 dev mode with firefox 147 ([#2135](https://github.com/wxt-dev/wxt/pull/2135))
- **types:** Add type safety to `browser.runtime.executeScript` `files` option ([#2142](https://github.com/wxt-dev/wxt/pull/2142))
- **types:** Include CSS entrypoints in PublicPath generation ([#2150](https://github.com/wxt-dev/wxt/pull/2150))
- Fix submit command ([#2157](https://github.com/wxt-dev/wxt/pull/2157))
### 📖 Documentation
- Added "Glossy New Tab" to the homepage ([#2133](https://github.com/wxt-dev/wxt/pull/2133))
- Add All API Hub to showcase extensions ([#2137](https://github.com/wxt-dev/wxt/pull/2137))
- Add `wxt-local-analytics` to community resources ([#2141](https://github.com/wxt-dev/wxt/pull/2141))
- Add deep outline to modules page to show recipes ([653608c9](https://github.com/wxt-dev/wxt/commit/653608c9))
### 🏡 Chore
- Add missing `async` to network.ts ([#2107](https://github.com/wxt-dev/wxt/pull/2107))
- Make type for `__ENTRYPOINT__` of vitest.globalSetup.ts ([#2128](https://github.com/wxt-dev/wxt/pull/2128))
- Remove unnecessary `@ts-ignore` of `builders/vite` index.ts ([#2127](https://github.com/wxt-dev/wxt/pull/2127))
- Rename `zipdir` to `zipDir` ([#2126](https://github.com/wxt-dev/wxt/pull/2126))
- Simplify imports in `wxt/e2e` ([#2122](https://github.com/wxt-dev/wxt/pull/2122))
- Remove `@ts-expect-error` from manifest.test.ts and fix typo ([#2123](https://github.com/wxt-dev/wxt/pull/2123))
### 🤖 CI
- Fix VHS action ([#2155](https://github.com/wxt-dev/wxt/pull/2155))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Jonathan Viney ([@jviney](https://github.com/jviney))
- Tam Dang ([@dahomita](https://github.com/dahomita))
- Nick Doan ([@nickbar01234](https://github.com/nickbar01234))
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
- Anthony Ciccarello ([@aciccarello](https://github.com/aciccarello))
- Henrik Wenz ([@HaNdTriX](https://github.com/HaNdTriX))
- Sheng Zhang ([@Arktomson](https://github.com/Arktomson))
- Oleksii ([@Aler1x](https://github.com/Aler1x))
- Qixing-jk <vq3d5d8c@duck.com>
- Muzammil ([@oyzamil](https://github.com/oyzamil))
## v0.20.17
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.16...wxt-v0.20.17)
### 🩹 Fixes
- Update left-over CJS require of `publish-browser-extension` to ESM ([#2120](https://github.com/wxt-dev/wxt/pull/2120))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.20.16
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.15...wxt-v0.20.16)
### 🩹 Fixes
- Revert "rename `name` to `names` prop of `rollup` to fix deprecated warning " ([#2106](https://github.com/wxt-dev/wxt/pull/2106))
### 🏡 Chore
- Speed up E2E tests by skipping `pnpm install` when possible ([#2113](https://github.com/wxt-dev/wxt/pull/2113))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.20.15
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.14...wxt-v0.20.15)
### 🚀 Enhancements
- Export `normalizePath` from `wxt` module ([#2080](https://github.com/wxt-dev/wxt/pull/2080))
- **popup:** Add Firefox `default_area` and `theme_icons` support ([#2097](https://github.com/wxt-dev/wxt/pull/2097))
- Add log `--level` cli option ([#1973](https://github.com/wxt-dev/wxt/pull/1973))
### 🩹 Fixes
- Content script is incorrectly invalidated when injected multiple times ([#2035](https://github.com/wxt-dev/wxt/pull/2035))
- Use `uiContainer` to apply position when creating shadow root ui ([#2036](https://github.com/wxt-dev/wxt/pull/2036))
- Make `ExtensionRunner#closeBrowser` optional ([#2092](https://github.com/wxt-dev/wxt/pull/2092))
- Rename `name` to `names` prop of `rollup` to fix deprecated warning ([#2106](https://github.com/wxt-dev/wxt/pull/2106))
### 💅 Refactors
- Standardize file existence checks to `pathExists` ([#2083](https://github.com/wxt-dev/wxt/pull/2083))
- Cleanup esm ([#1950](https://github.com/wxt-dev/wxt/pull/1950))
- `async`/`await` cleanup ([#2088](https://github.com/wxt-dev/wxt/pull/2088))
- Cleanup unnecssary checks ([#2089](https://github.com/wxt-dev/wxt/pull/2089))
- Simplify import paths ([#2093](https://github.com/wxt-dev/wxt/pull/2093))
### 📖 Documentation
- Fix JSDoc ([6ae5fb5e](https://github.com/wxt-dev/wxt/commit/6ae5fb5e))
### 🏡 Chore
- Cleanup comment line lengths ([#2090](https://github.com/wxt-dev/wxt/pull/2090))
- Cleanup `@ts-*` comments ([#2094](https://github.com/wxt-dev/wxt/pull/2094))
- Update JSDoc to use `@internal` ([#2091](https://github.com/wxt-dev/wxt/pull/2091))
- Fix typos ([#2086](https://github.com/wxt-dev/wxt/pull/2086))
- Remove unused code ([#2087](https://github.com/wxt-dev/wxt/pull/2087))
- Simplify imports of `cli/__tests__/index.ts` ([#2104](https://github.com/wxt-dev/wxt/pull/2104))
### ❤️ Contributors
- Nick Doan ([@nickbar01234](https://github.com/nickbar01234))
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Wotan-allfather <wotan-ai@proton.me>
- Namu ([@namuorg](https://github.com/namuorg))
- Eli ([@lishaduck](https://github.com/lishaduck))
## 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 ([@ariasuni](https://github.com/ariasuni))
- 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)
@@ -1,7 +0,0 @@
#!/usr/bin/env node
/**
* A alias around `publish-extension` that is always installed on the path without having to install
* `publish-browser-extension` as a direct dependency (like for PNPM, which doesn't link
* sub-dependency binaries to "node_modules/.bin")
*/
require('publish-browser-extension/cli');
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env node
/**
* A alias around `publish-extension` that is always installed on the path
* without having to install `publish-browser-extension` as a direct dependency
* (like for PNPM, which doesn't link sub-dependency binaries to
* "node_modules/.bin")
*/
await import('publish-browser-extension/cli');
-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');
}
@@ -47,6 +47,7 @@ exports[`Auto Imports > eslintrc > "enabled: 8" should output a JSON config file
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"getAppConfig": true,
"injectScript": true,
"storage": true,
"useAppConfig": true
@@ -101,6 +102,7 @@ const globals = {
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"getAppConfig": true,
"injectScript": true,
"storage": true,
"useAppConfig": true
@@ -164,6 +166,7 @@ exports[`Auto Imports > eslintrc > "enabled: true" should output a JSON config f
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"getAppConfig": true,
"injectScript": true,
"storage": true,
"useAppConfig": true
@@ -219,6 +222,7 @@ exports[`Auto Imports > eslintrc > should allow customizing the output 1`] = `
"defineUnlistedScript": "readonly",
"defineWxtPlugin": "readonly",
"fakeBrowser": "readonly",
"getAppConfig": "readonly",
"injectScript": "readonly",
"storage": "readonly",
"useAppConfig": "readonly"
+9 -9
View File
@@ -30,8 +30,8 @@ describe('Analysis', () => {
},
});
expect(await project.fileExists('stats.html')).toBe(true);
expect(await project.fileExists('.output/chrome-mv3/stats-0.json')).toBe(
expect(await project.pathExists('stats.html')).toBe(true);
expect(await project.pathExists('.output/chrome-mv3/stats-0.json')).toBe(
false,
);
});
@@ -52,9 +52,9 @@ describe('Analysis', () => {
},
});
expect(await project.fileExists('stats.html')).toBe(true);
expect(await project.fileExists('stats-0.json')).toBe(true);
expect(await project.fileExists('stats-1.json')).toBe(true);
expect(await project.pathExists('stats.html')).toBe(true);
expect(await project.pathExists('stats-0.json')).toBe(true);
expect(await project.pathExists('stats-1.json')).toBe(true);
});
it('should support customizing the stats output directory', async () => {
@@ -73,7 +73,7 @@ describe('Analysis', () => {
},
});
expect(await project.fileExists('stats/bundle.html')).toBe(true);
expect(await project.pathExists('stats/bundle.html')).toBe(true);
});
it('should place artifacts next to the custom output file', async () => {
@@ -93,9 +93,9 @@ describe('Analysis', () => {
},
});
expect(await project.fileExists('stats/bundle.html')).toBe(true);
expect(await project.fileExists('stats/bundle-0.json')).toBe(true);
expect(await project.fileExists('stats/bundle-1.json')).toBe(true);
expect(await project.pathExists('stats/bundle.html')).toBe(true);
expect(await project.pathExists('stats/bundle-0.json')).toBe(true);
expect(await project.pathExists('stats/bundle-1.json')).toBe(true);
});
it('should open the stats in the browser when requested', async () => {
+22 -35
View File
@@ -17,22 +17,23 @@ 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 getAppConfig: typeof import('wxt/utils/app-config').getAppConfig
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 {
@@ -111,7 +112,7 @@ describe('Auto Imports', () => {
declare module '#imports' {
export { browser, Browser } from 'wxt/browser';
export { storage, StorageArea, WxtStorage, WxtStorageItem, StorageItemKey, StorageAreaChanges, MigrationError } from 'wxt/utils/storage';
export { useAppConfig } from 'wxt/utils/app-config';
export { getAppConfig, useAppConfig } from 'wxt/utils/app-config';
export { ContentScriptContext, WxtWindowEventMap } from 'wxt/utils/content-script-context';
export { createIframeUi, IframeContentScriptUi, IframeContentScriptUiOptions } from 'wxt/utils/content-script-ui/iframe';
export { createIntegratedUi, IntegratedContentScriptUi, IntegratedContentScriptUiOptions } from 'wxt/utils/content-script-ui/integrated';
@@ -142,7 +143,7 @@ describe('Auto Imports', () => {
await project.prepare();
expect(await project.fileExists('.wxt/types/imports.d.ts')).toBe(false);
expect(await project.pathExists('.wxt/types/imports.d.ts')).toBe(false);
});
it('should only include imports-module.d.ts in the the project', async () => {
@@ -196,7 +197,7 @@ describe('Auto Imports', () => {
declare module '#imports' {
export { browser, Browser } from 'wxt/browser';
export { storage, StorageArea, WxtStorage, WxtStorageItem, StorageItemKey, StorageAreaChanges, MigrationError } from 'wxt/utils/storage';
export { useAppConfig } from 'wxt/utils/app-config';
export { getAppConfig, useAppConfig } from 'wxt/utils/app-config';
export { ContentScriptContext, WxtWindowEventMap } from 'wxt/utils/content-script-context';
export { createIframeUi, IframeContentScriptUi, IframeContentScriptUiOptions } from 'wxt/utils/content-script-ui/iframe';
export { createIntegratedUi, IntegratedContentScriptUi, IntegratedContentScriptUiOptions } from 'wxt/utils/content-script-ui/integrated';
@@ -280,24 +281,10 @@ describe('Auto Imports', () => {
},
});
expect(await project.fileExists('.wxt/eslint-auto-imports.mjs')).toBe(
expect(await project.pathExists('.wxt/eslint-auto-imports.mjs')).toBe(
false,
);
expect(await project.fileExists('.wxt/eslintrc-auto-import.json')).toBe(
false,
);
});
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(
expect(await project.pathExists('.wxt/eslintrc-auto-import.json')).toBe(
false,
);
});
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { TestProject } from '../utils';
import { WxtHooks } from '../../src/types';
import type { WxtHooks } from '../../src';
const hooks: WxtHooks = {
ready: vi.fn(),

Some files were not shown because too many files have changed in this diff Show More