Compare commits

...

227 Commits

Author SHA1 Message Date
github-actions[bot] 9757e2634f chore(release): wxt v0.20.24
📼 VHS / Create VHS (push) Waiting to run
2026-04-17 14:32:45 +00:00
John Wong 42b55f66fb fix: Correct actionKey for Firefox in MV3 (#2274) 2026-04-17 14:12:37 +00:00
aklinker1 e64058bc94 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-04-17 00:50:56 +00:00
github-actions[bot] 386503cce0 chore(release): wxt v0.20.23
📼 VHS / Create VHS (push) Waiting to run
2026-04-16 18:00:00 +00:00
Aaron 6224ff49f6 fix: Use correct color name
Addresses
https://github.com/wxt-dev/wxt/issues/2180#issuecomment-4259430737
2026-04-16 12:55:55 -05:00
aklinker1 6578be00ce fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-04-15 00:51:40 +00:00
Aaron 2f325d898f docs: Re-structure the WXT Modules documentation (#2260) 2026-04-14 12:28:45 -05:00
github-actions[bot] b039c599c8 chore(release): wxt v0.20.22
📼 VHS / Create VHS (push) Waiting to run
2026-04-14 14:38:40 +00:00
Aaron bec4b73839 fix: Apply expanded env to process.env (#2267) 2026-04-14 09:33:36 -05:00
aklinker1 e67bac0868 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-04-14 00:51:06 +00:00
Aaron 24ff92068d ci: Update lockfile with new version during release
Without this, the lockfile becomes out-of-date and CI fails after a
release.
2026-04-13 18:56:52 -05:00
Aaron 6e20036d6c fix: Skip applying esbuild.charset=ascii vite 8 (#2264) 2026-04-13 18:54:14 -05:00
offish b6059cfe4a docs: Add TF2 Trader to the list of extensions (#2213) 2026-04-13 11:31:42 -05:00
Aaron 4a305a2726 feat: Add dev.server.strictPort option (#2261) 2026-04-13 11:31:09 -05:00
Patryk Kuniczak 4ae6d8135f fix: Scripts injection for MV2 (#2232) 2026-04-13 13:47:24 +00:00
Suvesh Moza 225a94199c feat: add support for Firefox data collection permissions (#1976)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
Co-authored-by: Nick Doan <nickbar01234@gmail.com>
Co-authored-by: Tam Dang <139360620+dahomita@users.noreply.github.com>
2026-04-13 13:34:46 +00:00
GuaGua 35ffa00bae docs: fix Read Frog Firefox showcase link (#2256)
Co-authored-by: frogGuaGuaGuaGua <268840081+frogGuaGuaGuaGua@users.noreply.github.com>
2026-04-13 12:28:53 +00:00
github-actions[bot] 58e11d3c06 chore(release): wxt v0.20.21
📼 VHS / Create VHS (push) Waiting to run
2026-04-13 06:03:28 +00:00
Aaron fddd7c14ee ci: Fix bump-package-version script 2026-04-13 00:59:01 -05:00
Aaron 7d755f215f ci: Add back postinstall scripts 2026-04-13 00:45:54 -05:00
Aaron 7e473375e5 ci: Add warning to debug release script 2026-04-13 00:42:46 -05:00
Aaron 08b9ea6e47 ci: Skip postisntall scripts on windows runner in validate workflow
They're failing 50% of the time for some reason... This is just a test
change.
2026-04-13 00:42:46 -05:00
Aaron 245aea4ce8 ci: Enable bun package manager tests on windows (#2253) 2026-04-13 00:27:13 -05:00
Aaron e1e41c20ea ci: Fix version bump script 2026-04-13 00:24:13 -05:00
Aaron e16052cdc9 docs: Move homepage extensions to separate YAML file (#2255) 2026-04-13 00:12:22 -05:00
Kamer DINC de2b38217a docs: Add AlarmBot extension to the list of extensions (#2251)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-04-13 04:42:59 +00:00
Sullivan b2a099689f docs: Added "SoundCloud Enhanced Pro" to the homepage (#2238)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-04-13 04:39:21 +00:00
Skylar Bolton 42c8a02ef9 docs: Add 5 Whiteboard Works extensions to the homepage (#2207)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-04-13 04:33:50 +00:00
Skylar Bolton 32058027f3 docs: Add Firefox Add-on support to homepage showcase (#2208)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-04-13 04:24:29 +00:00
Aaron 99fc685e14 ci: Don't post thank you message on closed PRs for maintainers and bots (#2250) 2026-04-12 23:16:51 -05:00
Aaron 414117b9e6 chore: Fix flakey test (#2254) 2026-04-12 23:11:19 -05:00
Aaron 799bd92b28 docs: Fix non-replaced {{version}} in CLI reference 2026-04-12 22:50:42 -05:00
Aaron d7cc8dc229 docs: Fix deploy command after bun upgrade 2026-04-12 22:45:54 -05:00
Aaron f4fa36a0ab chore: Use bun run --sequential to simplify package scripts (#2252) 2026-04-12 22:35:20 -05:00
Aaron 4d5eb28cb4 chore: Migrate monorepo to use Bun instead of PNPM (#2009)
Co-authored-by: Okinea Dev <hi@okinea.dev>
2026-04-12 22:17:20 -05:00
Vaughn Bosu a008426317 fix: add Firefox action manifest typings (#2217) 2026-04-12 20:31:16 +00:00
Aaron 2a212790b6 refactor: Rename internal popup mv2Key option to actionType (#2249) 2026-04-12 11:15:43 -05:00
dependabot[bot] 09f27a902a chore(deps): bump actions/setup-go from 6.0.0 to 6.4.0 (#2219)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-12 10:50:17 -05:00
dependabot[bot] 6cbbca9e92 chore(deps): bump hookable from 6.0.1 to 6.1.0 (#2222)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-12 15:50:01 +00:00
dependabot[bot] f1fd8fc38b chore(deps): bump oven-sh/setup-bun from 2.1.2 to 2.2.0 (#2220)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-12 10:49:59 -05:00
dependabot[bot] a67e1b81e1 chore(deps): bump docker/login-action from 3.6.0 to 4.0.0 (#2221)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-12 10:49:38 -05:00
dependabot[bot] 936c5f7fda chore(deps-dev): bump vitepress-plugin-llms from 1.11.0 to 1.12.0 (#2225)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-12 15:49:29 +00:00
dependabot[bot] ab3151f924 chore(deps): bump codecov/codecov-action from 5.5.2 to 6.0.0 (#2223)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-12 10:48:54 -05:00
dependabot[bot] d4fb318381 chore(deps): bump filesize from 11.0.13 to 11.0.15 (#2226)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-12 15:44:49 +00:00
dependabot[bot] 31a7b5892e chore(deps-dev): bump vitepress-plugin-group-icons from 1.7.1 to 1.7.3 (#2227)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-12 10:42:29 -05:00
dependabot[bot] c469f0e85a chore(deps-dev): bump eslint from 10.0.2 to 10.1.0 (#2224)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-12 10:42:03 -05:00
SmAsHeD 355d36a838 fix: Support page_action for Firefox MV3 (#2200)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-04-12 10:37:57 -05:00
Patryk Kuniczak 43e46477df chore: Add dev scripts for Firefox in wxt-demo package.json (#2234) 2026-04-12 10:32:12 -05:00
Patryk Kuniczak 34a235fc68 chore: Create content.ts for wxt-demo to test injection (#2233) 2026-04-12 10:12:48 -05:00
eupthere ce9f9af980 fix: Support template literals in prepare:publicPaths (#2248)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-12 13:22:16 +00:00
Ofer Itzhaki 5f799ca23e feat: auto-discover Firefox theme_icons from public assets (#2242)
Co-authored-by: oferitz <oferitz@Ofers-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-04-11 20:37:50 +00:00
Aaron 0159b4315e feat: Add Brave browser to KnownTarget type (#2247) 2026-04-11 15:13:51 -05:00
eupthere 864d7a0bfd chore: replace picocolors with builtins (#2210)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-04-11 19:59:37 +00:00
Ofer Itzhaki fa698266b3 feat: type /_favicon/ paths when favicon permission is declared (#2241)
Co-authored-by: oferitz <oferitz@Ofers-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 17:33:55 +00:00
Patryk Kuniczak ba96bbd8ff docs: fix undocumented constants link from 0.14.0 -> 0.15.0 migration guide (#2244) 2026-04-11 10:03:50 -05:00
Skylar Bolton 20d5e3310a fix: resolve TS build errors in manifest.ts options_ui (#2237)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-04-11 09:27:44 +02:00
Aaron 70fdb04db5 docs: Add Artemis II photo to hero section
Because it's cool!
2026-04-10 17:12:00 -05:00
aklinker1 65c3c7eba9 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-04-09 00:39:21 +00:00
aklinker1 98e218042d fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-04-04 00:42:13 +00:00
alexharding-ux 8a56f73c7c fix: resolve url: imports in Vite 8 (#2218) 2026-04-01 16:47:43 +00:00
Aaron ffdd15e275 chore: Sort cspell ignore list 2026-03-28 11:49:41 -05:00
Aaron 8f71e174cb chore: Use deps.neverBundle instead of external
tsdown has deprecated `external` in favor of `deps.neverBundle`.
2026-03-28 11:49:11 -05:00
Aaron 6621aaf877 chore: Add cspell and fix all typos 2026-03-28 11:45:47 -05:00
Aaron c528361d07 fix: Output content scripts in ascii to avoid utf8 encoding errors (#2204)
Co-authored-by: Jorge Fuentes <jsfuentes@users.noreply.github.com>
2026-03-19 16:18:09 -05:00
Aaron 5376cfd6a9 docs: Simplify storage docs 2026-03-19 11:26:20 -05:00
Aaron 76615b9262 chore: Remove useless engines field from monorepo package.json 2026-03-19 09:31:27 -05:00
Aaron ce551fbdb0 fix: Add engines field to WXT's package.json
Includes the currently supported versions of NodeJS and Bun.

This closes #2198
2026-03-19 09:27:59 -05:00
Aaron 108e790cf8 ci: Fix commit step in VHS workflow 2026-03-19 09:16:19 -05:00
github-actions[bot] 0b5424cadb chore(release): @wxt-dev/module-svelte v2.0.5
📼 VHS / Create VHS (push) Waiting to run
2026-03-19 14:06:34 +00:00
Aaron 48571f5032 fix: Update @sveltejs/vite-plugin-svelte version range to support Vite
8
2026-03-19 09:01:44 -05:00
Aaron c6c9f65040 chore: Remove any from i18n's internal types (#2201)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-03-19 09:22:15 +01:00
Harryson ace6c20164 feat: replace dotenv with native node:util parseEnv (#2181)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-03-18 15:04:45 +00:00
Aditi d64b64f39e chore: replace minimatch with picomatch (#2188)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
Co-authored-by: Rizky Mirzaviandy Priambodo <Xavrir@users.noreply.github.com>
2026-03-18 14:22:38 +00:00
Aaron 41a687a63e docs: Add note about adding the browser package as a dependency for
module augmentation
2026-03-17 17:40:48 -05:00
Aaron f4f6704510 docs: Document augmenting the browser type for Firefox (#2199) 2026-03-17 08:44:33 -05:00
github-actions[bot] 65fc0fadb3 chore(release): wxt v0.20.20
📼 VHS / Create VHS (push) Waiting to run
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) Waiting to run
2026-03-14 23:48:01 +00:00
github-actions[bot] db5c22809c chore(release): wxt v0.20.19
📼 VHS / Create VHS (push) Waiting to run
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) Waiting to run
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) Waiting to run
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) Waiting to run
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) Waiting to run
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) Waiting to run
2026-02-22 17:29:53 -06:00
github-actions[bot] e20c012eb3 chore(release): @wxt-dev/storage v1.2.8
📼 VHS / Create VHS (push) Waiting to run
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) Waiting to run
2026-02-22 17:29:53 -06:00
github-actions[bot] 9491e6845e chore(release): @wxt-dev/i18n v0.2.5
📼 VHS / Create VHS (push) Waiting to run
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) Waiting to run
2026-02-13 00:01:40 +00:00
github-actions[bot] 7366d8aab7 chore(release): wxt v0.20.17
📼 VHS / Create VHS (push) Waiting to run
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) Waiting to run
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) Waiting to run
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) Waiting to run
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) Waiting to run
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
284 changed files with 11360 additions and 12744 deletions
+1
View File
@@ -7,6 +7,7 @@
pnpm-lock.yaml linguist-generated
package-lock.json linguist-generated
bun.lockb linguist-generated
bun.lock linguist-generated
yarn.lock linguist-generated
# Exclude templates from language statistics
+1 -1
View File
@@ -1,5 +1,5 @@
# Set default
* @aklinker1 @Timeraa
* @aklinker1
# Secure Directories
/.github/ @aklinker1
+6 -14
View File
@@ -1,29 +1,21 @@
name: Basic Setup
description: Install PNPM, Node, and dependencies
description: Install Bun and dependencies
inputs:
install:
default: 'true'
description: Whether or not to run 'pnpm install'
installArgs:
default: ''
description: Additional args to append to "pnpm install"
description: Whether or not to run 'bun install'
runs:
using: composite
steps:
- name: 🛠️ Setup PNPM
uses: pnpm/action-setup@f2b2b233b538f500472c7274c7012f57857d8ce0 # v4.1.0
- name: 🛠️ Setup NodeJS
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
- name: 🛠️ Setup Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
with:
node-version: 20
cache: pnpm
bun-version-file: '.tool-versions'
- name: 📦 Install Dependencies
if: ${{ inputs.install == 'true' }}
shell: bash
run: pnpm install ${{ inputs.installArgs }}
run: bun ci ${{ inputs.installArgs }}
+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');
}
@@ -10,7 +10,7 @@ jobs:
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
+8 -3
View File
@@ -17,13 +17,18 @@ 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
- name: Build All Packages
run: pnpm buildc all
run: bun run buildc all
- name: Publish
run: pnpx pkg-pr-new publish --compact --pnpm './packages/*'
run: bun pkg-pr-new publish --compact --bun './packages/*'
- name: DEBUG
if: always()
run: ls -lhA
working-directory: packages/analytics
+13
View File
@@ -15,7 +15,20 @@ jobs:
if: github.event.pull_request.merged == true
steps:
- name: Log PR Author Info
run: |
echo "PR Author Login: ${{ github.event.pull_request.user.login }}"
echo "PR Author ID: ${{ github.event.pull_request.user.id }}"
echo "PR Author Type: ${{ github.event.pull_request.user.type }}"
echo "PR Author Node ID: ${{ github.event.pull_request.user.node_id }}"
- name: Post Thank You Comment
if: >-
github.event.pull_request.user.login != 'aklinker1' &&
github.event.pull_request.user.login != 'Timeraa' &&
github.event.pull_request.user.login != 'PatrykKuniczak' &&
github.event.pull_request.user.login != 'dependabot[bot]' &&
github.event.pull_request.user.login != 'copilot-swe-agent[bot]'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
comment: Thanks for helping make WXT better!
+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
+3 -3
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@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
@@ -37,7 +37,7 @@ jobs:
- name: Build docs
run: |
pnpm docs:build
bun run docs:build
docker build docs/.vitepress -t ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
- name: Push Image
+6 -5
View File
@@ -10,6 +10,7 @@ on:
- analytics
- auto-icons
- i18n
- is-background
- module-react
- module-solid
- module-svelte
@@ -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
@@ -55,18 +56,18 @@ jobs:
- name: Bump and Tag
run: |
pnpm tsx scripts/bump-package-version.ts ${{ inputs.package }}
bun run scripts/bump-package-version.ts ${{ inputs.package }}
git push
git push --tags
- name: Publish to NPM
working-directory: packages/${{ inputs.package }}
run: |
pnpm pack
bun pm pack
sudo npm i -g npm@latest
/usr/local/bin/npm publish *.tgz
npm publish *.tgz
- name: Create GitHub release
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
run: bun run scripts/create-github-release.ts ${{ inputs.package }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+3 -2
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
@@ -38,6 +39,6 @@ jobs:
installArgs: --ignore-scripts
- name: Sync Releases
run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
run: bun run scripts/sync-releases.ts ${{ inputs.package }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+6 -6
View File
@@ -17,7 +17,7 @@ jobs:
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 }}
@@ -28,15 +28,15 @@ jobs:
- name: Generate Latest Code
working-directory: packages/browser
run: pnpm gen
run: bun run gen
- name: Run Checks
working-directory: packages/browser
run: pnpm check
run: bun run check
- 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:
@@ -46,6 +46,6 @@ jobs:
if: steps.commit.outputs.changes_detected == 'true'
working-directory: packages/browser
run: |
pnpm pack
bun pm pack
sudo npm i -g npm@latest
/usr/local/bin/npm publish *.tgz
npm publish *.tgz
+20 -18
View File
@@ -15,43 +15,43 @@ 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
- name: Basic Checks
run: pnpm check
run: bun run check
builds:
name: Builds
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
- name: Build All Packages
run: pnpm buildc all
run: bun run buildc all
build-demo:
name: Build Demo
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
- name: Build
run: pnpm build:all
run: bun run build:all
working-directory: packages/wxt-demo
- name: ZIP
run: pnpm wxt zip
run: bun run wxt zip
working-directory: packages/wxt-demo
tests:
@@ -68,25 +68,27 @@ 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
- name: Setup PNPM
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
version: latest
- name: Run Tests
if: ${{ ! matrix.coverage }}
run: pnpm test
run: bun run test
- name: Run Tests (Coverage)
if: matrix.coverage
run: pnpm test:coverage --reporter=default --reporter=hanging-process
run: bun run test:coverage --reporter=default --reporter=hanging-process
- name: Upload Coverage
if: matrix.coverage
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
@@ -104,13 +106,13 @@ 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
- name: Pack WXT package
run: pnpm pack
run: bun pm pack
working-directory: packages/wxt
- name: Install Dependencies
@@ -122,15 +124,15 @@ jobs:
working-directory: templates/${{ matrix.template }}
- name: Type Check Template
run: pnpm compile
run: bun run compile
if: matrix.template != 'svelte'
working-directory: templates/${{ matrix.template }}
- name: Type Check Template
run: pnpm check
run: bun run check
if: matrix.template == 'svelte'
working-directory: templates/${{ matrix.template }}
- name: Build Template
run: pnpm build
run: bun run build
working-directory: templates/${{ matrix.template }}
+23 -10
View File
@@ -11,18 +11,27 @@ 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
- name: Setup
uses: ./.github/actions/setup
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
install: false
ref: ${{ github.ref_name }}
- name: Setup PNPM
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
- name: Setup NodeJS
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
cache: pnpm
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c
with:
go-version: '1.25.1'
# This prevents pnpm dlx from downloading WXT in the video
- name: Pre-install WXT
@@ -31,15 +40,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 -1
View File
@@ -1,12 +1,14 @@
.DS_Store
.env
.env.*
!packages/wxt/src/core/utils/__tests__/fixtures/.env
.idea
.output
.webextrc
.wxt
.wxt-runner
*.log
*.tgz
/docs/.vitepress/cache
docs/.vitepress/.temp
coverage
@@ -20,6 +22,5 @@ templates/*/yarn.lock
templates/*/package-lock.json
docs/api/reference
stats.html
.tool-versions
.cache
*-stats.txt
+2
View File
@@ -1,2 +1,4 @@
singleQuote: true
endOfLine: lf
plugins:
- prettier-plugin-jsdoc
+2
View File
@@ -0,0 +1,2 @@
bun 1.3.12
nodejs 24.14.1
+2 -1
View File
@@ -10,5 +10,6 @@
// Additional guidelines for Copilot
"github.copilot.chat.codeGeneration.instructions": [
{ "file": "CONTRIBUTING.md" }
]
],
"oxc.enable": true
}
+81 -33
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 available, 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,18 +33,27 @@ 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.
WXT uses Bun for package management and development. Install it from: <https://bun.com/>
Then install dependencies:
```sh
corepack enable
```
Then, simply run the install command:
```sh
pnpm i
bun install
```
## Development
@@ -39,48 +61,68 @@ pnpm i
Here are some helpful commands:
```sh
# Build WXT package
cd packages/wxt
pnpm build
# Build WXT package and workspace dependencies
bun run --filter wxt build
```
```sh
# Build WXT package, then build demo extension
cd packages/wxt-demo
pnpm build
```
```sh
# Build WXT package, then start the demo extension in dev mode
cd packages/wxt-demo
pnpm dev
# Build workspace dependencies, then start the demo extension in dev mode
bun run --filter wxt-demo dev
```
```sh
# Run unit and E2E tests
pnpm test
bun run test
```
```sh
# Start the docs website locally
pnpm docs:dev
bun run docs:dev
```
> Above, we used bun's `--filter` flag to choose which package to run a command in, but there are other ways:
>
> ```sh
> bun run -F @wxt-dev/i18n build
> # or
> bun run --cwd packages/i18n build
> # or
> cd packages/i18n
> bun run build
> ```
>
> Pick your poison!
## Profiling
```sh
# Build the latest version
pnpm --filter wxt build
bun run --filter wxt build
# CD to the demo directory
cd packages/wxt-demo
# 1. Generate a flamechart with 0x
pnpm dlx 0x node_modules/wxt/bin/wxt.mjs build
# 2. Inspect the process with chrome @ chrome://inspect
pnpm node --inspect node_modules/wxt/bin/wxt.mjs build
```
Then there are a few different ways to profile WXT commands:
- Generate a flamechart with 0x:
```sh
bunx 0x node_modules/wxt/bin/wxt.mjs build
```
- Create a CPU profile:
```sh
bun run --cpu-prof node_modules/wxt/bin/wxt.mjs build
```
- Debug the process:
```sh
bun run --inspect node_modules/wxt/bin/wxt.mjs build
```
## Updating Docs
Documentation is written with VitePress, and is located in the `docs/` directory.
@@ -96,7 +138,7 @@ WXT has unit and E2E tests. When making a change or adding a feature, make sure
To run tests for a specific file, add the filename at the end of the test command:
```sh
pnpm test manifest-contents
bun run --filter wxt test manifest-contents
```
All test (unit and E2E) for all packages are ran together via [Vitest workspaces](https://vitest.dev/guide/#workspaces-support).
@@ -105,7 +147,7 @@ If you want to manually test a change, you can modify the demo project for your
## Templates
Each directory inside `templates/` is it's own standalone project. Simply `cd` into the directory you're updating, install dependencies with `npm` (NOT `pnpm`), and run the relevant commands
Each directory inside `templates/` is it's own standalone project. Simply `cd` into the directory you're updating, install dependencies with `npm` (NOT `bun`), and run the relevant commands
```sh
cd templates/vue
@@ -121,7 +163,7 @@ Note that templates are hardcoded to a specific version of `wxt` from NPM, they
"typescript": "^5.3.2",
"vite-plugin-solid": "^2.7.0",
- "wxt": "^0.8.0"
+ "wxt": "../.."
+ "wxt": "../../packages/wxt"
}
```
@@ -149,13 +191,13 @@ Releases are done with GitHub actions:
WXT has custom rules around what dependencies can be upgraded. Use the `scripts/upgrade-deps.ts` script to upgrade dependencies and follow these rules.
```sh
pnpm tsx scripts/upgrade-deps.ts
bun run scripts/upgrade-deps.ts
```
To see all the options, run:
```sh
pnpm tsx scripts/upgrade-deps.ts --help
bun run scripts/upgrade-deps.ts --help
```
## Install Unreleased Versions
@@ -188,3 +230,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 questions, 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>
bun 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.
Generated
+3033
View File
File diff suppressed because it is too large Load Diff
+118
View File
@@ -0,0 +1,118 @@
version: '0.2'
ignorePaths:
- pnpm-lock.yaml
- '**/node_modules/**'
- '**/dist/**'
- '**/coverage/**'
- docs/api/**
- '**/CHANGELOG.md'
- docs/public/_redirects
- packages/browser/src/gen/**
- patches/**
- '**/*.snap'
- docs/assets/extension-showcase.yml
words:
- Aabid
- aabidk
- aklinker
- automount
- avenir
- bidi
- buildc
- bunx
- cachable
- charmbracelet
- chromedriver
- Cira
- comctx
- confbox
- configfile
- consolas
- Cpath
- crbug
- crunchyroll
- cssinjs
- CSUI
- Csvg
- defu
- deinit
- dequal
- dtrace
- duckduckgo
- esbuild
- esque
- extglob
- favicons
- fieldtrials
- firefoxdeveloperedition
- flamechart
- Florian
- focusring
- frontmatter
- giget
- grayscale
- greyscale
- gtag
- hckhakegfgenefhikdcfkaaonnclljmf
- hookable
- iconify
- jsons
- Kenobi
- keystyle
- klinker
- linkedom
- llms
- locationchange
- magicast
- Menlo
- menupanel
- metas
- mktemp
- mycompany
- nacl
- nanospinner
- natsu
- newtab
- njsproj
- nodebuffer
- Noto
- ntvs
- nypm
- ohash
- oklab
- oxlint
- pbbf
- personaltoolbar
- portaling
- prebundled
- prefs
- proxified
- pWXLO
- repost
- rxliuli
- sandboxed
- scule
- Segoe
- sidepanel
- sidepanels
- solidjs
- styl
- subdependency
- tabstrip
- teleporting
- treemap
- triaging
- umami
- ungh
- unimport
- unocss
- unrun
- unstub
- unwatch
- USSS
- vitepress
- webext
- webextension
- webextrc
- xcrun
- xlink
+182 -174
View File
@@ -1,163 +1,120 @@
<script lang="ts" setup>
import { computed } from 'vue';
import useListExtensionDetails, {
ChromeExtension,
} from '../composables/useListExtensionDetails';
import useListExtensionDetails from '../composables/useListExtensionDetails';
import _extensionEntries from '../../assets/extension-showcase.yml';
// Add extension IDs to end of the list. On the website, extensions will be sorted by a combination of weekly active users and rating.
// Change the commit message or PR title to: "docs: Added "[extension name]" to the homepage"
const chromeExtensionIds = [
'ocfdgncpifmegplaglcnglhioflaimkd', // GitHub: Better Line Counts
'mgmdkjcljneegjfajchedjpdhbadklcf', // Anime Skip Player
'bfbnagnphiehemkdgmmficmjfddgfhpl', // UltraWideo
'elfaihghhjjoknimpccccmkioofjjfkf', // StayFree - Website Blocker & Web Analytics
'okifoaikfmpfcamplcfjkpdnhfodpkil', // Doozy: Ai Made Easy
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
'oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
'ceicccfeikoipigeghddpocceifjelph', // Plex Skipper
'aelkipgppclpfimeamgmlonimflbhlgf', // GitHub Custom Notifier
'djnlaiohfaaifbibleebjggkghlmcpcj', // Fluent Read
'nhclljcpfmmaiojbhhnkpjcfmacfcian', // Facebook Video Controls
'mblkhbaakhbhiimkbcnmeciblfhmafna', // ElemSnap - Quick capture of webpage elements and conversion to images,
'oajalfneblkfiejoadecnmodfpnaeblh', // MS Edge TTS (Text to Speech)
'nedcanggplmbbgmlpcjiafgjcpdimpea', // YTBlock - Block any content from YouTube™
'oadbjpccljkplmhnjekgjamejnbadlne', // demo.fun - Interactive product demos that convert
'iopdafdcollfgaoffingmahpffckmjni', // SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
'khjdmjcmpolknpccmaaipmidphjokhdf', // WorkFlowy MultiFlow
'fencadnndhdeggodopebjgdfdlhcimfk', // 香草布丁🌿🍮- https://github.com/Xdy1579883916/vanilla-pudding
'bnacincmbaknlbegecpioobkfgejlojp', // MaxFocus: Link Preview
'bcpgdpedphodjcjlminjbdeejccjbimp', // 汇率转换-中文版本
'loeilaonggnalkaiiaepbegccilkmjjp', // Currency Converter Plus
'npcnninnjghigjfiecefheeibomjpkak', // Respond Easy
'cfkdcideecefncbglkhneoflfnmhoicc', // mindful - stay focused on your goals
'lnhejcpclabmbgpiiomjbhalblnnbffg', // 1Proompt
'fonflmjnjbkigocpoommgmhljdpljain', // NiceTab - https://github.com/web-dahuyou/NiceTab
'fcffekbnfcfdemeekijbbmgmkognnmkd', // Draftly for LinkedIn
'nkndldfehcidpejfkokbeghpnlbppdmo', // YouTube Summarized - Summarize any YouTube video
'dbichmdlbjdeplpkhcejgkakobjbjalc', // 社媒助手 - https://github.com/iszhouhua/social-media-copilot
'opepfpjeogkbgeigkbepobceinnfmjdd', // Dofollow Links for SEO
'pdnenlnelpdomajfejgapbdpmjkfpjkp', // ChatGPT Writer: Use AI on Any Site (GPT-4o, Claude, Gemini, and More)
'jobnhifpphkgoelnhnopgkdhbdkiadmj', // discord message translator
'ncokhechhpjgjonhjnlaneglmdkfkcbj', // Habit Tracker app widget for daily habit tracking
'lnjaiaapbakfhlbjenjkhffcdpoompki', // Catppuccin for GitHub File Explorer Icons
'cpaedhbidlpnbdfegakhiamfpndhjpgf', // WebChat: Chat with anyone on any website
'fcphghnknhkimeagdglkljinmpbagone', // YouTube Auto HD + FPS
'lpomjgbicdemjkgmbnkjncgdebogkhlb', // MultiViewer Companion
'ggiafipgeeaaahnjamgpjcgkdpanhddg', // Sync Watch - Watch videos together on any site
'nmldnjcblcihmegipecakhmnieiofmgl', // Keyword Rank Checker
'gppllamhaciichleihemgilcpledblpn', // YouTube Simple View - Hide distractions & more
'pccbghdfdnnkkbcdcibchpbffdgednkf', // Propbar - Property Data Enhancer
'lfknakglefggmdkjdfhhofkjnnolffkh', // Text Search Pro - Search by case and whole-word match!
'mbenhbocjckkbaojacmaepiameldglij', // Invoice Generator
'phlfhkmdofajnbhgmbmjkbkdgppgoppb', // Monthly Bill Tracker
'macmkmchfoclhpbncclinhjflmdkaoom', // Wandpen - Instantly improve your writing with AI
'lhmgechokhmdekdpgkkemoeecelcaonm', // YouTube Hider - Remove Comments By Keywords, Usernames & Tools
'imgheieooppmahcgniieddodaliodeeg', // QA Compass - Record standardized bug reports easily
'npgghjedpchajflknnbngajkjkdhncdo', // aesthetic Notion, styled
'hmdcmlfkchdmnmnmheododdhjedfccka', // Eye Dropper
'eihpmapodnppeemkhkbhikmggfojdkjd', // Cursorful - Screen Recorder with Auto Zoom
'hjjkgbibknbahijglkffklflidncplkn', // Show IP Live View of Website IPs for Developers
'ilbikcehnpkmldojkcmlldkoelofnbde', // Strong Password Generator
'ocllfkhcdopiafndigclebelbecaiocp', // ZenGram: Mindful Instagram, Your Way
'odffpjnpocjfcaclnenaaaddghkgijdb', // Blync: Preview Links, Selection Search, AI Assistant
'kofbbilhmnkcmibjbioafflgmpkbnmme', // HTML to Markdown - Convert webpages to markdown
'boecmgggeigllcdocgioijmleimjbfkg', // Walmart WFS Profit Calculator
'dlnjcbkmomenmieechnmgglgcljhoepd', // Youtube Live Chat Fullscreen
'keiealdacakpnbbljlmhfgcebmaadieg', // Python Code Runner
'hafcajcllbjnoolpfngclfmmgpikdhlm', // Monochromate
'bmoggiinmnodjphdjnmpcnlleamkfedj', // AliasVault - Open-Source Password & (Email) Alias Manager
'hlnhhamckimoaiekbglafiebkfimhapb', // SnapThePrice: AI-Powered Real-time Lowest Price Finder
'gdjampjdgjmbifnhldgcnccdjkcoicmg', // radiofrance - news & broadcasts (French), music (international)
'jlnhphlghikichhgbnkepenehbmloenb', // Blens - Time Tracker and AI Insight
'njnammmpdodmfkodnfpammnpdcbhnlcm', // Always Light Mode - Setting website always in light mode
'lblmfclcfniabobmamfkdogcgdagbhhb', // DesignPicker - Color Picker & Font Detector
'pamnlaoeobcmhkliljfaofekeddpmfoh', // Web to PDF
'jmbcbeepjfenihlocplnbmbhimcoooka', // Online CSV Viewer
'nkjcoophmpcmmgadnljnlpbpfdfacgbo', // YouTube Video Transcript
'lcaieahkjgeggeiihblhcjbbjlppgieh', // NetSuite Record Scripts
'gmocfknjllodfiomnljmaehcplnekhlo', // VueTracker
'ggcfemmoabhhelfkhknhbnkmeahloiod', // CanCopy - A web extension that allow you to copy any content from website
'modkelfkcfjpgbfmnbnllalkiogfofhb', // Language Learning with AI
'npfopljnjbamegincfjelhjhnonnjloo', // Bilibili Feed History Helper
'edkhpdceeinkcacjdgebjehipmnbomce', // NZBDonkey - The ultimate NZB file download tool
'cckggnbnimdbbpmdinkkgbbncopbloob', // WeChat Markdown Editor(微信 Markdown 编辑器)
'jcblcjolcojmfopefcighfmkkefbaofg', // Tab Grab
'eehmoikadcijkapfjocnhjclpbaindlb', // BrowserLens - https://browserlens.com/
'hfhellofkjebbchcdffmicekjdomkcmc', // Epic Games Library Extension
'gknigcbhlammoakmmdddkblknanpjiac', // Zen Analytics Pixel Tracker - zapt.web.app
'cnklededohhcbmjjdlbjdkkihkgoggol', // Crypto Pulse - Compose your newtab with nature images, widgets & realtime Crypto Price & Bitcoin RSS.
'miponnamafdenpgjemkknimgjfibicdc', // Youtube Video Scheduler
'nhmbcmalgpkjbomhlhgdicanmkkaajmg', // Chatslator: Livestream Chat Translator
'mbamjfdjbcdgpopfnkkmlohadbbnplhm', // 公众号阅读增强器 - https://wxreader.honwhy.wang
'hannhecbnjnnbbafffmogdlnajpcomek', // 토탐정
'ehboaofjncodknjkngdggmpdinhdoijp', // 2FAS Pass - https://2fas.com/
'hnjamiaoicaepbkhdoknhhcedjdocpkd', // Quick Prompt - https://github.com/wenyuanw/quick-prompt
'kacblhilkacgfnkjfodalohcnllcgmjd', // Add QR Code Generator Icon Back To Address Bar
'fkbdlogfdjmpfepbbbjcgcfbgbcfcnne', // Piwik PRO Tracking Helper
'nkbikckldmljjiiajklecmgmajgapbfl', // PIPX - Take Control of Picture-in-Picture, Automatically
'hgppdobcpkfkmiegekaglonjajeojmdd', // Browsely - AI-powered browser extension
'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
interface StoreLink {
label: string;
url: string;
}
interface ListedExtension {
id: string;
name: string;
iconUrl: string;
shortDescription: string;
users: number;
rating: number | undefined;
stores: StoreLink[];
}
const extensionEntries = _extensionEntries as Array<{
chromeId?: string;
firefoxSlug?: string;
}>;
const chromeIds = extensionEntries.flatMap((e) =>
e.chromeId ? [e.chromeId] : [],
);
const firefoxSlugs = [
...new Set(
extensionEntries.flatMap((e) => (e.firefoxSlug ? [e.firefoxSlug] : [])),
),
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
const sortedExtensions = computed(() => {
if (!data.value?.length) return [];
const { data, isLoading } = useListExtensionDetails(chromeIds, firefoxSlugs);
return [...data.value]
.filter((item) => item != null)
.map((item) => ({
...item,
// Sort based on the user count weighted by the rating
sortKey: ((item.rating ?? 5) / 5) * item.weeklyActiveUsers,
}))
.filter((item) => !!item)
.sort((l, r) => r.sortKey - l.sortKey);
});
function getStoreUrl(extension: ChromeExtension) {
const url = new URL(extension.storeUrl);
function addUtmSource(storeUrl: string): string {
const url = new URL(storeUrl);
url.searchParams.set('utm_source', 'wxt.dev');
return url.href;
}
const sortedExtensions = computed((): ListedExtension[] => {
if (!data.value) return [];
const chromeById = new Map(
(data.value.chrome ?? []).filter(Boolean).map((e) => [e.id, e]),
);
const firefoxBySlug = new Map(
(data.value.firefox ?? []).filter(Boolean).map((e) => [e.id, e]),
);
const results: ListedExtension[] = [];
for (const entry of extensionEntries) {
const chrome = entry.chromeId ? chromeById.get(entry.chromeId) : undefined;
const firefox = entry.firefoxSlug
? firefoxBySlug.get(entry.firefoxSlug)
: undefined;
if (!chrome && !firefox) continue;
const primary = chrome ?? firefox!;
const users = (chrome?.users ?? 0) + (firefox?.users ?? 0);
const rating = chrome?.rating ?? firefox?.rating;
const stores: StoreLink[] = [];
if (chrome) {
stores.push({ label: 'Chrome', url: addUtmSource(chrome.storeUrl) });
}
if (firefox) {
stores.push({ label: 'Firefox', url: addUtmSource(firefox.storeUrl) });
}
results.push({
id: entry.chromeId ?? `firefox:${entry.firefoxSlug}`,
name: primary.name,
iconUrl: primary.iconUrl,
shortDescription: primary.shortDescription,
users,
rating,
stores,
});
}
const score = (e: ListedExtension) => ((e.rating ?? 5) / 5) * e.users;
return results.sort((a, b) => score(b) - score(a));
});
function formatUsers(n: number): string {
return `${n.toLocaleString()} users`;
}
function formatStars(r: number): string {
const rounded = Math.round(r * 10) / 10;
return `${rounded % 1 === 0 ? rounded.toFixed(0) : rounded.toFixed(1)} stars`;
}
</script>
<template>
<p v-if="isLoading" style="text-align: center; opacity: 50%">Loading...</p>
<p
v-else-if="err || sortedExtensions.length === 0"
v-else-if="sortedExtensions.length === 0"
style="text-align: center; opacity: 50%"
>
Failed to load extension details.
</p>
<ul v-else>
<li
v-for="extension of sortedExtensions"
:key="extension.id"
class="relative"
>
<ul v-else class="extension-grid">
<li v-for="extension of sortedExtensions" :key="extension.id">
<img
:src="extension.iconUrl"
:alt="`${extension.name} icon`"
referrerpolicy="no-referrer"
/>
<div class="relative">
<div class="card-content">
<a
:href="getStoreUrl(extension)"
:href="extension.stores[0]?.url ?? '#'"
target="_blank"
:title="extension.name"
class="extension-name"
@@ -166,19 +123,40 @@ function getStoreUrl(extension: ChromeExtension) {
<p class="description" :title="extension.shortDescription">
{{ extension.shortDescription }}
</p>
<div v-if="extension.stores.length > 0" class="store-stats">
<span class="store-stats-info">
<span>{{ formatUsers(extension.users) }}</span>
<template v-if="extension.rating">
<span class="store-stats-sep" aria-hidden="true">,</span>
<span>{{ formatStars(extension.rating) }}</span>
</template>
</span>
<span class="store-stats-sep" aria-hidden="true">&middot;</span>
<span class="store-links">
<a
v-for="(store, i) of extension.stores"
:key="store.label"
:href="store.url"
target="_blank"
class="store-link"
:title="store.label"
>
{{ store.label
}}<span
v-if="i < extension.stores.length - 1"
class="store-stats-sep"
aria-hidden="true"
>,&nbsp;</span
>
</a>
</span>
</div>
</div>
<p class="user-count">
<span>{{ extension.weeklyActiveUsers.toLocaleString() }} users</span
><template v-if="extension.rating != null"
>,
<span>{{ extension.rating }} stars</span>
</template>
</p>
</li>
</ul>
<p class="centered pr">
<a
href="https://github.com/wxt-dev/wxt/edit/main/docs/.vitepress/components/UsingWxtSection.vue"
href="https://github.com/wxt-dev/wxt/edit/main/docs/assets/extension-showcase.yml"
target="_blank"
>Open a PR</a
>
@@ -187,7 +165,7 @@ function getStoreUrl(extension: ChromeExtension) {
</template>
<style scoped>
li img {
.extension-grid > li > img {
width: 116px;
height: 116px;
padding: 16px;
@@ -195,7 +173,7 @@ li img {
background-color: var(--vp-c-default-soft);
}
ul {
.extension-grid {
display: grid;
grid-template-columns: repeat(1, 1fr);
align-items: stretch;
@@ -205,12 +183,12 @@ ul {
padding: 0;
}
@media (min-width: 960px) {
ul {
.extension-grid {
grid-template-columns: repeat(2, 1fr);
}
}
li {
.extension-grid > li {
margin: 0 !important;
padding: 16px;
display: flex;
@@ -218,28 +196,68 @@ li {
border-radius: 12px;
flex: 1;
gap: 24px;
align-items: center;
align-items: stretch;
}
.centered {
text-align: center;
}
li a,
li .user-count,
li .description {
.extension-grid > li .description {
padding: 0;
margin: 0;
}
li .user-count {
opacity: 70%;
font-size: small;
position: absolute;
bottom: 12px;
right: 16px;
.extension-grid > li .card-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
padding-right: 0;
}
li a {
.store-stats {
margin-top: auto;
padding-top: 6px;
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
align-items: baseline;
column-gap: 6px;
row-gap: 4px;
text-align: right;
font-size: 12px;
line-height: 1.45;
opacity: 0.72;
color: var(--vp-c-text-2);
}
.store-stats-info {
display: inline;
}
.store-stats-sep {
opacity: 0.45;
user-select: none;
}
.store-links {
display: inline;
}
.store-link {
text-decoration: none;
color: inherit;
cursor: pointer;
}
.store-link:hover {
color: var(--vp-c-text-1);
text-decoration: underline;
}
.extension-grid > li .extension-name {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
@@ -248,33 +266,23 @@ li a {
padding: 0;
margin: 0;
text-decoration: none;
font-size: large;
}
li a:hover {
.extension-grid > li .extension-name:hover {
text-decoration: underline;
}
li div {
flex: 1;
}
li .description {
.extension-grid > li .description {
opacity: 90%;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
margin-bottom: 16px;
}
li .extension-name {
font-size: large;
margin-bottom: 0 !important;
}
.pr {
opacity: 70%;
}
.relative {
position: relative;
}
</style>
@@ -1,39 +1,58 @@
import { ref } from 'vue';
export interface ChromeExtension {
export interface Extension {
id: string;
name: string;
iconUrl: string;
weeklyActiveUsers: number;
shortDescription: string;
storeUrl: string;
rating: number | undefined;
users: number;
}
export interface ExtensionResults {
chrome: Extension[];
firefox: Extension[];
}
const operationName = 'WxtDocsUsedBy';
const query = `query ${operationName}($ids:[String!]!) {
chromeExtensions(ids: $ids) {
const query = `query ${operationName}($chromeIds: [String!]!, $firefoxIds: [String!]!) {
chromeExtensions(ids: $chromeIds) {
id
name
iconUrl
weeklyActiveUsers
shortDescription
storeUrl
rating
...ExtensionData
}
firefoxAddons(ids: $firefoxIds) {
id: slug
...ExtensionData
}
}
fragment ExtensionData on Extension {
name
iconUrl
shortDescription
storeUrl
rating
users
}`;
export default function (ids: string[]) {
const data = ref<ChromeExtension[]>();
export default function (chromeIds: string[], firefoxSlugs: string[]) {
const data = ref<ExtensionResults>();
const err = ref<unknown>();
const isLoading = ref(true);
if (chromeIds.length === 0 && firefoxSlugs.length === 0) {
data.value = { chrome: [], firefox: [] };
isLoading.value = false;
return { data, err, isLoading };
}
fetch('https://queue.wxt.dev/api', {
method: 'POST',
body: JSON.stringify({
operationName,
query,
variables: { ids },
variables: { chromeIds, firefoxIds: firefoxSlugs },
}),
headers: {
'Content-Type': 'application/json',
@@ -41,10 +60,11 @@ export default function (ids: string[]) {
})
.then(async (res) => {
isLoading.value = false;
const {
data: { chromeExtensions },
} = await res.json();
data.value = chromeExtensions;
const { data: responseData } = await res.json();
data.value = {
chrome: responseData.chromeExtensions ?? [],
firefox: responseData.firefoxAddons ?? [],
};
err.value = undefined;
})
.catch((error) => {
@@ -54,9 +74,5 @@ export default function (ids: string[]) {
err.value = error;
});
return {
data,
err,
isLoading,
};
return { data, err, isLoading };
}
+35 -19
View File
@@ -1,5 +1,25 @@
import { Feed } from 'feed';
import footnote from 'markdown-it-footnote';
import fs, { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { DefaultTheme, defineConfig } from 'vitepress';
import addKnowledge from 'vitepress-knowledge';
import {
groupIconMdPlugin,
groupIconVitePlugin,
localIconLoader,
} from 'vitepress-plugin-group-icons';
import llmstxt from 'vitepress-plugin-llms';
import { version as analyticsVersion } from '../../packages/analytics/package.json';
import { version as autoIconsVersion } from '../../packages/auto-icons/package.json';
import { version as i18nVersion } from '../../packages/i18n/package.json';
import { version as runnerVersion } from '../../packages/runner/package.json';
import { version as storageVersion } from '../../packages/storage/package.json';
import { version as unocssVersion } from '../../packages/unocss/package.json';
import { version as wxtVersion } from '../../packages/wxt/package.json';
import { version as isBackgroundVersion } from '../../packages/is-background/package.json';
import typedocSidebar from '../api/reference/typedoc-sidebar.json';
import { meta, script } from './utils/head';
import {
menuGroup,
menuItem,
@@ -7,24 +27,6 @@ import {
navItem,
prepareTypedocSidebar,
} from './utils/menus';
import { meta, script } from './utils/head';
import footnote from 'markdown-it-footnote';
import { version as wxtVersion } from '../../packages/wxt/package.json';
import { version as i18nVersion } from '../../packages/i18n/package.json';
import { version as autoIconsVersion } from '../../packages/auto-icons/package.json';
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 addKnowledge from 'vitepress-knowledge';
import {
groupIconMdPlugin,
groupIconVitePlugin,
localIconLoader,
} from 'vitepress-plugin-group-icons';
import { Feed } from 'feed';
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
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(
@@ -79,6 +83,17 @@ export default defineConfig({
),
},
}),
{
name: 'yaml-loader',
load: {
handler: async (id) => {
if (id.endsWith('.yml') || id.endsWith('.yaml')) {
const obj = Bun.YAML.parse(await readFile(id, 'utf8'));
return `export default ${JSON.stringify(obj)}`;
}
},
},
},
],
},
lastUpdated: true,
@@ -109,7 +124,7 @@ export default defineConfig({
});
// console.log('rss.xml:');
// console.log(feed.rss2());
await writeFile(join(site.outDir, 'rss.xml'), feed.rss2(), 'utf8');
await fs.writeFile(join(site.outDir, 'rss.xml'), feed.rss2());
},
head: [
@@ -220,6 +235,7 @@ export default defineConfig({
menuItem('Storage', 'storage.md'),
menuItem('Messaging', 'messaging.md'),
menuItem('I18n', 'i18n.md'),
menuItem('Favicons', 'favicons.md'),
menuItem('Scripting', 'scripting.md'),
menuItem('WXT Modules', 'wxt-modules.md'),
menuItem('Frontend Frameworks', 'frontend-frameworks.md'),
+8 -6
View File
@@ -1,6 +1,7 @@
import { resolve } from 'node:path';
import consola from 'consola';
import spawn from 'nano-spawn';
import { resolve } from 'node:path';
import { version } from '../../../packages/wxt/package.json';
const cliDir = resolve('packages/wxt/src/cli/commands');
const cliDirGlob = resolve(cliDir, '**');
@@ -38,19 +39,20 @@ export default {
async function getHelp(command: string): Promise<string> {
const args = command.split(' ');
const res = await spawn(args[0], [...args.slice(1), '--help'], {
const result = await spawn(args[0], [...args.slice(1), '--help'], {
cwd: 'packages/wxt',
});
return res.stdout;
return result.stdout;
}
function getWxtHelp(command: string): Promise<string> {
return getHelp(`pnpm -s wxt ${command}`.trim());
async function getWxtHelp(command: string): Promise<string> {
const res = await getHelp(`bun run --silent wxt ${command}`.trim());
return res.replaceAll('{{version}}', version);
}
async function getPublishExtensionHelp(command: string): Promise<string> {
const res = await getHelp(
`./node_modules/.bin/publish-extension ${command}`.trim(),
`bun run --silent publish-extension ${command}`.trim(),
);
return res.replace(/\$ publish-extension/g, '$ wxt submit');
}
+26
View File
@@ -66,3 +66,29 @@ body {
.dark .dark-netlify {
display: inline;
}
/* Add Aaron's favorite pic from Artemis II to the hero section because it's cool. */
.dark .VPHome::before {
content: '';
background-image: url('/assets/art002e009298.jpg');
opacity: 50%;
position: absolute;
width: 100vw;
height: 110vh;
background-size: cover;
background-position: 90%;
pointer-events: none;
}
.dark .VPHome::after {
content: '';
position: absolute;
width: 100vw;
height: 10vh;
top: 100vh;
background: linear-gradient(to bottom, transparent, var(--vp-c-bg));
pointer-events: none;
z-index: 0;
}
.item {
z-index: 1;
}
+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.

After

Width:  |  Height:  |  Size: 237 KiB

+438
View File
@@ -0,0 +1,438 @@
# Add extension entries to end of the list. On the website, extensions will be sorted by a combination of users and rating.
#
# Change the commit message or PR title to:
#
# > "docs: Added "[extension name]" to showcase"
#
# Use:
#
# - `chromeId` for the Chrome Web Store listing,
# - `firefoxSlug` for Firefox Add-ons (slug from https://addons.mozilla.org/firefox/addon/{slug}/)
#
# You may include one or more of these fields for your extension.
- # GitHub: Better Line Counts
chromeId: ocfdgncpifmegplaglcnglhioflaimkd
firefoxSlug: github-better-line-counts
- # Anime Skip Player
chromeId: mgmdkjcljneegjfajchedjpdhbadklcf
firefoxSlug: anime-skip
- # UltraWideo
chromeId: bfbnagnphiehemkdgmmficmjfddgfhpl
firefoxSlug: ultrawideo
- # StayFree - Website Blocker & Web Analytics
chromeId: elfaihghhjjoknimpccccmkioofjjfkf
firefoxSlug: stayfree
- # Doozy: Ai Made Easy
chromeId: okifoaikfmpfcamplcfjkpdnhfodpkil
- # tl;dv - Record, Transcribe & ChatGPT for Google Meet
chromeId: lknmjhcajhfbbglglccadlfdjbaiifig
- # Youtube中文配音
chromeId: oglffgiaiekgeicdgkdlnlkhliajdlja
- # PreMiD
chromeId: agjnjboanicjcpenljmaaigopkgdnihi
- # Markdown Sticky Notes
chromeId: aiakblgmlabokilgljkglggnpflljdgp
firefoxSlug: markdown-sticky-notes
- # DocVersionRedirector
chromeId: nomnkbngkijpffepcgbbofhcnafpkiep
- # Plex Skipper
chromeId: ceicccfeikoipigeghddpocceifjelph
firefoxSlug: plex-skipper
- # GitHub Custom Notifier
chromeId: aelkipgppclpfimeamgmlonimflbhlgf
firefoxSlug: github-custom-notifier
- # Fluent Read
chromeId: djnlaiohfaaifbibleebjggkghlmcpcj
- # Facebook Video Controls
chromeId: nhclljcpfmmaiojbhhnkpjcfmacfcian
firefoxSlug: facebook-video-controls
- # ElemSnap - Quick capture of webpage elements and conversion to images,
chromeId: mblkhbaakhbhiimkbcnmeciblfhmafna
- # MS Edge TTS (Text to Speech)
chromeId: oajalfneblkfiejoadecnmodfpnaeblh
firefoxSlug: ms-edge-tts-text-to-speech
- # YTBlock - Block any content from YouTube™
chromeId: nedcanggplmbbgmlpcjiafgjcpdimpea
- # demo.fun - Interactive product demos that convert
chromeId: oadbjpccljkplmhnjekgjamejnbadlne
- # SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
chromeId: iopdafdcollfgaoffingmahpffckmjni
- # WorkFlowy MultiFlow
chromeId: khjdmjcmpolknpccmaaipmidphjokhdf
- # 香草布丁🌿🍮- https://github.com/Xdy1579883916/vanilla-pudding
chromeId: fencadnndhdeggodopebjgdfdlhcimfk
- # MaxFocus: Link Preview
chromeId: bnacincmbaknlbegecpioobkfgejlojp
firefoxSlug: maxfocus-link-preview
- # 汇率转换-中文版本
chromeId: bcpgdpedphodjcjlminjbdeejccjbimp
- # Currency Converter Plus
chromeId: loeilaonggnalkaiiaepbegccilkmjjp
firefoxSlug: currency-converter-plus
- # Respond Easy
chromeId: npcnninnjghigjfiecefheeibomjpkak
- # mindful - stay focused on your goals
chromeId: cfkdcideecefncbglkhneoflfnmhoicc
- # 1Proompt
chromeId: lnhejcpclabmbgpiiomjbhalblnnbffg
- # NiceTab - https://github.com/web-dahuyou/NiceTab
chromeId: fonflmjnjbkigocpoommgmhljdpljain
- # Draftly for LinkedIn
chromeId: fcffekbnfcfdemeekijbbmgmkognnmkd
- # YouTube Summarized - Summarize any YouTube video
chromeId: nkndldfehcidpejfkokbeghpnlbppdmo
- # 社媒助手 - https://github.com/iszhouhua/social-media-copilot
chromeId: dbichmdlbjdeplpkhcejgkakobjbjalc
- # Dofollow Links for SEO
chromeId: opepfpjeogkbgeigkbepobceinnfmjdd
- # ChatGPT Writer: Use AI on Any Site (GPT-4o, Claude, Gemini, and More)
chromeId: pdnenlnelpdomajfejgapbdpmjkfpjkp
- # discord message translator
chromeId: jobnhifpphkgoelnhnopgkdhbdkiadmj
- # Habit Tracker app widget for daily habit tracking
chromeId: ncokhechhpjgjonhjnlaneglmdkfkcbj
- # Catppuccin for GitHub File Explorer Icons
chromeId: lnjaiaapbakfhlbjenjkhffcdpoompki
firefoxSlug: catppuccin-web-file-icons
- # WebChat: Chat with anyone on any website
chromeId: cpaedhbidlpnbdfegakhiamfpndhjpgf
- # YouTube Auto HD + FPS
chromeId: fcphghnknhkimeagdglkljinmpbagone
firefoxSlug: youtube-auto-hd-fps
- # MultiViewer Companion
chromeId: lpomjgbicdemjkgmbnkjncgdebogkhlb
firefoxSlug: multiviewer-companion
- # Sync Watch - Watch videos together on any site
chromeId: ggiafipgeeaaahnjamgpjcgkdpanhddg
firefoxSlug: syncwatch
- # Keyword Rank Checker
chromeId: nmldnjcblcihmegipecakhmnieiofmgl
firefoxSlug: keyword-rank-checker-sa
- # YouTube Simple View - Hide distractions & more
chromeId: gppllamhaciichleihemgilcpledblpn
firefoxSlug: youtube-simple-view
- # Propbar - Property Data Enhancer
chromeId: pccbghdfdnnkkbcdcibchpbffdgednkf
- # Text Search Pro - Search by case and whole-word match!
chromeId: lfknakglefggmdkjdfhhofkjnnolffkh
firefoxSlug: text-search-pro-ext
- # Invoice Generator
chromeId: mbenhbocjckkbaojacmaepiameldglij
firefoxSlug: quick-invoice-generator
- # Monthly Bill Tracker
chromeId: phlfhkmdofajnbhgmbmjkbkdgppgoppb
- # Wandpen - Instantly improve your writing with AI
chromeId: macmkmchfoclhpbncclinhjflmdkaoom
- # YouTube Hider - Remove Comments By Keywords, Usernames & Tools
chromeId: lhmgechokhmdekdpgkkemoeecelcaonm
- # QA Compass - Record standardized bug reports easily
chromeId: imgheieooppmahcgniieddodaliodeeg
- # aesthetic Notion, styled
chromeId: npgghjedpchajflknnbngajkjkdhncdo
- # Eye Dropper
chromeId: hmdcmlfkchdmnmnmheododdhjedfccka
firefoxSlug: eye_dropper
- # Cursorful - Screen Recorder with Auto Zoom
chromeId: eihpmapodnppeemkhkbhikmggfojdkjd
- # Show IP Live View of Website IPs for Developers
chromeId: hjjkgbibknbahijglkffklflidncplkn
- # Strong Password Generator
chromeId: ilbikcehnpkmldojkcmlldkoelofnbde
firefoxSlug: strongpasswordgenerator
- # ZenGram: Mindful Instagram, Your Way
chromeId: ocllfkhcdopiafndigclebelbecaiocp
- # Blync: Preview Links, Selection Search, AI Assistant
chromeId: odffpjnpocjfcaclnenaaaddghkgijdb
- # HTML to Markdown - Convert webpages to markdown
chromeId: kofbbilhmnkcmibjbioafflgmpkbnmme
- # Walmart WFS Profit Calculator
chromeId: boecmgggeigllcdocgioijmleimjbfkg
firefoxSlug: walmart-wfs-profit-calculator
- # Youtube Live Chat Fullscreen
chromeId: dlnjcbkmomenmieechnmgglgcljhoepd
firefoxSlug: youtube-live-chat-fullscreen
- # Python Code Runner
chromeId: keiealdacakpnbbljlmhfgcebmaadieg
firefoxSlug: code-runner-manager
- # Monochromate
chromeId: hafcajcllbjnoolpfngclfmmgpikdhlm
- # AliasVault - Open-Source Password & (Email) Alias Manager
chromeId: bmoggiinmnodjphdjnmpcnlleamkfedj
- # SnapThePrice: AI-Powered Real-time Lowest Price Finder
chromeId: hlnhhamckimoaiekbglafiebkfimhapb
- # radiofrance - news & broadcasts (French), music (international)
chromeId: gdjampjdgjmbifnhldgcnccdjkcoicmg
- # Blens - Time Tracker and AI Insight
chromeId: jlnhphlghikichhgbnkepenehbmloenb
firefoxSlug: blens-timetracker-ai-insight
- # Always Light Mode - Setting website always in light mode
chromeId: njnammmpdodmfkodnfpammnpdcbhnlcm
- # DesignPicker - Color Picker & Font Detector
chromeId: lblmfclcfniabobmamfkdogcgdagbhhb
firefoxSlug: design-colorpicker-fontdetect
- # Web to PDF
chromeId: pamnlaoeobcmhkliljfaofekeddpmfoh
firefoxSlug: export-web-to-pdf
- # Online CSV Viewer
chromeId: jmbcbeepjfenihlocplnbmbhimcoooka
firefoxSlug: csv-viewer
- # YouTube Video Transcript
chromeId: nkjcoophmpcmmgadnljnlpbpfdfacgbo
firefoxSlug: youtube-transcript-copy
- # NetSuite Record Scripts
chromeId: lcaieahkjgeggeiihblhcjbbjlppgieh
firefoxSlug: netsuite-scripts-manager
- # VueTracker
chromeId: gmocfknjllodfiomnljmaehcplnekhlo
- # CanCopy - A web extension that allow you to copy any content from website
chromeId: ggcfemmoabhhelfkhknhbnkmeahloiod
- # Read Frog
chromeId: modkelfkcfjpgbfmnbnllalkiogfofhb
firefoxSlug: read-frog-open-ai-translator
- # Bilibili Feed History Helper
chromeId: npfopljnjbamegincfjelhjhnonnjloo
- # NZBDonkey - The ultimate NZB file download tool
chromeId: edkhpdceeinkcacjdgebjehipmnbomce
- # WeChat Markdown Editor(微信 Markdown 编辑器)
chromeId: cckggnbnimdbbpmdinkkgbbncopbloob
- # Tab Grab
chromeId: jcblcjolcojmfopefcighfmkkefbaofg
firefoxSlug: tab-grab
- # BrowserLens - https://browserlens.com/
chromeId: eehmoikadcijkapfjocnhjclpbaindlb
firefoxSlug: browserlens
- # Epic Games Library Extension
chromeId: hfhellofkjebbchcdffmicekjdomkcmc
firefoxSlug: epic-games-library-extension
- # Zen Analytics Pixel Tracker - zapt.web.app
chromeId: gknigcbhlammoakmmdddkblknanpjiac
- # Crypto Pulse - Compose your newtab with nature images, widgets & realtime Crypto Price & Bitcoin RSS.
chromeId: cnklededohhcbmjjdlbjdkkihkgoggol
- # Youtube Video Scheduler
chromeId: miponnamafdenpgjemkknimgjfibicdc
firefoxSlug: youtube-video-scheduler
- # Chatslator: Livestream Chat Translator
chromeId: nhmbcmalgpkjbomhlhgdicanmkkaajmg
firefoxSlug: chatslator
- # 公众号阅读增强器 - https://wxreader.honwhy.wang
chromeId: mbamjfdjbcdgpopfnkkmlohadbbnplhm
- # 토탐정
chromeId: hannhecbnjnnbbafffmogdlnajpcomek
firefoxSlug: 토탐정
- # 2FAS Pass - https://2fas.com/
chromeId: ehboaofjncodknjkngdggmpdinhdoijp
firefoxSlug: 2fas-pass-browser-extension
- # Quick Prompt - https://github.com/wenyuanw/quick-prompt
chromeId: hnjamiaoicaepbkhdoknhhcedjdocpkd
firefoxSlug: quick-prompt
- # Add QR Code Generator Icon Back To Address Bar
chromeId: kacblhilkacgfnkjfodalohcnllcgmjd
- # Piwik PRO Tracking Helper
chromeId: fkbdlogfdjmpfepbbbjcgcfbgbcfcnne
firefoxSlug: piwik-pro-tracking-helper
- # PIPX - Take Control of Picture-in-Picture, Automatically
chromeId: nkbikckldmljjiiajklecmgmajgapbfl
- # Browsely - AI-powered browser extension
chromeId: hgppdobcpkfkmiegekaglonjajeojmdd
- # Filmbudd Pro - Simple, private and synced ratings and watch notes across all your devices
chromeId: ehmoihnjgkdimihkhokkmfjdgomohjgm
- # MultiField CopyCat - Copy, Paste & Autofill Web Forms Instantly
chromeId: alglchohmdikgdjhafiicilegegieafa
- # ChatSight - Add Table of Contents to ChatGPT
chromeId: aamihahiiogceidpbnfgehacgiecephe
- # BetterCampus (prev. BetterCanvas)
chromeId: cndibmoanboadcifjkjbdpjgfedanolh
firefoxSlug: better-canvas
- # Leetcode Fonts - Change fonts in leetcode effortlessly
chromeId: hinfimgacobnellbncbcpdlpaapcofaa
- # TranslateManga - Manga Translator & Manga Tracker
chromeId: kbkbfefhhabpkibojinapkkgciiacggg
- # SiteData - Free Website Traffic Checker & Reverse AdSense Tool
chromeId: emeakbgdecgmdjgegnejpppcnkcnoaen
firefoxSlug: sitedata
- # Livestream Chat Reader - Text-to-Speech for YouTube/Twitch chat
chromeId: gpnckbhgpnjciklpoehkmligeaebigaa
- # ChatGPT Token Counter - Count tokens in real time on chatgpt conversation
chromeId: fjlalaedpfcojcfpkgkglbjjbbkofgnl
firefoxSlug: chatgpt-token-counter
- # LinuxDo Scripts - 为 linux.do 用户提供了一些增强功能
chromeId: fbgblmjbeebanackldpbmpacppflgmlj
firefoxSlug: linux_do-scripts
- # Zen Virtual Piano - https://zen-piano.web.app/
chromeId: dfacnjidgbagicaekenjgclfnhdnjjdi
- # Crypto Pulse price tracker - https://get-crypto-pulse.web.app/
chromeId: naeibcegmgpofimedkmfgjgphfhfhlab
- # Redirect Web - Automatically redirect pages or open them in another app
chromeId: ffglckbhfbfmdkefdmjbhpnffkcmlhdh
- # Capture It - Capture & Edit Screenshots
chromeId: eglpfhbhmelampoihamjomgkeobgdofl
- # Teams Chat Exporter
chromeId: jmghclbfbbapimhbgnpffbimphlpolnm
firefoxSlug: teams-chat-exporter
- # Lofi BGM Player - Free lofi focus music for work & study
chromeId: jdcppdokgfbnhiacbeplahgnciahnhck
firefoxSlug: lofi-bgm-player
- # Margin - Annotate and highlight any webpage, with your notes saved to the decentralized AT Protocol.
chromeId: cgpmbiiagnehkikhcbnhiagfomajncpa
- # KeyFloat - Floating multilingual keyboard with native key mappings, drag, dark mode, sounds, and dynamic layouts for macOS & Windows
chromeId: mfjdonmgmgcijagclnkfhmjiblbfjaid
- # Glossy New Tab - Say Goodbye to Boring Tabs with live wallpapers
chromeId: dhiekgdaipindoapjmcnpompdknjeijf
firefoxSlug: glossy-new-tab
- # All API Hub AI Relay & New API Manager - https://github.com/qixing-jk/all-api-hub
chromeId: lapnciffpekdengooeolaienkeoilfeo
firefoxSlug: all-api-hub
- # Scrape Similar - Extract data from websites into spreadsheets - https://github.com/zizzfizzix/scrape-similar
chromeId: bhgobenflkkhfcgkikejaaejenoddcmo
- # isTrust - https://github.com/Internet-Society-Belgium/isTrust/
chromeId: kinlknncggaihnhdcalijdmpbhbflalm
- # Dymo
chromeId: ojpakgiekphppgkcdihbjpafobhnhlkp
- # Extension Rank Checker - Extension Ranker
chromeId: pmgehhllikbjmadpenhabejhpemplhmd
firefoxSlug: rank-checker
- # PlayFaster - Enhanced playback speed control for online videos and audio
chromeId: fppcbkhpahkbgijkdcpjgjmhpfbmfiih
firefoxSlug: playfaster
- # LatTab - Learn Latin With Every New Tab
chromeId: eiocjaocpmackhbaffoejkcmnfbdpgpj
firefoxSlug: lattab-learn-latin-new-tabs
- # Vim What? - Interactive Vim command reference
chromeId: ngbehgnlcdjkbnihgpkgdangbhemidge
firefoxSlug: vim-what
- # FRED - Fraud Recognition Easy Detection
chromeId: bjdbcabacnlmbpcmiapcdfancfgcakfn
firefoxSlug: fred
- # Clear Wisdom - Gems of wisdom from James Clear's 3-2-1 newsletter
chromeId: jijfmgoijddfmlcdghopbkdpelbpmjdm
firefoxSlug: clear-wisdom
- # Soundcloud Enhanced Pro
chromeId: ggplcohodggmdfpopelnpplhgfjclomi
- # AlarmBot: ULTIMATE Web Monitoring & Smart Price Alerts
chromeId: mpckalcodookackleecihhnngdibelif
- # TF2 Trader - TF2 & Steam Trading Extension - https://github.com/offish/tf2-trader
chromeId: gmicpekfpbikhibodgokfpghadkclhoe
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
@@ -61,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'],
});
@@ -73,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
@@ -94,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.
+2
View File
@@ -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
+24
View File
@@ -159,6 +159,30 @@ export default defineConfig({
Alternatively, you can use [`@wxt-dev/auto-icons`](https://www.npmjs.com/package/@wxt-dev/auto-icons) to let WXT generate your icon at the required sizes.
### Firefox `theme_icons`
Firefox supports a [`theme_icons`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action#theme_icons) field on the toolbar action that swaps between a light and dark variant based on the current browser theme.
When [targeting Firefox](/guide/essentials/target-different-browsers.md), WXT auto-discovers paired light/dark icons in the `public/` directory and attaches them to `action` (MV3) or `browser_action` (MV2). You only need to drop both variants next to your regular icons:
```plaintext
public/
├─ icon-16.png # regular (default_icon)
├─ icon-light-16.png # Firefox light theme
├─ icon-dark-16.png # Firefox dark theme
├─ icon-32.png
├─ icon-light-32.png
└─ icon-dark-32.png
```
A size is only included in `theme_icons` if **both** a light and a dark file are present. The following filename patterns are discovered:
<<< @/../packages/wxt/src/core/utils/theme-icons.ts#snippet
Only `.png` files are discovered today, even though Firefox supports `.svg` - follow [#1120](https://github.com/wxt-dev/wxt/issues/1120) for updates.
If you set `manifest.action.theme_icons` (or `manifest.browser_action.theme_icons`) explicitly in `wxt.config.ts`, WXT will not overwrite it.
## Permissions
> [Chrome docs](https://developer.chrome.com/docs/extensions/reference/permissions/)
+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
+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', ...]" />
+1 -1
View File
@@ -25,7 +25,7 @@ export default defineBackground({
});
```
This will change the output format to ESM, enable code-spliting between your background script and HTML pages, and set `"type": "module"` in your manifest.
This will change the output format to ESM, enable code-splitting between your background script and HTML pages, and set `"type": "module"` in your manifest.
:::warning
Only MV3 supports ESM background scripts/service workers. When targeting MV2, the `type` option is ignored and the background is always bundled into a single file as IIFE.
+22
View File
@@ -76,6 +76,28 @@ Alternatively, if you're trying to use similar APIs under different names (to su
});
```
### Augmenting the Browser Type
WXT's `browser` types are based on the `@types/chrome` package. That means some Firefox-specific APIs may not be typed, like `browser.sidebarAction`. If you want to add types for these APIs, you can augment the browser type to add them yourself:
```ts
// <srcDir>/browser-types.d.ts
import '@wxt-dev/browser';
import type { SidebarAction } from 'webextension-polyfill';
declare module '@wxt-dev/browser' {
namespace Browser {
export const sidebarAction: SidebarAction.Static;
}
}
```
> For this to work, you may need to install `@wxt-dev/browser` as a direct dependency.
>
> ```sh
> pnpm add @wxt-dev/browser
> ```
## Entrypoint Limitations
Because WXT imports your entrypoint files into a NodeJS, non-extension environment, the `chrome`/`browser` variables provided to extensions by the browser **will not be available**.
+51
View File
@@ -0,0 +1,51 @@
# Favicons
[Chrome Docs](https://developer.chrome.com/docs/extensions/how-to/ui/favicons)
Chromium-based browsers expose cached favicons through the `_favicon/` URL served from your extension. To use it, declare the `favicon` permission in your manifest and call `browser.runtime.getURL` with a `_favicon/` path:
```ts
// wxt.config.ts
import { defineConfig } from 'wxt';
export default defineConfig({
manifest: {
permissions: ['favicon'],
},
});
```
```ts
// Any entrypoint
function getFaviconUrl(pageUrl: string, size = 16) {
const url = new URL(browser.runtime.getURL('/_favicon/'));
url.searchParams.set('pageUrl', pageUrl);
url.searchParams.set('size', String(size));
return url.toString();
}
```
::: warning Chromium only
The favicon API is only available on Chromium-based browsers. Firefox has no equivalent, so `/_favicon/` URLs will not resolve at runtime there. If your extension supports both browsers, gate favicon usage behind `import.meta.env.CHROME` (or similar) and declare the permission per-browser.
:::
## Usage from a content script
WXT does **not** add a `web_accessible_resources` entry for `_favicon/*` — not every extension needs one, and adding it unconditionally would expose internals to sites that don't need them. If you want to load a favicon inside a content script (for example, as an `<img src>`), add your own entry in `wxt.config.ts`:
```ts
// wxt.config.ts
import { defineConfig } from 'wxt';
export default defineConfig({
manifest: {
permissions: ['favicon'],
web_accessible_resources: [
{
resources: ['_favicon/*'],
matches: ['<all_urls>'],
},
],
},
});
```
+2 -2
View File
@@ -163,8 +163,8 @@ yarn zip:firefox
```
```sh [bun]
bun i
bun zip:firefox
bun install
bun run zip:firefox
```
:::
+77 -25
View File
@@ -1,32 +1,24 @@
---
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.
WXT provides a "module system" that lets you run code at different steps in the build process to modify it.
[[toc]]
## Adding a Module
## Installing a Module
There are two ways to add a module to your project:
To use a published module from NPM, install the package and add it to your config:
1. **NPM**: install an NPM package, like [`@wxt-dev/auto-icons`](https://www.npmjs.com/package/@wxt-dev/auto-icons) and add it to your config:
```ts [wxt.config.ts]
export default defineConfig({
modules: ['@wxt-dev/auto-icons'],
});
```
```ts [wxt.config.ts]
export default defineConfig({
modules: ['@wxt-dev/auto-icons'],
});
```
> Searching for ["wxt module"](https://www.npmjs.com/search?q=wxt%20module) on NPM is a good way to find published WXT modules.
2. **Local**: add a file to your project's `modules/` directory:
```plaintext
<rootDir>/
modules/
my-module.ts
```
> To learn more about writing your own modules, read the [Writing Modules](/guide/essentials/wxt-modules) docs.
> Searching for ["wxt module"](https://www.npmjs.com/search?q=wxt%20module) on NPM is a good way to find published WXT modules.
## Module Options
@@ -45,7 +37,7 @@ Modules are loaded in the same order as hooks are executed. Refer to the [Hooks
## Writing Modules
Here's what a basic WXT module looks like:
If you need custom build logic for your project, you can write your own local module. Here's what a basic WXT module looks like:
```ts
import { defineWxtModule } from 'wxt/modules';
@@ -57,6 +49,14 @@ export default defineWxtModule({
});
```
To add it to your project, place the file in the `modules/` directory at the root of your project. Any module file in this directory is **automatically discovered and loaded** — no additional configuration is needed:
```plaintext
<rootDir>/
modules/
my-module.ts ← loaded automatically
```
Each module's setup function is executed after the `wxt.config.ts` file is loaded. The `wxt` object provides everything you need to write a module:
- Use `wxt.hook(...)` to hook into the build's lifecycle and make changes
@@ -66,7 +66,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 +131,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 +272,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;
`;
+1 -1
View File
@@ -76,7 +76,7 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
```
```sh [Bun]
bun i -D wxt
bun add -D wxt
```
```sh [NPM]
+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 -3
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 ++]
```
@@ -476,7 +476,7 @@ export default defineConfig({
### Renamed Undocumented Constants
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: <https://wxt.dev/guide/multiple-browsers.html#runtime>
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: <https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables>
- `__BROWSER__` → `import.meta.env.BROWSER`
- `__COMMAND__` → `import.meta.env.COMMAND`
@@ -557,6 +557,8 @@ Unlisted scripts must now `export default defineUnlistedScript(...)`.
### `BackgroundDefinition` Type
<!-- cspell:disable -->
Rename `BackgroundScriptDefintition` to `BackgroundDefinition`.
## v0.6.0 &rarr; v0.7.0
+1
View File
@@ -0,0 +1 @@
<!--@include: ../packages/is-background/README.md-->
+3 -5
View File
@@ -4,7 +4,7 @@ outline: deep
# WXT Storage
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md)
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md) &bull; [API Reference](/api/reference/wxt/utils/storage/interfaces/WxtStorage)
A simplified wrapper around the extension storage APIs.
@@ -74,7 +74,7 @@ await storage.watch<number>(
await storage.getMeta<{ v: number }>('local:installDate');
```
For a full list of methods available, see the [API reference](/api/reference/wxt/utils/storage/interfaces/WxtStorage).
> This approach is fine for one-off storage fields or generic helpers, but [defining storage items](#defining-storage-items) is the recommended way to add type-safety.
## Watchers
@@ -146,7 +146,7 @@ const showChangelogOnUpdate = storage.defineItem<boolean>(
);
```
Now, instead of using the `storage` variable, you can use the helper functions on the storage item you created:
Now, instead of using the `storage` variable, you can use the storage item instead:
```ts
await showChangelogOnUpdate.getValue();
@@ -157,8 +157,6 @@ const unwatch = showChangelogOnUpdate.watch((newValue) => {
});
```
For a full list of properties and methods available, see the [API reference](/api/reference/wxt/utils/storage/interfaces/WxtStorageItem).
### Versioning
You can add versioning to storage items if you expect them to grow or change over time. When defining the first version of an item, start with version 1.
+1 -1
View File
@@ -26,7 +26,7 @@ Set FontSize 32
# Terminal settings
Set Shell "bash"
# Terminal prompt. It looks like "● mycommand ..."
# Terminal prompt. It looks like "● command ..."
Env PS1 "\e[0;32m●\e[0m "
+6
View File
@@ -0,0 +1,6 @@
[build]
command = "bun run docs:build"
publish = "docs/.vitepress/dist"
[build.environment]
BUN_VERSION = "1.3.5"
+38 -28
View File
@@ -2,41 +2,45 @@
"private": true,
"type": "module",
"engines": {
"node": ">=18.20.3"
"bun": ">=1.3.5"
},
"packageManager": "pnpm@10.15.1",
"workspaces": [
"packages/*"
],
"scripts": {
"check": "check && pnpm -r --sequential run check",
"test": "pnpm -r --sequential run test run",
"test:coverage": "pnpm -r --sequential run test:coverage",
"check": "check && bun run --workspaces check",
"test": "bun run --workspaces test run",
"test:coverage": "bun run --workspaces 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"
"docs:dev": "bun run --silent docs:gen && bun run --bun vitepress dev docs",
"docs:build": "bun run --silent docs:gen && bun run --bun vitepress build docs",
"docs:preview": "bun run --silent docs:gen && bun run --bun vitepress preview docs"
},
"devDependencies": {
"@aklinker1/buildc": "^1.1.5",
"@aklinker1/check": "^2.2.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.4.0",
"@commitlint/config-conventional": "^20.4.3",
"@commitlint/types": "^20.4.3",
"@types/bun": "^1.3.5",
"@types/semver": "^7.7.1",
"@vitest/coverage-v8": "^4.0.16",
"@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.2",
"lint-staged": "^16.2.7",
"cspell": "^9.7.0",
"feed": "^5.2.0",
"markdown-it-footnote": "^4.0.0",
"markdownlint-cli": "^0.45.0",
"nano-spawn": "^1.0.3",
"markdownlint-cli": "^0.48.0",
"nano-spawn": "^2.0.0",
"nano-staged": "^0.8.0",
"p-map": "^7.0.4",
"prettier": "^3.7.4",
"semver": "^7.7.3",
"pkg-pr-new": "^0.0.66",
"prettier": "^3.8.1",
"prettier-plugin-jsdoc": "^1.8.0",
"semver": "^7.7.4",
"simple-git-hooks": "^2.13.1",
"tinyglobby": "^0.2.15",
"tsdown": "^0.21.0",
"tsx": "4.21.0",
"typedoc": "^0.25.4",
"typedoc-plugin-frontmatter": "^1.3.1",
@@ -45,15 +49,21 @@
"typescript": "^5.9.3",
"vitepress": "^1.6.4",
"vitepress-knowledge": "^0.4.1",
"vitepress-plugin-group-icons": "^1.6.5",
"vitepress-plugin-group-icons": "^1.7.3",
"vitepress-plugin-llms": "^1.12.0",
"vitest-mock-extended": "^3.1.0",
"vue": "^3.5.25",
"vue": "^3.5.29",
"wxt": "workspace:*"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
"patchedDependencies": {
"markdown-it-footnote@4.0.0": "patches/markdown-it-footnote.patch",
"simple-git-hooks@2.13.1": "patches/simple-git-hooks@2.13.1.patch",
"pkg-pr-new@0.0.66": "patches/pkg-pr-new@0.0.66.patch"
},
"lint-staged": {
"simple-git-hooks": {
"pre-commit": "./node_modules/.bin/nano-staged"
},
"nano-staged": {
"*": "prettier --ignore-unknown --write"
}
}
+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,
});
+62 -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,34 +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<Record<string, any>>(key))[key] ??
(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>;
};
}
+30 -27
View File
@@ -1,14 +1,40 @@
{
"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": "bun run build && check",
"build": "buildc -- tsdown",
"prepack": "bun run build",
"postinstall": "buildc --deps-only -- wxt prepare",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'"
},
"dependencies": {
"@wxt-dev/browser": "workspace:^",
"@wxt-dev/is-background": "workspace:^",
"ua-parser-js": "^1.0.40"
},
"peerDependencies": {
"wxt": ">=0.20.0"
},
"devDependencies": {
"@aklinker1/buildc": "^1.1.7",
"@types/ua-parser-js": "^0.7.39",
"publint": "^0.3.18",
"tsdown": "^0.21.0",
"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 +61,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.2.0",
"@types/ua-parser-js": "^0.7.39",
"publint": "^0.3.16",
"typescript": "^5.9.3",
"unbuild": "^3.6.1",
"wxt": "workspace:*"
},
"dependencies": {
"@wxt-dev/browser": "workspace:^",
"ua-parser-js": "^1.0.40"
}
]
}
+19
View File
@@ -0,0 +1,19 @@
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',
},
deps: {
neverBundle: ['#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)
+1 -1
View File
@@ -16,7 +16,7 @@ Install the package:
npm i --save-dev @wxt-dev/auto-icons
pnpm i -D @wxt-dev/auto-icons
yarn add --dev @wxt-dev/auto-icons
bun i -D @wxt-dev/auto-icons
bun add -D @wxt-dev/auto-icons
```
Add the module to `wxt.config.ts`:
+13 -9
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,27 @@
"dist"
],
"scripts": {
"build": "buildc -- unbuild",
"check": "pnpm build && check"
"build": "buildc -- tsdown",
"check": "bun run build && check",
"test": "buildc --deps-only -- vitest",
"test:coverage": "bun run test run --coverage",
"prepack": "bun run build"
},
"peerDependencies": {
"wxt": ">=0.19.0"
},
"dependencies": {
"defu": "^6.1.4",
"fs-extra": "^11.3.2",
"sharp": "^0.34.5"
},
"devDependencies": {
"@aklinker1/check": "^2.2.0",
"oxlint": "^1.33.0",
"publint": "^0.3.16",
"@aklinker1/buildc": "^1.1.7",
"@types/fs-extra": "^11.0.4",
"oxlint": "^1.51.0",
"publint": "^0.3.18",
"tsdown": "^0.21.0",
"typescript": "^5.9.3",
"unbuild": "^3.6.1",
"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,
},
});
+8 -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.32",
"version": "0.1.40",
"type": "module",
"main": "src/index.mjs",
"types": "src/index.d.ts",
@@ -12,7 +12,9 @@
},
"scripts": {
"check": "check",
"gen": "tsx scripts/generate.ts"
"gen": "bun run scripts/generate.ts",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'"
},
"author": {
"name": "Aaron Klinker",
@@ -23,12 +25,11 @@
"src"
],
"devDependencies": {
"@types/chrome": "0.1.32",
"fs-extra": "^11.3.2",
"nano-spawn": "^1.0.3",
"tsx": "4.21.0",
"@types/chrome": "0.1.40",
"@types/node": "^20.0.0",
"nano-spawn": "^2.0.0",
"typescript": "^5.9.3",
"vitest": "^4.0.16"
"vitest": "^4.0.18"
},
"dependencies": {
"@types/filesystem": "*",
+17 -15
View File
@@ -1,24 +1,24 @@
import spawn from 'nano-spawn';
import fs from 'fs-extra';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve, sep } from 'node:path';
import { sep as posixSep } from 'node:path/posix';
import { readdir, mkdir } from 'node:fs/promises';
import { styleText } from 'node:util';
// Fetch latest version
console.log('Getting latest version of \x1b[36m@types/chrome\x1b[0m');
await spawn('pnpm', ['i', '--ignore-scripts', '-D', '@types/chrome@latest']);
console.log(`Getting latest version of ${styleText('cyan', '@types/chrome')}`);
await Bun.$`bun install --ignore-scripts -D @types/chrome@latest`;
// Generate new package.json
console.log('Generating new \x1b[36mpackage.json\x1b[0m');
console.log(`Generating new ${styleText('cyan', 'package.json')}`);
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 = await Bun.file(pkgJsonPath).json();
const pkgJsonTemplate = await Bun.file('templates/package.json').text();
const newPkgJson = JSON.parse(
pkgJsonTemplate.replaceAll('{{chromeTypesVersion}}', pkgJson.version),
);
@@ -27,15 +27,15 @@ newPkgJson.peerDependencies = pkgJson.peerDependencies;
newPkgJson.peerDependenciesMeta = pkgJson.peerDependenciesMeta;
const outPkgJsonPath = resolve('package.json');
await fs.writeJson(outPkgJsonPath, newPkgJson);
await spawn('pnpm', ['-w', 'prettier', '--write', outPkgJsonPath]);
await Bun.write(outPkgJsonPath, JSON.stringify(newPkgJson));
await Bun.$`bun run --cwd ../.. prettier --write "${outPkgJsonPath}"`;
// Generate declaration files
console.log('Generating declaration files');
const outDir = resolve('src/gen');
const declarationFileMapping = (
await fs.readdir(pkgDir, {
await readdir(pkgDir, {
recursive: true,
encoding: 'utf8',
})
@@ -50,17 +50,19 @@ const declarationFileMapping = (
}));
for (const { file, srcPath, destPath } of declarationFileMapping) {
const content = await fs.readFile(srcPath, 'utf8');
const content = await Bun.file(srcPath).text();
const transformedContent = transformFile(file, content);
const destDir = dirname(destPath);
await fs.mkdir(destDir, { recursive: true });
await fs.writeFile(destPath, transformedContent);
console.log(` \x1b[2m-\x1b[0m \x1b[36m${file}\x1b[0m`);
await mkdir(destDir, { recursive: true });
await Bun.write(destPath, transformedContent);
console.log(` ${styleText('dim', '-')} ${styleText('cyan', file)}`);
}
// Done!
console.log('\x1b[32m✔\x1b[0m Done in ' + performance.now().toFixed(0) + ' ms');
console.log(
`${styleText('green', '✔')} 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>();
});
});
});
+269 -98
View File
@@ -348,9 +348,10 @@ export namespace Browser {
export function setIcon(details: TabIconDetails, callback: () => void): void;
/**
* Sets the html document to be opened as a popup when the user clicks on the action's icon.
* Sets the HTML document to be opened as a popup when the user clicks on the action's icon.
*
* Can return its result via Promise.
* @since Chrome 96
*/
export function setPopup(details: PopupDetails): Promise<void>;
export function setPopup(details: PopupDetails, callback: () => void): void;
@@ -694,9 +695,16 @@ export namespace Browser {
/** @deprecated Bookmark write operations are no longer limited by Chrome. */
export const MAX_WRITE_OPERATIONS_PER_HOUR: 1000000;
/** @deprecated Bookmark write operations are no longer limited by Chrome. */
export const MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE: 1000000;
/**
* The `id` associated with the root level node.
* @since Chrome 145
*/
export const ROOT_NODE_ID = "0";
/**
* Creates a bookmark or folder under the specified parentId. If url is NULL or missing, it will be a folder.
*
@@ -1031,7 +1039,7 @@ export namespace Browser {
cookies?: boolean | undefined;
/**
* Stored passwords.
* @deprecated Support for password deletion through extensions has been removed. This data type will be ignored.
* @deprecated since Chrome 144. Support for password deletion through extensions has been removed. This data type will be ignored.
*/
passwords?: boolean | undefined;
/**
@@ -1127,7 +1135,7 @@ export namespace Browser {
* Clears the browser's stored passwords.
*
* Can return its result via Promise in Manifest V3 or later since Chrome 96.
* @deprecated Support for password deletion through extensions has been removed. This function has no effect.
* @deprecated since Chrome 144. Support for password deletion through extensions has been removed. This function has no effect.
*/
export function removePasswords(options: RemovalOptions): Promise<void>;
export function removePasswords(options: RemovalOptions, callback: () => void): void;
@@ -1160,6 +1168,7 @@ export namespace Browser {
* Clears websites' cache storage data.
*
* Can return its result via Promise in Manifest V3 or later since Chrome 96.
* @since Chrome 72
*/
export function removeCacheStorage(options: RemovalOptions): Promise<void>;
export function removeCacheStorage(options: RemovalOptions, callback: () => void): void;
@@ -2428,7 +2437,17 @@ export namespace Browser {
* Exactly one of `imageData` or `path` must be specified. Both are dictionaries mapping a number of pixels to an image representation. The image representation in `imageData` is an `ImageData` object; for example, from a `canvas` element, while the image representation in `path` is the path to an image file relative to the extension's manifest. If `scale` screen pixels fit into a device-independent pixel, the `scale * n` icon is used. If that scale is missing, another image is resized to the required size.
*/
export class SetIcon {
constructor(options?: { imageData?: ImageData | { [size: string]: ImageData } | undefined });
constructor(
options:
| {
imageData: ImageData | { [index: number]: ImageData };
path?: string | { [index: number]: string } | undefined;
}
| {
imageData?: ImageData | { [index: number]: ImageData } | undefined;
path: string | { [index: number]: string };
},
);
}
/** Provides the Declarative Event API consisting of {@link events.Event.addRules addRules}, {@link events.Event.removeRules removeRules}, and {@link events.Event.getRules getRules}. */
@@ -3325,7 +3344,7 @@ export namespace Browser {
/** Indicates the data type of the option. The requested data type must match the real data type of the underlying option. */
type: `${OptionType}`;
/** Indicates the value to set. Leave unset to request automatic setting for options that have `autoSettable` enabled. The data type supplied for `value` must match `type`. */
value?: string | number | boolean | number;
value?: string | number | boolean | number[];
}
/**
@@ -3555,6 +3574,8 @@ export namespace Browser {
/**
* Performs a document scan and returns a Promise that resolves with a {@link ScanResults} object. If a callback is passed to this function, the returned data is passed to it instead.
* @param options An object containing scan parameters.
*
* Can return its result via Promise in Manifest V3 or later since Chrome 96.
*/
export function scan(options: ScanOptions): Promise<ScanResults>;
export function scan(options: ScanOptions, callback: (result: ScanResults) => void): void;
@@ -3781,6 +3802,8 @@ export namespace Browser {
BLOCKED_SCAN_FAILED = "blockedScanFailed",
/** For use by the Secure Enterprise Browser extension. When required, Chrome will block the download to disc and download the file directly to Google Drive. */
FORCE_SAVE_TO_GDRIVE = "forceSaveToGdrive",
/** For use by the Secure Enterprise Browser extension. When required, Chrome will block the download to disc and download the file directly to OneDrive. */
FORCE_SAVE_TO_ONEDRIVE = "forceSaveToOnedrive",
}
export interface DownloadItem {
@@ -4067,14 +4090,17 @@ export namespace Browser {
id: string;
/**
* Implements the WebCrypto's SubtleCrypto interface. The cryptographic operations, including key generation, are hardware-backed.
* Only non-extractable keys can be generated. The supported key types are RSASSA-PKCS1-V1_5 and RSA-OAEP (on Chrome versions 134+) with `modulusLength` up to 2048 and ECDSA with `namedCurve` P-256. Each RSASSA-PKCS1-V1_5 and ECDSA key can be used for signing data at most once, unless the extension is allowlisted through the KeyPermissions policy, in which case the key can be used indefinitely. RSA-OAEP keys are supported since Chrome version 134 and can be used by extensions allowlisted through that same policy to unwrap other keys.
*
* Only non-extractable keys can be generated. The supported key types are RSASSA-PKCS1-V1_5 with `modulusLength` up to 2048 and ECDSA with `namedCurve` P-256. Each key can be used for signing data at most once, unless the extension is allowlisted by the KeyPermissions policy, in which case the key can be used indefinitely.
*
* Keys generated on a specific `Token` cannot be used with any other Tokens, nor can they be used with `window.crypto.subtle`. Equally, `Key` objects created with `window.crypto.subtle` cannot be used with this interface.
*/
subtleCrypto: SubtleCrypto;
/**
* Implements the WebCrypto's SubtleCrypto interface. The cryptographic operations, including key generation, are software-backed.
* Protection of the keys, and thus implementation of the non-extractable property, is done in software, so the keys are less protected than hardware-backed keys.
* Only non-extractable keys can be generated. The supported key types are RSASSA-PKCS1-V1_5 and RSA-OAEP (on Chrome versions 134+) with `modulusLength` up to 2048. Each RSASSA-PKCS1-V1_5 key can be used for signing data at most once, unless the extension is allowlisted through the KeyPermissions policy, in which case the key can be used indefinitely. RSA-OAEP keys are supported since Chrome version 134 and can be used by extensions allowlisted through that same policy to unwrap other keys.
* Implements the WebCrypto's SubtleCrypto interface. The cryptographic operations, including key generation, are software-backed. Protection of the keys, and thus implementation of the non-extractable property, is done in software, so the keys are less protected than hardware-backed keys.
*
* Only non-extractable keys can be generated. The only supported key type is RSASSA-PKCS1-V1_5 with `modulusLength` up to 2048. up to 2048. Each key can be used for signing data at most once, unless the extension is allowlisted through the KeyPermissions policy, in which case the key can be used indefinitely.
*
* Keys generated on a specific `Token` cannot be used with any other Tokens, nor can they be used with `window.crypto.subtle`. Equally, `Key` objects created with `window.crypto.subtle` cannot be used with this interface.
* @since Chrome 97
*/
@@ -4354,9 +4380,12 @@ export namespace Browser {
}
/**
* Retrieves the network details of the device's default network. If the user is not affiliated or the device is not connected to a network, runtime.lastError will be set with a failure reason.
* Retrieves the network details of the device's default network. If the user is not affiliated or the device is not connected to a network, {@link runtime.lastError} will be set with a failure reason.
* @param callback Called with the device's default network's NetworkDetails.
*
* Can return its result via Promise in Manifest V3 or later since Chrome 96.
*/
export function getNetworkDetails(): Promise<NetworkDetails>;
export function getNetworkDetails(callback: (networkDetails: NetworkDetails) => void): void;
}
@@ -6298,7 +6327,7 @@ export namespace Browser {
* This API is different from identity.getAccounts in two ways. The information returned is available offline, and it only applies to the primary account for the profile.
* @param details Profile options.
*
* Can return its result via Promise since Chrome 105.
* Can return its result via Promise since Chrome 106.
*/
export function getProfileUserInfo(details?: ProfileDetails): Promise<ProfileUserInfo>;
export function getProfileUserInfo(
@@ -6313,7 +6342,7 @@ export namespace Browser {
* If an access token is discovered to be invalid, it should be passed to removeCachedAuthToken to remove it from the cache. The app may then retrieve a fresh token with `getAuthToken`.
* @param details Token information.
*
* Can return its result via Promise since Chrome 105.
* Can return its result via Promise since Chrome 106.
*/
export function removeCachedAuthToken(details: InvalidTokenDetails): Promise<void>;
export function removeCachedAuthToken(details: InvalidTokenDetails, callback: () => void): void;
@@ -7901,7 +7930,7 @@ export namespace Browser {
export enum ClientCertificateType {
ECDSA_SIGN = "ecdsaSign",
RAS_SIGN = "rasSign",
RSA_SIGN = "rsaSign",
}
export interface SelectDetails {
@@ -8796,7 +8825,7 @@ export namespace Browser {
export type QueryInfo =
& {
/** String to query with the default search provider. */
text?: string | undefined;
text: string;
}
& (
| {
@@ -9237,36 +9266,37 @@ export namespace Browser {
export interface ManifestBase {
// Required
/** An integer specifying the version of the manifest file format that your extension uses. */
manifest_version: number;
/** A string that identifies the extension in the Chrome Web Store, the install dialog, and the user's Chrome Extensions page (`chrome://extensions`). The maximum length is 75 characters. */
name: string;
/** A string that identifies the extension's version number. */
version: string;
// Recommended
/** A string that defines the default language of an extension that supports multiple locales. Examples include "en" and "pt_BR". This key is required in localized extensions, and must not be used in extensions that aren't localized. */
default_locale?: string | undefined;
/** A string that describes the extension on both the Chrome Web Store and the user's extension management page. The maximum length is 132 characters. */
description?: string | undefined;
/** One or more icons that represent your extension. */
icons?: ManifestIcons | undefined;
// Optional
author?: {
email: string;
} | undefined;
background_page?: string | undefined;
/** @deprecated As of February 2024, the `author` key is no longer supported by Chrome or the Chrome Web Store. If present, it's silently ignored. */
author?: { email: string } | undefined;
/** Defines overrides for selected Chrome settings. */
chrome_settings_overrides?: {
homepage?: string | undefined;
search_provider?: SearchProvider | undefined;
startup_pages?: string[] | undefined;
} | undefined;
chrome_ui_overrides?: {
bookmarks_ui?: {
remove_bookmark_shortcut?: boolean | undefined;
remove_button?: boolean | undefined;
} | undefined;
} | undefined;
/** Defines overrides for default Chrome pages. */
chrome_url_overrides?: {
bookmarks?: string | undefined;
history?: string | undefined;
newtab?: string | undefined;
} | undefined;
/** Defines keyboard shortcuts within the extension. */
commands?: {
[name: string]: {
suggested_key?: {
@@ -9284,38 +9314,30 @@ export namespace Browser {
matches?: string[] | undefined;
permissions?: string[] | undefined;
} | undefined;
content_scripts?:
| Array<{
matches?: string[] | undefined;
exclude_matches?: string[] | undefined;
css?: string[] | undefined;
js?: string[] | undefined;
run_at?: string | undefined;
all_frames?: boolean | undefined;
match_about_blank?: boolean | undefined;
include_globs?: string[] | undefined;
exclude_globs?: string[] | undefined;
}>
| undefined;
converted_from_user_script?: boolean | undefined;
/** Specifies a value for the Cross-Origin-Embedder-Policy HTTP header, which configures embedding of cross-origin resources in an extension page. */
cross_origin_embedder_policy?: { value: string } | undefined;
/** Specifies a value for the Cross-Origin-Opener-Policy HTTP header, which lets you ensure that a top-level extension page doesn't share a browsing context group with cross-origin documents. */
cross_origin_opener_policy?: { value: string } | undefined;
current_locale?: string | undefined;
/** Defines static rules for the declarativeNetRequest API, which allows blocking and modifying of network requests. */
declarative_net_request?: { rule_resources?: declarativeNetRequest.Ruleset[] } | undefined;
/** Defines pages that use the DevTools APIs. */
devtools_page?: string | undefined;
event_rules?:
| Array<{
event?: string | undefined;
actions?:
| Array<{
type: string;
}>
| undefined;
actions?: Array<{ type: string }> | undefined;
conditions?: Browser.declarativeContent.PageStateMatcherProperties[] | undefined;
}>
| undefined;
/** Specifies what other pages and extensions can connect to your extensions. */
externally_connectable?: {
ids?: string[] | undefined;
matches?: string[] | undefined;
accepts_tls_channel_id?: boolean | undefined;
} | undefined;
/** Provides access to the fileBrowserHandler API, which lets extensions access the ChromeOS file browser. */
file_browser_handlers?:
| Array<{
id?: string | undefined;
@@ -9323,35 +9345,44 @@ export namespace Browser {
file_filters?: string[] | undefined;
}>
| undefined;
/** Allows access to the fileSystemProvider API, which lets extensions create file systems that ChromeOS can use. */
file_system_provider_capabilities?: {
/** Whether configuring via onConfigureRequested is supported. By default: false. */
configurable?: boolean | undefined;
/** Whether setting watchers and notifying about changes is supported. By default: false. */
watchable?: boolean | undefined;
/** Whether multiple (more than one) mounted file systems are supported. By default: false. */
multiple_mounts?: boolean | undefined;
source?: string | undefined;
/** Files app uses above information in order to render related UI elements appropriately. For example, if `configurable` is set to true, then a menu item for configuring volumes will be rendered. Similarly, if `multiple_mounts` is set to true, then Files app will allow to add more than one mount points from the UI. If `watchable` is false, then a refresh button will be rendered. Note, that if possible you should add support for watchers, so changes on the file system can be reflected immediately and automatically. */
source: "file" | "device" | "network";
} | undefined;
/** string specifying a URL for the extension's homepage. If this is undefined, the homepage defaults to the extension's Chrome Web Store page. This field is particularly useful if you host the extension on your own site. */
homepage_url?: string | undefined;
/** Allows resources to be imported into the extension. */
import?:
| Array<{
id: string;
minimum_version?: string | undefined;
}>
| undefined;
export?: {
whitelist?: string[] | undefined;
} | undefined;
incognito?: string | undefined;
/** Allows resources to be exported from the extension. */
export?: { allowlist?: string[] | undefined } | undefined;
/** Defines how the extension behaves in incognito mode. */
incognito?: "spanning" | "split" | "not_allowed" | undefined;
/** Allows the use of the Input Method Editor API. */
input_components?:
| Array<{
name?: string | undefined;
type?: string | undefined;
name: string;
id?: string | undefined;
description?: string | undefined;
language?: string[] | string | undefined;
layouts?: string[] | undefined;
indicator?: string | undefined;
layouts?: string[] | string | undefined;
input_view?: string | undefined;
options_page?: string | undefined;
}>
| undefined;
/** Specifies your extension's ID for various development use cases. */
key?: string | undefined;
/** Defines the oldest Chrome version that can install your extension. The value must be a substring of an existing Chrome browser version string, such as "107" or "107.0.5304.87". Users with versions of Chrome older than the minimum version see a "Not compatible" warning in the Chrome Web Store, and are unable to install your extension. If you add this to an existing extension, users whose Chrome version is older won't receive automatic updates to your extension. This includes business users in ephemeral mode. */
minimum_chrome_version?: string | undefined;
nacl_modules?:
| Array<{
@@ -9359,53 +9390,39 @@ export namespace Browser {
mime_type: string;
}>
| undefined;
/** Allows the use of an OAuth 2.0 security ID. The value of this key must be an object with "client_id" and "scopes" properties. */
oauth2?: {
client_id: string;
scopes?: string[] | undefined;
} | undefined;
offline_enabled?: boolean | undefined;
omnibox?: {
keyword: string;
} | undefined;
/** Allows the extension to register a keyword in Chrome's address bar. */
omnibox?: { keyword: string } | undefined;
/** Specifies a path to an options.html file for the extension to use as an options page. */
options_page?: string | undefined;
/** Specifies a path to an HTML file that lets a user change extension options from the Chrome Extensions page. */
options_ui?: {
page?: string | undefined;
chrome_style?: boolean | undefined;
open_in_tab?: boolean | undefined;
/** Path to the options page, relative to the extension's root. */
page: string;
/** Specify as `false` to declare an embedded options page. If `true`, the extension's options page will be opened in a new tab rather than embedded in `chrome://extensions`. */
open_in_tab: boolean;
} | undefined;
platforms?:
| Array<{
nacl_arch?: string | undefined;
sub_package_path: string;
}>
| undefined;
plugins?:
| Array<{
path: string;
}>
| undefined;
/** Lists technologies required to use the extension. */
requirements?: {
"3D"?: {
features?: string[] | undefined;
} | undefined;
plugins?: {
npapi?: boolean | undefined;
} | undefined;
"3D"?: { features?: string[] | undefined } | undefined;
/** @deprecated NPAPI Plugin support for extensions has been discontinued as of Chrome version 45 */
plugins?: { npapi?: boolean | undefined } | undefined;
} | undefined;
/** Defines a set of extension pages that don't have access to extension APIs or direct access to non-sandboxed pages. */
sandbox?: {
pages: string[];
content_security_policy?: string | undefined;
} | undefined;
/** A string containing a shortened version of the extension's name to be used when character space is limited. The maximum length is 12 characters. If this is undefined, a truncated version of the "name" key displays instead. */
short_name?: string | undefined;
spellcheck?: {
dictionary_language?: string | undefined;
dictionary_locale?: string | undefined;
dictionary_format?: string | undefined;
dictionary_path?: string | undefined;
} | undefined;
storage?: {
managed_schema: string;
} | undefined;
/** Declares a JSON schema for the managed storage area. */
storage?: { managed_schema: string } | undefined;
/** Registers the extension as a text to speech engine. */
tts_engine?: {
voices: Array<{
voice_name: string;
@@ -9414,13 +9431,14 @@ export namespace Browser {
event_types?: string[] | undefined;
}>;
} | undefined;
/** A string containing the URL of the extension's updates page. Use this key if you're hosting your extension outside the Chrome Web Store. */
update_url?: string | undefined;
/** A string describing the extension's version. Examples include "1.0 beta" and "build rc2". If this is unspecified, the "version" value displays on the extension management page instead. */
version_name?: string | undefined;
[key: string]: any;
}
export interface ManifestV2 extends ManifestBase {
// Required
manifest_version: 2;
// Pick one (or none)
@@ -9435,24 +9453,50 @@ export namespace Browser {
persistent?: boolean | undefined;
}
| undefined;
/** Specifies JavaScript or CSS files to be used when the user opens certain web pages. */
content_scripts?:
| Array<{
matches?: string[] | undefined;
exclude_matches?: string[] | undefined;
css?: string[] | undefined;
js?: string[] | undefined;
run_at?: string | undefined;
all_frames?: boolean | undefined;
match_about_blank?: boolean | undefined;
include_globs?: string[] | undefined;
exclude_globs?: string[] | undefined;
}>
| undefined;
/** Defines restrictions on the scripts, styles, and other resources an extension can use. */
content_security_policy?: string | undefined;
/** Declares optional permissions for your extension. */
optional_permissions?: (ManifestOptionalPermission | string)[] | undefined;
/** Enables use of particular extension APIs. */
permissions?: (ManifestPermission | string)[] | undefined;
platforms?:
| Array<{
nacl_arch?: string | undefined;
sub_package_path: string;
}>
| undefined;
/** Defines files within the extension that can be accessed by web pages or other extensions. */
web_accessible_resources?: string[] | undefined;
}
export interface ManifestV3 extends ManifestBase {
// Required
manifest_version: 3;
// Optional
/** Defines the appearance and behavior of the extension's icon in the Google Toolbar. */
action?: ManifestAction | undefined;
/** Specifies the JavaScript file containing the extension's service worker, which acts as an event handler. */
background?:
| {
service_worker: string;
type?: "module"; // If the service worker uses ES modules
type?: "module";
}
| undefined;
/** Specifies JavaScript or CSS files to be used when the user opens certain web pages. */
content_scripts?:
| Array<{
matches?: string[] | undefined;
@@ -9467,14 +9511,35 @@ export namespace Browser {
world?: "ISOLATED" | "MAIN" | undefined;
}>
| undefined;
/** Defines restrictions on the scripts, styles, and other resources an extension can use. */
content_security_policy?: {
extension_pages?: string;
sandbox?: string;
};
} | undefined;
/** Specifies file types for ChromeOS extensions to handle. */
file_handlers?:
| Array<{
/** Specifies an HTML file to show when a file is opened. The file must be within your extension. Processing the file, whether it's displayed or used in some other way, is done with JavaScript using appropriate web platform APIs. This code must be in a separate JavaScript file included via a `<script>` tag. */
action: string;
/** A user friendly description of the action. */
name: string;
/** The file types that can be processed by the page specified in "action". The items in the dictionary are a key/value pair where the key is a MIME type and the value is an array of file extensions. Only known MIME types are allowed for the key. Custom file types are allowed but the key for a custom type must be a known MIME type, and the mapping between the MIME type and the custom file type must be supported by the underlying operating system. */
accept: { [mime_type: string]: string[] };
/** Specifies whether multiple files should be opened in a single client or multiple clients. The default value is "single-client". */
launch_type?: "multiple-clients" | "single-client" | undefined;
}>
| undefined;
/** Lists the web pages your extension is allowed to interact with, defined using URL match patterns. User permission for these sites is requested at install time. */
host_permissions?: string[] | undefined;
/** Declares optional permissions for your extension. */
optional_permissions?: ManifestOptionalPermission[] | undefined;
/** Declares optional host permissions for your extension. */
optional_host_permissions?: string[] | undefined;
/** Enables use of particular extension APIs. */
permissions?: ManifestPermission[] | undefined;
/** Identifies an HTML file to display in a sidePanel. */
side_panel?: { default_path: string } | undefined;
/** Defines files within the extension that can be accessed by web pages or other extensions. */
web_accessible_resources?:
| Array<
& {
@@ -9683,7 +9748,7 @@ export namespace Browser {
/** Sent after onSuspend to indicate that the app won't be unloaded after all. */
export const onSuspendCanceled: events.Event<() => void>;
/** Fired when a message is sent from either an extension process (by {@link runtime.sendMessage}) or a content script (by {@link tabs.sendMessage}). */
/** Fired when a message is sent from either {@link runtime.sendMessage} or {@link tabs.sendMessage}. */
export const onMessage: events.Event<
(message: any, sender: MessageSender, sendResponse: (response?: any) => void) => void
>;
@@ -11047,6 +11112,11 @@ export namespace Browser {
height?: number | undefined;
/** The session ID used to uniquely identify a tab obtained from the {@link sessions} API. */
sessionId?: string | undefined;
/**
* The ID of the Split View that the tab belongs to.
* @since Chrome 140
*/
splitViewId?: number | undefined;
/**
* The ID of the group that the tab belongs to.
* @since Chrome 88
@@ -11118,6 +11188,12 @@ export namespace Browser {
*/
export const MAX_CAPTURE_VISIBLE_TAB_CALLS_PER_SECOND = 2;
/**
* An ID that represents the absence of a split tab.
* @since Chrome 140
*/
export const SPLIT_VIEW_ID_NONE: -1;
/**
* An ID that represents the absence of a browser tab.
* @since Chrome 46
@@ -11561,7 +11637,7 @@ export namespace Browser {
export function duplicate(tabId: number, callback: (tab?: Tab) => void): void;
/**
* Sends a single message to the content script(s) in the specified tab, with an optional callback to run when a response is sent back. The {@link runtime.onMessage} event is fired in each content script running in the specified tab for the current extension.
* Sends a single message to the content script(s) in the specified tab. The {@link runtime.onMessage} event is fired in each content script running in the specified tab for the current extension.
*
* Can return its result via Promise in Manifest V3 or later since Chrome 99.
*/
@@ -12121,7 +12197,7 @@ export namespace Browser {
* Called when the list of {@link TtsVoice} that would be returned by getVoices has changed.
* @since Chrome 124
*/
const onVoicesChanged: Browser.events.Event<() => void>;
export const onVoicesChanged: Browser.events.Event<() => void>;
}
////////////////////
@@ -13073,7 +13149,7 @@ export namespace Browser {
EXTRA_HEADERS = "extraHeaders",
}
/** @since Chrome 44 */
/** @since Chrome 79 */
export enum OnErrorOccurredOptions {
/** Specifies that headers can violate Cross-Origin Resource Sharing (CORS). */
EXTRA_HEADERS = "extraHeaders",
@@ -13087,6 +13163,10 @@ export namespace Browser {
EXTRA_HEADERS = "extraHeaders",
/** Specifies that the response headers should be included in the event. */
RESPONSE_HEADERS = "responseHeaders",
/** Specifies that the SecurityInfo should be included in the event. */
SECURITY_INFO = "securityInfo",
/** Specifies that the SecurityInfo with raw bytes of certificates should be included in the event. */
SECURITY_INFO_RAW_DER = "securityInfoRawDer",
}
/** @since Chrome 44 */
@@ -13147,6 +13227,23 @@ export namespace Browser {
OTHER = "other",
}
/** @since Chrome 144 */
export interface SecurityInfo {
/** A list of certificates */
certificates: {
/** Fingerprints of the certificate. */
fingerprint: {
/** sha256 fingerprint of the certificate. */
sha256: string;
};
/** Raw bytes of DER encoded server certificate */
rawDER?: ArrayBuffer;
}[];
/** State of the connection. One of secure, insecure, broken. */
state: string;
}
/** Contains data uploaded in a URL request. */
export interface UploadData {
/** An ArrayBuffer with a copy of the data. */
@@ -13164,7 +13261,7 @@ export namespace Browser {
* The UUID of the document making the request.
* @since Chrome 106
*/
documentId: string;
documentId?: string;
/**
* The lifecycle the document is in.
* @since Chrome 106
@@ -13244,14 +13341,14 @@ export namespace Browser {
extends SetPartial<WebRequestDetails, "documentId" | "documentLifecycle" | "frameType">
{
/** Contains the HTTP request body data. Only provided if extraInfoSpec contains 'requestBody'. */
requestBody: {
requestBody?: {
/** Errors when obtaining request body data. */
error?: string;
/** If the request method is POST and the body is a sequence of key-value pairs encoded in UTF8, encoded as either multipart/form-data, or application/x-www-form-urlencoded, this dictionary is present and for each key contains the list of all values for that key. If the data is of another media type, or if it is malformed, the dictionary is not present. An example value of this dictionary is {'key': \['value1', 'value2'\]}. */
formData?: { [key: string]: FormDataItem[] };
/** If the request method is PUT or POST, and the body is not already parsed in formData, then the unparsed request body elements are contained in this array. */
raw?: UploadData[];
} | undefined;
};
}
export interface OnBeforeSendHeadersDetails extends WebRequestDetails {
@@ -13284,6 +13381,11 @@ export namespace Browser {
export interface OnHeadersReceivedDetails extends WebRequestDetails {
/** The HTTP response headers that have been received with this response. */
responseHeaders?: HttpHeader[];
/**
* Information about the TLS/QUIC connection used for the underlying connection. Only provided if `securityInfo` is specified in the `extraInfoSpec` parameter.
* @since Chrome 144
*/
securityInfo?: SecurityInfo;
/** Standard HTTP status code returned by the server. */
statusCode: number;
/** HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers.*/
@@ -13954,6 +14056,32 @@ export namespace Browser {
*/
excludedTabIds?: number[] | undefined;
/**
* The rule will only match network requests when the associated top-level frame's domain matches one from the list of `topDomains`. If the list is omitted, the rule is applied to requests associated with all top-level frame domains. An empty list is not allowed.
*
* Notes:
* - Sub-domains like "a.example.com" are also allowed.
* - The entries must consist of only ascii characters.
* - Use punycode encoding for internationalized domains.
* - Sub-domains of the listed domains are also matched.
* - For requests with no associated top-level frame (e.g. ServiceWorker initiated requests, the request initiator's domain is considered instead.
* @since Chrome 141
*/
topDomains?: string[] | undefined;
/**
* The rule will not match network requests when the associated top-level frame's domain matches one from the list of `excludedTopDomains`. If the list is empty or omitted, no domains are excluded. This takes precedence over `topDomains`.
*
* Notes:
* - Sub-domains like "a.example.com" are also allowed.
* - The entries must consist of only ascii characters.
* - Use punycode encoding for internationalized domains.
* - Sub-domains of the listed domains are also excluded.
* - For requests with no associated top-level frame (e.g. ServiceWorker initiated requests, the request initiator's domain is considered instead.
* @since Chrome 141
*/
excludedTopDomains?: string[] | undefined;
/** Whether the `urlFilter` or `regexFilter` (whichever is specified) is case sensitive. Default is false. */
isUrlFilterCaseSensitive?: boolean | undefined;
@@ -14031,6 +14159,30 @@ export namespace Browser {
responseHeaders?: HeaderInfo[];
}
/** @since Chrome 145 */
export enum RuleConditionKeys {
URL_FILTER = "urlFilter",
REGEX_FILTER = "regexFilter",
IS_URL_FILTER_CASE_SENSITIVE = "isUrlFilterCaseSensitive",
INITIATOR_DOMAINS = "initiatorDomains",
EXCLUDED_INITIATOR_DOMAINS = "excludedInitiatorDomains",
REQUEST_DOMAINS = "requestDomains",
EXCLUDED_REQUEST_DOMAINS = "excludedRequestDomains",
TOP_DOMAINS = "topDomains",
EXCLUDED_TOP_DOMAINS = "excludedTopDomains",
DOMAINS = "domains",
EXCLUDED_DOMAINS = "excludedDomains",
RESOURCE_TYPES = "resourceTypes",
EXCLUDED_RESOURCE_TYPES = "excludedResourceTypes",
REQUEST_METHODS = "requestMethods",
EXCLUDED_REQUEST_METHODS = "excludedRequestMethods",
DOMAIN_TYPE = "domainType",
TAB_IDS = "tabIds",
EXCLUDED_TAB_IDS = "excludedTabIds",
RESPONSE_HEADERS = "responseHeaders",
EXCLUDED_RESPONSE_HEADERS = "excludedResponseHeaders",
}
export interface MatchedRule {
/** A matching rule's ID. */
ruleId: number;
@@ -14248,9 +14400,14 @@ export namespace Browser {
* The headers provided by a hypothetical response if the request does not get blocked or redirected before it is sent. Represented as an object which maps a header name to a list of string values. If not specified, the hypothetical response would return empty response headers, which can match rules which match on the non-existence of headers. E.g. `{"content-type": ["text/html; charset=utf-8", "multipart/form-data"]}`
* @since Chrome 129
*/
responseHeaders?: { [name: string]: unknown };
responseHeaders?: { [name: string]: string[] };
/** The ID of the tab in which the hypothetical request takes place. Does not need to correspond to a real tab ID. Default is -1, meaning that the request isn't related to a tab. */
tabId?: number;
/**
* The associated top-level frame URL (if any) for the request.
* @since Chrome 145
*/
topUrl?: string;
/** The resource type of the hypothetical request. */
type: `${ResourceType}`;
/** The URL of the hypothetical request. */
@@ -14517,6 +14674,14 @@ export namespace Browser {
default_path: string;
}
/**
* Closes the extension's side panel. This is a no-op if the panel is already closed.
* @param options Specifies the context in which to close the side panel.
* @since Chrome 141
*/
export function close(options: CloseOptions): Promise<void>;
export function close(options: CloseOptions, callback: () => void): void;
/**
* Returns the side panel's current layout.
* @since Chrome 140
@@ -14569,11 +14734,17 @@ export namespace Browser {
export function setPanelBehavior(behavior: PanelBehavior): Promise<void>;
export function setPanelBehavior(behavior: PanelBehavior, callback: () => void): void;
/**
* Fired when the extension's side panel is closed.
* @since Chrome 142
*/
export const onClosed: events.Event<(info: PanelClosedInfo) => void>;
/**
* Fired when the extension's side panel is opened.
* @since Chrome 141
*/
const onOpened: events.Event<(info: PanelOpenedInfo) => void>;
export const onOpened: events.Event<(info: PanelOpenedInfo) => void>;
}
////////////////////
@@ -14679,7 +14850,7 @@ export namespace Browser {
/** Specifies wildcard patterns for pages this user script will be injected into. */
includeGlobs?: string[] | undefined;
/** The list of ScriptSource objects defining sources of scripts to be injected into matching pages. This property must be specified for {@link register}, and when specified it must be a non-empty array.*/
js: ScriptSource[];
js?: ScriptSource[] | undefined;
/** Specifies which pages this user script will be injected into. See Match Patterns for more details on the syntax of these strings. This property must be specified for {@link register}. */
matches?: string[] | undefined;
/** Specifies when JavaScript files are injected into the web page. The preferred and default value is `document_idle` */
+7 -6
View File
@@ -12,7 +12,9 @@
},
"scripts": {
"check": "check",
"gen": "tsx scripts/generate.ts"
"gen": "bun run scripts/generate.ts",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'"
},
"author": {
"name": "Aaron Klinker",
@@ -24,10 +26,9 @@
],
"devDependencies": {
"@types/chrome": "{{chromeTypesVersion}}",
"fs-extra": "^11.3.1",
"nano-spawn": "^1.0.2",
"tsx": "4.20.5",
"typescript": "^5.9.2",
"vitest": "^4.0.16"
"@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)
+1 -1
View File
@@ -161,7 +161,7 @@ hello: Hello $1!
order: Thanks for ordering your $1
```
#### Escapting `$`
#### Escaping `$`
To escape the dollar sign, put another `$` in front of it:
+14 -12
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,17 @@
"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",
"test:coverage": "bun run test run --coverage",
"prepack": "bun run 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 +42,19 @@
}
},
"devDependencies": {
"@aklinker1/check": "^2.2.0",
"@aklinker1/buildc": "^1.1.7",
"@types/node": "^20.17.6",
"oxlint": "^1.33.0",
"publint": "^0.3.16",
"oxlint": "^1.51.0",
"publint": "^0.3.18",
"tsdown": "^0.21.0",
"typescript": "^5.9.3",
"unbuild": "^3.6.1",
"vitest": "^4.0.16",
"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": {
+1 -1
View File
@@ -43,7 +43,7 @@ describe('Utils', () => {
});
describe('getSubstitutionCount', () => {
it('should return the last substution present in the message', () => {
it('should return the last substitution present in the message', () => {
expect(getSubstitutionCount('I like $1, but I like $2 better')).toBe(2);
});
+31 -18
View File
@@ -16,12 +16,25 @@ export { SUPPORTED_LOCALES } from './supported-locales';
// TYPES
//
export type SimpleMessage = string;
export type PluralMessage = Record<'n' | number, string>;
export interface ChromeMessage {
message: string;
description?: string;
placeholders?: Record<string, { content: string; example?: string }>;
}
export type Message =
| SimpleMessage
| PluralMessage
| ChromeMessage
| Message[]
| { [key: string]: Message };
export type MessagesObject = Record<string, Message>;
export interface ParsedBaseMessage {
key: string[];
substitutions: number;
@@ -51,13 +64,14 @@ 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': {
message: '<browser.runtime.id>',
description:
"The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even unlocalized extensions can use this message.\nNote: You can't use this message in a manifest file.",
"The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even non-localized extensions can use this message.\nNote: You can't use this message in a manifest file.",
},
'@@ui_locale': {
message: '<browser.i18n.getUiLocale()>',
@@ -94,7 +108,7 @@ const EXT_FORMATS_MAP: Record<string, MessageFormat> = {
'.toml': 'TOML',
};
const PARSERS: Record<MessageFormat, (text: string) => any> = {
const PARSERS: Record<MessageFormat, (text: string) => MessagesObject> = {
YAML: parseYAML,
JSON5: parseJSON5,
TOML: parseTOML,
@@ -110,20 +124,17 @@ 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[]> {
const text = await readFile(file, 'utf8');
const ext = extname(file).toLowerCase();
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,10 +142,8 @@ export function parseMessagesText(
return parseMessagesObject(PARSERS[format](text));
}
/**
* Given the JS object form of a raw messages file, extract the messages.
*/
export function parseMessagesObject(object: any): ParsedMessage[] {
/** Given the JS object form of a raw messages file, extract the messages. */
export function parseMessagesObject(object: MessagesObject): ParsedMessage[] {
return _parseMessagesObject(
[],
{
@@ -147,7 +156,7 @@ export function parseMessagesObject(object: any): ParsedMessage[] {
function _parseMessagesObject(
path: string[],
object: any,
object: Message,
depth: number,
): ParsedMessage[] {
switch (typeof object) {
@@ -168,15 +177,17 @@ function _parseMessagesObject(
];
}
case 'object':
if ([null, undefined].includes(object)) {
if (object == null) {
throw new Error(
`Messages file should not contain \`${object}\` (found at "${path.join('.')}")`,
);
}
if (Array.isArray(object))
return object.flatMap((item, i) =>
_parseMessagesObject(path.concat(String(i)), item, depth + 1),
);
if (isPluralMessage(object)) {
const message = Object.values(object).join('|');
const substitutions = getSubstitutionCount(message);
@@ -189,6 +200,7 @@ function _parseMessagesObject(
},
];
}
if (depth === 1 && isChromeMessage(object)) {
const message = applyChromeMessagePlaceholders(object);
const substitutions = getSubstitutionCount(message);
@@ -201,6 +213,7 @@ function _parseMessagesObject(
},
];
}
return Object.entries(object).flatMap(([key, value]) =>
_parseMessagesObject(path.concat(key), value, depth + 1),
);
@@ -209,13 +222,13 @@ function _parseMessagesObject(
}
}
function isPluralMessage(object: any): object is Record<number | 'n', string> {
function isPluralMessage(object: Message): object is PluralMessage {
return Object.keys(object).every(
(key) => key === 'n' || isFinite(Number(key)),
);
}
function isChromeMessage(object: any): object is ChromeMessage {
function isChromeMessage(object: Message): object is ChromeMessage {
return Object.keys(object).every((key) =>
ALLOWED_CHROME_MESSAGE_KEYS.has(key),
);
@@ -227,7 +240,7 @@ function isChromeMessage(object: any): object is ChromeMessage {
export function generateTypeText(messages: ParsedMessage[]): string {
const renderMessageEntry = (message: ParsedMessage): string => {
// Use . for deep keys at runtime and types
// Use '.' for deep keys at runtime and types
const key = message.key.join('.');
const features = [
+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": "bun run build && check",
"test": "buildc --deps-only -- vitest",
"test:coverage": "bun run test run --coverage",
"prepack": "bun run 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' {
+16 -12
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": {
@@ -40,25 +40,29 @@
],
"scripts": {
"dev": "wxt",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"prepare": "buildc --deps-only -- wxt prepare"
"check": "bun run build && check",
"build": "buildc -- tsdown",
"postinstall": "buildc --deps-only -- wxt prepare",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'",
"prepack": "bun run 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.2.0",
"@types/react": "^19.2.7",
"@aklinker1/buildc": "^1.1.7",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"publint": "^0.3.16",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"publint": "^0.3.18",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"typescript": "^5.9.3",
"unbuild": "^3.6.1",
"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,
});
+11 -8
View File
@@ -22,7 +22,7 @@
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"import": {
@@ -40,9 +40,12 @@
],
"scripts": {
"dev": "wxt",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"prepare": "buildc --deps-only -- wxt prepare"
"check": "bun run build && check",
"build": "buildc -- tsdown",
"postinstall": "buildc --deps-only -- wxt prepare",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'",
"prepack": "bun run build"
},
"peerDependencies": {
"wxt": ">=0.19.16"
@@ -51,11 +54,11 @@
"vite-plugin-solid": "^2.11.10"
},
"devDependencies": {
"@aklinker1/check": "^2.2.0",
"publint": "^0.3.16",
"solid-js": "^1.9.10",
"@aklinker1/buildc": "^1.1.7",
"publint": "^0.3.18",
"solid-js": "^1.9.11",
"tsdown": "^0.21.0",
"typescript": "^5.9.3",
"unbuild": "^3.6.1",
"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',
},
]);
+21
View File
@@ -1,5 +1,26 @@
# Changelog
## v2.0.5
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.4...module-svelte-v2.0.5)
### 🩹 Fixes
- Update `@sveltejs/vite-plugin-svelte` version range to support Vite 8 ([48571f50](https://github.com/wxt-dev/wxt/commit/48571f50))
### 🏡 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))
- Add prepack script to all packages ([032f7931](https://github.com/wxt-dev/wxt/commit/032f7931))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v2.0.4
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.3...module-svelte-v2.0.4)
+11 -8
View File
@@ -18,11 +18,11 @@
},
"license": "MIT",
"funding": "https://github.com/sponsors/wxt-dev",
"version": "2.0.4",
"version": "2.0.5",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"import": {
@@ -39,21 +39,24 @@
"dist"
],
"scripts": {
"build": "buildc -- unbuild",
"check": "pnpm build && check"
"build": "buildc -- tsdown",
"check": "bun run build && check",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'",
"prepack": "bun run build"
},
"peerDependencies": {
"wxt": ">=0.18.6",
"svelte": ">=5"
},
"dependencies": {
"@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0"
"@sveltejs/vite-plugin-svelte": ">=4"
},
"devDependencies": {
"@aklinker1/check": "^2.2.0",
"publint": "^0.3.16",
"@aklinker1/buildc": "^1.1.7",
"publint": "^0.3.18",
"tsdown": "^0.21.0",
"typescript": "^5.9.3",
"unbuild": "^3.6.1",
"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',
},
]);
+9 -6
View File
@@ -22,7 +22,7 @@
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"types": "./dist/index.d.mts",
"exports": {
".": {
"import": {
@@ -39,8 +39,11 @@
"dist"
],
"scripts": {
"build": "buildc -- unbuild",
"check": "pnpm build && check"
"build": "buildc -- tsdown",
"check": "bun run build && check",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'",
"prepack": "bun run build"
},
"peerDependencies": {
"wxt": ">=0.19.16"
@@ -49,10 +52,10 @@
"@vitejs/plugin-vue": "^5.2.3 || ^6.0.0"
},
"devDependencies": {
"@aklinker1/check": "^2.2.0",
"publint": "^0.3.16",
"@aklinker1/buildc": "^1.1.7",
"publint": "^0.3.18",
"tsdown": "^0.21.0",
"typescript": "^5.9.3",
"unbuild": "^3.6.1",
"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',
},
]);
+3 -3
View File
@@ -1,8 +1,8 @@
//
// USAGE:
// pnpm dev
// pnpm dev firefox-nightly
// pnpm dev <target>
// bun run dev
// bun run dev firefox-nightly
// bun run dev <target>
//
import { run } from './src';
+11 -12
View File
@@ -21,24 +21,23 @@
"license": "MIT",
"funding": "https://github.com/sponsors/wxt-dev",
"scripts": {
"check": "pnpm build && check",
"check": "bun run build && check",
"test": "buildc --deps-only -- vitest",
"dev": "tsx --trace-warnings dev.ts",
"build": "buildc -- unbuild",
"prepublishOnly": "pnpm build"
"test:coverage": "bun run test run --coverage",
"dev": "bun run dev.ts",
"build": "buildc -- tsdown",
"prepack": "bun run build"
},
"dependencies": {},
"devDependencies": {
"@aklinker1/check": "^2.2.0",
"oxlint": "^1.33.0",
"publint": "^0.3.16",
"tsx": "4.21.0",
"@aklinker1/buildc": "^1.1.7",
"oxlint": "^1.51.0",
"publint": "^0.3.18",
"tsdown": "^0.21.0",
"typescript": "^5.9.3",
"unbuild": "^3.6.1",
"vitest": "^4.0.16"
"vitest": "^4.0.18"
},
"main": "dist/index.mjs",
"types": "dist/index.d.ts",
"types": "dist/index.d.mts",
"exports": {
".": {
"types": "./dist/index.d.mts",
+8 -1
View File
@@ -2,6 +2,7 @@ export type BrowserPlatform = 'windows' | 'mac' | 'linux';
export type KnownTarget =
| 'arc'
| 'brave'
| 'chromium'
| 'chrome'
| 'chrome-beta'
@@ -28,6 +29,11 @@ export const KNOWN_BROWSER_PATHS: Record<
linux: [],
windows: [],
},
brave: {
mac: ['Applications/Brave Browser.app/Contents/MacOS/Brave Browser'],
linux: [],
windows: [],
},
chromium: {
mac: [],
linux: [
@@ -124,7 +130,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: [

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