Compare commits

..

343 Commits

Author SHA1 Message Date
rxliuli ebe1f9da7e docs: Remove dangling "Remote Code" sidebar link (#2532)
✨ pkg.pr.new / Publish Test Packages (push) Cancelled after 0s
📝 Publish Docs / Publish Docs (push) Cancelled after 0s
🛡️ Validate / Checks (push) Cancelled after 0s
🛡️ Validate / Builds (push) Cancelled after 0s
🛡️ Validate / Build Demo (push) Cancelled after 0s
🛡️ Validate / Tests (Windows) (push) Cancelled after 0s
🛡️ Validate / Tests (Linux) (push) Cancelled after 0s
🛡️ Validate / Template (react) (push) Cancelled after 0s
🛡️ Validate / Template (solid) (push) Cancelled after 0s
🛡️ Validate / Template (svelte) (push) Cancelled after 0s
🛡️ Validate / Template (vanilla) (push) Cancelled after 0s
🛡️ Validate / Template (vue) (push) Cancelled after 0s
2026-07-30 16:38:23 +05:30
Aaron acc4cdec07 chore(deps): Replace async-mutex with superlock (#2529) 2026-07-29 09:12:36 -05:00
Aaron 8570ce29b0 chore(deps): Replace prompts with @topcli/prompts (#2528) 2026-07-28 17:38:45 -05:00
Aaron f7cce3e269 chore(deps): Replace open with tiny-open (#2527) 2026-07-28 17:10:22 -05:00
Aaron 315912ebf3 chore(deps): Drop import-meta-resolve (#2526) 2026-07-28 17:10:12 -05:00
Aaron 574c3bd90a docs: Update bundle size for 0.21 2026-07-28 13:57:00 -05:00
Aaron 8dc8fa6f51 chore: Add support for publish-browser-extension v6 2026-07-28 13:57:00 -05:00
github-actions[bot] 1e841ebad7 chore(release): wxt v0.21.2
📼 VHS / Create VHS (push) Cancelled after 0s
2026-07-28 13:01:06 +00:00
Aaron 925e97d4fc docs: Update package size links 2026-07-28 07:56:20 -05:00
Aaron 859d654611 fix: Remove web-ext-run, it's unused now 2026-07-28 07:46:01 -05:00
Aaron 466ae8a33c docs: Fix heading level 2026-07-26 18:23:51 -05:00
Aaron d1ab2d923d docs: Fix typos and wording of upgrade guide 2026-07-26 18:22:54 -05:00
github-actions[bot] 718b063ed7 chore(release): wxt v0.21.1
📼 VHS / Create VHS (push) Cancelled after 0s
2026-07-26 23:13:13 +00:00
Aaron 87a96d4661 chore: Add vite to template projects (#2517) 2026-07-26 18:02:38 -05:00
Aaron d8a86e7633 docs: Add section about install footprint 2026-07-26 18:02:38 -05:00
Aaron c3eaef6fb4 docs: Finalize migration guide for wxt v0.21 (#2516) 2026-07-26 18:02:38 -05:00
Aaron d1a956342d ci: Use @aklinker1/zero-changelog for release notes (#2515) 2026-07-26 18:02:38 -05:00
Aaron 0fdac0d4e4 feat(Firefox): List files in sources zip (#2513) 2026-07-26 18:02:38 -05:00
Aaron 7965ab0592 chore: Add type to type imports 2026-07-26 18:02:38 -05:00
Aaron 54d10af1fd fix(TS)!: Update .wxt/tsconfig.json compiler options, require TS >=5.4 (#2512)
BREAKING CHANGE: Updates `.wxt/tsconfig.json` defaults to latest recommended settings for vite projects and requires Typescript 5.4 or later. See #2512 for details on what compiler options were changed.

Co-authored-by: cookesan <cookesan@users.noreply.github.com>
2026-07-26 18:02:38 -05:00
Aaron 7dbe18ba0c fix!: Update content and unlisted script globalName default to false (#2511)
BREAKING CHANGE: If your script needs to return a value, like for `browser.scripting.executeScript`, set `globalName: true` to maintain the old behavior.
2026-07-26 18:02:38 -05:00
Aaron d4f78b623e Fix markdownlint 2026-07-26 18:02:38 -05:00
Aaron 7d6caad9b5 fix: Loosen version of @webext-core/isolated-element
Allows reverting to v1 if styles break
2026-07-26 18:02:38 -05:00
Aaron 0b352f6eff fix: Remove console.log 2026-07-26 18:02:38 -05:00
Aaron 885f8f0b76 docs: Cleanup v0.21 migration guide 2026-07-26 18:02:38 -05:00
ТΞNSΛI eae835fa80 feat!: new template variable {{versionName}} and change of the behaviour of {{version}} (#1612)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-07-26 18:02:38 -05:00
Kai Gritun 782a5b85f1 fix(zip)!: make includeSources/excludeSources follow standard allowlist behavior (#2114)
Co-authored-by: Eli <88557639+lishaduck@users.noreply.github.com>
Co-authored-by: ТΞNSΛI <tensai@gmx.net>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
Co-authored-by: Kai Gritun <kai@kaigritun.com>
2026-07-26 18:02:38 -05:00
Aaron 961cfa5f2a chore(deps)!: Upgrade major dependencies (#2508) 2026-07-26 18:02:38 -05:00
fkatsuhiro ada0c86877 chore!: Remove deprecated APIs (#2488)
Co-authored-by: Gliches <nishumurmu017@gmail.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-07-26 18:02:38 -05:00
Gliches 13151509b6 fix!: Remove url: import feature (#2456)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-07-26 18:02:38 -05:00
Aaron b7bdb11a15 chore!: Upgrade @webext-core/isolated-element v1 → v3 (#2098)
Co-authored-by: Eli <88557639+lishaduck@users.noreply.github.com>
Co-authored-by: ТΞNSΛI <tensai@gmx.net>
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-07-26 18:02:38 -05:00
Aaron ab679ba413 fix: Handle edge case where eslint version cannot be found 2026-07-26 18:02:38 -05:00
Patryk Kuniczak f69b594401 feat!: Change default behavior of getUnimportEslintOptions true flag (#2296) 2026-07-26 18:02:38 -05:00
Aaron be3af14f9d chore(deps): Remove esbuild dependency (#2506) 2026-07-26 18:02:38 -05:00
Aaron 3679bc772e chore(deps): Upgrade to @types/node 22 2026-07-26 18:02:38 -05:00
Aaron 9da43b6b75 Fix formatting 2026-07-26 18:02:38 -05:00
Aaron 008f5a66c9 chore!: Bump minimum supported Node version to >=22 2026-07-26 18:02:38 -05:00
Aaron cffacffe34 fix!: Make web-ext a peer dependency (#2079)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-07-26 18:02:38 -05:00
ТΞNSΛI 3bd7aea9ee feat!: add {{modeSuffix}} to zip templates and update defaults (#1623)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-07-26 18:02:38 -05:00
Eli 4b26d872ec refactor!: move to module-runner (#1946) 2026-07-26 18:02:38 -05:00
Eli c10d09fbdb refactor: make vite a default peer (#1945) 2026-07-26 18:02:38 -05:00
fkatsuhiro 9698f2fdd9 fix: Deprecated useAppConfig in favour of getAppConfig (#2487)
Co-authored-by: Gliches <nishumurmu017@gmail.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-07-26 11:44:52 -05:00
github-actions[bot] 299b0add80 docs: Update wxt init GIF 2026-07-26 15:04:13 +00:00
Aaron ec3d3196a9 ci: Run VHS workflow on PRs that touch relevant files 2026-07-26 10:01:54 -05:00
github-actions[bot] 13be7c5bfd docs: Update wxt init GIF 2026-07-26 15:01:02 +00:00
Aaron 575240aaf1 ci: Allow the VHS action to commit to main 2026-07-26 09:58:27 -05:00
Thribhuvan b124c82ed4 docs: use consola's fancy reporter in init demo GIF (#2485) 2026-07-26 09:50:44 -05:00
Gliches 6c5a1b0d2e fix: add user exclusion for GitHub Actions in PR closed workflow (#2509) 2026-07-26 09:32:34 -05:00
Gliches eb93fa4535 fix: docs sidebar header, background color issue (#2504) 2026-07-26 10:37:37 +02:00
nnthanh01061999 5bd36e90bf docs: add MindSnap to showcase (#2499) 2026-07-21 17:57:14 +00:00
Hugo Villanueva Jimenez 172d37193e Docs: add ArcPort extensions entries to showcase (#2495) 2026-07-17 18:31:58 +00:00
Eason d92d5a4892 Add Adaptive Tab Bar Colour to extension showcase (#2490) 2026-07-11 17:42:31 +00:00
aklinker1 40c6c19802 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-07-02 01:05:26 +00:00
Aaron 653fccc8eb docs: Cleanup the "extension APIs" page (#2473) 2026-07-01 21:02:23 +05:30
Nazım Gediz Aydındoğmuş 95657939a8 docs: Added "Teams Chat Exporter" Edge Addons (edgeId) to extension showcase (#2472) 2026-06-30 22:12:08 +00:00
Arthur a578dbf633 feat(analytics): add posthog as an analytics provider (#2414)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-06-30 21:20:04 +00:00
github-actions[bot] f14ec35889 chore(release): @wxt-dev/i18n v0.2.6
📼 VHS / Create VHS (push) Cancelled after 0s
2026-06-30 21:02:33 +00:00
github-actions[bot] cb851b1738 chore(release): @wxt-dev/analytics v0.5.5
📼 VHS / Create VHS (push) Cancelled after 0s
2026-06-30 20:56:54 +00:00
ZerGo0 82825a5b35 fix: file reloading mechanism and add profile watch ignores (#2112)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-06-30 20:12:01 +00:00
Aaron a63f2d2a33 chore(dev-deps): Upgrade commitlint to v21 (#2471) 2026-06-30 15:05:18 -05:00
SmAsHeD d3983a603f docs: Revert #2380 - locationWatcher (#2416) 2026-06-30 19:55:39 +00:00
dependabot[bot] 9304319f55 chore(deps-dev): bump markdownlint-cli from 0.48.0 to 0.49.0 (#2466)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-30 14:45:38 -05:00
dependabot[bot] e778449377 chore(deps-dev): bump feed from 5.2.0 to 5.2.1 (#2469)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-30 14:43:51 -05:00
Patryk Kuniczak 3ecafbe0f3 docs: Update BlogLayout.vue useData to correctly type (#2369) 2026-06-30 08:02:05 -05:00
dependabot[bot] 9ab345b6dc chore(deps-dev): bump semver from 7.7.4 to 7.8.0 (#2377)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-30 07:59:47 -05:00
dependabot[bot] ade1a6b670 chore(deps): bump codecov/codecov-action from 6.0.0 to 7.0.0 (#2407)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-30 07:58:15 -05:00
silverfish2525 af840d7169 feat: export getEntrypointBundlePath from wxt (#2463) 2026-06-30 12:55:09 +00:00
Gliches f2ced3f0e4 chore: Update code owners (#2462) 2026-06-30 07:51:19 -05:00
Muhammed Mustafa AKŞAM eb2e151520 fix(analytics): change method return types from void to Promise<void> (#2409)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-06-29 22:13:08 +00:00
dependabot[bot] b235c8591e chore(deps): bump actions/checkout from 6.0.2 to 7.0.0 (#2424)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-29 22:08:20 +00:00
dependabot[bot] 4c11f00176 chore(deps): bump actions/setup-go from 6.4.0 to 6.5.0 (#2432)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-29 21:58:26 +00:00
Gliches f75d1e1afc fix: opening the browser issue in windows. (#2436) 2026-06-29 21:05:04 +00:00
dependabot[bot] 4f419da1dc chore(deps): bump stefanzweifel/git-auto-commit-action from 7.1.0 to 7.2.0 (#2460)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-29 15:56:01 -05:00
Aaron d9c25ac4ce ci: Fix VHS action (#2459)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: ded-furby <190979964+ded-furby@users.noreply.github.com>
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-06-29 20:52:49 +00:00
T 8dfedf2a5a feat: add watch options config (#2439) 2026-06-29 15:32:46 +00:00
ded-furby 19f94e1e6a docs: prioritize Bun in package manager options (#2425)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-06-29 10:31:47 -05:00
T fc269ed78d chore: skip package manager tests when binaries are missing (#2441)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-06-29 14:44:35 +00:00
Aaron cfc633202b feat: Add prepare:tsconfig hook for updating .wxt/tsconfig.json file (#2450) 2026-06-29 09:42:19 -05:00
Aaron 6f1790deef fix(init): Allow entering an empty directory for project path (#2458) 2026-06-29 09:17:30 -05:00
T 331ce14170 fix: avoid deprecated Rollup asset name access (#2453) 2026-06-29 13:43:29 +00:00
Peter Wong a4c6cd1cd0 docs: Added "NiceTab" Edge Addons (edgeId) to extension showcase (#2455)
Co-authored-by: wangpeng618 <wangpeng618@jd.com>
2026-06-29 13:42:40 +00:00
T cb0e336812 fix: handle spread content script configs (#2448) 2026-06-29 08:37:59 -05:00
Shubhrajit Sadhukhan ba7c9cef60 docs: Update extension-showcase.yml (#2457) 2026-06-29 08:35:24 -05:00
dependabot[bot] 70b30ae431 chore(deps): bump pnpm/action-setup from 6.0.7 to 6.0.9 (#2418)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-29 13:31:21 +00:00
Patryk Kuniczak 12c267a5d1 docs: Fix undefined issue for ExampleSearch.vue (#2370) 2026-06-29 08:27:59 -05:00
Suvesh Moza 38625a8cd3 ci: use latest pnpm in validate workflow (#2390) 2026-06-29 08:25:47 -05:00
Aaron 700cd8c3e8 docs: Don't underline the comma when hovering over showcase item store links (#2454) 2026-06-29 01:29:06 -05:00
T c272fbe0ae docs: show Edge Add-ons in showcase (#2452)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-06-29 06:24:10 +00:00
Kral f40005da36 docs: document PerBrowser options for entrypoints (#2304) (#2403)
Co-authored-by: dashitongzhi <dashitongzhi@users.noreply.github.com>
Co-authored-by: ded-furby <ded-furby@users.noreply.github.com>
2026-06-28 20:51:14 +00:00
Gliches 34afac0165 chore: updating the alignment of the blue text in auto-mount-ui. (#2435) 2026-06-28 20:25:34 +00:00
Vaughn Bosu 7cbe9a477b feat(analytics): add Moderok analytics provider (#2393) 2026-06-28 15:16:31 -05:00
T 081d472176 feat: Add named substitutions to i18n (#2440) 2026-06-28 19:13:11 +00:00
Aaron d7f8ebec39 docs: Use bright green for example hover and link colors 2026-06-28 13:54:08 -05:00
Patryk Kuniczak bde65e2066 docs: Make vitepress components css files more readable and simply (#2272) 2026-06-28 13:53:25 -05:00
BlazerYoo 4896cd208e docs: Added package manager tab group synchronization across the entire installation page (#2421)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-06-27 19:03:26 +00:00
李超源 42a5b3cd67 Update extension-showcase.yml (#2433) 2026-06-25 19:26:27 +00:00
LUIDevo d60fd2516f fix: wxt clean removes .wxt/ directory at project root (#2415) 2026-06-24 09:09:13 -05:00
ded-furby 6b2c27cf45 docs: move Bun tabs to first position in installation (#2428) 2026-06-23 18:53:01 +00:00
github-actions[bot] 1a3f4f64e5 chore(release): wxt v0.20.27
📼 VHS / Create VHS (push) Cancelled after 0s
2026-06-23 18:08:15 +00:00
Patryk Kuniczak 492cbb4644 fix: Rename ManifestPermissions to ManifestPermission of types.ts in wxt package (#2430) 2026-06-23 12:57:50 -05:00
aklinker1 c8e93b15db fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-06-21 01:16:16 +00:00
RenovZ e3f4fe3e13 docs: added anylang translator to showcase (#2426) 2026-06-19 20:01:03 +00:00
Nicholas Yoannou 8cb973cf3c docs: Added Hayami to showcase (#2422) 2026-06-18 17:39:17 +00:00
Avi 22d1a72752 docs: added 4 extensions to the homepage (#2419) 2026-06-16 19:32:40 +00:00
Peter Wong 8f90331229 docs: Added NiceTab firefoxslug to extension showcase (#2417)
Co-authored-by: wangpeng618 <wangpeng618@jd.com>
2026-06-16 19:01:39 +00:00
ChamikaUluwatta 9a638449d5 fix: correct broken link at react router in createhashrouter (#2411) 2026-06-09 18:00:21 +00:00
Muhammed Mustafa AKŞAM 9ce9b714f6 docs: Added "LinkedIn Games Solver" to showcase (#2410) 2026-06-09 14:35:31 +00:00
aklinker1 22b7aba035 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-06-06 01:04:14 +00:00
Slovakian 4cbcf282f7 docs: Added "WageSlav3" to showcase (#2405)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-06-05 20:56:03 +00:00
Oliver Benns d8b1ddfb59 docs: Added Property Track to showcase (#2404) 2026-06-05 09:08:58 +00:00
David Nguyen 7905cb379b chore: Add Google Cloud Skills Boost - Helper extension to showcase (#2401) 2026-06-02 14:35:05 +00:00
Esteban Vera 1b2463d52e "docs: Added "Link Later" to showcase" (#2392) 2026-05-22 15:09:42 +00:00
Saksham Kushwaha b567f9957a Add firefoxSlug for Monochromate extension (#2389) 2026-05-20 14:17:28 +00:00
Sean Kenneth Doherty c2fb8c786d docs: self-host Netlify footer badge (#2386) 2026-05-18 06:08:24 +00:00
Muhammad Umer Farooq 4320d8e1fa docs: clarify SPA navigation setup with locationWatcher.run() (#2380)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-05-16 17:37:56 -07:00
Muzammil 344d14dafc docs: Added "Sound Booster & Equalizer" to showcase (#2381) 2026-05-15 16:21:59 +02:00
Aaron 3cb894c4e5 chore: Add benchmarks for i18n vs vanilla usage (#2372) 2026-05-14 06:41:42 -07:00
Aniket lodh dbbe14ddd6 docs: add warning about rem unit inheritance in Shadow Root UI (#2330)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-12 18:19:51 +00:00
Aaron 9ee07f431a chore: Fix lockfile 2026-05-12 07:34:44 -05:00
dependabot[bot] d517e9a0b9 chore(deps): bump tinyglobby from 0.2.15 to 0.2.16 (#2366)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 07:27:17 -05:00
dependabot[bot] d9adbb6f09 chore(deps): bump pnpm/action-setup from 6.0.6 to 6.0.7 (#2362)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 07:26:48 -05:00
dependabot[bot] 6b77277cea chore(deps): bump filesize from 11.0.15 to 11.0.17 (#2363)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 07:26:30 -05:00
dependabot[bot] bad24392fc chore(deps): bump c12 from 3.3.3 to 3.3.4 (#2364)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-12 07:26:13 -05:00
George Forse f24e62129b docs: Add Telsia extension to Showcase (#2368) 2026-05-12 07:13:58 -05:00
Aaron f38cb855ea ci: Don't error PR when author assignment fails 2026-05-12 07:12:44 -05:00
dependabot[bot] 891a0d50a6 chore(deps-dev): bump @vitest/coverage-v8 from 4.1.5 to 4.1.6 (#2367)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 07:09:42 -05:00
Shubhrajit Sadhukhan cfb384e618 feat: Add warning for missing Firefox extension ID in manifest (#2293)
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-12 12:08:38 +00:00
github-actions[bot] 6f14aa1c71 chore(release): wxt v0.20.26
📼 VHS / Create VHS (push) Cancelled after 0s
2026-05-11 12:35:14 +00:00
Jonathan Viney f771e6a122 feat: Add content script noScriptStartedPostMessage option (#2265)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-11 12:25:14 +00:00
Aaron a0a23947b6 chore(deps): Upgrade vitest and related deps (#2358) 2026-05-10 12:39:07 -05:00
rxliuli 7b6f1dc88b fix(manifest): exclude open_in_tab from options_ui for Safari (#2311)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-10 17:32:54 +00:00
Aaron 16f9e1705f chore: Update codeowners
I  was not an owner of the docs directory, you have to add yourself to every one you care about, the last matching rule wins, it doesn't combine them
2026-05-10 12:19:36 -05:00
CrawlerCode 40e28b702d docs: Add "Redmine Time Tracking" to extension showcase (#2312)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-10 12:16:27 -05:00
Thomas Howlett 75f0b843f8 docs: update Safari publishing instructions to match Apple docs (#2314)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-10 12:12:46 -05:00
Aaron 8793385325 chore: Use catalog: for dev dependencies (#2357) 2026-05-10 11:56:11 -05:00
Thomas Howlett 4f86143853 fix: avoid errors when files are removed during build (#2343)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-10 16:19:52 +00:00
dependabot[bot] 498f74b21b chore(deps-dev): bump oxlint from 1.59.0 to 1.63.0 (#2356)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-10 11:16:05 -05:00
dependabot[bot] 300798eb11 chore(deps-dev): bump vitepress-plugin-llms from 1.12.0 to 1.12.2 (#2352)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-10 11:06:04 -05:00
Aaron d482fe6832 ci: Group dependabot PRs 2026-05-10 11:05:46 -05:00
dependabot[bot] 955bb20914 chore(deps-dev): bump unocss from 66.6.0 to 66.6.8 (#2353)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-10 10:59:12 -05:00
dependabot[bot] 22339d261b chore(deps-dev): bump nano-staged from 0.8.0 to 1.0.2 (#2355)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-10 10:58:57 -05:00
dependabot[bot] 5d89a40c6d chore(deps-dev): bump vue from 3.5.32 to 3.5.34 (#2350)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-10 10:54:50 -05:00
Aaron c1fe602c8b ci: Tell dependabot to use bun 2026-05-10 10:49:21 -05:00
dependabot[bot] bea5e0da1e chore(deps): bump actions/github-script from 8.0.0 to 9.0.0 (#2321)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-10 10:46:47 -05:00
Brian Zhou 7907e56a31 fix: bump publish-browser-extension to v4.0.5, to resolve Chrome Web Store submission issue (#2331) 2026-05-10 10:37:17 -05:00
Aaron 4e43e6a315 chore: Upgrade pkg.pr.new and remove patch (#2349) 2026-05-10 10:36:08 -05:00
dependabot[bot] 78ef08390e chore(deps): bump docker/login-action from 4.0.0 to 4.1.0 (#2323)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-10 10:35:37 -05:00
dependabot[bot] b5235ed4f0 chore(deps-dev): bump typescript from 5.9.3 to 6.0.3 (#2325)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-10 10:33:47 -05:00
Aaron 868a567ebd ci: Run dependabot daily 2026-05-10 10:22:58 -05:00
dependabot[bot] 0797041643 chore(deps): bump actions/setup-node from 5.0.0 to 6.4.0 (#2322)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-10 10:17:51 -05:00
dependabot[bot] 60b22be29d chore(deps): bump pnpm/action-setup from 5.0.0 to 6.0.6 (#2324)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-10 10:09:06 -05:00
Aaron 1a817e1367 ci: Fix failing tests by pinning PNPM version 2026-05-10 09:59:07 -05:00
Patryk Kuniczak 2be3044f6a chore: Add me to CODEOWNERS for docs directory (#2346) 2026-05-10 09:55:31 -05:00
Aaron 59f6f4b926 ci: Update self-hosted docs setup 2026-05-10 09:54:09 -05:00
Thomas Howlett 3d2d36954e chore: add missing tsdown dependencies (#2316) 2026-05-10 09:02:36 -05:00
Nadeem Siddique e44cb3950f Added Headstart to showcase (#2345) 2026-05-10 09:00:21 -05:00
Thomas Howlett 06682a6edc chore: remove ts-expect-error that are no longer needed (#2344) 2026-05-10 08:56:57 -05:00
Soaib Haque e6e513a34b docs: Added "QIE Wallet" to showcase (#2341)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-10 08:54:56 -05:00
Arthur 13dd3742a8 docs: added modrinth extras and pi-hole in one to showcase (#2337)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-05-10 08:54:00 -05:00
Luis Ortiz c27278ecfb docs: Add Mimik extension to the list of extensions (#2319) 2026-05-10 08:52:56 -05:00
Patryk Kuniczak 2f68c6e344 docs: fix horizontal scrollbar displaying on landing page (#2329) 2026-05-10 08:44:39 -05:00
aklinker1 a7976d2e63 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-05-08 00:57:25 +00:00
laplace-anon c15167ca86 fix: Modify command to support variadic positional args (#2317) 2026-04-30 22:27:12 +00:00
Aaron e75e534882 chore: Keep lockfile in sync when generating @wxt-dev/browser (#2310) 2026-04-26 22:26:24 -05:00
Aaron b21ec34a65 chore: Upgrade bun to 1.3.13 (#2309) 2026-04-26 22:11:04 -05:00
Aaron e45b1fb8df chore: Move createFileReloader into it's own file (#2307) 2026-04-26 21:46:00 -05:00
Aaron 0b3c3ab39d ci: Remove unnecessary --frozen-lockfile 2026-04-26 21:34:10 -05:00
Aaron 8b530bc1c9 fix: Use manifestVersion from CLI during manifest generation (#2306) 2026-04-26 20:58:58 -05:00
Aaron a2212f7a89 chore: Update lockfile 2026-04-26 20:53:11 -05:00
kairi e613b54d94 feat: add default_state option to popup actions (#2010)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-04-26 17:01:59 -05:00
Patryk Kuniczak d9c140114a docs: Remove unused code (#2275) 2026-04-26 16:02:22 -05:00
Aaron 9b59f38c0d chore: More JSDoc fixes 2026-04-26 15:21:19 -05:00
Aaron 2a8ec0d786 docs: Fix JSDoc example formatting 2026-04-26 15:18:26 -05:00
Patryk Kuniczak e29fad16ea docs: Add types for components (#2273) 2026-04-26 14:50:52 -05:00
Patryk Kuniczak 50d223a30a ci: use --frozen-lockfile in action.yml for bun (#2300) 2026-04-26 14:47:55 -05:00
Thomas Howlett 1dbdf4ea9e docs: correct entrypoints icon example code (#2302) 2026-04-25 19:14:46 +00:00
aklinker1 09b5a19577 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-04-19 00:52:20 +00:00
Aaron b5ea72e214 fix: Use obug for debug logs in runner (#2281) 2026-04-18 09:35:32 -05:00
Aaron 57e3748d79 docs(wxt-modules): Add logging examples/best practices 2026-04-18 09:33:48 -05:00
github-actions[bot] 155626fbed chore(release): wxt v0.20.25
📼 VHS / Create VHS (push) Cancelled after 0s
2026-04-18 14:24:37 +00:00
Aaron e802aa211f docs: Update permissions section with per-browser warning (#2284) 2026-04-18 09:08:09 -05:00
Aaron 54fe8c6687 fix: Port change in dev server on reload (#2283) 2026-04-18 08:42:04 -05:00
aklinker1 5da6e4ff75 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-04-18 00:46:17 +00:00
Aaron 4090bb100e chore: Simplify tests (#2279) 2026-04-17 12:01:15 -05:00
Aaron 54ed99d91d chore: Update browser fallbacks (#2280) 2026-04-17 12:00:19 -05:00
Aaron 3db3eac62a chore: Increase PNPM test timeout 2026-04-17 11:55:51 -05:00
Patryk Kuniczak 0315882fa4 chore: Add new types for i18n (#2121) 2026-04-17 14:38:22 +00:00
github-actions[bot] 9757e2634f chore(release): wxt v0.20.24
📼 VHS / Create VHS (push) Cancelled after 0s
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) Cancelled after 0s
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) Cancelled after 0s
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) Cancelled after 0s
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) Cancelled after 0s
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) Cancelled after 0s
2026-03-17 13:21:45 +00:00
Aaron 64b686713b chore(deps): Add vite-node 6 support 2026-03-17 08:16:33 -05:00
Joseph Hu 9c6d3e37d4 docs: Add Extension Rank Checker to the list of extensions (#2193)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-03-17 08:00:23 -05:00
FJRG2007 ツ 37abe9db39 docs: Add Dymo extension ID to UsingWxtSection.vue (#2187)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-03-17 12:43:08 +00:00
Yue Yang 9ebef4a268 chore: remove useDefineForClassFields from svelte/tsconfig.json (#2190) 2026-03-17 07:37:38 -05:00
Hampus Tågerud e60f7c7e77 fix: unlisted script return values broken with Vite 8 sourcemaps (#2197) 2026-03-17 12:35:22 +00:00
aklinker1 36aadd0604 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-03-17 00:39:30 +00:00
github-actions[bot] e4598a5e37 chore(release): @wxt-dev/module-react v1.2.2
📼 VHS / Create VHS (push) Cancelled after 0s
2026-03-14 23:48:01 +00:00
github-actions[bot] db5c22809c chore(release): wxt v0.20.19
📼 VHS / Create VHS (push) Cancelled after 0s
2026-03-14 23:43:04 +00:00
Aaron c61fa8f858 fix: Add @vitejs/plugin-react v6 support 2026-03-14 18:38:33 -05:00
Florian Metz 5fe4681620 chore: replace fs-extra with node:fs/promises (#2174)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-03-06 09:29:49 -06:00
Aaron 80d384361b chore: Update nano-staged precommit command 2026-03-06 09:17:22 -06:00
dagangtj a1e72c0749 chore: replace lint-staged with nano-staged (#2179)
Co-authored-by: Dean <dagangtj@gmail.com>
2026-03-06 15:13:41 +00:00
Aaron e3a80b8247 ci: Re-enable react template validation 2026-03-06 08:54:08 -06:00
github-actions[bot] c0013acadc chore(release): @wxt-dev/module-react v1.2.1
📼 VHS / Create VHS (push) Cancelled after 0s
2026-03-06 14:51:43 +00:00
Aaron f472c53da7 ci: Temporarily disable react template validation 2026-03-06 08:47:58 -06:00
Aaron 032f7931e8 chore: Add prepack script to all packages 2026-03-06 08:43:51 -06:00
github-actions[bot] 57a12c1711 chore(release): @wxt-dev/module-react v1.2.0
📼 VHS / Create VHS (push) Cancelled after 0s
2026-03-06 14:24:19 +00:00
Aaron d13ca3983f chore(deps): Upgdate fs-extra in browser package 2026-03-06 08:19:54 -06:00
aklinker1 9769ce4ac9 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-03-06 00:44:23 +00:00
Aaron 55dc263dc9 fix: List eslint 10 as supported 2026-03-05 10:08:02 -06:00
Aaron e56ae23747 chore(deps): Upgrade deps (#2175) 2026-03-05 10:00:41 -06:00
Aaron bfd4af5d69 feat: Vite 8 support 2026-03-05 08:59:58 -06:00
Patryk Kuniczak 9673604878 perf: Use filter to improve plugin performance with rolldown (#1787)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-03-05 08:55:36 -06:00
Florian Metz ae55aecb0d chore: replace fast-glob and ora with lighter alternatives (#2173) 2026-03-04 14:26:11 -06:00
Martin Broder a14d0b4f2f feat: add vitePluginsBefore option (#2170) 2026-03-04 11:39:00 -06:00
Aaron 301e4a17da chore: Add prettier-plugin-jsdoc to project (#2171) 2026-03-03 08:43:04 -06:00
etoome 19cebbf673 docs: Added "isTrust" to the homepage (#2161) 2026-02-27 00:49:06 +00:00
Kuba ef73c07dfe docs: Added "Scrape Similar" to the homepage (#2158) 2026-02-23 13:57:08 +00:00
Aaron ca5ad7e51b chore: Update codeowners 2026-02-22 18:56:12 -06:00
github-actions[bot] 5dab944abc chore(release): wxt v0.20.18
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-22 23:45:06 +00:00
Aaron adaae6e1a4 fix: Fix submit command (#2157) 2026-02-22 17:29:53 -06:00
Aaron 4524cbdf76 ci: Fix VHS workflow 2026-02-22 17:29:53 -06:00
github-actions[bot] 1171cf52f0 chore(release): @wxt-dev/analytics v0.5.4
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-22 17:29:53 -06:00
Aaron c5978b1072 fix: Continue using useAppConfig to support older versions of WXT 2026-02-22 17:29:53 -06:00
Aaron e5ef9367ed ci: Fix VHS action (#2155) 2026-02-22 17:29:53 -06:00
github-actions[bot] 0ca908a6cc chore(release): @wxt-dev/analytics v0.5.3
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-22 17:29:53 -06:00
github-actions[bot] e20c012eb3 chore(release): @wxt-dev/storage v1.2.8
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-22 17:29:53 -06:00
github-actions[bot] e88df26d7c chore(release): @wxt-dev/auto-icons v1.1.1
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-22 17:29:53 -06:00
github-actions[bot] 9491e6845e chore(release): @wxt-dev/i18n v0.2.5
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-22 17:29:53 -06:00
Aaron e4433c4212 ci: Fix scripts/list_unreleased_commits.sh (#2153) 2026-02-22 17:29:53 -06:00
Jonathan Viney c6fdc5a5d5 feat: Add globalName entrypoint option. (#2017) 2026-02-22 17:29:53 -06:00
Aaron 68dd3fc027 docs: Update section in SECURITY.md around reporting vulnerabilities (#2147) 2026-02-22 17:29:53 -06:00
omerfardemir cd7aec6752 fix: auto icons override default icons (#1616)
Co-authored-by: Florian Metz <me@timeraa.dev>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-22 17:29:53 -06:00
Smit b80556498d fix: Improve background script detection logic for analytics package (#1808)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-22 17:29:53 -06:00
Aaron 38530cdb39 feat: New @wxt-dev/is-background package (#2152)
Co-authored-by: Smit <sm17p@users.noreply.github.com>
2026-02-22 17:29:53 -06:00
Tam Dang 078fd5990e fix(types): include CSS entrypoints in PublicPath generation (#2150)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-22 17:29:53 -06:00
Aaron b4be5feaa6 ci: Fix auto-labeling workflow for forks 2026-02-22 17:29:53 -06:00
Aaron d982f97bd8 feat(modules): Add support for augumenting entrypoint options (#2149) 2026-02-22 17:29:53 -06:00
Aaron 653608c98c docs: Add deep outline to modules page to show recipes 2026-02-22 17:29:53 -06:00
Aaron d63be2d6f3 ci: Create workflow for labeling and adding authors to PRs (#2148) 2026-02-22 17:29:53 -06:00
Nick Doan 4500ca577f fix(types): Add type safety to browser.runtime.executeScript files option (#2142)
Co-authored-by: nickbar01234 <nickbar01234gmail.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-22 17:29:53 -06:00
Aaron cc935daec0 fix: Allow userId option to return undefined 2026-02-22 17:29:53 -06:00
Patryk Kuniczak 51ff46ec58 chore: Created new types, instead of any for analytics (#2119) 2026-02-22 17:29:53 -06:00
Patryk Kuniczak 206b7731cf chore: Remove @ts-expect-error from manifest.test.ts and fix typo (#2123) 2026-02-22 17:29:53 -06:00
Patryk Kuniczak d16f59d33d chore: Simplify imports in wxt/e2e (#2122) 2026-02-22 17:29:53 -06:00
Patryk Kuniczak a5ec046346 chore: Rename zipdir to zipDir (#2126) 2026-02-22 17:29:49 -06:00
Patryk Kuniczak 51dd803115 chore: Remove unnecessary @ts-ignore of builders/vite index.ts (#2127) 2026-02-21 10:47:39 -06:00
Patryk Kuniczak 6e39424a30 chore: Make type for __ENTRYPOINT__ of vitest.globalSetup.ts (#2128) 2026-02-21 10:46:36 -06:00
Anthony Ciccarello 6ca84782aa fix: enable mv3 dev mode with firefox 147 (#2135) 2026-02-21 16:45:04 +00:00
Henrik Wenz ee1ba6369e docs: Add wxt-local-analytics to community resources (#2141) 2026-02-21 16:41:13 +00:00
Aaron f35d70e6fe fix: Correctly update version metadata when setting a value for the first time (#2139)
Co-authored-by: willow <42willow@pm.me>
Co-authored-by: absdjfh <absdjfh@users.noreply.github.com>
2026-02-21 10:39:21 -06:00
sheng zhang 430a3acfeb fix: Add getAppConfig as an alias to useAppConfig (#2144)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-21 15:38:42 +00:00
aklinker1 395c5841f9 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2026-02-21 00:39:02 +00:00
Oleksii 173161ca4d feat: use Navigation API for location change detection with polling fallback (#2136) 2026-02-19 01:21:48 +00:00
Patryk Kuniczak a1a8e14368 feat: Create tests for all func of network.ts (#2132) 2026-02-18 19:14:36 -06:00
qixing-jk 2379806c61 docs: add All API Hub to showcase extensions (#2137) 2026-02-19 00:58:20 +00:00
Muzammil f604208a8b docs: Added "Glossy New Tab" to the homepage (#2133) 2026-02-15 03:48:59 +00:00
Patryk Kuniczak cf31fbec5d chore: add missing async to network.ts (#2107) 2026-02-13 12:18:37 +00:00
github-actions[bot] 279be8deb7 chore(release): @wxt-dev/storage v1.2.7
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-13 00:01:40 +00:00
github-actions[bot] 7366d8aab7 chore(release): wxt v0.20.17
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-12 22:35:05 +00:00
Aaron a7ab409d48 fix: Update left-over CJS require of publish-browser-extension to ESM (#2120) 2026-02-12 16:30:47 -06:00
Aaron 9dea0cf3ae chore: Cleanup type tests 2026-02-12 15:53:41 -06:00
Patryk Kuniczak 50d7c1ec80 chore: Change browser tests to fix deprecated warning (#2117)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-12 21:35:27 +00:00
github-actions[bot] f32cdafdf6 chore(release): @wxt-dev/analytics v0.5.2
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-12 21:08:03 +00:00
Honwhy Wang 2e2272d07a fix: Allow custom API URL in Google Analytics 4 provider options (#1653)
Co-authored-by: Patryk Kuniczak <p.kuniczak@gmail.com>
2026-02-12 13:34:05 -06:00
github-actions[bot] 27ffff7e0a chore(release): wxt v0.20.16
📼 VHS / Create VHS (push) Cancelled after 0s
2026-02-12 19:16:33 +00:00
Aaron 305cebe0d0 fix: Revert "rename name to names prop of rollup to fix deprecated warning (#2106)"
This reverts commit 9470d2b696.
2026-02-12 13:11:36 -06:00
Aaron 4602a068f8 Update contributor in changelog 2026-02-12 13:06:06 -06:00
Aaron 3a947d72c6 chore: Include doc updates in wxt changelog 2026-02-12 13:03:58 -06:00
Aaron 1940e3530f chore: Speed up E2E tests by skipping pnpm install when possible (#2113) 2026-02-11 12:24:36 -06:00
320 changed files with 16040 additions and 15175 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
+4 -1
View File
@@ -1,5 +1,5 @@
# Set default
* @aklinker1 @Timeraa
* @aklinker1
# Secure Directories
/.github/ @aklinker1
@@ -7,3 +7,6 @@
# Creator of specific wxt modules
/packages/auto-icons/ @Timeraa
/packages/unocss/ @Timeraa
# Other Directories
/docs @aklinker1 @PatrykKuniczak @nishu-murmu
+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 }}
+12 -7
View File
@@ -1,15 +1,20 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: npm
- package-ecosystem: bun
directory: /
schedule:
interval: 'monthly'
interval: 'daily'
groups:
production-updates:
applies-to: version-updates
dependency-type: production
update-types: [minor, patch]
dev-updates:
applies-to: version-updates
dependency-type: development
update-types: [minor, patch]
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'monthly'
interval: 'daily'
+115
View File
@@ -0,0 +1,115 @@
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@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
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@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
try {
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 }}');
} catch (err) {
console.warn("Error assigning author:", err);
}
- name: Sync Labels
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- 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: bunx pkg-pr-new publish --compact --bun './packages/*'
- name: DEBUG
if: always()
run: ls -lhA
working-directory: packages/analytics
+15 -1
View File
@@ -15,8 +15,22 @@ 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
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
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 != 'nishu-murmu' &&
github.event.pull_request.user.login != 'dependabot[bot]' &&
github.event.pull_request.user.login != 'copilot-swe-agent[bot]'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
comment: Thanks for helping make WXT better!
with:
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Only fetch the config file from the repository
sparse-checkout-cone-mode: false
+4 -4
View File
@@ -23,13 +23,13 @@ jobs:
contents: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Login to Docker Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
@@ -37,11 +37,11 @@ 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
run: docker push ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
- name: Deploy
run: curl -X POST -i ${{ secrets.UPDATE_DOCS_WEBHOOK }}
run: 'curl -X POST -i -H "X-API-Key: ${{ secrets.DOCS_WEBHOOK_API_KEY }}" https://${{ secrets.DOCS_WEBHOOK_DOMAIN }}/api/webhook/stacks/${{ secrets.DOCS_WEBHOOK_STACK_ID }}'
+52 -19
View File
@@ -10,6 +10,7 @@ on:
- analytics
- auto-icons
- i18n
- is-background
- module-react
- module-solid
- module-svelte
@@ -19,6 +20,13 @@ on:
- unocss
- webextension-polyfill
- wxt
bump:
description: Blank (auto), a version (1.4.1), or major/minor/patch
type: string
dryRun:
description: Do a dry-run and don't publish anything
type: boolean
default: false
permissions:
contents: read
@@ -28,18 +36,23 @@ jobs:
name: Validate
uses: './.github/workflows/validate.yml'
secrets: inherit
if: ${{ inputs.dryRun != true }}
publish:
name: Publish
name: Publish ${{ case(inputs.dryRun, ' (dry-run)', '') }}
runs-on: ubuntu-24.04
permissions:
contents: write # Push version changes
id-token: write # OIDC for NPM publishing
needs:
- validate
# Allow dry runs to skip validation but still run this job
if: |
always() &&
(needs.validate.result == 'success' || needs.validate.result == 'skipped')
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
ssh-key: ${{ secrets.DEPLOY_KEY }} # https://github.com/sbellone/release-workflow-example
@@ -51,22 +64,42 @@ jobs:
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
git config --global push.followTags true
- name: Bump and Tag
run: |
pnpm tsx scripts/bump-package-version.ts ${{ inputs.package }}
git push
git push --tags
- name: Upgrade NPM for Provence
run: sudo npm i -g npm@latest
- name: Publish to NPM
working-directory: packages/${{ inputs.package }}
run: |
pnpm pack
sudo npm i -g npm@latest
/usr/local/bin/npm publish *.tgz
- name: Create GitHub release
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Bump and Release
uses: aklinker1/zero-changelog/actions/release@b6fcca17cebfc45f996b80fe5926da924d8441f8 # 0.2.1
with:
bump: ${{ inputs.bump }}
path: packages/${{ inputs.package }}
# Include documentation updates in WXT's releases
additionalDirs: |-
${{ case(
inputs.package == 'wxt', 'docs',
''
) }}
# Use package name in commit message
commitTemplate: |-
${{ case(
inputs.package == 'wxt', 'chore(release): wxt v{{version}}',
'chore(release): @wxt-dev/{{dirname}} v{{version}}'
) }}
# Use package name in release name
releaseNameTemplate: |-
${{ case(
inputs.package == 'wxt', 'wxt v{{version}}',
'@wxt-dev/{{dirname}} v{{version}}'
) }}
# Update lockfile with new versions since it includes the package versions
preCommitCommands: |-
bun i
publishCommands: |-
bun pm pack
npm publish *.tgz
dryRun: ${{ inputs.dryRun }}
dryRunPublishCommands: |-
bun pm pack
npm publish *.tgz --dry-run
githubToken: ${{ secrets.GITHUB_TOKEN }}
latestRelease: ${{ inputs.package == 'wxt' }}
-43
View File
@@ -1,43 +0,0 @@
name: 🔄 Sync Releases
on:
workflow_dispatch:
inputs:
package:
description: Package to sync
default: wxt
type: choice
options:
- analytics
- auto-icons
- i18n
- module-react
- module-solid
- module-svelte
- module-vue
- runner
- storage
- webextension-polyfill
- wxt
permissions:
contents: read
jobs:
sync:
name: Sync Releases
runs-on: ubuntu-22.04
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
with:
installArgs: --ignore-scripts
- name: Sync Releases
run: pnpm tsx 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Setup Bun
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
- name: Setup PNPM
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
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@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
@@ -104,13 +106,13 @@ jobs:
- vue
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- 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 }}
+27 -14
View File
@@ -2,7 +2,12 @@ name: 📼 VHS
on:
push:
paths:
- 'docs/tapes/*.tape'
- docs/tapes/*.tape
- .github/workflows/vhs.yml
pull_request:
paths:
- docs/tapes/*.tape
- .github/workflows/vhs.yml
workflow_dispatch:
permissions:
@@ -11,35 +16,43 @@ 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ssh-key: ${{ secrets.DEPLOY_KEY }}
- name: Setup
uses: ./.github/actions/setup
with:
install: false
# This prevents pnpm dlx from downloading WXT in the video
- name: Pre-install WXT
run: |
pnpm store add wxt@latest
pnpm dlx wxt@latest --version
- name: Setup Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16
with:
go-version: '1.25.1'
# This prevents bunx from downloading WXT in the video, and it uses the checked-out version
- name: Build WXT
working-directory: packages/wxt
run: bun run build
- 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@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.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.13
nodejs 24.14.1
+51 -35
View File
@@ -15,7 +15,7 @@ WXT is two things:
The long term goal of WXT is provide an opinionated build tool that keeps WXT projects standard, while providing light-weight runtime utils that simplify a lot of the boilerplate/overhead when setting up a new extension.
I also want to provide a way for developers to use either one of those two things independently, and not require them to use both. This is why all of WXT's runtime utils are shipped as their own NPM packages, most of them not bundled inside the core `wxt` package. If you just want to use the packages, they're availalbe, and if you just want to use WXT's built tool, you don't have to import any of WXT's utilities, you can use your own.
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.
@@ -48,16 +48,12 @@ To make a breaking change:
## 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
@@ -65,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.
@@ -122,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).
@@ -131,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
@@ -147,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"
}
```
@@ -175,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
@@ -219,4 +235,4 @@ Anyone is welcome to submit a blog post on <https://wxt.dev/blog>!
If you're interested in becoming a maintainer, send an email to Aaron at <aaronklinker1@gmail.com> with your github username saying you're interested. The process is very informal, I will add you quickly if you've contributed code or answered questions and helped out the community!
Maintainers don't have to just write code - they can manage issues, answer questsions, review PRs, organize and prioritize work - there's lots of ways for you to help out.
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.
+41
View File
@@ -47,3 +47,44 @@ Here's an example of how to ask for a reproduction: <https://github.com/wxt-dev/
## 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.
-1
View File
@@ -53,7 +53,6 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
- 📦 [Module system](https://wxt.dev/guide/essentials/wxt-modules.html#overview) for reusing code between extensions
- 🖍️ Quickly bootstrap a new project
- 📏 Bundle analysis
- ⬇️ Download and bundle remote URL imports
## Sponsors
+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
+3089
View File
File diff suppressed because it is too large Load Diff
+128
View File
@@ -0,0 +1,128 @@
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
- packages/*/stats.html
words:
- Aabid
- aabidk
- aklinker
- automount
- avenir
- bidi
- blocklists
- buildc
- bunx
- cachable
- cacjs
- charmbracelet
- chromedriver
- Cira
- comctx
- confbox
- configfile
- consola
- consolas
- Cpath
- crbug
- cros
- 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
- moderok
- mktemp
- mycompany
- nacl
- nanospinner
- natsu
- newtab
- njsproj
- nodebuffer
- Noto
- ntvs
- nypm
- obug
- ohash
- oklab
- oxlint
- pbbf
- personaltoolbar
- portaling
- posthog
- prebundled
- prefs
- proxified
- pWXLO
- repost
- rxliuli
- sandboxed
- scule
- Segoe
- sidepanel
- sidepanels
- solidjs
- styl
- subdependency
- superlock
- tabstrip
- teleporting
- topcli
- treemap
- triaging
- umami
- ungh
- unimport
- unocss
- unrun
- unstub
- unwatch
- USSS
- vitepress
- webext
- webextension
- webextrc
- xcrun
- xlink
+29 -34
View File
@@ -3,8 +3,9 @@ import { computed } from 'vue';
// @ts-expect-error: Vitepress data-loader magic, this import is correct
import { data } from '../loaders/blog.data';
import BlogPostPreview from './BlogPostPreview.vue';
import { Post } from '../utils/types';
const posts = computed(() =>
const posts = computed<Post[]>(() =>
data
.map((post) => ({
...post,
@@ -17,10 +18,8 @@ const posts = computed(() =>
<template>
<div class="container">
<div>
<div class="vp-doc">
<h1>Blog</h1>
</div>
<div class="inner-container">
<h1>Blog</h1>
<ul>
<BlogPostPreview v-for="post of posts" :key="post.url" :post />
@@ -34,37 +33,33 @@ const posts = computed(() =>
display: flex;
flex-direction: column;
align-items: center;
}
.container > div {
padding: 32px;
max-width: 900px;
width: 100%;
min-width: 0;
}
h1 {
padding-bottom: 16px;
}
.inner-container {
padding: 32px;
max-width: 900px;
width: 100%;
ul {
display: flex;
flex-direction: column;
list-style: none;
}
ul,
li {
padding: 0;
margin: 0;
}
h1 {
font-size: 32px;
font-weight: 600;
margin-top: 10px;
padding-bottom: 16px;
}
ul li {
padding-top: 16px;
margin-top: 16px;
border-top: 1px solid var(--vp-c-default);
}
ul li:last-child {
padding-bottom: 16px;
margin-bottom: 16px;
border-bottom: 1px solid var(--vp-c-default);
ul {
display: flex;
flex-direction: column;
li {
padding-top: 16px;
margin-top: 16px;
&:last-child {
padding-bottom: 16px;
margin-bottom: 16px;
}
}
}
}
}
</style>
+51 -48
View File
@@ -1,23 +1,31 @@
<script lang="ts" setup>
import { Ref } from 'vue';
import useBlogDate from '../composables/useBlogDate';
import { useData } from 'vitepress';
import { Content, useData } from 'vitepress';
import { PostFrontmatter } from '../utils/types';
const { frontmatter } = useData() as unknown as {
frontmatter: Ref<PostFrontmatter>;
};
const { frontmatter } = useData();
const date = useBlogDate(() => frontmatter.value.date);
</script>
<template>
<div class="vp-doc">
<main class="container-content">
<h1 v-html="$frontmatter.title" />
<main class="container">
<h1 v-html="frontmatter.title" />
<p class="meta-row">
<a
class="author"
v-for="author of $frontmatter.authors"
v-for="author of frontmatter.authors"
:key="author.github"
:href="`https://github.com/${author.github}`"
class="author"
>
<img :src="`https://github.com/${author.github}.png?size=96`" />
<img
:src="`https://github.com/${author.github}.png?size=96`"
alt="Author's avatar"
/>
<span>{{ author.name }}</span>
</a>
<span>&bull;</span>
@@ -29,48 +37,43 @@ const date = useBlogDate(() => frontmatter.value.date);
</template>
<style scoped>
vp-doc {
.vp-doc {
display: flex;
}
main {
max-width: 1080px;
padding: 32px;
margin: auto;
}
@media (min-width: 768px) {
main {
padding: 64px;
.container {
max-width: 1080px;
padding: 32px;
margin: auto;
@media (min-width: 768px) {
padding: 64px;
}
}
.meta-row {
display: flex;
color: var(--vp-c-text-2);
gap: 16px;
overflow: hidden;
padding-bottom: 32px;
.author {
display: flex;
gap: 8px;
align-items: center;
color: var(--vp-c-text-2);
font-weight: normal;
text-decoration: none;
:hover {
text-decoration: underline;
}
img {
width: 24px;
border-radius: 100%;
}
}
}
}
.meta-row {
display: flex;
color: var(--vp-c-text-2);
gap: 16px;
overflow: hidden;
padding-bottom: 32px;
}
.meta-row > * {
flex-shrink: 0;
}
.author {
display: flex;
gap: 8px;
align-items: center;
color: var(--vp-c-text-2);
font-weight: normal;
text-decoration: none;
}
.author img {
width: 24px;
height: 24px;
border-radius: 100%;
}
.author span {
padding: 0;
margin: 0;
}
.author:hover {
text-decoration: underline;
color: var(--vp-c-text-2);
}
</style>
+16 -35
View File
@@ -31,42 +31,23 @@ const date = useBlogDate(() => props.post.date);
</template>
<style scoped>
li {
padding: 0;
margin: 0;
}
.vp-doc {
* {
margin-block: 10px;
}
p {
margin: 0;
}
h3 {
margin: 0;
padding: 0;
border: none;
}
.title {
color: var(--vp-c-text-1);
}
li > a > div {
display: flex;
flex-direction: column;
margin: 0 -16px;
padding: 16px;
border-radius: 16px;
}
li > a > div:hover {
background: var(--vp-c-default);
}
li .title {
color: var(--vp-c-text);
margin-bottom: 12px;
}
li .description {
font-size: 16px;
color: var(--vp-c-text-2);
margin-bottom: 8px;
}
li .meta {
font-weight: 400;
font-size: 12px;
color: var(--vp-c-text-2);
.description {
color: var(--vp-c-text-2);
}
.meta {
font-weight: 400;
font-size: 12px;
color: var(--vp-c-text-2);
}
}
</style>
@@ -1,40 +1,65 @@
<script lang="ts" setup>
const props = defineProps<{
defineProps<{
patterns: Array<[intput: string, output: string]>;
}>();
</script>
<template>
<table class="no-vertical-dividers">
<table class="no-vertical-dividers entrypoint-patterns">
<thead>
<tr>
<th style="width: 100%">Filename</th>
<th class="col-filename">Filename</th>
<th></th>
<th>Output Path</th>
</tr>
</thead>
<tbody>
<tr v-for="pattern of patterns">
<td style="white-space: nowrap; padding-right: 8px">
<tr v-for="pattern of patterns" :key="pattern[0]">
<td class="cell-input">
<code>entrypoints/{{ pattern[0] }}</code>
</td>
<td style="padding: 6px; opacity: 50%">
<td class="cell-arrow">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M4 11v2h12l-5.5 5.5l1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5L16 11H4Z"
fill="currentColor"
/>
</svg>
</td>
<td style="white-space: nowrap; padding-left: 8px">
<td class="cell-output">
<code>/{{ pattern[1] }}</code>
</td>
</tr>
</tbody>
</table>
</template>
<style scoped>
.entrypoint-patterns {
width: 100%;
.col-filename {
width: 100%;
}
.cell-input {
white-space: nowrap;
padding-right: 8px;
}
.cell-arrow {
padding: 6px;
opacity: 0.5;
}
.cell-output {
white-space: nowrap;
padding-left: 8px;
}
}
</style>
+52 -110
View File
@@ -1,14 +1,19 @@
<script lang="ts" setup>
import { ref, onMounted, computed, toRaw, Ref } from 'vue';
import { computed, onMounted, ref, Ref, toRaw } from 'vue';
import ExampleSearchFilterByItem from './ExampleSearchFilterByItem.vue';
import ExampleSearchResult from './ExampleSearchResult.vue';
import { ExamplesMetadata, KeySelectedObject } from '../utils/types';
const props = defineProps<{
defineProps<{
tag?: string;
}>();
const exampleMetadata = ref<ExamplesMetadata>();
const exampleMetadata = ref<ExamplesMetadata>({
allApis: [],
allPackages: [],
allPermissions: [],
examples: [],
});
onMounted(async () => {
const res = await fetch(
'https://raw.githubusercontent.com/wxt-dev/examples/main/metadata.json',
@@ -48,7 +53,7 @@ function doesExampleMatchSelected(
const filteredExamples = computed(() => {
const text = searchText.value.toLowerCase();
return exampleMetadata.value.examples.filter((example) => {
return exampleMetadata.value?.examples.filter((example) => {
const matchesText = example.searchText.toLowerCase().includes(text);
const matchesApis = doesExampleMatchSelected(example.apis, requiredApis);
const matchesPermissions = doesExampleMatchSelected(
@@ -72,19 +77,19 @@ const filteredExamples = computed(() => {
<div class="filters">
<ExampleSearchFilterByItem
label="APIs"
:items="exampleMetadata?.allApis"
v-model="selectedApis"
:items="exampleMetadata?.allApis"
label="APIs"
/>
<ExampleSearchFilterByItem
label="Permissions"
:items="exampleMetadata?.allPermissions"
v-model="selectedPermissions"
:items="exampleMetadata?.allPermissions"
label="Permissions"
/>
<ExampleSearchFilterByItem
label="Packages"
:items="exampleMetadata?.allPackages"
v-model="selectedPackages"
:items="exampleMetadata?.allPackages"
label="Packages"
/>
</div>
@@ -107,120 +112,57 @@ const filteredExamples = computed(() => {
<style scoped>
.example-layout {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
grid-template-areas:
'search'
'results';
'filters search'
'filters results';
gap: 16px;
}
@media only screen and (min-width: 720px) {
.example-layout {
@media only screen and (min-width: 710px) {
grid-template-columns: 256px 1fr;
grid-template-rows: auto 1fr;
grid-template-areas:
'filters search'
'filters results';
}
}
.search {
grid-area: search;
background: var(--vp-c-bg-soft);
padding: 20px;
width: 100%;
display: flex;
border-radius: 16px;
}
.filters {
display: none;
grid-area: filters;
}
@media only screen and (min-width: 720px) {
.search {
grid-area: search;
background: var(--vp-c-bg-soft);
padding: 20px;
border-radius: 16px;
input {
font-size: 16px;
}
}
.filters {
grid-area: filters;
display: flex;
flex-direction: column;
gap: 2px;
border-radius: 16px;
overflow: hidden;
align-self: flex-start;
gap: 10px;
@media only screen and (min-width: 710px) {
border-radius: 16px;
}
}
}
.results {
grid-area: results;
}
.box {
border-radius: 16px;
overflow: hidden;
}
.results {
grid-area: results;
.search input {
min-width: 0;
flex: 1;
font-size: 16px;
}
.search-results {
display: grid;
gap: 16px;
.checkbox-col {
flex: 1;
padding: 16px;
display: flex;
flex-direction: column;
overflow-y: auto;
max-height: 200px;
font-size: 14px;
gap: 4px;
}
@media only screen and (min-width: 800px) {
grid-template-columns: repeat(2, 1fr);
}
.filter-btn {
color: var(--vp-c-brand-1);
}
@media only screen and (min-width: 1024px) {
grid-template-columns: repeat(3, 1fr);
}
.checkbox-col .header {
font-size: 12px;
font-weight: bold;
opacity: 50%;
}
.checkbox-col p {
display: flex;
gap: 4px;
align-items: flex-start;
text-wrap: wrap;
overflow-wrap: anywhere;
line-height: 140%;
}
span {
padding-top: 1px;
}
.checkbox-col input[type='checkbox'] {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.checkbox-col-container {
display: flex;
}
.search-results {
display: grid;
grid-template-columns: repeat(1, 1fr);
gap: 16px;
}
@media only screen and (min-width: 800px) {
.search-results {
grid-template-columns: repeat(2, 1fr);
a {
background-color: red;
}
}
}
}
@media only screen and (min-width: 1024px) {
.search-results {
grid-template-columns: repeat(3, 1fr);
}
}
a {
background-color: red;
}
</style>
@@ -2,7 +2,7 @@
import { computed, toRaw } from 'vue';
import { KeySelectedObject } from '../utils/types';
const props = defineProps<{
defineProps<{
label: string;
items?: string[];
}>();
@@ -29,12 +29,12 @@ function toggleItem(pkg: string) {
<span>Filter by {{ label }}</span> <span v-if="count">({{ count }})</span>
</p>
<div class="scroll-container">
<ul>
<ul class="items">
<li v-for="item in items">
<label :title="item">
<label :title="item" class="item-label">
<input
type="checkbox"
:checked="selectedItems[item]"
type="checkbox"
@input="toggleItem(item)"
/>
<span>{{ item }}</span>
@@ -51,50 +51,45 @@ function toggleItem(pkg: string) {
display: flex;
flex-direction: column;
background: var(--vp-c-bg-soft);
}
border-radius: 20px;
scrollbar-width: thin;
scrollbar-color: var(--vp-c-brand-3) transparent;
padding-bottom: 10px;
.scroll-container {
flex: 1;
overflow: hidden;
position: relative;
}
.header {
padding: 8px 16px;
font-size: 12px;
font-weight: bold;
opacity: 50%;
}
.scroll-container ul {
position: absolute;
overflow-y: auto;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
gap: 4px;
font-size: small;
padding: 8px 16px 16px 16px;
}
.scroll-container {
flex: 1;
position: relative;
.header {
padding: 8px 16px;
font-size: 12px;
font-weight: bold;
opacity: 50%;
}
label {
display: flex;
gap: 4px;
align-items: flex-start;
text-wrap: wrap;
overflow-wrap: anywhere;
line-height: 140%;
cursor: pointer;
text-wrap: nowrap;
}
span {
padding-top: 1px;
}
input[type='checkbox'] {
width: 16px;
height: 16px;
flex-shrink: 0;
.items {
position: absolute;
overflow-y: auto;
inset: 0;
display: flex;
flex-direction: column;
gap: 4px;
font-size: small;
padding: 8px 16px 16px;
.item-label {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
input[type='checkbox'] {
width: 16px;
height: 16px;
flex-shrink: 0;
}
}
}
}
}
</style>
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { Example } from '../utils/types';
const props = defineProps<{
defineProps<{
example: Example;
}>();
</script>
@@ -17,56 +17,45 @@ const props = defineProps<{
</template>
<style scoped>
* {
min-width: 0;
}
a {
padding: 16px;
display: flex;
flex-direction: column;
border: 2px solid var(--vp-c-bg-soft);
border-radius: 16px;
color: var(--vp-c-text-1) !important;
gap: 8px;
}
a:hover {
outline: 2px solid var(--vp-c-brand-2);
}
.name {
font-size: 16px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
&:hover {
outline: 2px solid var(--vp-c-brand-2);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.description {
height: 53px;
opacity: 70%;
font-size: 14px;
font-weight: normal;
line-height: 120%;
min-height: 0;
.link {
opacity: 100%;
}
}
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.name {
font-weight: 500;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.link {
opacity: 0;
transition: 250ms;
color: var(--vp-c-brand-2);
font-weight: bold;
text-align: right;
}
a:hover .link {
opacity: 100%;
.description {
opacity: 70%;
font-size: 14px;
line-height: 130%;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.link {
opacity: 0;
transition: 250ms;
color: var(--vp-c-brand-2);
font-weight: bold;
text-align: right;
}
}
</style>
+199 -179
View File
@@ -1,168 +1,138 @@
<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
'eglpfhbhmelampoihamjomgkeobgdofl', // Capture It - Capture & Edit Screenshots
'jmghclbfbbapimhbgnpffbimphlpolnm', // Teams Chat Exporter
'jdcppdokgfbnhiacbeplahgnciahnhck', // Lofi BGM Player - Free lofi focus music for work & study
'cgpmbiiagnehkikhcbnhiagfomajncpa', // Margin - Annotate and highlight any webpage, with your notes saved to the decentralized AT Protocol.
'mfjdonmgmgcijagclnkfhmjiblbfjaid', // KeyFloat - Floating multilingual keyboard with native key mappings, drag, dark mode, sounds, and dynamic layouts for macOS & Windows
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;
edgeId?: string;
}>;
const chromeIds = extensionEntries.flatMap((e) =>
e.chromeId ? [e.chromeId] : [],
);
const firefoxSlugs = [
...new Set(
extensionEntries.flatMap((e) => (e.firefoxSlug ? [e.firefoxSlug] : [])),
),
];
const edgeIds = extensionEntries.flatMap((e) => (e.edgeId ? [e.edgeId] : []));
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
const sortedExtensions = computed(() => {
if (!data.value?.length) return [];
const { data, isLoading } = useListExtensionDetails(
chromeIds,
firefoxSlugs,
edgeIds,
);
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 edgeById = new Map(
(data.value.edge ?? []).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;
const edge = entry.edgeId ? edgeById.get(entry.edgeId) : undefined;
if (!chrome && !firefox && !edge) continue;
const primary = chrome ?? firefox ?? edge!;
const users =
(chrome?.users ?? 0) + (firefox?.users ?? 0) + (edge?.users ?? 0);
const rating = chrome?.rating ?? firefox?.rating ?? edge?.rating;
const stores: StoreLink[] = [];
if (chrome) {
stores.push({ label: 'Chrome', url: addUtmSource(chrome.storeUrl) });
}
if (firefox) {
stores.push({ label: 'Firefox', url: addUtmSource(firefox.storeUrl) });
}
if (edge) {
stores.push({ label: 'Edge', url: addUtmSource(edge.storeUrl) });
}
results.push({
id:
entry.chromeId ??
(entry.firefoxSlug
? `firefox:${entry.firefoxSlug}`
: `edge:${entry.edgeId}`),
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"
@@ -171,19 +141,39 @@ function getStoreUrl(extension: ChromeExtension) {
<p class="description" :title="extension.shortDescription">
{{ extension.shortDescription }}
</p>
<div v-if="extension.stores.length > 0" class="store-stats">
<p 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>
</p>
<p class="store-stats-sep" aria-hidden="true">&middot;</p>
<p class="store-links">
<template v-for="(store, i) of extension.stores" :key="store.label">
<a
:href="store.url"
target="_blank"
class="store-link"
:title="store.label"
>{{ store.label }}</a
>
<span
v-if="i < extension.stores.length - 1"
class="store-stats-sep"
aria-hidden="true"
>,&nbsp;</span
>
</template>
</p>
</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
>
@@ -192,7 +182,7 @@ function getStoreUrl(extension: ChromeExtension) {
</template>
<style scoped>
li img {
.extension-grid > li > img {
width: 116px;
height: 116px;
padding: 16px;
@@ -200,7 +190,7 @@ li img {
background-color: var(--vp-c-default-soft);
}
ul {
.extension-grid {
display: grid;
grid-template-columns: repeat(1, 1fr);
align-items: stretch;
@@ -210,12 +200,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;
@@ -223,28 +213,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;
@@ -253,33 +283,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,71 @@
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[];
edge: Extension[];
}
const operationName = 'WxtDocsUsedBy';
const query = `query ${operationName}($ids:[String!]!) {
chromeExtensions(ids: $ids) {
const query = `query ${operationName}($chromeIds: [String!]!, $firefoxIds: [String!]!, $edgeIds: [String!]!) {
chromeExtensions(ids: $chromeIds) {
id
name
iconUrl
weeklyActiveUsers
shortDescription
storeUrl
rating
...ExtensionData
}
firefoxAddons(ids: $firefoxIds) {
id: slug
...ExtensionData
}
edgeAddons(ids: $edgeIds) {
id
...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[],
edgeIds: string[],
) {
const data = ref<ExtensionResults>();
const err = ref<unknown>();
const isLoading = ref(true);
if (
chromeIds.length === 0 &&
firefoxSlugs.length === 0 &&
edgeIds.length === 0
) {
data.value = { chrome: [], firefox: [], edge: [] };
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, edgeIds },
}),
headers: {
'Content-Type': 'application/json',
@@ -41,10 +73,12 @@ 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 ?? [],
edge: responseData.edgeAddons ?? [],
};
err.value = undefined;
})
.catch((error) => {
@@ -54,9 +88,5 @@ export default function (ids: string[]) {
err.value = error;
});
return {
data,
err,
isLoading,
};
return { data, err, isLoading };
}
+39 -23
View File
@@ -1,5 +1,26 @@
import { Feed } from 'feed';
// @ts-expect-error; It isn't TypeScript lib
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,25 +28,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';
import llmstxt from 'vitepress-plugin-llms';
const origin = 'https://wxt.dev';
@@ -44,6 +46,7 @@ const otherPackages = {
storage: storageVersion,
unocss: unocssVersion,
runner: runnerVersion,
'is-background': isBackgroundVersion,
};
const knowledge = addKnowledge<DefaultTheme.Config>({
@@ -71,8 +74,11 @@ export default defineConfig({
description,
vite: {
clearScreen: false,
//TODO: REMOVE THIS @TS-EXPECT-ERROR AFTER BUMP VITEPRESS TO V2.0
plugins: [
// @ts-expect-error: Vite version mismatch between this project and the plugin
llmstxt(),
// @ts-expect-error: Vite version mismatch between this project and the plugin
groupIconVitePlugin({
customIcon: {
'wxt.config.ts': localIconLoader(
@@ -81,6 +87,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,
@@ -111,7 +128,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: [
@@ -146,8 +163,7 @@ export default defineConfig({
footer: {
message: [
'<a class="light-netlify" href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-bg.svg" alt="Deploys by Netlify" style="display: inline;" /></a>',
'<a class="dark-netlify" href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-accent.svg" alt="Deploys by Netlify" style="display: inline;" /></a>',
'<span class="netlify-badge"><a class="light-netlify" href="https://www.netlify.com"><img src="/netlify-badge-light.svg" alt="Deploys by Netlify" /></a><a class="dark-netlify" href="https://www.netlify.com"><img src="/netlify-badge-dark.svg" alt="Deploys by Netlify" /></a></span>',
'Released under the <a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE">MIT License</a>.',
].join('<br/>'),
copyright:
@@ -222,11 +238,11 @@ 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'),
menuItem('ES Modules', 'es-modules.md'),
menuItem('Remote Code', 'remote-code.md'),
menuItem('Unit Testing', 'unit-testing.md'),
menuItem('E2E Testing', 'e2e-testing.md'),
menuItem('Publishing', 'publishing.md'),
+8 -2
View File
@@ -1,3 +1,9 @@
import { createContentLoader } from 'vitepress';
import { type ContentData, createContentLoader } from 'vitepress';
import { PostFrontmatter } from '../utils/types';
export default createContentLoader('blog/*.md');
declare const data: Array<ContentData & { frontmatter: PostFrontmatter }>;
export { data };
export default createContentLoader<
Array<ContentData & { frontmatter: PostFrontmatter }>
>('blog/*.md');
+8 -11
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,24 +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');
}
export interface Command {
name: string;
docs: string;
}
+52 -33
View File
@@ -1,9 +1,15 @@
/* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */
/* Colors */
:root {
--wxt-c-green-1: #0b8a00;
--wxt-c-green-2: #096600;
--wxt-c-green-3: #096600;
--wxt-c-green-soft: rgba(11, 138, 0, 0.14);
--vp-c-brand-1: var(--wxt-c-green-1);
--vp-c-brand-2: var(--wxt-c-green-2);
--vp-c-brand-3: var(--wxt-c-green-3);
--vp-c-brand-soft: var(--wxt-c-green-soft);
}
.dark {
@@ -13,56 +19,69 @@
--wxt-c-green-soft: rgba(103, 212, 94, 0.14);
}
/* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */
:root {
--vp-c-brand-1: var(--wxt-c-green-1);
--vp-c-brand-2: var(--wxt-c-green-2);
--vp-c-brand-3: var(--wxt-c-green-3);
--vp-c-brand-soft: var(--wxt-c-green-soft);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Customize Individual Components */
.vp-doc .no-vertical-dividers {
th,
td {
border: none;
}
.vp-doc .no-vertical-dividers th,
.vp-doc .no-vertical-dividers td {
border: none;
}
.vp-doc .no-vertical-dividers tr {
border: 1px solid var(--vp-c-divider);
tr {
border: 1px solid var(--vp-c-divider);
}
}
body {
overflow-y: scroll;
}
.VPSidebar {
user-select: none;
}
.VPSidebarItem .badge {
display: inline-block;
min-width: 1.6em;
padding: 0.3em 0.4em 0.2em;
border-radius: 1rem;
font-size: 0.75em;
line-height: 1;
margin-left: 0.5rem;
text-align: center;
vertical-align: middle;
background-color: var(--vp-c-default-2);
}
.light-netlify {
display: inline;
display: inline-flex;
}
.dark .light-netlify {
display: none;
}
.dark-netlify {
display: none;
}
.dark .dark-netlify {
display: inline;
display: inline-flex;
}
/* Add Aaron's favorite pic from Artemis II to the hero section because it's cool. */
.dark .VPHome::before {
content: '';
position: absolute;
background-image: url('../../assets/art002e009298.jpg');
opacity: 50%;
width: 100%;
height: 110vh;
background-size: cover;
background-position: 90%;
}
.dark .VPHome::after {
content: '';
position: absolute;
width: 100%;
height: 10vh;
top: 100vh;
background: linear-gradient(to bottom, transparent, var(--vp-c-bg));
}
.item {
z-index: 1;
}
.VPSidebar .curtain {
z-index: 2;
}
+5 -1
View File
@@ -6,15 +6,19 @@ import ExampleSearch from '../components/ExampleSearch.vue';
import BlogLayout from '../components/BlogLayout.vue';
import './custom.css';
import 'virtual:group-icons.css';
import type { EnhanceAppContext } from 'vitepress/client';
import { setupCodeGroupSync } from '../utils/code-groups-sync';
export default {
extends: DefaultTheme,
enhanceApp(ctx) {
enhanceApp(ctx: EnhanceAppContext) {
ctx.app
.component('Icon', Icon)
.component('EntrypointPatterns', EntrypointPatterns)
.component('UsingWxtSection', UsingWxtSection)
.component('ExampleSearch', ExampleSearch)
.component('blog', BlogLayout);
setupCodeGroupSync();
},
};
+73
View File
@@ -0,0 +1,73 @@
const CODE_GROUP_SELECTORS = {
root: '.vp-code-group',
input: '.vp-code-group .tabs input',
label: '.tabs label',
} as const;
function getTabTitle(label: HTMLLabelElement) {
return label.dataset.title?.trim() || label.textContent?.trim();
}
function getCodeGroupTitle(input: HTMLInputElement) {
const label = input.parentElement?.querySelector<HTMLLabelElement>(
`label[for="${input.id}"]`,
);
return label ? getTabTitle(label) : undefined;
}
function findCodeGroupTabIndex(group: HTMLElement, title: string) {
return Array.from(
group.querySelectorAll<HTMLLabelElement>(CODE_GROUP_SELECTORS.label),
).findIndex((label) => getTabTitle(label) === title);
}
function syncCodeGroupSelection(group: HTMLElement, title: string) {
const targetIndex = findCodeGroupTabIndex(group, title);
if (targetIndex < 0) return;
const blocks = group.querySelector('.blocks');
const targetBlock = blocks?.children[targetIndex];
if (!blocks || !targetBlock || targetBlock.classList.contains('active'))
return;
blocks.querySelector('.active')?.classList.remove('active');
targetBlock.classList.add('active');
const input = group.querySelectorAll<HTMLInputElement>(
CODE_GROUP_SELECTORS.input,
)[targetIndex];
if (input) input.checked = true;
}
function syncCodeGroupTabs(event: Event) {
const target = event.target;
if (
!(target instanceof HTMLInputElement) ||
!target.matches(CODE_GROUP_SELECTORS.input)
)
return;
const activeGroup = target.closest(CODE_GROUP_SELECTORS.root);
const activeTitle = getCodeGroupTitle(target);
if (!activeTitle) return;
document
.querySelectorAll<HTMLElement>(CODE_GROUP_SELECTORS.root)
.forEach((group) => {
if (group === activeGroup) return;
syncCodeGroupSelection(group, activeTitle);
});
}
/**
* Keeps every code group on the page in sync: selecting a package manager (or
* any tab) in one group switches the matching tab in all the other groups.
*/
export function setupCodeGroupSync() {
if (typeof window === 'undefined') return;
window.addEventListener('change', syncCodeGroupTabs, true);
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { HeadConfig } from 'vitepress/types/shared';
import type { HeadConfig } from 'vitepress';
export function meta(
property: string,
+2 -14
View File
@@ -1,8 +1,6 @@
import { DefaultTheme } from 'vitepress';
type SidebarItem = DefaultTheme.SidebarItem;
type NavItem = DefaultTheme.NavItem;
type NavItemWithLink = DefaultTheme.NavItemWithLink;
type NavItemWithChildren = DefaultTheme.NavItemWithChildren;
type NavItemChildren = DefaultTheme.NavItemChildren;
@@ -19,7 +17,7 @@ export function navItem(text: string, arg2?: unknown): any {
}
export function menuRoot(items: SidebarItem[]) {
return items.map((item, index) => {
return items.map((item) => {
// item.collapsed = false; // uncomment to expand all level-0 items
return item;
});
@@ -60,12 +58,6 @@ export function menuGroup(
throw Error('Unknown overload');
}
export function menuItems(items: SidebarItem[]) {
return {
items,
};
}
export function menuItem(
text: string,
link: string,
@@ -77,9 +69,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);
@@ -99,9 +89,7 @@ export function prepareTypedocSidebar(items: SidebarItem[]) {
}
};
// process
prepareItems(filtered);
// return
return filtered;
}
+14
View File
@@ -1,3 +1,5 @@
import { ContentData } from 'vitepress';
export interface Example {
name: string;
description?: string;
@@ -16,3 +18,15 @@ export type ExamplesMetadata = {
};
export type KeySelectedObject = Record<string, boolean | undefined>;
export interface PostFrontmatter {
title: string;
description?: string;
date: Date;
authors: { name: string; github: string }[];
}
export interface Post
extends Omit<ContentData, 'frontmatter'>, Omit<PostFrontmatter, 'date'> {
date: Date;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

+533
View File
@@ -0,0 +1,533 @@
# 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}/),
# - `edgeId` for the Edge Add-ons listing (ID from https://microsoftedge.microsoft.com/addons/detail/{slug}/{id})
#
# You may include one or more of these fields for your extension.
- # Anylang Translator
chromeId: injdenmpjncknciccmjjmjlgbmnkince
firefoxSlug: anylang-translator
- # 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
edgeId: aghmglalnfmjbipodadgbgpakneabgnp
- # 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
firefoxSlug: nice-tab-manager
edgeId: ompjiaelpibiggcnanhbdblkhfdmkgnl
- # 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
- # YouTube Auto Feed
chromeId: jcdebdlnakhdinkindpogcehnhggbfad
firefoxSlug: youtube-auto-feed
- # YouTube Like-Dislike Shortcut
chromeId: fdkpkpelkkdkjhpacficichkfifijipc
firefoxSlug: youtube-like-dislike-shortcut
- # Twitch Channel Points Bonus Collector
chromeId: kbbdnbaghpcjpdhbjbccadodjejlkkgg
firefoxSlug: twitch-cp-bonus-collector
- # Skillshare Player Control
chromeId: agbhgcomfpcfboebbfmefbicfkpnlfeg
firefoxSlug: skillshare-player-control
- # 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
firefoxSlug: monochromate
- # 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
edgeId: phlomfiieaggnbfpacmjmidcjdlaiplp
- # 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
- # Mimik
chromeId: jmfohdaflahliammccpiadmkcibohgha
firefoxSlug: mimik
- # Modrinth Extras - https://github.com/creeperkatze/modrinth-extras
chromeId: ajmkilipadfpaefpcjfgnkejalmhdlcj
firefoxSlug: modrinth-extras
- # Pi-hole In One - https://github.com/creeperkatze/pihole-in-one
chromeId: gaaobidjebianpcngcfpkniaocibidhe
firefoxSlug: pihole-in-one
- # QIE Wallet Non-Custodial Multi-Chain Crypto Wallet
chromeId: oljchdcgmibnjbbopolafbjncfhdacjb
- # Headstart Supercharge your browsing efficiency https://useheadstart.app/
chromeId: fhhldbfombjdnjnpeojjohloohmbanda
- # Redmine Time Tracking
chromeId: ldcanhhkffokndenejhafhlkapflgcjg
firefoxSlug: redmine-time-tracking
- # Telsia: Eye Strain Reduction
chromeId: iakkmikmbknfcffhalijnhijblikoldc
firefoxSlug: telsia-eye-strain-reduction
- # Sound Booster & Equalizer
chromeId: podnfipggfpfcpglhjebcefinglohbhn
- # Link Later
chromeId: gpehbbegbcpjmipphokcmfhkchhcpfam
- # Google Cloud Skills Boost - Helper
chromeId: lmbhjioadhcoebhgapaidogodllonbgg
firefoxSlug: cloud-skills-boost-helper
- # Property Track - https://www.propertytrack.co
chromeId: ahknifalnhmbanoclebllcolcdomfdop
firefoxSlug: propertytrack
- # WageSlav3
chromeId: anhkcmapjhiiofpcgglfljphdgppilof
- # LinkedIn Games Solver
chromeId: jnhgapnkejaijibcdhcldhdfikjmdaph
firefoxSlug: linkedin-games-solver-addon
- # Hayami: Anime comments & discussions
chromeId: nhkggpiaeaeeeimohfpchnjobbamfcbg
firefoxSlug: hayami
- # CopyApes Assistant
chromeId: affmjifigldmicnbgpghddaneomejmfo
- # Leetcode Enhancer
chromeId: cpoclfijojgjiafnlgnhjalkaiabcjch
- # Adaptive Tab Bar Colour
firefoxSlug: adaptive-tab-bar-colour
- #ArcPort - Tab Checkpoint
chromeId: kajmnfhpmkimleehomeioondgfcjjnbp
- #ArcPort - Tab Rebrand
chromeId: pgmohcikgaeddapienpncpbiklbmfkgp
- #MindSnap
chromeId: mdcpckfcphpicfpiopfcpeambjknjabl
Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 138 KiB

+4 -4
View File
@@ -43,7 +43,7 @@ ESLint doesn't know about the auto-imported variables unless they are explicitly
:::code-group
```ts [ESLint 9]
```ts [ESLint >=9]
export default defineConfig({
imports: {
eslintrc: {
@@ -53,7 +53,7 @@ export default defineConfig({
});
```
```ts [ESLint 8]
```ts [ESLint <=8]
export default defineConfig({
imports: {
eslintrc: {
@@ -69,7 +69,7 @@ Then in your ESLint config, import and use the generated file:
:::code-group
```js [ESLint 9]
```js [ESLint >=9]
// eslint.config.mjs
import autoImports from './.wxt/eslint-auto-imports.mjs';
@@ -81,7 +81,7 @@ export default [
];
```
```js [ESLint 8]
```js [ESLint <=8]
// .eslintrc.mjs
export default {
extends: ['./.wxt/eslintrc-auto-import.json'],
@@ -4,11 +4,17 @@ outline: deep
# Browser Startup
During development, WXT will use any of the below packages to automatically open a browser with your extension installed.
- [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext)
Just install the dependency you want WXT to use to open the browser.
## `web-ext` Usage
> See the [API Reference](/api/reference/wxt/interfaces/WebExtConfig) for a full list of config.
During development, WXT uses [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext) to automatically open a browser window with your extension installed.
## Config Files
### Config Files
You can configure browser startup in 3 places:
@@ -25,9 +31,9 @@ You can configure browser startup in 3 places:
2. `<rootDir>/wxt.config.ts`: Via the [`webExt` config](/api/reference/wxt/interfaces/InlineConfig#webext), included in version control
3. `$HOME/web-ext.config.ts`: Provide default values for all WXT projects on your computer
## Recipes
### Recipes
### Set Browser Binaries
#### Set Browser Binaries
To set or customize the browser opened during development:
@@ -57,7 +63,7 @@ export default defineConfig({
By default, WXT will try to automatically discover where Chrome/Firefox are installed. However, if you have chrome installed in a non-standard location, you need to set it manually as shown above.
### Persist Data
#### Persist Data
By default, to keep from modifying your browser's existing profiles, `web-ext` creates a brand new profile every time you run the `dev` script.
@@ -94,9 +100,9 @@ Now, next time you run the `dev` script, a persistent profile will be created in
You can use any directory you'd like for `--user-data-dir`, the examples above create a persistent profile for each WXT project. To create a profile for all WXT projects, you can put the `chrome-data` directory inside your user's home directory.
:::
### Disable Opening Browser
#### Disable Opening Browser
If you prefer to load the extension into your browser manually, you can disable the auto-open behavior:
If you don't want to uninstall `web-ext`, like to test in your normal profile, you can do so via `disabled: true`:
```ts [web-ext.config.ts]
import { defineWebExtConfig } from 'wxt';
+41
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/)
@@ -176,6 +200,23 @@ export default defineConfig({
});
```
:::warning
Different browsers support different permissions. You are responsible for passing only the permissions required for each browser:
```ts
export default defineConfig({
manifest: ({ browser }) => ({
permissions:
browser === 'chrome'
? ['storage', 'favicon', 'declarativeNetRequest']
: ['storage', 'webRequest'],
}),
});
```
:::
## Host Permissions
> [Chrome docs](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions#host-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
+33 -16
View File
@@ -17,28 +17,14 @@ Or if you're in a monorepo, you may not want to extend the config. If you don't
/// <reference path="./.wxt/wxt.d.ts" />
```
## Compiler Options
To specify custom compiler options, add them in `<rootDir>/tsconfig.json`:
```jsonc
// <rootDir>/tsconfig.json
{
"extends": ".wxt/tsconfig.json",
"compilerOptions": {
"jsx": "preserve",
},
}
```
## TSConfig Paths
WXT provides a default set of path aliases.
| Alias | To | Example |
| ----- | ------------- | ----------------------------------------------- |
| `~~` | `<rootDir>/*` | `import "~~/scripts"` |
| `@@` | `<rootDir>/*` | `import "@@/scripts"` |
| `~~` | `<rootDir>/*` | `import "~~/package.json"` |
| `@@` | `<rootDir>/*` | `import "@@/package.json"` |
| `~` | `<srcDir>/*` | `import { toLowerCase } from "~/utils/strings"` |
| `@` | `<srcDir>/*` | `import { toLowerCase } from "@/utils/strings"` |
@@ -63,3 +49,34 @@ export default defineConfig({
import { fakeTab } from 'testing/fake-objects';
import { toLowerCase } from 'strings';
```
## Custom Options
To specify custom compiler or top-level options, you have two options:
1. Override WXT's value completely by setting it in your root `tsconfig.json`:
```jsonc
// <rootDir>/tsconfig.json
{
"extends": ".wxt/tsconfig.json",
"compilerOptions": {
"jsx": "preserve",
},
}
```
2. Merge custom values into values generated by WXT via the `prepare:tsconfig` hook:
```ts
// wxt.config.ts
export default defineConfig({
hooks: {
'prepare:tsconfig': (wxt, { tsconfig }) => {
tsconfig.compilerOptions.lib.push('WebWorker');
},
},
});
```
Overriding is great for simple boolean values, but not for complex options like `paths` or `libs`, where you can't just "add" a value to the object from your `tsconfig.json` file. So WXT provides the hook that let's you add or merge or delete or do whatever you want to the config file before it's written to the `.wxt/` directory.
+9
View File
@@ -449,6 +449,15 @@ Full examples:
- [react-content-script-ui](https://github.com/wxt-dev/examples/tree/main/examples/react-content-script-ui)
- [tailwindcss](https://github.com/wxt-dev/examples/tree/main/examples/tailwindcss)
:::warning `rem` Units Are Not Fully Isolated
WXT resets most inherited styles via `all: initial`. This doesn't reset the `<html>` element's font size, which determines the relative size of `rem` units.
If your CSS framework uses `rem` units, like Tailwind CSS, you may notice your UI's scale changing on different websites.
See the [FAQ](/guide/resources/faq#my-content-script-ui-looks-different-on-certain-websites) for a fix.
:::
### IFrame
If you don't need to run your UI in the same frame as the content script, you can use an IFrame to host your UI instead. Since an IFrame just hosts an HTML page, **_HMR is supported_**.
+4 -4
View File
@@ -463,8 +463,8 @@ When you define a Newtab entrypoint, WXT will automatically update the manifest
<meta
name="manifest.default_icon"
content="{
16: '/icon-16.png',
24: '/icon-24.png',
'16': '/icon-16.png',
'24': '/icon-24.png',
...
}"
/>
@@ -554,8 +554,8 @@ Firefox does not support sandboxed pages.
<meta
name="manifest.default_icon"
content="{
16: '/icon-16.png',
24: '/icon-24.png',
'16': '/icon-16.png',
'24': '/icon-24.png',
...
}"
/>
+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.
+37 -18
View File
@@ -76,6 +76,34 @@ Alternatively, if you're trying to use similar APIs under different names (to su
});
```
## Add Firefox Types
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 by:
1. Installing `@wxt-dev/browser` as a direct dependency
```sh
pnpm add @wxt-dev/browser
```
2. Augmenting the `Browser` type:
```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;
}
}
```
You can add types from any source: `webextension-polyfill`, `@types/firefox-webext-browser`, or your own custom types.
See TypeScript's documentation on [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) for more information.
## 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**.
@@ -94,41 +122,32 @@ The fix is simple, just move your API usage into the entrypoint's main function:
:::code-group
<!-- prettier-ignore -->
```ts [background.ts]
browser.action.onClicked.addListener(() => {
/* ... */
}); // [!code --]
browser.action.onClicked.addListener(() => { /* ... */ }); // [!code --]
export default defineBackground(() => {
browser.action.onClicked.addListener(() => {
/* ... */
}); // [!code ++]
browser.action.onClicked.addListener(() => { /* ... */ }); // [!code ++]
});
```
<!-- prettier-ignore -->
```ts [content.ts]
browser.runtime.onMessage.addListener(() => {
/* ... */
}); // [!code --]
browser.runtime.onMessage.addListener(() => { /* ... */ }); // [!code --]
export default defineContentScript({
main() {
browser.runtime.onMessage.addListener(() => {
/* ... */
}); // [!code ++]
browser.runtime.onMessage.addListener(() => { /* ... */ }); // [!code ++]
},
});
```
<!-- prettier-ignore -->
```ts [unlisted.ts]
browser.runtime.onMessage.addListener(() => {
/* ... */
}); // [!code --]
browser.runtime.onMessage.addListener(() => { /* ... */ }); // [!code --]
export default defineUnlistedScript(() => {
browser.runtime.onMessage.addListener(() => {
/* ... */
}); // [!code ++]
browser.runtime.onMessage.addListener(() => { /* ... */ }); // [!code ++]
});
```
+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>'],
},
],
},
});
```
+1 -1
View File
@@ -97,7 +97,7 @@ Instead, you need to configure the router to run in "hash" mode, where the routi
Refer to your router's docs for information about hash mode and how to enable it. Here's a non-extensive list of a few popular routers:
- [`react-router`](https://reactrouter.com/en/main/routers/create-hash-router)
- [`react-router`](https://reactrouter.com/api/data-routers/createHashRouter#createhashrouter)
- [`vue-router`](https://router.vuejs.org/guide/essentials/history-mode.html#Hash-Mode)
- [`svelte-spa-router`](https://www.npmjs.com/package/svelte-spa-router#hash-based-routing)
- [`solid-router`](https://github.com/solidjs/solid-router?tab=readme-ov-file#hash-mode-router)
+5 -5
View File
@@ -163,8 +163,8 @@ yarn zip:firefox
```
```sh [bun]
bun i
bun zip:firefox
bun install
bun run zip:firefox
```
:::
@@ -206,13 +206,13 @@ WXT uses the command `npm pack <package-name>` to download the package. That mea
WXT does not currently support automated publishing for Safari. Safari extensions require a native MacOS or iOS app wrapper, which WXT does not create yet. For now, if you want to publish to Safari, follow this guide:
- [Converting a web extension for Safari](https://developer.apple.com/documentation/safariservices/safari_web_extensions/converting_a_web_extension_for_safari) - "Convert your existing extension to a Safari web extension using Xcodes command-line tool."
- [Packaging a web extension for Safari](https://developer.apple.com/documentation/safariservices/packaging-a-web-extension-for-safari) - "Package your existing extension as a Safari web extension using Xcodes command-line tool."
When running the `safari-web-extension-converter` CLI tool, pass the `.output/safari-mv2` or `.output/safari-mv3` directory, not your source code directory.
When running the `safari-web-extension-packager` CLI tool, pass the `.output/safari-mv2` or `.output/safari-mv3` directory, not your source code directory.
```sh
pnpm wxt build -b safari
xcrun safari-web-extension-converter .output/safari-mv2
xcrun safari-web-extension-packager .output/safari-mv2
```
### Edge Addons
-31
View File
@@ -1,31 +0,0 @@
# Remote Code
WXT will automatically download and bundle imports with the `url:` prefix so the extension does not depend on remote code, [a requirement from Google for MV3](https://developer.chrome.com/docs/extensions/migrating/improve-security/#remove-remote-code).
## Google Analytics
For example, you can import Google Analytics:
```ts
// utils/google-analytics.ts
import 'url:https://www.googletagmanager.com/gtag/js?id=G-XXXXXX';
window.dataLayer = window.dataLayer || [];
// NOTE: This line is different from Google's documentation
window.gtag = function () {
dataLayer.push(arguments);
};
gtag('js', new Date());
gtag('config', 'G-XXXXXX');
```
Then you can import this in your HTML files to enable Google Analytics:
```ts
// popup/main.ts
import '~/utils/google-analytics';
gtag('event', 'event_name', {
key: 'value',
});
```
@@ -78,3 +78,28 @@ Here are some examples:
```
Alternatively, you can use the [`filterEntrypoints` config](/api/reference/wxt/interfaces/InlineConfig#filterentrypoints) to list all the entrypoints you want to build.
## Per-Browser Options
Some entrypoint options can be customized per build target by passing an object keyed by the [target browser](/guide/essentials/target-different-browsers#target-a-browser) instead of a single value. This is useful when different browsers need different match patterns, run timings, or other entrypoint behavior:
```ts
export default defineContentScript({
matches: {
chrome: ['*://chrome.example.com/*'],
firefox: ['*://firefox.example.com/*'],
},
runAt: {
chrome: 'document_start',
firefox: 'document_end',
},
world: {
firefox: 'MAIN',
},
main(ctx) {
// ...
},
});
```
-1
View File
@@ -21,7 +21,6 @@ This plugin does several things:
- Polyfills the extension API, `browser`, with an in-memory implementation using [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/fake-browser/installation)
- Adds all vite config or plugins in `wxt.config.ts`
- Configures auto-imports (if enabled)
- Applies internal WXT vite plugins for things like [bundling remote code](/guide/essentials/remote-code)
- Sets up global variables provided by WXT (`import.meta.env.BROWSER`, `import.meta.env.MANIFEST_VERSION`, `import.meta.env.IS_CHROME`, etc)
- Configures aliases (`@/*`, `@@/*`, etc) so imports can be resolved
+105 -24
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
@@ -131,12 +131,93 @@ 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).
#### Logging
There are two "correct" ways to add console logs to your modules:
1. Use `wxt.logger` for info, warnings, and errors
2. Install [`obug`](https://www.npmjs.com/package/obug) for debug messages
```ts
import { defineWxtModule } from 'wxt/modules';
import { createDebug } from 'obug';
const debug = createDebug('my-module');
export default defineWxtModule({
setup(wxt) {
wxt.logger.info('Module loaded');
debug('Debug details');
},
});
```
`wxt.logger` is great for formatted, pretty messages that match the rest of WXT's logs.
`obug` makes it easy for devs to enable and filter debug logs when necessary:
```sh
DEBUG=my-module wxt dev
```
#### 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 +301,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;
`;
+17 -17
View File
@@ -10,14 +10,14 @@ Run the [`init` command](/api/cli/wxt-init), and follow the instructions.
:::code-group
```sh [PNPM]
pnpm dlx wxt@latest init
```
```sh [Bun]
bunx wxt@latest init
```
```sh [PNPM]
pnpm dlx wxt@latest init
```
```sh [NPM]
npx wxt@latest init
```
@@ -46,16 +46,16 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
1. Create a new project
:::code-group
```sh [PNPM]
cd my-project
pnpm init
```
```sh [Bun]
cd my-project
bun init
```
```sh [PNPM]
cd my-project
pnpm init
```
```sh [NPM]
cd my-project
npm init
@@ -71,12 +71,12 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
2. Install WXT:
:::code-group
```sh [PNPM]
pnpm i -D wxt
```sh [Bun]
bun add -D wxt
```
```sh [Bun]
bun i -D wxt
```sh [PNPM]
pnpm i -D wxt
```
```sh [NPM]
@@ -119,14 +119,14 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
5. Run your extension in dev mode
:::code-group
```sh [PNPM]
pnpm dev
```
```sh [Bun]
bun run dev
```
```sh [PNPM]
pnpm dev
```
```sh [NPM]
npm run dev
```
+1
View File
@@ -13,3 +13,4 @@ This page is dedicated to all the awesome people how have made something for WXT
- [`@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)
-1
View File
@@ -24,7 +24,6 @@ Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework)
| Supports all frontend frameworks | ✅ | 🟡 [^c] | ✅ |
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 [^d] | ✅ [^e] | ❌ |
| Automated publishing | ✅ | ✅ | ❌ |
| Remote Code Bundling (Google Analytics) | ✅ | ✅ | ❌ |
| Unlisted HTML Pages | ✅ | ✅ | ✅ |
| Unlisted Scripts | ✅ | ❌ | ❌ |
| ESM Content Scripts | ❌ [^l] | ❌ | ✅ |
+42
View File
@@ -164,6 +164,36 @@ 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.
## My content script UI looks different on certain websites
If your `createShadowRootUi` looks correct on most sites but appears at the wrong size on others (e.g., Reddit), it's because `rem` unit is relative to the `<html>` element's `font-size`, which lives outside the Shadow DOM. When a website overrides it, your UI scales incorrectly.
The fix is to convert `rem` units to `px` at build time using [`postcss-rem-to-responsive-pixel`](https://www.npmjs.com/package/postcss-rem-to-responsive-pixel). This eliminates the dependency on the host page's root font-size.
1. Install the package:
```sh
bun i -D postcss-rem-to-responsive-pixel
```
2. Configure your PostCSS config:
```js [postcss.config.mjs]
import remToPx from 'postcss-rem-to-responsive-pixel';
export default {
plugins: [
remToPx({
rootValue: 16,
propList: ['*'],
transformUnit: 'px',
}),
],
};
```
See [Issue #678](https://github.com/wxt-dev/wxt/issues/678) for additional context on this behavior.
## Does WXT provide docs for LLMs?
Yes, WXT's documentation provides markdown files based on the [the /llms.txt proposal](https://llmstxt.org/).
@@ -191,6 +221,18 @@ 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`.
4. **Enable polling if file changes are not detected**
Some container, WSL, and network file system setups do not emit native file events reliably. Configure Chokidar polling in `wxt.config.ts`:
```ts
export default defineConfig({
watchOptions: {
usePolling: true,
interval: 1000,
},
});
```
## 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".
+1 -1
View File
@@ -51,7 +51,7 @@ Here's specific steps for other popular frameworks/build tools.
3. Move public `assets/*` into the `public/` directory
4. If you use CSUI, migrate to WXT's `createContentScriptUi`
5. Convert Plasmo's custom import resolutions to Vite's
6. If importing remote code via a URL, add a `url:` prefix so it works with WXT
6. Importing remote code via a URL is not supported in WXT, see [this issue](https://github.com/wxt-dev/wxt/issues/2262) for alternatives
7. Replace your [Plasmo tags](https://docs.plasmo.com/framework/workflows/build#with-a-custom-tag) (`--tag`) with [WXT build modes](/guide/essentials/config/build-mode) (`--mode`)
8. ⚠️ Compare the old production manifest to `.output/*/manifest.json`. They should have the same content as before. If not, tweak your entrypoints and config until they are the same.
+340 -4
View File
@@ -15,7 +15,7 @@ To upgrade WXT to the latest major version:
```
2. Follow the upgrade steps below to fix any breaking changes.
3. Run `wxt prepare`. It should succeed and type errors will go away afterwords.
3. Run `wxt prepare`. It should succeed and type errors will go away afterwards.
```sh
pnpm wxt prepare
@@ -35,6 +35,340 @@ Listed below are all the breaking changes you should address when upgrading to a
Currently, WXT is in pre-release. This means changes to the second digit, `v0.X`, are considered major and have breaking changes. Once v1 is released, only major version bumps will have breaking changes.
## v0.20.0 &rarr; v0.21.0
This release focuses on trimming WXT's install footprint and simplifying project configuration.
:::tip
Read through all the changes once before updating your code.
:::
### Install Footprint
When installing `wxt` v0.20, it downloaded 98 MB and 366 packages to your `node_modules`. v0.21 cuts this down to 22MB / 156 packages - that's 22.4% the previous size! This is a good improvement, but frankly, not good enough. Why?
- `wxt init` is slow for new developers trying it out
- Less exposure to supply chain attacks
- More compute and storage used by servers hosting the package and clients downloading it degrades hardware and shortens their life-cycle
If you want to explore WXT's dependencies and why it's so large, here's a few links:
- <https://pkg-size.dev/wxt@%5E0.21?no-peers>
- <https://npmgraph.js.org/?q=wxt@^0.21>
- <https://node-modules.dev/grid/depth#install=wxt@^0.21>
We'll continue working on minimizing WXT's install footprint.
### Minimum Node.js, Vite, and TypeScript Versions
| Tool | Old requirement | New requirement |
| ---------- | --------------------------------- | ---------------------------------- |
| Node.js | `>=20.12.0` | `>=22` |
| Vite | `^5.4.19 \|\| ^6.3.4 \|\| ^7.0.0` | `^6.3.4 \|\| ^7.0.0 \|\| ^8.0.0-0` |
| TypeScript | Not enforced | `>=5.4` |
Vite 5 support was dropped as part of moving WXT's dev server off `vite-node` and onto Vite's native `ModuleRunner`/Environment API, which requires Vite &ge;6.3.4.
### `vite`, `web-ext`, and `typescript` Are Now Peer Dependencies
Previously, WXT installed its own copies of `vite` and `web-ext` for you (you never had to add them to your `package.json`). Now, all three are declared as [`peerDependencies`](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#peerdependencies) instead of regular dependencies, so **your project controls the exact version that gets used**, and WXT's install size shrinks.
- `vite` is a **required** peer, add it to your `devDependencies`.
- `web-ext` and `typescript` are **optional** peers, only needed if you use the corresponding features (see [below](#opt-into-opening-the-browser-during-dev-mode) for `web-ext`).
```sh
pnpm add -D vite typescript
```
### Opt Into Opening the Browser During Dev Mode
In v0.21, `web-ext` is now an optional peer dependency - if it's installed, the browser will be opened automatically. If not, the feature is disabled.
- **To continue opening the browser automatically**: Install `web-ext` as a dependency
```sh
pnpm add -D web-ext
```
- **If you don't use this feature**: Don't install it and you can delete any `web-ext.config.ts` options and/or remove any `webExt.enabled: false` config
You can still disable this behavior by setting `webExt.enabled: false` in your `wxt.config.ts` file or `web-ext.config.ts` file if the package is present.
> Projects created with `wxt init` will include `web-ext` in their `package.json`, opting new projects into this feature by default.
### `.wxt/tsconfig.json` Compiler Option Changes
The generated `.wxt/tsconfig.json` was updated to match Vite's current recommended TypeScript settings, and now requires **TypeScript &ge;5.4**.
```jsonc
{
"compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"], // [!code ++]
"target": "ESNext",
"module": "ESNext", // [!code --]
"module": "Preserve", // [!code ++]
"moduleDetection": "force", // [!code ++]
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true, // [!code ++]
"verbatimModuleSyntax": true, // [!code ++]
"noEmit": true,
"esModuleInterop": true, // [!code --]
"forceConsistentCasingInFileNames": true, // [!code --]
"resolveJsonModule": true, // [!code --]
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true, // [!code ++]
"noUncheckedIndexedAccess": true, // [!code ++]
"noImplicitOverride": true, // [!code ++]
},
}
```
If these changes do cause problems, `verbatimModuleSyntax` and `noUncheckedIndexedAccess` are the two most likely culprits. It's recommended you fix the issues and leave the new settings enabled.
However, if you want to revert the config back and upgrade to the recommended options at a later time, you can add the following to your `wxt.config.ts` file:
<details>
```ts
export default defineConfig({
hooks: {
'prepare:tsconfig': (_, { tsconfig }) => {
// Reverts TSConfig back to WXT v0.20
// - https://wxt.dev/guide/resources/upgrading.html#wxttsconfigjson-compiler-option-changes
delete tsconfig.lib;
tsconfig.module = 'ESNext';
delete tsconfig.moduleDetection;
delete tsconfig.allowImportingTsExtensions;
delete tsconfig.verbatimModuleSyntax;
tsconfig.esModuleInterop = true;
tsconfig.forceConsistentCasingInFileNames = true;
tsconfig.resolveJsonModule = true;
delete tsconfig.noFallthroughCasesInSwitch;
delete tsconfig.noUncheckedIndexedAccess;
delete tsconfig.noImplicitOverride;
},
},
});
```
</details>
### `url:` Imports Removed
The `url:` import feature (importing and bundling remote code by URL, e.g. `import 'url:https://code.jquery.com/jquery.js'`) has been removed due to the supply-chain risk of a compromised CDN silently injecting code into your build.
There is no config flag to restore this behavior. If you relied on it:
- **Prefer an NPM package** if one exists for the library you need.
- **Vendor the file**: download it once, commit it to your repo, and import it locally like any other file. This lets you review the code and track changes via version control, instead of trusting a URL to never change.
:::details Why was this removed?
See <https://github.com/wxt-dev/wxt/issues/2262> and <https://github.com/wxt-dev/wxt/issues/2229> for the full discussion. A proposal to restore this behind a mandatory integrity hash is being tracked in [#2514](https://github.com/wxt-dev/wxt/pull/2514); if there's enough demand, it may come back as a separate, opt-in WXT module instead of core.
:::
### Zip Filename Template Changes
The default filename templates for `zip.artifactTemplate` and `zip.sourcesTemplate` changed, and the <span v-pre>`{{version}}`</span> template variable's meaning changed:
- <span v-pre>`{{version}}`</span> now always resolves to `manifest.version` (previously it resolved to `manifest.version_name ?? manifest.version`).
- A new <span v-pre>`{{versionName}}`</span> variable was added with the old <span v-pre>`{{version}}`</span> behavior (`manifest.version_name ?? manifest.version`).
- A new <span v-pre>`{{packageVersion}}`</span> variable was added, resolving to your `package.json`'s version.
- A new <span v-pre>`{{modeSuffix}}`</span> variable was added (`-dev` for development builds, `''` for production).
The defaults themselves changed to use these new variables:
```diff
- artifactTemplate: '{{name}}-{{version}}-{{browser}}.zip'
+ artifactTemplate: '{{name}}-{{packageVersion}}-{{browser}}{{modeSuffix}}.zip'
- sourcesTemplate: '{{name}}-{{version}}-sources.zip'
+ sourcesTemplate: '{{name}}-{{packageVersion}}-sources{{modeSuffix}}.zip'
```
- **If you don't customize `artifactTemplate`/`sourcesTemplate`**: your production zip filenames will not change, but dev and other build modes will be output to separate directories.
> If you previously install the dev version of your extension in your browser, note that the output directory has changed and you'll need to re-install it next time you run the dev command.
- **If you have a custom template**: replace any <span v-pre>`{{version}}`</span> usage with <span v-pre>`{{versionName}}`</span> to keep the old value, or explicitly opt back into the old defaults:
```ts [wxt.config.ts]
export default defineConfig({
zip: {
artifactTemplate: '{{name}}-{{versionName}}-{{browser}}.zip',
sourcesTemplate: '{{name}}-{{versionName}}-sources.zip',
},
});
```
For more info on the difference between `manifest.version` and `manifest.version_name`, see the [Manifest Config docs](/guide/essentials/config/manifest#version-and-version-name).
### Sources ZIP: `includeSources`/`excludeSources` Now Behave Like a Standard Allowlist
Previously, `includeSources`/`excludeSources` behaved counterintuitively: `all files - excludeSources + includedSources = zipped files`. This is not the standard behavior for allowlist/blocklists.
As of v0.21, these options behave as expected: `includedSources - excludeSources = zipped files`.
Additionally, a new `zip.dotSources` option (default `false`) was added to control whether hidden files/directories can be included in the zip.
This will likely be the biggest pain point of the upgrade. You will need to rebuild your included and excluded sources lists from scratch. Luckily, it's simpler than before!
1. Do you need any hidden files? If so, set `zip.dotSources` to `true`
1. Add base files and directories that need to be in the ZIP to `zip.includeSources`
1. Run `wxt zip -b firefox` and confirm all the files you need are included
1. Remove files from the zip by adding them to `zip.excludeSources`
1. Run `wxt zip -b firefox` and confirm only the files you need are in the zip
:::tip
`wxt zip -b firefox` now prints every file included in the sources ZIP. This will help you identify missing or unwanted files more quickly.
:::
Here's an example configuration:
```ts
// wxt.config.ts
export default defineConfig({
srcDir: 'src',
zip: {
// Say I have a .tool-versions file I want to include
dotSources: true,
// Specify base folders and files to include
includeSources: [
'src',
'public',
'package.json',
'bun.lock',
'README.md',
'.tool-versions',
],
// Exclude any folders inside the base folders that are not needed, like test data
excludeSources: ['**/__fixtures__'],
},
});
```
### `createShadowRootUi` DOM Changes
`@webext-core/isolated-element` was upgraded from v1 to v3. The only breaking change relevant to WXT projects is that the internal structure of the shadow root's is simpler:
:::code-group
```html [Before]
<html>
<head>
<style>
...
</style>
</head>
<body>
...your app
</body>
</html>
```
```html [After]
<style>
...
</style>
<div>...your app</div>
```
:::
Historically, CSS frameworks haven't had good support for shadow roots, so we needed a full `<html>` structure for styles to be applied correctly. But that has change this past few years, frameworks have started to include the `host:` selector alongside `root:` (which is required for base styles to be applied to the shadow root's host element):
```css
:root { /* [!code --] */
:root, :host { /* [!code ++] */
/* base styles... */
}
```
If you use `createShadowRootUi`, see if your UI looks the same after this update without any changes.
If the styles are broken, you can continue using the full `<html>` structure by installing the v1 of `@webext-core/isolated-element` - WXT will respect whatever version of the package is listed in your `package.json`.
```sh
pnpm add @webext-core/isolated-element@^1
```
In the next major version, support for `@webext-core/isolated-element` v1 will be dropped, so you have some time to migrate.
### Content & Unlisted Script `globalName` Now Defaults to `false`
Content scripts and unlisted scripts are built as an IIFE. Previously, WXT generated a global variable to hold the IIFE's return value for all scripts by default. Now, no global variable is generated by default, producing a smaller, anonymous IIFE that prevents variable collisions with the page.
If you rely on the return value of your script, for example when injecting it with `browser.scripting.executeScript` and reading `InjectionResult.result`, set `globalName: true` to restore the old behavior:
```ts
export default defineUnlistedScript({
globalName: true, // [!code ++]
main() {
return 'some value';
},
});
```
This applies per-entrypoint, to both `defineContentScript` and `defineUnlistedScript`.
### ESLint Auto-Import Config Now Detects Your Installed ESLint Version
Setting `imports.eslintrc.enabled: true` used to always assume you wanted the legacy ESLint 8 config format, regardless of which ESLint version you actually had installed.
Now, `true` behaves the same as `'auto'`: WXT detects your installed ESLint version and generates the matching file automatically (ESLint 9+ &rarr; `eslint-auto-imports.mjs`, ESLint &le;8 &rarr; `eslintrc-auto-import.json`).
- If you're on ESLint 9+ but never explicitly set `enabled: 9`, you'll now get the new flat-config file instead of the legacy one.
- To force the old ESLint 8 format regardless of your installed version, set `enabled: 8` explicitly.
See the [ESLint config docs](/guide/essentials/config/auto-imports#eslint) for the full set of options and how to wire up the generated file.
### Deprecated APIs Removed From v0.21
APIs deprecated in v0.20 have been removed:
- `wxt.config.runner` / `ExtensionRunnerConfig` / `defineRunnerConfig` &rarr; use `wxt.config.webExt` / `defineWebExtConfig` instead.
- `dev.server.hostname` &rarr; use `dev.server.host` instead.
- The `wxt/testing` barrel export &rarr; import from the specific submodule instead:
```ts
import { fakeBrowser, WxtVitest } from 'wxt/testing'; // [!code --]
import { fakeBrowser } from 'wxt/testing/fake-browser'; // [!code ++]
import { WxtVitest } from 'wxt/testing/vitest-plugin'; // [!code ++]
```
- The `clean(root: string)` JS API overload &rarr; Pass a root like this instead `clean({ root: './my-extension' })`
### CWS v2 API Support
`publish-browser-extension` was upgraded to v5, which adds support for the new Chrome Web Store v2 API to `wxt submit`! Instead of using a refresh token for auth, it uses a service account, which is much easier to setup and never expires. It also supports a few other features: <https://developer.chrome.com/blog/cws-api-v2>
To setup your project to use v2, run `wxt submit init` and go through the setup process for the CWS, selecting v2 when prompted.
v1 will stop working **15th October 2026**, so you have a few months to migrate.
### `@webext-core/fake-browser` v2
If you use `wxt/testing/fake-browser` (or `@webext-core/fake-browser` directly) in unit tests, note that its types now match `@wxt-dev/browser` instead of `webextension-polyfill`. This is the same change WXT's own `browser` went through in [v0.20](#webextension-polyfill-removed).
Most notably, **mocking `browser.runtime.onMessage` listeners that return a promise no longer works**, use `sendResponse` instead:
```ts
fakeBrowser.runtime.onMessage.addListener(async () => { // [!code --]
return await someAsyncWork(); // [!code --]
fakeBrowser.runtime.onMessage.addListener((_message, _sender, sendResponse) => { // [!code ++]
someAsyncWork().then(sendResponse); // [!code ++]
return true; // [!code ++]
});
```
If you use use a messaging library, it will likely continue working as-is.
### New Deprecations in v0.20
Deprecated APIs will be removed in the next major release.
- `useAppConfig` deprecated in favor of `getAppConfig` - same function, just renamed. This avoids react linters mistaking it as a hook.
## v0.19.0 &rarr; v0.20.0
v0.20 is a big release! There are lots of breaking changes because this version is intended to be a release candidate for v1.0. If all goes well, v1.0 will be released with no additional breaking changes.
@@ -177,7 +511,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 +525,7 @@ import {
storage, // [!code ++]
defineContentScript, // [!code ++]
ContentScriptContext, // [!code ++]
useAppConfig, // [!code ++]
getAppConfig, // [!code ++]
} from '#imports'; // [!code ++]
```
@@ -476,7 +810,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 +891,8 @@ Unlisted scripts must now `export default defineUnlistedScript(...)`.
### `BackgroundDefinition` Type
<!-- cspell:disable -->
Rename `BackgroundScriptDefintition` to `BackgroundDefinition`.
## v0.6.0 &rarr; v0.7.0
-5
View File
@@ -66,11 +66,6 @@ features:
- icon: 📏
title: Bundle Analysis
details: Tools for analyzing the final extension bundle and minimizing your extension's size.
- icon: ⬇️
title: Bundle Remote Code
details: Downloads and bundles remote code imported from URLs.
link: /guide/essentials/remote-code
linkText: Read docs
---
## Sponsors
+1
View File
@@ -0,0 +1 @@
<!--@include: ../packages/is-background/README.md-->
+5 -1
View File
@@ -38,6 +38,10 @@
# 0.19.0
/guide/go-further/entrypoint-side-effects.html /guide/go-further/entrypoint-loaders.html
# 0.21.0
# The `url:` import feature was removed, along with its docs page.
/guide/essentials/remote-code.html /guide/resources/upgrading.html#url-imports-removed
# https://github.com/wxt-dev/wxt/issues/704
# Generated via `pnpm docs:build && find docs/.vitepress/dist -type f -name "*.html"`
@@ -59,7 +63,7 @@
/guide/go-further/handling-updates.html /guide/essentials/testing-updates.html
/guide/go-further/custom-events.html /guide/essentials/content-scripts.html#dealing-with-spas
/guide/go-further/debugging.html /TODO
/guide/go-further/remote-code.html /guide/essentials/remote-code.html
/guide/go-further/remote-code.html /guide/resources/upgrading.html#url-imports-removed
/guide/go-further/vite.html /guide/essentials/config/vite.html
/guide/go-further/testing.html /guide/essentials/unit-testing.html
/guide/go-further/how-wxt-works.html /guide/resources/how-wxt-works.html
+1
View File
@@ -0,0 +1 @@
<svg width="114" height="50" viewBox="0 0 114 50" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path fill="#060B10" d="M0 0h114v51H0z"/><g clip-path="url(#b)" fill="#fff"><path d="M44.445 40.302v-7.695l.153-.16h1.853l.154.16v7.695l-.154.16h-1.853l-.153-.16ZM44.445 17.855v-7.696l.153-.159h1.853l.154.16v7.695l-.154.16h-1.853l-.153-.16ZM38.068 34.892h-.254l-1.275-1.323v-.264l2.388-2.482 1.349.002.18.185v1.4l-2.388 2.482ZM36.54 17.227v-.267l1.274-1.32h.254l2.388 2.479v1.398l-.18.19h-1.349l-2.388-2.48ZM28.154 24.107h10.5l.153.16v1.926l-.153.16h-10.5l-.154-.16v-1.927l.154-.16ZM99.844 24.11l.154.159v1.924l-.154.16H89.19l-.153-.16.77-1.924.154-.16h9.883ZM49 29.24h-1.853l-.153-.16v-4.502c0-.802-.303-1.423-1.234-1.443a24.2 24.2 0 0 0-1.613.025l-.089.092v5.826l-.153.16h-1.853l-.154-.16v-7.695l.154-.16h4.169c1.62 0 2.933 1.364 2.933 3.046v4.81l-.154.159v.002ZM57.974 25.871l-.154.16h-4.79l-.154.16c0 .32.31 1.283 1.546 1.283.463 0 .926-.16 1.082-.481l.154-.16h1.852l.154.16c-.154.962-.926 2.407-3.245 2.407-2.625 0-3.861-1.924-3.861-4.172 0-2.248 1.236-4.171 3.708-4.171 2.472 0 3.708 1.924 3.708 4.171v.643Zm-2.319-1.605c0-.16-.153-1.283-1.39-1.283-1.235 0-1.389 1.124-1.389 1.283l.154.16h2.472l.153-.16ZM62.298 26.833c0 .322.154.481.464.481h1.39l.153.16v1.604l-.154.16h-1.39c-1.389 0-2.625-.643-2.625-2.407v-3.529l-.154-.16H58.9l-.154-.159v-1.605l.154-.16h1.082l.154-.159v-1.443l.154-.16h1.852l.154.16v1.443l.154.16h1.699l.153.16v1.604l-.153.16h-1.7l-.153.159v3.529l.002.002ZM68.015 29.24h-1.853l-.153-.16V18.172l.153-.16h1.853l.154.16v10.907l-.154.16v.002ZM72.186 19.935h-1.852l-.154-.16v-1.604l.154-.16h1.852l.154.16v1.605l-.154.16Zm0 9.305h-1.852l-.154-.16v-7.7l.154-.159h1.852l.154.16v7.7l-.154.16ZM79.449 18.171v1.605l-.154.16h-1.39c-.309 0-.463.159-.463.48v.643l.154.16h1.546l.153.16v1.604l-.153.16h-1.546l-.154.159v5.774l-.153.16h-1.853l-.154-.16v-5.774l-.153-.16h-1.083l-.153-.159v-1.605l.153-.16h1.083l.153-.159v-.643c0-1.764 1.236-2.407 2.626-2.407h1.39l.153.16-.002.002ZM85.163 29.4c-.619 1.605-1.236 2.567-3.398 2.567h-.773l-.154-.16v-1.605l.154-.16h.773c.773 0 .926-.159 1.082-.64v-.16l-2.472-6.257v-1.604l.154-.16h1.39l.153.16 1.853 5.455h.153l1.853-5.455.154-.16h1.39l.153.16v1.604l-2.472 6.417.007-.002Z"/></g><path fill="#fff" d="M0 0v50h15V0z"/><path d="M6.518 45.367v-.774H11v.774H6.518Zm0-1.746c0-.44.098-.784.294-1.032.196-.248.474-.372.834-.372.348 0 .622.13.822.39.2.26.3.628.3 1.104l-.336.018c.004-.608.114-1.062.33-1.362.212-.3.524-.45.936-.45.424 0 .748.146.972.438.22.288.33.686.33 1.194v1.242h-.738v-1.152c0-.268-.05-.488-.15-.66a.498.498 0 0 0-.462-.258c-.224 0-.39.088-.498.264-.108.172-.162.39-.162.654v1.146h-.738V43.68c0-.192-.04-.36-.12-.504a.426.426 0 0 0-.396-.216c-.192 0-.33.068-.414.204a.955.955 0 0 0-.126.51v1.116h-.678v-1.17Zm0-2.925v-.774H9.17c.256 0 .472-.04.648-.12a.88.88 0 0 0 .39-.336.926.926 0 0 0 .126-.486.926.926 0 0 0-.126-.486.845.845 0 0 0-.39-.33 1.497 1.497 0 0 0-.648-.126H6.518v-.75h2.664c.604 0 1.068.146 1.392.438.324.288.486.71.486 1.266 0 .56-.162.984-.486 1.272-.324.288-.788.432-1.392.432H6.518Zm0-5.048v-.774H11v.774H6.518Zm0-2.475V32.4H11v.774H6.518Zm3.75-.546v-2.412H11v2.412h-.732Zm-3.69-3.903v-.773H11v.773H6.578Zm-.06 1.41v-3.6h.732v3.6h-.732Zm0-6.98v-.827l3.684-1.056v.318L6.518 20.43v-.888l3.684-1.122v.318L6.518 17.64v-.798L11 18.18v.81l-4.086 1.254v-.438L11 21.097v.756l-4.482 1.302Zm0-7.385v-.774H11v.774H6.518Zm.06-3.283v-.774H11v.774H6.578Zm-.06 1.41v-3.6h.732v3.6h-.732Zm0-4.7v-.773H11v.774H6.518Zm1.572-.545V6.179h.732V8.65H8.09ZM6.518 6.407v-.774H11v.774H6.518Z" fill="#060B10"/></g><rect x="1" y="1" width="112" height="48" rx="3" stroke="#060B10" stroke-width="2"/><defs><clipPath id="a"><rect width="114" height="50" rx="4" fill="#fff"/></clipPath><clipPath id="b"><path fill="#fff" d="M28 10h72v30.462H28z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

+1
View File
@@ -0,0 +1 @@
<svg width="114" height="50" viewBox="0 0 114 50" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><rect width="114" height="50" rx="4" fill="#181A1C"/><path fill="#fff" d="M0 0h114v51H0z"/><g clip-path="url(#b)" fill="#181A1C"><path d="M44.445 40.3v-7.694l.153-.16h1.853l.154.16V40.3l-.154.16h-1.853l-.153-.16ZM44.445 17.854V10.16l.153-.16h1.853l.154.16v7.694l-.154.16h-1.853l-.153-.16ZM38.068 34.89h-.254l-1.275-1.322v-.264l2.388-2.482 1.349.002.18.184v1.4l-2.388 2.483ZM36.54 17.226v-.266l1.274-1.321h.254l2.388 2.48v1.397l-.18.19h-1.349l-2.388-2.48ZM28.154 24.106h10.5l.153.16v1.926l-.153.16h-10.5l-.154-.16v-1.926l.154-.16ZM99.844 24.109l.154.16v1.923l-.154.16H89.19l-.153-.16.77-1.924.154-.16h9.883ZM49 29.24h-1.853l-.153-.16v-4.503c0-.802-.303-1.423-1.234-1.443-.48-.012-1.027 0-1.613.025l-.089.093v5.825l-.153.16h-1.853l-.154-.16v-7.694l.154-.16h4.169c1.62 0 2.933 1.363 2.933 3.045v4.81l-.154.159v.002ZM57.974 25.87l-.154.16h-4.79l-.154.16c0 .32.31 1.283 1.546 1.283.463 0 .926-.16 1.082-.481l.154-.16h1.852l.154.16c-.154.962-.926 2.407-3.245 2.407-2.625 0-3.861-1.924-3.861-4.172 0-2.247 1.236-4.17 3.708-4.17 2.472 0 3.708 1.923 3.708 4.17v.643Zm-2.319-1.604c0-.16-.153-1.284-1.39-1.284-1.235 0-1.389 1.124-1.389 1.284l.154.16h2.472l.153-.16ZM62.298 26.832c0 .322.154.481.464.481h1.39l.153.16v1.604l-.154.16h-1.39c-1.389 0-2.625-.643-2.625-2.407V23.3l-.154-.16H58.9l-.154-.159v-1.604l.154-.16h1.082l.154-.16v-1.442l.154-.16h1.852l.154.16v1.443l.154.16h1.699l.153.159v1.604l-.153.16h-1.7l-.153.16v3.528l.002.002ZM68.015 29.24h-1.853l-.153-.16V18.17l.153-.159h1.853l.154.16v10.906l-.154.16v.002ZM72.186 19.935h-1.852l-.154-.16v-1.604l.154-.16h1.852l.154.16v1.604l-.154.16Zm0 9.304h-1.852l-.154-.16V21.38l.154-.16h1.852l.154.16v7.7l-.154.16ZM79.449 18.17v1.605l-.154.16h-1.39c-.309 0-.463.16-.463.48v.644l.154.16h1.546l.153.159v1.604l-.153.16h-1.546l-.154.16v5.773l-.153.16h-1.853l-.154-.16V23.3l-.153-.16h-1.083l-.153-.159v-1.604l.153-.16h1.083l.153-.16v-.642c0-1.764 1.236-2.407 2.626-2.407h1.39l.153.16-.002.002ZM85.163 29.399c-.619 1.604-1.236 2.566-3.398 2.566h-.773l-.154-.16v-1.604l.154-.16h.773c.773 0 .926-.159 1.082-.64v-.16l-2.472-6.256V21.38l.154-.16h1.39l.153.16 1.853 5.455h.153l1.853-5.455.154-.16h1.39l.153.16v1.605L85.156 29.4l.007-.002Z"/></g><path fill="#181A1C" d="M0 0h15v50H0z"/><path d="M5.518 45.367v-.774H10v.774H5.518Zm0-1.746c0-.44.098-.784.294-1.032.196-.248.474-.372.834-.372.348 0 .622.13.822.39.2.26.3.628.3 1.104l-.336.018c.004-.608.114-1.062.33-1.362.212-.3.524-.45.936-.45.424 0 .748.146.972.438.22.288.33.686.33 1.194v1.242h-.738v-1.152c0-.268-.05-.488-.15-.66a.498.498 0 0 0-.462-.258c-.224 0-.39.088-.498.264-.108.172-.162.39-.162.654v1.146h-.738V43.68c0-.192-.04-.36-.12-.504a.426.426 0 0 0-.396-.216c-.192 0-.33.068-.414.204a.955.955 0 0 0-.126.51v1.116h-.678v-1.17Zm0-2.925v-.774H8.17c.256 0 .472-.04.648-.12a.88.88 0 0 0 .39-.336.926.926 0 0 0 .126-.486.926.926 0 0 0-.126-.486.845.845 0 0 0-.39-.33 1.497 1.497 0 0 0-.648-.126H5.518v-.75h2.664c.604 0 1.068.146 1.392.438.324.288.486.71.486 1.266 0 .56-.162.984-.486 1.272-.324.288-.788.432-1.392.432H5.518Zm0-5.048v-.774H10v.774H5.518Zm0-2.475V32.4H10v.774H5.518Zm3.75-.546v-2.412H10v2.412h-.732Zm-3.69-3.903v-.773H10v.773H5.578Zm-.06 1.41v-3.6h.732v3.6h-.732Zm0-6.98v-.827l3.684-1.056v.318L5.518 20.43v-.888l3.684-1.122v.318L5.518 17.64v-.798L10 18.18v.81l-4.086 1.254v-.438L10 21.097v.756l-4.482 1.302Zm0-7.385v-.774H10v.774H5.518Zm.06-3.283v-.774H10v.774H5.578Zm-.06 1.41v-3.6h.732v3.6h-.732Zm0-4.7v-.773H10v.774H5.518Zm1.572-.545V6.179h.732V8.65H7.09ZM5.518 6.407v-.774H10v.774H5.518Z" fill="#fff"/></g><rect x="1" y="1" width="112" height="48" rx="3" stroke="#181A1C" stroke-width="2"/><defs><clipPath id="a"><rect width="114" height="50" rx="4" fill="#fff"/></clipPath><clipPath id="b"><path fill="#fff" d="M28 10h72v30.46H28z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

+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.
+11 -7
View File
@@ -7,7 +7,7 @@
Output docs/assets/init-demo.gif
# The tools we will use
Require pnpm
Require bun
# === Scene ====
@@ -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 "
@@ -35,19 +35,22 @@ Env PS1 "\e[0;32m●\e[0m "
Hide
# Create a temporary folder for demo
Type 'vhs_sandbox="$(mktemp -d)"' Enter
Type 'cd "$vhs_sandbox"' Enter
Type 'mkdir packages/wxt/init-demo' Enter
Type 'cd packages/wxt/init-demo' Enter
# Unset CI variables inherited from the GitHub Actions runner so consola
# picks its fancy reporter instead of the basic one (#2461)
Type 'unset CI GITHUB_ACTIONS' Enter
Type 'clear' Enter
Show
# ===== Actions =====
Type 'pnpm dlx wxt@latest init .' Sleep 1s Enter
Type 'bunx wxt init .' Sleep 1s Enter
Sleep 3.25s
Down@750ms Enter@750ms # Select `vue` template
Sleep 1.25s
Down@750ms Enter@750ms # Select `pnpm` as a package manager
Down@500ms Down@500ms Enter@750ms # Select `pnpm` as a package manager
Sleep 5s
@@ -55,5 +58,6 @@ Sleep 5s
Hide
# Delete the temporary folder
Type 'rm -rf "$vhs_sandbox"' Enter
Type 'cd .."' Enter
Type 'rm -rf init-demo' Enter
Show
+6
View File
@@ -0,0 +1,6 @@
[build]
command = "bun run docs:build"
publish = "docs/.vitepress/dist"
[build.environment]
BUN_VERSION = "1.3.5"
+55 -31
View File
@@ -2,59 +2,83 @@
"private": true,
"type": "module",
"engines": {
"node": ">=18.20.3"
"bun": ">=1.3.5"
},
"packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8",
"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",
"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"
},
"catalog": {
"@aklinker1/buildc": "^1.1.7",
"@aklinker1/check": "^2.4.0",
"@types/bun": "^1.3.5",
"@types/node": "^22",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@webext-core/fake-browser": "^1.3.4",
"nano-spawn": "^2.0.0",
"oxlint": "^1.63.0",
"publint": "^0.3.18",
"tsdown": "^0.21.0",
"typescript": "^6.0.3",
"unocss": "^66.6.8",
"vite": "^7.3.1",
"vitest": "^4.1.5",
"vitest-plugin-random-seed": "^1.1.2"
},
"devDependencies": {
"@aklinker1/buildc": "^1.1.6",
"@aklinker1/check": "^2.2.0",
"@commitlint/config-conventional": "^20.4.1",
"@commitlint/types": "^20.4.0",
"@types/fs-extra": "^11.0.4",
"@aklinker1/buildc": "catalog:",
"@aklinker1/check": "catalog:",
"@commitlint/config-conventional": "^21.2.0",
"@commitlint/types": "^21.2.0",
"@types/bun": "catalog:",
"@types/semver": "^7.7.1",
"@vitest/coverage-v8": "^4.0.18",
"changelogen": "^0.6.2",
"@vitest/coverage-v8": "^4.1.6",
"consola": "^3.4.2",
"fast-glob": "^3.3.3",
"feed": "^5.2.0",
"fs-extra": "^11.3.3",
"lint-staged": "^16.2.7",
"cspell": "^9.7.0",
"feed": "^5.2.1",
"markdown-it-footnote": "^4.0.0",
"markdownlint-cli": "^0.47.0",
"nano-spawn": "^2.0.0",
"markdownlint-cli": "^0.49.0",
"nano-spawn": "catalog:",
"nano-staged": "^1.0.2",
"p-map": "^7.0.4",
"prettier": "^3.8.1",
"semver": "^7.7.4",
"prettier-plugin-jsdoc": "^1.8.0",
"semver": "^7.8.0",
"simple-git-hooks": "^2.13.1",
"tsdown": "^0.20.3",
"tinyglobby": "^0.2.16",
"tsdown": "catalog:",
"tsx": "4.21.0",
"typedoc": "^0.25.4",
"typedoc-plugin-frontmatter": "^1.3.1",
"typedoc-plugin-markdown": "4.0.0-next.23",
"typedoc-vitepress-theme": "1.0.0-next.3",
"typescript": "^5.9.3",
"typescript": "catalog:",
"vitepress": "^1.6.4",
"vitepress-knowledge": "^0.4.1",
"vitepress-plugin-group-icons": "^1.7.1",
"vitepress-plugin-llms": "^1.11.0",
"vitest-mock-extended": "^3.1.0",
"vue": "^3.5.27",
"vitepress-plugin-group-icons": "^1.7.3",
"vitepress-plugin-llms": "^1.12.2",
"vitest-mock-extended": "^4.0.0",
"vue": "^3.5.34",
"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"
},
"lint-staged": {
"simple-git-hooks": {
"pre-commit": "./node_modules/.bin/nano-staged"
},
"nano-staged": {
"*": "prettier --ignore-unknown --write"
}
}
+88
View File
@@ -1,5 +1,93 @@
# Changelog
## v0.5.5
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.4...analytics-v0.5.5)
### 🚀 Enhancements
- **analytics:** Add Moderok analytics provider ([#2393](https://github.com/wxt-dev/wxt/pull/2393))
### 🩹 Fixes
- **analytics:** Change method return types from void to Promise<void> ([#2409](https://github.com/wxt-dev/wxt/pull/2409))
### 🏡 Chore
- Add `prettier-plugin-jsdoc` to project ([#2171](https://github.com/wxt-dev/wxt/pull/2171))
- **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))
- Use `deps.neverBundle` instead of `external` ([8f71e174](https://github.com/wxt-dev/wxt/commit/8f71e174))
- Migrate monorepo to use Bun instead of PNPM ([#2009](https://github.com/wxt-dev/wxt/pull/2009))
- **deps-dev:** Bump typescript from 5.9.3 to 6.0.3 ([#2325](https://github.com/wxt-dev/wxt/pull/2325))
- Use `catalog:` for dev dependencies ([#2357](https://github.com/wxt-dev/wxt/pull/2357))
### ❤️ Contributors
- Muhammed Mustafa AKŞAM ([@muhammedaksam](https://github.com/muhammedaksam))
- Vaughn Bosu ([@VaughnBosu](https://github.com/VaughnBosu))
- Aaron ([@aklinker1](https://github.com/aklinker1))
## 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)
+66
View File
@@ -5,6 +5,8 @@ Report analytics events from your web extension extension.
## Supported Analytics Providers
- [Google Analytics 4 (Measurement Protocol)](#google-analytics-4-measurement-protocol)
- [Moderok](#moderok)
- [PostHog](#posthog)
- [Umami](#umami)
## Install With WXT
@@ -120,6 +122,70 @@ export default defineAppConfig({
});
```
### Moderok
[Moderok](https://moderok.dev) is an analytics platform built specifically for browser extensions. It requires no `host_permissions`, works in Manifest V3 service workers, and collects only anonymous usage data.
Sign up at [moderok.dev](https://moderok.dev) to get your app key, then save it to your `.env` file:
```dotenv
WXT_MODEROK_APP_KEY='mk_...'
```
Then add the `moderok` provider to your `<srcDir>/app.config.ts` file:
```ts
import { moderok } from '@wxt-dev/analytics/providers/moderok';
export default defineAppConfig({
analytics: {
providers: [
moderok({
appKey: import.meta.env.WXT_MODEROK_APP_KEY,
// Automatically track first open, install, update, and daily ping events (default: true)
trackLifecycle: true,
// Track when users uninstall the extension (default: false)
trackUninstalls: false,
// Optional: when trackUninstalls is on, redirect users to this page
// after they uninstall (e.g. a feedback survey)
uninstallUrl: 'https://example.com/uninstall',
}),
],
},
});
```
For a full walkthrough — module setup, sending events, and all provider options — see the [Moderok WXT guide](https://docs.moderok.dev/guide/wxt).
### PostHog
[PostHog](https://posthog.com/) is an open source product analytics platform. It supports event tracking, session recording, feature flags, surveys, and more.
In your PostHog project settings, find your **Project API key** and save it to your `.env` file:
```dotenv
WXT_POSTHOG_API_KEY='phc_...'
```
Then add the `posthog` provider to your `<srcDir>/app.config.ts` file:
```ts
import { posthog } from '@wxt-dev/analytics/providers/posthog';
export default defineAppConfig({
analytics: {
providers: [
posthog({
apiKey: import.meta.env.WXT_POSTHOG_API_KEY,
// apiHost defaults to 'https://us.i.posthog.com'.
// Change to 'https://eu.i.posthog.com' for EU Cloud, or your self-hosted URL.
apiHost: 'https://eu.i.posthog.com',
}),
],
},
});
```
### Umami
[Umami](https://umami.is/) is a privacy-first, open source analytics platform.
+4
View File
@@ -1,5 +1,6 @@
import { defineAppConfig } from 'wxt/utils/define-app-config';
import { googleAnalytics4 } from './modules/analytics/providers/google-analytics-4';
import { posthog } from './modules/analytics/providers/posthog';
import { umami } from './modules/analytics/providers/umami';
export default defineAppConfig({
@@ -10,6 +11,9 @@ export default defineAppConfig({
apiSecret: '...',
measurementId: '...',
}),
posthog({
apiKey: '...',
}),
umami({
apiUrl: 'https://umami.aklinker1.io/api',
domain: 'analytics.wxt.dev',
+49 -30
View File
@@ -2,14 +2,30 @@ 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]>) => Promise<void>;
const ANALYTICS_PORT = '@wxt-dev/analytics';
@@ -40,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,
@@ -70,9 +84,13 @@ function createBackgroundAnalytics(
// Cached values
const platformInfo = browser.runtime.getPlatformInfo();
const userAgent = UAParser();
let userId = Promise.resolve(userIdStorage.getValue()).then(
(id) => id ?? globalThis.crypto.randomUUID(),
);
let userId = Promise.resolve(userIdStorage.getValue()).then(async (id) => {
if (id != null) return id;
// Persist the generated ID so it's stable across service worker restarts.
const generatedId = globalThis.crypto.randomUUID();
await userIdStorage.setValue?.(generatedId);
return generatedId;
});
let userProperties = userPropertiesStorage.getValue();
const manifest = browser.runtime.getManifest();
@@ -163,7 +181,7 @@ function createBackgroundAnalytics(
},
track: async (
eventName: string,
eventProperties?: Record<string, string>,
eventProperties?: Record<string, string | undefined>,
meta: AnalyticsEventMetadata = getBackgroundMeta(),
) => {
const baseEvent = await getBaseEvent(meta);
@@ -197,9 +215,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);
});
}
});
@@ -207,9 +224,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();
@@ -217,18 +232,17 @@ 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()] });
return Promise.resolve();
};
const analytics: Analytics = {
@@ -238,11 +252,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;
@@ -251,7 +265,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 });
@@ -263,13 +277,18 @@ 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 }),
};
@@ -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;
}
@@ -17,8 +18,8 @@ export const googleAnalytics4 =
eventProperties: Record<string, string | undefined> | undefined,
): Promise<void> => {
const url = new URL(
config?.debug ? '/debug/mp/collect' : '/mp/collect',
'https://www.google-analytics.com',
config.debug ? '/debug/mp/collect' : '/mp/collect',
options.apiUrl ?? 'https://www.google-analytics.com',
);
if (options.apiSecret)
url.searchParams.set('api_secret', options.apiSecret);
@@ -37,6 +38,12 @@ export const googleAnalytics4 =
]),
);
if (config.debug) {
console.debug(
'[@wxt-dev/analytics] Sending event to Google Analytics 4:',
{ eventName, eventProperties },
);
}
await fetch(url.href, {
method: 'POST',
body: JSON.stringify({
@@ -0,0 +1,267 @@
import { defineAnalyticsProvider } from '../client';
import { browser } from '@wxt-dev/browser';
import type { BaseAnalyticsEvent } from '../types';
const SDK_VERSION = 'wxt/0.1.0';
const DEFAULT_ENDPOINT = 'https://api.moderok.dev/v1/events';
const PING_STORAGE_KEY = 'moderok:last-ping-date';
const FIRST_OPEN_STORAGE_KEY = 'moderok:first-open';
export interface ModerokProviderOptions {
appKey: string;
endpoint?: string;
trackLifecycle?: boolean;
trackUninstalls?: boolean;
uninstallUrl?: string;
}
const OS_MAP: Record<string, string> = {
mac: 'MacOS',
win: 'Windows',
linux: 'Linux',
cros: 'ChromeOS',
android: 'Android',
};
const BROWSER_MAP: Record<string, string> = {
chrome: 'chrome',
edge: 'edge',
firefox: 'firefox',
chromium: 'other_chromium',
};
function mapOs(wxtOs: string | undefined): string {
if (!wxtOs) return 'unknown';
return OS_MAP[wxtOs.toLowerCase()] ?? 'unknown';
}
function mapBrowser(wxtBrowser: string | undefined): string {
if (!wxtBrowser) return 'unknown';
return BROWSER_MAP[wxtBrowser.toLowerCase()] ?? 'unknown';
}
function detectSource(meta: BaseAnalyticsEvent['meta']): string {
if (meta.sessionId == null) return 'background';
const url = meta.url;
if (!url) return 'unknown';
const isExtensionUrl =
url.startsWith('chrome-extension://') || url.startsWith('moz-extension://');
if (!isExtensionUrl) return 'content_script';
const path = url.toLowerCase();
if (path.includes('popup')) return 'popup';
if (path.includes('option')) return 'options';
if (
path.includes('sidepanel') ||
path.includes('side-panel') ||
path.includes('side_panel')
)
return 'side_panel';
return 'extension_page';
}
function buildContext(event: BaseAnalyticsEvent, extensionId: string) {
return {
sdkVersion: SDK_VERSION,
extensionId: extensionId || 'unknown',
extensionVersion: event.user.properties.version || 'unknown',
browser: mapBrowser(event.user.properties.browser),
browserVersion: event.user.properties.browserVersion || 'unknown',
os: mapOs(event.user.properties.os),
locale: event.meta.language || 'unknown',
source: detectSource(event.meta),
};
}
function sendEvent(
endpoint: string,
appKey: string,
event: {
name: string;
userId: string;
timestamp: number;
context: ReturnType<typeof buildContext>;
properties?: Record<string, string | number | boolean>;
},
debug: boolean,
) {
const payload = {
appKey,
sentAt: Date.now(),
events: [
{
id: globalThis.crypto.randomUUID(),
name: event.name,
timestamp: event.timestamp,
userId: event.userId,
context: event.context,
...(event.properties && Object.keys(event.properties).length > 0
? { properties: event.properties }
: {}),
},
],
};
if (debug) {
console.debug('[@wxt-dev/analytics][moderok] Sending:', payload);
}
return fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
body: JSON.stringify(payload),
});
}
function cleanProperties(
raw: Record<string, string | undefined> | undefined,
): Record<string, string> | undefined {
if (!raw) return undefined;
const clean: Record<string, string> = {};
for (const [key, value] of Object.entries(raw)) {
if (value != null) clean[key] = value;
}
return Object.keys(clean).length > 0 ? clean : undefined;
}
function utcDateStamp(): string {
return new Date().toISOString().slice(0, 10);
}
function buildUninstallUrl(
endpoint: string,
appKey: string,
userId: string,
redirectUrl?: string,
): string {
const url = new URL(endpoint);
url.pathname = url.pathname.replace(/\/[^/]*$/, '/uninstall');
url.searchParams.set('app', appKey);
url.searchParams.set('uid', userId);
if (redirectUrl) url.searchParams.set('redirect', redirectUrl);
return url.toString();
}
export const moderok = defineAnalyticsProvider<ModerokProviderOptions>(
(analytics, config, options) => {
const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
const debug = config.debug ?? false;
const trackLifecycle = options.trackLifecycle ?? true;
const extensionId = browser.runtime.id ?? '';
let firstOpenPromise: Promise<void> | undefined;
const maybeTrackFirstOpen = async (event: BaseAnalyticsEvent) => {
if (!trackLifecycle) return;
if (firstOpenPromise) return firstOpenPromise;
firstOpenPromise = (async () => {
const stored = await browser.storage.local.get(FIRST_OPEN_STORAGE_KEY);
if (stored[FIRST_OPEN_STORAGE_KEY]) return;
const response = await sendEvent(
endpoint,
options.appKey,
{
name: '__first_open',
userId: event.user.id,
timestamp: event.meta.timestamp,
context: buildContext(event, extensionId),
},
debug,
);
if (response.ok) {
await browser.storage.local.set({ [FIRST_OPEN_STORAGE_KEY]: true });
} else {
firstOpenPromise = undefined;
}
})().catch((error) => {
firstOpenPromise = undefined;
throw error;
});
return firstOpenPromise;
};
if (trackLifecycle) {
browser.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
analytics.track('__install');
} else if (details.reason === 'update') {
analytics.track('__update', {
previousVersion: (details as { previousVersion?: string })
.previousVersion,
});
}
});
void (async () => {
const today = utcDateStamp();
const stored = await browser.storage.local.get(PING_STORAGE_KEY);
if (stored[PING_STORAGE_KEY] === today) return;
await browser.storage.local.set({ [PING_STORAGE_KEY]: today });
analytics.track('__daily_ping');
})();
}
let uninstallUrlSet = false;
function maybeSetUninstallUrl(userId: string) {
if (!options.trackUninstalls || uninstallUrlSet) return;
uninstallUrlSet = true;
const url = buildUninstallUrl(
endpoint,
options.appKey,
userId,
options.uninstallUrl,
);
if (url.length <= 1023) {
browser.runtime.setUninstallURL(url);
}
}
return {
identify: () => Promise.resolve(),
page: async (event) => {
maybeSetUninstallUrl(event.user.id);
await maybeTrackFirstOpen(event);
await sendEvent(
endpoint,
options.appKey,
{
name: '__page_view',
userId: event.user.id,
timestamp: event.meta.timestamp,
context: buildContext(event, extensionId),
properties: {
url: event.page.url,
...(event.page.title ? { title: event.page.title } : {}),
...(event.page.location ? { location: event.page.location } : {}),
},
},
debug,
);
},
track: async (event) => {
maybeSetUninstallUrl(event.user.id);
await maybeTrackFirstOpen(event);
await sendEvent(
endpoint,
options.appKey,
{
name: event.event.name.trim(),
userId: event.user.id,
timestamp: event.meta.timestamp,
context: buildContext(event, extensionId),
properties: cleanProperties(event.event.properties),
},
debug,
);
},
};
},
);
@@ -0,0 +1,83 @@
import { defineAnalyticsProvider } from '../client';
export interface PostHogProviderOptions {
/** Your PostHog project API key. */
apiKey: string;
/**
* PostHog API host URL.
*
* @default 'https://us.i.posthog.com'
*/
apiHost?: string;
}
export const posthog = defineAnalyticsProvider<PostHogProviderOptions>(
(_, config, options) => {
const apiHost = (options.apiHost ?? 'https://us.i.posthog.com').replace(
/\/$/,
'',
);
const capture = async (
distinctId: string,
event: string,
properties: Record<string, unknown>,
): Promise<void> => {
if (config.debug) {
console.debug('[@wxt-dev/analytics] Sending event to PostHog:', {
event,
properties,
});
}
const body: PostHogCaptureBody = {
api_key: options.apiKey,
distinct_id: distinctId,
event,
properties,
timestamp: new Date().toISOString(),
};
await fetch(`${apiHost}/i/v0/e/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
};
return {
identify: async (event) => {
await capture(event.user.id, '$identify', {
$set: event.user.properties,
});
},
page: async (event) => {
await capture(event.user.id, '$pageview', {
$current_url: event.page.url,
$title: event.page.title,
$session_id: event.meta.sessionId,
$screen: event.meta.screen,
$language: event.meta.language,
$referrer: event.meta.referrer,
$set: event.user.properties,
});
},
track: async (event) => {
await capture(event.user.id, event.event.name, {
...event.event.properties,
$screen: event.meta.screen,
$language: event.meta.language,
$referrer: event.meta.referrer,
$set: event.user.properties,
});
},
};
},
);
/** @see https://posthog.com/docs/api/capture */
interface PostHogCaptureBody {
api_key: string;
distinct_id: string;
event: string;
properties: Record<string, unknown>;
timestamp: string;
}
+25 -16
View File
@@ -1,40 +1,49 @@
export interface Analytics {
/** Report a page change. */
page: (url: string) => void;
page: (url: string) => Promise<void>;
/** Report a custom event. */
track: (eventName: string, eventProperties?: Record<string, string>) => void;
track: (
eventName: string,
eventProperties?: Record<string, string | undefined>,
) => Promise<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. */
identify: (
userId: string,
userProperties?: Record<string, string>,
) => Promise<void>;
/**
* 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;
setEnabled: (enabled: boolean) => Promise<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 +103,6 @@ export interface AnalyticsPageViewEvent extends BaseAnalyticsEvent {
export interface AnalyticsTrackEvent extends BaseAnalyticsEvent {
event: {
name: string;
properties?: Record<string, string>;
properties?: Record<string, string | undefined>;
};
}
+37 -24
View File
@@ -1,14 +1,40 @@
{
"name": "@wxt-dev/analytics",
"version": "0.5.1",
"version": "0.5.5",
"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": "catalog:",
"@types/ua-parser-js": "^0.7.39",
"publint": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:",
"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",
@@ -29,35 +55,22 @@
"types": "./dist/providers/google-analytics-4.d.mts",
"default": "./dist/providers/google-analytics-4.mjs"
},
"./providers/moderok": {
"types": "./dist/providers/moderok.d.mts",
"default": "./dist/providers/moderok.mjs"
},
"./providers/umami": {
"types": "./dist/providers/umami.d.mts",
"default": "./dist/providers/umami.mjs"
},
"./providers/posthog": {
"types": "./dist/providers/posthog.d.mts",
"default": "./dist/providers/posthog.mjs"
}
},
"module": "./dist/index.mjs",
"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 -- tsdown",
"prepack": "pnpm -s build",
"prepare": "buildc --deps-only -- wxt prepare"
},
"peerDependencies": {
"wxt": ">=0.20.0"
},
"devDependencies": {
"@types/ua-parser-js": "^0.7.39",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"wxt": "workspace:*"
},
"dependencies": {
"@wxt-dev/browser": "workspace:^",
"ua-parser-js": "^1.0.40"
}
]
}
+5 -1
View File
@@ -9,8 +9,12 @@ export default defineConfig({
'providers/google-analytics-4':
'./modules/analytics/providers/google-analytics-4.ts',
'providers/umami': './modules/analytics/providers/umami.ts',
'providers/moderok': './modules/analytics/providers/moderok.ts',
'providers/posthog': './modules/analytics/providers/posthog.ts',
},
deps: {
neverBundle: ['#analytics'],
},
external: ['#analytics'],
define: {
'process.env.NPM': 'true',
},
+29
View File
@@ -1,5 +1,34 @@
# Changelog
## v1.1.1
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.1.0...auto-icons-v1.1.1)
### 🩹 Fixes
- Auto icons override default icons ([#1616](https://github.com/wxt-dev/wxt/pull/1616))
### 💅 Refactors
- Standardize file existence checks to `pathExists` ([#2083](https://github.com/wxt-dev/wxt/pull/2083))
### 🏡 Chore
- **deps:** Upgrade oxlint from 0.16.8 to 1.14.0 ([a01928e0](https://github.com/wxt-dev/wxt/commit/a01928e0))
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
- **deps:** Upgrade non-breaking production dependencies ([#1877](https://github.com/wxt-dev/wxt/pull/1877))
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
### ❤️ Contributors
- Omerfardemir <od080624@gmail.com>
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v1.1.0
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.2...auto-icons-v1.1.0)
+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`:
+11 -6
View File
@@ -24,7 +24,7 @@
}
],
"license": "MIT",
"version": "1.1.0",
"version": "1.1.1",
"type": "module",
"module": "./dist/index.mjs",
"types": "./dist/index.d.mts",
@@ -39,20 +39,25 @@
],
"scripts": {
"build": "buildc -- tsdown",
"check": "pnpm build && check"
"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.3",
"sharp": "^0.34.5"
},
"devDependencies": {
"oxlint": "^1.43.0",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"@aklinker1/buildc": "catalog:",
"oxlint": "catalog:",
"publint": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:",
"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, pathExists } 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 pathExists(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,
},
});
+9 -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.36",
"version": "0.2.2",
"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,11 +25,11 @@
"src"
],
"devDependencies": {
"@types/chrome": "0.1.36",
"fs-extra": "^11.3.3",
"nano-spawn": "^2.0.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
"@types/chrome": "0.2.2",
"@types/node": "catalog:",
"nano-spawn": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
},
"dependencies": {
"@types/filesystem": "*",
+19 -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,21 @@ 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)}`);
}
await Bun.$`bun install --ignore-scripts`;
// 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>();
});
});
});
+1
View File
@@ -1,5 +1,6 @@
/* DO NOT EDIT - generated by scripts/generate.ts */
/* eslint-disable @definitelytyped/strict-export-declare-modifiers */
/* eslint-disable @typescript-eslint/no-wrapper-object-types */
export namespace Browser {
////////////////////
+2 -2
View File
@@ -3,7 +3,7 @@
import { Entry, Log } from "har-format";
declare global {
export type HARFormatEntry = Entry;
export type HARFormatLog = Log;
type HARFormatEntry = Entry;
type HARFormatLog = Log;
}
+1955 -1836
View File
File diff suppressed because it is too large Load Diff
+7 -5
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,9 +26,9 @@
],
"devDependencies": {
"@types/chrome": "{{chromeTypesVersion}}",
"fs-extra": "^11.3.3",
"nano-spawn": "^2.0.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
"@types/node": "catalog:",
"nano-spawn": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
}
}
+57
View File
@@ -1,5 +1,62 @@
# Changelog
## v0.2.6
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.5...i18n-v0.2.6)
### 🚀 Enhancements
- Add named substitutions to i18n ([#2440](https://github.com/wxt-dev/wxt/pull/2440))
### 🏡 Chore
- Add `prettier-plugin-jsdoc` to project ([#2171](https://github.com/wxt-dev/wxt/pull/2171))
- Replace fast-glob and ora with lighter alternatives ([#2173](https://github.com/wxt-dev/wxt/pull/2173))
- **deps:** Upgrade deps ([#2175](https://github.com/wxt-dev/wxt/pull/2175))
- Add prepack script to all packages ([032f7931](https://github.com/wxt-dev/wxt/commit/032f7931))
- Remove `any` from `i18n`'s internal types ([#2201](https://github.com/wxt-dev/wxt/pull/2201))
- Add cspell and fix all typos ([6621aaf8](https://github.com/wxt-dev/wxt/commit/6621aaf8))
- Migrate monorepo to use Bun instead of PNPM ([#2009](https://github.com/wxt-dev/wxt/pull/2009))
- Add new types for i18n ([#2121](https://github.com/wxt-dev/wxt/pull/2121))
- **deps-dev:** Bump typescript from 5.9.3 to 6.0.3 ([#2325](https://github.com/wxt-dev/wxt/pull/2325))
- **deps-dev:** Bump oxlint from 1.59.0 to 1.63.0 ([#2356](https://github.com/wxt-dev/wxt/pull/2356))
- Use `catalog:` for dev dependencies ([#2357](https://github.com/wxt-dev/wxt/pull/2357))
- **deps:** Bump tinyglobby from 0.2.15 to 0.2.16 ([#2366](https://github.com/wxt-dev/wxt/pull/2366))
- Add benchmarks for i18n vs vanilla usage ([#2372](https://github.com/wxt-dev/wxt/pull/2372))
### ❤️ Contributors
- T ([@cookesan](https://github.com/cookesan))
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
- Florian Metz ([@Timeraa](https://github.com/Timeraa))
## 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)

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