Compare commits

..

12 Commits

Author SHA1 Message Date
Aaron c9205bb08f feat: New @wxt-dev/is-background package (#2152)
📼 VHS / Create VHS (push) Cancelled after 0s
Co-authored-by: Smit <sm17p@users.noreply.github.com>
2026-02-22 13:57:09 -06:00
Tam Dang 05e23e70b4 fix(types): include CSS entrypoints in PublicPath generation (#2150)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2026-02-22 09:00:58 -06:00
Aaron 68fa36ef2b ci: Fix auto-labeling workflow for forks 2026-02-22 08:57:06 -06:00
Aaron 182d575205 feat(modules): Add support for augumenting entrypoint options (#2149) 2026-02-21 15:57:49 -06:00
Aaron 09eadf6796 docs: Add deep outline to modules page to show recipes 2026-02-21 15:57:25 -06:00
Aaron a6904b6319 ci: Create workflow for labeling and adding authors to PRs (#2148) 2026-02-21 15:29:04 -06:00
Nick Doan 42d65cb10b 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-21 20:58:15 +00:00
Aaron 636cf1f841 fix: Allow userId option to return undefined 2026-02-21 14:48:28 -06:00
Patryk Kuniczak 3b154508bd chore: Created new types, instead of any for analytics (#2119) 2026-02-21 20:20:42 +00:00
Patryk Kuniczak d34eac9a4c chore: Remove @ts-expect-error from manifest.test.ts and fix typo (#2123) 2026-02-21 14:20:07 -06:00
Patryk Kuniczak 54c6fcca10 chore: Simplify imports in wxt/e2e (#2122) 2026-02-21 18:44:56 +00:00
Patryk Kuniczak 0114ee6a7a chore!: Rename zipdir to zipDir (#2126) 2026-02-21 12:40:34 -06:00
257 changed files with 13668 additions and 11164 deletions
-1
View File
@@ -7,7 +7,6 @@
pnpm-lock.yaml linguist-generated
package-lock.json linguist-generated
bun.lockb linguist-generated
bun.lock linguist-generated
yarn.lock linguist-generated
# Exclude templates from language statistics
+1 -4
View File
@@ -1,5 +1,5 @@
# Set default
* @aklinker1
* @aklinker1 @Timeraa
# Secure Directories
/.github/ @aklinker1
@@ -7,6 +7,3 @@
# Creator of specific wxt modules
/packages/auto-icons/ @Timeraa
/packages/unocss/ @Timeraa
# Other Directories
/docs @aklinker1 @PatrykKuniczak
+14 -6
View File
@@ -1,21 +1,29 @@
name: Basic Setup
description: Install Bun and dependencies
description: Install PNPM, Node, and dependencies
inputs:
install:
default: 'true'
description: Whether or not to run 'bun install'
description: Whether or not to run 'pnpm install'
installArgs:
default: ''
description: Additional args to append to "pnpm install"
runs:
using: composite
steps:
- name: 🛠️ Setup Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
- name: 🛠️ Setup PNPM
uses: pnpm/action-setup@f2b2b233b538f500472c7274c7012f57857d8ce0 # v4.1.0
- name: 🛠️ Setup NodeJS
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
bun-version-file: '.tool-versions'
node-version: 20
cache: pnpm
- name: 📦 Install Dependencies
if: ${{ inputs.install == 'true' }}
shell: bash
run: bun ci ${{ inputs.installArgs }}
run: pnpm install ${{ inputs.installArgs }}
+7 -12
View File
@@ -1,20 +1,15 @@
# 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: bun
- package-ecosystem: npm
directory: /
schedule:
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]
interval: 'monthly'
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'daily'
interval: 'monthly'
+3 -3
View File
@@ -13,7 +13,7 @@ jobs:
steps:
- name: Gather Info
id: check
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const { data: pr } = await github.rest.pulls.get({
@@ -59,7 +59,7 @@ jobs:
- name: Sync Author
if: steps.check.outputs.has_assignees == 'false'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
await github.rest.issues.addAssignees({
@@ -72,7 +72,7 @@ jobs:
console.log('Assigned PR author: ${{ steps.check.outputs.author }}');
- name: Sync Labels
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
const newLabels = ${{ steps.check.outputs.labels }};
+2 -7
View File
@@ -23,12 +23,7 @@ jobs:
uses: ./.github/actions/setup
- name: Build All Packages
run: bun run buildc all
run: pnpm buildc all
- name: Publish
run: bunx pkg-pr-new publish --compact --bun './packages/*'
- name: DEBUG
if: always()
run: ls -lhA
working-directory: packages/analytics
run: pnpx pkg-pr-new publish --compact --pnpm './packages/*'
+1 -14
View File
@@ -15,21 +15,8 @@ jobs:
if: github.event.pull_request.merged == true
steps:
- name: Log PR Author Info
run: |
echo "PR Author Login: ${{ github.event.pull_request.user.login }}"
echo "PR Author ID: ${{ github.event.pull_request.user.id }}"
echo "PR Author Type: ${{ github.event.pull_request.user.type }}"
echo "PR Author Node ID: ${{ github.event.pull_request.user.node_id }}"
- name: Post Thank You Comment
if: >-
github.event.pull_request.user.login != 'aklinker1' &&
github.event.pull_request.user.login != 'Timeraa' &&
github.event.pull_request.user.login != 'PatrykKuniczak' &&
github.event.pull_request.user.login != 'dependabot[bot]' &&
github.event.pull_request.user.login != 'copilot-swe-agent[bot]'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
comment: Thanks for helping make WXT better!
with:
+3 -3
View File
@@ -29,7 +29,7 @@ jobs:
uses: ./.github/actions/setup
- name: Login to Docker Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
@@ -37,11 +37,11 @@ jobs:
- name: Build docs
run: |
bun run docs:build
pnpm 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 -H "X-API-Key: ${{ secrets.DOCS_WEBHOOK_API_KEY }}" https://${{ secrets.DOCS_WEBHOOK_DOMAIN }}/api/webhook/stacks/${{ secrets.DOCS_WEBHOOK_STACK_ID }}'
run: curl -X POST -i ${{ secrets.UPDATE_DOCS_WEBHOOK }}
+4 -4
View File
@@ -56,18 +56,18 @@ jobs:
- name: Bump and Tag
run: |
bun run scripts/bump-package-version.ts ${{ inputs.package }}
pnpm tsx scripts/bump-package-version.ts ${{ inputs.package }}
git push
git push --tags
- name: Publish to NPM
working-directory: packages/${{ inputs.package }}
run: |
bun pm pack
pnpm pack
sudo npm i -g npm@latest
npm publish *.tgz
/usr/local/bin/npm publish *.tgz
- name: Create GitHub release
run: bun run scripts/create-github-release.ts ${{ inputs.package }}
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -39,6 +39,6 @@ jobs:
installArgs: --ignore-scripts
- name: Sync Releases
run: bun run scripts/sync-releases.ts ${{ inputs.package }}
run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+4 -4
View File
@@ -28,11 +28,11 @@ jobs:
- name: Generate Latest Code
working-directory: packages/browser
run: bun run gen
run: pnpm gen
- name: Run Checks
working-directory: packages/browser
run: bun run check
run: pnpm check
- name: Commit Changes
id: commit
@@ -46,6 +46,6 @@ jobs:
if: steps.commit.outputs.changes_detected == 'true'
working-directory: packages/browser
run: |
bun pm pack
pnpm pack
sudo npm i -g npm@latest
npm publish *.tgz
/usr/local/bin/npm publish *.tgz
+13 -15
View File
@@ -21,7 +21,7 @@ jobs:
uses: ./.github/actions/setup
- name: Basic Checks
run: bun run check
run: pnpm check
builds:
name: Builds
@@ -34,7 +34,7 @@ jobs:
uses: ./.github/actions/setup
- name: Build All Packages
run: bun run buildc all
run: pnpm buildc all
build-demo:
name: Build Demo
@@ -47,11 +47,11 @@ jobs:
uses: ./.github/actions/setup
- name: Build
run: bun run build:all
run: pnpm build:all
working-directory: packages/wxt-demo
- name: ZIP
run: bun run wxt zip
run: pnpm wxt zip
working-directory: packages/wxt-demo
tests:
@@ -73,22 +73,20 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
- name: Setup PNPM
uses: pnpm/action-setup@91ab88e2619ed1f46221f0ba42d1492c02baf788 # v6.0.6
with:
version: 10.33.0
- name: Setup Bun
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
- name: Run Tests
if: ${{ ! matrix.coverage }}
run: bun run test
run: pnpm test
- name: Run Tests (Coverage)
if: matrix.coverage
run: bun run test:coverage --reporter=default --reporter=hanging-process
run: pnpm test:coverage --reporter=default --reporter=hanging-process
- name: Upload Coverage
if: matrix.coverage
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
@@ -112,7 +110,7 @@ jobs:
uses: ./.github/actions/setup
- name: Pack WXT package
run: bun pm pack
run: pnpm pack
working-directory: packages/wxt
- name: Install Dependencies
@@ -124,15 +122,15 @@ jobs:
working-directory: templates/${{ matrix.template }}
- name: Type Check Template
run: bun run compile
run: pnpm compile
if: matrix.template != 'svelte'
working-directory: templates/${{ matrix.template }}
- name: Type Check Template
run: bun run check
run: pnpm check
if: matrix.template == 'svelte'
working-directory: templates/${{ matrix.template }}
- name: Build Template
run: bun run build
run: pnpm build
working-directory: templates/${{ matrix.template }}
+7 -20
View File
@@ -11,27 +11,18 @@ permissions:
jobs:
vhs:
name: Create VHS
runs-on: macos-latest
runs-on: ubuntu-22.04
if: ${{ github.repository == 'wxt-dev/wxt' }}
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.ref_name }}
- name: Setup PNPM
uses: pnpm/action-setup@91ab88e2619ed1f46221f0ba42d1492c02baf788 # v6.0.6
- name: Setup NodeJS
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- name: Setup
uses: ./.github/actions/setup
with:
cache: pnpm
- name: Setup Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c
with:
go-version: '1.25.1'
install: false
# This prevents pnpm dlx from downloading WXT in the video
- name: Pre-install WXT
@@ -40,10 +31,9 @@ jobs:
pnpm dlx wxt@latest --version
- name: Record VHS
run: |
brew install ttyd ffmpeg
go install github.com/charmbracelet/vhs@517bcda0faf416728bcf6b7fe489eb0e2469d9b5 # v0.10.0
vhs docs/tapes/init-demo.tape
uses: charmbracelet/vhs-action@59641cdc7fadf3978db65eb8c6937ea2752f4ec3 # v2.1.0
with:
path: 'docs/tapes/init-demo.tape'
- name: Save recorded GIF
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
@@ -51,8 +41,5 @@ jobs:
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'
+1 -2
View File
@@ -1,14 +1,12 @@
.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
@@ -22,5 +20,6 @@ templates/*/yarn.lock
templates/*/package-lock.json
docs/api/reference
stats.html
.tool-versions
.cache
*-stats.txt
-2
View File
@@ -1,4 +1,2 @@
singleQuote: true
endOfLine: lf
plugins:
- prettier-plugin-jsdoc
-2
View File
@@ -1,2 +0,0 @@
bun 1.3.13
nodejs 24.14.1
+35 -58
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 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.
I also want to provide a way for developers to use either one of those two things independently, and not require them to use both. This is why all of WXT's runtime utils are shipped as their own NPM packages, most of them not bundled inside the core `wxt` package. If you just want to use the packages, they're availalbe, and if you just want to use WXT's built tool, you don't have to import any of WXT's utilities, you can use your own.
> The few runtime utils shipped inside WXT are things that should be used by 90% of extensions. That said, they're also legacy utils left in from before I started creating separate NPM packages, and in the future, they may be removed from the core package.
@@ -48,12 +48,16 @@ To make a breaking change:
## Setup
WXT uses Bun for package management and development. Install it from: <https://bun.com/>
Then install dependencies:
WXT uses `pnpm`, so make sure you have it installed.
```sh
bun install
corepack enable
```
Then, simply run the install command:
```sh
pnpm i
```
## Development
@@ -61,68 +65,48 @@ bun install
Here are some helpful commands:
```sh
# Build WXT package and workspace dependencies
bun run --filter wxt build
# Build WXT package
cd packages/wxt
pnpm build
```
```sh
# Build workspace dependencies, then start the demo extension in dev mode
bun run --filter wxt-demo dev
# 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
```
```sh
# Run unit and E2E tests
bun run test
pnpm test
```
```sh
# Start the docs website locally
bun run docs:dev
pnpm 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
bun run --filter wxt build
pnpm --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.
@@ -138,7 +122,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
bun run --filter wxt test manifest-contents
pnpm test manifest-contents
```
All test (unit and E2E) for all packages are ran together via [Vitest workspaces](https://vitest.dev/guide/#workspaces-support).
@@ -147,7 +131,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 `bun`), 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 `pnpm`), and run the relevant commands
```sh
cd templates/vue
@@ -163,7 +147,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": "../../packages/wxt"
+ "wxt": "../.."
}
```
@@ -179,25 +163,18 @@ cp -r templates/vanilla templates/<new-template-name>
That's it. Once your template is merged, it will be available inside `wxt init` immediately. You don't need to release a new version of WXT to release a new template.
## Releasing Updates
Releases are done with GitHub actions:
- Use the [Release workflow](https://github.com/wxt-dev/wxt/actions/workflows/release.yml) to release a single package in the monorepo. This automatically detects the version change with conventional commits, builds and uploads the package to NPM, and creates a GitHub release.
- Use the [Sync Releases workflow](https://github.com/wxt-dev/wxt/actions/workflows/sync-releases.yml) to sync the GitHub releases with changes to the changelog. To change a release, update the `CHANGELOG.md` file and run the workflow. It will sync the releases of a single package in the monorepo.
## Upgrading Dependencies
WXT has custom rules around what dependencies can be upgraded. Use the `scripts/upgrade-deps.ts` script to upgrade dependencies and follow these rules.
```sh
bun run scripts/upgrade-deps.ts
pnpm tsx scripts/upgrade-deps.ts
```
To see all the options, run:
```sh
bun run scripts/upgrade-deps.ts --help
pnpm tsx scripts/upgrade-deps.ts --help
```
## Install Unreleased Versions
@@ -235,4 +212,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 questions, 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 questsions, review PRs, organize and prioritize work - there's lots of ways for you to help out.
+1 -1
View File
@@ -78,7 +78,7 @@ Example PR: <https://github.com/wxt-dev/wxt/pull/2152>
```sh
cd packages/<dir-name>
bun publish --access public
pnpm publish --access public
```
7. Create a basic release on GitHub mentioning the new package is available.
+6 -2
View File
@@ -4,8 +4,12 @@ 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
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.
Use this section to tell people how to report a vulnerability.
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.
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.
-->
-2983
View File
File diff suppressed because it is too large Load Diff
-121
View File
@@ -1,121 +0,0 @@
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
- buildc
- bunx
- cachable
- cacjs
- charmbracelet
- chromedriver
- Cira
- comctx
- confbox
- configfile
- consolas
- Cpath
- crbug
- crunchyroll
- cssinjs
- CSUI
- Csvg
- defu
- deinit
- dequal
- dtrace
- duckduckgo
- esbuild
- esque
- extglob
- favicons
- fieldtrials
- firefoxdeveloperedition
- flamechart
- Florian
- focusring
- frontmatter
- giget
- grayscale
- greyscale
- gtag
- hckhakegfgenefhikdcfkaaonnclljmf
- hookable
- iconify
- jsons
- Kenobi
- keystyle
- klinker
- linkedom
- llms
- locationchange
- magicast
- Menlo
- menupanel
- metas
- mktemp
- mycompany
- nacl
- nanospinner
- natsu
- newtab
- njsproj
- nodebuffer
- Noto
- ntvs
- nypm
- obug
- ohash
- oklab
- oxlint
- pbbf
- personaltoolbar
- portaling
- prebundled
- prefs
- proxified
- pWXLO
- repost
- rxliuli
- sandboxed
- scule
- Segoe
- sidepanel
- sidepanels
- solidjs
- styl
- subdependency
- tabstrip
- teleporting
- treemap
- triaging
- umami
- ungh
- unimport
- unocss
- unrun
- unstub
- unwatch
- USSS
- vitepress
- webext
- webextension
- webextrc
- xcrun
- xlink
+1 -2
View File
@@ -3,9 +3,8 @@ 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<Post[]>(() =>
const posts = computed(() =>
data
.map((post) => ({
...post,
+5 -6
View File
@@ -1,22 +1,21 @@
<script lang="ts" setup>
import useBlogDate from '../composables/useBlogDate';
import { Content, useData } from 'vitepress';
import { PostFrontmatter } from '../utils/types';
import { useData } from 'vitepress';
const { frontmatter } = useData<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" />
<h1 v-html="$frontmatter.title" />
<p class="meta-row">
<a
v-for="author of frontmatter.authors"
class="author"
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`" />
<span>{{ author.name }}</span>
@@ -1,5 +1,5 @@
<script lang="ts" setup>
defineProps<{
const props = defineProps<{
patterns: Array<[intput: string, output: string]>;
}>();
</script>
+3 -3
View File
@@ -1,10 +1,10 @@
<script lang="ts" setup>
import { computed, onMounted, ref, Ref, toRaw } from 'vue';
import { ref, onMounted, computed, toRaw, Ref } from 'vue';
import ExampleSearchFilterByItem from './ExampleSearchFilterByItem.vue';
import ExampleSearchResult from './ExampleSearchResult.vue';
import { ExamplesMetadata, KeySelectedObject } from '../utils/types';
defineProps<{
const props = defineProps<{
tag?: string;
}>();
@@ -48,7 +48,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(
@@ -2,7 +2,7 @@
import { computed, toRaw } from 'vue';
import { KeySelectedObject } from '../utils/types';
defineProps<{
const props = defineProps<{
label: string;
items?: string[];
}>();
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { Example } from '../utils/types';
defineProps<{
const props = defineProps<{
example: Example;
}>();
</script>
+181 -182
View File
@@ -1,120 +1,170 @@
<script lang="ts" setup>
import { computed } from 'vue';
import useListExtensionDetails from '../composables/useListExtensionDetails';
import _extensionEntries from '../../assets/extension-showcase.yml';
import useListExtensionDetails, {
ChromeExtension,
} from '../composables/useListExtensionDetails';
interface StoreLink {
label: string;
url: string;
}
interface ListedExtension {
id: string;
name: string;
iconUrl: string;
shortDescription: string;
users: number;
rating: number | undefined;
stores: StoreLink[];
}
const extensionEntries = _extensionEntries as Array<{
chromeId?: string;
firefoxSlug?: string;
}>;
const chromeIds = extensionEntries.flatMap((e) =>
e.chromeId ? [e.chromeId] : [],
);
const firefoxSlugs = [
...new Set(
extensionEntries.flatMap((e) => (e.firefoxSlug ? [e.firefoxSlug] : [])),
),
// 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
'dhiekgdaipindoapjmcnpompdknjeijf', // Glossy New Tab - Say Goodbye to Boring Tabs with live wallpapers
'lapnciffpekdengooeolaienkeoilfeo', // All API Hub AI Relay & New API Manager - https://github.com/qixing-jk/all-api-hub
];
const { data, isLoading } = useListExtensionDetails(chromeIds, firefoxSlugs);
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
const sortedExtensions = computed(() => {
if (!data.value?.length) return [];
function addUtmSource(storeUrl: string): string {
const url = new URL(storeUrl);
url.searchParams.set('utm_source', 'wxt.dev');
return url.href;
}
const sortedExtensions = computed((): ListedExtension[] => {
if (!data.value) return [];
const chromeById = new Map(
(data.value.chrome ?? []).filter(Boolean).map((e) => [e.id, e]),
);
const firefoxBySlug = new Map(
(data.value.firefox ?? []).filter(Boolean).map((e) => [e.id, e]),
);
const results: ListedExtension[] = [];
for (const entry of extensionEntries) {
const chrome = entry.chromeId ? chromeById.get(entry.chromeId) : undefined;
const firefox = entry.firefoxSlug
? firefoxBySlug.get(entry.firefoxSlug)
: undefined;
if (!chrome && !firefox) continue;
const primary = chrome ?? firefox!;
const users = (chrome?.users ?? 0) + (firefox?.users ?? 0);
const rating = chrome?.rating ?? firefox?.rating;
const stores: StoreLink[] = [];
if (chrome) {
stores.push({ label: 'Chrome', url: addUtmSource(chrome.storeUrl) });
}
if (firefox) {
stores.push({ label: 'Firefox', url: addUtmSource(firefox.storeUrl) });
}
results.push({
id: entry.chromeId ?? `firefox:${entry.firefoxSlug}`,
name: primary.name,
iconUrl: primary.iconUrl,
shortDescription: primary.shortDescription,
users,
rating,
stores,
});
}
const score = (e: ListedExtension) => ((e.rating ?? 5) / 5) * e.users;
return results.sort((a, b) => score(b) - score(a));
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 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`;
function getStoreUrl(extension: ChromeExtension) {
const url = new URL(extension.storeUrl);
url.searchParams.set('utm_source', 'wxt.dev');
return url.href;
}
</script>
<template>
<p v-if="isLoading" style="text-align: center; opacity: 50%">Loading...</p>
<p
v-else-if="sortedExtensions.length === 0"
v-else-if="err || sortedExtensions.length === 0"
style="text-align: center; opacity: 50%"
>
Failed to load extension details.
</p>
<ul v-else class="extension-grid">
<li v-for="extension of sortedExtensions" :key="extension.id">
<ul v-else>
<li
v-for="extension of sortedExtensions"
:key="extension.id"
class="relative"
>
<img
:src="extension.iconUrl"
:alt="`${extension.name} icon`"
referrerpolicy="no-referrer"
/>
<div class="card-content">
<div class="relative">
<a
:href="extension.stores[0]?.url ?? '#'"
:href="getStoreUrl(extension)"
target="_blank"
:title="extension.name"
class="extension-name"
@@ -123,40 +173,19 @@ function formatStars(r: number): string {
<p class="description" :title="extension.shortDescription">
{{ extension.shortDescription }}
</p>
<div v-if="extension.stores.length > 0" class="store-stats">
<span class="store-stats-info">
<span>{{ formatUsers(extension.users) }}</span>
<template v-if="extension.rating">
<span class="store-stats-sep" aria-hidden="true">,</span>
<span>{{ formatStars(extension.rating) }}</span>
</template>
</span>
<span class="store-stats-sep" aria-hidden="true">&middot;</span>
<span class="store-links">
<a
v-for="(store, i) of extension.stores"
:key="store.label"
:href="store.url"
target="_blank"
class="store-link"
:title="store.label"
>
{{ store.label
}}<span
v-if="i < extension.stores.length - 1"
class="store-stats-sep"
aria-hidden="true"
>,&nbsp;</span
>
</a>
</span>
</div>
</div>
<p class="user-count">
<span>{{ extension.weeklyActiveUsers.toLocaleString() }} users</span
><template v-if="extension.rating != null"
>,
<span>{{ extension.rating }} stars</span>
</template>
</p>
</li>
</ul>
<p class="centered pr">
<a
href="https://github.com/wxt-dev/wxt/edit/main/docs/assets/extension-showcase.yml"
href="https://github.com/wxt-dev/wxt/edit/main/docs/.vitepress/components/UsingWxtSection.vue"
target="_blank"
>Open a PR</a
>
@@ -165,7 +194,7 @@ function formatStars(r: number): string {
</template>
<style scoped>
.extension-grid > li > img {
li img {
width: 116px;
height: 116px;
padding: 16px;
@@ -173,7 +202,7 @@ function formatStars(r: number): string {
background-color: var(--vp-c-default-soft);
}
.extension-grid {
ul {
display: grid;
grid-template-columns: repeat(1, 1fr);
align-items: stretch;
@@ -183,12 +212,12 @@ function formatStars(r: number): string {
padding: 0;
}
@media (min-width: 960px) {
.extension-grid {
ul {
grid-template-columns: repeat(2, 1fr);
}
}
.extension-grid > li {
li {
margin: 0 !important;
padding: 16px;
display: flex;
@@ -196,68 +225,28 @@ function formatStars(r: number): string {
border-radius: 12px;
flex: 1;
gap: 24px;
align-items: stretch;
align-items: center;
}
.centered {
text-align: center;
}
.extension-grid > li .description {
li a,
li .user-count,
li .description {
padding: 0;
margin: 0;
}
.extension-grid > li .card-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
padding-right: 0;
li .user-count {
opacity: 70%;
font-size: small;
position: absolute;
bottom: 12px;
right: 16px;
}
.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 {
li a {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
@@ -266,23 +255,33 @@ function formatStars(r: number): string {
padding: 0;
margin: 0;
text-decoration: none;
font-size: large;
}
.extension-grid > li .extension-name:hover {
li a:hover {
text-decoration: underline;
}
.extension-grid > li .description {
li div {
flex: 1;
}
li .description {
opacity: 90%;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
margin-bottom: 0 !important;
margin-bottom: 16px;
}
li .extension-name {
font-size: large;
}
.pr {
opacity: 70%;
}
.relative {
position: relative;
}
</style>
@@ -1,58 +1,39 @@
import { ref } from 'vue';
export interface Extension {
export interface ChromeExtension {
id: string;
name: string;
iconUrl: string;
weeklyActiveUsers: number;
shortDescription: string;
storeUrl: string;
rating: number | undefined;
users: number;
}
export interface ExtensionResults {
chrome: Extension[];
firefox: Extension[];
}
const operationName = 'WxtDocsUsedBy';
const query = `query ${operationName}($chromeIds: [String!]!, $firefoxIds: [String!]!) {
chromeExtensions(ids: $chromeIds) {
const query = `query ${operationName}($ids:[String!]!) {
chromeExtensions(ids: $ids) {
id
...ExtensionData
name
iconUrl
weeklyActiveUsers
shortDescription
storeUrl
rating
}
firefoxAddons(ids: $firefoxIds) {
id: slug
...ExtensionData
}
}
fragment ExtensionData on Extension {
name
iconUrl
shortDescription
storeUrl
rating
users
}`;
export default function (chromeIds: string[], firefoxSlugs: string[]) {
const data = ref<ExtensionResults>();
export default function (ids: string[]) {
const data = ref<ChromeExtension[]>();
const err = ref<unknown>();
const isLoading = ref(true);
if (chromeIds.length === 0 && firefoxSlugs.length === 0) {
data.value = { chrome: [], firefox: [] };
isLoading.value = false;
return { data, err, isLoading };
}
fetch('https://queue.wxt.dev/api', {
method: 'POST',
body: JSON.stringify({
operationName,
query,
variables: { chromeIds, firefoxIds: firefoxSlugs },
variables: { ids },
}),
headers: {
'Content-Type': 'application/json',
@@ -60,11 +41,10 @@ export default function (chromeIds: string[], firefoxSlugs: string[]) {
})
.then(async (res) => {
isLoading.value = false;
const { data: responseData } = await res.json();
data.value = {
chrome: responseData.chromeExtensions ?? [],
firefox: responseData.firefoxAddons ?? [],
};
const {
data: { chromeExtensions },
} = await res.json();
data.value = chromeExtensions;
err.value = undefined;
})
.catch((error) => {
@@ -74,5 +54,9 @@ export default function (chromeIds: string[], firefoxSlugs: string[]) {
err.value = error;
});
return { data, err, isLoading };
return {
data,
err,
isLoading,
};
}
+21 -37
View File
@@ -1,26 +1,5 @@
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,
@@ -28,6 +7,26 @@ 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 { version as isBackgroundVersion } from '../../packages/is-background/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';
@@ -74,11 +73,8 @@ 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(
@@ -87,17 +83,6 @@ 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,
@@ -128,7 +113,7 @@ export default defineConfig({
});
// console.log('rss.xml:');
// console.log(feed.rss2());
await fs.writeFile(join(site.outDir, 'rss.xml'), feed.rss2());
await writeFile(join(site.outDir, 'rss.xml'), feed.rss2(), 'utf8');
},
head: [
@@ -239,7 +224,6 @@ 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'),
+2 -8
View File
@@ -1,9 +1,3 @@
import { type ContentData, createContentLoader } from 'vitepress';
import { PostFrontmatter } from '../utils/types';
import { createContentLoader } from 'vitepress';
declare const data: Array<ContentData & { frontmatter: PostFrontmatter }>;
export { data };
export default createContentLoader<
Array<ContentData & { frontmatter: PostFrontmatter }>
>('blog/*.md');
export default createContentLoader('blog/*.md');
+11 -8
View File
@@ -1,7 +1,6 @@
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, '**');
@@ -39,20 +38,24 @@ export default {
async function getHelp(command: string): Promise<string> {
const args = command.split(' ');
const result = await spawn(args[0], [...args.slice(1), '--help'], {
const res = await spawn(args[0], [...args.slice(1), '--help'], {
cwd: 'packages/wxt',
});
return result.stdout;
return res.stdout;
}
async function getWxtHelp(command: string): Promise<string> {
const res = await getHelp(`bun run --silent wxt ${command}`.trim());
return res.replaceAll('{{version}}', version);
function getWxtHelp(command: string): Promise<string> {
return getHelp(`pnpm -s wxt ${command}`.trim());
}
async function getPublishExtensionHelp(command: string): Promise<string> {
const res = await getHelp(
`bun run --silent publish-extension ${command}`.trim(),
`./node_modules/.bin/publish-extension ${command}`.trim(),
);
return res.replace(/\$ publish-extension/g, '$ wxt submit');
}
export interface Command {
name: string;
docs: string;
}
-26
View File
@@ -66,29 +66,3 @@ body {
.dark .dark-netlify {
display: inline;
}
/* Add Aaron's favorite pic from Artemis II to the hero section because it's cool. */
.dark .VPHome::before {
content: '';
background-image: url('/assets/art002e009298.jpg');
opacity: 50%;
position: absolute;
width: 100%;
height: 110vh;
background-size: cover;
background-position: 90%;
pointer-events: none;
}
.dark .VPHome::after {
content: '';
position: absolute;
width: 100%;
height: 10vh;
top: 100vh;
background: linear-gradient(to bottom, transparent, var(--vp-c-bg));
pointer-events: none;
z-index: 0;
}
.item {
z-index: 1;
}
+1 -2
View File
@@ -6,11 +6,10 @@ 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';
export default {
extends: DefaultTheme,
enhanceApp(ctx: EnhanceAppContext) {
enhanceApp(ctx) {
ctx.app
.component('Icon', Icon)
.component('EntrypointPatterns', EntrypointPatterns)
+1 -1
View File
@@ -1,4 +1,4 @@
import type { HeadConfig } from 'vitepress';
import { HeadConfig } from 'vitepress/types/shared';
export function meta(
property: string,
+14 -2
View File
@@ -1,6 +1,8 @@
import { DefaultTheme } from 'vitepress';
type SidebarItem = DefaultTheme.SidebarItem;
type NavItem = DefaultTheme.NavItem;
type NavItemWithLink = DefaultTheme.NavItemWithLink;
type NavItemWithChildren = DefaultTheme.NavItemWithChildren;
type NavItemChildren = DefaultTheme.NavItemChildren;
@@ -17,7 +19,7 @@ export function navItem(text: string, arg2?: unknown): any {
}
export function menuRoot(items: SidebarItem[]) {
return items.map((item) => {
return items.map((item, index) => {
// item.collapsed = false; // uncomment to expand all level-0 items
return item;
});
@@ -58,6 +60,12 @@ export function menuGroup(
throw Error('Unknown overload');
}
export function menuItems(items: SidebarItem[]) {
return {
items,
};
}
export function menuItem(
text: string,
link: string,
@@ -69,7 +77,9 @@ 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);
@@ -89,7 +99,9 @@ export function prepareTypedocSidebar(items: SidebarItem[]) {
}
};
// process
prepareItems(filtered);
// return
return filtered;
}
-14
View File
@@ -1,5 +1,3 @@
import { ContentData } from 'vitepress';
export interface Example {
name: string;
description?: string;
@@ -18,15 +16,3 @@ 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.

Before

Width:  |  Height:  |  Size: 237 KiB

-460
View File
@@ -1,460 +0,0 @@
# Add extension entries to end of the list. On the website, extensions will be sorted by a combination of users and rating.
#
# Change the commit message or PR title to:
#
# > "docs: Added "[extension name]" to showcase"
#
# Use:
#
# - `chromeId` for the Chrome Web Store listing,
# - `firefoxSlug` for Firefox Add-ons (slug from https://addons.mozilla.org/firefox/addon/{slug}/)
#
# You may include one or more of these fields for your extension.
- # GitHub: Better Line Counts
chromeId: ocfdgncpifmegplaglcnglhioflaimkd
firefoxSlug: github-better-line-counts
- # Anime Skip Player
chromeId: mgmdkjcljneegjfajchedjpdhbadklcf
firefoxSlug: anime-skip
- # UltraWideo
chromeId: bfbnagnphiehemkdgmmficmjfddgfhpl
firefoxSlug: ultrawideo
- # StayFree - Website Blocker & Web Analytics
chromeId: elfaihghhjjoknimpccccmkioofjjfkf
firefoxSlug: stayfree
- # Doozy: Ai Made Easy
chromeId: okifoaikfmpfcamplcfjkpdnhfodpkil
- # tl;dv - Record, Transcribe & ChatGPT for Google Meet
chromeId: lknmjhcajhfbbglglccadlfdjbaiifig
- # Youtube中文配音
chromeId: oglffgiaiekgeicdgkdlnlkhliajdlja
- # PreMiD
chromeId: agjnjboanicjcpenljmaaigopkgdnihi
- # Markdown Sticky Notes
chromeId: aiakblgmlabokilgljkglggnpflljdgp
firefoxSlug: markdown-sticky-notes
- # DocVersionRedirector
chromeId: nomnkbngkijpffepcgbbofhcnafpkiep
- # Plex Skipper
chromeId: ceicccfeikoipigeghddpocceifjelph
firefoxSlug: plex-skipper
- # GitHub Custom Notifier
chromeId: aelkipgppclpfimeamgmlonimflbhlgf
firefoxSlug: github-custom-notifier
- # Fluent Read
chromeId: djnlaiohfaaifbibleebjggkghlmcpcj
- # Facebook Video Controls
chromeId: nhclljcpfmmaiojbhhnkpjcfmacfcian
firefoxSlug: facebook-video-controls
- # ElemSnap - Quick capture of webpage elements and conversion to images,
chromeId: mblkhbaakhbhiimkbcnmeciblfhmafna
- # MS Edge TTS (Text to Speech)
chromeId: oajalfneblkfiejoadecnmodfpnaeblh
firefoxSlug: ms-edge-tts-text-to-speech
- # YTBlock - Block any content from YouTube™
chromeId: nedcanggplmbbgmlpcjiafgjcpdimpea
- # demo.fun - Interactive product demos that convert
chromeId: oadbjpccljkplmhnjekgjamejnbadlne
- # SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
chromeId: iopdafdcollfgaoffingmahpffckmjni
- # WorkFlowy MultiFlow
chromeId: khjdmjcmpolknpccmaaipmidphjokhdf
- # 香草布丁🌿🍮- https://github.com/Xdy1579883916/vanilla-pudding
chromeId: fencadnndhdeggodopebjgdfdlhcimfk
- # MaxFocus: Link Preview
chromeId: bnacincmbaknlbegecpioobkfgejlojp
firefoxSlug: maxfocus-link-preview
- # 汇率转换-中文版本
chromeId: bcpgdpedphodjcjlminjbdeejccjbimp
- # Currency Converter Plus
chromeId: loeilaonggnalkaiiaepbegccilkmjjp
firefoxSlug: currency-converter-plus
- # Respond Easy
chromeId: npcnninnjghigjfiecefheeibomjpkak
- # mindful - stay focused on your goals
chromeId: cfkdcideecefncbglkhneoflfnmhoicc
- # 1Proompt
chromeId: lnhejcpclabmbgpiiomjbhalblnnbffg
- # NiceTab - https://github.com/web-dahuyou/NiceTab
chromeId: fonflmjnjbkigocpoommgmhljdpljain
- # Draftly for LinkedIn
chromeId: fcffekbnfcfdemeekijbbmgmkognnmkd
- # YouTube Summarized - Summarize any YouTube video
chromeId: nkndldfehcidpejfkokbeghpnlbppdmo
- # 社媒助手 - https://github.com/iszhouhua/social-media-copilot
chromeId: dbichmdlbjdeplpkhcejgkakobjbjalc
- # Dofollow Links for SEO
chromeId: opepfpjeogkbgeigkbepobceinnfmjdd
- # ChatGPT Writer: Use AI on Any Site (GPT-4o, Claude, Gemini, and More)
chromeId: pdnenlnelpdomajfejgapbdpmjkfpjkp
- # discord message translator
chromeId: jobnhifpphkgoelnhnopgkdhbdkiadmj
- # Habit Tracker app widget for daily habit tracking
chromeId: ncokhechhpjgjonhjnlaneglmdkfkcbj
- # Catppuccin for GitHub File Explorer Icons
chromeId: lnjaiaapbakfhlbjenjkhffcdpoompki
firefoxSlug: catppuccin-web-file-icons
- # WebChat: Chat with anyone on any website
chromeId: cpaedhbidlpnbdfegakhiamfpndhjpgf
- # YouTube Auto HD + FPS
chromeId: fcphghnknhkimeagdglkljinmpbagone
firefoxSlug: youtube-auto-hd-fps
- # MultiViewer Companion
chromeId: lpomjgbicdemjkgmbnkjncgdebogkhlb
firefoxSlug: multiviewer-companion
- # Sync Watch - Watch videos together on any site
chromeId: ggiafipgeeaaahnjamgpjcgkdpanhddg
firefoxSlug: syncwatch
- # Keyword Rank Checker
chromeId: nmldnjcblcihmegipecakhmnieiofmgl
firefoxSlug: keyword-rank-checker-sa
- # YouTube Simple View - Hide distractions & more
chromeId: gppllamhaciichleihemgilcpledblpn
firefoxSlug: youtube-simple-view
- # Propbar - Property Data Enhancer
chromeId: pccbghdfdnnkkbcdcibchpbffdgednkf
- # Text Search Pro - Search by case and whole-word match!
chromeId: lfknakglefggmdkjdfhhofkjnnolffkh
firefoxSlug: text-search-pro-ext
- # Invoice Generator
chromeId: mbenhbocjckkbaojacmaepiameldglij
firefoxSlug: quick-invoice-generator
- # Monthly Bill Tracker
chromeId: phlfhkmdofajnbhgmbmjkbkdgppgoppb
- # Wandpen - Instantly improve your writing with AI
chromeId: macmkmchfoclhpbncclinhjflmdkaoom
- # YouTube Hider - Remove Comments By Keywords, Usernames & Tools
chromeId: lhmgechokhmdekdpgkkemoeecelcaonm
- # QA Compass - Record standardized bug reports easily
chromeId: imgheieooppmahcgniieddodaliodeeg
- # aesthetic Notion, styled
chromeId: npgghjedpchajflknnbngajkjkdhncdo
- # Eye Dropper
chromeId: hmdcmlfkchdmnmnmheododdhjedfccka
firefoxSlug: eye_dropper
- # Cursorful - Screen Recorder with Auto Zoom
chromeId: eihpmapodnppeemkhkbhikmggfojdkjd
- # Show IP Live View of Website IPs for Developers
chromeId: hjjkgbibknbahijglkffklflidncplkn
- # Strong Password Generator
chromeId: ilbikcehnpkmldojkcmlldkoelofnbde
firefoxSlug: strongpasswordgenerator
- # ZenGram: Mindful Instagram, Your Way
chromeId: ocllfkhcdopiafndigclebelbecaiocp
- # Blync: Preview Links, Selection Search, AI Assistant
chromeId: odffpjnpocjfcaclnenaaaddghkgijdb
- # HTML to Markdown - Convert webpages to markdown
chromeId: kofbbilhmnkcmibjbioafflgmpkbnmme
- # Walmart WFS Profit Calculator
chromeId: boecmgggeigllcdocgioijmleimjbfkg
firefoxSlug: walmart-wfs-profit-calculator
- # Youtube Live Chat Fullscreen
chromeId: dlnjcbkmomenmieechnmgglgcljhoepd
firefoxSlug: youtube-live-chat-fullscreen
- # Python Code Runner
chromeId: keiealdacakpnbbljlmhfgcebmaadieg
firefoxSlug: code-runner-manager
- # Monochromate
chromeId: hafcajcllbjnoolpfngclfmmgpikdhlm
- # AliasVault - Open-Source Password & (Email) Alias Manager
chromeId: bmoggiinmnodjphdjnmpcnlleamkfedj
- # SnapThePrice: AI-Powered Real-time Lowest Price Finder
chromeId: hlnhhamckimoaiekbglafiebkfimhapb
- # radiofrance - news & broadcasts (French), music (international)
chromeId: gdjampjdgjmbifnhldgcnccdjkcoicmg
- # Blens - Time Tracker and AI Insight
chromeId: jlnhphlghikichhgbnkepenehbmloenb
firefoxSlug: blens-timetracker-ai-insight
- # Always Light Mode - Setting website always in light mode
chromeId: njnammmpdodmfkodnfpammnpdcbhnlcm
- # DesignPicker - Color Picker & Font Detector
chromeId: lblmfclcfniabobmamfkdogcgdagbhhb
firefoxSlug: design-colorpicker-fontdetect
- # Web to PDF
chromeId: pamnlaoeobcmhkliljfaofekeddpmfoh
firefoxSlug: export-web-to-pdf
- # Online CSV Viewer
chromeId: jmbcbeepjfenihlocplnbmbhimcoooka
firefoxSlug: csv-viewer
- # YouTube Video Transcript
chromeId: nkjcoophmpcmmgadnljnlpbpfdfacgbo
firefoxSlug: youtube-transcript-copy
- # NetSuite Record Scripts
chromeId: lcaieahkjgeggeiihblhcjbbjlppgieh
firefoxSlug: netsuite-scripts-manager
- # VueTracker
chromeId: gmocfknjllodfiomnljmaehcplnekhlo
- # CanCopy - A web extension that allow you to copy any content from website
chromeId: ggcfemmoabhhelfkhknhbnkmeahloiod
- # Read Frog
chromeId: modkelfkcfjpgbfmnbnllalkiogfofhb
firefoxSlug: read-frog-open-ai-translator
- # Bilibili Feed History Helper
chromeId: npfopljnjbamegincfjelhjhnonnjloo
- # NZBDonkey - The ultimate NZB file download tool
chromeId: edkhpdceeinkcacjdgebjehipmnbomce
- # WeChat Markdown Editor(微信 Markdown 编辑器)
chromeId: cckggnbnimdbbpmdinkkgbbncopbloob
- # Tab Grab
chromeId: jcblcjolcojmfopefcighfmkkefbaofg
firefoxSlug: tab-grab
- # BrowserLens - https://browserlens.com/
chromeId: eehmoikadcijkapfjocnhjclpbaindlb
firefoxSlug: browserlens
- # Epic Games Library Extension
chromeId: hfhellofkjebbchcdffmicekjdomkcmc
firefoxSlug: epic-games-library-extension
- # Zen Analytics Pixel Tracker - zapt.web.app
chromeId: gknigcbhlammoakmmdddkblknanpjiac
- # Crypto Pulse - Compose your newtab with nature images, widgets & realtime Crypto Price & Bitcoin RSS.
chromeId: cnklededohhcbmjjdlbjdkkihkgoggol
- # Youtube Video Scheduler
chromeId: miponnamafdenpgjemkknimgjfibicdc
firefoxSlug: youtube-video-scheduler
- # Chatslator: Livestream Chat Translator
chromeId: nhmbcmalgpkjbomhlhgdicanmkkaajmg
firefoxSlug: chatslator
- # 公众号阅读增强器 - https://wxreader.honwhy.wang
chromeId: mbamjfdjbcdgpopfnkkmlohadbbnplhm
- # 토탐정
chromeId: hannhecbnjnnbbafffmogdlnajpcomek
firefoxSlug: 토탐정
- # 2FAS Pass - https://2fas.com/
chromeId: ehboaofjncodknjkngdggmpdinhdoijp
firefoxSlug: 2fas-pass-browser-extension
- # Quick Prompt - https://github.com/wenyuanw/quick-prompt
chromeId: hnjamiaoicaepbkhdoknhhcedjdocpkd
firefoxSlug: quick-prompt
- # Add QR Code Generator Icon Back To Address Bar
chromeId: kacblhilkacgfnkjfodalohcnllcgmjd
- # Piwik PRO Tracking Helper
chromeId: fkbdlogfdjmpfepbbbjcgcfbgbcfcnne
firefoxSlug: piwik-pro-tracking-helper
- # PIPX - Take Control of Picture-in-Picture, Automatically
chromeId: nkbikckldmljjiiajklecmgmajgapbfl
- # Browsely - AI-powered browser extension
chromeId: hgppdobcpkfkmiegekaglonjajeojmdd
- # Filmbudd Pro - Simple, private and synced ratings and watch notes across all your devices
chromeId: ehmoihnjgkdimihkhokkmfjdgomohjgm
- # MultiField CopyCat - Copy, Paste & Autofill Web Forms Instantly
chromeId: alglchohmdikgdjhafiicilegegieafa
- # ChatSight - Add Table of Contents to ChatGPT
chromeId: aamihahiiogceidpbnfgehacgiecephe
- # BetterCampus (prev. BetterCanvas)
chromeId: cndibmoanboadcifjkjbdpjgfedanolh
firefoxSlug: better-canvas
- # Leetcode Fonts - Change fonts in leetcode effortlessly
chromeId: hinfimgacobnellbncbcpdlpaapcofaa
- # TranslateManga - Manga Translator & Manga Tracker
chromeId: kbkbfefhhabpkibojinapkkgciiacggg
- # SiteData - Free Website Traffic Checker & Reverse AdSense Tool
chromeId: emeakbgdecgmdjgegnejpppcnkcnoaen
firefoxSlug: sitedata
- # Livestream Chat Reader - Text-to-Speech for YouTube/Twitch chat
chromeId: gpnckbhgpnjciklpoehkmligeaebigaa
- # ChatGPT Token Counter - Count tokens in real time on chatgpt conversation
chromeId: fjlalaedpfcojcfpkgkglbjjbbkofgnl
firefoxSlug: chatgpt-token-counter
- # LinuxDo Scripts - 为 linux.do 用户提供了一些增强功能
chromeId: fbgblmjbeebanackldpbmpacppflgmlj
firefoxSlug: linux_do-scripts
- # Zen Virtual Piano - https://zen-piano.web.app/
chromeId: dfacnjidgbagicaekenjgclfnhdnjjdi
- # Crypto Pulse price tracker - https://get-crypto-pulse.web.app/
chromeId: naeibcegmgpofimedkmfgjgphfhfhlab
- # Redirect Web - Automatically redirect pages or open them in another app
chromeId: ffglckbhfbfmdkefdmjbhpnffkcmlhdh
- # Capture It - Capture & Edit Screenshots
chromeId: eglpfhbhmelampoihamjomgkeobgdofl
- # Teams Chat Exporter
chromeId: jmghclbfbbapimhbgnpffbimphlpolnm
firefoxSlug: teams-chat-exporter
- # Lofi BGM Player - Free lofi focus music for work & study
chromeId: jdcppdokgfbnhiacbeplahgnciahnhck
firefoxSlug: lofi-bgm-player
- # Margin - Annotate and highlight any webpage, with your notes saved to the decentralized AT Protocol.
chromeId: cgpmbiiagnehkikhcbnhiagfomajncpa
- # KeyFloat - Floating multilingual keyboard with native key mappings, drag, dark mode, sounds, and dynamic layouts for macOS & Windows
chromeId: mfjdonmgmgcijagclnkfhmjiblbfjaid
- # Glossy New Tab - Say Goodbye to Boring Tabs with live wallpapers
chromeId: dhiekgdaipindoapjmcnpompdknjeijf
firefoxSlug: glossy-new-tab
- # All API Hub AI Relay & New API Manager - https://github.com/qixing-jk/all-api-hub
chromeId: lapnciffpekdengooeolaienkeoilfeo
firefoxSlug: all-api-hub
- # Scrape Similar - Extract data from websites into spreadsheets - https://github.com/zizzfizzix/scrape-similar
chromeId: bhgobenflkkhfcgkikejaaejenoddcmo
- # isTrust - https://github.com/Internet-Society-Belgium/isTrust/
chromeId: kinlknncggaihnhdcalijdmpbhbflalm
- # Dymo
chromeId: ojpakgiekphppgkcdihbjpafobhnhlkp
- # Extension Rank Checker - Extension Ranker
chromeId: pmgehhllikbjmadpenhabejhpemplhmd
firefoxSlug: rank-checker
- # PlayFaster - Enhanced playback speed control for online videos and audio
chromeId: fppcbkhpahkbgijkdcpjgjmhpfbmfiih
firefoxSlug: playfaster
- # LatTab - Learn Latin With Every New Tab
chromeId: eiocjaocpmackhbaffoejkcmnfbdpgpj
firefoxSlug: lattab-learn-latin-new-tabs
- # Vim What? - Interactive Vim command reference
chromeId: ngbehgnlcdjkbnihgpkgdangbhemidge
firefoxSlug: vim-what
- # FRED - Fraud Recognition Easy Detection
chromeId: bjdbcabacnlmbpcmiapcdfancfgcakfn
firefoxSlug: fred
- # Clear Wisdom - Gems of wisdom from James Clear's 3-2-1 newsletter
chromeId: jijfmgoijddfmlcdghopbkdpelbpmjdm
firefoxSlug: clear-wisdom
- # Soundcloud Enhanced Pro
chromeId: ggplcohodggmdfpopelnpplhgfjclomi
- # AlarmBot: ULTIMATE Web Monitoring & Smart Price Alerts
chromeId: mpckalcodookackleecihhnngdibelif
- # TF2 Trader - TF2 & Steam Trading Extension - https://github.com/offish/tf2-trader
chromeId: gmicpekfpbikhibodgokfpghadkclhoe
- # 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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 131 KiB

-41
View File
@@ -159,30 +159,6 @@ 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/)
@@ -200,23 +176,6 @@ 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)
+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-splitting between your background script and HTML pages, and set `"type": "module"` in your manifest.
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.
:::warning
Only MV3 supports ESM background scripts/service workers. When targeting MV2, the `type` option is ignored and the background is always bundled into a single file as IIFE.
-22
View File
@@ -76,28 +76,6 @@ Alternatively, if you're trying to use similar APIs under different names (to su
});
```
### Augmenting the Browser Type
WXT's `browser` types are based on the `@types/chrome` package. That means some Firefox-specific APIs may not be typed, like `browser.sidebarAction`. If you want to add types for these APIs, you can augment the browser type to add them yourself:
```ts
// <srcDir>/browser-types.d.ts
import '@wxt-dev/browser';
import type { SidebarAction } from 'webextension-polyfill';
declare module '@wxt-dev/browser' {
namespace Browser {
export const sidebarAction: SidebarAction.Static;
}
}
```
> For this to work, you may need to install `@wxt-dev/browser` as a direct dependency.
>
> ```sh
> pnpm add @wxt-dev/browser
> ```
## Entrypoint Limitations
Because WXT imports your entrypoint files into a NodeJS, non-extension environment, the `chrome`/`browser` variables provided to extensions by the browser **will not be available**.
-51
View File
@@ -1,51 +0,0 @@
# 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>'],
},
],
},
});
```
+5 -5
View File
@@ -163,8 +163,8 @@ yarn zip:firefox
```
```sh [bun]
bun install
bun run zip:firefox
bun i
bun 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:
- [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."
- [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."
When running the `safari-web-extension-packager` CLI tool, pass the `.output/safari-mv2` or `.output/safari-mv3` directory, not your source code directory.
When running the `safari-web-extension-converter` 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-packager .output/safari-mv2
xcrun safari-web-extension-converter .output/safari-mv2
```
### Edge Addons
+22 -47
View File
@@ -4,21 +4,33 @@ outline: deep
# WXT Modules
WXT provides a "module system" that lets you run code at different steps in the build process to modify it.
WXT provides a "module system" that let's you run code at different steps in the build process to modify it.
[[toc]]
## Installing a Module
## Adding a Module
To use a published module from NPM, install the package and add it to your config:
There are two ways to add a module to your project:
```ts [wxt.config.ts]
export default defineConfig({
modules: ['@wxt-dev/auto-icons'],
});
```
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:
> Searching for ["wxt module"](https://www.npmjs.com/search?q=wxt%20module) on NPM is a good way to find published WXT modules.
```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.
## Module Options
@@ -37,7 +49,7 @@ Modules are loaded in the same order as hooks are executed. Refer to the [Hooks
## Writing Modules
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:
Here's what a basic WXT module looks like:
```ts
import { defineWxtModule } from 'wxt/modules';
@@ -49,14 +61,6 @@ 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
@@ -137,35 +141,6 @@ 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.
+1 -1
View File
@@ -76,7 +76,7 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
```
```sh [Bun]
bun add -D wxt
bun i -D wxt
```
```sh [NPM]
+1 -3
View File
@@ -476,7 +476,7 @@ export default defineConfig({
### Renamed Undocumented Constants
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: <https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables>
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>
- `__BROWSER__` → `import.meta.env.BROWSER`
- `__COMMAND__` → `import.meta.env.COMMAND`
@@ -557,8 +557,6 @@ Unlisted scripts must now `export default defineUnlistedScript(...)`.
### `BackgroundDefinition` Type
<!-- cspell:disable -->
Rename `BackgroundScriptDefintition` to `BackgroundDefinition`.
## v0.6.0 &rarr; v0.7.0
+5 -3
View File
@@ -4,7 +4,7 @@ outline: deep
# WXT Storage
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md) &bull; [API Reference](/api/reference/wxt/utils/storage/interfaces/WxtStorage)
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md)
A simplified wrapper around the extension storage APIs.
@@ -74,7 +74,7 @@ await storage.watch<number>(
await storage.getMeta<{ v: number }>('local:installDate');
```
> 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.
For a full list of methods available, see the [API reference](/api/reference/wxt/utils/storage/interfaces/WxtStorage).
## Watchers
@@ -146,7 +146,7 @@ const showChangelogOnUpdate = storage.defineItem<boolean>(
);
```
Now, instead of using the `storage` variable, you can use the storage item instead:
Now, instead of using the `storage` variable, you can use the helper functions on the storage item you created:
```ts
await showChangelogOnUpdate.getValue();
@@ -157,6 +157,8 @@ const unwatch = showChangelogOnUpdate.watch((newValue) => {
});
```
For a full list of properties and methods available, see the [API reference](/api/reference/wxt/utils/storage/interfaces/WxtStorageItem).
### Versioning
You can add versioning to storage items if you expect them to grow or change over time. When defining the first version of an item, start with version 1.
+1 -1
View File
@@ -26,7 +26,7 @@ Set FontSize 32
# Terminal settings
Set Shell "bash"
# Terminal prompt. It looks like "● command ..."
# Terminal prompt. It looks like "● mycommand ..."
Env PS1 "\e[0;32m●\e[0m "
-6
View File
@@ -1,6 +0,0 @@
[build]
command = "bun run docs:build"
publish = "docs/.vitepress/dist"
[build.environment]
BUN_VERSION = "1.3.5"
+27 -51
View File
@@ -2,83 +2,59 @@
"private": true,
"type": "module",
"engines": {
"bun": ">=1.3.5"
"node": ">=18.20.3"
},
"workspaces": [
"packages/*"
],
"packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8",
"scripts": {
"check": "check && bun run --workspaces check",
"test": "bun run --workspaces test run",
"test:coverage": "bun run --workspaces test:coverage",
"check": "check && pnpm -r --sequential run check",
"test": "pnpm -r --sequential run test run",
"test:coverage": "pnpm -r --sequential run test:coverage",
"prepare": "simple-git-hooks",
"docs:gen": "typedoc --options docs/typedoc.json",
"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": "^20.17.6",
"@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",
"vitest": "^4.1.5",
"vitest-plugin-random-seed": "^1.1.2"
"docs:dev": "pnpm -s docs:gen && vitepress dev docs",
"docs:build": "pnpm -s docs:gen && vitepress build docs",
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
},
"devDependencies": {
"@aklinker1/buildc": "catalog:",
"@aklinker1/check": "catalog:",
"@commitlint/config-conventional": "^20.4.3",
"@commitlint/types": "^20.4.3",
"@types/bun": "catalog:",
"@aklinker1/buildc": "^1.1.6",
"@aklinker1/check": "^2.2.0",
"@commitlint/config-conventional": "^20.4.1",
"@commitlint/types": "^20.4.0",
"@types/fs-extra": "^11.0.4",
"@types/semver": "^7.7.1",
"@vitest/coverage-v8": "^4.1.5",
"@vitest/coverage-v8": "^4.0.18",
"changelogen": "^0.6.2",
"consola": "^3.4.2",
"cspell": "^9.7.0",
"fast-glob": "^3.3.3",
"feed": "^5.2.0",
"fs-extra": "^11.3.3",
"lint-staged": "^16.2.7",
"markdown-it-footnote": "^4.0.0",
"markdownlint-cli": "^0.48.0",
"nano-spawn": "catalog:",
"nano-staged": "^1.0.2",
"markdownlint-cli": "^0.47.0",
"nano-spawn": "^2.0.0",
"p-map": "^7.0.4",
"prettier": "^3.8.1",
"prettier-plugin-jsdoc": "^1.8.0",
"semver": "^7.7.4",
"simple-git-hooks": "^2.13.1",
"tinyglobby": "^0.2.15",
"tsdown": "catalog:",
"tsdown": "^0.20.3",
"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": "catalog:",
"typescript": "^5.9.3",
"vitepress": "^1.6.4",
"vitepress-knowledge": "^0.4.1",
"vitepress-plugin-group-icons": "^1.7.3",
"vitepress-plugin-llms": "^1.12.2",
"vitest-mock-extended": "^4.0.0",
"vue": "^3.5.34",
"vitepress-plugin-group-icons": "^1.7.1",
"vitepress-plugin-llms": "^1.11.0",
"vitest-mock-extended": "^3.1.0",
"vue": "^3.5.27",
"wxt": "workspace:*"
},
"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"
},
"simple-git-hooks": {
"pre-commit": "./node_modules/.bin/nano-staged"
"pre-commit": "pnpm lint-staged"
},
"nano-staged": {
"lint-staged": {
"*": "prettier --ignore-unknown --write"
}
}
-33
View File
@@ -1,38 +1,5 @@
# Changelog
## v0.5.4
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.3...analytics-v0.5.4)
### 🩹 Fixes
- Continue using `useAppConfig` to support older versions of WXT ([bfd94556](https://github.com/wxt-dev/wxt/commit/bfd94556))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.5.3
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.2...analytics-v0.5.3)
### 🩹 Fixes
- Add `getAppConfig` as an alias to `useAppConfig` ([#2144](https://github.com/wxt-dev/wxt/pull/2144))
- Allow `userId` option to return `undefined` ([636cf1f8](https://github.com/wxt-dev/wxt/commit/636cf1f8))
- Improve background script detection logic for analytics package ([#1808](https://github.com/wxt-dev/wxt/pull/1808))
### 🏡 Chore
- Created new types, instead of `any` for `analytics` ([#2119](https://github.com/wxt-dev/wxt/pull/2119))
### ❤️ Contributors
- Smit ([@sm17p](https://github.com/sm17p))
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
- Sheng Zhang ([@Arktomson](https://github.com/Arktomson))
## v0.5.2
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.1...analytics-v0.5.2)
@@ -10,7 +10,6 @@ import type {
BaseAnalyticsEvent,
} from './types';
import { browser } from '@wxt-dev/browser';
import { isBackground } from '@wxt-dev/is-background';
type AnalyticsMessage = {
[K in keyof Analytics]: {
@@ -56,14 +55,16 @@ export function createAnalytics(config?: AnalyticsConfig): Analytics {
);
}
if (isBackground()) return createBackgroundAnalytics(config);
// 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);
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,
@@ -220,7 +221,9 @@ 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();
@@ -44,9 +44,9 @@ export default defineWxtModule({
? clientModuleId
: normalizePath(relative(wxtAnalyticsFolder, clientModuleId))
}';
import { useAppConfig } from '#imports';
import { getAppConfig } from '#imports';
export const analytics = createAnalytics(useAppConfig().analytics);
export const analytics = createAnalytics(getAppConfig().analytics);
`;
addAlias(wxt, '#analytics', wxtAnalyticsIndex);
wxt.hook('prepare:types', async (_, entries) => {
+10 -13
View File
@@ -8,39 +8,36 @@ export interface Analytics {
) => void;
/** Save information about the user. */
identify: (userId: string, userProperties?: Record<string, string>) => void;
/**
* Automatically setup and track user interactions, returning a function to
* remove any listeners that were setup.
*/
/** Automatically setup and track user interactions, returning a function to remove any listeners that were setup. */
autoTrack: (root: Document | ShadowRoot | Element) => () => void;
/** Calls `config.enabled.setValue`. */
setEnabled: (enabled: boolean) => void;
}
export interface AnalyticsConfig {
/** Array of providers to send analytics to. */
/**
* Array of providers to send analytics to.
*/
providers: AnalyticsProvider[];
/** Enable debug logs and other provider-specific debugging features. */
/**
* Enable debug logs and other provider-specific debugging features.
*/
debug?: boolean;
/**
* Your extension's version, reported alongside events.
*
* @default browser.runtime.getManifest().version`.
*/
version?: string;
/**
* Configure how the enabled flag is persisted. Defaults to using
* `browser.storage.local`.
* Configure how the enabled flag is persisted. Defaults to using `browser.storage.local`.
*/
enabled?: AnalyticsStorageItem<boolean>;
/**
* Configure how the user Id is persisted. Defaults to using
* `browser.storage.local`.
* Configure how the user Id is persisted. Defaults to using `browser.storage.local`.
*/
userId?: AnalyticsStorageItem<string | 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>>;
}
+24 -29
View File
@@ -1,40 +1,14 @@
{
"name": "@wxt-dev/analytics",
"version": "0.5.4",
"version": "0.5.2",
"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",
@@ -64,5 +38,26 @@
"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"
}
}
+1 -3
View File
@@ -10,9 +10,7 @@ export default defineConfig({
'./modules/analytics/providers/google-analytics-4.ts',
'providers/umami': './modules/analytics/providers/umami.ts',
},
deps: {
neverBundle: ['#analytics'],
},
external: ['#analytics'],
define: {
'process.env.NPM': 'true',
},
-29
View File
@@ -1,34 +1,5 @@
# 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 add -D @wxt-dev/auto-icons
bun i -D @wxt-dev/auto-icons
```
Add the module to `wxt.config.ts`:
+6 -11
View File
@@ -24,7 +24,7 @@
}
],
"license": "MIT",
"version": "1.1.1",
"version": "1.1.0",
"type": "module",
"module": "./dist/index.mjs",
"types": "./dist/index.d.mts",
@@ -39,25 +39,20 @@
],
"scripts": {
"build": "buildc -- tsdown",
"check": "bun run build && check",
"test": "buildc --deps-only -- vitest",
"test:coverage": "bun run test run --coverage",
"prepack": "bun run build"
"check": "pnpm build && check"
},
"peerDependencies": {
"wxt": ">=0.19.0"
},
"dependencies": {
"defu": "^6.1.4",
"fs-extra": "^11.3.3",
"sharp": "^0.34.5"
},
"devDependencies": {
"@aklinker1/buildc": "catalog:",
"oxlint": "catalog:",
"publint": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:",
"oxlint": "^1.43.0",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"wxt": "workspace:*"
}
}
@@ -1,630 +0,0 @@
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();
});
});
});
+13 -19
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 { access, mkdir } from 'node:fs/promises';
import { ensureDir, pathExists } from 'fs-extra';
export default defineWxtModule<AutoIconsOptions>({
name: '@wxt-dev/auto-icons',
@@ -37,11 +37,7 @@ export default defineWxtModule<AutoIconsOptions>({
if (!parsedOptions.enabled)
return wxt.logger.warn(`\`[auto-icons]\` ${this.name} disabled`);
const iconExists = await access(resolvedPath).then(
() => true,
() => false,
);
if (!iconExists) {
if (!(await pathExists(resolvedPath))) {
return wxt.logger.warn(
`\`[auto-icons]\` Skipping icon generation, no base icon found at ${relative(process.cwd(), resolvedPath)}`,
);
@@ -49,7 +45,7 @@ export default defineWxtModule<AutoIconsOptions>({
wxt.hooks.hook('build:manifestGenerated', async (wxt, manifest) => {
if (manifest.icons)
wxt.logger.warn(
return wxt.logger.warn(
'`[auto-icons]` icons property found in manifest, overwriting with auto-generated icons',
);
@@ -95,7 +91,7 @@ export default defineWxtModule<AutoIconsOptions>({
}
}
mkdir(resolve(outputFolder, 'icons'), { recursive: true });
ensureDir(resolve(outputFolder, 'icons'));
await resizedImage.toFile(resolve(outputFolder, `icons/${size}.png`));
output.publicAssets.push({
@@ -113,11 +109,12 @@ 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;
@@ -125,30 +122,27 @@ 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
*
* @deprecated Use `developmentIndicator` instead
* @default true
* @deprecated Use `developmentIndicator` instead
*/
grayscaleOnDevelopment?: boolean;
/**
* Sizes to generate icons for
*
* @default [128, 48, 32, 16]
*/
sizes?: number[];
-8
View File
@@ -1,8 +0,0 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
mockReset: true,
restoreMocks: true,
},
});
+7 -9
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.42",
"version": "0.1.37",
"type": "module",
"main": "src/index.mjs",
"types": "src/index.d.ts",
@@ -12,9 +12,7 @@
},
"scripts": {
"check": "check",
"gen": "bun run scripts/generate.ts",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'"
"gen": "tsx scripts/generate.ts"
},
"author": {
"name": "Aaron Klinker",
@@ -25,11 +23,11 @@
"src"
],
"devDependencies": {
"@types/chrome": "0.1.42",
"@types/node": "catalog:",
"nano-spawn": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
"@types/chrome": "0.1.37",
"fs-extra": "^11.3.3",
"nano-spawn": "^2.0.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"dependencies": {
"@types/filesystem": "*",
+15 -19
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 ${styleText('cyan', '@types/chrome')}`);
await Bun.$`bun install --ignore-scripts -D @types/chrome@latest`;
console.log('Getting latest version of \x1b[36m@types/chrome\x1b[0m');
await spawn('pnpm', ['i', '--ignore-scripts', '-D', '@types/chrome@latest']);
// Generate new package.json
console.log(`Generating new ${styleText('cyan', 'package.json')}`);
console.log('Generating new \x1b[36mpackage.json\x1b[0m');
const pkgJsonPath = fileURLToPath(
import.meta.resolve('@types/chrome/package.json'),
);
const pkgDir = dirname(pkgJsonPath);
const pkgJson = await Bun.file(pkgJsonPath).json();
const pkgJsonTemplate = await Bun.file('templates/package.json').text();
const pkgJson = await fs.readJson(pkgJsonPath);
const pkgJsonTemplate = await fs.readFile('templates/package.json', 'utf8');
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 Bun.write(outPkgJsonPath, JSON.stringify(newPkgJson));
await Bun.$`bun run --cwd ../.. prettier --write "${outPkgJsonPath}"`;
await fs.writeJson(outPkgJsonPath, newPkgJson);
await spawn('pnpm', ['-w', 'prettier', '--write', outPkgJsonPath]);
// Generate declaration files
console.log('Generating declaration files');
const outDir = resolve('src/gen');
const declarationFileMapping = (
await readdir(pkgDir, {
await fs.readdir(pkgDir, {
recursive: true,
encoding: 'utf8',
})
@@ -50,21 +50,17 @@ const declarationFileMapping = (
}));
for (const { file, srcPath, destPath } of declarationFileMapping) {
const content = await Bun.file(srcPath).text();
const content = await fs.readFile(srcPath, 'utf8');
const transformedContent = transformFile(file, content);
const destDir = dirname(destPath);
await mkdir(destDir, { recursive: true });
await Bun.write(destPath, transformedContent);
console.log(` ${styleText('dim', '-')} ${styleText('cyan', file)}`);
await fs.mkdir(destDir, { recursive: true });
await fs.writeFile(destPath, transformedContent);
console.log(` \x1b[2m-\x1b[0m \x1b[36m${file}\x1b[0m`);
}
await Bun.$`bun install --ignore-scripts`;
// Done!
console.log(
`${styleText('green', '✔')} Done in ${performance.now().toFixed(0)} ms`,
);
console.log('\x1b[32m✔\x1b[0m Done in ' + performance.now().toFixed(0) + ' ms');
// Transformations
-1
View File
@@ -1,6 +1,5 @@
/* 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 {
type HARFormatEntry = Entry;
type HARFormatLog = Log;
export type HARFormatEntry = Entry;
export type HARFormatLog = Log;
}
+1771 -1828
View File
File diff suppressed because it is too large Load Diff
+5 -7
View File
@@ -12,9 +12,7 @@
},
"scripts": {
"check": "check",
"gen": "bun run scripts/generate.ts",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'"
"gen": "tsx scripts/generate.ts"
},
"author": {
"name": "Aaron Klinker",
@@ -26,9 +24,9 @@
],
"devDependencies": {
"@types/chrome": "{{chromeTypesVersion}}",
"@types/node": "catalog:",
"nano-spawn": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
"fs-extra": "^11.3.3",
"nano-spawn": "^2.0.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
}
}
-26
View File
@@ -1,31 +1,5 @@
# Changelog
## v0.2.5
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.4...i18n-v0.2.5)
### 🩹 Fixes
- Add `.jsonc` support for locale files to match docs ([#2066](https://github.com/wxt-dev/wxt/pull/2066))
### 🏡 Chore
- Manually fix markdownlint errors ([#1711](https://github.com/wxt-dev/wxt/pull/1711))
- **deps:** Upgrade oxlint from 0.16.8 to 1.14.0 ([a01928e0](https://github.com/wxt-dev/wxt/commit/a01928e0))
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
- Upgrade major deps ([#2070](https://github.com/wxt-dev/wxt/pull/2070))
### ❤️ Contributors
- Ilya Kubariev ([@gymnasy55](https://github.com/gymnasy55))
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.2.4
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.3...i18n-v0.2.4)
+1 -1
View File
@@ -161,7 +161,7 @@ hello: Hello $1!
order: Thanks for ordering your $1
```
#### Escaping `$`
#### Escapting `$`
To escape the dollar sign, put another `$` in front of it:
+10 -14
View File
@@ -1,7 +1,7 @@
{
"name": "@wxt-dev/i18n",
"description": "Type-safe wrapper around browser.i18n.getMessage with additional features",
"version": "0.2.5",
"version": "0.2.4",
"type": "module",
"repository": {
"type": "git",
@@ -23,15 +23,13 @@
"scripts": {
"build": "buildc -- tsdown",
"check": "buildc --deps-only -- check",
"test": "buildc --deps-only -- vitest",
"test:coverage": "bun run test run --coverage",
"prepack": "bun run build"
"test": "buildc --deps-only -- vitest"
},
"dependencies": {
"@wxt-dev/browser": "workspace:^",
"chokidar": "^5.0.0",
"confbox": "^0.2.4",
"tinyglobby": "^0.2.15"
"confbox": "^0.1.8 || ^0.2.2",
"fast-glob": "^3.3.3"
},
"peerDependencies": {
"wxt": ">=0.19.7"
@@ -42,14 +40,12 @@
}
},
"devDependencies": {
"@aklinker1/buildc": "catalog:",
"@types/node": "catalog:",
"oxlint": "catalog:",
"publint": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:",
"vitest-plugin-random-seed": "catalog:",
"@types/node": "^20.17.6",
"oxlint": "^1.43.0",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"vitest": "^4.0.18",
"vitest-plugin-random-seed": "^1.1.2",
"wxt": "workspace:*"
},
"main": "./dist/index.cjs",
+2 -4
View File
@@ -35,16 +35,14 @@ describe('I18n Types', () => {
});
describe('With type-safety', () => {
type MyStructure = {
const i18n = createI18n<{
simple: { plural: false; substitutions: 0 };
simpleSub1: { plural: false; substitutions: 1 };
simpleSub2: { plural: false; substitutions: 2 };
plural: { plural: true; substitutions: 0 };
pluralSub1: { plural: true; substitutions: 1 };
pluralSub2: { plural: true; substitutions: 2 };
};
const i18n = createI18n<MyStructure>();
}>();
describe('t', () => {
it('should only allow passing valid combinations of arguments', () => {
+1 -1
View File
@@ -43,7 +43,7 @@ describe('Utils', () => {
});
describe('getSubstitutionCount', () => {
it('should return the last substitution present in the message', () => {
it('should return the last substution present in the message', () => {
expect(getSubstitutionCount('I like $1, but I like $2 better')).toBe(2);
});
+18 -31
View File
@@ -16,25 +16,12 @@ export { SUPPORTED_LOCALES } from './supported-locales';
// TYPES
//
export type SimpleMessage = string;
export type PluralMessage = Record<'n' | number, string>;
export interface ChromeMessage {
message: string;
description?: string;
placeholders?: Record<string, { content: string; example?: string }>;
}
export type Message =
| SimpleMessage
| PluralMessage
| ChromeMessage
| Message[]
| { [key: string]: Message };
export type MessagesObject = Record<string, Message>;
export interface ParsedBaseMessage {
key: string[];
substitutions: number;
@@ -64,14 +51,13 @@ export type MessageFormat = 'JSON5' | 'YAML' | 'TOML';
//
/**
* See
* https://developer.chrome.com/docs/extensions/reference/api/i18n#overview-predefined
* See https://developer.chrome.com/docs/extensions/reference/api/i18n#overview-predefined
*/
const PREDEFINED_MESSAGES: Record<string, ChromeMessage> = {
'@@extension_id': {
message: '<browser.runtime.id>',
description:
"The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even non-localized extensions can use this message.\nNote: You can't use this message in a manifest file.",
"The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even unlocalized extensions can use this message.\nNote: You can't use this message in a manifest file.",
},
'@@ui_locale': {
message: '<browser.i18n.getUiLocale()>',
@@ -108,7 +94,7 @@ const EXT_FORMATS_MAP: Record<string, MessageFormat> = {
'.toml': 'TOML',
};
const PARSERS: Record<MessageFormat, (text: string) => MessagesObject> = {
const PARSERS: Record<MessageFormat, (text: string) => any> = {
YAML: parseYAML,
JSON5: parseJSON5,
TOML: parseTOML,
@@ -124,17 +110,20 @@ const ALLOWED_CHROME_MESSAGE_KEYS: Set<string> = new Set<keyof ChromeMessage>([
// PARSING
//
/** Parse a messages file, extract the messages. Supports JSON, JSON5, and YAML. */
/**
* Parse a messages file, extract the messages. Supports JSON, JSON5, and YAML.
*/
export async function parseMessagesFile(
file: string,
): Promise<ParsedMessage[]> {
const text = await readFile(file, 'utf8');
const ext = extname(file).toLowerCase();
return parseMessagesText(text, EXT_FORMATS_MAP[ext] ?? 'JSON5');
}
/** Parse a string, extracting the messages. Supports JSON, JSON5, and YAML. */
/**
* Parse a string, extracting the messages. Supports JSON, JSON5, and YAML.
*/
export function parseMessagesText(
text: string,
format: 'JSON5' | 'YAML' | 'TOML',
@@ -142,8 +131,10 @@ export function parseMessagesText(
return parseMessagesObject(PARSERS[format](text));
}
/** Given the JS object form of a raw messages file, extract the messages. */
export function parseMessagesObject(object: MessagesObject): ParsedMessage[] {
/**
* Given the JS object form of a raw messages file, extract the messages.
*/
export function parseMessagesObject(object: any): ParsedMessage[] {
return _parseMessagesObject(
[],
{
@@ -156,7 +147,7 @@ export function parseMessagesObject(object: MessagesObject): ParsedMessage[] {
function _parseMessagesObject(
path: string[],
object: Message,
object: any,
depth: number,
): ParsedMessage[] {
switch (typeof object) {
@@ -177,17 +168,15 @@ function _parseMessagesObject(
];
}
case 'object':
if (object == null) {
if ([null, undefined].includes(object)) {
throw new Error(
`Messages file should not contain \`${object}\` (found at "${path.join('.')}")`,
);
}
if (Array.isArray(object))
return object.flatMap((item, i) =>
_parseMessagesObject(path.concat(String(i)), item, depth + 1),
);
if (isPluralMessage(object)) {
const message = Object.values(object).join('|');
const substitutions = getSubstitutionCount(message);
@@ -200,7 +189,6 @@ function _parseMessagesObject(
},
];
}
if (depth === 1 && isChromeMessage(object)) {
const message = applyChromeMessagePlaceholders(object);
const substitutions = getSubstitutionCount(message);
@@ -213,7 +201,6 @@ function _parseMessagesObject(
},
];
}
return Object.entries(object).flatMap(([key, value]) =>
_parseMessagesObject(path.concat(key), value, depth + 1),
);
@@ -222,13 +209,13 @@ function _parseMessagesObject(
}
}
function isPluralMessage(object: Message): object is PluralMessage {
function isPluralMessage(object: any): object is Record<number | 'n', string> {
return Object.keys(object).every(
(key) => key === 'n' || isFinite(Number(key)),
);
}
function isChromeMessage(object: Message): object is ChromeMessage {
function isChromeMessage(object: any): object is ChromeMessage {
return Object.keys(object).every((key) =>
ALLOWED_CHROME_MESSAGE_KEYS.has(key),
);
@@ -240,7 +227,7 @@ function isChromeMessage(object: Message): object is ChromeMessage {
export function generateTypeText(messages: ParsedMessage[]): string {
const renderMessageEntry = (message: ParsedMessage): string => {
// Use '.' for deep keys at runtime and types
// Use . for deep keys at runtime and types
const key = message.key.join('.');
const features = [
+14 -7
View File
@@ -1,11 +1,18 @@
/** @module @wxt-dev/i18n */
import { I18nStructure, I18n, Substitution, UntypedI18n } from './types';
/**
* @module @wxt-dev/i18n
*/
import {
I18nStructure,
DefaultI18nStructure,
I18n,
Substitution,
} from './types';
import { browser } from '@wxt-dev/browser';
export function createI18n(): UntypedI18n;
export function createI18n<T extends I18nStructure>(): I18n<T>;
export function createI18n(): UntypedI18n {
const t: UntypedI18n['t'] = (key: string, ...args: unknown[]) => {
export function createI18n<
T extends I18nStructure = DefaultI18nStructure,
>(): I18n<T> {
const t = (key: string, ...args: any[]) => {
// Resolve args
let sub: Substitution[] | undefined;
let count: number | undefined;
@@ -59,5 +66,5 @@ export function createI18n(): UntypedI18n {
}
};
return { t };
return { t } as I18n<T>;
}
+6 -6
View File
@@ -3,7 +3,7 @@
*
* ```ts
* export default defineConfig({
* modules: ['@wxt-dev/i18n/module'],
* modules: ["@wxt-dev/i18n/module"],
* });
* ```
*
@@ -18,7 +18,7 @@ import {
generateTypeText,
SUPPORTED_LOCALES,
} from './build';
import { glob } from 'tinyglobby';
import glob from 'fast-glob';
import { basename, extname, join, resolve } from 'node:path';
import { watch } from 'chokidar';
import { GeneratedPublicFile, WxtDirFileEntry } from 'wxt';
@@ -47,7 +47,6 @@ export default defineWxtModule<I18nOptions>({
const files = await glob('*.{json,json5,jsonc,yml,yaml,toml}', {
cwd: localesDir,
absolute: true,
expandDirectories: false,
});
const unsupportedLocales: string[] = [];
@@ -181,12 +180,13 @@ export { type GeneratedI18nStructure }
},
});
/** Options for the i18n module */
/**
* Options for the i18n module
*/
export interface I18nOptions {
/**
* Directory containing files that define the translations.
*
* @default '${config.srcDir}/locales'
* @default "${config.srcDir}/locales"
*/
localesDir?: string;
}
+9 -14
View File
@@ -3,22 +3,13 @@ export interface I18nFeatures {
substitutions: SubstitutionCount;
}
export interface UntypedI18n {
t: UntypedTFunction;
}
export type UntypedTFunction = {
(key: string): string;
(key: string, substitutions: Substitution[]): string;
(key: string, n: number): string;
(key: string, n: number, substitutions: Substitution[]): string;
export type I18nStructure = {
[K: string]: I18nFeatures;
};
export type I18nStructure = Record<string, I18nFeatures>;
export interface I18n<T extends I18nStructure> {
t: TFunction<T>;
}
export type DefaultI18nStructure = {
[K: string]: any;
};
// prettier-ignore
export type SubstitutionTuple<T extends SubstitutionCount> =
@@ -75,6 +66,10 @@ export type TFunction<T extends I18nStructure> = {
): string;
};
export interface I18n<T extends DefaultI18nStructure> {
t: TFunction<T>;
}
export type Substitution = string | number;
type SubstitutionCount = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
+7 -8
View File
@@ -6,20 +6,19 @@
"license": "MIT",
"scripts": {
"build": "buildc -- tsdown",
"check": "bun run build && check",
"check": "pnpm build && check",
"test": "buildc --deps-only -- vitest",
"test:coverage": "bun run test run --coverage",
"prepack": "bun run build"
"test:coverage": "pnpm test run --coverage",
"prepack": "pnpm build"
},
"dependencies": {
"@wxt-dev/browser": "workspace:^"
},
"devDependencies": {
"oxlint": "catalog:",
"publint": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
"oxlint": "^1.43.0",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"repository": {
"type": "git",
+6 -10
View File
@@ -1,14 +1,10 @@
/**
* This module uses a lazy getter function so the logic isn't ran until it's
* needed.
* This module uses a lazy getter function so the logic isn't ran until it's needed.
*
* This has a few benefits:
*
* 1. Easier to mock in tests
* 2. Safe to import in NodeJS environments (but it should be safe to run just
* in-case)
* 3. Keeps startup fast by waiting to run the slow functions (`instanceof` or
* `browser.extension.getBackgroundPage`) until needed
* 2. Safe to import in NodeJS environments (but it should be safe to run just in-case)
* 3. Keeps startup fast by waiting to run the slow functions (`instanceof` or `browser.extension.getBackgroundPage`) until needed
*
* @module @wxt-dev/is-background
*/
@@ -20,10 +16,10 @@ let cached: boolean | undefined;
* Getter that returns if the current context is apart of an extension's
* background or not.
*
* > This function caches the result when called for the first time so it doesn't
* > have to recalculate.
* > This function caches the result when called for the first time so it
* > doesn't have to recalculate.
*
* @returns True when in a background page or service worker.
* @returns true when in a background page or service worker.
*/
export function isBackground(): boolean {
if (cached == null) cached = getIsBackground();
-46
View File
@@ -1,51 +1,5 @@
# Changelog
## v1.2.2
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.2.1...module-react-v1.2.2)
### 🩹 Fixes
- Add `@vitejs/plugin-react` v6 support ([c61fa8f8](https://github.com/wxt-dev/wxt/commit/c61fa8f8))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v1.2.1
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.2.0...module-react-v1.2.1)
### 🏡 Chore
- Add prepack script to all packages ([032f7931](https://github.com/wxt-dev/wxt/commit/032f7931))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v1.2.0
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.5...module-react-v1.2.0)
### 🚀 Enhancements
- Add `vitePluginsBefore` option ([#2170](https://github.com/wxt-dev/wxt/pull/2170))
- Vite 8 support ([bfd4af5d](https://github.com/wxt-dev/wxt/commit/bfd4af5d))
### 🏡 Chore
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
- **deps:** Upgrade deps ([#2175](https://github.com/wxt-dev/wxt/pull/2175))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Martin Broder <hello@martinbroder.com>
## v1.1.5
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.4...module-react-v1.1.5)
+2 -9
View File
@@ -1,16 +1,15 @@
import 'wxt';
import { addImportPreset, addViteConfig, defineWxtModule } from 'wxt/modules';
import react, { Options as PluginOptions } from '@vitejs/plugin-react';
import type { PluginOption } from 'vite';
export default defineWxtModule<ReactModuleOptions>({
name: '@wxt-dev/module-react',
configKey: 'react',
setup(wxt, options) {
const { vite, vitePluginsBefore } = options ?? {};
const { vite } = options ?? {};
addViteConfig(wxt, () => ({
plugins: [...(vitePluginsBefore ?? []), react(vite)],
plugins: [react(vite)],
}));
addImportPreset(wxt, 'react');
@@ -31,12 +30,6 @@ export default defineWxtModule<ReactModuleOptions>({
export interface ReactModuleOptions {
vite?: PluginOptions;
/**
* Vite plugins to add before the `react()` plugin. Some plugins like the
* `@tanstack/router-plugin` need to be added before `react()` to work
* correctly.
*/
vitePluginsBefore?: PluginOption[];
}
declare module 'wxt' {
+8 -15
View File
@@ -18,7 +18,7 @@
},
"license": "MIT",
"funding": "https://github.com/sponsors/wxt-dev",
"version": "1.2.2",
"version": "1.1.5",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
@@ -40,30 +40,23 @@
],
"scripts": {
"dev": "wxt",
"check": "bun run build && check",
"check": "pnpm build && check",
"build": "buildc -- tsdown",
"postinstall": "buildc --deps-only -- wxt prepare",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'",
"prepack": "bun run build"
"prepare": "buildc --deps-only -- wxt prepare"
},
"peerDependencies": {
"vite": "^5.4.19 || ^6.3.4 || ^7.0.0 || ^8.0.0-0",
"wxt": ">=0.19.16"
},
"dependencies": {
"@vitejs/plugin-react": "^4.4.1 || ^5.0.0 || ^6.0.0"
"@vitejs/plugin-react": "^4.4.1 || ^5.0.0"
},
"devDependencies": {
"@aklinker1/buildc": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"publint": "catalog:",
"@types/react": "^19.2.13",
"@types/react-dom": "^19.2.3",
"publint": "^0.3.17",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tsdown": "catalog:",
"typescript": "catalog:",
"vite": "^7.0.0",
"typescript": "^5.9.3",
"wxt": "workspace:*"
}
}
+4 -9
View File
@@ -40,12 +40,9 @@
],
"scripts": {
"dev": "wxt",
"check": "bun run build && check",
"check": "pnpm build && check",
"build": "buildc -- tsdown",
"postinstall": "buildc --deps-only -- wxt prepare",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'",
"prepack": "bun run build"
"prepare": "buildc --deps-only -- wxt prepare"
},
"peerDependencies": {
"wxt": ">=0.19.16"
@@ -54,11 +51,9 @@
"vite-plugin-solid": "^2.11.10"
},
"devDependencies": {
"@aklinker1/buildc": "catalog:",
"publint": "catalog:",
"publint": "^0.3.17",
"solid-js": "^1.9.11",
"tsdown": "catalog:",
"typescript": "catalog:",
"typescript": "^5.9.3",
"wxt": "workspace:*"
}
}
-21
View File
@@ -1,26 +1,5 @@
# Changelog
## v2.0.5
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.4...module-svelte-v2.0.5)
### 🩹 Fixes
- Update `@sveltejs/vite-plugin-svelte` version range to support Vite 8 ([48571f50](https://github.com/wxt-dev/wxt/commit/48571f50))
### 🏡 Chore
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
- **deps:** Upgrade deps ([#2175](https://github.com/wxt-dev/wxt/pull/2175))
- Add prepack script to all packages ([032f7931](https://github.com/wxt-dev/wxt/commit/032f7931))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v2.0.4
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.3...module-svelte-v2.0.4)
+5 -10
View File
@@ -18,7 +18,7 @@
},
"license": "MIT",
"funding": "https://github.com/sponsors/wxt-dev",
"version": "2.0.5",
"version": "2.0.4",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
@@ -40,23 +40,18 @@
],
"scripts": {
"build": "buildc -- tsdown",
"check": "bun run build && check",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'",
"prepack": "bun run build"
"check": "pnpm build && check"
},
"peerDependencies": {
"wxt": ">=0.18.6",
"svelte": ">=5"
},
"dependencies": {
"@sveltejs/vite-plugin-svelte": ">=4"
"@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0"
},
"devDependencies": {
"@aklinker1/buildc": "catalog:",
"publint": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"wxt": "workspace:*"
}
}
+3 -8
View File
@@ -40,10 +40,7 @@
],
"scripts": {
"build": "buildc -- tsdown",
"check": "bun run build && check",
"test": "echo 'noop'",
"test:coverage": "echo 'noop'",
"prepack": "bun run build"
"check": "pnpm build && check"
},
"peerDependencies": {
"wxt": ">=0.19.16"
@@ -52,10 +49,8 @@
"@vitejs/plugin-vue": "^5.2.3 || ^6.0.0"
},
"devDependencies": {
"@aklinker1/buildc": "catalog:",
"publint": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"wxt": "workspace:*"
}
}
+3 -3
View File
@@ -1,8 +1,8 @@
//
// USAGE:
// bun run dev
// bun run dev firefox-nightly
// bun run dev <target>
// pnpm dev
// pnpm dev firefox-nightly
// pnpm dev <target>
//
import { run } from './src';
+8 -14
View File
@@ -21,20 +21,17 @@
"license": "MIT",
"funding": "https://github.com/sponsors/wxt-dev",
"scripts": {
"check": "bun run build && check",
"check": "pnpm build && check",
"test": "buildc --deps-only -- vitest",
"test:coverage": "bun run test run --coverage",
"dev": "bun run dev.ts",
"dev": "tsx --trace-warnings dev.ts",
"build": "buildc -- tsdown",
"prepack": "bun run build"
"prepublishOnly": "pnpm build"
},
"devDependencies": {
"@aklinker1/buildc": "catalog:",
"oxlint": "catalog:",
"publint": "catalog:",
"tsdown": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
"oxlint": "^1.43.0",
"publint": "^0.3.17",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"main": "dist/index.mjs",
"types": "dist/index.d.mts",
@@ -46,8 +43,5 @@
},
"files": [
"dist"
],
"dependencies": {
"obug": "^2.1.1"
}
]
}
+6 -26
View File
@@ -25,7 +25,6 @@ describe('Options', () => {
describe('extensionDir', () => {
it('should default to the current working directory', async () => {
const actual = await resolveRunOptions({});
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
extensionDir: process.cwd(),
});
@@ -35,27 +34,17 @@ describe('Options', () => {
const actual = await resolveRunOptions({
extensionDir: './path/to/extension',
});
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
extensionDir: resolve(process.cwd(), './path/to/extension'),
});
});
it('should use absolute paths as-is', async () => {
const actual = await resolveRunOptions({
extensionDir: '/abs/path/to/extension2',
});
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
extensionDir: resolve('/abs/path/to/extension2'),
});
});
});
describe('target', () => {
it('should be "chrome" by default', async () => {
const actual = await resolveRunOptions({});
const actual = await resolveRunOptions({
extensionDir: 'path/to/extension',
});
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
target: 'chrome',
});
@@ -63,12 +52,12 @@ describe('Options', () => {
it('should be what is passed in', async () => {
const actual = await resolveRunOptions({
extensionDir: 'path/to/extension',
target: 'custom',
browserBinaries: {
custom: '/path/to/custom/browser',
},
});
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
target: 'custom',
});
@@ -76,9 +65,9 @@ describe('Options', () => {
it('should throw an error if the target binary could not be found', async () => {
const actual = resolveRunOptions({
extensionDir: 'path/to/extension',
target: 'custom',
});
await expect(actual).rejects.toThrow('Could not find "custom" binary.');
});
});
@@ -94,6 +83,7 @@ describe('Options', () => {
? 'C:\\path\\to\\custom\\browser.exe'
: path;
const actual = await resolveRunOptions({
extensionDir: 'path/to/extension',
target: 'custom',
browserBinaries: {
custom: path,
@@ -108,11 +98,9 @@ describe('Options', () => {
describe('chromiumArgs', () => {
it('should log a warning when --user-data-dir is passed in', async () => {
const warnSpy = vi.spyOn(console, 'warn');
await resolveRunOptions({
chromiumArgs: ['--user-data-dir=some/custom/path'],
});
expect(warnSpy).toBeCalledTimes(1);
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining(
@@ -123,11 +111,9 @@ describe('Options', () => {
it('should log a warning when --remote-debugging-port is passed in', async () => {
const warnSpy = vi.spyOn(console, 'warn');
await resolveRunOptions({
chromiumArgs: ['--remote-debugging-port=9222'],
});
expect(warnSpy).toBeCalledTimes(1);
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining(
@@ -140,7 +126,6 @@ describe('Options', () => {
const actual = await resolveRunOptions({
chromiumArgs: ['--window-size=1920,1080'],
});
expect(actual.chromiumArgs).toEqual([
// Defaults
'--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculateNativeWinOcclusion,InterestFeedContentSuggestions,CertificateTransparencyComponentUpdater,AutofillServerCommunication,PrivacySandboxSettings4',
@@ -176,11 +161,9 @@ describe('Options', () => {
describe('firefoxArgs', () => {
it('should log a warning when --profile is passed in', async () => {
const warnSpy = vi.spyOn(console, 'warn');
await resolveRunOptions({
firefoxArgs: ['--profile=some/custom/path'],
});
expect(warnSpy).toBeCalledTimes(1);
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining('Custom Firefox --profile argument ignored'),
@@ -189,11 +172,9 @@ describe('Options', () => {
it('should log a warning when --remote-debugging-port is passed in', async () => {
const warnSpy = vi.spyOn(console, 'warn');
await resolveRunOptions({
firefoxArgs: ['--remote-debugging-port=9222'],
});
expect(warnSpy).toBeCalledTimes(1);
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining(
@@ -206,7 +187,6 @@ describe('Options', () => {
const actual = await resolveRunOptions({
firefoxArgs: ['--window-size=1920,1080'],
});
expect(actual.firefoxArgs).toEqual([
// Defaults
'--new-instance',
+10 -10
View File
@@ -1,7 +1,7 @@
import { openWebSocket } from './web-socket';
import { runnerDebug } from './debug';
import { debug } from './debug';
const debug = runnerDebug.extend('bidi');
const debugBidi = debug.scoped('bidi');
export interface BidiConnection extends Disposable {
send<T>(method: string, params: any, timeout?: number): Promise<T>;
@@ -12,10 +12,10 @@ export async function createBidiConnection(
baseUrl: string,
): Promise<BidiConnection> {
const url = new URL('/session', baseUrl);
debug('Connecting to BiDi server @', url.href);
debugBidi('Connecting to BiDi server @', url.href);
const webSocket = await openWebSocket(url.href);
debug('Connected');
debugBidi('Connected');
let requestId = 0;
@@ -23,7 +23,7 @@ export async function createBidiConnection(
send(method, params, timeout = 10e3) {
const id = ++requestId;
const command = { id, method, params };
debug('Sending command:', command);
debugBidi('Sending command:', command);
return new Promise((resolve, reject) => {
const cleanup = () => {
@@ -43,7 +43,7 @@ export async function createBidiConnection(
const onMessage = (event: MessageEvent) => {
const data = JSON.parse(event.data);
if (data.id === id) {
debug('Received response:', data);
debugBidi('Received response:', data);
cleanup();
if (data.type === 'success') resolve(data.result);
else reject(Error(data.message, { cause: data }));
@@ -62,14 +62,14 @@ export async function createBidiConnection(
},
close() {
debug('Closing connection...');
debugBidi('Closing connection...');
webSocket.close();
debug('Closed connection');
debugBidi('Closed connection');
},
[Symbol.dispose]() {
debug('Disposing connection...');
debugBidi('Disposing connection...');
webSocket.close();
debug('Disposed connection');
debugBidi('Disposed connection');
},
};
}
+2 -10
View File
@@ -2,7 +2,6 @@ export type BrowserPlatform = 'windows' | 'mac' | 'linux';
export type KnownTarget =
| 'arc'
| 'brave'
| 'chromium'
| 'chrome'
| 'chrome-beta'
@@ -29,11 +28,6 @@ export const KNOWN_BROWSER_PATHS: Record<
linux: [],
windows: [],
},
brave: {
mac: ['Applications/Brave Browser.app/Contents/MacOS/Brave Browser'],
linux: [],
windows: [],
},
chromium: {
mac: [],
linux: [
@@ -130,17 +124,15 @@ export const KNOWN_BROWSER_PATHS: Record<
};
/**
* When targeting a browser, this map contains the other targets to fall back on
* when a binary could not be found for the primary target.
* When targeting a browser, this map contains the other targets to fall back on when a binary could not be found for the primary target.
*/
export const FALLBACK_TARGETS: Partial<Record<KnownTarget, KnownTarget[]>> = {
chrome: [
'arc',
'chromium',
'chrome-canary',
'chrome-beta',
'chrome-dev',
'brave',
'arc',
'dia',
'edge',
'edge-canary',
+4 -4
View File
@@ -1,8 +1,8 @@
import type { ChildProcess } from 'node:child_process';
import type { Readable, Writable } from 'node:stream';
import { runnerDebug } from './debug';
import { debug } from './debug';
const debug = runnerDebug.extend('cdp');
const debugCdp = debug.scoped('cdp');
export interface CDPConnection extends Disposable {
send<T>(method: string, params: any, timeout?: number): Promise<T>;
@@ -21,7 +21,7 @@ export function createCdpConnection(
send(method, params, timeout = 10e3) {
const id = ++requestId;
const command = { id, method, params };
debug('Sending command:', command);
debugCdp('Sending command:', command);
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
@@ -35,7 +35,7 @@ export function createCdpConnection(
if (res.id !== id) return;
debug('Received response:', res);
debugCdp('Received response:', res);
clearTimeout(timer);
outputStream.removeListener('data', onData);
+23 -2
View File
@@ -1,3 +1,24 @@
import { createDebug } from 'obug';
export interface Debug {
(...args: any[]): void;
scoped: (scope: string) => Debug;
}
export const runnerDebug = createDebug('wxt:runner', { color: 31 });
function createDebug(scopes: string[]): Debug {
const debug = (...args: any[]) => {
const scope = scopes.join(':');
if (
process.env.DEBUG === '1' ||
process.env.DEBUG === 'true' ||
scope.startsWith(process.env.DEBUG ?? '@NOT')
) {
const params = scope ? [`\x1b[31m${scope}\x1b[0m`, ...args] : args;
console.log(...params);
}
};
debug.scoped = (scope: string) => createDebug([...scopes, scope]);
return debug;
}
export const debug = createDebug(['@wxt-dev/runner']);
+3 -7
View File
@@ -4,11 +4,8 @@ import { createCdpConnection } from './cdp';
/**
* Install an extension to an already running instance of Firefox.
*
* @param debuggerUrl The URL of the Firefox BiDi server (ex:
* `ws://127.0.0.1:45912`).
* @param extensionDir Absolute path to the directory containing the extension
* to be installed.
* @param debuggerUrl The URL of the Firefox BiDi server (ex: `ws://127.0.0.1:45912`).
* @param extensionDir Absolute path to the directory containing the extension to be installed.
*/
export async function installFirefox(
debuggerUrl: string,
@@ -36,8 +33,7 @@ export type BidiWebExtensionInstallResponse = {
};
/**
* Given a child process of Chrome, install an extension. The process must be
* started with the following flags:
* Given a child process of Chrome, install an extension. The process must be started with the following flags:
*
* - `--remote-debugging-pipe`
* - `--user-data-dir=...`
+22 -45
View File
@@ -6,11 +6,10 @@ import {
} from './browser-paths';
import { resolve, join } from 'node:path';
import { homedir, tmpdir } from 'node:os';
import { runnerDebug } from './debug';
import { debug } from './debug';
import { mkdtemp, open } from 'node:fs/promises';
import { styleText } from 'node:util';
const debug = runnerDebug.extend('options');
const debugOptions = debug.scoped('options');
export type UnknownTarget = string & {};
export type Target = KnownTarget | UnknownTarget;
@@ -18,46 +17,21 @@ export type Target = KnownTarget | UnknownTarget;
export type RunOptions = {
/** Paths to binaries to use for each target. */
browserBinaries?: Record<string, string>;
/**
* Customize the arguments passed to the chromium binary. Conflicting
* arguments with required ones to install extensions are ignored.
*/
/** Customize the arguments passed to the chromium binary. Conflicting arguments with required ones to install extensions are ignored. */
chromiumArgs?: string[];
/**
* Control how data is persisted between launches. Either save data at a user
* level, project level, or don't persist data at all. Defaults to `project`.
*/
/** Control how data is persisted between launches. Either save data at a user level, project level, or don't persist data at all. Defaults to `project`. */
dataPersistence?: 'user' | 'project' | 'none';
/**
* Customize where your profile's data is stored when using `dataPersistence:
* 'project'`. Can be absolute or relative to the current working directory.
*/
/** Customize where your profile's data is stored when using `dataPersistence: 'project'`. Can be absolute or relative to the current working directory. */
projectDataDir?: string;
/**
* Customize the port Chrome's debugger is listening on. Defaults to a random
* open port.
*/
/** Customize the port Chrome's debugger is listening on. Defaults to a random open port. */
chromiumRemoteDebuggingPort?: number;
/**
* Directory where the extension will be installed from. Should contain a
* `manifest.json` file. Can be relative to the current working directory.
* Defaults to the current working directory.
*/
/** Directory where the extension will be installed from. Should contain a `manifest.json` file. Can be relative to the current working directory. Defaults to the current working directory. */
extensionDir?: string;
/**
* Customize the arguments passed to the firefox binary. Conflicting arguments
* with required ones to install extensions are ignored.
*/
/** Customize the arguments passed to the firefox binary. Conflicting arguments with required ones to install extensions are ignored. */
firefoxArgs?: string[];
/**
* Customize the port Firefox's debugger is listening on. Defaults to a random
* open port.
*/
/** Customize the port Firefox's debugger is listening on. Defaults to a random open port. */
firefoxRemoteDebuggingPort?: number;
/**
* Specify the browser to open. Defaults to `"chrome"`, but you can pass any
* string.
*/
/** Specify the browser to open. Defaults to `"chrome"`, but you can pass any string. */
target?: Target;
};
@@ -79,7 +53,7 @@ export type ResolvedRunOptions = {
export async function resolveRunOptions(
options: RunOptions | undefined,
): Promise<ResolvedRunOptions> {
debug('User options:', options);
debugOptions('User options:', options);
const target = options?.target || 'chrome';
@@ -126,7 +100,7 @@ export async function resolveRunOptions(
firefoxRemoteDebuggingPort,
target,
};
debug('Resolved options:', resolved);
debugOptions('Resolved options:', resolved);
return resolved;
}
@@ -174,8 +148,10 @@ function resolveChromiumArgs(
],
userArgs,
{
'--remote-debugging-port': `${styleText(['bold', 'yellow'], 'Custom Chromium --remote-debugging-port argument ignored.')} Use ${styleText('cyan', 'chromiumRemoteDebuggingPort')} option instead.`,
'--user-data-dir': `${styleText(['bold', 'yellow'], 'Custom Chromium --user-data-dir argument ignored.')} Use ${styleText('cyan', 'dataPersistence')} option instead.`,
'--remote-debugging-port':
'\x1b[1m\x1b[33mCustom Chromium --remote-debugging-port argument ignored.\x1b[0m Use \x1b[36mchromiumRemoteDebuggingPort\x1b[0m option instead.',
'--user-data-dir':
'\x1b[1m\x1b[33mCustom Chromium --user-data-dir argument ignored.\x1b[0m Use \x1b[36mdataPersistence\x1b[0m option instead.',
},
);
}
@@ -199,8 +175,10 @@ function resolveFirefoxArgs(
],
userArgs,
{
'--remote-debugging-port': `${styleText(['bold', 'yellow'], 'Custom Firefox --remote-debugging-port argument ignored.')} Use ${styleText('cyan', 'firefoxRemoteDebuggingPort')} option instead.`,
'--profile': `${styleText(['bold', 'yellow'], 'Custom Firefox --profile argument ignored.')} Use ${styleText('cyan', 'dataPersistence')} option instead.`,
'--remote-debugging-port':
'\x1b[1m\x1b[33mCustom Firefox --remote-debugging-port argument ignored.\x1b[0m Use \x1b[36mfirefoxDebuggerPort\x1b[0m option instead.',
'--profile':
'\x1b[1m\x1b[33mCustom Firefox --profile argument ignored.\x1b[0m Use \x1b[36mdataPersistence\x1b[0m option instead.',
},
);
}
@@ -241,9 +219,8 @@ async function pathExists(path: string): Promise<boolean> {
}
/**
* Copied from
* https://github.com/GoogleChrome/chrome-launcher/blob/main/src/flags.ts with
* some flags commented out. Run tests after updating to compare.
* Copied from https://github.com/GoogleChrome/chrome-launcher/blob/main/src/flags.ts
* with some flags commented out. Run tests after updating to compare.
*/
const CHROME_LAUNCHER_DEFAULT_FLAGS = [
'--disable-features=' +
+7 -7
View File
@@ -1,4 +1,4 @@
import { runnerDebug } from './debug';
import { debug } from './debug';
import {
resolveRunOptions,
type ResolvedRunOptions,
@@ -8,8 +8,8 @@ import { spawn } from 'node:child_process';
import { installChromium, installFirefox } from './install';
import { promiseWithResolvers } from './promises';
const debugFirefox = runnerDebug.extend('firefox');
const debugChrome = runnerDebug.extend('chrome');
const debugFirefox = debug.scoped('firefox');
const debugChrome = debug.scoped('chrome');
export interface Runner {
stop(): void;
@@ -44,7 +44,7 @@ async function runFirefox(options: ResolvedRunOptions): Promise<Runner> {
shell: true,
},
);
const debugFirefoxStderr = debugFirefox.extend('stderr');
const debugFirefoxStderr = debugFirefox.scoped('stderr');
browserProcess.stderr.on('data', (data: string) => {
const message = data.toString().trim();
debugFirefoxStderr(message);
@@ -54,7 +54,7 @@ async function runFirefox(options: ResolvedRunOptions): Promise<Runner> {
urlRes.resolve(message.slice(28));
}
});
const debugFirefoxStdout = debugFirefox.extend('stdout');
const debugFirefoxStdout = debugFirefox.scoped('stdout');
browserProcess.stdout.on('data', (data: string) => {
const message = data.toString().trim();
debugFirefoxStdout(message);
@@ -85,7 +85,7 @@ async function runChromium(options: ResolvedRunOptions): Promise<Runner> {
opened.reject(Error('Timed out after 10s waiting for browser to open.'));
}, 10e3);
const debugChromeStderr = debugChrome.extend('stderr');
const debugChromeStderr = debugChrome.scoped('stderr');
browserProcess.stderr!.on('data', (data: string) => {
const message = data.toString().trim();
debugChromeStderr(message);
@@ -96,7 +96,7 @@ async function runChromium(options: ResolvedRunOptions): Promise<Runner> {
opened.resolve();
}
});
const debugChromeStdout = debugChrome.extend('stdout');
const debugChromeStdout = debugChrome.scoped('stdout');
browserProcess.stdout!.on('data', (data: string) => {
const message = data.toString().trim();
debugChromeStdout(message);
-12
View File
@@ -1,17 +1,5 @@
# Changelog
## v1.2.8
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.7...storage-v1.2.8)
### 🩹 Fixes
- Correctly update version metadata when setting a value for the first time ([#2139](https://github.com/wxt-dev/wxt/pull/2139))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v1.2.7
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.6...storage-v1.2.7)

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