Compare commits
187 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e5fb7745a | |||
| 2bc92043f8 | |||
| 3024bd825d | |||
| 0b39774690 | |||
| a461a23845 | |||
| c0867e3374 | |||
| 47040277a2 | |||
| 0f0daf378c | |||
| 51b315f23a | |||
| 60f5117bbc | |||
| 15634ecb95 | |||
| 0a5007aee0 | |||
| bc54e1bd63 | |||
| d8a8202e63 | |||
| c9eb3b0ca9 | |||
| 665f5a52c4 | |||
| 798f3b8e47 | |||
| a886ab28e1 | |||
| 28cff9c90a | |||
| 8ce197b023 | |||
| a5553457c6 | |||
| 7d9ed777fb | |||
| 59362e7274 | |||
| d32d17952c | |||
| 6630bca73f | |||
| befd442075 | |||
| da885ff6c8 | |||
| 349b216439 | |||
| 47b78d66cf | |||
| fe2baef2b1 | |||
| 357bbdaec9 | |||
| a38e3c2733 | |||
| f17e527d72 | |||
| e20de4ca0d | |||
| 16ac53e0a1 | |||
| fee978bf25 | |||
| f80909d63e | |||
| d5b2503155 | |||
| 80ca5760e2 | |||
| 44dab3726f | |||
| c4295a4f4b | |||
| a6a91250d1 | |||
| 1db50ed7a9 | |||
| 342057b3f5 | |||
| 151b1392c6 | |||
| 1d5d0663ac | |||
| 30b8ba495d | |||
| 5775c9ce0f | |||
| 4f82dd87fc | |||
| fab9fa81d1 | |||
| d6ee4560d8 | |||
| 655ba863a0 | |||
| ac92d4019f | |||
| 0ff964a04b | |||
| 89459f0a4d | |||
| 0d56100abb | |||
| dc770b5952 | |||
| 8cd2bff67a | |||
| 11e55e2499 | |||
| 4cab3a5593 | |||
| daa0eed851 | |||
| 868a347d40 | |||
| 48a87aa352 | |||
| dfec8a3608 | |||
| 9a71231436 | |||
| db4592fbb3 | |||
| 8551d6071e | |||
| c40285f750 | |||
| 9238f9fd85 | |||
| ba3d8838e3 | |||
| e9b6f5f97c | |||
| 5b6c82d38f | |||
| 5b41b70abe | |||
| 93f624610f | |||
| 73bdd49471 | |||
| 635f3f0898 | |||
| a53ee6d590 | |||
| 665b9194cf | |||
| 4690418c94 | |||
| 20eea2a7d1 | |||
| ac7b6760f2 | |||
| a593cfbbb6 | |||
| a205f2a0a8 | |||
| 8e2badc92b | |||
| 7c18d9c0b8 | |||
| 55a1f39450 | |||
| d708eca78e | |||
| 5d7e2516b6 | |||
| fb6a29872b | |||
| 187206062e | |||
| 2615e29e52 | |||
| 8ca176d37e | |||
| cb2bf43f30 | |||
| 1719d17cff | |||
| 32afb8d97c | |||
| 72b8a29d76 | |||
| af0a32d478 | |||
| 060e85f90e | |||
| 654a54ac79 | |||
| ef24358942 | |||
| e5e67e39bd | |||
| 228304bef9 | |||
| 78f97f32a2 | |||
| 459662a6c1 | |||
| b150a52471 | |||
| 028c601515 | |||
| af382ef065 | |||
| 91ef7f2ab5 | |||
| 0bf7279c8f | |||
| 62525d33c6 | |||
| 88ff40e301 | |||
| 1a72a0cdc6 | |||
| 2b07b4bc12 | |||
| 06c09e7c87 | |||
| 30b96ac87c | |||
| 2e9b5cffe8 | |||
| 3e1641a6ec | |||
| d64ca52bb0 | |||
| 8c645fa1ce | |||
| 79c9c89923 | |||
| 11d93fe724 | |||
| 039c561a80 | |||
| 64a704fc72 | |||
| 4fbf137d15 | |||
| ff065ab88b | |||
| 7fdf3fb968 | |||
| 7ebd451409 | |||
| a7a48a8a75 | |||
| 27298b7f22 | |||
| 565ce93a9a | |||
| 0bc6088b6a | |||
| 2fb99e8fbb | |||
| cd2050c534 | |||
| 5b557d0224 | |||
| 0137d79ba0 | |||
| e05fbf4fb5 | |||
| 72e6ca5063 | |||
| af9e842fe0 | |||
| 230962686f | |||
| 413cd9354f | |||
| 64a42de31f | |||
| f7f03e2669 | |||
| 1dc7c34bfd | |||
| 0fd5c3846c | |||
| e42e92f9df | |||
| e45c91f941 | |||
| 8f71d80f94 | |||
| 78e896e5fb | |||
| a643852387 | |||
| e221252325 | |||
| 88f126e658 | |||
| 17af05acc8 | |||
| fd436a0d63 | |||
| 0d796b63c2 | |||
| cb143d2bbc | |||
| f3e7758521 | |||
| 23ef101954 | |||
| c2f5efbde4 | |||
| 4ce178c592 | |||
| 7aa30afa7f | |||
| 90aaed202c | |||
| c92ed13d66 | |||
| 0e4953fbfc | |||
| 799d687061 | |||
| 866eb0694c | |||
| e5723aab03 | |||
| f51f5dd19f | |||
| aae5f0789d | |||
| 586fe3950c | |||
| 241c907c65 | |||
| aab4244d84 | |||
| 767a3a987c | |||
| a2924d872a | |||
| 54baf21490 | |||
| 5a3790a7d3 | |||
| 236e099b81 | |||
| 9b54bc4704 | |||
| ffb99af719 | |||
| 90bb145d91 | |||
| 190c87d120 | |||
| 4c511d5728 | |||
| b8ecbc5a54 | |||
| 8c3d756187 | |||
| cfdc6b3e6b | |||
| 226e6c9fbe | |||
| db41f27072 | |||
| db025cd3a2 |
@@ -0,0 +1,10 @@
|
||||
extends:
|
||||
- '@commitlint/config-conventional'
|
||||
rules:
|
||||
subject-case:
|
||||
- 0
|
||||
- always
|
||||
- - sentence-case
|
||||
- start-case
|
||||
- pascal-case
|
||||
- upper-case
|
||||
@@ -1,5 +1,13 @@
|
||||
# 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
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Set default
|
||||
* @aklinker1 @Timeraa
|
||||
|
||||
# Secure Directories
|
||||
/.github/ @aklinker1
|
||||
|
||||
# Creator of specific wxt modules
|
||||
/packages/auto-icons/ @Timeraa
|
||||
/packages/unocss/ @Timeraa
|
||||
+2
-14
@@ -1,15 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
# 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
|
||||
|
||||
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']
|
||||
github: wxt-dev
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
name: Basic Setup
|
||||
description: Install PNPM, Node, and dependencies
|
||||
|
||||
inputs:
|
||||
install:
|
||||
default: 'true'
|
||||
type: boolean
|
||||
description: Whether or not to run 'pnpm install'
|
||||
|
||||
installArgs:
|
||||
default: ''
|
||||
type: string
|
||||
description: Additional args to append to "pnpm install"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Setup PNPM
|
||||
- name: 🛠️ Setup PNPM
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Setup NodeJS
|
||||
|
||||
- name: 🛠️ Setup NodeJS
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: pnpm
|
||||
- name: Install Dependencies
|
||||
|
||||
- name: 📦 Install Dependencies
|
||||
if: ${{ inputs.install == 'true' }}
|
||||
shell: bash
|
||||
run: pnpm install ${{ inputs.installArgs }}
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directories:
|
||||
- /
|
||||
- packages/*
|
||||
directory: /
|
||||
schedule:
|
||||
interval: 'monthly'
|
||||
- package-ecosystem: 'github-actions'
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
### 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,4 +1,4 @@
|
||||
name: Continuous Publish
|
||||
name: ✨ pkg.pr.new
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -7,11 +7,23 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Publish Test Packages
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ github.repository == 'wxt-dev/wxt' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm buildc all
|
||||
- run: pnpx pkg-pr-new publish --compact './packages/*'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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/*'
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
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
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
name: 🛡️ Check PR Title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited]
|
||||
|
||||
jobs:
|
||||
lint-pr-title:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish Docs
|
||||
name: 📝 Publish Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -10,20 +10,38 @@ 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:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: docker/login-action@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
|
||||
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
|
||||
- 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 }}
|
||||
|
||||
- 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 }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Release
|
||||
name: 🚀 Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -7,6 +7,7 @@ on:
|
||||
default: wxt
|
||||
type: choice
|
||||
options:
|
||||
- analytics
|
||||
- auto-icons
|
||||
- i18n
|
||||
- module-react
|
||||
@@ -17,38 +18,51 @@ on:
|
||||
- unocss
|
||||
- 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:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
- 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
|
||||
|
||||
- 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: NPM
|
||||
- name: Publish to NPM
|
||||
working-directory: packages/${{ inputs.package }}
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
|
||||
pnpm build
|
||||
pnpm publish
|
||||
working-directory: packages/${{ inputs.package }}
|
||||
|
||||
- name: GitHub Release
|
||||
- name: Create GitHub release
|
||||
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Sync Releases
|
||||
name: 🔄 Sync Releases
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -7,6 +7,7 @@ on:
|
||||
default: wxt
|
||||
type: choice
|
||||
options:
|
||||
- analytics
|
||||
- auto-icons
|
||||
- i18n
|
||||
- module-react
|
||||
@@ -16,14 +17,25 @@ on:
|
||||
- storage
|
||||
- wxt
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: Sync Releases
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
installArgs: --ignore-scripts
|
||||
- run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
|
||||
|
||||
- name: Sync Releases
|
||||
run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Validate
|
||||
name: 🛡️ Validate
|
||||
on:
|
||||
workflow_call:
|
||||
pull_request:
|
||||
@@ -6,46 +6,90 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
name: Checks
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm check
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Basic Checks
|
||||
run: pnpm check
|
||||
|
||||
builds:
|
||||
name: Builds
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm buildc all
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build All Packages
|
||||
run: pnpm buildc all
|
||||
|
||||
build-demo:
|
||||
name: Build Demo
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm build:all
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build
|
||||
run: pnpm build:all
|
||||
working-directory: packages/wxt-demo
|
||||
- run: pnpm wxt zip
|
||||
|
||||
- name: ZIP
|
||||
run: pnpm wxt zip
|
||||
working-directory: packages/wxt-demo
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- name: pnpm test:coverage
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Run Tests
|
||||
run: pnpm test:coverage -- --reporter=default --reporter=hanging-process
|
||||
- uses: codecov/codecov-action@v4
|
||||
|
||||
- name: Upload Coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
windows-tests:
|
||||
name: Windows Tests
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm test
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Run Tests
|
||||
run: pnpm test
|
||||
|
||||
template:
|
||||
name: Template
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -57,19 +101,34 @@ jobs:
|
||||
- vanilla
|
||||
- vue
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- run: pnpm pack
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Pack WXT package
|
||||
run: pnpm pack
|
||||
working-directory: packages/wxt
|
||||
- run: npm i
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm i
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: npm i -D ../../packages/wxt/wxt-*.tgz
|
||||
|
||||
- name: Install Packed WXT
|
||||
run: npm i -D ../../packages/wxt/wxt-*.tgz
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: pnpm compile
|
||||
|
||||
- name: Type Check Template
|
||||
run: pnpm compile
|
||||
if: matrix.template != 'svelte'
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: pnpm check
|
||||
|
||||
- name: Type Check Template
|
||||
run: pnpm check
|
||||
if: matrix.template == 'svelte'
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
- run: pnpm build
|
||||
|
||||
- name: Build Template
|
||||
run: pnpm build
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
|
||||
@@ -1,26 +1,42 @@
|
||||
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:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
install: 'false'
|
||||
- name: Preinstall WXT
|
||||
install: false
|
||||
|
||||
# This prevents pnpm dlx from downloading WXT in the video
|
||||
- name: Pre-install WXT
|
||||
run: |
|
||||
pnpm store add wxt@latest
|
||||
pnpm dlx wxt@latest --version
|
||||
- uses: charmbracelet/vhs-action@v2.1.0
|
||||
|
||||
- name: Record VHS
|
||||
uses: charmbracelet/vhs-action@v2.1.0
|
||||
with:
|
||||
path: 'docs/tapes/init-demo.tape'
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
|
||||
- name: Save recorded GIF
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
.webextrc
|
||||
.wxt
|
||||
*.log
|
||||
/.vscode/
|
||||
/docs/.vitepress/cache
|
||||
coverage
|
||||
dist
|
||||
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"davidanson.vscode-markdownlint",
|
||||
"esbenp.prettier-vscode",
|
||||
"github.vscode-github-actions"
|
||||
]
|
||||
}
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
// 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" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
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
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
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.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
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.
|
||||
+31
-2
@@ -8,9 +8,17 @@ If you want to add a new feature, please create an issue or discussion first so
|
||||
|
||||
## Conventional Commits
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
Maintainers, commits to the `main` branch (either directly or via PRs) must be valid conventional commits.
|
||||
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.
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -145,3 +153,24 @@ pnpm dlx taze -r
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```sh
|
||||
npm i https://pkg.pr.new/[package-name]@[ref]
|
||||
```
|
||||
|
||||
Or use one of the shorthands:
|
||||
|
||||
```sh
|
||||
# Install the latest build of `wxt` from a PR:
|
||||
npm i https://pkg.pr.new/wxt@1283
|
||||
|
||||
# Install the latest build of `@wxt-dev/module-react` on the `main` branch
|
||||
npm i https://pkg.pr.new/@wxt-dev/module-react@main
|
||||
|
||||
# Install `@wxt-dev/storage` from a specific commit:
|
||||
npm i https://pkg.pr.new/@wxt-dev/module-react@426f907
|
||||
```
|
||||
|
||||
@@ -75,6 +75,12 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
|
||||
- 📏 Bundle analysis
|
||||
- ⬇️ Download and bundle remote URL imports
|
||||
|
||||
## Sponsors
|
||||
|
||||
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).
|
||||
|
||||
<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" -->
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# 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.
|
||||
-->
|
||||
@@ -4,11 +4,8 @@ import useListExtensionDetails, {
|
||||
ChromeExtension,
|
||||
} from '../composables/useListExtensionDetails';
|
||||
|
||||
// Add extension IDs here. Order doesn't matter, will be sorted by weekly active users
|
||||
// During the transition from chrome.google.com/webstore to
|
||||
// chromewebstore.google.com, queue.wxt.dev might return null for your
|
||||
// extension. If it does, use "<slug>/<id>" instead of just the ID. The slug
|
||||
// can be retrieved from the URL of the item on chromewebstore.google.com
|
||||
// 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
|
||||
'mgmdkjcljneegjfajchedjpdhbadklcf', // Anime Skip Player
|
||||
@@ -16,7 +13,7 @@ const chromeExtensionIds = [
|
||||
'elfaihghhjjoknimpccccmkioofjjfkf', // StayFree - Website Blocker & Web Analytics
|
||||
'okifoaikfmpfcamplcfjkpdnhfodpkil', // Doozy: Ai Made Easy
|
||||
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
|
||||
'youtube中文配音/oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
'oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
|
||||
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
|
||||
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
|
||||
@@ -56,6 +53,21 @@ const chromeExtensionIds = [
|
||||
'lfknakglefggmdkjdfhhofkjnnolffkh', // Text Search Pro - Search by case and whole-word match!
|
||||
'mbenhbocjckkbaojacmaepiameldglij', // Invoice Generator
|
||||
'phlfhkmdofajnbhgmbmjkbkdgppgoppb', // Monthly Bill Tracker
|
||||
'macmkmchfoclhpbncclinhjflmdkaoom', // Wandpen - Instantly improve your writing with AI
|
||||
'lhmgechokhmdekdpgkkemoeecelcaonm', // YouTube Hider - Remove Comments By Keywords, Usernames & Tools
|
||||
'imgheieooppmahcgniieddodaliodeeg', // QA Compass - Record standardized bug reports easily
|
||||
'npgghjedpchajflknnbngajkjkdhncdo', // aesthetic Notion, styled
|
||||
'hmdcmlfkchdmnmnmheododdhjedfccka', // Eye Dropper
|
||||
'eihpmapodnppeemkhkbhikmggfojdkjd', // Cursorful - Screen Recorder with Auto Zoom
|
||||
'hjjkgbibknbahijglkffklflidncplkn', // Show IP – Live View of Website IPs for Developers
|
||||
'ilbikcehnpkmldojkcmlldkoelofnbde', // Strong Password Generator
|
||||
'ocllfkhcdopiafndigclebelbecaiocp', // ZenGram: Mindful Instagram, Your Way
|
||||
'odffpjnpocjfcaclnenaaaddghkgijdb', // Blync: Preview Links, Selection Search, AI Assistant
|
||||
'kofbbilhmnkcmibjbioafflgmpkbnmme', // HTML to Markdown - Convert webpages to markdown
|
||||
'boecmgggeigllcdocgioijmleimjbfkg', // Walmart WFS Profit Calculator
|
||||
'dlnjcbkmomenmieechnmgglgcljhoepd', // Youtube Live Chat Fullscreen
|
||||
'keiealdacakpnbbljlmhfgcebmaadieg', // Python Code Runner
|
||||
'hafcajcllbjnoolpfngclfmmgpikdhlm', // Monochromate
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
|
||||
@@ -14,17 +14,38 @@ 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 knowledge from 'vitepress-knowledge';
|
||||
|
||||
const title = 'Next-gen Web Extension Framework';
|
||||
const titleSuffix = ' – WXT';
|
||||
const description =
|
||||
"WXT provides the best developer experience, making it quick, easy, and fun to develop chrome extensions for all browsers. With built-in utilities for building, zipping, and publishing your extension, it's easy to get started.";
|
||||
"WXT provides the best developer experience, making it quick, easy, and fun to develop web extensions. With built-in utilities for building, zipping, and publishing your extension, it's easy to get started.";
|
||||
const ogTitle = `${title}${titleSuffix}`;
|
||||
const ogUrl = 'https://wxt.dev';
|
||||
const ogImage = 'https://wxt.dev/social-preview.png';
|
||||
|
||||
const otherPackages = {
|
||||
analytics: analyticsVersion,
|
||||
'auto-icons': autoIconsVersion,
|
||||
i18n: i18nVersion,
|
||||
storage: storageVersion,
|
||||
unocss: unocssVersion,
|
||||
};
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
extends: knowledge({
|
||||
serverUrl: 'https://knowledge.wxt.dev',
|
||||
paths: {
|
||||
'/': 'docs',
|
||||
'/api/': 'api-reference',
|
||||
},
|
||||
pageSelectors: {
|
||||
'examples.md': '#VPContent > .VPPage',
|
||||
},
|
||||
}),
|
||||
|
||||
titleTemplate: `:title${titleSuffix}`,
|
||||
title: 'WXT',
|
||||
description,
|
||||
@@ -62,6 +83,16 @@ export default defineConfig({
|
||||
alt: 'WXT logo',
|
||||
},
|
||||
|
||||
footer: {
|
||||
message: [
|
||||
'<a class="light-netlify" href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-bg.svg" alt="Deploys by Netlify" style="display: inline;" /></a>',
|
||||
'<a class="dark-netlify" href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-accent.svg" alt="Deploys by Netlify" style="display: inline;" /></a>',
|
||||
'Released under the <a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE">MIT License</a>.',
|
||||
].join('<br/>'),
|
||||
copyright:
|
||||
'Copyright © 2023-present <a href="https://github.com/aklinker1">Aaron Klinker</a>',
|
||||
},
|
||||
|
||||
editLink: {
|
||||
pattern: 'https://github.com/wxt-dev/wxt/edit/main/docs/:path',
|
||||
},
|
||||
@@ -87,12 +118,12 @@ export default defineConfig({
|
||||
'https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md',
|
||||
),
|
||||
]),
|
||||
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'),
|
||||
]),
|
||||
navItem(
|
||||
'Other Packages',
|
||||
Object.entries(otherPackages).map(([name, version]) =>
|
||||
navItem(`@wxt-dev/${name} — ${version}`, `/${name}`),
|
||||
),
|
||||
),
|
||||
]),
|
||||
],
|
||||
|
||||
@@ -142,6 +173,7 @@ 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,72 +1,28 @@
|
||||
/* Colors */
|
||||
:root {
|
||||
--wxt-c-green: #53bc4a;
|
||||
--wxt-c-green-1: #0b8a00;
|
||||
--wxt-c-green-2: #096600;
|
||||
--wxt-c-green-3: #096600;
|
||||
--wxt-c-green-soft: rgba(11, 138, 0, 0.14);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--wxt-c-green-1: #67d45e;
|
||||
--wxt-c-green-2: #4fa048;
|
||||
--wxt-c-green-3: #447e3f;
|
||||
--wxt-c-green-2: #329929;
|
||||
--wxt-c-green-3: #21651b;
|
||||
--wxt-c-green-soft: rgba(103, 212, 94, 0.14);
|
||||
}
|
||||
|
||||
/* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */
|
||||
|
||||
:root {
|
||||
--vp-c-brand: var(--wxt-c-green);
|
||||
--vp-c-brand-1: var(--wxt-c-green-1);
|
||||
--vp-c-brand-2: var(--wxt-c-green-2);
|
||||
--vp-c-brand-3: var(--wxt-c-green-3);
|
||||
|
||||
--vp-button-brand-bg: var(--wxt-c-green);
|
||||
--vp-button-brand-hover-bg: var(--wxt-c-green-2);
|
||||
--vp-button-brand-active-bg: var(--wxt-c-green-3);
|
||||
|
||||
--vp-code-link-color: var(--wxt-c-green);
|
||||
|
||||
/* --vp-c-text-1: var(--wxt-c-green-1); */
|
||||
|
||||
--vp-button-brand-text: var(--vp-c-black);
|
||||
--vp-button-brand-hover-text: var(--vp-c-black);
|
||||
--vp-button-brand-active-text: var(--vp-c-black);
|
||||
|
||||
--vp-custom-block-tip-border: var(--wxt-c-green);
|
||||
--vp-custom-block-tip-text: var(--wxt-c-green);
|
||||
|
||||
/* --vp-code-block-bg: #222422;
|
||||
--vp-code-copy-code-bg: #313431;
|
||||
--vp-code-copy-code-hover-bg: #3c403c; */
|
||||
|
||||
--vp-custom-block-tip-bg: var(--vp-code-block-bg);
|
||||
--vp-custom-block-info-bg: var(--vp-code-block-bg);
|
||||
|
||||
--vp-code-color: #476582;
|
||||
--vp-c-brand-soft: var(--wxt-c-green-soft);
|
||||
}
|
||||
|
||||
.vp-doc a {
|
||||
color: var(--wxt-c-green);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-c-bg: #131413;
|
||||
|
||||
--vp-c-bg-soft: #1a1b1a;
|
||||
--vp-c-bg-soft-up: #1f201f;
|
||||
--vp-c-bg-soft-down: #262926;
|
||||
--vp-c-bg-soft-mute: #242424;
|
||||
|
||||
--vp-c-bg-alt: #171817;
|
||||
|
||||
--vp-c-default: #313136;
|
||||
--vp-c-default-1: #3a3a3c;
|
||||
--vp-c-default-2: #505053;
|
||||
--vp-c-default-3: #2c2c30;
|
||||
--vp-c-default-soft: #252529;
|
||||
|
||||
--vp-code-block-bg: #191a19;
|
||||
--vp-code-copy-code-bg: #212321;
|
||||
--vp-code-copy-code-hover-bg: #292d29;
|
||||
|
||||
--vp-custom-block-info-bg: #191a19;
|
||||
|
||||
--vp-code-color: #c9def1;
|
||||
}
|
||||
/* Customize Individual Components */
|
||||
|
||||
.vp-doc .no-vertical-dividers th,
|
||||
.vp-doc .no-vertical-dividers td {
|
||||
@@ -97,3 +53,16 @@ body {
|
||||
vertical-align: middle;
|
||||
background-color: var(--vp-c-default-2);
|
||||
}
|
||||
|
||||
.light-netlify {
|
||||
display: inline;
|
||||
}
|
||||
.dark .light-netlify {
|
||||
display: none;
|
||||
}
|
||||
.dark-netlify {
|
||||
display: none;
|
||||
}
|
||||
.dark .dark-netlify {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
@@ -28,13 +28,13 @@ export function menuRoot(items: SidebarItem[]) {
|
||||
export function menuGroup(
|
||||
text: string,
|
||||
items: SidebarItem[],
|
||||
collapsable?: boolean,
|
||||
collapsible?: boolean,
|
||||
): SidebarItem;
|
||||
export function menuGroup(
|
||||
text: string,
|
||||
base: string,
|
||||
items: SidebarItem[],
|
||||
collapsable?: boolean,
|
||||
collapsible?: boolean,
|
||||
): SidebarItem;
|
||||
export function menuGroup(
|
||||
text: string,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<!--@include: ../packages/analytics/README.md-->
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 131 KiB |
@@ -16,7 +16,8 @@ img.src = imageUrl;
|
||||
```
|
||||
|
||||
```html [HTML]
|
||||
<img src="~/assets/image.png" />
|
||||
<!-- In HTML tags, you must use the relative path --->
|
||||
<img src="../assets/image.png" />
|
||||
```
|
||||
|
||||
```css [CSS]
|
||||
@@ -25,6 +26,22 @@ 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
|
||||
@@ -52,6 +69,16 @@ img.src = imageUrl;
|
||||
}
|
||||
```
|
||||
|
||||
```vue [Vue]
|
||||
<template>
|
||||
<img src="/image.png" />
|
||||
</template>
|
||||
```
|
||||
|
||||
```jsx [JSX]
|
||||
<img src="/image.png" />
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Inside Content Scripts
|
||||
@@ -77,6 +104,84 @@ export default defineContentScript({
|
||||
|
||||
## WASM
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
How a `.wasm` file is loaded varies greatly between packages, but most follow a basic setup: Use a JS API to load and execute the `.wasm` file.
|
||||
|
||||
For an extension, that means two things:
|
||||
|
||||
1. The `.wasm` file needs to be present in output folder so it can be loaded.
|
||||
2. You must import the JS API to load and initialize the `.wasm` file, usually provided by the NPM package.
|
||||
|
||||
For an example, let's say you have a content script needs to parse TS code into AST. We'll use [`@oxc-parser/wasm`](https://www.npmjs.com/package/@oxc-parser/wasm) to do it!
|
||||
|
||||
First, we need to copy the `.wasm` file to the output directory. We'll do it with a [WXT module](/guide/essentials/wxt-modules):
|
||||
|
||||
```ts
|
||||
// modules/oxc-parser-wasm.ts
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export default defineWxtModule((wxt) => {
|
||||
wxt.hook('build:publicAssets', (_, assets) => {
|
||||
assets.push({
|
||||
absoluteSrc: resolve(
|
||||
'node_modules/@oxc-parser/wasm/web/oxc_parser_wasm_bg.wasm',
|
||||
),
|
||||
relativeDest: 'oxc_parser_wasm_bg.wasm',
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Run `wxt build`, and you should see the WASM file copied into your `.output/chrome-mv3` folder!
|
||||
|
||||
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
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
web_accessible_resources: [
|
||||
{
|
||||
// We'll use this matches in the content script as well
|
||||
matches: ['*://*.github.com/*'],
|
||||
// Use the same path as `relativeDest` from the WXT module
|
||||
resources: ['/oxc_parser_wasm_bg.wasm'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
And finally, we need to load and initialize the `.wasm` file inside the content script to use it:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
import initWasm, { parseSync } from '@oxc-parser/wasm';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: '*://*.github.com/*',
|
||||
async main(ctx) {
|
||||
if (!location.pathname.endsWith('.ts')) return;
|
||||
|
||||
// Get text from GitHub
|
||||
const code = document.getElementById(
|
||||
'read-only-cursor-text-area',
|
||||
)?.textContent;
|
||||
if (!code) return;
|
||||
const sourceFilename = document.getElementById('file-name-id')?.textContent;
|
||||
if (!sourceFilename) return;
|
||||
|
||||
// Load the WASM file:
|
||||
await initWasm({
|
||||
module_or_path: browser.runtime.getURL('/oxc_parser_wasm_bg.wasm'),
|
||||
});
|
||||
|
||||
// Once loaded, we can use `parseSync`!
|
||||
const ast = parseSync(code, { sourceFilename });
|
||||
console.log(ast);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This code is taken directly from `@oxc-parser/wasm` docs with one exception: We manually pass in a file path. In a standard NodeJS or web project, the default path works just fine so you don't have to pass anything in. However, extensions are different. You should always explicitly pass in the full URL to the WASM file in your output directory, which is what `browser.runtime.getURL` returns.
|
||||
|
||||
Run your extension, and you should see OXC parse the TS file!
|
||||
|
||||
@@ -11,7 +11,7 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
By default, WXT automatically setups up auto-imports for all of it's own APIs:
|
||||
By default, WXT sets up auto-imports for all of it's own APIs:
|
||||
|
||||
- [`browser`](/api/reference/wxt/browser/variables/browser) from `wxt/browser`
|
||||
- [`defineContentScript`](/api/reference/wxt/sandbox/functions/defineContentScript) from `wxt/sandbox`
|
||||
|
||||
@@ -6,7 +6,7 @@ outline: deep
|
||||
|
||||
> See the [API Reference](/api/reference/wxt/interfaces/ExtensionRunnerConfig) 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
|
||||
|
||||
@@ -41,6 +41,8 @@ export default defineRunnerConfig({
|
||||
});
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -62,7 +64,8 @@ import { resolve } from 'node:path';
|
||||
|
||||
export default defineRunnerConfig({
|
||||
// On Windows, the path must be absolute
|
||||
chromiumArgs: [`--user-data-dir="${resolve('.wxt/chrome-data')}"`],
|
||||
chromiumProfile: resolve('.wxt/chrome-data'),
|
||||
keepProfileChanges: true,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -71,7 +74,7 @@ export default defineRunnerConfig({
|
||||
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 you're 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 your user's home directory.
|
||||
:::
|
||||
|
||||
### Disable Opening Browser
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
# Manifest
|
||||
|
||||
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.
|
||||
In WXT, there is no `manifest.json` file in your source code. Instead, WXT generates the manifest from multiple sources:
|
||||
|
||||
## Manifest Config
|
||||
- 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
|
||||
|
||||
To manually add a property to the `manifest.json` output during builds, use the `manifest` config inside `wxt.config.ts`:
|
||||
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`:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
@@ -130,7 +137,7 @@ public/
|
||||
└─ icon-128.png
|
||||
```
|
||||
|
||||
Specifically, if an icon must match one of these regex to be discovered:
|
||||
Specifically, an icon must match one of these regex to be discovered:
|
||||
|
||||
<<< @/../packages/wxt/src/core/utils/manifest.ts#snippet
|
||||
|
||||
@@ -156,6 +163,11 @@ Alternatively, you can use [`@wxt-dev/auto-icons`](https://www.npmjs.com/package
|
||||
|
||||
> [Chrome docs](https://developer.chrome.com/docs/extensions/reference/permissions/)
|
||||
|
||||
Most of the time, you need to manually add permissions to your manifest. Only in a few specific situations are permissions added automatically:
|
||||
|
||||
- During development: the `tabs` and `scripting` permissions will be added to enable hot reloading.
|
||||
- When a `sidepanel` entrypoint is present: The `sidepanel` permission is added.
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
@@ -211,22 +223,7 @@ 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).
|
||||
|
||||
```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:
|
||||
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:
|
||||
|
||||
```html
|
||||
<meta name="manifest.type" content="page_action" />
|
||||
|
||||
@@ -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 types="./.wxt/wxt.d.ts" />
|
||||
/// <reference path="./.wxt/wxt.d.ts" />
|
||||
```
|
||||
|
||||
## Compiler Options
|
||||
|
||||
@@ -4,12 +4,14 @@ 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 it's "context".
|
||||
The first argument to a content script's `main` function is its "context".
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
// entrypoints/example.content.ts
|
||||
export default defineContentScript({
|
||||
main(ctx) {},
|
||||
});
|
||||
@@ -62,7 +64,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/content/index.ts
|
||||
// entrypoints/example.content/index.ts
|
||||
import './style.css';
|
||||
|
||||
export default defineContentScript({
|
||||
@@ -78,16 +80,16 @@ To create a standalone content script that only includes a CSS file:
|
||||
// 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
|
||||
@@ -198,6 +200,7 @@ 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>'],
|
||||
@@ -208,14 +211,13 @@ export default defineContentScript({
|
||||
anchor: 'body',
|
||||
onMount: (container) => {
|
||||
// Create the Svelte app inside the UI container
|
||||
const app = new App({
|
||||
mount(App, {
|
||||
target: container,
|
||||
});
|
||||
return app;
|
||||
},
|
||||
onRemove: (app) => {
|
||||
// Destroy the app when the UI is removed
|
||||
app.$destroy();
|
||||
unmount(app);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -379,6 +381,7 @@ 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>'],
|
||||
@@ -393,14 +396,13 @@ export default defineContentScript({
|
||||
anchor: 'body',
|
||||
onMount: (container) => {
|
||||
// Create the Svelte app inside the UI container
|
||||
const app = new App({
|
||||
mount(App, {
|
||||
target: container,
|
||||
});
|
||||
return app;
|
||||
},
|
||||
onRemove: (app) => {
|
||||
onRemove: () => {
|
||||
// Destroy the app when the UI is removed
|
||||
app?.$destroy();
|
||||
unmount(app);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -548,16 +550,35 @@ To use `injectScript`, we need two entrypoints, one content script and one unlis
|
||||
```ts
|
||||
// entrypoints/example-main-world.ts
|
||||
export default defineUnlistedScript(() => {
|
||||
console.log('Hello from the main world!');
|
||||
console.log('Hello from the main world');
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
// entrypoints/example.content.ts
|
||||
export default defineContentScript(async () => {
|
||||
await injectScript('/example-main-world.js', {
|
||||
keepInDom: true,
|
||||
});
|
||||
export default defineContentScript({
|
||||
matches: ['*://*/*'],
|
||||
async main() {
|
||||
console.log('Injecting script...');
|
||||
await injectScript('/example-main-world.js', {
|
||||
keepInDom: true,
|
||||
});
|
||||
console.log('Done!');
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```json
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
// ...
|
||||
web_accessible_resources: [
|
||||
{
|
||||
resources: ["example-main-world.js"],
|
||||
matches: ["*://*/*"],
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@@ -571,6 +592,39 @@ For MV3, `injectScript` is synchronous and the injected script will be evaluated
|
||||
However for MV2, `injectScript` has to `fetch` the script's text content and create an inline `<script>` block. This means for MV2, your script is injected asynchronously and it will not be evaluated at the same time as your content script's `run_at`.
|
||||
:::
|
||||
|
||||
## Mounting UI to dynamic element
|
||||
|
||||
In many cases, you may need to mount a UI to a DOM element that does not exist at the time the web page is initially loaded. To handle this, use the `autoMount` API to automatically mount the UI when the target element appears dynamically and unmount it when the element disappears. In WXT, the `anchor` option is used to target the element, enabling automatic mounting and unmounting based on its appearance and removal.
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
// It observes the anchor
|
||||
anchor: '#your-target-dynamic-element',
|
||||
onMount: (container) => {
|
||||
// Append children to the container
|
||||
const app = document.createElement('p');
|
||||
app.textContent = '...';
|
||||
container.append(app);
|
||||
},
|
||||
});
|
||||
|
||||
// Call autoMount to observe anchor element for add/remove.
|
||||
ui.autoMount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::tip
|
||||
When the `ui.remove` is called, `autoMount` also stops.
|
||||
:::
|
||||
|
||||
See the [API Reference](/api/reference/wxt/client/interfaces/ContentScriptUi.html#automount) for the complete list of options.
|
||||
|
||||
## Dealing with SPAs
|
||||
|
||||
It is difficult to write content scripts for SPAs (single page applications) and websites using HTML5 history mode for navigation because content scripts are only ran on full page reloads. SPAs and websites that take advantage of HTML5 history mode **_do not perform a full reload when changing paths_**, and thus your content script isn't going to be ran when you expect it to be.
|
||||
|
||||
@@ -28,7 +28,7 @@ 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:
|
||||
Throughout the rest of WXT's documentation, listed entrypoints are referred to by name. For example:
|
||||
|
||||
- Popup
|
||||
- Options
|
||||
@@ -42,7 +42,7 @@ Some examples of "unlisted" entrypoints:
|
||||
- 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.
|
||||
Regardless of whether an entrypoint is listed or unlisted, it will still be bundled into your extension and be available at runtime.
|
||||
:::
|
||||
|
||||
## Adding Entrypoints
|
||||
@@ -72,7 +72,7 @@ Refer to the [Entrypoint Types](#entrypoint-types) section for the full list of
|
||||
|
||||
## Defining Manifest Options
|
||||
|
||||
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_.
|
||||
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_.
|
||||
|
||||
For example, here's how to define `matches` for content scripts:
|
||||
|
||||
@@ -86,6 +86,17 @@ 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.
|
||||
@@ -304,14 +315,14 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewportYou 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."
|
||||
content="width=device-width, initial-scale=1.0"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Options Title</title>
|
||||
|
||||
<!-- Customize the manifest options -->
|
||||
<meta name="manifest.open_in_tab" content="true|false" />
|
||||
<meta name="manifest.chrome_style" content="true|false" />
|
||||
<meta name="manifest.browser_style" content="true|false" />
|
||||
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
@@ -339,7 +350,11 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<!-- Set the `action.default_title` in the manifest -->
|
||||
<title>Default Popup Title</title>
|
||||
|
||||
<!-- Customize the manifest options -->
|
||||
<meta
|
||||
name="manifest.default_icon"
|
||||
content="{
|
||||
@@ -350,6 +365,7 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
|
||||
/>
|
||||
<meta name="manifest.type" content="page_action|browser_action" />
|
||||
<meta name="manifest.browser_style" content="true|false" />
|
||||
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
@@ -384,6 +400,7 @@ Firefox does not support sandboxed pages.
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
@@ -416,6 +433,8 @@ In Chrome, side panels use the `side_panel` API, while Firefox uses the `sidebar
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Default Side Panel Title</title>
|
||||
|
||||
<!-- Customize the manifest options -->
|
||||
<meta
|
||||
name="manifest.default_icon"
|
||||
content="{
|
||||
@@ -426,6 +445,7 @@ In Chrome, side panels use the `side_panel` API, while Firefox uses the `sidebar
|
||||
/>
|
||||
<meta name="manifest.open_at_install" content="true|false" />
|
||||
<meta name="manifest.browser_style" content="true|false" />
|
||||
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
@@ -475,6 +495,7 @@ body {
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
@@ -532,4 +553,4 @@ const url = browser.runtime.getURL('/<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 stylesheets to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources).
|
||||
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).
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
# ES Modules
|
||||
|
||||
Currently, ESM entrypoints are opt-in, so you must configure each entrypoint with that in mind.
|
||||
You source code should always be written as ESM. However, you have some control whether an entrypoint is bundled as ESM.
|
||||
|
||||
## HTML Pages <Badge type="warning" text="≥0.0.1" />
|
||||
|
||||
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"`:
|
||||
Vite only supports bundling JS from HTML pages as ESM. Ensure you have added `type="module"` to your `<script>` tags:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
@@ -16,23 +14,25 @@ To make a script ESM, add `type="module"`:
|
||||
|
||||
## Background <Badge type="warning" text="≥0.16.0" />
|
||||
|
||||
In your background script, set `type: "module"`:
|
||||
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:
|
||||
|
||||
```ts
|
||||
export default defineBackground({
|
||||
type: 'module', // !code ++
|
||||
type: 'module', // [!code ++]
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
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 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -81,6 +81,10 @@ 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)
|
||||
|
||||
@@ -14,3 +14,4 @@ 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.
|
||||
|
||||
@@ -59,24 +59,24 @@ After enabling it, your project structure should look like this:
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
📂 {rootDir}/
|
||||
📁 .output/
|
||||
📁 .wxt/
|
||||
📂 src/
|
||||
📁 assets/
|
||||
📁 components/
|
||||
📁 composables/
|
||||
📁 entrypoints/
|
||||
📁 hooks/
|
||||
📁 modules/
|
||||
📁 public/
|
||||
📁 utils/
|
||||
📄 app.config.ts
|
||||
📄 .env
|
||||
📄 .env.publish
|
||||
📄 package.json
|
||||
📄 tsconfig.json
|
||||
📄 web-ext.config.ts
|
||||
📄 wxt.config.ts
|
||||
📁 .output/
|
||||
📁 .wxt/
|
||||
📂 src/
|
||||
📁 assets/
|
||||
📁 components/
|
||||
📁 composables/
|
||||
📁 entrypoints/
|
||||
📁 hooks/
|
||||
📁 modules/
|
||||
📁 public/
|
||||
📁 utils/
|
||||
📄 app.config.ts
|
||||
📄 .env
|
||||
📄 .env.publish
|
||||
📄 package.json
|
||||
📄 tsconfig.json
|
||||
📄 web-ext.config.ts
|
||||
📄 wxt.config.ts
|
||||
```
|
||||
|
||||
## Customizing Other Directories
|
||||
|
||||
@@ -129,7 +129,7 @@ wxt zip
|
||||
|
||||
Firefox requires you to upload a ZIP of your source code. This allows them to rebuild your extension and review the code in a readable way. More details can be found in [Firefox's docs](https://extensionworkshop.com/documentation/publish/source-code-submission/).
|
||||
|
||||
When running `wxt zip -b firefox`, WXT will zip both your extension and sources. Certain files (such as config files, hidden files, and tests) are automatically excluded from your sources. However, it's important to manually check the ZIP to ensure it only contains the files necessary to rebuild your extension.
|
||||
When running `wxt zip -b firefox`, WXT will zip both your extension and sources. Certain files (such as config files, hidden files, tests, and excluded entrypoints) are automatically excluded from your sources. However, it's important to manually check the ZIP to ensure it only contains the files necessary to rebuild your extension.
|
||||
|
||||
To customize which files are zipped, add the `zip` option to your config file.
|
||||
|
||||
|
||||
@@ -12,15 +12,18 @@ When using `browser.scripting.executeScript`, you can execute content scripts or
|
||||
// entrypoints/background.ts
|
||||
const res = await browser.scripting.executeScript({
|
||||
target: { tabId },
|
||||
files: ['injected.js'],
|
||||
files: ['content-scripts/example.js'],
|
||||
});
|
||||
console.log(res); // "Hello John!"
|
||||
```
|
||||
|
||||
```ts
|
||||
// entrypoints/injected.js
|
||||
export default defineUnlistedScript(() => {
|
||||
console.log('Script was injected!');
|
||||
return 'Hello John!';
|
||||
// entrypoints/example.content.ts
|
||||
export default defineContentScript({
|
||||
registration: 'runtime',
|
||||
main(ctx) {
|
||||
console.log('Script was executed!');
|
||||
return 'Hello John!';
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -75,3 +75,5 @@ Here are some examples:
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Alternatively, you can use the [`filterEntrypoints` config](/api/reference/wxt/interfaces/InlineConfig#filterentrypoints) to list all the entrypoints you want to build.
|
||||
|
||||
@@ -167,6 +167,31 @@ This file could then be loaded at runtime:
|
||||
const res = await fetch(browser.runtime.getURL('/some-text.txt'));
|
||||
```
|
||||
|
||||
#### Add custom entrypoints
|
||||
|
||||
Once the existing files under the `entrypoints/` directory have been discovered, the `entrypoints:found` hook can be used to add custom entrypoints.
|
||||
|
||||
:::info
|
||||
The `entrypoints:found` hook is triggered before validation is carried out on the list of entrypoints. Thus, any custom entrypoints will still be checked for duplicate names and logged during debugging.
|
||||
:::
|
||||
|
||||
```ts
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
|
||||
export default defineWxtModule({
|
||||
setup(wxt) {
|
||||
wxt.hook('entrypoints:found', (_, entrypointInfos) => {
|
||||
// Add your new entrypoint
|
||||
entrypointInfos.push({
|
||||
name: 'my-custom-script',
|
||||
inputPath: 'path/to/custom-script.js',
|
||||
type: 'content-script',
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Generate runtime module
|
||||
|
||||
Create a file in `.wxt`, add an alias to import it, and add auto-imports for exported variables.
|
||||
|
||||
@@ -121,3 +121,4 @@ 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!
|
||||
|
||||
@@ -9,7 +9,7 @@ Check out the [comparison](/guide/resources/compare) to see how WXT compares to
|
||||
|
||||
## Prerequisites
|
||||
|
||||
These docs assume you have a basic knowledge of how Chrome extensions are structured and how you access the extension APIs.
|
||||
These docs assume you have a basic knowledge of how web extensions are structured and how you access the extension APIs.
|
||||
|
||||
:::warning New to extension development?
|
||||
If you have never written an extension before, follow Chrome's [Hello World tutorial](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world) to first **_create an extension without WXT_**, then come back here.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
@@ -10,6 +10,7 @@ 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] |
|
||||
@@ -64,3 +65,7 @@ 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))_
|
||||
|
||||
+144
-36
@@ -18,49 +18,157 @@ To list the content scripts registered during development, open the service work
|
||||
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
|
||||
|
||||
## How do I stay logged into a website during development?
|
||||
|
||||
See https://wxt.dev/guide/essentials/config/browser-startup.html#persist-data
|
||||
|
||||
## My component library doesn't work in content scripts!
|
||||
|
||||
Component libraries place their CSS in the document's `<head>` by default. When using `createShadowRoot`, your UI is isolated from the document's styles because it's inside a ShadowRoot.
|
||||
This is usually caused by one of two things (or both) when using `createShadowRootUi`:
|
||||
|
||||
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:
|
||||
1. Styles are added outside the `ShadowRoot`
|
||||
|
||||
- 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/)
|
||||
:::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.
|
||||
|
||||
> If your library isn't listed above, try searching it's docs/issues for "shadow root", "shadow dom", or "css container".
|
||||
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:
|
||||
|
||||
`createShadowRoot` 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:
|
||||
- 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/)
|
||||
|
||||
```tsx
|
||||
import { StyleProvider } from '@ant-design/cssinjs'; // [!code ++]
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
> 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.
|
||||
|
||||
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();
|
||||
},
|
||||
});
|
||||
```
|
||||
Here's an example of configuring Antd's styles:
|
||||
|
||||
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:
|
||||
```tsx
|
||||
import { StyleProvider } from '@ant-design/cssinjs';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
|
||||
```ts
|
||||
import 'vuetify/styles'; // <-- This line imports the CSS, just like importing a .css file
|
||||
import { createVuetify } from 'vuetify';
|
||||
```
|
||||
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;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
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?
|
||||
|
||||
Not yet, but we're working on it. For now, https://wxt.dev hosts pre-aggregated and pre-formatted knowledge files containing all the docs from this website:
|
||||
|
||||
- Index listing available knowledge files: 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!
|
||||
|
||||
@@ -18,7 +18,7 @@ pnpm dlx wxt@latest init example-wxt --template vanilla
|
||||
In general, you'll need to:
|
||||
|
||||
 <input type="checkbox" /> Install `wxt`<br />
|
||||
 <input type="checkbox" /> [Extend `.wxt/tsconfig.json`](/guide/essentials/config/typescript.html#typescript-configuration) in your project's `tsconfig.json`<br />
|
||||
 <input type="checkbox" /> [Extend `.wxt/tsconfig.json`](/guide/essentials/config/typescript#typescript-configuration) in your project's `tsconfig.json`<br />
|
||||
 <input type="checkbox" /> Update/create `package.json` scripts to use `wxt` (don't forget about `postinstall`)<br />
|
||||
 <input type="checkbox" /> Move entrypoints into `entrypoints/` directory<br />
|
||||
 <input type="checkbox" /> Move assets into either the `assets/` or `public/` directories<br />
|
||||
@@ -26,7 +26,7 @@ In general, you'll need to:
|
||||
 <input type="checkbox" /> Convert custom import syntax to be compatible with Vite<br />
|
||||
 <input type="checkbox" /> Add a default export to JS entrypoints (`defineBackground`, `defineContentScript`, or `defineUnlistedScript`)<br />
|
||||
 <input type="checkbox" /> Use the `browser` global instead of `chrome`<br />
|
||||
 <input type="checkbox" /> Compare final `manifest.json` files, making sure permissions and host permissions are unchanged<br/>
|
||||
 <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,7 +48,25 @@ 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 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.
|
||||
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 → WXT](https://github.com/aklinker1/github-better-line-counts/commit/39d766d2ba86866efefc2e9004af554ee434e2a8)
|
||||
|
||||
### `vite-plugin-web-extension`
|
||||
|
||||
@@ -60,4 +78,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 `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.
|
||||
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.
|
||||
|
||||
@@ -109,27 +109,6 @@ You have two options:
|
||||
```
|
||||
2. Rename your `modules` directory to something else.
|
||||
|
||||
## v0.18.0 → v0.18.5
|
||||
|
||||
> When this version was released, it was not considered a breaking change... but it should have been.
|
||||
|
||||
### New `modules/` Directory
|
||||
|
||||
WXT now recognizes the `modules/` directory as a folder containing [WXT modules](/guide/essentials/wxt-modules).
|
||||
|
||||
If you already have `<srcDir>/modules` or `<srcDir>/Modules` directory, `wxt prepare` and other commands will fail.
|
||||
|
||||
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
|
||||
export default defineConfig({
|
||||
modulesDir: 'wxt-modules', // defaults to "modules"
|
||||
});
|
||||
```
|
||||
2. Rename your `modules` directory to something else.
|
||||
|
||||
## v0.17.0 → v0.18.0
|
||||
|
||||
### Automatic MV3 `host_permissions` to MV2 `permissions`
|
||||
|
||||
+9
-3
@@ -6,7 +6,7 @@ title: Next-gen Web Extension Framework
|
||||
hero:
|
||||
name: WXT
|
||||
text: Next-gen Web Extension Framework
|
||||
tagline: An open source tool that makes Chrome Extension development faster than ever before.
|
||||
tagline: An open source tool that makes web extension development faster than ever before.
|
||||
image:
|
||||
src: /hero-logo.svg
|
||||
alt: WXT
|
||||
@@ -73,9 +73,15 @@ features:
|
||||
linkText: Read docs
|
||||
---
|
||||
|
||||
## Sponsors
|
||||
|
||||
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).
|
||||
|
||||
<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-wide.svg"></a>
|
||||
|
||||
## Put <span style="color: var(--vp-c-brand-1)">Developer Experience</span> First
|
||||
|
||||
WXT simplifies the chrome extension development process by providing tools for zipping and publishing, the best-in-class dev mode, an opinionated project structure, and more. Iterate faster, develop features not build scripts, and use everything the JS ecosystem has to offer.
|
||||
WXT simplifies the web extension development process by providing tools for zipping and publishing, the best-in-class dev mode, an opinionated project structure, and more. Iterate faster, develop features not build scripts, and use everything the JS ecosystem has to offer.
|
||||
|
||||
<div style="margin: auto; width: 100%; max-width: 900px; text-align: center">
|
||||
<video src="https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94" controls></video>
|
||||
@@ -87,7 +93,7 @@ WXT simplifies the chrome extension development process by providing tools for z
|
||||
|
||||
## Who's Using WXT?
|
||||
|
||||
Battle tested and ready for production. Explore chrome extensions made with WXT.
|
||||
Battle tested and ready for production. Explore web extensions made with WXT.
|
||||
|
||||
<ClientOnly>
|
||||
<UsingWxtSection />
|
||||
|
||||
@@ -20,7 +20,7 @@ Set Framerate 50
|
||||
# Terminal theme with WXT brand colors (which was taken from the website)
|
||||
# Based on the standard charmbracelet/vhs theme:
|
||||
# https://github.com/charmbracelet/vhs/blob/88e634f4a10bbe305b6aea9a12b4d8dc3dd7f31c/style.go#L7-L28
|
||||
Set Theme {"background": "#1a1b1a", "foreground": "#dddddd", "black": "#282a2e", "brightBlack": "#4d4d4d", "red": "#D74E6F", "brightRed": "#FE5F86", "green": "#67d45e", "brightGreen": "#67d45e", "yellow": "#D3E561", "brightYellow": "#EBFF71", "blue": "#8056FF", "brightBlue": "#9B79FF", "magenta": "#ED61D7", "brightMagenta": "#FF7AEA", "cyan": "#04D7D7", "brightCyan": "#00FEFE", "white": "#bfbfbf", "brightWhite": "#e6e6e6", "indigo": "#5B56E0"}
|
||||
Set Theme {"background": "#161618", "foreground": "#dddddd", "black": "#282a2e", "brightBlack": "#4d4d4d", "red": "#D74E6F", "brightRed": "#FE5F86", "green": "#67d45e", "brightGreen": "#67d45e", "yellow": "#D3E561", "brightYellow": "#EBFF71", "blue": "#8056FF", "brightBlue": "#9B79FF", "magenta": "#ED61D7", "brightMagenta": "#FF7AEA", "cyan": "#04D7D7", "brightCyan": "#00FEFE", "white": "#bfbfbf", "brightWhite": "#e6e6e6", "indigo": "#5B56E0"}
|
||||
Set FontSize 32
|
||||
|
||||
# Terminal settings
|
||||
|
||||
+9
-2
@@ -2,11 +2,18 @@
|
||||
"$schema": "https://typedoc.org/schema.json",
|
||||
"entryPointStrategy": "packages",
|
||||
"entryPoints": ["../packages/wxt"],
|
||||
"plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme"],
|
||||
"plugin": [
|
||||
"typedoc-plugin-markdown",
|
||||
"typedoc-vitepress-theme",
|
||||
"typedoc-plugin-frontmatter"
|
||||
],
|
||||
"out": "./api/reference",
|
||||
"githubPages": false,
|
||||
"excludePrivate": true,
|
||||
"excludeProtected": true,
|
||||
"excludeInternal": true,
|
||||
"readme": "none"
|
||||
"readme": "none",
|
||||
"frontmatterGlobals": {
|
||||
"editLink": false
|
||||
}
|
||||
}
|
||||
|
||||
+26
-22
@@ -4,7 +4,7 @@
|
||||
"engines": {
|
||||
"node": ">=18.20.3"
|
||||
},
|
||||
"packageManager": "pnpm@9.13.0",
|
||||
"packageManager": "pnpm@10.5.2",
|
||||
"scripts": {
|
||||
"check": "check && pnpm -r --sequential run check",
|
||||
"test": "pnpm -r --sequential run test run",
|
||||
@@ -17,27 +17,31 @@
|
||||
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.4",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@vitest/coverage-v8": "^2.1.4",
|
||||
"changelogen": "^0.5.7",
|
||||
"consola": "^3.2.3",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fs-extra": "^11.2.0",
|
||||
"lint-staged": "^15.2.10",
|
||||
"markdown-it-footnote": "^4.0.0",
|
||||
"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-markdown": "4.0.0-next.23",
|
||||
"typedoc-vitepress-theme": "1.0.0-next.3",
|
||||
"typescript": "^5.6.3",
|
||||
"vitepress": "^1.5.0",
|
||||
"vitest-mock-extended": "^2.0.2",
|
||||
"vue": "^3.5.12",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@commitlint/config-conventional": "catalog:",
|
||||
"@commitlint/types": "catalog:",
|
||||
"@types/fs-extra": "catalog:",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"changelogen": "catalog:",
|
||||
"consola": "catalog:",
|
||||
"fast-glob": "catalog:",
|
||||
"fs-extra": "catalog:",
|
||||
"lint-staged": "catalog:",
|
||||
"markdown-it-footnote": "catalog:",
|
||||
"nano-spawn": "catalog:",
|
||||
"prettier": "catalog:",
|
||||
"simple-git-hooks": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
"typedoc": "catalog:",
|
||||
"typedoc-plugin-frontmatter": "catalog:",
|
||||
"typedoc-plugin-markdown": "catalog:",
|
||||
"typedoc-vitepress-theme": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitepress": "catalog:",
|
||||
"vitepress-knowledge": "catalog:",
|
||||
"vitest-mock-extended": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
# 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);
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
import { defineAppConfig } from 'wxt/sandbox';
|
||||
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',
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
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,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
<!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" />
|
||||
 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>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { analytics } from '#analytics';
|
||||
|
||||
declare const enabledCheckbox: HTMLInputElement;
|
||||
|
||||
analytics.autoTrack(document);
|
||||
|
||||
enabledCheckbox.oninput = () => {
|
||||
void analytics.setEnabled(enabledCheckbox.checked);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
import '#analytics';
|
||||
|
||||
export default () => {};
|
||||
@@ -0,0 +1,288 @@
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import type {
|
||||
Analytics,
|
||||
AnalyticsConfig,
|
||||
AnalyticsPageViewEvent,
|
||||
AnalyticsStorageItem,
|
||||
AnalyticsTrackEvent,
|
||||
BaseAnalyticsEvent,
|
||||
AnalyticsEventMetadata,
|
||||
AnalyticsProvider,
|
||||
} from './types';
|
||||
|
||||
const ANALYTICS_PORT = '@wxt-dev/analytics';
|
||||
|
||||
export function createAnalytics(config?: AnalyticsConfig): Analytics {
|
||||
if (typeof chrome === 'undefined' || !chrome?.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 = chrome.runtime.getPlatformInfo();
|
||||
const userAgent = UAParser();
|
||||
let userId = Promise.resolve(userIdStorage.getValue()).then(
|
||||
(id) => id ?? globalThis.crypto.randomUUID(),
|
||||
);
|
||||
let userProperties = userPropertiesStorage.getValue();
|
||||
const manifest = chrome.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
|
||||
chrome.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 = chrome.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 chrome.storage.local.get(key))[key] ?? defaultValue,
|
||||
setValue: (newValue) => chrome.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);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import 'wxt';
|
||||
import 'wxt/sandbox';
|
||||
import {
|
||||
addAlias,
|
||||
addViteConfig,
|
||||
addWxtPlugin,
|
||||
defineWxtModule,
|
||||
} from 'wxt/modules';
|
||||
import { relative, resolve } from 'node:path';
|
||||
import type { AnalyticsConfig } from './types';
|
||||
|
||||
declare module 'wxt/sandbox' {
|
||||
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 'wxt/client';`,
|
||||
``,
|
||||
`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'],
|
||||
},
|
||||
}));
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
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,
|
||||
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),
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,70 @@
|
||||
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>;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
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>;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "@wxt-dev/analytics",
|
||||
"version": "0.4.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.19.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@types/chrome": "catalog:",
|
||||
"@types/ua-parser-js": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"ua-parser-js": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"],
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"#analytics": ["./.wxt/analytics/index.ts"]
|
||||
},
|
||||
"types": ["chrome"]
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
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',
|
||||
},
|
||||
});
|
||||
@@ -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.2.0",
|
||||
"sharp": "^0.33.5"
|
||||
"defu": "catalog:",
|
||||
"fs-extra": "catalog:",
|
||||
"sharp": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,6 +354,7 @@ You'll need to configure it the extension so it knows where your localization fi
|
||||
# You can check available language ids here: https://code.visualstudio.com/docs/languages/identifiers
|
||||
languageIds:
|
||||
- typescript
|
||||
- typescriptreact
|
||||
|
||||
# Look for t("...")
|
||||
usageMatchRegex:
|
||||
|
||||
+13
-12
@@ -19,15 +19,16 @@
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"scripts": {
|
||||
"build": "buildc -- unbuild",
|
||||
"check": "buildc --deps-only -- check",
|
||||
"test": "buildc --deps-only -- vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"chokidar": "^3.6.0",
|
||||
"confbox": "^0.1.8",
|
||||
"fast-glob": "^3.3.2"
|
||||
"chokidar": "catalog:",
|
||||
"confbox": "catalog:",
|
||||
"fast-glob": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.7"
|
||||
@@ -38,15 +39,15 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/chrome": "^0.0.280",
|
||||
"@types/node": "^20.17.6",
|
||||
"oxlint": "^0.11.1",
|
||||
"publint": "^0.2.12",
|
||||
"typescript": "^5.6.3",
|
||||
"unbuild": "^2.0.0",
|
||||
"vitest": "^2.1.4",
|
||||
"vitest-plugin-random-seed": "^1.1.0",
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@types/chrome": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vitest-plugin-random-seed": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## v1.1.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.2...module-react-v1.1.3)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Use react 19 in `@wxt-dev/module-react` and `templates/react` ([#1285](https://github.com/wxt-dev/wxt/pull/1285))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v1.1.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.1...module-react-v1.1.2)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineBuildConfig } from 'unbuild';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export default defineBuildConfig({
|
||||
rootDir: 'modules',
|
||||
rootDir: resolve(__dirname, 'modules'),
|
||||
outDir: resolve(__dirname, 'dist'),
|
||||
entries: [{ input: 'react.ts', name: 'index' }],
|
||||
rollup: {
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"version": "1.1.2",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"version": "1.1.3",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -47,17 +48,17 @@
|
||||
"wxt": ">=0.19.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "^4.3.3"
|
||||
"@vitejs/plugin-react": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"publint": "^0.2.12",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"typescript": "^5.6.3",
|
||||
"unbuild": "^2.0.0",
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineBuildConfig } from 'unbuild';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export default defineBuildConfig({
|
||||
rootDir: 'modules',
|
||||
rootDir: resolve(__dirname, 'modules'),
|
||||
outDir: resolve(__dirname, 'dist'),
|
||||
entries: [{ input: 'solid.ts', name: 'index' }],
|
||||
rollup: {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"version": "1.1.3",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
@@ -47,14 +48,14 @@
|
||||
"wxt": ">=0.19.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"vite-plugin-solid": "^2.10.2"
|
||||
"vite-plugin-solid": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"publint": "^0.2.12",
|
||||
"solid-js": "^1.9.3",
|
||||
"typescript": "^5.6.3",
|
||||
"unbuild": "^2.0.0",
|
||||
"@aklinker1/check": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,45 @@
|
||||
# Changelog
|
||||
|
||||
## v2.0.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.2...module-svelte-v2.0.3)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Upgrade `vite-plugin-svelte` to support Vite 6 ([#1375](https://github.com/wxt-dev/wxt/pull/1375))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Eli ([@lishaduck](http://github.com/lishaduck))
|
||||
|
||||
## v2.0.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.1...module-svelte-v2.0.2)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Svelte production errors and warnings on resolve conditions ([#1283](https://github.com/wxt-dev/wxt/pull/1283))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v2.0.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.0...module-svelte-v2.0.1)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Modify dev `vite.resolve.conditions` to support Vite 6 + Svelte 5 ([#1230](https://github.com/wxt-dev/wxt/pull/1230))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v2.0.0
|
||||
|
||||
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) • [compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v1.0.1...module-svelte-v2.0.0)
|
||||
@@ -51,4 +91,4 @@ To upgrade to svelte 5, just install `svelte@5`.
|
||||
|
||||
## v1.0.0
|
||||
|
||||
Initial release 🎉
|
||||
Initial release 🎉
|
||||
@@ -17,7 +17,8 @@
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"version": "2.0.0",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"version": "2.0.3",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -46,13 +47,13 @@
|
||||
"svelte": ">=5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0"
|
||||
"@sveltejs/vite-plugin-svelte": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"publint": "^0.2.12",
|
||||
"typescript": "^5.6.3",
|
||||
"unbuild": "^2.0.0",
|
||||
"@aklinker1/check": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export default defineWxtModule<SvelteModuleOptions>({
|
||||
setup(wxt, options) {
|
||||
const { vite } = options ?? {};
|
||||
|
||||
addViteConfig(wxt, () => ({
|
||||
addViteConfig(wxt, ({ mode }) => ({
|
||||
plugins: [
|
||||
svelte({
|
||||
// Using a svelte.config.js file causes a segmentation fault when importing the file
|
||||
@@ -21,6 +21,9 @@ export default defineWxtModule<SvelteModuleOptions>({
|
||||
...vite,
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
conditions: ['browser', mode],
|
||||
},
|
||||
}));
|
||||
|
||||
addImportPreset(wxt, 'svelte');
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"version": "1.0.2",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
@@ -45,13 +46,13 @@
|
||||
"wxt": ">=0.19.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.0"
|
||||
"@vitejs/plugin-vue": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"publint": "^0.2.12",
|
||||
"typescript": "^5.6.3",
|
||||
"unbuild": "^2.0.0",
|
||||
"@aklinker1/check": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,63 @@
|
||||
# Changelog
|
||||
|
||||
## v1.1.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.1.0...storage-v1.1.1)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Return early if no migration is needed ([#1502](https://github.com/wxt-dev/wxt/pull/1502))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- 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))
|
||||
- **deps:** Upgrade to Vite 6 and related dependencies ([#1496](https://github.com/wxt-dev/wxt/pull/1496))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- ergou ([@RayGuo-ergou](https://github.com/RayGuo-ergou))
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
- Okinea Dev ([@okineadev](http://github.com/okineadev))
|
||||
|
||||
## v1.1.0
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.0.1...storage-v1.1.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add `storage.clear` ([#1368](https://github.com/wxt-dev/wxt/pull/1368))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Update link ([654a54a](https://github.com/wxt-dev/wxt/commit/654a54a))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Chengxi ([@chengxilo](http://github.com/chengxilo))
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v1.0.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.0.0...storage-v1.0.1)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Use `browser` for mv2 storage ([#1200](https://github.com/wxt-dev/wxt/pull/1200))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Cleanup changelog ([f5b7f7e](https://github.com/wxt-dev/wxt/commit/f5b7f7e))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Init changelog for storage package ([6fc227b](https://github.com/wxt-dev/wxt/commit/6fc227b))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v1.0.0
|
||||
|
||||
Extracted `wxt/storage` into it's own package, `@wxt-dev/storage`!
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/storage",
|
||||
"description": "Web extension storage API provided by WXT, supports all browsers.",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.1",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -23,24 +23,25 @@
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"scripts": {
|
||||
"build": "buildc -- unbuild",
|
||||
"check": "buildc --deps-only -- check",
|
||||
"test": "buildc --deps-only -- vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"async-mutex": "^0.5.0",
|
||||
"dequal": "^2.0.3"
|
||||
"async-mutex": "catalog:",
|
||||
"dequal": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/chrome": "^0.0.268",
|
||||
"@webext-core/fake-browser": "^1.3.1",
|
||||
"oxlint": "^0.9.9",
|
||||
"publint": "^0.2.11",
|
||||
"typescript": "^5.6.2",
|
||||
"unbuild": "^2.0.0",
|
||||
"vitest": "^2.0.0"
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@types/chrome": "catalog:",
|
||||
"@webext-core/fake-browser": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
},
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
@@ -489,6 +489,18 @@ describe('Storage Utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('clear', () => {
|
||||
it('should remove all items', async () => {
|
||||
await fakeBrowser.storage[storageArea].set({
|
||||
one: 1,
|
||||
two: 2,
|
||||
});
|
||||
|
||||
await storage.clear(storageArea);
|
||||
expect(await fakeBrowser.storage[storageArea].get()).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeMeta', () => {
|
||||
it('should remove all metadata', async () => {
|
||||
await fakeBrowser.storage[storageArea].set({ count$: { v: 4 } });
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
import { dequal } from 'dequal/lite';
|
||||
import { Mutex } from 'async-mutex';
|
||||
|
||||
const browser: typeof chrome =
|
||||
// @ts-expect-error
|
||||
globalThis.browser?.runtime?.id == null
|
||||
? globalThis.chrome
|
||||
: // @ts-expect-error
|
||||
globalThis.browser;
|
||||
|
||||
export const storage = createStorage();
|
||||
|
||||
function createStorage(): WxtStorage {
|
||||
@@ -197,7 +204,7 @@ function createStorage(): WxtStorage {
|
||||
const resultsMap: Record<string, any> = {};
|
||||
await Promise.all(
|
||||
Object.entries(areaToDriverMetaKeysMap).map(async ([area, keys]) => {
|
||||
const areaRes = await chrome.storage[area as StorageArea].get(
|
||||
const areaRes = await browser.storage[area as StorageArea].get(
|
||||
keys.map((key) => key.driverMetaKey),
|
||||
);
|
||||
keys.forEach((key) => {
|
||||
@@ -319,6 +326,10 @@ function createStorage(): WxtStorage {
|
||||
}),
|
||||
);
|
||||
},
|
||||
clear: async (base) => {
|
||||
const driver = getDriver(base);
|
||||
await driver.clear();
|
||||
},
|
||||
removeMeta: async (key, properties) => {
|
||||
const { driver, driverKey } = resolveKey(key);
|
||||
await removeMeta(driver, driverKey, properties);
|
||||
@@ -368,6 +379,9 @@ function createStorage(): WxtStorage {
|
||||
`Version downgrade detected (v${currentVersion} -> v${targetVersion}) for "${key}"`,
|
||||
);
|
||||
}
|
||||
if (currentVersion === targetVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug(
|
||||
`[@wxt-dev/storage] Running storage migration for ${key}: v${currentVersion} -> v${targetVersion}`,
|
||||
@@ -383,7 +397,7 @@ function createStorage(): WxtStorage {
|
||||
(await migrations?.[migrateToVersion]?.(migratedValue)) ??
|
||||
migratedValue;
|
||||
} catch (err) {
|
||||
throw Error(`v${migrateToVersion} migration failed for "${key}"`, {
|
||||
throw new MigrationError(key, migrateToVersion, {
|
||||
cause: err,
|
||||
});
|
||||
}
|
||||
@@ -474,7 +488,7 @@ function createStorage(): WxtStorage {
|
||||
|
||||
function createDriver(storageArea: StorageArea): WxtStorageDriver {
|
||||
const getStorageArea = () => {
|
||||
if (chrome.runtime == null) {
|
||||
if (browser.runtime == null) {
|
||||
throw Error(
|
||||
[
|
||||
"'wxt/storage' must be loaded in a web extension environment",
|
||||
@@ -483,15 +497,15 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
|
||||
].join('\n'),
|
||||
);
|
||||
}
|
||||
if (chrome.storage == null) {
|
||||
if (browser.storage == null) {
|
||||
throw Error(
|
||||
"You must add the 'storage' permission to your manifest to use 'wxt/storage'",
|
||||
);
|
||||
}
|
||||
|
||||
const area = chrome.storage[storageArea];
|
||||
const area = browser.storage[storageArea];
|
||||
if (area == null)
|
||||
throw Error(`"chrome.storage.${storageArea}" is undefined`);
|
||||
throw Error(`"browser.storage.${storageArea}" is undefined`);
|
||||
return area;
|
||||
};
|
||||
const watchListeners = new Set<(changes: StorageAreaChanges) => void>();
|
||||
@@ -527,6 +541,9 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
|
||||
removeItems: async (keys) => {
|
||||
await getStorageArea().remove(keys);
|
||||
},
|
||||
clear: async () => {
|
||||
await getStorageArea().clear();
|
||||
},
|
||||
snapshot: async () => {
|
||||
return await getStorageArea().get();
|
||||
},
|
||||
@@ -667,6 +684,12 @@ export interface WxtStorage {
|
||||
| { item: WxtStorageItem<any, any>; options?: RemoveItemOptions }
|
||||
>,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Removes all items from the provided storage area.
|
||||
*/
|
||||
clear(base: StorageArea): Promise<void>;
|
||||
|
||||
/**
|
||||
* Remove the entire metadata for a key, or specific properties by name.
|
||||
*
|
||||
@@ -705,7 +728,7 @@ export interface WxtStorage {
|
||||
/**
|
||||
* Define a storage item with a default value, type, or versioning.
|
||||
*
|
||||
* Read full docs: https://wxt.dev/guide/extension-apis/storage.html#defining-storage-items
|
||||
* Read full docs: https://wxt.dev/storage.html#defining-storage-items
|
||||
*/
|
||||
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
|
||||
key: StorageItemKey,
|
||||
@@ -723,6 +746,7 @@ interface WxtStorageDriver {
|
||||
setItems(values: Array<{ key: string; value: any }>): Promise<void>;
|
||||
removeItem(key: string): Promise<void>;
|
||||
removeItems(keys: string[]): Promise<void>;
|
||||
clear(): Promise<void>;
|
||||
snapshot(): Promise<Record<string, unknown>>;
|
||||
restoreSnapshot(data: Record<string, unknown>): Promise<void>;
|
||||
watch<T>(key: string, cb: WatchCallback<T | null>): Unwatch;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Changelog
|
||||
|
||||
## v1.0.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/unocss-v1.0.0...unocss-v1.0.1)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Respect `configOrPath` for dev server ([#1169](https://github.com/wxt-dev/wxt/pull/1169))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Use full URLs in README so they work on the docs site ([d20793d](https://github.com/wxt-dev/wxt/commit/d20793d))
|
||||
- Fix unocss readme ([#1329](https://github.com/wxt-dev/wxt/pull/1329))
|
||||
|
||||
### 🏡 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))
|
||||
- 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))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
- Ntnyq ([@ntnyq](http://github.com/ntnyq))
|
||||
@@ -38,10 +38,10 @@ The module can be configured via the `unocss` config:
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/unocss'],
|
||||
unocss: {
|
||||
// Will only apply unocss for popup/main.ts
|
||||
entrypoints: ['popup/main.ts'],
|
||||
// Exclude unocss from running for the background
|
||||
excludeEntrypoints: ['background'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Options have JSDocs available in your editor, or you can read them in the source code: [`UnoCSSOptions`](https://github.com/wxt-dev/wxt/blob/main/packages/auto-icons/src/index.ts).
|
||||
Options have JSDocs available in your editor, or you can read them in the source code: [`UnoCSSOptions`](https://github.com/wxt-dev/wxt/blob/main/packages/unocss/src/index.ts).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/unocss",
|
||||
"description": "UnoCSS integration for WXT",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wxt-dev/wxt.git",
|
||||
@@ -40,16 +40,16 @@
|
||||
"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",
|
||||
"unocss": "^0.64.0",
|
||||
"@aklinker1/check": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"unocss": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"fast-glob": "^3.3.2"
|
||||
"defu": "catalog:",
|
||||
"fast-glob": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,20 +18,20 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/i18n": "workspace:*",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chrome": "^0.0.280",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/chrome": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"@wxt-dev/auto-icons": "workspace:*",
|
||||
"@wxt-dev/unocss": "workspace:*",
|
||||
"sass": "^1.80.7",
|
||||
"typescript": "^5.6.3",
|
||||
"unocss": "^0.64.0",
|
||||
"vitest": "^2.1.4",
|
||||
"vitest-plugin-random-seed": "^1.1.0",
|
||||
"sass": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unocss": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vitest-plugin-random-seed": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"buildc": {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'uno.css';
|
||||
import './style.css';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['https://*.duckduckgo.com/*'],
|
||||
cssInjectionMode: 'manifest',
|
||||
|
||||
async main(ctx) {
|
||||
const dynamicUI = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
append: 'after',
|
||||
anchor: 'form[role=search]',
|
||||
onMount: (container) => {
|
||||
const app = document.createElement('div');
|
||||
container.id = 'automount-anchor';
|
||||
app.classList.add('m-4', 'text-center', 'text-red-500');
|
||||
app.textContent = i18n.t('prompt_for_name');
|
||||
container.append(app);
|
||||
return { container, app };
|
||||
},
|
||||
onRemove() {
|
||||
console.log('dynamicUI removed');
|
||||
},
|
||||
});
|
||||
|
||||
const autoMountUi = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
append: 'after',
|
||||
anchor: '#automount-anchor',
|
||||
onMount: (container) => {
|
||||
const app = document.createElement('div');
|
||||
app.id = 'automount-ui';
|
||||
app.classList.add('m-0', 'text-center', 'text-blue-500');
|
||||
app.textContent = `Hello, I'm automount UI.`;
|
||||
container.append(app);
|
||||
},
|
||||
onRemove() {
|
||||
console.log('autoMountUi removed');
|
||||
},
|
||||
});
|
||||
|
||||
autoMountUi.autoMount({
|
||||
onStop: () => {
|
||||
console.log('Auto mount stopped.');
|
||||
},
|
||||
});
|
||||
|
||||
const stopAutoMountButton = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
append: 'last',
|
||||
anchor: 'form[role=search]',
|
||||
onMount: (container) => {
|
||||
const app = document.createElement('button');
|
||||
container.classList.add('flex', 'flex-justify-center');
|
||||
app.classList.add('mt-4', 'p-2');
|
||||
app.textContent = 'Stop auto-mount';
|
||||
app.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
autoMountUi.remove();
|
||||
};
|
||||
container.append(app);
|
||||
return { container, app };
|
||||
},
|
||||
});
|
||||
|
||||
stopAutoMountButton.mount();
|
||||
|
||||
setInterval(() => {
|
||||
if (dynamicUI.mounted) {
|
||||
dynamicUI.remove();
|
||||
} else {
|
||||
dynamicUI.mount();
|
||||
}
|
||||
}, 2000);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
import 'uno.css';
|
||||
import './style.css';
|
||||
import manualStyle from './manual-style.css?inline';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['https://*.duckduckgo.com/*'],
|
||||
cssInjectionMode: 'ui',
|
||||
|
||||
async main(ctx) {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = manualStyle;
|
||||
document.head.append(style);
|
||||
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: 'demo-ui',
|
||||
position: 'inline',
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
body {
|
||||
padding: 2rem;
|
||||
background-color: blanchedalmond;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ export default defineConfig({
|
||||
},
|
||||
example: {
|
||||
a: 'a',
|
||||
// @ts-expect-error: c is not defined, this should error out
|
||||
// @ts-expect-error: c is not defined, this should be a type error, but it should show up in the module
|
||||
c: 'c',
|
||||
},
|
||||
unocss: {
|
||||
|
||||
@@ -1,5 +1,253 @@
|
||||
# Changelog
|
||||
|
||||
## v0.19.29
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.28...wxt-v0.19.29)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Tolerate syntax errors ([#1437](https://github.com/wxt-dev/wxt/pull/1437))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Support `registration: "runtime"` for MV2 ([#1431](https://github.com/wxt-dev/wxt/pull/1431))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Thomas ([@harmonyharmo](http://github.com/harmonyharmo))
|
||||
- Alec Larson ([@aleclarson](http://github.com/aleclarson))
|
||||
|
||||
## v0.19.28
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.27...wxt-v0.19.28)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Ignore `manifest.manifest_version` option and warn about incorrect usage ([#1419](https://github.com/wxt-dev/wxt/pull/1419))
|
||||
- Resolve wxt modules from the root ([#1417](https://github.com/wxt-dev/wxt/pull/1417))
|
||||
- Properly detect and reload changed files when using Vite's `?suffix` imports ([#1432](https://github.com/wxt-dev/wxt/pull/1432))
|
||||
- Regression on variable expansion in dotenv files ([#1449](https://github.com/wxt-dev/wxt/pull/1449))
|
||||
- Allow vite 6.0.9+ ([#1497](https://github.com/wxt-dev/wxt/pull/1497))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Host pre-aggregated LLM knowledge files ([#1355](https://github.com/wxt-dev/wxt/pull/1355))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- 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))
|
||||
- **deps:** Upgrade to Vite 6 and related dependencies ([#1496](https://github.com/wxt-dev/wxt/pull/1496))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
- Stefan Maric ([@stefanmaric](https://github.com/stefanmaric))
|
||||
- Okinea Dev ([@okineadev](http://github.com/okineadev))
|
||||
- 1natsu ([@1natsu172](http://github.com/1natsu172))
|
||||
- Alec Larson ([@aleclarson](http://github.com/aleclarson))
|
||||
|
||||
## v0.19.27
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.26...wxt-v0.19.27)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Allow `vite-node@^3.0.0` ([#1378](https://github.com/wxt-dev/wxt/pull/1378))
|
||||
- Use path triple slash directive for types that are referenced via path ([#1407](https://github.com/wxt-dev/wxt/pull/1407))
|
||||
- Support `publish-browser-extension` v3.0.0 ([ac92d40](https://github.com/wxt-dev/wxt/commit/ac92d40))
|
||||
- Respect `inlineConfig.mode` when setting NODE_ENV ([#1416](https://github.com/wxt-dev/wxt/pull/1416))
|
||||
- Auto-import does not work when using `@vitejs/plugin-vue` alone ([#1412](https://github.com/wxt-dev/wxt/pull/1412))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Jaguar Zhou ([@aiktb](http://github.com/aiktb))
|
||||
- Alec Larson ([@aleclarson](http://github.com/aleclarson))
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
- St3h3n <st3h3n@gmail.com>
|
||||
- Eli ([@lishaduck](http://github.com/lishaduck))
|
||||
|
||||
## v0.19.26
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.25...wxt-v0.19.26)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **context:** Deduplicate `wxt:content-script-started` ([#1364](https://github.com/wxt-dev/wxt/pull/1364))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Deniz Uğur ([@DenizUgur](http://github.com/DenizUgur))
|
||||
|
||||
## v0.19.25
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.24...wxt-v0.19.25)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Drop support for vite 6.0.9+ until websocket fix is finished ([8e2badc](https://github.com/wxt-dev/wxt/commit/8e2badc))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v0.19.24
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.23...wxt-v0.19.24)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **init:** Remove "experimental" from bun package manager ([b150a52](https://github.com/wxt-dev/wxt/commit/b150a52))
|
||||
- **web-ext:** Correctly pass `browserConsole` option to open devtools in Firefox ([#1308](https://github.com/wxt-dev/wxt/pull/1308))
|
||||
- `autoMount` not working if anchor is already present ([#1350](https://github.com/wxt-dev/wxt/pull/1350))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Fix typo in CLI docs ([028c601](https://github.com/wxt-dev/wxt/commit/028c601))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- 1natsu ([@1natsu172](http://github.com/1natsu172))
|
||||
- Paulo Pinto ([@psrpinto](https://github.com/psrpinto))
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v0.19.23
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.22...wxt-v0.19.23)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **hooks:** Move `entrypoints:resolved` before debug logs and add `entrypoints:found` ([#1292](https://github.com/wxt-dev/wxt/pull/1292))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Allow runtime registered content scripts to not have `matches` ([#1306](https://github.com/wxt-dev/wxt/pull/1306))
|
||||
- Properly close readline instance on close ([#1278](https://github.com/wxt-dev/wxt/pull/1278))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Sync READMEs ([27298b7](https://github.com/wxt-dev/wxt/commit/27298b7))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Simplify keyboard shortcuts ([#1284](https://github.com/wxt-dev/wxt/pull/1284))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
- Abhigyan Trips ([@abhigyantrips](http://github.com/abhigyantrips))
|
||||
|
||||
## v0.19.22
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.21...wxt-v0.19.22)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Exclude entire `import.meta.env` object from content script output ([#1267](https://github.com/wxt-dev/wxt/pull/1267))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v0.19.21
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.20...wxt-v0.19.21)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Exclude skipped entrypoints from manifest ([#1265](https://github.com/wxt-dev/wxt/pull/1265))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v0.19.20
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.19...wxt-v0.19.20)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- `autoMount` content script UIs ([#1210](https://github.com/wxt-dev/wxt/pull/1210))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Only show reopen browser shortcut when it can be done ([#1263](https://github.com/wxt-dev/wxt/pull/1263))
|
||||
- Import entrypoint improvements ([#1207](https://github.com/wxt-dev/wxt/pull/1207))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
- 1natsu ([@1natsu172](http://github.com/1natsu172))
|
||||
|
||||
## v0.19.19
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.18...wxt-v0.19.19)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Keyboard shortcut to reopen the browser without restarting the dev command ([#1211](https://github.com/wxt-dev/wxt/pull/1211))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Prevent changing dev server port when reloading config ([#1241](https://github.com/wxt-dev/wxt/pull/1241))
|
||||
- Ensure content scripts are registered immediately in dev mode ([#1253](https://github.com/wxt-dev/wxt/pull/1253))
|
||||
- Exclude skipped entrypoints from Firefox sources zip ([#1238](https://github.com/wxt-dev/wxt/pull/1238))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Nishu ([@nishu-murmu](http://github.com/nishu-murmu))
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v0.19.18
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.17...wxt-v0.19.18)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Correct `dev.reloadCommand` restriction to consider only commands with suggested keys ([#1226](https://github.com/wxt-dev/wxt/pull/1226))
|
||||
|
||||
### 🌊 Types
|
||||
|
||||
- Add overloads to `ContentScriptContext#addEventListener` for different targets ([#1245](https://github.com/wxt-dev/wxt/pull/1245))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Refactor `findEntrypoints` to return all entrypoints with `skipped` set properly ([#1244](https://github.com/wxt-dev/wxt/pull/1244))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
- Jaguar Zhou ([@aiktb](http://github.com/aiktb))
|
||||
|
||||
## v0.19.17
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.16...wxt-v0.19.17)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- New `server:created`, `server:started`, and `server:closed` hooks ([#1179](https://github.com/wxt-dev/wxt/pull/1179))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Re-initialize WXT modules correctly during development ([#1176](https://github.com/wxt-dev/wxt/pull/1176))
|
||||
- ESLint config being generated when ESLint is not installed. ([#1198](https://github.com/wxt-dev/wxt/pull/1198))
|
||||
- Update `vite` dependency range to support v6 ([#1215](https://github.com/wxt-dev/wxt/pull/1215))
|
||||
- Automatically convert MV3 `content_security_policy` to MV2 ([#1168](https://github.com/wxt-dev/wxt/pull/1168))
|
||||
- Correctly remove child elements with integrated UI remove ([#1219](https://github.com/wxt-dev/wxt/pull/1219))
|
||||
- Make content script `matches` optional ([#1220](https://github.com/wxt-dev/wxt/pull/1220))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Fix analyze typo in type ([#1187](https://github.com/wxt-dev/wxt/pull/1187))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
- 1natsu ([@1natsu172](http://github.com/1natsu172))
|
||||
- Nishu ([@nishu-murmu](https://github.com/nishu-murmu))
|
||||
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
|
||||
|
||||
## v0.19.16
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.15...wxt-v0.19.16)
|
||||
|
||||
@@ -76,6 +76,12 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
|
||||
- 📏 Bundle analysis
|
||||
- ⬇️ Download and bundle remote URL imports
|
||||
|
||||
## Sponsors
|
||||
|
||||
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).
|
||||
|
||||
<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" -->
|
||||
|
||||
@@ -52,10 +52,10 @@ describe('Auto Imports', () => {
|
||||
----------------------------------------
|
||||
// Generated by wxt
|
||||
/// <reference types="wxt/vite-builder-env" />
|
||||
/// <reference types="./types/paths.d.ts" />
|
||||
/// <reference types="./types/i18n.d.ts" />
|
||||
/// <reference types="./types/globals.d.ts" />
|
||||
/// <reference types="./types/imports.d.ts" />
|
||||
/// <reference path="./types/paths.d.ts" />
|
||||
/// <reference path="./types/i18n.d.ts" />
|
||||
/// <reference path="./types/globals.d.ts" />
|
||||
/// <reference path="./types/imports.d.ts" />
|
||||
"
|
||||
`);
|
||||
});
|
||||
@@ -91,9 +91,9 @@ describe('Auto Imports', () => {
|
||||
----------------------------------------
|
||||
// Generated by wxt
|
||||
/// <reference types="wxt/vite-builder-env" />
|
||||
/// <reference types="./types/paths.d.ts" />
|
||||
/// <reference types="./types/i18n.d.ts" />
|
||||
/// <reference types="./types/globals.d.ts" />
|
||||
/// <reference path="./types/paths.d.ts" />
|
||||
/// <reference path="./types/i18n.d.ts" />
|
||||
/// <reference path="./types/globals.d.ts" />
|
||||
"
|
||||
`,
|
||||
);
|
||||
@@ -152,6 +152,40 @@ describe('Auto Imports', () => {
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('"enabled: false" should NOT output an ESlint config file', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', `<html></html>`);
|
||||
|
||||
await project.prepare({
|
||||
imports: {
|
||||
eslintrc: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(await project.fileExists('.wxt/eslint-auto-imports.mjs')).toBe(
|
||||
false,
|
||||
);
|
||||
expect(await project.fileExists('.wxt/eslintrc-auto-import.json')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should NOT output an ESlint config file by default', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', `<html></html>`);
|
||||
|
||||
await project.prepare();
|
||||
|
||||
expect(await project.fileExists('.wxt/eslint-auto-imports.mjs')).toBe(
|
||||
false,
|
||||
);
|
||||
expect(await project.fileExists('.wxt/eslintrc-auto-import.json')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow customizing the output', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile('entrypoints/popup.html', `<html></html>`);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
|
||||
describe('Dev Mode', () => {
|
||||
it('should not change ports when restarting the server', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
'export default defineBackground(() => {})',
|
||||
);
|
||||
|
||||
const server = await project.startServer({
|
||||
runner: {
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
const initialPort = server.port;
|
||||
await server.restart();
|
||||
const finalPort = server.port;
|
||||
await server.stop();
|
||||
|
||||
expect(finalPort).toBe(initialPort);
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,7 @@ const hooks: WxtHooks = {
|
||||
'build:done': vi.fn(),
|
||||
'build:manifestGenerated': vi.fn(),
|
||||
'build:publicAssets': vi.fn(),
|
||||
'entrypoints:found': vi.fn(),
|
||||
'entrypoints:resolved': vi.fn(),
|
||||
'entrypoints:grouped': vi.fn(),
|
||||
'vite:build:extendConfig': vi.fn(),
|
||||
@@ -21,6 +22,9 @@ const hooks: WxtHooks = {
|
||||
'zip:sources:start': vi.fn(),
|
||||
'zip:sources:done': vi.fn(),
|
||||
'zip:done': vi.fn(),
|
||||
'server:created': vi.fn(),
|
||||
'server:started': vi.fn(),
|
||||
'server:closed': vi.fn(),
|
||||
};
|
||||
|
||||
function expectHooksToBeCalled(
|
||||
@@ -57,6 +61,7 @@ describe('Hooks', () => {
|
||||
'build:done': false,
|
||||
'build:publicAssets': false,
|
||||
'build:manifestGenerated': false,
|
||||
'entrypoints:found': true,
|
||||
'entrypoints:grouped': false,
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': false,
|
||||
@@ -67,6 +72,9 @@ describe('Hooks', () => {
|
||||
'zip:sources:start': false,
|
||||
'zip:sources:done': false,
|
||||
'zip:done': false,
|
||||
'server:created': false,
|
||||
'server:started': false,
|
||||
'server:closed': false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,6 +93,7 @@ describe('Hooks', () => {
|
||||
'build:done': true,
|
||||
'build:publicAssets': true,
|
||||
'build:manifestGenerated': true,
|
||||
'entrypoints:found': true,
|
||||
'entrypoints:grouped': true,
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': 1,
|
||||
@@ -95,6 +104,9 @@ describe('Hooks', () => {
|
||||
'zip:sources:start': false,
|
||||
'zip:sources:done': false,
|
||||
'zip:done': false,
|
||||
'server:created': false,
|
||||
'server:started': false,
|
||||
'server:closed': false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -113,6 +125,7 @@ describe('Hooks', () => {
|
||||
'build:done': true,
|
||||
'build:publicAssets': true,
|
||||
'build:manifestGenerated': true,
|
||||
'entrypoints:found': true,
|
||||
'entrypoints:grouped': true,
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': 1,
|
||||
@@ -123,6 +136,9 @@ describe('Hooks', () => {
|
||||
'zip:sources:start': false,
|
||||
'zip:sources:done': false,
|
||||
'zip:done': true,
|
||||
'server:created': false,
|
||||
'server:started': false,
|
||||
'server:closed': false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -141,8 +157,9 @@ describe('Hooks', () => {
|
||||
'build:done': true,
|
||||
'build:publicAssets': true,
|
||||
'build:manifestGenerated': true,
|
||||
'entrypoints:found': 2,
|
||||
'entrypoints:grouped': true,
|
||||
'entrypoints:resolved': true,
|
||||
'entrypoints:resolved': 2,
|
||||
'vite:build:extendConfig': 1,
|
||||
'vite:devServer:extendConfig': false,
|
||||
'zip:start': true,
|
||||
@@ -151,6 +168,9 @@ describe('Hooks', () => {
|
||||
'zip:sources:start': true,
|
||||
'zip:sources:done': true,
|
||||
'zip:done': true,
|
||||
'server:created': false,
|
||||
'server:started': false,
|
||||
'server:closed': false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -164,6 +184,7 @@ describe('Hooks', () => {
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
expect(hooks['server:closed']).not.toBeCalled();
|
||||
await server.stop();
|
||||
|
||||
expectHooksToBeCalled({
|
||||
@@ -175,6 +196,7 @@ describe('Hooks', () => {
|
||||
'build:done': true,
|
||||
'build:publicAssets': true,
|
||||
'build:manifestGenerated': true,
|
||||
'entrypoints:found': true,
|
||||
'entrypoints:grouped': true,
|
||||
'entrypoints:resolved': true,
|
||||
'vite:build:extendConfig': 2,
|
||||
@@ -185,6 +207,9 @@ describe('Hooks', () => {
|
||||
'zip:sources:start': false,
|
||||
'zip:sources:done': false,
|
||||
'zip:done': false,
|
||||
'server:created': 1,
|
||||
'server:started': 1,
|
||||
'server:closed': 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import { normalizePath } from '../../src/core/utils/paths';
|
||||
|
||||
describe('Module Helpers', () => {
|
||||
describe('options', () => {
|
||||
it('should recieve the options defined in wxt.config.ts based on the configKey field', async () => {
|
||||
it('should receive the options defined in wxt.config.ts based on the configKey field', async () => {
|
||||
const options = { key: '123' };
|
||||
const reportOptions = vi.fn();
|
||||
vi.stubGlobal('reportOptions', reportOptions);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user