Compare commits

..

19 Commits

Author SHA1 Message Date
Aaron 32500a78be feat: Add @wxt-dev/webextension-polyfill module (#1310)
vhs / vhs (push) Waiting to run
2024-12-28 09:35:20 -06:00
Aaron 6c9beee71a docs: Add upgrade guide for v0.20 (#1270) 2024-12-25 23:26:58 -06:00
aklinker1 b6a0cbbbed docs: Update wxt init GIF 2024-12-25 19:51:26 +00:00
Aaron 406766b37f docs: Fix api reference for wxt/utils/storage 2024-12-25 13:50:05 -06:00
Aaron 6f26eb5072 fix!: Move wxt/storage to wxt/utils/storage (#1271) 2024-12-25 13:50:05 -06:00
Aaron 900d3bbb18 docs: Fix broken links 2024-12-25 13:50:05 -06:00
Aaron 148bc4a6eb feat!: Reset inherited styles inside shadow root (#1269) 2024-12-25 13:50:05 -06:00
Aaron ee3cbe6925 chore: Remove duplicate test 2024-12-25 13:50:05 -06:00
Aaron 11ecd3f818 feat!: Individual exports and introduce the #imports module (#1258) 2024-12-25 13:50:02 -06:00
Aaron 72d4f7de3a docs: Add blog and first blog post to wxt.dev (#1261) 2024-12-25 13:33:01 -06:00
Aaron 6be319f379 fix!: Make publicDir and modulesDir relative to project root (#1216) 2024-12-25 13:32:28 -06:00
Aaron 84083bd9b0 chore: Fix type errors 2024-12-25 13:32:26 -06:00
Aaron d718f7fb50 chore: Replace require.resolve with import.meta.resolve (#1221) 2024-12-25 13:31:30 -06:00
Aaron 7530628eb8 feat: Upgrade to c12@2 so config is loaded as ESM (#1182) 2024-12-25 13:30:05 -06:00
Aaron 4b3ffc90c8 fix!: Remove transformManfiest option (#1181) 2024-12-25 13:29:12 -06:00
Aaron 01e4c1975a fix!: Rename runner to webExt (#1180) 2024-12-25 13:29:12 -06:00
Aaron 65dfded682 fix!: Remove deprecated jiti entrypoint loader (#1087) 2024-12-25 13:29:09 -06:00
Aaron c712175445 fix!: Add suffix to non-production output directories (#1086) 2024-12-25 13:17:40 -06:00
Aaron 0f71d570ca feat!: Remove webextension-polyfill (#1084) 2024-12-25 13:17:37 -06:00
248 changed files with 5215 additions and 27324 deletions
-10
View File
@@ -1,10 +0,0 @@
extends:
- '@commitlint/config-conventional'
rules:
subject-case:
- 0
- always
- - sentence-case
- start-case
- pascal-case
- upper-case
-11
View File
@@ -1,16 +1,5 @@
# See https://git-scm.com/docs/gitattributes#_pattern_format for more about `.gitattributes`.
# Normalize EOL for all files that Git considers text files
* text=auto eol=lf
# Mark lock files as generated to avoid diffing
pnpm-lock.yaml linguist-generated
package-lock.json linguist-generated
bun.lockb linguist-generated
yarn.lock linguist-generated
# Exclude templates from language statistics
templates/**/* linguist-vendored
# Other generated files
packages/browser/src/gen/** linguist-generated
-9
View File
@@ -1,9 +0,0 @@
# Set default
* @aklinker1 @Timeraa
# Secure Directories
/.github/ @aklinker1
# Creator of specific wxt modules
/packages/auto-icons/ @Timeraa
/packages/unocss/ @Timeraa
+14 -2
View File
@@ -1,3 +1,15 @@
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository#about-funding-files
# These are supported funding model platforms
github: wxt-dev
github: [wxt-dev] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
-1
View File
@@ -1,7 +1,6 @@
name: "\U0001F41E Bug report"
description: Report an issue with WXT
labels: [pending-triage]
type: Bug
body:
- type: markdown
attributes:
+1 -1
View File
@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for WXT
title: ''
type: Feature
labels: feature
assignees: ''
---
+6 -12
View File
@@ -1,29 +1,23 @@
name: Basic Setup
description: Install PNPM, Node, and dependencies
inputs:
install:
default: 'true'
description: Whether or not to run 'pnpm install'
installArgs:
default: ''
description: Additional args to append to "pnpm install"
runs:
using: composite
steps:
- name: 🛠️ Setup PNPM
uses: pnpm/action-setup@f2b2b233b538f500472c7274c7012f57857d8ce0 # v4.1.0
- name: 🛠️ Setup NodeJS
uses: actions/setup-node@b7234cc9fe124f0f4932554b4e5284543083ae7b # v4.4.0
- name: Setup PNPM
uses: pnpm/action-setup@v4
- name: Setup NodeJS
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 18
cache: pnpm
- name: 📦 Install Dependencies
- name: Install Dependencies
if: ${{ inputs.install == 'true' }}
shell: bash
run: pnpm install ${{ inputs.installArgs }}
+3 -1
View File
@@ -6,7 +6,9 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
directories:
- /
- packages/*
schedule:
interval: 'monthly'
- package-ecosystem: 'github-actions'
-13
View File
@@ -1,13 +0,0 @@
### Overview
<!-- Describe your changes and why you made them -->
### Manual Testing
<!-- Describe how to test your changes to make sure the PR works as intended -->
### Related Issue
<!-- If this PR is related to an issue, please link it here -->
This PR closes #<issue_number>
@@ -1,35 +0,0 @@
name: 🔔 Notify Unreleased Commits
on:
workflow_dispatch:
schedule:
- cron: '0 20 * * 1' # Weekly at 8 PM UTC (3 PM CT)
jobs:
notify:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
with:
fetch-depth: 0
- name: List Commits
id: list-commits
run: |
./scripts/list-unreleased-commits.sh > summary.txt
- name: Discord notification
run: |
PAYLOAD=$(jq -n --arg content "${{ env.MESSAGE }}" '{"content": $content}')
curl -X POST \
-F "payload_json=${PAYLOAD}" \
-F "file1=@summary.txt" \
$DISCORD_WEBHOOK
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_UNRELEASED_COMMITS }}
MESSAGE: |
If a package needs released, please [run the workflow](<https://github.com/wxt-dev/wxt/actions/workflows/release.yml>).
Before running, consider:
- Are there any breaking changes? If so, prepare a list of breaking changes and update the changelog and release notes after the release.
- Are there any PRs open that we wait to release after they're merged?
+5 -17
View File
@@ -1,4 +1,4 @@
name: ✨ pkg.pr.new
name: Continuous Publish
on:
push:
branches:
@@ -7,23 +7,11 @@ on:
branches:
- main
permissions:
contents: read
jobs:
build:
name: Publish Test Packages
runs-on: ubuntu-22.04
if: ${{ github.repository == 'wxt-dev/wxt' }}
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Build All Packages
run: pnpm buildc all
- name: Publish
run: pnpx pkg-pr-new publish --compact --pnpm './packages/*'
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm buildc all
- run: pnpx pkg-pr-new publish --compact './packages/*'
-29
View File
@@ -1,29 +0,0 @@
name: 🎉 PR closed
on:
pull_request_target:
types:
- closed
permissions:
contents: read
pull-requests: write
jobs:
thank-you:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
steps:
- name: Post Thank You Comment
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
comment: Thanks for helping make WXT better!
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.comment
})
-26
View File
@@ -1,26 +0,0 @@
name: 🛡️ Check PR Title
on:
pull_request:
types: [opened, edited]
jobs:
lint-pr-title:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
with:
# Only fetch the config file from the repository
sparse-checkout-cone-mode: false
sparse-checkout: .commitlintrc.yml
- name: Install dependencies
run: npm install --global @commitlint/config-conventional commitlint
- name: Check PR title with commitlint
env:
PR_TITLE: ${{ github.event.pull_request.title }}
HELP_URL: https://github.com/wxt-dev/wxt/blob/main/CONTRIBUTING.md#conventional-pr-titles
run: echo "$PR_TITLE" | npx commitlint --help-url $HELP_URL
+8 -26
View File
@@ -1,4 +1,4 @@
name: 📝 Publish Docs
name: Publish Docs
on:
push:
branches:
@@ -10,38 +10,20 @@ on:
required: true
default: latest
permissions:
contents: read
jobs:
publish:
# Only run if it's the upstream repository, not forks
if: github.repository == 'wxt-dev/wxt'
name: Publish Docs
runs-on: ubuntu-22.04
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Login to Docker Registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- uses: docker/login-action@v3
with:
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
- name: Build docs
run: |
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 ${{ secrets.UPDATE_DOCS_WEBHOOK }}
- run: pnpm docs:build
- run: docker build docs/.vitepress -t ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
- run: docker push ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
- run: curl -X POST -i ${{ secrets.UPDATE_DOCS_WEBHOOK }}
+8 -24
View File
@@ -1,4 +1,4 @@
name: 🚀 Release
name: Release
on:
workflow_dispatch:
inputs:
@@ -7,65 +7,49 @@ on:
default: wxt
type: choice
options:
- analytics
- auto-icons
- i18n
- module-react
- module-solid
- module-svelte
- module-vue
- runner
- storage
- unocss
- webextension-polyfill
- wxt
permissions:
contents: read
jobs:
validate:
name: Validate
uses: './.github/workflows/validate.yml'
secrets: inherit
publish:
name: Publish
runs-on: ubuntu-22.04
permissions:
contents: write
needs:
- validate
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
- uses: actions/checkout@v4
with:
fetch-depth: 0
ssh-key: ${{ secrets.DEPLOY_KEY }} # https://github.com/sbellone/release-workflow-example
- name: Setup
uses: ./.github/actions/setup
- name: Configure Git
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
git config --global push.followTags true
- uses: ./.github/actions/setup
- name: Bump and Tag
run: |
git config user.email "github-actions@users.noreply.github.com"
git config user.name "GitHub Actions"
pnpm tsx scripts/bump-package-version.ts ${{ inputs.package }}
git push
git push --tags
- name: Publish to NPM
working-directory: packages/${{ inputs.package }}
- name: NPM
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
pnpm build
pnpm publish
working-directory: packages/${{ inputs.package }}
- name: Create GitHub release
- name: GitHub Release
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+4 -17
View File
@@ -1,4 +1,4 @@
name: 🔄 Sync Releases
name: Sync Releases
on:
workflow_dispatch:
inputs:
@@ -7,37 +7,24 @@ on:
default: wxt
type: choice
options:
- analytics
- auto-icons
- i18n
- module-react
- module-solid
- module-svelte
- module-vue
- runner
- storage
- webextension-polyfill
- wxt
permissions:
contents: read
jobs:
sync:
name: Sync Releases
runs-on: ubuntu-22.04
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
- name: Setup
uses: ./.github/actions/setup
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
with:
installArgs: --ignore-scripts
- name: Sync Releases
run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
- run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,46 +0,0 @@
name: 🔄 Update @wxt-dev/browser
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *' # Every day at midnight
permissions:
contents: read
jobs:
sync:
name: 'Sync with @types/chrome'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
- name: Setup
uses: ./.github/actions/setup
with:
installArgs: --ignore-scripts
- name: Generate Latest Code
working-directory: packages/browser
run: pnpm gen
- name: Run Checks
working-directory: packages/browser
run: pnpm check
- name: Commit Changes
id: commit
uses: stefanzweifel/git-auto-commit-action@01d77ca6cb089da1360e540865f7d035c95aa199 # v6.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commit_message: 'fix: Upgrade `@wxt-dev/browser` to latest `@types/chrome` version'
- name: Publish Package
if: steps.commit.outputs.changes_detected == 'true'
working-directory: packages/browser
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
pnpm publish
+32 -93
View File
@@ -1,4 +1,4 @@
name: 🛡️ Validate
name: Validate
on:
workflow_call:
pull_request:
@@ -6,92 +6,46 @@ on:
branches:
- main
permissions:
contents: read
jobs:
checks:
name: Checks
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Basic Checks
run: pnpm check
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm check
builds:
name: Builds
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Build All Packages
run: pnpm buildc all
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm buildc all
build-demo:
name: Build Demo
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Build
run: pnpm build:all
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm build:all
working-directory: packages/wxt-demo
- name: ZIP
run: pnpm wxt zip
- run: pnpm wxt zip
working-directory: packages/wxt-demo
tests:
name: Tests (${{ matrix.title }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- title: 'Linux'
os: ubuntu-22.04
coverage: true
- title: 'Windows'
os: windows-latest
coverage: false
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Setup Bun
uses: oven-sh/setup-bun@22457c87c1b161cf7dde222c3e82b2b5f8d2bed2 # v2.0.2
- name: Run Tests
if: ${{ ! matrix.coverage }}
run: pnpm test
- name: Run Tests (Coverage)
if: matrix.coverage
run: pnpm test:coverage --reporter=default --reporter=hanging-process
- name: Upload Coverage
if: matrix.coverage
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- uses: oven-sh/setup-bun@v2
- name: pnpm test:coverage
run: pnpm test:coverage -- --reporter=default --reporter=hanging-process
- uses: codecov/codecov-action@v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
windows-tests:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm test
template:
name: Template
runs-on: ubuntu-22.04
strategy:
fail-fast: false
@@ -103,34 +57,19 @@ jobs:
- vanilla
- vue
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Pack WXT package
run: pnpm pack
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm pack
working-directory: packages/wxt
- name: Install Dependencies
run: npm i
- run: npm i
working-directory: templates/${{ matrix.template }}
- name: Install Packed WXT
run: npm i -D ../../packages/wxt/wxt-*.tgz
- run: npm i -D ../../packages/wxt/wxt-*.tgz
working-directory: templates/${{ matrix.template }}
- name: Type Check Template
run: pnpm compile
- run: pnpm compile
if: matrix.template != 'svelte'
working-directory: templates/${{ matrix.template }}
- name: Type Check Template
run: pnpm check
- run: pnpm check
if: matrix.template == 'svelte'
working-directory: templates/${{ matrix.template }}
- name: Build Template
run: pnpm build
- run: pnpm build
working-directory: templates/${{ matrix.template }}
+7 -23
View File
@@ -1,42 +1,26 @@
name: 📼 VHS
name: vhs
on:
push:
paths:
- 'docs/tapes/*.tape'
workflow_dispatch:
permissions:
contents: read
jobs:
vhs:
name: Create VHS
runs-on: ubuntu-22.04
if: ${{ github.repository == 'wxt-dev/wxt' }}
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
- name: Setup
uses: ./.github/actions/setup
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
with:
install: false
# This prevents pnpm dlx from downloading WXT in the video
- name: Pre-install WXT
install: 'false'
- name: Preinstall WXT
run: |
pnpm store add wxt@latest
pnpm dlx wxt@latest --version
- name: Record VHS
uses: charmbracelet/vhs-action@59641cdc7fadf3978db65eb8c6937ea2752f4ec3 # v2.1.0
- uses: charmbracelet/vhs-action@v2.1.0
with:
path: 'docs/tapes/init-demo.tape'
- name: Save recorded GIF
uses: stefanzweifel/git-auto-commit-action@01d77ca6cb089da1360e540865f7d035c95aa199 # v6.0.1
- uses: stefanzweifel/git-auto-commit-action@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
+1 -2
View File
@@ -5,10 +5,9 @@
.output
.webextrc
.wxt
.wxt-runner
*.log
/.vscode/
/docs/.vitepress/cache
docs/.vitepress/.temp
coverage
dist
node_modules
-6
View File
@@ -1,6 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/refs/heads/main/schema/markdownlint-config-schema.json",
"line-length": false,
"no-inline-html": false,
"first-line-heading": false
}
-9
View File
@@ -1,9 +0,0 @@
node_modules
.git
.output
dist
# Generated files
packages/wxt/README.md
packages/*/CHANGELOG.md
docs/api
-1
View File
@@ -5,4 +5,3 @@ dist
docs/.vitepress/cache
pnpm-lock.yaml
CHANGELOG.md
packages/browser/src/gen
-7
View File
@@ -1,7 +0,0 @@
{
"recommendations": [
"davidanson.vscode-markdownlint",
"esbenp.prettier-vscode",
"github.vscode-github-actions"
]
}
-14
View File
@@ -1,14 +0,0 @@
{
// Set default formatter
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
// Additional guidelines for Copilot
"github.copilot.chat.codeGeneration.instructions": [
{ "file": "CONTRIBUTING.md" }
]
}
+4 -4
View File
@@ -60,7 +60,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<aaronklinker1@gmail.com>.
aaronklinker1@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
@@ -116,7 +116,7 @@ the community.
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
<https://www.contributor-covenant.org/version/2/0/code_of_conduct.html>.
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
@@ -124,5 +124,5 @@ enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
<https://www.contributor-covenant.org/faq>. Translations are available at
<https://www.contributor-covenant.org/translations>.
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+8 -20
View File
@@ -8,17 +8,9 @@ If you want to add a new feature, please create an issue or discussion first so
## Conventional Commits
This project uses [Conventional Commit format](https://www.conventionalcommits.org/en/v1.0.0/) to automatically generate a changelog and better understand the changes in the project
This project uses [Conventional Commits](https://www.conventionalcommits.org/en) to automate versioning. If you're a new contributor, don't worry about this. When you open a PR, a maintainer will change the PR's title so it's in the style of conventional commits, but that's all.
Here are some examples of conventional commit messages:
- `feat: add new functionality`
- `fix: correct typos in code`
- `ci: add GitHub Actions for automated testing`
## Conventional PR Titles
The title of your pull request should follow the [conventional commit format](#conventional-commits). When a pull request is merged to the main branch, all changes are going to be squashed into a single commit. The message of this commit will be the title of the pull request. And for every release, the commit messages are used to generate the changelog.
Maintainers, commits to the `main` branch (either directly or via PRs) must be valid conventional commits.
## Setup
@@ -146,21 +138,17 @@ Releases are done with GitHub actions:
## 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.
Use [`taze`](https://www.npmjs.com/package/taze) to upgrade dependencies throughout the entire monorepo.
```sh
pnpm tsx scripts/upgrade-deps.ts
pnpm dlx taze -r
```
To see all the options, run:
```sh
pnpm tsx scripts/upgrade-deps.ts --help
```
Configuration is in [`taze.config.ts`](./taze.config.ts).
## Install Unreleased Versions
This repo uses <https://pkg.pr.new> to publish versions of all it's packages for almost every commit. You can install them via:
This repo uses https://pkg.pr.new to publish versions of all it's packages for almost every commit. You can install them via:
```sh
npm i https://pkg.pr.new/[package-name]@[ref]
@@ -181,10 +169,10 @@ npm i https://pkg.pr.new/@wxt-dev/module-react@426f907
## Blog Posts
Anyone is welcome to submit a blog post on <https://wxt.dev/blog>!
Anyone is welcome to submit a blog post on https://wxt.dev/blog!
> [!NOTE]
> Before starting on a blog post, please message Aaron on Discord or start a discussion on GitHub to get permission to write about a topic, but most topics are welcome: Major version updates, tutorials, etc.
- **English only**: Blog posts should be written in English. Unfortunately, our maintainers don't have the bandwidth right now to translate our docs, let alone blog posts. Sorry 😓
- **English only**: Blog posts should be written in English. Unfortunately, our maintainers doesn't have the bandwidth right now to translate our docs, let alone blog posts. Sorry 😓
- **AI**: Please only use AI to translate or proof-read your blog post. Don't generate the whole thing... We don't want to publish that.
+44 -18
View File
@@ -1,32 +1,50 @@
<div align="center">
<h1 align="center">
<img align="top" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
<span>WXT</span>
</h1>
# <img align="top" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo"> WXT
<p align="center">
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="downloads" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="license | MIT" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="coverage" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
</p>
[![npm version](https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048)](https://www.npmjs.com/package/wxt)
[![downloads](https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048)](https://www.npmjs.com/package/wxt)
[![license | MIT](https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048)](https://github.com/wxt-dev/wxt/blob/main/LICENSE)
[![coverage](https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048)](https://codecov.io/github/wxt-dev/wxt)
<p align="center">
<span>Next-gen framework for developing web extensions.</span>
<br/>
<span>⚡</span>
<br/>
<q><i>It's like Nuxt, but for Web Extensions</i></q>
</p>
Next-gen framework for developing web extensions.<br/>⚡<br/><q><i>It's like Nuxt, but for Web Extensions</i></q>
[Get Started](https://wxt.dev/guide/installation.html) •
[Configuration](https://wxt.dev/api/config.html) •
[Examples](https://wxt.dev/examples.html) •
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md) •
[Discord](https://discord.gg/ZFsZqGery9)
</div>
<p align="center">
<a href="https://wxt.dev/guide/installation.html" target="_blank">Get Started</a>
&bull;
<a href="https://wxt.dev/api/config.html" target="_blank">Configuration</a>
&bull;
<a href="https://wxt.dev/examples.html" target="_blank">Examples</a>
&bull;
<a href="https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md" target="_blank">Changelog</a>
&bull;
<a href="https://discord.gg/ZFsZqGery9" target="_blank">Discord</a>
</p>
![Example CLI Output](https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/assets/cli-output.png)
## Demo
<https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94>
https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94
## Quick Start
Bootstrap a new project:
<!-- automd:pm-x version="latest" name="wxt" args="init" -->
```sh
# npm
npx wxt@latest init
@@ -38,6 +56,8 @@ pnpm dlx wxt@latest init
bunx wxt@latest init
```
<!-- /automd -->
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
## Features
@@ -59,11 +79,17 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
WXT is a [MIT-licensed](https://github.com/wxt-dev/wxt/blob/main/LICENSE) open source project with its ongoing development made possible entirely by the support of these awesome backers. If you'd like to join them, please consider [sponsoring WXT's development](https://github.com/sponsors/wxt-dev).
[![WXT Sponsors](https://raw.githubusercontent.com/wxt-dev/static/refs/heads/main/sponsorkit/sponsors.svg)](https://github.com/sponsors/wxt-dev)
<a href="https://github.com/sponsors/wxt-dev"><img alt="WXT Sponsors" src="https://raw.githubusercontent.com/wxt-dev/static/refs/heads/main/sponsorkit/sponsors.svg"></a>
## Contributors
<!-- automd:contributors author="aklinker1" license="MIT" github="wxt-dev/wxt" -->
Published under the [MIT](https://github.com/wxt-dev/wxt/blob/main/LICENSE) license.
Made by [@aklinker1](https://github.com/aklinker1) and [community](https://github.com/wxt-dev/wxt/graphs/contributors) 💛
<br><br>
<a href="https://github.com/wxt-dev/wxt/graphs/contributors">
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
</a>
[![WXT contributors](https://contrib.rocks/image?repo=wxt-dev/wxt)](https://github.com/wxt-dev/wxt/graphs/contributors)
<!-- /automd -->
-15
View File
@@ -1,15 +0,0 @@
# Security Policy
While WXT is in prerelease, only the latest version will receive security updates. The latest version is:
<img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048">
<!--
## Reporting a Vulnerability
Use this section to tell people how to report a vulnerability.
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.
-->
+1 -42
View File
@@ -4,7 +4,7 @@ import useListExtensionDetails, {
ChromeExtension,
} from '../composables/useListExtensionDetails';
// Add extension IDs to end of the list. On the website, extensions will be sorted by a combination of weekly active users and rating.
// Add extension IDs here. Order doesn't matter, 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
@@ -61,47 +61,6 @@ const chromeExtensionIds = [
'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
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
@@ -35,9 +35,6 @@ export default function (ids: string[]) {
query,
variables: { ids },
}),
headers: {
'Content-Type': 'application/json',
},
})
.then(async (res) => {
isLoading.value = false;
+9 -62
View File
@@ -1,4 +1,4 @@
import { DefaultTheme, defineConfig } from 'vitepress';
import { defineConfig } from 'vitepress';
import typedocSidebar from '../api/reference/typedoc-sidebar.json';
import {
menuGroup,
@@ -14,14 +14,6 @@ import { version as i18nVersion } from '../../packages/i18n/package.json';
import { version as autoIconsVersion } from '../../packages/auto-icons/package.json';
import { version as unocssVersion } from '../../packages/unocss/package.json';
import { version as storageVersion } from '../../packages/storage/package.json';
import { version as analyticsVersion } from '../../packages/analytics/package.json';
import { version as runnerVersion } from '../../packages/runner/package.json';
import addKnowledge from 'vitepress-knowledge';
import {
groupIconMdPlugin,
groupIconVitePlugin,
localIconLoader,
} from 'vitepress-plugin-group-icons';
import { Feed } from 'feed';
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
@@ -36,50 +28,13 @@ const ogTitle = `${title}${titleSuffix}`;
const ogUrl = origin;
const ogImage = `${origin}/social-preview.png`;
const otherPackages = {
analytics: analyticsVersion,
'auto-icons': autoIconsVersion,
i18n: i18nVersion,
storage: storageVersion,
unocss: unocssVersion,
runner: runnerVersion,
};
const knowledge = addKnowledge<DefaultTheme.Config>({
serverUrl: 'https://knowledge.wxt.dev',
paths: {
'/': 'docs',
'/api/': 'api-reference',
'/blog/': 'blog',
},
layoutSelectors: {
blog: '.container-content',
},
pageSelectors: {
'examples.md': '#VPContent > .VPPage',
'blog.md': '#VPContent > .VPPage',
},
});
// https://vitepress.dev/reference/site-config
export default defineConfig({
extends: knowledge,
titleTemplate: `:title${titleSuffix}`,
title: 'WXT',
description,
vite: {
clearScreen: false,
plugins: [
groupIconVitePlugin({
customIcon: {
'wxt.config.ts': localIconLoader(
import.meta.url,
'../public/logo.svg',
),
},
}),
],
},
lastUpdated: true,
sitemap: {
@@ -87,9 +42,6 @@ export default defineConfig({
},
async buildEnd(site) {
// @ts-expect-error: knowledge.buildEnd is not typed, but it exists.
await knowledge.buildEnd(site);
// Only construct the RSS document for production builds
const { default: blogDataLoader } = await import('./loaders/blog.data');
const posts = await blogDataLoader.load();
@@ -107,8 +59,8 @@ export default defineConfig({
description: post.frontmatter.description,
});
});
// console.log('rss.xml:');
// console.log(feed.rss2());
console.log('rss.xml:');
console.log(feed.rss2());
await writeFile(join(site.outDir, 'rss.xml'), feed.rss2(), 'utf8');
},
@@ -128,10 +80,6 @@ export default defineConfig({
markdown: {
config: (md) => {
md.use(footnote);
md.use(groupIconMdPlugin);
},
languageAlias: {
mjs: 'js',
},
},
@@ -178,12 +126,12 @@ export default defineConfig({
'https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md',
),
]),
navItem(
'Other Packages',
Object.entries(otherPackages).map(([name, version]) =>
navItem(`@wxt-dev/${name}${version}`, `/${name}`),
),
),
navItem('Other Packages', [
navItem(`@wxt-dev/storage — ${storageVersion}`, '/storage'),
navItem(`@wxt-dev/auto-icons — ${autoIconsVersion}`, '/auto-icons'),
navItem(`@wxt-dev/i18n${i18nVersion}`, '/i18n'),
navItem(`@wxt-dev/unocss — ${unocssVersion}`, '/unocss'),
]),
]),
],
@@ -233,7 +181,6 @@ export default defineConfig({
menuGroup('Resources', '/guide/resources/', [
menuItem('Compare', 'compare.md'),
menuItem('FAQ', 'faq.md'),
menuItem('Community', 'community.md'),
menuItem('Upgrading WXT', 'upgrading.md'),
menuItem('Migrate to WXT', 'migrate.md'),
menuItem('How WXT Works', 'how-wxt-works.md'),
-1
View File
@@ -5,7 +5,6 @@ import UsingWxtSection from '../components/UsingWxtSection.vue';
import ExampleSearch from '../components/ExampleSearch.vue';
import BlogLayout from '../components/BlogLayout.vue';
import './custom.css';
import 'virtual:group-icons.css';
export default {
extends: DefaultTheme,
+2 -2
View File
@@ -28,13 +28,13 @@ export function menuRoot(items: SidebarItem[]) {
export function menuGroup(
text: string,
items: SidebarItem[],
collapsible?: boolean,
collapsable?: boolean,
): SidebarItem;
export function menuGroup(
text: string,
base: string,
items: SidebarItem[],
collapsible?: boolean,
collapsable?: boolean,
): SidebarItem;
export function menuGroup(
text: string,
-1
View File
@@ -1 +0,0 @@
<!--@include: ../packages/analytics/README.md-->
Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 132 KiB

@@ -70,7 +70,3 @@ You don't have to use `#imports` if you don't like - you can continue importing
Happy Coding 😄
> P.S. Yes, this is exactly how [Nuxt's `#imports`](https://nuxt.com/docs/guide/concepts/auto-imports#explicit-imports) works! We use the exact same library, [`unimport`](https://github.com/unjs/unimport).
---
[Discuss this blog post on Github](https://github.com/wxt-dev/wxt/discussions/1543).
+8 -32
View File
@@ -16,8 +16,7 @@ img.src = imageUrl;
```
```html [HTML]
<!-- In HTML tags, you must use the relative path --->
<img src="../assets/image.png" />
<img src="~/assets/image.png" />
```
```css [CSS]
@@ -26,22 +25,6 @@ img.src = imageUrl;
}
```
```vue [Vue]
<script>
import image from '~/assets/image.png';
</script>
<template>
<img :src="image" />
</template>
```
```jsx [JSX]
import image from '~/assets/image.png';
<img src={image} />;
```
:::
## `/public` Directory
@@ -69,16 +52,6 @@ img.src = imageUrl;
}
```
```vue [Vue]
<template>
<img src="/image.png" />
</template>
```
```jsx [JSX]
<img src="/image.png" />
```
:::
:::warning
@@ -93,7 +66,8 @@ But, inside content scripts, the hostname is whatever the tab is set to. So if y
To fix this, you need to convert the image to a full URL using `browser.runtime.getURL`:
```ts [entrypoints/content.ts]
```ts
// entrypoints/content.ts
import iconUrl from '/icon/128.png';
export default defineContentScript({
@@ -138,12 +112,13 @@ Run `wxt build`, and you should see the WASM file copied into your `.output/chro
Next, since this is in a content script and we'll be fetching the WASM file over the network to load it, we need to add the file to the `web_accessible_resources`:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
manifest: {
web_accessible_resources: [
{
// We'll use this matches in the content script as well
// We'll use this matches in the cotent script as well
matches: ['*://*.github.com/*'],
// Use the same path as `relativeDest` from the WXT module
resources: ['/oxc_parser_wasm_bg.wasm'],
@@ -155,7 +130,8 @@ export default defineConfig({
And finally, we need to load and initialize the `.wasm` file inside the content script to use it:
```ts [entrypoints/content.ts]
```ts
// entrypoints/content.ts
import initWasm, { parseSync } from '@oxc-parser/wasm';
export default defineContentScript({
@@ -6,7 +6,7 @@ outline: deep
> See the [API Reference](/api/reference/wxt/interfaces/WebExtConfig) for a full list of config.
During development, WXT uses [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext) to automatically open a browser window with your extension installed.
During development WXT uses [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext) to automatically open a browser window with your extension installed.
## Config Files
@@ -14,7 +14,7 @@ You can configure browser startup in 3 places:
1. `<rootDir>/web-ext.config.ts`: Ignored from version control, this file lets you configure your own options for a specific project without affecting other developers
```ts [web-ext.config.ts]
```ts
import { defineWebExtConfig } from 'wxt';
export default defineWebExtConfig({
@@ -22,7 +22,7 @@ You can configure browser startup in 3 places:
});
```
2. `<rootDir>/wxt.config.ts`: Via the [`webExt` config](/api/reference/wxt/interfaces/InlineConfig#webext), included in version control
2. `<rootDir>/wxt.config.ts`: Via the [`runner` config](/api/reference/wxt/interfaces/InlineConfig#runner), included in version control
3. `$HOME/web-ext.config.ts`: Provide default values for all WXT projects on your computer
## Recipes
@@ -31,7 +31,7 @@ You can configure browser startup in 3 places:
To set or customize the browser opened during development:
```ts [web-ext.config.ts]
```ts
export default defineWebExtConfig({
binaries: {
chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome
@@ -41,8 +41,6 @@ export default defineWebExtConfig({
});
```
By default, WXT will try to automatically discover where Chrome/Firefox are installed. However, if you have chrome installed in a non-standard location, you need to set it manually as shown above.
### Persist Data
By default, to keep from modifying your browser's existing profiles, `web-ext` creates a brand new profile every time you run the `dev` script.
@@ -74,14 +72,14 @@ export default defineWebExtConfig({
Now, next time you run the `dev` script, a persistent profile will be created in `.wxt/chrome-data/{profile-name}`. With a persistent profile, you can install devtools extensions to help with development, allow the browser to remember logins, etc, without worrying about the profile being reset the next time you run the `dev` script.
:::tip
You can use any directory you'd like for `--user-data-dir`, the examples above create a persistent profile for each WXT project. To create a profile for all WXT projects, you can put the `chrome-data` directory inside your user's home directory.
You can use any directory you'd like for `--user-data-dir`, the examples above create a persistent profile for each WXT project. To create a profile for all WXT projects, you can put the `chrome-data` directory inside you're user's home directory.
:::
### Disable Opening Browser
If you prefer to load the extension into your browser manually, you can disable the auto-open behavior:
```ts [web-ext.config.ts]
```ts
export default defineWebExtConfig({
disabled: true,
});
@@ -10,7 +10,7 @@ WXT does several pre-processing steps to try and prevent errors during this proc
2. Use `@webext-core/fake-browser` to create a fake version of the `chrome` and `browser` globals expected by extensions.
3. Pre-process the JS/TS code, stripping out the `main` function then tree-shaking unused code from the file
However, this process is not perfect. It doesn't setup all the globals found in the browser and the APIs may behave differently. As such, **_you should avoid using browser or extension APIs outside the `main` function of your entrypoints!_** See [Entrypoint Limitations](/guide/essentials/extension-apis#entrypoint-limitations) for more details.
However, this process is not perfect. It doesn't setup all the globals found in the browser and the APIs may behave differently. As such, **_you should avoid using browser or extension APIs outside the `main` function of your entrypoints!_**
:::tip
If you're running into errors while importing entrypoints, run `wxt prepare --debug` to see more details about this process. When debugging, WXT will print out the pre-processed code to help you identify issues.
@@ -4,7 +4,7 @@
WXT supports [dotenv files the same way as Vite](https://vite.dev/guide/env-and-mode.html#env-files). Create any of the following files:
```plaintext
```
.env
.env.local
.env.[mode]
@@ -42,8 +42,6 @@ WXT provides some custom environment variables based on the current command:
| `import.meta.env.EDGE` | `boolean` | Equivalent to `import.meta.env.BROWSER === "edge"` |
| `import.meta.env.OPERA` | `boolean` | Equivalent to `import.meta.env.BROWSER === "opera"` |
You can set the [`targetBrowsers`](/api/reference/wxt/interfaces/InlineConfig#targetbrowsers) option to make the `BROWSER` variable a more specific type, like `"chrome" | "firefox"`.
You can also access all of [Vite's environment variables](https://vite.dev/guide/env-and-mode.html#env-variables):
| Usage | Type | Description |
@@ -65,6 +63,7 @@ To use environment variables in the manifest, you need to use the function synta
```ts
export default defineConfig({
extensionApi: 'chrome',
modules: ['@wxt-dev/module-vue'],
manifest: { // [!code --]
oauth2: { // [!code --]
@@ -80,16 +79,3 @@ export default defineConfig({
```
WXT can't load your `.env` files until after the config file has been loaded. So by using the function syntax for `manifest`, it defers creating the object until after the `.env` files are loaded into the process.
Note that Vite's runtime environment variables, like `import.meta.env.DEV`, will not be defined. Instead, access the `mode` like this:
```ts
export default defineConfig({
manifest: ({ mode }) => {
const isDev = mode === 'development';
console.log('Is development mode:', isDev);
// ...
},
});
```
+3 -4
View File
@@ -6,7 +6,8 @@ WXT includes a system that lets you hook into the build process and make changes
The easiest way to add a hook is via the `wxt.config.ts`. Here's an example hook that modifies the `manifest.json` file before it is written to the output directory:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
hooks: {
'build:manifestGenerated': (wxt, manifest) => {
@@ -32,7 +33,7 @@ Because hooks can be defined in multiple places, including [WXT Modules](/guide/
To see the order for your project, run `wxt prepare --debug` flag and search for the "Hook execution order":
```plaintext
```
⚙ Hook execution order:
⚙ 1. wxt:built-in:unimport
⚙ 2. src/modules/auto-icons.ts
@@ -50,9 +51,7 @@ Changing execution order is simple:
📄 0.my-module.ts
📄 1.another-module.ts
```
- If you need to run an NPM module after user modules, just make it a user module and prefix the filename with a number!
```ts
// modules/2.i18n.ts
export { default } from '@wxt-dev/i18n/module';
+22 -15
View File
@@ -1,17 +1,10 @@
# Manifest
In WXT, there is no `manifest.json` file in your source code. Instead, WXT generates the manifest from multiple sources:
In WXT, there is no `manifest.json` file in your source code. Instead, WXT generates it during the build process based off files in your project.
- Global options [defined in your `wxt.config.ts` file](#global-options)
- Entrypoint-specific options [defined in your entrypoints](/guide/essentials/entrypoints#defining-manifest-options)
- [WXT Modules](/guide/essentials/wxt-modules) added to your project can modify your manifest
- [Hooks](/guide/essentials/config/hooks) defined in your project can modify your manifest
## Manifest Config
Your extension's `manifest.json` will be output to `.output/{target}/manifest.json` when running `wxt build`.
## Global Options
To add a property to your manifest, use the `manifest` config inside your `wxt.config.ts`:
To manually add a property to the `manifest.json` output during builds, use the `manifest` config inside `wxt.config.ts`:
```ts
export default defineConfig({
@@ -128,7 +121,7 @@ If a version is not present in your `package.json`, it defaults to `"0.0.0"`.
WXT automatically discovers your extension's icon by looking at files in the `public/` directory:
```plaintext
```
public/
├─ icon-16.png
├─ icon-24.png
@@ -137,7 +130,7 @@ public/
└─ icon-128.png
```
Specifically, an icon must match one of these regex to be discovered:
Specifically, if an icon must match one of these regex to be discovered:
<<< @/../packages/wxt/src/core/utils/manifest.ts#snippet
@@ -183,7 +176,7 @@ export default defineConfig({
```ts
export default defineConfig({
manifest: {
host_permissions: ['https://www.google.com/*'],
permissions: ['storage', 'tabs'],
},
});
```
@@ -223,7 +216,22 @@ By default, whenever an `action` is generated, WXT falls back to `browser_action
### Action With Popup
To generate a manifest where a UI appears after clicking the icon, just create a [Popup entrypoint](/guide/essentials/entrypoints#popup). If you want to use a `page_action` for MV2, add the following meta tag to the HTML document's head:
To generate a manifest where a UI appears after clicking the icon, just create a [Popup entrypoint](/guide/essentials/entrypoints#popup).
```ts
export default defineConfig({
hooks: {
build: {
manifestGenerated(manifest) {
// Update the manifest variable by reference
manifest.name = 'Overriden name';
},
},
},
});
```
If you want to use a `page_action` for MV2, add the following meta tag to the HTML document's head:
```html
<meta name="manifest.type" content="page_action" />
@@ -235,7 +243,6 @@ If you want to use the `activeTab` permission or the `browser.action.onClicked`
1. Delete the [Popup entrypoint](/guide/essentials/entrypoints#popup) if it exists
2. Add the `action` key to your manifest:
```ts
export default defineConfig({
manifest: {
+1 -1
View File
@@ -20,7 +20,7 @@ export default defineAppConfig({
```
:::warning
This file is committed to the repo, so don't put any secrets here. Instead, use [Environment Variables](/guide/essentials/config/environment-variables)
This file is committed to the repo, so don't put any secrets here. Instead, use [Environment Variables](#environment-variables)
:::
To access runtime config, WXT provides the `useAppConfig` function:
+1 -1
View File
@@ -14,7 +14,7 @@ At a minimum, you need to create a TSConfig in your root directory that looks li
Or if you're in a monorepo, you may not want to extend the config. If you don't extend it, you need to add `.wxt/wxt.d.ts` to the TypeScript project:
```ts
/// <reference path="./.wxt/wxt.d.ts" />
/// <reference types="./.wxt/wxt.d.ts" />
```
## Compiler Options
+6 -3
View File
@@ -12,7 +12,8 @@ In most cases, you shouldn't change Vite's build settings. WXT provides sensible
You can change Vite's config via the `wxt.config.ts` file:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
import { defineConfig } from 'wxt';
export default defineConfig({
@@ -27,7 +28,8 @@ export default defineConfig({
To add a plugin, install the NPM package and add it to the Vite config:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
import { defineConfig } from 'wxt';
import VueRouter from 'unplugin-vue-router/vite';
@@ -45,7 +47,8 @@ export default defineConfig({
:::warning
Due to the way WXT orchestrates Vite builds, some plugins may not work as expected. For example, `vite-plugin-remove-console` normally only runs when you build for production (`vite build`). However, WXT uses a combination of dev server and builds during development, so you need to manually tell it when to run:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
import { defineConfig } from 'wxt';
import removeConsole from 'vite-plugin-remove-console';
+25 -40
View File
@@ -4,14 +4,12 @@ outline: deep
# Content Scripts
> To create a content script, see [Entrypoint Types](/guide/essentials/entrypoints#content-scripts).
## Context
The first argument to a content script's `main` function is its "context".
The first argument to a content script's `main` function is it's "context".
```ts
// entrypoints/example.content.ts
// entrypoints/content.ts
export default defineContentScript({
main(ctx) {},
});
@@ -19,7 +17,7 @@ export default defineContentScript({
This object is responsible for tracking whether or not the content script's context is "invalidated". Most browsers, by default, do not stop content scripts if the extension is uninstalled, updated, or disabled. When this happens, content scripts start reporting this error:
```plaintext
```
Error: Extension context invalidated.
```
@@ -64,7 +62,7 @@ In regular web extensions, CSS for content scripts is usually a separate CSS fil
In WXT, to add CSS to a content script, simply import the CSS file into your JS entrypoint, and WXT will automatically add the bundled CSS output to the `css` array.
```ts
// entrypoints/example.content/index.ts
// entrypoints/content/index.ts
import './style.css';
export default defineContentScript({
@@ -76,20 +74,20 @@ To create a standalone content script that only includes a CSS file:
1. Create the CSS file: `entrypoints/example.content.css`
2. Use the `build:manifestGenerated` hook to add the content script to the manifest:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
hooks: {
'build:manifestGenerated': (wxt, manifest) => {
"build:manifestGenerated": (wxt, manifest) => {
manifest.content_scripts ??= [];
manifest.content_scripts.push({
// Build extension once to see where your CSS get's written to
css: ['content-scripts/example.css'],
matches: ['*://*/*'],
});
},
},
});
css: ["content-scripts/example.css"],
matches: ["*://*/*"]
)
}
}
})
```
## UI
@@ -200,7 +198,6 @@ export default defineContentScript({
```ts [Svelte]
// entrypoints/example-ui.content/index.ts
import App from './App.svelte';
import { mount, unmount } from 'svelte';
export default defineContentScript({
matches: ['<all_urls>'],
@@ -211,11 +208,14 @@ export default defineContentScript({
anchor: 'body',
onMount: (container) => {
// Create the Svelte app inside the UI container
return mount(App, { target: container });
const app = new App({
target: container,
});
return app;
},
onRemove: (app) => {
// Destroy the app when the UI is removed
unmount(app);
app.$destroy();
},
});
@@ -379,7 +379,6 @@ export default defineContentScript({
// 1. Import the style
import './style.css';
import App from './App.svelte';
import { mount, unmount } from 'svelte';
export default defineContentScript({
matches: ['<all_urls>'],
@@ -394,11 +393,14 @@ export default defineContentScript({
anchor: 'body',
onMount: (container) => {
// Create the Svelte app inside the UI container
return mount(App, { target: container });
const app = new App({
target: container,
});
return app;
},
onRemove: (app) => {
// Destroy the app when the UI is removed
unmount(app);
app?.$destroy();
},
});
@@ -456,7 +458,6 @@ If you don't need to run your UI in the same frame as the content script, you ca
WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/utils/content-script-ui/iframe/functions/createIframeUi), which simplifies setting up the IFrame.
1. Create an HTML page that will be loaded into your IFrame:
```html
<!-- entrypoints/example-iframe.html -->
<!doctype html>
@@ -471,10 +472,9 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/utils/cont
</body>
</html>
```
1. Add the page to the manifest's `web_accessible_resources`:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
manifest: {
web_accessible_resources: [
@@ -486,7 +486,6 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/utils/cont
},
});
```
1. Create and mount the IFrame:
```ts
@@ -567,20 +566,6 @@ export default defineContentScript({
});
```
```json
export default defineConfig({
manifest: {
// ...
web_accessible_resources: [
{
resources: ["example-main-world.js"],
matches: ["*://*/*"],
}
]
}
});
```
`injectScript` works by creating a `script` element on the page pointing to your script. This loads the script into the page's context so it runs in the main world.
`injectScript` returns a promise, that when resolved, means the script has been evaluated by the browser and you can start communicating with it.
+73 -207
View File
@@ -4,30 +4,50 @@ outline: deep
# Entrypoints
WXT uses the files inside the `entrypoints/` directory as inputs when bundling your extension. They can be HTML, JS, CSS, or any variant of those file types supported by Vite (TS, JSX, SCSS, etc).
WXT uses the files inside the `entrypoints/` directory as inputs when bundling your extension. They can be HTML, JS, CSS, or any variant of those file types supported by Vite (Pug, TS, JSX, SCSS, etc).
## Folder Structure
Inside the `entrypoints/` directory, an entrypoint is defined as a single file or directory (with an `index` file) inside it.
:::code-group
Here's an example set of entrypoints:
<!-- prettier-ignore -->
```html [Single File]
```html
📂 entrypoints/
📄 {name}.{ext}
📂 popup/
📄 index.html
📄 main.ts
📄 style.css
📄 background.ts
📄 content.ts
```
<!-- prettier-ignore -->
```html [Directory]
📂 entrypoints/
📂 {name}/
📄 index.{ext}
```
[[toc]]
## Listed vs Unlisted
For web extensions, there are two types of entrypoints:
- **Listed**: Referenced in the `manifest.json`
- **Unlisted**: Not referenced in the `manifest.json`
Throughout the rest of WXT's documentation, listed files are referred to by name. For example:
- Popup
- Options
- Background
- Content Scripts
- Etc.
Some examples of "unlisted" entrypoints:
- A welcome page shown when the extension is installed
- JS files injected by content scripts into the page's main world
:::tip
Regardless of whether a entrypoint is listed or unlisted, it will still be bundled into your extension and be available at runtime.
:::
The entrypoint's `name` dictates the type of entrypoint. For example, to add a ["Background" entrypoint](#background), either of these files would work:
## Adding Entrypoints
An entrypoint can be defined as a single file or directory with an `index` file inside it.
:::code-group
@@ -46,103 +66,18 @@ The entrypoint's `name` dictates the type of entrypoint. For example, to add a [
:::
The entrypoint's name dictates the type of entrypoint, listed vs unlisted. In this example, "background" is the name of the ["Background" entrypoint](#background).
Refer to the [Entrypoint Types](#entrypoint-types) section for the full list of listed entrypoints and their filename patterns.
### Including Other Files
When using an entrypoint directory, `entrypoints/{name}/index.{ext}`, you can add related files next to the `index` file.
<!-- prettier-ignore -->
```html
📂 entrypoints/
📂 popup/
📄 index.html ← This file is the entrypoint
📄 main.ts
📄 style.css
📂 background/
📄 index.ts ← This file is the entrypoint
📄 alarms.ts
📄 messaging.ts
📂 youtube.content/
📄 index.ts ← This file is the entrypoint
📄 style.css
```
:::danger
**DO NOT** put files related to an entrypoint directly inside the `entrypoints/` directory. WXT will treat them as entrypoints and try to build them, usually resulting in an error.
Instead, use a directory for that entrypoint:
<!-- prettier-ignore -->
```html
📂 entrypoints/
📄 popup.html <!-- [!code --] -->
📄 popup.ts <!-- [!code --] -->
📄 popup.css <!-- [!code --] -->
📂 popup/ <!-- [!code ++] -->
📄 index.html <!-- [!code ++] -->
📄 main.ts <!-- [!code ++] -->
📄 style.css <!-- [!code ++] -->
```
:::
### Deeply Nested Entrypoints
While the `entrypoints/` directory might resemble the `pages/` directory of other web frameworks, like Nuxt or Next.js, **it does not support deeply nesting entrypoints** in the same way.
Entrypoints must be zero or one levels deep for WXT to discover and build them:
<!-- prettier-ignore -->
```html
📂 entrypoints/
📂 youtube/ <!-- [!code --] -->
📂 content/ <!-- [!code --] -->
📄 index.ts <!-- [!code --] -->
📄 ... <!-- [!code --] -->
📂 injected/ <!-- [!code --] -->
📄 index.ts <!-- [!code --] -->
📄 ... <!-- [!code --] -->
📂 youtube.content/ <!-- [!code ++] -->
📄 index.ts <!-- [!code ++] -->
📄 ... <!-- [!code ++] -->
📂 youtube-injected/ <!-- [!code ++] -->
📄 index.ts <!-- [!code ++] -->
📄 ... <!-- [!code ++] -->
```
## Unlisted Entrypoints
In web extensions, there are two types of entrypoints:
1. **Listed**: Referenced in the `manifest.json`
2. **Unlisted**: Not referenced in the `manifest.json`
Throughout the rest of WXT's documentation, listed entrypoints are referred to by name. For example:
- Popup
- Options
- Background
- Content Script
However, not all entrypoints in web extensions are listed in the manifest. Some are not listed in the manifest, but are still used by extensions. For example:
- A welcome page shown in a new tab when the extension is installed
- JS files injected by content scripts into the main world
For more details on how to add unlisted entrypoints, see:
- [Unlisted Pages](#unlisted-pages)
- [Unlisted Scripts](#unlisted-scripts)
- [Unlisted CSS](#unlisted-css)
## Defining Manifest Options
Most listed entrypoints have options that need to be added to the `manifest.json`. However with WXT, instead of defining the options in a separate file, _you define these options inside the entrypoint file itself_.
Most listed entrypoints have options that need to be added to the `manifest.json`. With WXT however, instead of defining the options in a separate file, _you define these options inside the entrypoint file itself_.
For example, here's how to define `matches` for content scripts:
```ts [entrypoints/content.ts]
```ts
// entrypoints/content.ts
export default defineContentScript({
matches: ['*://*.wxt.dev/*'],
main() {
@@ -151,17 +86,6 @@ export default defineContentScript({
});
```
For HTML entrypoints, options are configured as `<meta>` tags. For example, to use a `page_action` for your MV2 popup:
```html
<!doctype html>
<html lang="en">
<head>
<meta name="manifest.type" content="page_action" />
</head>
</html>
```
> Refer to the [Entrypoint Types](#entrypoint-types) sections for a list of options configurable inside each entrypoint, and how to define them.
When building your extension, WXT will look at the options defined in your entrypoints, and generate the manifest accordingly.
@@ -172,6 +96,8 @@ When building your extension, WXT will look at the options defined in your entry
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/background/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/background)
For MV2, the background is added as a script to the background page. For MV3, the background becomes a service worker.
<EntrypointPatterns
:patterns="[
['background.[jt]s', 'background.js'],
@@ -205,25 +131,6 @@ export default defineBackground({
:::
For MV2, the background is added as a script to the background page. For MV3, the background becomes a service worker.
When defining your background entrypoint, keep in mind that WXT will import this file in a NodeJS environment during the build process. That means you cannot place any runtime code outside the `main` function.
<!-- prettier-ignore -->
```ts
browser.action.onClicked.addListener(() => { // [!code --]
// ... // [!code --]
}); // [!code --]
export default defineBackground(() => {
browser.action.onClicked.addListener(() => { // [!code ++]
// ... // [!code ++]
}); // [!code ++]
});
```
Refer to the [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) documentation for more details.
### Bookmarks
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
@@ -252,18 +159,18 @@ Refer to the [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) d
</html>
```
When you define a Bookmarks entrypoint, WXT will automatically update the manifest to override the browser's bookmarks page with your own HTML page.
### Content Scripts
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/content_scripts/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts)
See [Content Script UI](/guide/essentials/content-scripts) for more info on creating UIs and including CSS in content scripts.
<EntrypointPatterns
:patterns="[
['content.[jt]sx?', 'content-scripts/content.js'],
['content/index.[jt]sx?', 'content-scripts/content.js'],
['{name}.content.[jt]sx?', 'content-scripts/{name}.js'],
['{name}.content/index.[jt]sx?', 'content-scripts/{name}.js'],
['<name>.content.[jt]sx?', 'content-scripts/<name>.js'],
['<name>.content/index.[jt]sx?', 'content-scripts/<name>.js'],
]"
/>
@@ -296,29 +203,12 @@ export default defineContentScript({
});
```
When defining content script entrypoints, keep in mind that WXT will import this file in a NodeJS environment during the build process. That means you cannot place any runtime code outside the `main` function.
<!-- prettier-ignore -->
```ts
const container = document.createElement('div'); // [!code --]
document.body.append(container); // [!code --]
export default defineContentScript({
main: function () {
const container = document.createElement('div'); // [!code ++]
document.body.append(container); // [!code ++]
},
});
```
Refer to the [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) documentation for more details.
See [Content Script UI](/guide/essentials/content-scripts) for more info on creating UIs and including CSS in content scripts.
### Devtools
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/devtools/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/devtools_page)
Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/examples/devtools-extension#readme) to add different panels and panes.
<EntrypointPatterns
:patterns="[
['devtools.html', 'devtools.html'],
@@ -342,8 +232,6 @@ See [Content Script UI](/guide/essentials/content-scripts) for more info on crea
</html>
```
Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/examples/devtools-extension#readme) to add different panels and panes.
### History
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
@@ -372,8 +260,6 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
</html>
```
When you define a History entrypoint, WXT will automatically update the manifest to override the browser's history page with your own HTML page.
### Newtab
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
@@ -402,8 +288,6 @@ When you define a History entrypoint, WXT will automatically update the manifest
</html>
```
When you define a Newtab entrypoint, WXT will automatically update the manifest to override the browser's new tab page with your own HTML page.
### Options
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/options/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/options_ui)
@@ -493,8 +377,8 @@ Firefox does not support sandboxed pages.
:patterns="[
['sandbox.html', 'sandbox.html'],
['sandbox/index.html', 'sandbox.html'],
['{name}.sandbox.html', '{name}.html'],
['{name}.sandbox/index.html', '{name}.html'],
['<name>.sandbox.html', '<name>.html'],
['<name>.sandbox/index.html', '<name>.html'],
]"
/>
@@ -520,12 +404,14 @@ Firefox does not support sandboxed pages.
[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/sidePanel/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Sidebars)
In Chrome, side panels use the `side_panel` API, while Firefox uses the `sidebar_action` API.
<EntrypointPatterns
:patterns="[
['sidepanel.html', 'sidepanel.html'],
['sidepanel/index.html', 'sidepanel.html'],
['{name}.sidepanel.html', '{name}.html` '],
['{name}.sidepanel/index.html', '{name}.html` '],
['<name>.sidepanel.html', '<name>.html` '],
['<name>.sidepanel/index.html', '<name>.html` '],
]"
/>
@@ -559,18 +445,20 @@ Firefox does not support sandboxed pages.
</html>
```
In Chrome, side panels use the `side_panel` API, while Firefox uses the `sidebar_action` API.
### Unlisted CSS
Follow Vite's guide to setup your preprocessor of choice: https://vitejs.dev/guide/features.html#css-pre-processors
CSS entrypoints are always unlisted. To add CSS to a content script, see the [Content Script](/guide/essentials/content-scripts#css) docs.
<EntrypointPatterns
:patterns="[
['{name}.(css|scss|sass|less|styl|stylus)', '{name}.css'],
['{name}/index.(css|scss|sass|less|styl|stylus)', '{name}.css'],
['<name>.(css|scss|sass|less|styl|stylus)', '<name>.css'],
['<name>/index.(css|scss|sass|less|styl|stylus)', '<name>.css'],
['content.(css|scss|sass|less|styl|stylus)', 'content-scripts/content.css'],
['content/index.(css|scss|sass|less|styl|stylus)', 'content-scripts/content.css'],
['{name}.content.(css|scss|sass|less|styl|stylus)', 'content-scripts/{name}.css'],
['{name}.content/index.(css|scss|sass|less|styl|stylus)', 'content-scripts/{name}.css'],
['<name>.content.(css|scss|sass|less|styl|stylus)', 'content-scripts/<name>.css'],
['<name>.content/index.(css|scss|sass|less|styl|stylus)', 'content-scripts/<name>.css'],
]"
/>
@@ -580,16 +468,12 @@ body {
}
```
Follow Vite's guide to setup your preprocessor of choice: <https://vitejs.dev/guide/features.html#css-pre-processors>
CSS entrypoints are always unlisted. To add CSS to a content script, see the [Content Script](/guide/essentials/content-scripts#css) docs.
### Unlisted Pages
<EntrypointPatterns
:patterns="[
['{name}.html', '{name}.html'],
['{name}/index.html', '{name}.html'],
['<name>.html', '<name>.html'],
['<name>/index.html', '<name>.html'],
]"
/>
@@ -611,21 +495,20 @@ CSS entrypoints are always unlisted. To add CSS to a content script, see the [Co
</html>
```
At runtime, unlisted pages are accessible at `/{name}.html`:
Pages are accessible at `/<name>.html`:
```ts
const url = browser.runtime.getURL('/{name}.html');
const url = browser.runtime.getURL('/<name>.html');
console.log(url); // "chrome-extension://{id}/{name}.html"
window.open(url); // Open the page in a new tab
console.log(url); // "chrome-extension://<id>/<name>.html"
```
### Unlisted Scripts
<EntrypointPatterns
:patterns="[
['{name}.[jt]sx?', '{name}.js'],
['{name}/index.[jt]sx?', '{name}.js'],
['<name>.[jt]sx?', '<name>.js'],
['<name>/index.[jt]sx?', '<name>.js'],
]"
/>
@@ -651,29 +534,12 @@ export default defineUnlistedScript({
:::
At runtime, unlisted scripts are accessible from `/{name}.js`:
Scripts are accessible from `/<name>.js`:
```ts
const url = browser.runtime.getURL('/{name}.js');
const url = browser.runtime.getURL('/<name>.js');
console.log(url); // "chrome-extension://{id}/{name}.js"
console.log(url); // "chrome-extension://<id>/<name>.js"
```
You are responsible for loading/running these scripts where needed. If necessary, don't forget to add the script and/or any related assets to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources).
When defining an unlisted script, keep in mind that WXT will import this file in a NodeJS environment during the build process. That means you cannot place any runtime code outside the `main` function.
<!-- prettier-ignore -->
```ts
document.querySelectorAll('a').forEach((anchor) => { // [!code --]
// ... // [!code --]
}); // [!code --]
export default defineUnlistedScript(() => {
document.querySelectorAll('a').forEach((anchor) => { // [!code ++]
// ... // [!code ++]
}); // [!code ++]
});
```
Refer to the [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) documentation for more details.
You are responsible for loading/running these scripts where needed. If necessary, don't forget to add the script and/or any related stylesheets to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources).
+7 -7
View File
@@ -1,10 +1,12 @@
# ES Modules
Your source code should always be written as ESM. However, you have some control whether an entrypoint is bundled as ESM.
Currently, ESM entrypoints are opt-in, so you must configure each entrypoint with that in mind.
## HTML Pages <Badge type="warning" text="≥0.0.1" />
Vite only supports bundling JS from HTML pages as ESM. Ensure you have added `type="module"` to your `<script>` tags:
In general, you should always make HTML pages import ESM scripts, unless you need to support old browsers.
To make a script ESM, add `type="module"`:
<!-- prettier-ignore -->
```html
@@ -14,7 +16,7 @@ Vite only supports bundling JS from HTML pages as ESM. Ensure you have added `ty
## Background <Badge type="warning" text="≥0.16.0" />
By default, your background will be bundled into a single file as IIFE. You can change this by setting `type: "module"` in your background entrypoint:
In your background script, set `type: "module"`:
```ts
export default defineBackground({
@@ -25,14 +27,12 @@ export default defineBackground({
});
```
This will change the output format to ESM, enable code-spliting between your background script and HTML pages, and set `"type": "module"` in your manifest.
:::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.
:::
## Content Scripts
WXT does not yet include built-in support for bundling content scripts as ESM. The plan is to add support for chunking to reduce bundle size, but not support HMR for now. There are several technical issues that make implementing a generic solution for HMR impossible. See [Content Script ESM Support #357](https://github.com/wxt-dev/wxt/issues/357) for details.
WXT does not yet include built-in support for ESM content scripts. The plan is to add support for chunking to reduce bundle size, but not support HMR for now. There are several technical issues that make implementing a generic solution for HMR impossible. See [Content Script ESM Support #357](https://github.com/wxt-dev/wxt/issues/357) for details.
If you can't wait, and need ESM support right now, you can implement ESM support manually. See the [ESM Content Script UI](https://github.com/wxt-dev/examples/tree/main/examples/esm-content-script-ui) example to learn how.
If you can't wait, and need ESM support right now, you can implement ESM support manually. See the [ESM Content Script UI](https://github.com/wxt-dev/examples/tree/main/examples/esm-content-script-ui) example to get started.
+3 -71
View File
@@ -20,28 +20,20 @@ With auto-imports enabled, you don't even need to import this variable from `wxt
The `browser` variable WXT provides is a simple export of the `browser` or `chrome` globals provided by the browser at runtime:
<<< @/../packages/browser/src/index.mjs#snippet
<<< @/../packages/wxt/src/browser.ts#snippet
This means you can use the promise-style API for both MV2 and MV3, and it will work across all browsers (Chromium, Firefox, Safari, etc).
## Accessing Types
All types can be accessed via WXT's `Browser` namespace:
All types can be accessed via WXT's `browser` object:
```ts
import { type Browser } from 'wxt/browser';
function handleMessage(message: any, sender: Browser.runtime.MessageSender) {
function handleMessage(message: any, sender: browser.runtime.Sender) {
// ...
}
```
## Using `webextension-polyfill`
If you want to use the `webextension-polyfill` when importing `browser`, you can do so by installing the `@wxt-dev/webextension-polyfill` package.
See it's [Installation Guide](https://github.com/wxt-dev/wxt/blob/main/packages/webextension-polyfill/README.md) to get started.
## Feature Detection
Depending on the manifest version, browser, and permissions, some APIs are not available at runtime. If an API is not available, it will be `undefined`.
@@ -75,63 +67,3 @@ Alternatively, if you're trying to use similar APIs under different names (to su
//
});
```
## 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**.
To prevent some basic errors, WXT polyfills these globals with the same in-memory, fake implementation it uses for testing: [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/fake-browser/installation/). However, not all the APIs have been implemented.
So it is extremely important to NEVER use `browser.*` extension APIs outside the main function of any JS/TS entrypoints (background, content scripts, and unlisted scripts). If you do, you'll see an error like this:
```plaintext
✖ Command failed after 440 ms
ERROR Browser.action.onClicked.addListener not implemented.
```
The fix is simple, just move your API usage into the entrypoint's main function:
:::code-group
```ts [background.ts]
browser.action.onClicked.addListener(() => {
/* ... */
}); // [!code --]
export default defineBackground(() => {
browser.action.onClicked.addListener(() => {
/* ... */
}); // [!code ++]
});
```
```ts [content.ts]
browser.runtime.onMessage.addListener(() => {
/* ... */
}); // [!code --]
export default defineContentScript({
main() {
browser.runtime.onMessage.addListener(() => {
/* ... */
}); // [!code ++]
},
});
```
```ts [unlisted.ts]
browser.runtime.onMessage.addListener(() => {
/* ... */
}); // [!code --]
export default defineUnlistedScript(() => {
browser.runtime.onMessage.addListener(() => {
/* ... */
}); // [!code ++]
});
```
:::
Read [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) for more technical details about this limitation.
+1 -9
View File
@@ -9,7 +9,6 @@ This page discusses how to setup internationalization using the vanilla `browser
## Usage
1. Add `default_locale` to your manifest:
```ts
export default defineConfig({
manifest: {
@@ -17,12 +16,11 @@ This page discusses how to setup internationalization using the vanilla `browser
},
});
```
2. Create `messages.json` files in the `public/` directory:
<!-- prettier-ignore -->
```html
📂 {rootDir}/
📂 {srcDir}/
📂 public/
📂 _locales/
📂 en/
@@ -43,11 +41,9 @@ This page discusses how to setup internationalization using the vanilla `browser
```
3. Get the translation:
```ts
browser.i18n.getMessage('helloWorld');
```
4. _Optional_: Add translations for extension name and description:
```json
@@ -85,10 +81,6 @@ However, it is recommended you stick with the vanilla API (or a package based on
- Translations are not bundled multiple times, keeping your extension small
- Zero configuration
However, there is one major downside to the vanilla API and any packages built on top of it:
- Language cannot be changed without changing your browser/system language
Here are some examples of how to setup a third party i18n library:
- [vue-i18n](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vue-i18n)
-1
View File
@@ -14,4 +14,3 @@ Here are some popular messaging libraries that support all browsers and work wit
- [`webext-bridge`](https://www.npmjs.com/package/webext-bridge) - Messaging in WebExtensions made super easy. Out of the box.
- [`@webext-core/messaging`](https://www.npmjs.com/package/@webext-core/messaging) - Light weight, type-safe wrapper around the web extension messaging APIs
- [`@webext-core/proxy-service`](https://www.npmjs.com/package/@webext-core/proxy-service) - A type-safe wrapper around the web extension messaging APIs that lets you call a function from anywhere, but execute it in the background.
- [`Comctx`](https://github.com/molvqingtai/comctx) - Cross-context RPC solution with type safety and flexible adapters.
+26 -25
View File
@@ -30,10 +30,9 @@ Here's a brief summary of each of these files and directories:
- `.wxt/`: Generated by WXT, it contains TS config
- `assets/`: Contains all CSS, images, and other assets that should be processed by WXT
- `components/`: Auto-imported by default, contains UI components
- `composables/`: Auto-imported by default, contains source code for your project's composable functions for Vue
- `composables/`: Auto-imported by default, contains composable functions for Vue
- `entrypoints/`: Contains all the entrypoints that get bundled into your extension
- `hooks/`: Auto-imported by default, contains source code for your project's hooks for React and Solid
- `modules/`: Contains [local WXT Modules](/guide/essentials/wxt-modules) for your project
- `hooks/`: Auto-imported by default, contains hooks for React and Solid
- `public/`: Contains any files you want to copy into the output folder as-is, without being processed by WXT
- `utils/`: Auto-imported by default, contains generic utilities used throughout your project
- `.env`: Contains [Environment Variables](/guide/essentials/config/environment-variables)
@@ -48,7 +47,8 @@ Here's a brief summary of each of these files and directories:
Many developers like having a `src/` directory to separate source code from configuration files. You can enable it inside the `wxt.config.ts` file:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
srcDir: 'src',
});
@@ -59,24 +59,24 @@ After enabling it, your project structure should look like this:
<!-- prettier-ignore -->
```html
📂 {rootDir}/
📁 .output/
📁 .wxt/
📁 modules/
📁 public/
📂 src/
📁 assets/
📁 components/
📁 composables/
📁 entrypoints/
📁 hooks/
📁 utils/
📄 app.config.ts
📄 .env
📄 .env.publish
📄 package.json
📄 tsconfig.json
📄 web-ext.config.ts
📄 wxt.config.ts
📁 .output/
📁 .wxt/
📁 modules/
📁 public/
📂 src/
📁 assets/
📁 components/
📁 composables/
📁 entrypoints/
📁 hooks/
📁 utils/
📄 app.config.ts
📄 .env
📄 .env.publish
📄 package.json
📄 tsconfig.json
📄 web-ext.config.ts
📄 wxt.config.ts
```
## Customizing Other Directories
@@ -84,16 +84,17 @@ After enabling it, your project structure should look like this:
You can configure the following directories:
<!-- prettier-ignore -->
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
// Relative to project root
srcDir: "src", // default: "."
modulesDir: "wxt-modules", // default: "modules"
outDir: "dist", // default: ".output"
publicDir: "static", // default: "public"
// Relative to srcDir
entrypointsDir: "entries", // default: "entrypoints"
modulesDir: "wxt-modules", // default: "modules"
publicDir: "static", // default: "public"
})
```
+7 -5
View File
@@ -23,7 +23,7 @@ WXT provides two commands to help automate submitting a new version for review a
- `wxt submit init`: Setup all the required secrets and options for the `wxt submit` command
- `wxt submit`: Submit new versions of your extension for review (and publish them automatically once approved)
To get started, run `wxt submit init` and follow the prompts, or run `wxt submit --help` to view all available options. Once finished, you should have a `.env.submit` file! WXT will use this file to submit your updates.
To get started, run `wxt submit init` and follow the prompts. Once finished, you should have a `.env.submit` file! WXT will use this file to submit your updates.
> In CI, make sure you add all the environment variables to the submit step.
@@ -72,13 +72,13 @@ jobs:
submit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
node-version: 20
cache: 'pnpm'
- name: Install dependencies
@@ -133,7 +133,8 @@ When running `wxt zip -b firefox`, WXT will zip both your extension and sources.
To customize which files are zipped, add the `zip` option to your config file.
```ts [wxt.config.ts]
```ts
// wxt.config.ts
import { defineConfig } from 'wxt';
export default defineConfig({
@@ -183,7 +184,8 @@ See Issue [#377](https://github.com/wxt-dev/wxt/issues/377) for more details.
If you use private packages and you don't want to provide your auth token to the Firefox team during the review process, you can use `zip.downloadPackages` to download any private packages and include them in the zip.
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
zip: {
downloadPackages: [
@@ -62,7 +62,6 @@ Here are some examples:
```
- HTML file only built for all targets other than `chrome`:
```html
<!doctype html>
<html lang="en">
+2 -2
View File
@@ -85,7 +85,7 @@ import { injectScript, createShadowRootUi } from '#imports';
But Vitest sees this:
```ts
import { injectScript } from 'wxt/utils/inject-script';
import { injectScript } from 'wxt/browser';
import { createShadowRootUi } from 'wxt/utils/content-script-ui/shadow-root';
```
@@ -105,4 +105,4 @@ To use a different framework, you will likely have to disable auto-imports, setu
It is possible to do, but will require a bit more setup. Refer to Vitest's setup for an example of how to setup a test environment:
<https://github.com/wxt-dev/wxt/blob/main/packages/wxt/src/testing/wxt-vitest-plugin.ts>
https://github.com/wxt-dev/wxt/blob/main/packages/wxt/src/testing/wxt-vitest-plugin.ts
+4 -8
View File
@@ -9,23 +9,19 @@ WXT provides a "module system" that let's you run code at different steps in the
There are two ways to add a module to your project:
1. **NPM**: install an NPM package, like [`@wxt-dev/auto-icons`](https://www.npmjs.com/package/@wxt-dev/auto-icons) and add it to your config:
```ts [wxt.config.ts]
```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>/
```
<srcDir>/
modules/
my-module.ts
```
> To learn more about writing your own modules, read the [Writing Modules](/guide/essentials/wxt-modules) docs.
## Module Options
+1 -24
View File
@@ -45,64 +45,48 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
1. Create a new project
:::code-group
```sh [PNPM]
cd my-project
pnpm init
```
```sh [Bun]
cd my-project
bun init
```
```sh [NPM]
cd my-project
npm init
```
```sh [Yarn]
cd my-project
yarn init
```
:::
2. Install WXT:
:::code-group
```sh [PNPM]
pnpm i -D wxt
```
```sh [Bun]
bun i -D wxt
```
```sh [NPM]
npm i -D wxt
```
```sh [Yarn]
yarn add --dev wxt
```
:::
3. Add an entrypoint, `my-project/entrypoints/background.ts`:
:::code-group
```ts
export default defineBackground(() => {
console.log('Hello world!');
});
```
:::
4. Add scripts to your `package.json`:
```json [package.json]
```json
{
"scripts": {
"dev": "wxt", // [!code ++]
@@ -115,26 +99,20 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
}
}
```
5. Run your extension in dev mode
:::code-group
```sh [PNPM]
pnpm dev
```
```sh [Bun]
bun run dev
```
```sh [NPM]
npm run dev
```
```sh [Yarn]
yarn dev
```
:::
WXT will automatically open a browser window with your extension installed.
@@ -143,4 +121,3 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
- Keep reading on about WXT's [Project Structure](/guide/essentials/project-structure) and other essential concepts to learn
- Configure [automatic browser startup](/guide/essentials/config/browser-startup) during dev mode
- Explore [WXT's example library](/examples) to see how to use specific APIs or perform common tasks
- Checkout the [community page](/guide/resources/community) for a list of resources made by the community!
+1 -1
View File
@@ -1,4 +1,4 @@
# Welcome to WXT
# Welcome to WXT!
WXT is a modern, open-source framework for building web extensions. Inspired by Nuxt, its goals are to:
-14
View File
@@ -1,14 +0,0 @@
# Community
This page is dedicated to all the awesome people how have made something for WXT or that works with WXT. Blog posts, YouTube videos, NPM packages, etc. If a section doesn't exist for the thing you made, add one!
[[toc]]
## Blog Posts
- [Building Modern Cross Browser Web Extensions](https://aabidk.dev/tags/wxt/) by Aabid ([@aabidk20](https://github.com/aabidk20))
## NPM Packages
- [`@webext-core/*`](https://webext-core.aklinker1.io/): Easy-to-use utilities for writing and testing web extensions that work on all browsers.
- [`Comctx`](https://github.com/molvqingtai/comctx): Cross-context RPC solution with type safety and flexible adapters.
-5
View File
@@ -10,7 +10,6 @@ Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework)
| Features | WXT | Plasmo | CRXJS |
| ------------------------------------------------------- | :-----: | :-----: | :-----: |
| Maintained | ✅ | 🟡 [^n] | 🟡 [^m] |
| Supports all browsers | ✅ | ✅ | 🟡 [^j] |
| MV2 Support | ✅ | ✅ | 🟡 [^a] |
| MV3 Support | ✅ | ✅ | 🟡 [^a] |
@@ -65,7 +64,3 @@ Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework)
[^k]: There is no built-in wrapper around this API. However, you can still access the standard APIs via `chrome`/`browser` globals or use any 3rd party NPM package.
[^l]: WIP, moving very slowly. Follow [wxt-dev/wxt#357](https://github.com/wxt-dev/wxt/issues/357) for updates.
[^m]: See [crxjs/chrome-extension-tools#974](https://github.com/crxjs/chrome-extension-tools/discussions/974)
[^n]: Appears to be in maintenance mode with little to no maintainers nor feature development happening and _(see [wxt-dev/wxt#1404 (comment)](https://github.com/wxt-dev/wxt/pull/1404#issuecomment-2643089518))_
+39 -153
View File
@@ -20,169 +20,55 @@ await chrome.scripting.getRegisteredContentScripts();
## How do I disable opening the browser automatically during development?
See <https://wxt.dev/guide/essentials/config/browser-startup.html#disable-opening-browser>
See https://wxt.dev/guide/essentials/config/browser-startup.html#disable-opening-browser
## How do I stay logged into a website during development?
See <https://wxt.dev/guide/essentials/config/browser-startup.html#persist-data>
See https://wxt.dev/guide/essentials/config/browser-startup.html#persist-data
## My component library doesn't work in content scripts
## My component library doesn't work in content scripts!
This is usually caused by one of two things (or both) when using `createShadowRootUi`:
Component libraries place their CSS in the document's `<head>` by default. When using `createShadowRootUi`, your UI is isolated from the document's styles because it's inside a ShadowRoot.
1. Styles are added outside the `ShadowRoot`
To fix this, you need to tell your component library to insert it's CSS inside the shadow root. Here's the docs for a couple of popular libraries:
:::details
Some component libraries manually add CSS to the page by adding a `<style>` or `<link>` element. They place this element in the document's `<head>` by default. This causes your styles to be placed outside the `ShadowRoot` and it's isolation blocks the styles from being applied to your UI.
- React
- Ant Design: [`StyleProvider`](https://ant.design/docs/react/compatible-style#shadow-dom-usage)
- Mantine: [`MantineProvider#getRootElement` and `MantineProvider#cssVariablesSelector`](https://mantine.dev/theming/mantine-provider/)
When a library does this, **you need to tell the library where to put its styles**. Here's the documentation for a few popular component libraries:
- Ant Design: [`StyleProvider`](https://ant.design/docs/react/compatible-style#shadow-dom-usage)
- Mantine: [`MantineProvider#getRootElement` and `MantineProvider#cssVariablesSelector`](https://mantine.dev/theming/mantine-provider/)
> If your library isn't listed above, try searching it's docs/issues for "shadow root", "shadow dom", or "css container".
> If your library isn't listed above, try searching it's docs/issues for "shadow root", "shadow dom", or "css container". Not all libraries support shadow DOMs, you may have to open an issue to request this feature.
`createShadowRootUi` provides it's own `<head>` element inside the shadow root, so that were you should tell the library to add the CSS. Here's an example with Ant Design:
Here's an example of configuring Antd's styles:
```tsx
import { StyleProvider } from '@ant-design/cssinjs'; // [!code ++]
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
```tsx
import { StyleProvider } from '@ant-design/cssinjs';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
const ui = await createShadowRootUi(ctx, {
name: 'example-ui',
position: 'inline',
anchor: 'body',
onMount: (container) => { // [!code --]
onMount: (container, shadow) => { // [!code ++]
const cssContainer = shadow.querySelector("head")!; // [!code ++]
const root = ReactDOM.createRoot(container);
root.render(
<StyleProvider container={cssContainer}> // [!code ++]
<App />
</StyleProvider> // [!code ++]
);
return root;
},
onRemove: (root) => {
root?.unmount();
},
});
```
const ui = await create`ShadowRoot`Ui(ctx, {
// ...
onMount: (container, shadow) => {
const cssContainer = shadow.querySelector('head')!;
const root = ReactDOM.createRoot(container);
root.render(
<StyleProvider container={cssContainer}>
<App />
</StyleProvider>,
);
return root;
},
});
```
Note that this doesn't effect all component libraries, just ones that inject CSS themselves rather than having you import their CSS. This approach is more prevailent in the React community, but not limited to it. That's why only React libraries are listed above. Vuetify, for example, works just fine because you import its CSS - WXT picks up on this and the CSS is added inside the shadow root automatically:
:::
2. UI elements are added outside the `ShadowRoot`
::::::details
This is mostly caused by `Teleport` or `Portal` components that render an element somewhere else in the DOM, usually in the document's `<body>`. This is usually done for dialogs or popover components. This renders the element is outside the `ShadowRoot`, so styles are not applied to it.
To fix this, **you need to both provide a target to your app AND pass the target to the `Teleport`/`Portal`**.
First, store the reference to the `ShadowRoot`'s `<body>` element (not the document's `<body>`):
:::code-group
```ts [Vue]
import { createApp } from 'vue';
import App from './App.vue';
const ui = await create`ShadowRoot`Ui(ctx, {
// ...
onMount: (container, shadow) => {
const teleportTarget = shadow.querySelector('body')!;
const app = createApp(App)
.provide('TeleportTarget', teleportTarget)
.mount(container);
return app;
},
});
ui.mount();
```
```tsx [React]
// hooks/PortalTargetContext.ts
import { createContext } from 'react';
export const PortalTargetContext = createContext<HTMLElement>();
// entrypoints/example.content.ts
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import PortalTargetContext from '~/hooks/PortalTargetContext';
const ui = await create`ShadowRoot`Ui(ctx, {
// ...
onMount: (container, shadow) => {
const portalTarget = shadow.querySelector('body')!;
const root = ReactDOM.createRoot(container);
root.render(
<PortalTargetContext.Provider value={portalTarget}>
<App />
</PortalTargetContext.Provider>,
);
return root;
},
});
ui.mount();
```
:::
Then use the reference when teleporting/portaling part of your UI to a different place in the DOM:
:::code-group
```vue [Vue]
<script lang="ts" setup>
import { Teleport } from 'vue';
const teleportTarget = inject('TeleportTarget');
</script>
<template>
<div>
<Teleport :to="teleportTarget">
<dialog>My dialog</dialog>
</Teleport>
</div>
</template>
```
```tsx [React]
import { useContext } from 'react';
import { createPortal } from 'react-dom';
import PortalTargetContext from '~/hooks/PortalTargetContext';
const MyComponent = () => {
const portalTarget = useContext(PortalTargetContext);
return <div>{createPortal(<dialog>My dialog</dialog>, portalTarget)}</div>;
};
```
:::
If you use ShadCN, [see this blog post](https://aabidk.dev/blog/building-modern-cross-browser-web-extensions-content-scripts-and-ui/#using-radixui-portals-to-move-the-dialog-to-shadow-dom).
::::::
Both issues have the same cause: the library puts something outside the `ShadowRoot`, and the `ShadowRoot`'s isolation prevents CSS from being applied to your UI.
Both issues have the same fix: tell the library to put elements inside the `ShadowRoot`, not outside it. See the details above for more information and example fixes for each problem.
## Is there an LLM trained on WXT's docs that I chat with?
Yes! There's a "Ask AI" button in the bottom right of the page, try it out! Or visit <https://knowledge.wxt.dev/> for a fullscreen experience.
Additionally, if you want to train your own model or provide context to your editor, you can use the LLM knowledge files hosted by the site:
<https://wxt.dev/knowledge/index.json>
You don't need to crawl the entire website, these files already contain all the relevant docs for training a LLM on WXT. But feel free to crawl it and generate your own files if you want!
## How do I run my WXT project with docker / [devcontainers](https://containers.dev)?
To run the WXT dev server in a devcontainer, but load the dev build of your extension in your browser:
1. **Bind-mount your project directory to your host**
If you're using VS Code, you can open your project folder with the `Dev Containers: Open Folder in Container...` command. This keeps the folder synchronized between your host and the devcontainer, ensuring that the extension `dist` directory remains accessible from the host.
2. **Disable auto-opening the browser**
WXT automatically opens your browser during development, but since you're running inside a container, it won't be able to access it. Follow [the guide here](https://wxt.dev/guide/essentials/config/browser-startup.html#disable-opening-browser) to disable browser auto-opening in your `wxt.config.ts`.
3. **Tell WXT to listen on all network interfaces**
To enable hot-reloading, your extension has to connect to the WXT dev server running inside your container. WXT will only listen on `localhost` by default, which prevents connections from outside the devcontainer. To fix this you can instruct WXT to listen on all interfaces with `wxt --host 0.0.0.0`.
```ts
import 'vuetify/styles'; // <-- This line imports the CSS, just like importing a .css file
import { createVuetify } from 'vuetify';
```
+4 -22
View File
@@ -18,7 +18,7 @@ pnpm dlx wxt@latest init example-wxt --template vanilla
In general, you'll need to:
&ensp;<input type="checkbox" /> Install `wxt`<br />
&ensp;<input type="checkbox" /> [Extend `.wxt/tsconfig.json`](/guide/essentials/config/typescript#typescript-configuration) in your project's `tsconfig.json`<br />
&ensp;<input type="checkbox" /> [Extend `.wxt/tsconfig.json`](/guide/essentials/config/typescript.html#typescript-configuration) in your project's `tsconfig.json`<br />
&ensp;<input type="checkbox" /> Update/create `package.json` scripts to use `wxt` (don't forget about `postinstall`)<br />
&ensp;<input type="checkbox" /> Move entrypoints into `entrypoints/` directory<br />
&ensp;<input type="checkbox" /> Move assets into either the `assets/` or `public/` directories<br />
@@ -26,7 +26,7 @@ In general, you'll need to:
&ensp;<input type="checkbox" /> Convert custom import syntax to be compatible with Vite<br />
&ensp;<input type="checkbox" /> Add a default export to JS entrypoints (`defineBackground`, `defineContentScript`, or `defineUnlistedScript`)<br />
&ensp;<input type="checkbox" /> Use the `browser` global instead of `chrome`<br />
&ensp;<input type="checkbox" /> ⚠️ Compare final `manifest.json` files, making sure permissions and host permissions are unchanged<br/>
&ensp;<input type="checkbox" /> Compare final `manifest.json` files, making sure permissions and host permissions are unchanged<br/>
:::warning
If your extension is already live on the Chrome Web Store, use [Google's update testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool) to make sure no new permissions are being requested.
:::
@@ -48,25 +48,7 @@ Here's specific steps for other popular frameworks/build tools.
5. Convert Plasmo's custom import resolutions to Vite's
6. If importing remote code via a URL, add a `url:` prefix so it works with WXT
7. Replace your [Plasmo tags](https://docs.plasmo.com/framework/workflows/build#with-a-custom-tag) (`--tag`) with [WXT build modes](/guide/essentials/config/build-mode) (`--mode`)
8. ⚠️ Compare the old production manifest to `.output/*/manifest.json`. They should have the same content as before. If not, tweak your entrypoints and config until they are the same.
### CRXJS
If you used CRXJS's vite plugin, it's a simple refactor! The main difference between CRXJS and WXT is how the tools decide which entrypoints to build. CRXJS looks at your `manifest` (and vite config for "unlisted" entries), while WXT looks at files in the `entrypoints` directory.
To migrate:
1. Move all entrypoints into the `entrypoints` directory, refactoring to WXT's style (TS files have a default export).
2. Move [entrypoint specific options out of the manifest](/guide/essentials/entrypoints#defining-manifest-options) and into the entrypoint files themselves (like content script `matches` or `run_at`).
3. Move any other `manifest.json` options [into the `wxt.config.ts` file](/guide/essentials/config/manifest), like permissions.
4. For simplicity, you'll probably want to [disable auto-imports](/guide/essentials/config/auto-imports#disabling-auto-imports) at first (unless you were already using them via `unimport` or `unplugin-auto-imports`). If you like the feature, you can enable it later once you've finished the migration.
5. Update your `package.json` to include all of [WXT's suggested scripts (see step 4)](/guide/installation#from-scratch)
6. Specifically, make sure you add the `"postinstall": "wxt prepare"` script to your `package.json`.
7. Delete your `vite.config.ts` file. Move any plugins into the `wxt.config.ts` file. If you use a frontend framework, [install the relevant WXT module](/guide/essentials/frontend-frameworks).
8. Update your typescript project. [Extend WXT's generated config](/guide/essentials/config/typescript), and [add any path aliases to your `wxt.config.ts` file](/guide/essentials/config/typescript#tsconfig-paths).
9. ⚠️ Compare the old production manifest to `.output/*/manifest.json`. They should have the same content as before. If not, tweak your entrypoints and config until they are the same.
Here's an example migration: [GitHub Better Line Counts - CRXJS &rarr; WXT](https://github.com/aklinker1/github-better-line-counts/commit/39d766d2ba86866efefc2e9004af554ee434e2a8)
8. Compare your output `manifest.json` files from before the migration to after the migration. They should have the same content. If not, tweak your entrypoints and config to get as close as possible.
### `vite-plugin-web-extension`
@@ -78,4 +60,4 @@ Since you're already using Vite, it's a simple refactor.
4. Add `"postinstall": "wxt prepare"` script
5. Move the `manifest.json` into `wxt.config.ts`
6. Move any custom settings from `vite.config.ts` into `wxt.config.ts`'s
7. ⚠️ Compare the old production manifest to `.output/*/manifest.json`. They should have the same content as before. If not, tweak your entrypoints and config until they are the same.
7. Compare `dist/manifest.json` to `.output/*/manifest.json`, they should have the same content as before. If not, tweak your entrypoints and config to get as close as possible.
+103 -182
View File
@@ -6,31 +6,12 @@ outline: deep
## Overview
To upgrade WXT to the latest major version:
1. Install it, skipping scripts so `wxt prepare` doesn't run - it will probably throw an error after a major version change (we'll run it later).
```sh
pnpm i wxt@latest --ignore-scripts
```
2. Follow the upgrade steps below to fix any breaking changes.
3. Run `wxt prepare`. It should succeed and type errors will go away afterwords.
```sh
pnpm wxt prepare
```
4. Manually test to make sure both dev mode and production builds work.
For minor or patch version updates, there are no special steps. Just update it with your package manager:
To upgrade WXT to the latest version... just install it!
```sh
pnpm i wxt@latest
```
---
Listed below are all the breaking changes you should address when upgrading to a new version of WXT.
Currently, WXT is in pre-release. This means changes to the second digit, `v0.X`, are considered major and have breaking changes. Once v1 is released, only major version bumps will have breaking changes.
@@ -40,136 +21,88 @@ Currently, WXT is in pre-release. This means changes to the second digit, `v0.X`
v0.20 is a big release! There are lots of breaking changes because this version is intended to be a release candidate for v1.0. If all goes well, v1.0 will be released with no additional breaking changes.
:::tip
Read through all the changes once before updating your code.
Read through all the changes once before making any code changes.
:::
### `webextension-polyfill` Removed
WXT's `browser` no longer uses the `webextension-polyfill`!
:::details Why?
See <https://github.com/wxt-dev/wxt/issues/784>
:::
WXT no longer uses the `webextension-polyfill` internally and `wxt/browser` uses the `chrome`/`browser` globals provided by the browser.
To upgrade, you have two options:
1. **Stop using the polyfill**
- If you're already using `extensionApi: "chrome"`, then you're not using the polyfill and there is nothing to change!
- Otherwise there is only one change: `browser.runtime.onMessage` no longer supports using promises to return a response:
```ts
browser.runtime.onMessage.addListener(async () => { // [!code --]
const res = await someAsyncWork(); // [!code --]
return res; // [!code --]
browser.runtime.onMessage.addListener(async (_message, _sender, sendResponse) => { // [!code ++]
someAsyncWork().then((res) => { // [!code ++]
sendResponse(res); // [!code ++]
}); // [!code ++]
return true; // [!code ++]
});
```
2. **Continue using the polyfill** - If you want to keep using the polyfill, you can! One less thing to worry about during this upgrade.
- Install `webextension-polyfill` and WXT's [new polyfill module](https://www.npmjs.com/package/@wxt-dev/webextension-polyfill):
- Replace any manual imports from `wxt/browser/chrome` with `wxt/browser`
2. **Continue using the polyfill**
- Install the polyfill and WXT's [new polyfill module](https://www.npmjs.com/package/@wxt-dev/webextension-polyfill):
```sh
pnpm i webextension-polyfill @wxt-dev/webextension-polyfill
```
- Add the WXT module to your config:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
modules: ['@wxt-dev/webextension-polyfill'],
});
```
The new `browser` object (and types) is backed by WXT's new package: [`@wxt-dev/browser`](https://www.npmjs.com/package/@wxt-dev/browser). This package continues WXT's mission of providing useful packages for the whole community. Just like [`@wxt-dev/storage`](https://www.npmjs.com/package/@wxt-dev/storage), [`@wxt-dev/i18n`](https://www.npmjs.com/package/@wxt-dev/i18n), [`@wxt-dev/analytics`](https://www.npmjs.com/package/@wxt-dev/analytics), it is designed to be easy to use in any web extension project, not just those using WXT, and provides a consistent API across all browsers and manifest versions.
Regardless of your choice, the `extensionApi` config has been removed. Remove it from your `wxt.config.ts` file if present:
### `extensionApi` Config Removed
The `extensionApi` config has been removed. Before, this config provided a way to opt into using the new `browser` object prior to v0.20.0.
Remove it from your `wxt.config.ts` file if present:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
extensionApi: 'chrome', // [!code --]
});
```
### Extension API Type Changes
With the new `browser` introduced in v0.20, how you access types has changed. WXT now provides types based on `@types/chrome` instead of `@types/webextension-polyfill`.
These types are more up-to-date with MV3 APIs, contain less bugs, are better organized, and don't have any auto-generated names.
To access types, use the new `Browser` namespace from `wxt/browser`:
Additionally, extension API types have changed. `wxt/browser` now uses types from `@types/chrome` instead of `@types/webextension-polyfill`. You will have to migrate any type imports to use `@types/chrome`'s namespace approach:
<!-- prettier-ignore -->
```ts
import type { Runtime } from 'wxt/browser'; // [!code --]
import type { Browser } from 'wxt/browser'; // [!code ++]
import { browser } from 'wxt/browser'; // [!code ++]
function getMessageSenderUrl(sender: Runtime.MessageSender): string { // [!code --]
function getMessageSenderUrl(sender: Browser.runtime.MessageSender): string { // [!code ++]
function getMessageSenderUrl(sender: browser.runtime.MessageSender): string { // [!code ++]
// ...
}
```
> If you use auto-imports, `Browser` will be available without manually importing it.
Not all type names will be the same as what `@types/webextension-polyfill` provides. You'll have to find the new type names by looking at the types of the `browser.*` API's you use.
`@types/chrome` are more up-to-date, contain less bugs, and don't have any auto-generated names. So even if you continue to use the polyfill, you will need to update your types to use these types.
### `public/` and `modules/` Directories Moved
The default location for the `public/` and `modules/` directories have changed to better align with standards set by other frameworks (Nuxt, Next, Astro, etc). Now, each path is relative to the project's **root directory**, not the src directory.
The default location for the `public/` and `modules/` directories have changed to better align with standards set by other frameworks (Nuxt, Next, Astro, etc). Now, each path is relative to the project's root directory.
- If you follow the default folder structure, you don't need to make any changes.
- If you set a custom `srcDir`, you have two options:
1. Move the your `public/` and `modules/` directories to the project root:
<!-- prettier-ignore -->
```html
📂 {rootDir}/
📁 modules/ <!-- [!code ++] -->
📁 public/ <!-- [!code ++] -->
📂 src/
📁 components/
📁 entrypoints/
📁 modules/ <!-- [!code --] -->
📁 public/ <!-- [!code --] -->
📁 utils/
📄 app.config.ts
📄 wxt.config.ts
1. Keep the folders in the same place and update your project config:
```ts
// wxt.config.ts
export default defineConfig({
srcDir: 'src',
publicDir: 'src/public', // [!code ++]
modulesDir: 'src/modules', // [!code ++]
});
```
2. Move the your `public/` and `modules/` directories to the project root:
```diff
<root>/
+ modules/
+ public/
src/
components/
entrypoints/
- modules/
- public/
utils/
wxt.config.ts
```
2. Keep the folders in the same place and update your project config:
### Import Path Changes
```ts [wxt.config.ts]
export default defineConfig({
srcDir: 'src',
publicDir: 'src/public', // [!code ++]
modulesDir: 'src/modules', // [!code ++]
});
```
The APIs exported by `wxt/sandbox`, `wxt/client`, or `wxt/storage` have moved to `wxt/utils/*`.
### Import Path Changes and `#imports`
The APIs exported by `wxt/sandbox`, `wxt/client`, or `wxt/storage` have moved to individual exports under the `wxt/utils/*` path.
:::details Why?
As WXT grows and more utilities are added, any helper with side-effects will not be tree-shaken out of your final bundle.
This can cause problems because not every API used by these side-effects is available in every type of entrypoint. Some APIs can only be used in the background, sandboxed pages can't use any extension API, etc. This was leading to JS throwing errors in the top-level scope, preventing your code from running.
Splitting each util into it's own module solves this problem, making sure you're only importing APIs and side-effects into entrypoints they can run in.
:::
Refer to the updated [API Reference](/api/reference/) to see the list of new import paths.
However, you don't need to memorize or learn the new import paths! v0.20 introduces a new virtual module, `#imports`, that abstracts all this away from developers. See the [blog post](/blog/2024-12-06-using-imports-module) for more details about how this module works.
So to upgrade, just replace any imports from `wxt/storage`, `wxt/client`, and `wxt/sandbox` with an import to the new `#imports` module:
To upgrade, replace these imports with the new `#imports` module:
```ts
import { storage } from 'wxt/storage'; // [!code --]
@@ -182,44 +115,33 @@ import { ContentScriptContext, useAppConfig } from '#imports'; // [!code ++]
You can combine the imports into a single import statement, but it's easier to just find/replace each statement.
```ts
import { storage } from 'wxt/storage'; // [!code --]
import { defineContentScript } from 'wxt/sandbox'; // [!code --]
import { ContentScriptContext, useAppConfig } from 'wxt/client'; // [!code --]
import {
// [!code ++]
storage, // [!code ++]
defineContentScript, // [!code ++]
ContentScriptContext, // [!code ++]
useAppConfig, // [!code ++]
} from '#imports'; // [!code ++]
```
:::tip
Before types will work, you'll need to run `wxt prepare` after installing v0.20 to generate the new TypeScript declarations.
:::
Read more about the new `#imports` module in the [blog post](/blog/2024-12-06-using-imports-module).
### `createShadowRootUi` CSS Changes
WXT now resets styles inherited from the webpage (`visibility`, `color`, `font-size`, etc.) by setting `all: initial` inside the shadow root.
:::warning
This doesn't effect `rem` units. You should continue using `postcss-rem-to-px` or an equivalent library if the webpage sets the HTML element's `font-size`.
:::
If you use `createShadowRootUi`:
1. Remove any manual CSS overrides that reset the style of specific websites. For example:
1. Double check that your UI looks the same as before.
2. If you have any manual CSS resets to override a page style, you can remove them:
<!-- prettier-ignore -->
```css [entrypoints/reddit.content/style.css]
```css
/* entrypoints/reddit.content/style.css */
body { /* [!code --] */
/* Override Reddit's default "hidden" visibility on elements */ /* [!code --] */
visibility: visible !important; /* [!code --] */
} /* [!code --] */
```
2. Double check that your UI looks the same as before.
:::warning
This doesn't effect `rem` units. You should continue using `postcss-rem-to-px` or an equivalent library if the webpage sets the HTML element's `font-size`.
:::
If you run into problems with the new behavior, you can disable it and continue using your current CSS:
@@ -232,32 +154,61 @@ const ui = await createShadowRootUi({
### Default Output Directories Changed
The default value for the [`outDirTemplate`](/api/reference/wxt/interfaces/InlineConfig#outdirtemplate) config has changed. Now, different build modes are output to different directories:
The default value for [`outDirTemplate`](/api/reference/wxt/interfaces/InlineConfig#outdirtemplate) has changed. Now, different build modes are output to different directories:
- `--mode production` &rarr; `.output/chrome-mv3`: Production builds are unchanged
- `--mode development` &rarr; `.output/chrome-mv3-dev`: Dev mode now has a `-dev` suffix so it doesn't overwrite production builds
- `--mode custom` &rarr; `.output/chrome-mv3-custom`: Other custom modes end with a `-[mode]` suffix
- `--mode production`: `.output/chrome-mv3` (unchanged)
- `--mode development`: `.output/chrome-mv3-dev` (`-dev` suffix)
- `--mode custom`: `.output/chrome-mv3-custom` (`-[mode]` suffix)
To use the old behavior, writing all output to the same directory, set the `outDirTemplate` option:
To revert and use the old behavior, writing all output to the same directory, set `outDirTemplate` option:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
outDirTemplate: '{{browser}}-mv{{manifestVersion}}', // [!code ++]
});
```
:::warning
If you've previously loaded the extension into your browser manually for development, you'll need to uninstall and re-install it from the new dev output directory.
:::
### `runner` APIs Renamed
To improve consistency with the `web-ext.config.ts` file, the "runner" APIs have been renamed. You can continue using the old names, but they have been deprecated and will be removed in a future version:
1. The `runner` option has been renamed to `webExt`:
```ts
// wxt.config.ts
export default defineConfig({
runner: { // [!code --]
webExt: { // [!code ++]
startUrls: ["https://wxt.dev"],
},
});
```
2. `defineRunnerConfig` has been renamed to `defineWebExtConfig`:
```ts
// web-ext.config.ts
import { defineRunnerConfig } from 'wxt'; // [!code --]
import { defineWebExtConfig } from 'wxt'; // [!code ++]
```
3. The `ExtensionRunnerConfig` type has been renamed to `WebExtConfig`
```ts
import type { ExtensionRunnerConfig } from 'wxt'; // [!code --]
import type { WebExtConfig } from 'wxt'; // [!code ++]
```
### Load Config as ESM
`wxt.config.ts` and `web-ext.config.ts` are now loaded as ESM modules. Previously, they were loaded as CJS.
If you're using any CJS APIs, like `__filename` or `__dirname`, replace them with their ESM counterparts, like `import.meta.filename` or `import.meta.dirname`.
### Deprecated APIs Removed
- `entrypointLoader` option: WXT now uses `vite-node` for importing entrypoints during the build process.
<!-- markdownlint-disable-next-line MD051 -->
> This was deprecated in v0.19.0, see the [v0.19 section](#v0-18-5-rarr-v0-19-0) for migration steps.
- `transformManifest` option: Use the `build:manifestGenerated` hook to transform the manifest instead:
<!-- prettier-ignore -->
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
transformManifest(manifest) { // [!code --]
hooks: { // [!code ++]
@@ -268,37 +219,6 @@ If you've previously loaded the extension into your browser manually for develop
});
```
### New Deprecations
#### `runner` APIs Renamed
To improve consistency with the `web-ext.config.ts` filename, the "runner" API and config options have been renamed. You can continue using the old names, but they have been deprecated and will be removed in a future version:
1. The `runner` option has been renamed to `webExt`:
```ts [wxt.config.ts]
export default defineConfig({
runner: { // [!code --]
webExt: { // [!code ++]
startUrls: ["https://wxt.dev"],
},
});
```
2. `defineRunnerConfig` has been renamed to `defineWebExtConfig`:
```ts [web-ext.config.ts]
import { defineRunnerConfig } from 'wxt'; // [!code --]
import { defineWebExtConfig } from 'wxt'; // [!code ++]
```
3. The `ExtensionRunnerConfig` type has been renamed to `WebExtConfig`
```ts
import type { ExtensionRunnerConfig } from 'wxt'; // [!code --]
import type { WebExtConfig } from 'wxt'; // [!code ++]
```
## v0.18.5 &rarr; v0.19.0
### `vite-node` Entrypoint Loader
@@ -306,7 +226,7 @@ To improve consistency with the `web-ext.config.ts` filename, the "runner" API a
The default entrypoint loader has changed to `vite-node`. If you use any NPM packages that depend on the `webextension-polyfill`, you need to add them to Vite's `ssr.noExternal` option:
<!-- prettier-ignore -->
```ts [wxt.config.ts]
```ts
export default defineConfig({
vite: () => ({ // [!code ++]
ssr: { // [!code ++]
@@ -322,7 +242,8 @@ export default defineConfig({
Importing variables and using them in the entrypoint options:
```ts [entrypoints/content.ts]
```ts
// entrypoints/content.ts
import { GOOGLE_MATCHES } from '~/utils/constants'
export default defineContentScript({
@@ -333,7 +254,8 @@ export default defineContentScript({
Using Vite-specific APIs like `import.meta.glob` to define entrypoint options:
```ts [entrypoints/content.ts]
```ts
// entrypoints/content.ts
const providers: Record<string, any> = import.meta.glob('../providers/*', {
eager: true,
});
@@ -354,7 +276,7 @@ Basically, you can now import and do things outside the `main` function of the e
To continue using the old approach, add the following to your `wxt.config.ts` file:
```ts [wxt.config.ts]
```ts
export default defineConfig({
entrypointLoader: 'jiti', // [!code ++]
});
@@ -371,7 +293,7 @@ WXT no longer ships with Common JS support. If you're using CJS, here's your mig
1. Add [`"type": "module"`](https://nodejs.org/api/packages.html#type) to your `package.json`.
2. Change the file extension of any `.js` files that use CJS syntax to `.cjs`, or update them to use EMS syntax.
Vite also provides steps for migrating to ESM. Check them out for more details: <https://vitejs.dev/guide/migration#deprecate-cjs-node-api>
Vite also provides steps for migrating to ESM. Check them out for more details: https://vitejs.dev/guide/migration#deprecate-cjs-node-api
## v0.18.0 &rarr; v0.18.5
@@ -386,13 +308,12 @@ If you already have `<srcDir>/modules` or `<srcDir>/Modules` directory, `wxt pre
You have two options:
1. [Recommended] Keep your files where they are and tell WXT to look in a different folder:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
modulesDir: 'wxt-modules', // defaults to "modules"
});
```
2. Rename your `modules` directory to something else.
## v0.17.0 &rarr; v0.18.0
@@ -446,7 +367,7 @@ item.watch((newValue: number, oldValue: number) => { // [!code ++]
JS entrypoints in the output directory have been moved. Unless you're doing some kind of post-build work referencing files, you don't have to make any changes.
```plaintext
```
.output/
<target>/
chunks/
@@ -461,7 +382,8 @@ JS entrypoints in the output directory have been moved. Unless you're doing some
### Renamed `zip.ignoredSources` to `zip.excludeSources`
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
zip: {
ignoredSources: [
@@ -476,7 +398,7 @@ export default defineConfig({
### Renamed Undocumented Constants
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: <https://wxt.dev/guide/multiple-browsers.html#runtime>
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: https://wxt.dev/guide/multiple-browsers.html#runtime
- `__BROWSER__` → `import.meta.env.BROWSER`
- `__COMMAND__` → `import.meta.env.COMMAND`
@@ -503,7 +425,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
### New `wxt/storage` APIs
`wxt/storage` no longer relies on [`unstorage`](https://www.npmjs.com/package/unstorage). Some `unstorage` APIs, like `prefixStorage`, have been removed, while others, like `snapshot`, are methods on the new `storage` object. Most of the standard usage remains the same. See <https://wxt.dev/guide/storage> and <https://wxt.dev/api/reference/wxt/storage/> for more details ([#300](https://github.com/wxt-dev/wxt/pull/300))
`wxt/storage` no longer relies on [`unstorage`](https://www.npmjs.com/package/unstorage). Some `unstorage` APIs, like `prefixStorage`, have been removed, while others, like `snapshot`, are methods on the new `storage` object. Most of the standard usage remains the same. See https://wxt.dev/guide/storage and https://wxt.dev/api/reference/wxt/storage/ for more details ([#300](https://github.com/wxt-dev/wxt/pull/300))
## v0.11.0 &rarr; v0.12.0
@@ -513,7 +435,6 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
- If you use auto-imports, no changes are required.
- If you have disabled auto-imports, you'll need to manually update your import statements:
```ts
import { defineBackground, defineContentScript } from 'wxt/client'; // [!code --]
import { defineBackground, defineContentScript } from 'wxt/sandbox'; // [!code ++]
-2
View File
@@ -32,8 +32,6 @@
/guide/build-targets.html /guide/multiple-browsers.html
/guide/installation.html /get-started/installation.html
/guide/introduction.html /get-started/introduction.html
/guide/upgrade-guide/wxt /guide/resources/upgrading.html
/guide/upgrade-guide/wxt.html /guide/resources/upgrading.html
# 0.19.0
/guide/go-further/entrypoint-side-effects.html /guide/go-further/entrypoint-loaders.html
-1
View File
@@ -1 +0,0 @@
<!--@include: ../packages/runner/README.md-->
+2 -1
View File
@@ -39,7 +39,8 @@ import { storage } from '@wxt-dev/storage';
To use the `@wxt-dev/storage` API, the `"storage"` permission must be added to the manifest:
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
manifest: {
permissions: ['storage'],
+19 -26
View File
@@ -4,7 +4,7 @@
"engines": {
"node": ">=18.20.3"
},
"packageManager": "pnpm@10.15.1",
"packageManager": "pnpm@9.13.0",
"scripts": {
"check": "check && pnpm -r --sequential run check",
"test": "pnpm -r --sequential run test run",
@@ -17,36 +17,29 @@
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
},
"devDependencies": {
"@aklinker1/buildc": "^1.1.5",
"@aklinker1/check": "^2.1.0",
"@commitlint/config-conventional": "^19.8.1",
"@commitlint/types": "^19.8.1",
"@aklinker1/buildc": "^1.1.4",
"@aklinker1/check": "^1.4.5",
"@types/fs-extra": "^11.0.4",
"@types/semver": "^7.7.1",
"@vitest/coverage-v8": "^3.2.4",
"changelogen": "^0.6.2",
"consola": "^3.4.2",
"fast-glob": "^3.3.3",
"feed": "^5.1.0",
"fs-extra": "^11.3.1",
"lint-staged": "^16.1.6",
"@vitest/coverage-v8": "^2.1.4",
"changelogen": "^0.5.7",
"consola": "^3.2.3",
"fast-glob": "^3.3.2",
"feed": "^4.2.2",
"fs-extra": "^11.2.0",
"lint-staged": "^15.2.10",
"markdown-it-footnote": "^4.0.0",
"markdownlint-cli": "^0.45.0",
"nano-spawn": "^1.0.2",
"prettier": "^3.6.2",
"semver": "^7.7.2",
"simple-git-hooks": "^2.13.1",
"tsx": "4.20.5",
"nano-spawn": "^0.2.0",
"prettier": "^3.3.3",
"simple-git-hooks": "^2.11.1",
"tsx": "4.15.7",
"typedoc": "^0.25.4",
"typedoc-plugin-frontmatter": "^1.3.0",
"typedoc-plugin-frontmatter": "^1.1.0",
"typedoc-plugin-markdown": "4.0.0-next.23",
"typedoc-vitepress-theme": "1.0.0-next.3",
"typescript": "^5.9.2",
"vitepress": "^1.6.4",
"vitepress-knowledge": "^0.4.1",
"vitepress-plugin-group-icons": "^1.6.3",
"vitest-mock-extended": "^3.1.0",
"vue": "^3.5.21",
"typescript": "^5.6.3",
"vitepress": "^1.5.0",
"vitest-mock-extended": "^2.0.2",
"vue": "^3.5.12",
"wxt": "workspace:*"
},
"simple-git-hooks": {
-40
View File
@@ -1,40 +0,0 @@
# Changelog
## v0.5.1
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.0...analytics-v0.5.1)
### 🚀 Enhancements
- Integrate latest measurement protocol changes ([#1767](https://github.com/wxt-dev/wxt/pull/1767))
### 🩹 Fixes
- Use `@wxt-dev/browser` instead of `@types/chrome` ([#1645](https://github.com/wxt-dev/wxt/pull/1645))
### 🏡 Chore
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
- Change browser workspace dependency to `^` ([c7335add](https://github.com/wxt-dev/wxt/commit/c7335add))
- Fix auto-fixable `markdownlint` errors ([#1710](https://github.com/wxt-dev/wxt/pull/1710))
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Tanishq-aggarwal ([@tanishq-aggarwal](https://github.com/tanishq-aggarwal))
## v0.5.0
[⚠️ breaking changes](https://wxt.dev/guide/resources/upgrading.html) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.4.1...analytics-v0.5.0)
### 🩹 Fixes
- ⚠️ Update min WXT version to 0.20 ([2e8baf0](https://github.com/wxt-dev/wxt/commit/2e8baf0))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
-277
View File
@@ -1,277 +0,0 @@
# WXT Analytics
Report analytics events from your web extension extension.
## Supported Analytics Providers
- [Google Analytics 4 (Measurement Protocol)](#google-analytics-4-measurement-protocol)
- [Umami](#umami)
## Install With WXT
1. Install the NPM package:
```bash
pnpm i @wxt-dev/analytics
```
2. In your `wxt.config.ts`, add the WXT module:
```ts
export default defineConfig({
modules: ['@wxt-dev/analytics/module'],
});
```
3. In your `<srcDir>/app.config.ts`, add a provider:
```ts
// <srcDir>/app.config.ts
import { umami } from '@wxt-dev/analytics/providers/umami';
export default defineAppConfig({
analytics: {
debug: true,
providers: [
// ...
],
},
});
```
4. Then use the `#analytics` module to report events:
```ts
import { analytics } from '#analytics';
await analytics.track('some-event');
await analytics.page();
await analytics.identify('some-user-id');
analytics.autoTrack(document.body);
```
## Install Without WXT
1. Install the NPM package:
```bash
pnpm i @wxt-dev/analytics
```
2. Create an `analytics` instance:
```ts
// utils/analytics.ts
import { createAnalytics } from '@wxt-dev/analytics';
export const analytics = createAnalytics({
providers: [
// ...
],
});
```
3. Import your analytics module in the background to initialize the message listener:
```ts
// background.ts
import './utils/analytics';
```
4. Then use your `analytics` instance to report events:
```ts
import { analytics } from './utils/analytics';
await analytics.track('some-event');
await analytics.page();
await analytics.identify('some-user-id');
analytics.autoTrack(document.body);
```
## Providers
### Google Analytics 4 (Measurement Protocol)
The [Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/ga4) is an alternative to GTag for reporting events to Google Analytics for MV3 extensions.
> [Why use the Measurement Protocol instead of GTag?](https://developer.chrome.com/docs/extensions/how-to/integrate/google-analytics-4#measurement-protocol)
Follow [Google's documentation](https://developer.chrome.com/docs/extensions/how-to/integrate/google-analytics-4#setup-credentials) to obtain your credentials and put them in your `.env` file:
```dotenv
WXT_GA_API_SECRET='...'
```
Then add the `googleAnalytics4` provider to your `<srcDir>/app.config.ts` file:
```ts
import { googleAnalytics4 } from '@wxt-dev/analytics/providers/google-analytics-4';
export default defineAppConfig({
analytics: {
providers: [
googleAnalytics4({
apiSecret: import.meta.env.WXT_GA_API_SECRET,
measurementId: '...',
}),
],
},
});
```
### Umami
[Umami](https://umami.is/) is a privacy-first, open source analytics platform.
In Umami's dashboard, create a new website. The website's name and domain can be anything. Obviously, an extension doesn't have a domain, so make one up if you don't have one.
After the website has been created, save the website ID and domain to your `.env` file:
```dotenv
WXT_UMAMI_WEBSITE_ID='...'
WXT_UMAMI_DOMAIN='...'
```
Then add the `umami` provider to your `<srcDir>/app.config.ts` file:
```ts
import { umami } from '@wxt-dev/analytics/providers/umami';
export default defineAppConfig({
analytics: {
providers: [
umami({
apiUrl: 'https://<your-umami-instance>/api',
websiteId: import.meta.env.WXT_UMAMI_WEBSITE_ID,
domain: import.meta.env.WXT_UMAMI_DOMAIN,
}),
],
},
});
```
### Custom Provider
If your analytics platform is not supported, you can provide an implementation of the `AnalyticsProvider` type in your `app.config.ts` instead:
```ts
import { defineAnalyticsProvider } from '@wxt-dev/analytics/client';
interface CustomAnalyticsOptions {
// ...
}
const customAnalytics = defineAnalyticsProvider<CustomAnalyticsOptions>(
(analytics, analyticsConfig, providerOptions) => {
// ...
},
);
export default defineAppConfig({
analytics: {
providers: [
customAnalytics({
// ...
}),
],
},
});
```
Example `AnalyticsProvider` implementations can be found at [`./modules/analytics/providers`](https://github.com/wxt-dev/wxt/tree/main/packages/analytics/modules/analytics/providers).
## User Properties
User ID and properties are stored in `browser.storage.local`. To change this or customize where these values are stored, use the `userId` and `userProperties` config:
```ts
// app.config.ts
import { storage } from 'wxt/storage';
export default defineAppConfig({
analytics: {
userId: storage.defineItem('local:custom-user-id-key'),
userProperties: storage.defineItem('local:custom-user-properties-key'),
},
});
```
To set the values at runtime, use the `identify` function:
```ts
await analytics.identify(userId, userProperties);
```
Alternatively, a common pattern is to use a random string as the user ID. This keeps the actual user information private, while still providing useful metrics in your analytics platform. This can be done very easily using WXT's storage API:
```ts
// app.config.ts
import { storage } from 'wxt/storage';
export default defineAppConfig({
analytics: {
userId: storage.defineItem('local:custom-user-id-key', {
init: () => crypto.randomUUID(),
}),
},
});
```
If you aren't using `wxt` or `@wxt-dev/storage`, you can define custom implementations for the `userId` and `userProperties` config:
```ts
const analytics = createAnalytics({
userId: {
getValue: () => ...,
setValue: (userId) => ...,
}
})
```
## Auto-track UI events
Call `analytics.autoTrack(container)` to automatically track UI events so you don't have to manually add them. Currently it:
- Tracks clicks to elements inside the `container`
In your extension's HTML pages, you'll want to call it with `document`:
```ts
analytics.autoTrack(document);
```
But in content scripts, you usually only care about interactions with your own UI:
```ts
const ui = createIntegratedUi({
// ...
onMount(container) {
analytics.autoTrack(container);
},
});
ui.mount();
```
## Enabling/Disabling
By default, **analytics is disabled**. You can configure how the value is stored (and change the default value) via the `enabled` config:
```ts
// app.config.ts
import { storage } from 'wxt/storage';
export default defineAppConfig({
analytics: {
enabled: storage.defineItem('local:analytics-enabled', {
fallback: true,
}),
},
});
```
At runtime, you can call `setEnabled` to change the value:
```ts
analytics.setEnabled(true);
```
-20
View File
@@ -1,20 +0,0 @@
import { defineAppConfig } from 'wxt/utils/define-app-config';
import { googleAnalytics4 } from './modules/analytics/providers/google-analytics-4';
import { umami } from './modules/analytics/providers/umami';
export default defineAppConfig({
analytics: {
debug: true,
providers: [
googleAnalytics4({
apiSecret: '...',
measurementId: '...',
}),
umami({
apiUrl: 'https://umami.aklinker1.io/api',
domain: 'analytics.wxt.dev',
websiteId: '8f1c2aa4-fad3-406e-a5b2-33e8d4501716',
}),
],
},
});
-21
View File
@@ -1,21 +0,0 @@
import { defineBuildConfig } from 'unbuild';
import { resolve } from 'node:path';
// Build module and plugins
export default defineBuildConfig({
rootDir: resolve(__dirname, 'modules/analytics'),
outDir: resolve(__dirname, 'dist'),
entries: [
{ input: 'index.ts', name: 'module' },
{ input: 'client.ts', name: 'index' },
'background-plugin.ts',
'types.ts',
'providers/google-analytics-4.ts',
'providers/umami.ts',
],
externals: ['#analytics'],
replace: {
'import.meta.env.NPM': 'true',
},
declaration: true,
});
@@ -1,17 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Popup</title>
</head>
<body>
<label>
<input id="enabledCheckbox" type="checkbox" />
&emsp;Analytics enabled
</label>
<button id="button1">Button 1</button>
<button class="cool-button">Button 2</button>
<script type="module" src="./main.ts"></script>
</body>
</html>
@@ -1,9 +0,0 @@
import { analytics } from '#analytics';
declare const enabledCheckbox: HTMLInputElement;
analytics.autoTrack(document);
enabledCheckbox.oninput = () => {
void analytics.setEnabled(enabledCheckbox.checked);
};
@@ -1,3 +0,0 @@
import '#analytics';
export default () => {};
@@ -1,289 +0,0 @@
import { UAParser } from 'ua-parser-js';
import type {
Analytics,
AnalyticsConfig,
AnalyticsPageViewEvent,
AnalyticsStorageItem,
AnalyticsTrackEvent,
BaseAnalyticsEvent,
AnalyticsEventMetadata,
AnalyticsProvider,
} from './types';
import { browser } from '@wxt-dev/browser';
const ANALYTICS_PORT = '@wxt-dev/analytics';
export function createAnalytics(config?: AnalyticsConfig): Analytics {
if (!browser?.runtime?.id)
throw Error(
'Cannot use WXT analytics in contexts without access to the browser.runtime APIs',
);
if (config == null) {
console.warn(
"[@wxt-dev/analytics] Config not provided to createAnalytics. If you're using WXT, add the 'analytics' property to '<srcDir>/app.config.ts'.",
);
}
// 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.
*/
function createBackgroundAnalytics(
config: AnalyticsConfig | undefined,
): Analytics {
// User properties storage
const userIdStorage =
config?.userId ?? defineStorageItem<string>('wxt-analytics:user-id');
const userPropertiesStorage =
config?.userProperties ??
defineStorageItem<Record<string, string>>(
'wxt-analytics:user-properties',
{},
);
const enabled =
config?.enabled ??
defineStorageItem<boolean>('local:wxt-analytics:enabled', false);
// Cached values
const platformInfo = browser.runtime.getPlatformInfo();
const userAgent = UAParser();
let userId = Promise.resolve(userIdStorage.getValue()).then(
(id) => id ?? globalThis.crypto.randomUUID(),
);
let userProperties = userPropertiesStorage.getValue();
const manifest = browser.runtime.getManifest();
const getBackgroundMeta = () => ({
timestamp: Date.now(),
// Don't track sessions for the background, it can be running
// indefinitely, and will inflate session duration stats.
sessionId: undefined,
language: navigator.language,
referrer: undefined,
screen: undefined,
url: location.href,
title: undefined,
});
const getBaseEvent = async (
meta: AnalyticsEventMetadata,
): Promise<BaseAnalyticsEvent> => {
const platform = await platformInfo;
return {
meta,
user: {
id: await userId,
properties: {
version: config?.version ?? manifest.version_name ?? manifest.version,
wxtMode: import.meta.env.MODE,
wxtBrowser: import.meta.env.BROWSER,
arch: platform.arch,
os: platform.os,
browser: userAgent.browser.name,
browserVersion: userAgent.browser.version,
...(await userProperties),
},
},
};
};
const analytics = {
identify: async (
newUserId: string,
newUserProperties: Record<string, string> = {},
meta: AnalyticsEventMetadata = getBackgroundMeta(),
) => {
// Update in-memory cache for all providers
userId = Promise.resolve(newUserId);
userProperties = Promise.resolve(newUserProperties);
// Persist user info to storage
await Promise.all([
userIdStorage.setValue?.(newUserId),
userPropertiesStorage.setValue?.(newUserProperties),
]);
// Notify providers
const event = await getBaseEvent(meta);
if (config?.debug) console.debug('[@wxt-dev/analytics] identify', event);
if (await enabled.getValue()) {
await Promise.allSettled(
providers.map((provider) => provider.identify(event)),
);
} else if (config?.debug) {
console.debug(
'[@wxt-dev/analytics] Analytics disabled, identify() not uploaded',
);
}
},
page: async (
location: string,
meta: AnalyticsEventMetadata = getBackgroundMeta(),
) => {
const baseEvent = await getBaseEvent(meta);
const event: AnalyticsPageViewEvent = {
...baseEvent,
page: {
url: meta?.url ?? globalThis.location?.href,
location,
title: meta?.title ?? globalThis.document?.title,
},
};
if (config?.debug) console.debug('[@wxt-dev/analytics] page', event);
if (await enabled.getValue()) {
await Promise.allSettled(
providers.map((provider) => provider.page(event)),
);
} else if (config?.debug) {
console.debug(
'[@wxt-dev/analytics] Analytics disabled, page() not uploaded',
);
}
},
track: async (
eventName: string,
eventProperties?: Record<string, string>,
meta: AnalyticsEventMetadata = getBackgroundMeta(),
) => {
const baseEvent = await getBaseEvent(meta);
const event: AnalyticsTrackEvent = {
...baseEvent,
event: { name: eventName, properties: eventProperties },
};
if (config?.debug) console.debug('[@wxt-dev/analytics] track', event);
if (await enabled.getValue()) {
await Promise.allSettled(
providers.map((provider) => provider.track(event)),
);
} else if (config?.debug) {
console.debug(
'[@wxt-dev/analytics] Analytics disabled, track() not uploaded',
);
}
},
setEnabled: async (newEnabled) => {
await enabled.setValue?.(newEnabled);
},
autoTrack: () => {
// Noop, background doesn't have a UI
return () => {};
},
} satisfies Analytics;
const providers =
config?.providers?.map((provider) => provider(analytics, config)) ?? [];
// Listen for messages from the rest of the extension
browser.runtime.onConnect.addListener((port) => {
if (port.name === ANALYTICS_PORT) {
port.onMessage.addListener(({ fn, args }) => {
// @ts-expect-error: Untyped fn key
void analytics[fn]?.(...args);
});
}
});
return analytics;
}
/**
* Creates an analytics client for non-background contexts.
*/
function createFrontendAnalytics(): Analytics {
const port = browser.runtime.connect({ name: ANALYTICS_PORT });
const sessionId = Date.now();
const getFrontendMetadata = (): AnalyticsEventMetadata => ({
sessionId,
timestamp: Date.now(),
language: navigator.language,
referrer: globalThis.document?.referrer || undefined,
screen: globalThis.window
? `${globalThis.window.screen.width}x${globalThis.window.screen.height}`
: undefined,
url: location.href,
title: document.title || undefined,
});
const methodForwarder =
(fn: string) =>
(...args: any[]) => {
port.postMessage({ fn, args: [...args, getFrontendMetadata()] });
};
const analytics: Analytics = {
identify: methodForwarder('identify'),
page: methodForwarder('page'),
track: methodForwarder('track'),
setEnabled: methodForwarder('setEnabled'),
autoTrack: (root) => {
const onClick = (event: Event) => {
const element = event.target as any;
if (
!element ||
(!INTERACTIVE_TAGS.has(element.tagName) &&
!INTERACTIVE_ROLES.has(element.getAttribute('role')))
)
return;
void analytics.track('click', {
tagName: element.tagName?.toLowerCase(),
id: element.id || undefined,
className: element.className || undefined,
textContent: element.textContent?.substring(0, 50) || undefined, // Limit text content length
href: element.href,
});
};
root.addEventListener('click', onClick, { capture: true, passive: true });
return () => {
root.removeEventListener('click', onClick);
};
},
};
return analytics;
}
function defineStorageItem<T>(
key: string,
defaultValue?: NonNullable<T>,
): AnalyticsStorageItem<T> {
return {
getValue: async () =>
(await browser.storage.local.get(key))[key] ?? defaultValue,
setValue: (newValue) => browser.storage.local.set({ [key]: newValue }),
};
}
const INTERACTIVE_TAGS = new Set([
'A',
'BUTTON',
'INPUT',
'SELECT',
'TEXTAREA',
]);
const INTERACTIVE_ROLES = new Set([
'button',
'link',
'checkbox',
'menuitem',
'tab',
'radio',
]);
export function defineAnalyticsProvider<T = never>(
definition: (
/** The analytics object. */
analytics: Analytics,
/** Config passed into the analytics module from `app.config.ts`. */
config: AnalyticsConfig,
/** Provider options */
options: T,
) => ReturnType<AnalyticsProvider>,
): (options: T) => AnalyticsProvider {
return (options) => (analytics, config) =>
definition(analytics, config, options);
}
@@ -1,91 +0,0 @@
import 'wxt';
import 'wxt/utils/define-app-config';
import {
addAlias,
addViteConfig,
addWxtPlugin,
defineWxtModule,
} from 'wxt/modules';
import { relative, resolve } from 'node:path';
import type { AnalyticsConfig } from './types';
declare module 'wxt/utils/define-app-config' {
export interface WxtAppConfig {
analytics: AnalyticsConfig;
}
}
export default defineWxtModule({
name: 'analytics',
imports: [{ name: 'analytics', from: '#analytics' }],
setup(wxt) {
// Paths
const wxtAnalyticsFolder = resolve(wxt.config.wxtDir, 'analytics');
const wxtAnalyticsIndex = resolve(wxtAnalyticsFolder, 'index.ts');
const clientModuleId = import.meta.env.NPM
? '@wxt-dev/analytics'
: resolve(wxt.config.modulesDir, 'analytics/client');
const pluginModuleId = import.meta.env.NPM
? '@wxt-dev/analytics/background-plugin'
: resolve(wxt.config.modulesDir, 'analytics/background-plugin');
// Add required permissions
wxt.hook('build:manifestGenerated', (_, manifest) => {
manifest.permissions ??= [];
if (!manifest.permissions.includes('storage')) {
manifest.permissions.push('storage');
}
});
// Generate #analytics module
const wxtAnalyticsCode = [
`import { createAnalytics } from '${
import.meta.env.NPM
? clientModuleId
: relative(wxtAnalyticsFolder, clientModuleId)
}';`,
`import { useAppConfig } from '#imports';`,
``,
`export const analytics = createAnalytics(useAppConfig().analytics);`,
``,
].join('\n');
addAlias(wxt, '#analytics', wxtAnalyticsIndex);
wxt.hook('prepare:types', async (_, entries) => {
entries.push({
path: wxtAnalyticsIndex,
text: wxtAnalyticsCode,
});
});
// Ensure there is a background entrypoint
wxt.hook('entrypoints:resolved', (_, entrypoints) => {
const hasBackground = entrypoints.find(
(entry) => entry.type === 'background',
);
if (!hasBackground) {
entrypoints.push({
type: 'background',
inputPath: 'virtual:user-background',
name: 'background',
options: {},
outputDir: wxt.config.outDir,
skipped: false,
});
}
});
// Ensure analytics is initialized in every context, mainly the background.
// TODO: Once there's a way to filter which entrypoints a plugin is applied to, only apply this to the background
addWxtPlugin(wxt, pluginModuleId);
// Fix issues with dependencies
addViteConfig(wxt, () => ({
optimizeDeps: {
// Ensure the "#analytics" import is processed by vite in the background plugin
exclude: ['@wxt-dev/analytics'],
// Ensure the CJS subdependency is preprocessed into ESM
include: ['@wxt-dev/analytics > ua-parser-js'],
},
}));
},
});
@@ -1,74 +0,0 @@
import { defineAnalyticsProvider } from '../client';
import type { BaseAnalyticsEvent } from '../types';
const DEFAULT_ENGAGEMENT_TIME_IN_MSEC = 100;
export interface GoogleAnalytics4ProviderOptions {
apiSecret: string;
measurementId: string;
}
export const googleAnalytics4 =
defineAnalyticsProvider<GoogleAnalytics4ProviderOptions>(
(_, config, options) => {
const send = async (
data: BaseAnalyticsEvent,
eventName: string,
eventProperties: Record<string, string | undefined> | undefined,
): Promise<void> => {
const url = new URL(
config?.debug ? '/debug/mp/collect' : '/mp/collect',
'https://www.google-analytics.com',
);
if (options.apiSecret)
url.searchParams.set('api_secret', options.apiSecret);
if (options.measurementId)
url.searchParams.set('measurement_id', options.measurementId);
const userProperties = {
language: data.meta.language,
screen: data.meta.screen,
...data.user.properties,
};
const mappedUserProperties = Object.fromEntries(
Object.entries(userProperties).map(([name, value]) => [
name,
value == null ? undefined : { value },
]),
);
await fetch(url.href, {
method: 'POST',
body: JSON.stringify({
client_id: data.user.id,
consent: {
ad_user_data: 'DENIED',
ad_personalization: 'DENIED',
},
user_properties: mappedUserProperties,
user_agent: navigator.userAgent,
events: [
{
name: eventName,
params: {
session_id: data.meta.sessionId,
engagement_time_msec: DEFAULT_ENGAGEMENT_TIME_IN_MSEC,
...eventProperties,
},
},
],
}),
});
};
return {
identify: () => Promise.resolve(), // No-op, user data uploaded in page/track
page: (event) =>
send(event, 'page_view', {
page_title: event.page.title,
page_location: event.page.location,
}),
track: (event) => send(event, event.event.name, event.event.properties),
};
},
);
@@ -1,70 +0,0 @@
import { defineAnalyticsProvider } from '../client';
export interface UmamiProviderOptions {
apiUrl: string;
websiteId: string;
domain: string;
}
export const umami = defineAnalyticsProvider<UmamiProviderOptions>(
(_, config, options) => {
const send = (payload: UmamiPayload) => {
if (config.debug) {
console.debug('[@wxt-dev/analytics] Sending event to Umami:', payload);
}
return fetch(`${options.apiUrl}/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ type: 'event', payload }),
});
};
return {
identify: () => Promise.resolve(), // No-op, user data uploaded in page/track
page: async (event) => {
await send({
name: 'page_view',
website: options.websiteId,
url: event.page.url,
hostname: options.domain,
language: event.meta.language ?? '',
referrer: event.meta.referrer ?? '',
screen: event.meta.screen ?? '',
title: event.page.title ?? '<blank>',
data: event.user.properties,
});
},
track: async (event) => {
await send({
name: event.event.name,
website: options.websiteId,
url: event.meta.url ?? '/',
title: '<blank>',
hostname: options.domain,
language: event.meta.language ?? '',
referrer: event.meta.referrer ?? '',
screen: event.meta.screen ?? '',
data: {
...event.event.properties,
...event.user.properties,
},
});
},
};
},
);
/** @see https://umami.is/docs/api/sending-stats#post-/api/send */
interface UmamiPayload {
hostname?: string;
language?: string;
referrer?: string;
screen?: string;
title?: string;
url?: string;
website: string;
name: string;
data?: Record<string, string | undefined>;
}
@@ -1,99 +0,0 @@
export interface Analytics {
/** Report a page change. */
page: (url: string) => void;
/** Report a custom event. */
track: (eventName: string, eventProperties?: Record<string, string>) => 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. */
autoTrack: (root: Document | ShadowRoot | Element) => () => void;
/** Calls `config.enabled.setValue`. */
setEnabled: (enabled: boolean) => void;
}
export interface AnalyticsConfig {
/**
* Array of providers to send analytics to.
*/
providers: AnalyticsProvider[];
/**
* 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`.
*/
enabled?: AnalyticsStorageItem<boolean>;
/**
* Configure how the user Id is persisted. Defaults to using `browser.storage.local`.
*/
userId?: AnalyticsStorageItem<string>;
/**
* Configure how user properties are persisted. Defaults to using `browser.storage.local`.
*/
userProperties?: AnalyticsStorageItem<Record<string, string>>;
}
export interface AnalyticsStorageItem<T> {
getValue: () => T | Promise<T>;
setValue?: (newValue: T) => void | Promise<void>;
}
export type AnalyticsProvider = (
analytics: Analytics,
config: AnalyticsConfig,
) => {
/** Upload a page view event. */
page: (event: AnalyticsPageViewEvent) => Promise<void>;
/** Upload a custom event. */
track: (event: AnalyticsTrackEvent) => Promise<void>;
/** Upload information about the user. */
identify: (event: BaseAnalyticsEvent) => Promise<void>;
};
export interface BaseAnalyticsEvent {
meta: AnalyticsEventMetadata;
user: {
id: string;
properties: Record<string, string | undefined>;
};
}
export interface AnalyticsEventMetadata {
/** Identifier of the session the event was fired from. */
sessionId: number | undefined;
/** `Date.now()` of when the event was reported. */
timestamp: number;
/** Ex: `"1920x1080"`. */
screen: string | undefined;
/** `document.referrer` */
referrer: string | undefined;
/** `navigator.language` */
language: string | undefined;
/** `location.href` */
url: string | undefined;
/** `document.title` */
title: string | undefined;
}
export interface AnalyticsPageInfo {
url: string;
title: string | undefined;
location: string | undefined;
}
export interface AnalyticsPageViewEvent extends BaseAnalyticsEvent {
page: AnalyticsPageInfo;
}
export interface AnalyticsTrackEvent extends BaseAnalyticsEvent {
event: {
name: string;
properties?: Record<string, string>;
};
}
-65
View File
@@ -1,65 +0,0 @@
{
"name": "@wxt-dev/analytics",
"version": "0.5.1",
"description": "Add analytics to your web extension",
"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",
"default": "./dist/index.mjs"
},
"./module": {
"types": "./dist/module.d.mts",
"default": "./dist/module.mjs"
},
"./background-plugin": {
"types": "./dist/background-plugin.d.mts",
"default": "./dist/background-plugin.mjs"
},
"./types": {
"types": "./dist/types.d.mts"
},
"./providers/google-analytics-4": {
"types": "./dist/providers/google-analytics-4.d.mts",
"default": "./dist/providers/google-analytics-4.mjs"
},
"./providers/umami": {
"types": "./dist/providers/umami.d.mts",
"default": "./dist/providers/umami.mjs"
}
},
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"dev": "buildc --deps-only -- wxt",
"dev:build": "buildc --deps-only -- wxt build",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"prepack": "pnpm -s build",
"prepare": "buildc --deps-only -- wxt prepare"
},
"peerDependencies": {
"wxt": ">=0.20.0"
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"@types/ua-parser-js": "^0.7.39",
"publint": "^0.3.12",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"wxt": "workspace:*"
},
"dependencies": {
"@wxt-dev/browser": "workspace:^",
"ua-parser-js": "^1.0.40"
}
}
View File
-9
View File
@@ -1,9 +0,0 @@
{
"extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"],
"compilerOptions": {
"paths": {
"#analytics": ["./.wxt/analytics/index.ts"]
}
},
"exclude": ["node_modules", "dist"]
}
-11
View File
@@ -1,11 +0,0 @@
import { defineConfig } from 'wxt';
export default defineConfig({
// Unimport doesn't look for imports in node_modules, so when developing a
// WXT module, we need to disable this to simplify the build process
imports: false,
manifest: {
name: 'Analytics Demo',
},
});
-32
View File
@@ -1,37 +1,5 @@
# Changelog
## v1.1.0
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.2...auto-icons-v1.1.0)
### 🚀 Enhancements
- Add overlay option for dev icons ([#1825](https://github.com/wxt-dev/wxt/pull/1825))
### 📖 Documentation
- Rewrite and restructure the documentation website ([#933](https://github.com/wxt-dev/wxt/pull/933))
- Use full URLs in README so they work on the docs site ([d20793d5](https://github.com/wxt-dev/wxt/commit/d20793d5))
- Add SVG compatibility note ([#1830](https://github.com/wxt-dev/wxt/pull/1830))
### 🏡 Chore
- Add `oxlint` for linting ([#947](https://github.com/wxt-dev/wxt/pull/947))
- **deps:** Bump sharp from 0.33.4 to 0.33.5 ([#959](https://github.com/wxt-dev/wxt/pull/959))
- Upgrade all non-major dependencies ([#1040](https://github.com/wxt-dev/wxt/pull/1040))
- **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164))
- **deps:** Bump dev and non-breaking major dependencies ([#1167](https://github.com/wxt-dev/wxt/pull/1167))
- Use PNPM 10's new catelog feature ([#1493](https://github.com/wxt-dev/wxt/pull/1493))
- Move production dependencies to PNPM 10 catelog ([#1494](https://github.com/wxt-dev/wxt/pull/1494))
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
### ❤️ Contributors
- Typed SIGTERM ([@typed-sigterm](https://github.com/typed-sigterm))
- Kuba ([@zizzfizzix](https://github.com/zizzfizzix))
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v1.0.2
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.1...auto-icons-v1.0.2)
+1 -2
View File
@@ -5,8 +5,7 @@
## Features
- Generate extension icons with the correct sizes
- Make the icon greyscale or include a visible overlay during development
- SVG is supported
- Make the icon greyscale during development
## Usage
+11 -11
View File
@@ -24,7 +24,7 @@
}
],
"license": "MIT",
"version": "1.1.0",
"version": "1.0.2",
"type": "module",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
@@ -44,17 +44,17 @@
"peerDependencies": {
"wxt": ">=0.19.0"
},
"devDependencies": {
"@aklinker1/check": "^1.4.5",
"oxlint": "^0.11.1",
"publint": "^0.2.12",
"typescript": "^5.6.3",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
},
"dependencies": {
"defu": "^6.1.4",
"fs-extra": "^11.3.1",
"sharp": "^0.34.3"
},
"devDependencies": {
"@aklinker1/check": "^2.1.0",
"oxlint": "^1.14.0",
"publint": "^0.3.12",
"typescript": "^5.9.2",
"unbuild": "^3.6.1",
"wxt": "workspace:*"
"fs-extra": "^11.2.0",
"sharp": "^0.33.5"
}
}
+12 -59
View File
@@ -14,24 +14,11 @@ export default defineWxtModule<AutoIconsOptions>({
{
enabled: true,
baseIconPath: resolve(wxt.config.srcDir, 'assets/icon.png'),
developmentIndicator: 'grayscale',
grayscaleOnDevelopment: true,
sizes: [128, 48, 32, 16],
},
);
// Backward compatibility for the deprecated option
if (options?.grayscaleOnDevelopment !== undefined) {
wxt.logger.warn(
'`[auto-icons]` "grayscaleOnDevelopment" is deprecated. Use "developmentIndicator" instead.',
);
if (options?.developmentIndicator === undefined) {
parsedOptions.developmentIndicator = options!.grayscaleOnDevelopment
? 'grayscale'
: false;
}
}
const resolvedPath = resolve(wxt.config.srcDir, parsedOptions.baseIconPath);
if (!parsedOptions.enabled)
@@ -55,44 +42,21 @@ export default defineWxtModule<AutoIconsOptions>({
});
wxt.hooks.hook('build:done', async (wxt, output) => {
const image = sharp(resolvedPath).png();
if (
wxt.config.mode === 'development' &&
parsedOptions.grayscaleOnDevelopment
) {
image.grayscale();
}
const outputFolder = wxt.config.outDir;
for (const size of parsedOptions.sizes) {
const resizedImage = sharp(resolvedPath).resize(size).png();
if (wxt.config.mode === 'development') {
if (parsedOptions.developmentIndicator === 'grayscale') {
resizedImage.grayscale();
} else if (parsedOptions.developmentIndicator === 'overlay') {
// Helper to build an overlay that places a yellow rectangle at the bottom
// of the icon with the text "DEV" in black. The overlay has the same
// dimensions as the icon so we can composite it with default gravity.
const buildDevOverlay = (size: number) => {
const rectHeight = Math.round(size * 0.5);
const fontSize = Math.round(size * 0.35);
return Buffer.from(`<?xml version="1.0" encoding="UTF-8"?>
<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="${size - rectHeight}" width="${size}" height="${rectHeight}" fill="#ffff00" />
<text x="${size / 2}" y="${size - rectHeight / 2}" font-family="Arial, Helvetica, sans-serif" font-size="${fontSize}" font-weight="bold" fill="black" text-anchor="middle" dominant-baseline="middle">DEV</text>
</svg>`);
};
const overlayBuffer = await sharp(buildDevOverlay(size))
.png()
.toBuffer();
resizedImage.composite([
{
input: overlayBuffer,
left: 0,
top: 0,
},
]);
}
}
const resized = image.resize(size);
ensureDir(resolve(outputFolder, 'icons'));
await resizedImage.toFile(resolve(outputFolder, `icons/${size}.png`));
await resized.toFile(resolve(outputFolder, `icons/${size}.png`));
output.publicAssets.push({
type: 'asset',
@@ -125,20 +89,9 @@ export interface AutoIconsOptions {
* @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.
*
* @default "grayscale"
*/
developmentIndicator?: 'grayscale' | 'overlay' | false;
/**
* Grayscale the image when in development mode to indicate development
* @default true
* @deprecated Use `developmentIndicator` instead
*/
grayscaleOnDevelopment?: boolean;
/**
-58
View File
@@ -1,58 +0,0 @@
# `@wxt-dev/browser`
Provides access to the `browser` or `chrome` extension APIs and related types.
```ts
import { browser, Browser } from '@wxt-dev/browser';
// Or if you're using WXT:
// import { browser, Browser } from 'wxt/browser';
console.log(browser.runtime.id);
const onMessage = (message: any, sender: Browser.runtime.MessageSender) => {
console.log(message);
};
browser.runtime.onMessage.addListener(onMessage);
```
## Installation
If you're using WXT, this package is already installed, you don't need to install it manually.
Otherwise, you can install the package from NPM:
```sh
pnpm install @wxt-dev/browser
```
## Upgrading to Latest Types
Just run:
```sh
pnpm upgrade @wxt-dev/browser
```
This should update both the manually installed version and the subdependency inside WXT.
## Contributing
### Code Generation
Types are generated based on the `@types/chrome` package, and with modifications specifically for use with WXT.
### Updating `@types/chrome` Version
You don't need to do anything! [A github action](https://github.com/wxt-dev/wxt/actions/workflows/update-browser-package.yml) is ran every day to generate and publish this package using the latest `@types/chrome` version.
You can manually generate types via:
```sh
pnpm gen
```
### Why not just use `@types/chrome`?
With WXT, you must import the `browser` variable to use the extension APIs. The way `@types/chrome` is implemented forces you to define a global `chrome` variable. With WXT, this isn't acceptable, we don't want to pollute the global (type) scope or introduce conflicts with auto-imports.
Additionally, WXT overrides types to provide additional type safety for some APIs, like `browser.runtime.getURL` and `browser.i18n.getMessage`. With `@types/chrome`'s nested namespace approach, it's not possible to override the types for those functions.
-38
View File
@@ -1,38 +0,0 @@
{
"name": "@wxt-dev/browser",
"description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome",
"version": "0.1.4",
"type": "module",
"main": "src/index.mjs",
"types": "src/index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/browser"
},
"scripts": {
"check": "check",
"gen": "tsx scripts/generate.ts"
},
"author": {
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
},
"license": "MIT",
"files": [
"src"
],
"devDependencies": {
"@types/chrome": "0.1.6",
"fs-extra": "^11.3.1",
"nano-spawn": "^1.0.2",
"tsx": "4.20.5",
"typescript": "^5.9.2",
"vitest": "^3.2.4"
},
"dependencies": {
"@types/filesystem": "*",
"@types/har-format": "*"
},
"peerDependencies": {}
}
-82
View File
@@ -1,82 +0,0 @@
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';
// Fetch latest version
console.log('Getting latest version of \x1b[36m@types/chrome\x1b[0m');
await spawn('pnpm', ['i', '--ignore-scripts', '-D', '@types/chrome@latest']);
// Generate new 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 fs.readJson(pkgJsonPath);
const pkgJsonTemplate = await fs.readFile('templates/package.json', 'utf8');
const newPkgJson = JSON.parse(
pkgJsonTemplate.replaceAll('{{chromeTypesVersion}}', pkgJson.version),
);
newPkgJson.dependencies = pkgJson.dependencies;
newPkgJson.peerDependencies = pkgJson.peerDependencies;
newPkgJson.peerDependenciesMeta = pkgJson.peerDependenciesMeta;
const outPkgJsonPath = resolve('package.json');
await fs.writeJson(outPkgJsonPath, newPkgJson);
await spawn('pnpm', ['-w', 'prettier', '--write', outPkgJsonPath]);
// Generate declaration files
console.log('Generating declaration files');
const outDir = resolve('src/gen');
const declarationFileMapping = (
await fs.readdir(pkgDir, {
recursive: true,
encoding: 'utf8',
})
)
// Filter to .d.ts files
.filter((file) => file.endsWith('.d.ts'))
// Map to usable paths
.map((file) => ({
file: file.replaceAll(sep, posixSep),
srcPath: join(pkgDir, file),
destPath: join(outDir, file),
}));
for (const { file, srcPath, destPath } of declarationFileMapping) {
const content = await fs.readFile(srcPath, 'utf8');
const transformedContent = transformFile(file, content);
const destDir = dirname(destPath);
await fs.mkdir(destDir, { recursive: true });
await fs.writeFile(destPath, transformedContent);
console.log(` \x1b[2m-\x1b[0m \x1b[36m${file}\x1b[0m`);
}
// Done!
console.log(
'\x1b[32m✔\x1b[0m Done in ' + performance.now().toFixed(0) + ' ms',
);
// Transformations
function transformFile(file: string, content: string): string {
return (
// Add prefix
`/* DO NOT EDIT - generated by scripts/generate.ts */\n\n${content}\n`
// Remove global type declaration
.replaceAll('chrome: typeof chrome;', '// chrome: typeof chrome;')
// Rename `chrome` namespace to `Browser` and export it
.replaceAll('declare namespace chrome', 'export namespace Browser')
// Update references to `chrome` namespace to `Browser`
.replaceAll('chrome.', 'Browser.')
// Fix links to developer.chrome.com
.replaceAll('developer.Browser.com', 'developer.chrome.com')
);
}
@@ -1,23 +0,0 @@
/// <reference types="chrome" />
import { describe, expectTypeOf, it } from 'vitest';
import { browser, type Browser } from '../index';
describe('browser', () => {
describe('types', () => {
it('should provide types via the Browser import', () => {
expectTypeOf<Browser.runtime.MessageSender>().toMatchTypeOf<chrome.runtime.MessageSender>();
expectTypeOf<Browser.storage.AreaName>().toMatchTypeOf<chrome.storage.AreaName>();
expectTypeOf<Browser.i18n.LanguageDetectionResult>().toMatchTypeOf<chrome.i18n.LanguageDetectionResult>();
});
it('should provide values via the browser import', () => {
expectTypeOf(browser.runtime.id).toMatchTypeOf<string>();
expectTypeOf(
browser.storage.local,
).toMatchTypeOf<Browser.storage.StorageArea>();
expectTypeOf(
browser.i18n.detectLanguage('Hello, world!'),
).resolves.toMatchTypeOf<chrome.i18n.LanguageDetectionResult>();
});
});
});
File diff suppressed because it is too large Load Diff
-9
View File
@@ -1,9 +0,0 @@
/* DO NOT EDIT - generated by scripts/generate.ts */
import { Entry, Log } from "har-format";
declare global {
export type HARFormatEntry = Entry;
export type HARFormatLog = Log;
}
-14547
View File
File diff suppressed because it is too large Load Diff
-4
View File
@@ -1,4 +0,0 @@
import { Browser } from './gen';
export const browser: typeof Browser;
export { Browser };
-5
View File
@@ -1,5 +0,0 @@
// #region snippet
export const browser = globalThis.browser?.runtime?.id
? globalThis.browser
: globalThis.chrome;
// #endregion snippet
-33
View File
@@ -1,33 +0,0 @@
{
"name": "@wxt-dev/browser",
"description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome",
"version": "{{chromeTypesVersion}}",
"type": "module",
"main": "src/index.mjs",
"types": "src/index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/browser"
},
"scripts": {
"check": "check",
"gen": "tsx scripts/generate.ts"
},
"author": {
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
},
"license": "MIT",
"files": [
"src"
],
"devDependencies": {
"@types/chrome": "{{chromeTypesVersion}}",
"fs-extra": "^11.3.1",
"nano-spawn": "^1.0.2",
"tsx": "4.19.4",
"typescript": "^5.9.2",
"vitest": "^3.1.2"
}
}
-4
View File
@@ -1,4 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {}
}
-31
View File
@@ -1,36 +1,5 @@
# Changelog
## v0.2.4
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.3...i18n-v0.2.4)
### 🩹 Fixes
- Standardize locale codes and warn about unsupported ones ([#1617](https://github.com/wxt-dev/wxt/pull/1617))
- Use `@wxt-dev/browser` instead of `@types/chrome` ([#1645](https://github.com/wxt-dev/wxt/pull/1645))
### 📖 Documentation
- Add react language ID to README ([#1347](https://github.com/wxt-dev/wxt/pull/1347))
- Fix public path reference ([bcb20874](https://github.com/wxt-dev/wxt/commit/bcb20874))
### 🏡 Chore
- **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164))
- **deps:** Bump dev and non-breaking major dependencies ([#1167](https://github.com/wxt-dev/wxt/pull/1167))
- Add funding links to `package.json` files ([#1446](https://github.com/wxt-dev/wxt/pull/1446))
- Use PNPM 10's new catelog feature ([#1493](https://github.com/wxt-dev/wxt/pull/1493))
- Move production dependencies to PNPM 10 catelog ([#1494](https://github.com/wxt-dev/wxt/pull/1494))
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
- Change browser workspace dependency to `^` ([c7335add](https://github.com/wxt-dev/wxt/commit/c7335add))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Okinea Dev ([@okineadev](https://github.com/okineadev))
- Redwoodlid ([@redwoodlid](https://github.com/redwoodlid))
## v0.2.3
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.2...i18n-v0.2.3)

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