Compare commits
149 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 868c042511 | |||
| bdb49c0b3d | |||
| cc3c43d1bd | |||
| e47131efc1 | |||
| 82d8024fb4 | |||
| c662a59d85 | |||
| 4e50fb1f92 | |||
| fbae370d1e | |||
| 9c64c9dd61 | |||
| 8f6dba20d2 | |||
| f531fcd5c3 | |||
| ff1720a4c6 | |||
| d62203dff8 | |||
| 99b5076d69 | |||
| 298e7101f7 | |||
| b4ce36b708 | |||
| 5d096a4a92 | |||
| e54df0aca8 | |||
| 2e8baf0161 | |||
| c9dca0222c | |||
| 6044ab73bd | |||
| 660945c792 | |||
| bcb20874a8 | |||
| 83ad0e3ff0 | |||
| aad17c8d26 | |||
| b0ef178c9c | |||
| 0175c430a3 | |||
| 2776587392 | |||
| b978465d7a | |||
| f7989ea1e0 | |||
| 0cf34d3170 | |||
| 4fe04c6f8a | |||
| 1773762d2f | |||
| 9057000e45 | |||
| d540233673 | |||
| 9907290a04 | |||
| d9fb919580 | |||
| 17723d5828 | |||
| c7db2d26b3 | |||
| 259cec9ea8 | |||
| 2ced9c40d3 | |||
| 756efc9311 | |||
| 17fd2ff0ce | |||
| 35833c00f0 | |||
| 300c045187 | |||
| 4c05cc19e5 | |||
| 72dab65a1d | |||
| 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 |
@@ -0,0 +1,10 @@
|
||||
extends:
|
||||
- '@commitlint/config-conventional'
|
||||
rules:
|
||||
subject-case:
|
||||
- 0
|
||||
- always
|
||||
- - sentence-case
|
||||
- start-case
|
||||
- pascal-case
|
||||
- upper-case
|
||||
@@ -1,5 +1,16 @@
|
||||
# See https://git-scm.com/docs/gitattributes#_pattern_format for more about `.gitattributes`.
|
||||
|
||||
# Normalize EOL for all files that Git considers text files
|
||||
* text=auto eol=lf
|
||||
|
||||
# Mark lock files as generated to avoid diffing
|
||||
pnpm-lock.yaml linguist-generated
|
||||
package-lock.json linguist-generated
|
||||
bun.lockb linguist-generated
|
||||
yarn.lock linguist-generated
|
||||
|
||||
# Exclude templates from language statistics
|
||||
templates/**/* linguist-vendored
|
||||
|
||||
# Other generated files
|
||||
packages/browser/src/gen/** linguist-generated
|
||||
|
||||
@@ -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,6 +1,7 @@
|
||||
name: "\U0001F41E Bug report"
|
||||
description: Report an issue with WXT
|
||||
labels: [pending-triage]
|
||||
type: Bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: Feature request
|
||||
about: Suggest an idea for WXT
|
||||
title: ''
|
||||
labels: feature
|
||||
type: Feature
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -16,40 +16,54 @@ on:
|
||||
- module-vue
|
||||
- storage
|
||||
- unocss
|
||||
- webextension-polyfill
|
||||
- wxt
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate
|
||||
uses: './.github/workflows/validate.yml'
|
||||
secrets: inherit
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
needs:
|
||||
- validate
|
||||
steps:
|
||||
- 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:
|
||||
@@ -15,16 +15,28 @@ on:
|
||||
- module-svelte
|
||||
- module-vue
|
||||
- storage
|
||||
- webextension-polyfill
|
||||
- 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 }}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
name: 🔄 Update @wxt-dev/browser
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Every day at midnight
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: 'Sync with @types/chrome'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
installArgs: --ignore-scripts
|
||||
|
||||
- name: Generate Latest Code
|
||||
working-directory: packages/browser
|
||||
run: pnpm gen
|
||||
|
||||
- name: Run Checks
|
||||
working-directory: packages/browser
|
||||
run: pnpm check
|
||||
|
||||
- name: Commit Changes
|
||||
id: commit
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
commit_message: 'fix: Upgrade \`@wxt-dev/browser\` to latest \`@types/chrome\` version'
|
||||
file_pattern: 'packages/browser/**'
|
||||
|
||||
- name: Publish Package
|
||||
if: steps.commit.outputs.changes_detected == 'true'
|
||||
working-directory: packages/browser
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
|
||||
pnpm publish
|
||||
@@ -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@v5
|
||||
|
||||
- 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
|
||||
|
||||
@@ -5,3 +5,4 @@ dist
|
||||
docs/.vitepress/cache
|
||||
pnpm-lock.yaml
|
||||
CHANGELOG.md
|
||||
packages/browser/src/gen
|
||||
|
||||
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" }
|
||||
]
|
||||
}
|
||||
+20
-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
|
||||
|
||||
@@ -166,3 +174,13 @@ 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
|
||||
```
|
||||
|
||||
## Blog Posts
|
||||
|
||||
Anyone is welcome to submit a blog post on https://wxt.dev/blog!
|
||||
|
||||
> [!NOTE]
|
||||
> Before starting on a blog post, please message Aaron on Discord or start a discussion on GitHub to get permission to write about a topic, but most topics are welcome: Major version updates, tutorials, etc.
|
||||
|
||||
- **English only**: Blog posts should be written in English. Unfortunately, our maintainers doesn't have the bandwidth right now to translate our docs, let alone blog posts. Sorry 😓
|
||||
- **AI**: Please only use AI to translate or proof-read your blog post. Don't generate the whole thing... We don't want to publish that.
|
||||
|
||||
+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.
|
||||
-->
|
||||
@@ -0,0 +1,70 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
// @ts-expect-error: Vitepress data-loader magic, this import is correct
|
||||
import { data } from '../loaders/blog.data';
|
||||
import BlogPostPreview from './BlogPostPreview.vue';
|
||||
|
||||
const posts = computed(() =>
|
||||
data
|
||||
.map((post) => ({
|
||||
...post,
|
||||
...post.frontmatter,
|
||||
date: new Date(post.frontmatter.date),
|
||||
}))
|
||||
.sort((a, b) => b.date.getTime() - a.date.getTime()),
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div>
|
||||
<div class="vp-doc">
|
||||
<h1>Blog</h1>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<BlogPostPreview v-for="post of posts" :key="post.url" :post />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.container > div {
|
||||
padding: 32px;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
list-style: none;
|
||||
}
|
||||
ul,
|
||||
li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul li {
|
||||
padding-top: 16px;
|
||||
margin-top: 16px;
|
||||
border-top: 1px solid var(--vp-c-default);
|
||||
}
|
||||
ul li:last-child {
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid var(--vp-c-default);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,76 @@
|
||||
<script lang="ts" setup>
|
||||
import useBlogDate from '../composables/useBlogDate';
|
||||
import { useData } from 'vitepress';
|
||||
|
||||
const { frontmatter } = useData();
|
||||
const date = useBlogDate(() => frontmatter.value.date);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vp-doc">
|
||||
<main class="container-content">
|
||||
<h1 v-html="$frontmatter.title" />
|
||||
<p class="meta-row">
|
||||
<a
|
||||
class="author"
|
||||
v-for="author of $frontmatter.authors"
|
||||
:key="author.github"
|
||||
:href="`https://github.com/${author.github}`"
|
||||
>
|
||||
<img :src="`https://github.com/${author.github}.png?size=96`" />
|
||||
<span>{{ author.name }}</span>
|
||||
</a>
|
||||
<span>•</span>
|
||||
<span>{{ date }}</span>
|
||||
</p>
|
||||
<Content />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
vp-doc {
|
||||
display: flex;
|
||||
}
|
||||
main {
|
||||
max-width: 1080px;
|
||||
padding: 32px;
|
||||
margin: auto;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
main {
|
||||
padding: 64px;
|
||||
}
|
||||
}
|
||||
.meta-row {
|
||||
display: flex;
|
||||
color: var(--vp-c-text-2);
|
||||
gap: 16px;
|
||||
overflow: hidden;
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
.meta-row > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.author {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
}
|
||||
.author img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
.author span {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.author:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts" setup>
|
||||
import useBlogDate from '../composables/useBlogDate';
|
||||
|
||||
const props = defineProps<{
|
||||
post: {
|
||||
title: string;
|
||||
description?: string;
|
||||
date: Date;
|
||||
url: string;
|
||||
authors: Array<{ name: string; github: string }>;
|
||||
};
|
||||
}>();
|
||||
|
||||
const date = useBlogDate(() => props.post.date);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li class="blog-list-item">
|
||||
<a :href="post.url">
|
||||
<div class="vp-doc">
|
||||
<h3 class="title" v-html="post.title" />
|
||||
<p class="description" v-html="post.description" />
|
||||
<p class="meta">
|
||||
{{ post.authors.map((author) => author.name).join(', ') }}
|
||||
•
|
||||
{{ date }}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
li > a > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 -16px;
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
li > a > div:hover {
|
||||
background: var(--vp-c-default);
|
||||
}
|
||||
li .title {
|
||||
color: var(--vp-c-text);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
li .description {
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
li .meta {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
</style>
|
||||
@@ -62,6 +62,14 @@ const chromeExtensionIds = [
|
||||
'hjjkgbibknbahijglkffklflidncplkn', // Show IP – Live View of Website IPs for Developers
|
||||
'ilbikcehnpkmldojkcmlldkoelofnbde', // Strong Password Generator
|
||||
'ocllfkhcdopiafndigclebelbecaiocp', // ZenGram: Mindful Instagram, Your Way
|
||||
'odffpjnpocjfcaclnenaaaddghkgijdb', // Blync: Preview Links, Selection Search, AI Assistant
|
||||
'kofbbilhmnkcmibjbioafflgmpkbnmme', // HTML to Markdown - Convert webpages to markdown
|
||||
'boecmgggeigllcdocgioijmleimjbfkg', // Walmart WFS Profit Calculator
|
||||
'dlnjcbkmomenmieechnmgglgcljhoepd', // Youtube Live Chat Fullscreen
|
||||
'keiealdacakpnbbljlmhfgcebmaadieg', // Python Code Runner
|
||||
'hafcajcllbjnoolpfngclfmmgpikdhlm', // Monochromate
|
||||
'bmoggiinmnodjphdjnmpcnlleamkfedj', // AliasVault - Open-Source Password & (Email) Alias Manager
|
||||
'hlnhhamckimoaiekbglafiebkfimhapb', // SnapThePrice: AI-Powered Real-time Lowest Price Finder
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { computed, toValue, MaybeRefOrGetter } from 'vue';
|
||||
|
||||
const MONTH_FORMATTER = new Intl.DateTimeFormat(
|
||||
globalThis?.navigator?.language,
|
||||
{
|
||||
month: 'long',
|
||||
},
|
||||
);
|
||||
|
||||
export default function (date: MaybeRefOrGetter<Date | string>) {
|
||||
return computed(() => {
|
||||
const d = new Date(toValue(date));
|
||||
return `${MONTH_FORMATTER.format(d)} ${d.getDate()}, ${d.getFullYear()}`;
|
||||
});
|
||||
}
|
||||
@@ -15,14 +15,25 @@ import { version as autoIconsVersion } from '../../packages/auto-icons/package.j
|
||||
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';
|
||||
import {
|
||||
groupIconMdPlugin,
|
||||
groupIconVitePlugin,
|
||||
localIconLoader,
|
||||
} from 'vitepress-plugin-group-icons';
|
||||
import { Feed } from 'feed';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const origin = 'https://wxt.dev';
|
||||
|
||||
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 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 ogUrl = origin;
|
||||
const ogImage = `${origin}/social-preview.png`;
|
||||
|
||||
const otherPackages = {
|
||||
analytics: analyticsVersion,
|
||||
@@ -34,15 +45,59 @@ const otherPackages = {
|
||||
|
||||
// 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,
|
||||
vite: {
|
||||
clearScreen: false,
|
||||
plugins: [
|
||||
groupIconVitePlugin({
|
||||
customIcon: {
|
||||
'wxt.config.ts': localIconLoader(
|
||||
import.meta.url,
|
||||
'../public/logo.svg',
|
||||
),
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
lastUpdated: true,
|
||||
sitemap: {
|
||||
hostname: 'https://wxt.dev',
|
||||
hostname: origin,
|
||||
},
|
||||
|
||||
async buildEnd(site) {
|
||||
// Only construct the RSS document for production builds
|
||||
const { default: blogDataLoader } = await import('./loaders/blog.data');
|
||||
const posts = await blogDataLoader.load();
|
||||
const feed = new Feed({
|
||||
copyright: 'MIT',
|
||||
id: 'wxt',
|
||||
title: 'WXT Blog',
|
||||
link: `${origin}/blog`,
|
||||
});
|
||||
posts.forEach((post) => {
|
||||
feed.addItem({
|
||||
date: post.frontmatter.date,
|
||||
link: new URL(post.url, origin).href,
|
||||
title: post.frontmatter.title,
|
||||
description: post.frontmatter.description,
|
||||
});
|
||||
});
|
||||
console.log('rss.xml:');
|
||||
console.log(feed.rss2());
|
||||
await writeFile(join(site.outDir, 'rss.xml'), feed.rss2(), 'utf8');
|
||||
},
|
||||
|
||||
head: [
|
||||
@@ -61,6 +116,7 @@ export default defineConfig({
|
||||
markdown: {
|
||||
config: (md) => {
|
||||
md.use(footnote);
|
||||
md.use(groupIconMdPlugin);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -98,6 +154,7 @@ export default defineConfig({
|
||||
navItem('Guide', '/guide/installation'),
|
||||
navItem('Examples', '/examples'),
|
||||
navItem('API', '/api/reference/wxt'),
|
||||
navItem('Blog', '/blog'),
|
||||
navItem(`v${wxtVersion}`, [
|
||||
navItem('wxt', [
|
||||
navItem(`v${wxtVersion}`, '/'),
|
||||
@@ -161,6 +218,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'),
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { createContentLoader } from 'vitepress';
|
||||
|
||||
export default createContentLoader('blog/*.md');
|
||||
@@ -3,14 +3,18 @@ import Icon from '../components/Icon.vue';
|
||||
import EntrypointPatterns from '../components/EntrypointPatterns.vue';
|
||||
import UsingWxtSection from '../components/UsingWxtSection.vue';
|
||||
import ExampleSearch from '../components/ExampleSearch.vue';
|
||||
import BlogLayout from '../components/BlogLayout.vue';
|
||||
import './custom.css';
|
||||
import 'virtual:group-icons.css';
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
enhanceApp(ctx) {
|
||||
ctx.app.component('Icon', Icon);
|
||||
ctx.app.component('EntrypointPatterns', EntrypointPatterns);
|
||||
ctx.app.component('UsingWxtSection', UsingWxtSection);
|
||||
ctx.app.component('ExampleSearch', ExampleSearch);
|
||||
ctx.app
|
||||
.component('Icon', Icon)
|
||||
.component('EntrypointPatterns', EntrypointPatterns)
|
||||
.component('UsingWxtSection', UsingWxtSection)
|
||||
.component('ExampleSearch', ExampleSearch)
|
||||
.component('blog', BlogLayout);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,9 @@
|
||||
---
|
||||
layout: page
|
||||
---
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BlogHome from './.vitepress/components/BlogHome.vue';
|
||||
</script>
|
||||
|
||||
<BlogHome />
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
layout: blog
|
||||
title: Real World Messaging
|
||||
description: |
|
||||
The extension messaging APIs are difficult to learn. Let's go beyond the simple examples from Chrome and Firefox's documentation to build our own simple messaging system from scratch.
|
||||
authors:
|
||||
- name: Aaron Klinker
|
||||
github: aklinker1
|
||||
date: 2024-10-20T04:54:23.601Z
|
||||
---
|
||||
|
||||
Test content **bold** _italic_
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
layout: blog
|
||||
title: Introducing <code>#imports</code>
|
||||
description: Learn how WXT's new <code>#imports</code> module works and how to use it.
|
||||
authors:
|
||||
- name: Aaron Klinker
|
||||
github: aklinker1
|
||||
date: 2024-12-06T14:39:00.000Z
|
||||
---
|
||||
|
||||
WXT v0.20 introduced a new way of manually importing its APIs: **the `#imports` module**. This module was introduced to simplify import statements and provide more visibility into all the APIs WXT provides.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
import { browser } from 'wxt/browser'; // [!code --]
|
||||
import { createShadowRootUi } from 'wxt/utils/content-script-ui/shadow-root'; // [!code --]
|
||||
import { defineContentScript } from 'wxt/utils/define-content-script'; // [!code --]
|
||||
import { injectScript } from 'wxt/utils/inject-script'; // [!code --]
|
||||
import { // [!code ++]
|
||||
browser, createShadowRootUi, defineContentScript, injectScript // [!code ++]
|
||||
} from '#imports'; // [!code ++]
|
||||
```
|
||||
|
||||
The `#imports` module is considered a "virtual module", because the file doesn't actually exist. At build-time, imports are split into individual statements for each API:
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [What you write]
|
||||
import { defineContentScript, injectScript } from '#imports';
|
||||
```
|
||||
|
||||
```ts [What the bundler sees]
|
||||
import { defineContentScript } from 'wxt/utils/define-content-script';
|
||||
import { injectScript } from 'wxt/utils/inject-script';
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Think of `#imports` as a convenient way to access all of WXT's APIs from one place, without impacting performance or bundle size.
|
||||
|
||||
This enables better tree-shaking compared to v0.19 and below.
|
||||
|
||||
:::tip Need to lookup the full import path of an API?
|
||||
Open up your project's `.wxt/types/imports-module.d.ts` file.
|
||||
:::
|
||||
|
||||
## Mocking
|
||||
|
||||
When writing tests, you might need to mock APIs from the `#imports` module. While mocking these APIs is very easy, it may not be immediately clear how to accomplish it.
|
||||
|
||||
Let's look at an example using Vitest. When [configured with `wxt/testing`](/guide/essentials/unit-testing#vitest), Vitest sees the same transformed code as the bundler. That means to mock an API from `#imports`, you need to call `vi.mock` with the real import path, not `#imports`:
|
||||
|
||||
```ts
|
||||
import { injectScript } from '#imports';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
vi.mock('wxt/utils/inject-script')
|
||||
const injectScriptMock = vi.mocked(injectScript);
|
||||
|
||||
injectScriptMock.mockReturnValueOnce(...);
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
You don't have to use `#imports` if you don't like - you can continue importing APIs from their submodules. However, using `#imports` is the recommended approach moving forwards.
|
||||
|
||||
- As more APIs are added, you won't have to memorize additional import paths.
|
||||
- If breaking changes are made to import paths in future major versions, `#imports` won't break.
|
||||
|
||||
Happy Coding 😄
|
||||
|
||||
> P.S. Yes, this is exactly how [Nuxt's `#imports`](https://nuxt.com/docs/guide/concepts/auto-imports#explicit-imports) works! We use the exact same library, [`unimport`](https://github.com/unjs/unimport).
|
||||
@@ -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,11 +26,27 @@ 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
|
||||
|
||||
Files inside `<srcDir>/public/` are copied into the output folder as-is, without being processed by WXT's bundler.
|
||||
Files inside `<rootDir>/public/` are copied into the output folder as-is, without being processed by WXT's bundler.
|
||||
|
||||
Here's how you access them:
|
||||
|
||||
@@ -52,6 +69,20 @@ img.src = imageUrl;
|
||||
}
|
||||
```
|
||||
|
||||
```vue [Vue]
|
||||
<template>
|
||||
<img src="/image.png" />
|
||||
</template>
|
||||
```
|
||||
|
||||
```jsx [JSX]
|
||||
<img src="/image.png" />
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::warning
|
||||
Assets in the `public/` directory are **_not_** accessible in content scripts by default. To use a public asset in a content script, you must add it to your manifest's [`web_accessible_resources` array](/api/reference/wxt/type-aliases/UserManifest#web-accessible-resources).
|
||||
:::
|
||||
|
||||
## Inside Content Scripts
|
||||
@@ -62,8 +93,7 @@ But, inside content scripts, the hostname is whatever the tab is set to. So if y
|
||||
|
||||
To fix this, you need to convert the image to a full URL using `browser.runtime.getURL`:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
```ts [entrypoints/content.ts]
|
||||
import iconUrl from '/icon/128.png';
|
||||
|
||||
export default defineContentScript({
|
||||
@@ -108,13 +138,12 @@ Run `wxt build`, and you should see the WASM file copied into your `.output/chro
|
||||
|
||||
Next, since this is in a content script and we'll be fetching the WASM file over the network to load it, we need to add the file to the `web_accessible_resources`:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
web_accessible_resources: [
|
||||
{
|
||||
// We'll use this matches in the cotent script as well
|
||||
// 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'],
|
||||
@@ -126,8 +155,7 @@ export default defineConfig({
|
||||
|
||||
And finally, we need to load and initialize the `.wasm` file inside the content script to use it:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
```ts [entrypoints/content.ts]
|
||||
import initWasm, { parseSync } from '@oxc-parser/wasm';
|
||||
|
||||
export default defineContentScript({
|
||||
|
||||
@@ -11,19 +11,7 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
By default, WXT automatically setups 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`
|
||||
- [`defineBackground`](/api/reference/wxt/sandbox/functions/defineBackground) from `wxt/sandbox`
|
||||
- [`defineUnlistedScript`](/api/reference/wxt/sandbox/functions/defineUnlistedScript) from `wxt/sandbox`
|
||||
- [`createIntegratedUi`](/api/reference/wxt/client/functions/createIntegratedUi) from `wxt/client`
|
||||
- [`createShadowRootUi`](/api/reference/wxt/client/functions/createShadowRootUi) from `wxt/client`
|
||||
- [`createIframeUi`](/api/reference/wxt/client/functions/createIframeUi) from `wxt/client`
|
||||
- [`fakeBrowser`](/api/reference/wxt/testing/variables/fakeBrowser) from `wxt/testing`
|
||||
- And more!
|
||||
|
||||
WXT also adds some project directories as auto-import sources automatically:
|
||||
By default, WXT automatically sets up auto-imports for all of it's own APIs and some of your project directories:
|
||||
|
||||
- `<srcDir>/components/*`
|
||||
- `<srcDir>/composables/*`
|
||||
@@ -32,6 +20,8 @@ WXT also adds some project directories as auto-import sources automatically:
|
||||
|
||||
All named and default exports from files in these directories are available everywhere else in your project without having to import them.
|
||||
|
||||
To see the complete list of auto-imported APIs, run [`wxt prepare`](/api/cli/wxt-prepare) and look at your project's `.wxt/types/imports-module.d.ts` file.
|
||||
|
||||
## TypeScript
|
||||
|
||||
For TypeScript and your editor to recognize auto-imported variables, you need to run the [`wxt prepare` command](/api/cli/wxt-prepare).
|
||||
@@ -110,3 +100,19 @@ export default defineConfig({
|
||||
imports: false, // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
## Explicit Imports (`#imports`)
|
||||
|
||||
You can manually import all of WXT's APIs via the `#imports` module:
|
||||
|
||||
```ts
|
||||
import {
|
||||
createShadowRootUi,
|
||||
ContentScriptContext,
|
||||
MatchPattern,
|
||||
} from '#imports';
|
||||
```
|
||||
|
||||
To learn more about how the `#imports` module works, read the [related blog post](/blog/2024-12-06-using-imports-module).
|
||||
|
||||
If you've disabled auto-imports, you should still use `#imports` to import all of WXT's APIs from a single place.
|
||||
|
||||
@@ -4,9 +4,9 @@ outline: deep
|
||||
|
||||
# Browser Startup
|
||||
|
||||
> See the [API Reference](/api/reference/wxt/interfaces/ExtensionRunnerConfig) for a full list of config.
|
||||
> See the [API Reference](/api/reference/wxt/interfaces/WebExtConfig) for a full list of config.
|
||||
|
||||
During development WXT uses [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext) to automatically open a browser window with your extension installed.
|
||||
During development, WXT uses [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext) to automatically open a browser window with your extension installed.
|
||||
|
||||
## Config Files
|
||||
|
||||
@@ -15,9 +15,9 @@ You can configure browser startup in 3 places:
|
||||
1. `<rootDir>/web-ext.config.ts`: Ignored from version control, this file lets you configure your own options for a specific project without affecting other developers
|
||||
|
||||
```ts
|
||||
import { defineRunnerConfig } from 'wxt';
|
||||
import { defineWebExtConfig } from 'wxt';
|
||||
|
||||
export default defineRunnerConfig({
|
||||
export default defineWebExtConfig({
|
||||
// ...
|
||||
});
|
||||
```
|
||||
@@ -32,7 +32,7 @@ You can configure browser startup in 3 places:
|
||||
To set or customize the browser opened during development:
|
||||
|
||||
```ts
|
||||
export default defineRunnerConfig({
|
||||
export default defineWebExtConfig({
|
||||
binaries: {
|
||||
chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome
|
||||
firefox: 'firefoxdeveloperedition', // Use Firefox Developer Edition instead of regular Firefox
|
||||
@@ -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.
|
||||
@@ -52,7 +54,7 @@ To persist data, set the `--user-data-dir` flag:
|
||||
:::code-group
|
||||
|
||||
```ts [Mac/Linux]
|
||||
export default defineRunnerConfig({
|
||||
export default defineWebExtConfig({
|
||||
chromiumArgs: ['--user-data-dir=./.wxt/chrome-data'],
|
||||
});
|
||||
```
|
||||
@@ -60,7 +62,7 @@ export default defineRunnerConfig({
|
||||
```ts [Windows]
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export default defineRunnerConfig({
|
||||
export default defineWebExtConfig({
|
||||
// On Windows, the path must be absolute
|
||||
chromiumProfile: resolve('.wxt/chrome-data'),
|
||||
keepProfileChanges: true,
|
||||
@@ -72,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
|
||||
@@ -80,7 +82,7 @@ You can use any directory you'd like for `--user-data-dir`, the examples above c
|
||||
If you prefer to load the extension into your browser manually, you can disable the auto-open behavior:
|
||||
|
||||
```ts
|
||||
export default defineRunnerConfig({
|
||||
export default defineWebExtConfig({
|
||||
disabled: true,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -17,63 +17,3 @@ If you're running into errors while importing entrypoints, run `wxt prepare --de
|
||||
:::
|
||||
|
||||
Once the environment has been polyfilled and your code pre-processed, it's up the entrypoint loader to import your code, extracting the options from the default export.
|
||||
|
||||
There are two options for loading your entrypoints:
|
||||
|
||||
1. `vite-node` - default as of `v0.19.0`
|
||||
2. `jiti` (**DEPRECATED, will be removed in `v0.20.0`**) - Default before `v0.19.0`
|
||||
|
||||
## vite-node
|
||||
|
||||
Since 0.19.0, WXT uses `vite-node`, the same tool that powers Vitest and Nuxt, to import your entrypoint files. It re-uses the same vite config used when building your extension, making it the most stable entrypoint loader.
|
||||
|
||||
## jiti
|
||||
|
||||
To enable `jiti`:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
entrypointLoader: 'jiti',
|
||||
});
|
||||
```
|
||||
|
||||
This is the original method WXT used to import TS files. However, because it doesn't support vite plugins like `vite-node`, it does one additional pre-processing step: It removes **_ALL_** imports from your code.
|
||||
|
||||
That means you cannot use imported variables outside the `main` function in JS entrypoints, like for content script `matches` or other options:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
import { GOOGLE_MATCHES } from '~/utils/match-patterns';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: GOOGLE_MATCHES,
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```
|
||||
$ wxt build
|
||||
wxt build
|
||||
|
||||
WXT 0.14.1
|
||||
ℹ Building chrome-mv3 for production with Vite 5.0.5
|
||||
✖ Command failed after 360 ms
|
||||
|
||||
[8:55:54 AM] ERROR entrypoints/content.ts: Cannot use imported variable "GOOGLE_MATCHES" before main function.
|
||||
```
|
||||
|
||||
Usually, this error occurs when you try to extract options into a shared file or when running code outside the `main` function. To fix the example from above, use literal values when defining an entrypoint instead of importing them:
|
||||
|
||||
```ts
|
||||
import { GOOGLE_MATCHES } from '~/utils/match-patterns'; // [!code --]
|
||||
|
||||
export default defineContentScript({
|
||||
matches: GOOGLE_MATCHES, // [!code --]
|
||||
matches: ['*//*.google.com/*'], // [!code ++]
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -6,8 +6,7 @@ WXT includes a system that lets you hook into the build process and make changes
|
||||
|
||||
The easiest way to add a hook is via the `wxt.config.ts`. Here's an example hook that modifies the `manifest.json` file before it is written to the output directory:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
hooks: {
|
||||
'build:manifestGenerated': (wxt, manifest) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -176,7 +183,7 @@ export default defineConfig({
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['storage', 'tabs'],
|
||||
host_permissions: ['https://www.google.com/*'],
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -216,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" />
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
Define runtime configuration in a single place, `<srcDir>/app.config.ts`:
|
||||
|
||||
```ts
|
||||
import { defineAppConfig } from 'wxt/sandbox';
|
||||
import { defineAppConfig } from '#imports';
|
||||
|
||||
// Define types for your config
|
||||
declare module 'wxt/sandbox' {
|
||||
declare module 'wxt/utils/define-app-config' {
|
||||
export interface WxtAppConfig {
|
||||
theme?: 'light' | 'dark';
|
||||
}
|
||||
@@ -26,7 +26,7 @@ This file is committed to the repo, so don't put any secrets here. Instead, use
|
||||
To access runtime config, WXT provides the `useAppConfig` function:
|
||||
|
||||
```ts
|
||||
import { useAppConfig } from 'wxt/sandbox';
|
||||
import { useAppConfig } from '#imports';
|
||||
|
||||
console.log(useAppConfig()); // { theme: "dark" }
|
||||
```
|
||||
@@ -36,7 +36,7 @@ console.log(useAppConfig()); // { theme: "dark" }
|
||||
You can use environment variables in the `app.config.ts` file.
|
||||
|
||||
```ts
|
||||
declare module 'wxt/sandbox' {
|
||||
declare module 'wxt/utils/define-app-config' {
|
||||
export interface WxtAppConfig {
|
||||
apiKey?: string;
|
||||
skipWelcome: boolean;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,8 +12,7 @@ In most cases, you shouldn't change Vite's build settings. WXT provides sensible
|
||||
|
||||
You can change Vite's config via the `wxt.config.ts` file:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
@@ -28,8 +27,7 @@ export default defineConfig({
|
||||
|
||||
To add a plugin, install the NPM package and add it to the Vite config:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
import { defineConfig } from 'wxt';
|
||||
import VueRouter from 'unplugin-vue-router/vite';
|
||||
|
||||
@@ -47,8 +45,7 @@ export default defineConfig({
|
||||
:::warning
|
||||
Due to the way WXT orchestrates Vite builds, some plugins may not work as expected. For example, `vite-plugin-remove-console` normally only runs when you build for production (`vite build`). However, WXT uses a combination of dev server and builds during development, so you need to manually tell it when to run:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
import { defineConfig } from 'wxt';
|
||||
import removeConsole from 'vite-plugin-remove-console';
|
||||
|
||||
|
||||
@@ -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({
|
||||
@@ -74,20 +76,19 @@ To create a standalone content script that only includes a CSS file:
|
||||
|
||||
1. Create the CSS file: `entrypoints/example.content.css`
|
||||
2. Use the `build:manifestGenerated` hook to add the content script to the manifest:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
hooks: {
|
||||
"build:manifestGenerated": (wxt, manifest) => {
|
||||
'build:manifestGenerated': (wxt, manifest) => {
|
||||
manifest.content_scripts ??= [];
|
||||
manifest.content_scripts.push({
|
||||
// Build extension once to see where your CSS get's written to
|
||||
css: ["content-scripts/example.css"],
|
||||
matches: ["*://*/*"]
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
css: ['content-scripts/example.css'],
|
||||
matches: ['*://*/*'],
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## UI
|
||||
@@ -198,6 +199,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 +210,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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -255,13 +256,13 @@ export default defineContentScript({
|
||||
|
||||
:::
|
||||
|
||||
See the [API Reference](/api/reference/wxt/client/functions/createIntegratedUi) for the complete list of options.
|
||||
See the [API Reference](/api/reference/wxt/utils/content-script-ui/integrated/functions/createIntegratedUi) for the complete list of options.
|
||||
|
||||
### Shadow Root
|
||||
|
||||
Often in web extensions, you don't want your content script's CSS affecting the page, or vise-versa. The [`ShadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot) API is ideal for this.
|
||||
|
||||
WXT's [`createShadowRootUi`](/api/reference/wxt/client/functions/createShadowRootUi) abstracts all the `ShadowRoot` setup away, making it easy to create UIs whose styles are isolated from the page. It also supports an optional `isolateEvents` parameter to further isolate user interactions.
|
||||
WXT's [`createShadowRootUi`](/api/reference/wxt/utils/content-script-ui/shadow-root/functions/createShadowRootUi) abstracts all the `ShadowRoot` setup away, making it easy to create UIs whose styles are isolated from the page. It also supports an optional `isolateEvents` parameter to further isolate user interactions.
|
||||
|
||||
To use `createShadowRootUi`, follow these steps:
|
||||
|
||||
@@ -379,6 +380,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 +395,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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -444,7 +445,7 @@ export default defineContentScript({
|
||||
|
||||
:::
|
||||
|
||||
See the [API Reference](/api/reference/wxt/client/functions/createShadowRootUi) for the complete list of options.
|
||||
See the [API Reference](/api/reference/wxt/utils/content-script-ui/shadow-root/functions/createShadowRootUi) for the complete list of options.
|
||||
|
||||
Full examples:
|
||||
|
||||
@@ -455,7 +456,7 @@ Full examples:
|
||||
|
||||
If you don't need to run your UI in the same frame as the content script, you can use an IFrame to host your UI instead. Since an IFrame just hosts an HTML page, **_HMR is supported_**.
|
||||
|
||||
WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/client/functions/createIframeUi), which simplifies setting up the IFrame.
|
||||
WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/utils/content-script-ui/iframe/functions/createIframeUi), which simplifies setting up the IFrame.
|
||||
|
||||
1. Create an HTML page that will be loaded into your IFrame:
|
||||
```html
|
||||
@@ -473,8 +474,7 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/client/fun
|
||||
</html>
|
||||
```
|
||||
1. Add the page to the manifest's `web_accessible_resources`:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
web_accessible_resources: [
|
||||
@@ -510,7 +510,7 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/client/fun
|
||||
});
|
||||
```
|
||||
|
||||
See the [API Reference](/api/reference/wxt/client/functions/createIframeUi) for the complete list of options.
|
||||
See the [API Reference](/api/reference/wxt/utils/content-script-ui/iframe/functions/createIframeUi) for the complete list of options.
|
||||
|
||||
## Isolated World vs Main World
|
||||
|
||||
@@ -566,6 +566,20 @@ export default defineContentScript({
|
||||
});
|
||||
```
|
||||
|
||||
```json
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
// ...
|
||||
web_accessible_resources: [
|
||||
{
|
||||
resources: ["example-main-world.js"],
|
||||
matches: ["*://*/*"],
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
`injectScript` works by creating a `script` element on the page pointing to your script. This loads the script into the page's context so it runs in the main world.
|
||||
|
||||
`injectScript` returns a promise, that when resolved, means the script has been evaluated by the browser and you can start communicating with it.
|
||||
@@ -607,7 +621,7 @@ export default defineContentScript({
|
||||
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.
|
||||
See the [API Reference](/api/reference/wxt/utils/content-script-ui/types/interfaces/ContentScriptUi#automount) for the complete list of options.
|
||||
|
||||
## Dealing with SPAs
|
||||
|
||||
|
||||
@@ -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,12 +72,11 @@ 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:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
```ts [entrypoints/content.ts]
|
||||
export default defineContentScript({
|
||||
matches: ['*://*.wxt.dev/*'],
|
||||
main() {
|
||||
@@ -86,6 +85,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.
|
||||
@@ -542,4 +552,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,7 +14,7 @@ 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({
|
||||
@@ -27,12 +25,14 @@ export default defineBackground({
|
||||
});
|
||||
```
|
||||
|
||||
This will change the output format to ESM, enable code-spliting between your background script and HTML pages, and set `"type": "module"` in your manifest.
|
||||
|
||||
:::warning
|
||||
Only MV3 supports ESM background scripts/service workers. When targeting MV2, the `type` option is ignored and the background is always bundled into a single file as IIFE.
|
||||
:::
|
||||
|
||||
## Content Scripts
|
||||
|
||||
WXT does not yet include built-in support for 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.
|
||||
|
||||
@@ -4,62 +4,47 @@
|
||||
|
||||
Different browsers provide different global variables for accessing the extension APIs (chrome provides `chrome`, firefox provides `browser`, etc).
|
||||
|
||||
WXT simplifies this - always use `browser`:
|
||||
WXT merges these two into a unified API accessed through the `browser` variable.
|
||||
|
||||
```ts
|
||||
import { browser } from 'wxt/browser';
|
||||
|
||||
browser.action.onClicked.addListener(() => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Other than that, refer to Chrome and Mozilla's documentation for how to use specific APIs. Everything a normal extension can do, WXT can do as well, just via `browser` instead of `chrome`.
|
||||
:::tip
|
||||
With auto-imports enabled, you don't even need to import this variable from `wxt/browser`!
|
||||
:::
|
||||
|
||||
## Webextension Polyfill
|
||||
The `browser` variable WXT provides is a simple export of the `browser` or `chrome` globals provided by the browser at runtime:
|
||||
|
||||
> Since `v0.1.0`
|
||||
<<< @/../packages/browser/src/index.mjs#snippet
|
||||
|
||||
By default, WXT uses the [`webextension-polyfill` by Mozilla](https://www.npmjs.com/package/webextension-polyfill) to make the extension API consistent between browsers.
|
||||
This means you can use the promise-style API for both MV2 and MV3, and it will work across all browsers (Chromium, Firefox, Safari, etc).
|
||||
|
||||
To access types, you should import the relevant namespace from `wxt/browser`:
|
||||
## Accessing Types
|
||||
|
||||
All types can be accessed via WXT's `Browser` namespace:
|
||||
|
||||
```ts
|
||||
import { Runtime } from 'wxt/browser';
|
||||
import { Browser } from 'wxt/browser';
|
||||
|
||||
function handleMessage(message: any, sender: Runtime.Sender) {
|
||||
function handleMessage(message: any, sender: Browser.runtime.MessageSender) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Disabling the polyfill
|
||||
## Using `webextension-polyfill`
|
||||
|
||||
> Since `v0.19.0`
|
||||
If you want to use the `webextension-polyfill` when importing `browser`, you can do so by installing the `@wxt-dev/webextension-polyfill` package.
|
||||
|
||||
After the release of MV3 and Chrome's official deprecation of MV2 in June 2024, the polyfill isn't really doing anything useful anymore.
|
||||
|
||||
You can disable it with a single line:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
extensionApi: 'chrome',
|
||||
});
|
||||
```
|
||||
|
||||
This will change `wxt/browser` to simply export the `browser` or `chrome` globals based on browser at runtime:
|
||||
|
||||
<<< @/../packages/wxt/src/browser/chrome.ts#snippet
|
||||
|
||||
Accessing types is a little different with the polyfill disabled. They do not need to be imported; they're available on the `browser` object itself:
|
||||
|
||||
```ts
|
||||
function handleMessage(message: any, sender: browser.runtime.Sender) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
See it's [Installation Guide](https://github.com/wxt-dev/wxt/blob/main/packages/webextension-polyfill/README.md) to get started.
|
||||
|
||||
## Feature Detection
|
||||
|
||||
Depending on the manifest version and browser, some APIs are not available at runtime. If an API is not available, it will be `undefined`.
|
||||
Depending on the manifest version, browser, and permissions, some APIs are not available at runtime. If an API is not available, it will be `undefined`.
|
||||
|
||||
:::warning
|
||||
Types will not help you here. The types WXT provides for `browser` assume all APIs exist. You are responsible for knowing whether an API is available or not.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -47,8 +47,7 @@ Here's a brief summary of each of these files and directories:
|
||||
|
||||
Many developers like having a `src/` directory to separate source code from configuration files. You can enable it inside the `wxt.config.ts` file:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
srcDir: 'src',
|
||||
});
|
||||
@@ -59,24 +58,24 @@ After enabling it, your project structure should look like this:
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
📂 {rootDir}/
|
||||
📁 .output/
|
||||
📁 .wxt/
|
||||
📂 src/
|
||||
📁 assets/
|
||||
📁 components/
|
||||
📁 composables/
|
||||
📁 entrypoints/
|
||||
📁 hooks/
|
||||
📁 .output/
|
||||
📁 .wxt/
|
||||
📁 modules/
|
||||
📁 public/
|
||||
📁 utils/
|
||||
📄 app.config.ts
|
||||
📄 .env
|
||||
📄 .env.publish
|
||||
📄 package.json
|
||||
📄 tsconfig.json
|
||||
📄 web-ext.config.ts
|
||||
📄 wxt.config.ts
|
||||
📂 src/
|
||||
📁 assets/
|
||||
📁 components/
|
||||
📁 composables/
|
||||
📁 entrypoints/
|
||||
📁 hooks/
|
||||
📁 utils/
|
||||
📄 app.config.ts
|
||||
📄 .env
|
||||
📄 .env.publish
|
||||
📄 package.json
|
||||
📄 tsconfig.json
|
||||
📄 web-ext.config.ts
|
||||
📄 wxt.config.ts
|
||||
```
|
||||
|
||||
## Customizing Other Directories
|
||||
@@ -84,17 +83,16 @@ After enabling it, your project structure should look like this:
|
||||
You can configure the following directories:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
// Relative to project root
|
||||
srcDir: "src", // default: "."
|
||||
modulesDir: "wxt-modules", // default: "modules"
|
||||
outDir: "dist", // default: ".output"
|
||||
publicDir: "static", // default: "public"
|
||||
|
||||
// Relative to srcDir
|
||||
entrypointsDir: "entries", // default: "entrypoints"
|
||||
modulesDir: "wxt-modules", // default: "modules"
|
||||
publicDir: "static", // default: "public"
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -133,8 +133,7 @@ When running `wxt zip -b firefox`, WXT will zip both your extension and sources.
|
||||
|
||||
To customize which files are zipped, add the `zip` option to your config file.
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
@@ -184,8 +183,7 @@ See Issue [#377](https://github.com/wxt-dev/wxt/issues/377) for more details.
|
||||
|
||||
If you use private packages and you don't want to provide your auth token to the Firefox team during the review process, you can use `zip.downloadPackages` to download any private packages and include them in the zip.
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
zip: {
|
||||
downloadPackages: [
|
||||
|
||||
@@ -6,7 +6,7 @@ You can use the vanilla APIs (see docs above), use [WXT's built-in storage API](
|
||||
|
||||
## Alternatives
|
||||
|
||||
1. [`wxt/storage`](/storage) (recommended): WXT ships with its own wrapper around the vanilla storage APIs that simplifies common use cases
|
||||
1. [`wxt/utils/storage`](/storage) (recommended): WXT ships with its own wrapper around the vanilla storage APIs that simplifies common use cases
|
||||
|
||||
2. DIY: If you're migrating to WXT and already have a storage wrapper, keep using it. In the future, if you want to delete that code, you can use one of these alternatives, but there's no reason to replace working code during a migration.
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ Here are real projects with unit testing setup. Look at the code and tests to se
|
||||
|
||||
### Example Tests
|
||||
|
||||
This example demonstrates that you don't have to mock `browser.storage` (used by `wxt/storage`) in tests - [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/fake-browser/installation) implements storage in-memory so it behaves like it would in a real extension!
|
||||
This example demonstrates that you don't have to mock `browser.storage` (used by `wxt/utils/storage`) in tests - [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/fake-browser/installation) implements storage in-memory so it behaves like it would in a real extension!
|
||||
|
||||
```ts
|
||||
import { describe, it, expect } from 'vitest';
|
||||
@@ -71,6 +71,34 @@ describe('isLoggedIn', () => {
|
||||
});
|
||||
```
|
||||
|
||||
### Mocking WXT APIs
|
||||
|
||||
First, you need to understand how the `#imports` module works. When WXT (and vitest) sees this import during a preprocessing step, the import is replaced with multiple imports pointing to their "real" import path.
|
||||
|
||||
For example, this is what your write in your source code:
|
||||
|
||||
```ts
|
||||
// What you write
|
||||
import { injectScript, createShadowRootUi } from '#imports';
|
||||
```
|
||||
|
||||
But Vitest sees this:
|
||||
|
||||
```ts
|
||||
import { injectScript } from 'wxt/browser';
|
||||
import { createShadowRootUi } from 'wxt/utils/content-script-ui/shadow-root';
|
||||
```
|
||||
|
||||
So in this case, if you wanted to mock `injectScript`, you need to pass in `"wxt/utils/inject-script"`, not `"#imports"`.
|
||||
|
||||
```ts
|
||||
vi.mock("wxt/utils/inject-script", () => ({
|
||||
injectScript: ...
|
||||
}))
|
||||
```
|
||||
|
||||
Refer to your project's `.wxt/types/imports-module.d.ts` file to lookup real import paths for `#imports`. If the file doesn't exist, run [`wxt prepare`](/guide/essentials/config/typescript).
|
||||
|
||||
## Other Testing Frameworks
|
||||
|
||||
To use a different framework, you will likely have to disable auto-imports, setup import aliases, manually mock the extension APIs, and setup the test environment to support all of WXT's features that you use.
|
||||
|
||||
@@ -9,8 +9,7 @@ WXT provides a "module system" that let's you run code at different steps in the
|
||||
There are two ways to add a module to your project:
|
||||
|
||||
1. **NPM**: install an NPM package, like [`@wxt-dev/auto-icons`](https://www.npmjs.com/package/@wxt-dev/auto-icons) and add it to your config:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/auto-icons'],
|
||||
});
|
||||
@@ -18,7 +17,7 @@ There are two ways to add a module to your project:
|
||||
> Searching for ["wxt module"](https://www.npmjs.com/search?q=wxt%20module) on NPM is a good way to find published WXT modules.
|
||||
2. **Local**: add a file to your project's `modules/` directory:
|
||||
```
|
||||
<srcDir>/
|
||||
<rootDir>/
|
||||
modules/
|
||||
my-module.ts
|
||||
```
|
||||
@@ -112,12 +111,12 @@ export default defineWxtModule<AnalyticModuleOptions>({
|
||||
|
||||
```ts
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
import 'wxt/sandbox';
|
||||
import 'wxt/utils/define-app-config';
|
||||
|
||||
export interface MyModuleRuntimeOptions {
|
||||
// Add your runtime options here...
|
||||
}
|
||||
declare module 'wxt/sandbox' {
|
||||
declare module 'wxt/utils/define-app-config' {
|
||||
export interface WxtAppConfig {
|
||||
myModule: MyModuleOptions;
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
|
||||
```
|
||||
:::
|
||||
4. Add scripts to your `package.json`:
|
||||
```json
|
||||
```json [package.json]
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "wxt", // [!code ++]
|
||||
@@ -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!
|
||||
|
||||
@@ -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))_
|
||||
|
||||
+138
-36
@@ -28,47 +28,149 @@ 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 `createShadowRootUi`, 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:
|
||||
|
||||
`createShadowRootUi` provides it's own `<head>` element inside the shadow root, so that were you should tell the library to add the CSS. Here's an example with Ant Design:
|
||||
- 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?
|
||||
|
||||
Yes! There's a "Ask AI" button in the bottom right of the page, try it out! Or visit https://knowledge.wxt.dev/ for a fullscreen experience.
|
||||
|
||||
Additionally, if you want to train your own model or provide context to your editor, you can use the LLM knowledge files hosted by the site:
|
||||
|
||||
https://wxt.dev/knowledge/index.json
|
||||
|
||||
You don't need to crawl the entire website, these files already contain all the relevant docs for training a LLM on WXT. But feel free to crawl it and generate your own files if you want!
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Upgrading WXT
|
||||
|
||||
## Overview
|
||||
@@ -12,6 +16,243 @@ Listed below are all the breaking changes you should address when upgrading to a
|
||||
|
||||
Currently, WXT is in pre-release. This means changes to the second digit, `v0.X`, are considered major and have breaking changes. Once v1 is released, only major version bumps will have breaking changes.
|
||||
|
||||
## v0.19.0 → v0.20.0
|
||||
|
||||
v0.20 is a big release! There are lots of breaking changes because this version is intended to be a release candidate for v1.0. If all goes well, v1.0 will be released with no additional breaking changes.
|
||||
|
||||
:::tip
|
||||
Read through all the changes once before making any code changes.
|
||||
:::
|
||||
|
||||
### `webextension-polyfill` Removed
|
||||
|
||||
WXT's `browser` no longer uses the `webextension-polyfill`!
|
||||
|
||||
:::details Why?
|
||||
See https://github.com/wxt-dev/wxt/issues/784
|
||||
:::
|
||||
|
||||
To upgrade, you have two options:
|
||||
|
||||
1. **Stop using the polyfill** - No changes necessary, though you may want to do some manual testing to make sure everything continues to work. None of the early testers of this feature reported any runtime issues once they stopped using the polyfill.
|
||||
- If you're already using `extensionApi: "chrome"`, then you don't need to test anything! You're already using the same `browser` object v0.20 provides by default.
|
||||
2. **Continue using the polyfill** - If you want to keep using the polyfill, you can! One less thing to worry about during this upgrade.
|
||||
- Install `webextension-polyfill` and WXT's [new polyfill module](https://www.npmjs.com/package/@wxt-dev/webextension-polyfill):
|
||||
```sh
|
||||
pnpm i webextension-polyfill @wxt-dev/webextension-polyfill
|
||||
```
|
||||
- Add the WXT module to your config:
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/webextension-polyfill'],
|
||||
});
|
||||
```
|
||||
|
||||
The new `browser` object (and types) is backed by WXT's new package: [`@wxt-dev/browser`](https://www.npmjs.com/package/@wxt-dev/browser). This package continues WXT's mission of providing useful packages for the whole community. Just like [`@wxt-dev/storage`](https://www.npmjs.com/package/@wxt-dev/storage), [`@wxt-dev/i18n`](https://www.npmjs.com/package/@wxt-dev/i18n), [`@wxt-dev/analytics`](https://www.npmjs.com/package/@wxt-dev/analytics), it is designed to be easy to use in any web extension project, not just those using WXT, and provides a consistent API across all browsers and manifest versions.
|
||||
|
||||
### `extensionApi` Config Removed
|
||||
|
||||
The `extensionApi` config has been removed. Before, this config provided a way to opt into using the new `browser` object prior to v0.20.0.
|
||||
|
||||
Remove it from your `wxt.config.ts` file if present:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
extensionApi: 'chrome', // [!code --]
|
||||
});
|
||||
```
|
||||
|
||||
### Extension API Type Changes
|
||||
|
||||
With the new `browser` introduced in v0.20, how you access types has changed. WXT now provides types based on `@types/chrome` instead of `@types/webextension-polyfill`.
|
||||
|
||||
These types are more up-to-date with MV3 APIs, contain less bugs, are better organized, and don't have any auto-generated names.
|
||||
|
||||
To access types, use the new `Browser` namespace from `wxt/browser`:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
import type { Runtime } from 'wxt/browser'; // [!code --]
|
||||
import type { Browser } from 'wxt/browser'; // [!code ++]
|
||||
|
||||
function getMessageSenderUrl(sender: Runtime.MessageSender): string { // [!code --]
|
||||
function getMessageSenderUrl(sender: Browser.runtime.MessageSender): string { // [!code ++]
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
> If you use auto-imports, `Browser` will be available without manually importing it.
|
||||
|
||||
Not all type names will be the same as what `@types/webextension-polyfill` provides. You'll have to find the new type names by looking at the types of the `browser.*` API's you use.
|
||||
|
||||
### `public/` and `modules/` Directories Moved
|
||||
|
||||
The default location for the `public/` and `modules/` directories have changed to better align with standards set by other frameworks (Nuxt, Next, Astro, etc). Now, each path is relative to the project's **root directory**, not the src directory.
|
||||
|
||||
- If you follow the default folder structure, you don't need to make any changes.
|
||||
- If you set a custom `srcDir`, you have two options:
|
||||
1. Move the your `public/` and `modules/` directories to the project root:
|
||||
```diff
|
||||
<root>/
|
||||
+ modules/
|
||||
+ public/
|
||||
src/
|
||||
components/
|
||||
entrypoints/
|
||||
- modules/
|
||||
- public/
|
||||
utils/
|
||||
wxt.config.ts
|
||||
```
|
||||
2. Keep the folders in the same place and update your project config:
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
srcDir: 'src',
|
||||
publicDir: 'src/public', // [!code ++]
|
||||
modulesDir: 'src/modules', // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
### Import Path Changes and `#imports`
|
||||
|
||||
The APIs exported by `wxt/sandbox`, `wxt/client`, or `wxt/storage` have moved to individual exports under the `wxt/utils/*` path.
|
||||
|
||||
:::details Why?
|
||||
As WXT grows and more utilities are added, any helper with side-effects will not be tree-shaken out of your final bundle.
|
||||
|
||||
This can cause problems because not every API used by these side-effects is available in every type of entrypoint. Some APIs can only be used in the background, sandboxed pages can't use any extension API, etc. This was leading to JS throwing errors in the top-level scope, preventing your code from running.
|
||||
|
||||
Splitting each util into it's own module solves this problem, making sure you're only importing APIs and side-effects into entrypoints they can run in.
|
||||
:::
|
||||
|
||||
Refer to the updated [API Reference](/api/reference/) to see the list of new import paths.
|
||||
|
||||
However, you don't need to memorize or learn the new import paths! v0.20 introduces a new virtual module, `#imports`, that abstracts all this away from developers. See the [blog post](/blog/2024-12-06-using-imports-module) for more details about how this module works.
|
||||
|
||||
So to upgrade, just replace any imports from `wxt/storage`, `wxt/client`, and `wxt/sandbox` with an import to the new `#imports` module:
|
||||
|
||||
```ts
|
||||
import { storage } from 'wxt/storage'; // [!code --]
|
||||
import { defineContentScript } from 'wxt/sandbox'; // [!code --]
|
||||
import { ContentScriptContext, useAppConfig } from 'wxt/client'; // [!code --]
|
||||
import { storage } from '#imports'; // [!code ++]
|
||||
import { defineContentScript } from '#imports'; // [!code ++]
|
||||
import { ContentScriptContext, useAppConfig } from '#imports'; // [!code ++]
|
||||
```
|
||||
|
||||
You can combine the imports into a single import statement, but it's easier to just find/replace each statement.
|
||||
|
||||
```ts
|
||||
import { storage } from 'wxt/storage'; // [!code --]
|
||||
import { defineContentScript } from 'wxt/sandbox'; // [!code --]
|
||||
import { ContentScriptContext, useAppConfig } from 'wxt/client'; // [!code --]
|
||||
import {
|
||||
// [!code ++]
|
||||
storage, // [!code ++]
|
||||
defineContentScript, // [!code ++]
|
||||
ContentScriptContext, // [!code ++]
|
||||
useAppConfig, // [!code ++]
|
||||
} from '#imports'; // [!code ++]
|
||||
```
|
||||
|
||||
:::tip
|
||||
Before types will work, you'll need to run `wxt prepare` after installing v0.20 to generate the new TypeScript declarations.
|
||||
:::
|
||||
|
||||
### `createShadowRootUi` CSS Changes
|
||||
|
||||
WXT now resets styles inherited from the webpage (`visibility`, `color`, `font-size`, etc.) by setting `all: initial` inside the shadow root.
|
||||
|
||||
:::warning
|
||||
This doesn't effect `rem` units. You should continue using `postcss-rem-to-px` or an equivalent library if the webpage sets the HTML element's `font-size`.
|
||||
:::
|
||||
|
||||
If you use `createShadowRootUi`:
|
||||
|
||||
1. Remove any manual CSS overrides that reset the style of specific websites. For example:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```css [entrypoints/reddit.content/style.css]
|
||||
body { /* [!code --] */
|
||||
/* Override Reddit's default "hidden" visibility on elements */ /* [!code --] */
|
||||
visibility: visible !important; /* [!code --] */
|
||||
} /* [!code --] */
|
||||
```
|
||||
|
||||
2. Double check that your UI looks the same as before.
|
||||
|
||||
If you run into problems with the new behavior, you can disable it and continue using your current CSS:
|
||||
|
||||
```ts
|
||||
const ui = await createShadowRootUi({
|
||||
inheritStyles: true, // [!code ++]
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
### Default Output Directories Changed
|
||||
|
||||
The default value for the [`outDirTemplate`](/api/reference/wxt/interfaces/InlineConfig#outdirtemplate) config has changed. Now, different build modes are output to different directories:
|
||||
|
||||
- `--mode production` → `.output/chrome-mv3`: Production builds are unchanged
|
||||
- `--mode development` → `.output/chrome-mv3-dev`: Dev mode now has a `-dev` suffix so it doesn't overwrite production builds
|
||||
- `--mode custom` → `.output/chrome-mv3-custom`: Other custom modes end with a `-[mode]` suffix
|
||||
|
||||
To use the old behavior, writing all output to the same directory, set the `outDirTemplate` option:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
outDirTemplate: '{{browser}}-mv{{manifestVersion}}', // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
:::warning
|
||||
If you've previously loaded the extension into your browser manually for development, you'll need to uninstall and re-install it from the new dev output directory.
|
||||
:::
|
||||
|
||||
### Deprecated APIs Removed
|
||||
|
||||
- `entrypointLoader` option: WXT now uses `vite-node` for importing entrypoints during the build process.
|
||||
> This was deprecated in v0.19.0, see the [v0.19 section](#v0-18-5-rarr-v0-19-0) for migration steps.
|
||||
- `transformManifest` option: Use the `build:manifestGenerated` hook to transform the manifest instead:
|
||||
<!-- prettier-ignore -->
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
transformManifest(manifest) { // [!code --]
|
||||
hooks: { // [!code ++]
|
||||
'build:manifestGenerated': (_, manifest) => { // [!code ++]
|
||||
// ...
|
||||
}, // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### New Deprecations
|
||||
|
||||
#### `runner` APIs Renamed
|
||||
|
||||
To improve consistency with the `web-ext.config.ts` filename, the "runner" API and config options have been renamed. You can continue using the old names, but they have been deprecated and will be removed in a future version:
|
||||
|
||||
1. The `runner` option has been renamed to `webExt`:
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
runner: { // [!code --]
|
||||
webExt: { // [!code ++]
|
||||
startUrls: ["https://wxt.dev"],
|
||||
},
|
||||
});
|
||||
```
|
||||
2. `defineRunnerConfig` has been renamed to `defineWebExtConfig`:
|
||||
```ts [web-ext.config.ts]
|
||||
import { defineRunnerConfig } from 'wxt'; // [!code --]
|
||||
import { defineWebExtConfig } from 'wxt'; // [!code ++]
|
||||
```
|
||||
3. The `ExtensionRunnerConfig` type has been renamed to `WebExtConfig`
|
||||
```ts
|
||||
import type { ExtensionRunnerConfig } from 'wxt'; // [!code --]
|
||||
import type { WebExtConfig } from 'wxt'; // [!code ++]
|
||||
```
|
||||
|
||||
## v0.18.5 → v0.19.0
|
||||
|
||||
### `vite-node` Entrypoint Loader
|
||||
@@ -19,7 +260,7 @@ Currently, WXT is in pre-release. This means changes to the second digit, `v0.X`
|
||||
The default entrypoint loader has changed to `vite-node`. If you use any NPM packages that depend on the `webextension-polyfill`, you need to add them to Vite's `ssr.noExternal` option:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
vite: () => ({ // [!code ++]
|
||||
ssr: { // [!code ++]
|
||||
@@ -35,8 +276,7 @@ export default defineConfig({
|
||||
|
||||
Importing variables and using them in the entrypoint options:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
```ts [entrypoints/content.ts]
|
||||
import { GOOGLE_MATCHES } from '~/utils/constants'
|
||||
|
||||
export default defineContentScript({
|
||||
@@ -47,8 +287,7 @@ export default defineContentScript({
|
||||
|
||||
Using Vite-specific APIs like `import.meta.glob` to define entrypoint options:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
```ts [entrypoints/content.ts]
|
||||
const providers: Record<string, any> = import.meta.glob('../providers/*', {
|
||||
eager: true,
|
||||
});
|
||||
@@ -69,7 +308,7 @@ Basically, you can now import and do things outside the `main` function of the e
|
||||
|
||||
To continue using the old approach, add the following to your `wxt.config.ts` file:
|
||||
|
||||
```ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
entrypointLoader: 'jiti', // [!code ++]
|
||||
});
|
||||
@@ -101,8 +340,7 @@ If you already have `<srcDir>/modules` or `<srcDir>/Modules` directory, `wxt pre
|
||||
You have two options:
|
||||
|
||||
1. [Recommended] Keep your files where they are and tell WXT to look in a different folder:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
modulesDir: 'wxt-modules', // defaults to "modules"
|
||||
});
|
||||
@@ -175,8 +413,7 @@ JS entrypoints in the output directory have been moved. Unless you're doing some
|
||||
|
||||
### Renamed `zip.ignoredSources` to `zip.excludeSources`
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
zip: {
|
||||
ignoredSources: [
|
||||
|
||||
+7
-8
@@ -15,7 +15,7 @@ A simplified wrapper around the extension storage APIs.
|
||||
This module is built-in to WXT, so you don't need to install anything.
|
||||
|
||||
```ts
|
||||
import { storage } from 'wxt/storage';
|
||||
import { storage } from '#imports';
|
||||
```
|
||||
|
||||
If you use auto-imports, `storage` is auto-imported for you, so you don't even need to import it!
|
||||
@@ -37,10 +37,9 @@ import { storage } from '@wxt-dev/storage';
|
||||
|
||||
## Storage Permission
|
||||
|
||||
To use the `wxt/storage` API, the `"storage"` permission must be added to the manifest:
|
||||
To use the `@wxt-dev/storage` API, the `"storage"` permission must be added to the manifest:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['storage'],
|
||||
@@ -75,7 +74,7 @@ await storage.watch<number>(
|
||||
await storage.getMeta<{ v: number }>('local:installDate');
|
||||
```
|
||||
|
||||
For a full list of methods available, see the [API reference](/api/reference/@wxt-dev/storage/interfaces/WxtStorage).
|
||||
For a full list of methods available, see the [API reference](/api/reference/wxt/utils/storage/interfaces/WxtStorage).
|
||||
|
||||
## Watchers
|
||||
|
||||
@@ -98,7 +97,7 @@ unwatch();
|
||||
|
||||
## Metadata
|
||||
|
||||
`wxt/storage` also supports setting metadata for keys, stored at `key + "$"`. Metadata is a collection of properties associated with a key. It might be a version number, last modified date, etc.
|
||||
`@wxt-dev/storage` also supports setting metadata for keys, stored at `key + "$"`. Metadata is a collection of properties associated with a key. It might be a version number, last modified date, etc.
|
||||
|
||||
[Other than versioning](#versioning), you are responsible for managing a field's metadata:
|
||||
|
||||
@@ -158,7 +157,7 @@ const unwatch = showChangelogOnUpdate.watch((newValue) => {
|
||||
});
|
||||
```
|
||||
|
||||
For a full list of properties and methods available, see the [API reference](/api/reference/@wxt-dev/storage/interfaces/WxtStorageItem).
|
||||
For a full list of properties and methods available, see the [API reference](/api/reference/wxt/utils/storage/interfaces/WxtStorageItem).
|
||||
|
||||
### Versioning
|
||||
|
||||
@@ -354,4 +353,4 @@ await storage.setItems([
|
||||
]);
|
||||
```
|
||||
|
||||
Refer to the [API Reference](/api/reference/@wxt-dev/storage/interfaces/WxtStorage) for types and examples of how to use all the bulk APIs.
|
||||
Refer to the [API Reference](/api/reference/wxt/utils/storage/interfaces/WxtStorage) for types and examples of how to use all the bulk APIs.
|
||||
|
||||
+28
-23
@@ -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,28 +17,33 @@
|
||||
"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-frontmatter": "^1.1.0",
|
||||
"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:",
|
||||
"feed": "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:",
|
||||
"vitepress-plugin-group-icons": "catalog:",
|
||||
"vitest-mock-extended": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineAppConfig } from 'wxt/sandbox';
|
||||
import { defineAppConfig } from 'wxt/utils/define-app-config';
|
||||
import { googleAnalytics4 } from './modules/analytics/providers/google-analytics-4';
|
||||
import { umami } from './modules/analytics/providers/umami';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { resolve } from 'node:path';
|
||||
|
||||
// Build module and plugins
|
||||
export default defineBuildConfig({
|
||||
rootDir: 'modules/analytics',
|
||||
rootDir: resolve(__dirname, 'modules/analytics'),
|
||||
outDir: resolve(__dirname, 'dist'),
|
||||
entries: [
|
||||
{ input: 'index.ts', name: 'module' },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'wxt';
|
||||
import 'wxt/sandbox';
|
||||
import 'wxt/utils/define-app-config';
|
||||
import {
|
||||
addAlias,
|
||||
addViteConfig,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { relative, resolve } from 'node:path';
|
||||
import type { AnalyticsConfig } from './types';
|
||||
|
||||
declare module 'wxt/sandbox' {
|
||||
declare module 'wxt/utils/define-app-config' {
|
||||
export interface WxtAppConfig {
|
||||
analytics: AnalyticsConfig;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export default defineWxtModule({
|
||||
? clientModuleId
|
||||
: relative(wxtAnalyticsFolder, clientModuleId)
|
||||
}';`,
|
||||
`import { useAppConfig } from 'wxt/client';`,
|
||||
`import { useAppConfig } from '#imports';`,
|
||||
``,
|
||||
`export const analytics = createAnalytics(useAppConfig().analytics);`,
|
||||
``,
|
||||
|
||||
@@ -48,18 +48,18 @@
|
||||
"prepare": "buildc --deps-only -- wxt prepare"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.23"
|
||||
"wxt": ">=0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/chrome": "^0.0.268",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"publint": "^0.2.12",
|
||||
"typescript": "^5.6.3",
|
||||
"unbuild": "^2.0.0",
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@types/chrome": "catalog:",
|
||||
"@types/ua-parser-js": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"ua-parser-js": "^1.0.38"
|
||||
"ua-parser-js": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# `@wxt-dev/browser`
|
||||
|
||||
Provides access to the `browser` or `chrome` extension APIs and related types.
|
||||
|
||||
```ts
|
||||
import { browser, Browser } from '@wxt-dev/browser';
|
||||
// Or if you're using WXT:
|
||||
// import { browser, Browser } from 'wxt/browser';
|
||||
|
||||
console.log(browser.runtime.id);
|
||||
|
||||
const onMessage = (message: any, sender: Browser.runtime.MessageSender) => {
|
||||
console.log(message);
|
||||
};
|
||||
browser.runtime.onMessage.addListener(onMessage);
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
If you're using WXT, this package is already installed, you don't need to install it manually.
|
||||
|
||||
Otherwise, you can install the package from NPM:
|
||||
|
||||
```sh
|
||||
pnpm install @wxt-dev/browser
|
||||
```
|
||||
|
||||
## Upgrading to Latest Types
|
||||
|
||||
Just run:
|
||||
|
||||
```sh
|
||||
pnpm upgrade @wxt-dev/browser
|
||||
```
|
||||
|
||||
This should update both the manually installed version and the subdependency inside WXT.
|
||||
|
||||
## Contributing
|
||||
|
||||
### Code Generation
|
||||
|
||||
Types are generated based on the `@types/chrome` package, and with modifications specifically for use with WXT.
|
||||
|
||||
### Updating `@types/chrome` Version
|
||||
|
||||
You don't need to do anything! [A github action](https://github.com/wxt-dev/wxt/actions/workflows/update-browser-package.yml) is ran every day to generate and publish this package using the latest `@types/chrome` version.
|
||||
|
||||
You can manually generate types via:
|
||||
|
||||
```sh
|
||||
pnpm gen
|
||||
```
|
||||
|
||||
### Why not just use `@types/chrome`?
|
||||
|
||||
With WXT, you must import the `browser` variable to use the extension APIs. The way `@types/chrome` is implemented forces you to define a global `chrome` variable. With WXT, this isn't acceptable, we don't want to pollute the global (type) scope or introduce conflicts with auto-imports.
|
||||
|
||||
Additionally, WXT overrides types to provide additional type safety for some APIs, like `browser.runtime.getURL` and `browser.i18n.getMessage`. With `@types/chrome`'s nested namespace approach, it's not possible to override the types for those functions.
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@wxt-dev/browser",
|
||||
"description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome",
|
||||
"version": "0.0.310",
|
||||
"type": "module",
|
||||
"main": "src/index.mjs",
|
||||
"types": "src/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wxt-dev/wxt.git",
|
||||
"directory": "packages/browser"
|
||||
},
|
||||
"scripts": {
|
||||
"gen": "tsx scripts/generate.ts"
|
||||
},
|
||||
"author": {
|
||||
"name": "Aaron Klinker",
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "0.0.310",
|
||||
"fs-extra": "catalog:",
|
||||
"nano-spawn": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/filesystem": "*",
|
||||
"@types/har-format": "*"
|
||||
},
|
||||
"peerDependencies": {}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import spawn from 'nano-spawn';
|
||||
import fs from 'fs-extra';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join, resolve, sep } from 'node:path';
|
||||
import { sep as posixSep } from 'node:path/posix';
|
||||
|
||||
// Fetch latest version
|
||||
|
||||
console.log('Getting latest version of \x1b[36m@types/chrome\x1b[0m');
|
||||
await spawn('pnpm', ['i', '--ignore-scripts', '-D', '@types/chrome']);
|
||||
|
||||
// Generate new package.json
|
||||
|
||||
console.log('Generating new \x1b[36mpackage.json\x1b[0m');
|
||||
|
||||
const pkgJsonPath = fileURLToPath(
|
||||
import.meta.resolve('@types/chrome/package.json'),
|
||||
);
|
||||
const pkgDir = dirname(pkgJsonPath);
|
||||
const pkgJson = await fs.readJson(pkgJsonPath);
|
||||
const pkgJsonTemplate = await fs.readFile('templates/package.json', 'utf8');
|
||||
const newPkgJson = JSON.parse(
|
||||
pkgJsonTemplate.replaceAll('{{chromeTypesVersion}}', pkgJson.version),
|
||||
);
|
||||
newPkgJson.dependencies = pkgJson.dependencies;
|
||||
newPkgJson.peerDependencies = pkgJson.peerDependencies;
|
||||
newPkgJson.peerDependenciesMeta = pkgJson.peerDependenciesMeta;
|
||||
|
||||
const outPkgJsonPath = resolve('package.json');
|
||||
await fs.writeJson(outPkgJsonPath, newPkgJson);
|
||||
await spawn('pnpm', ['-w', 'prettier', '--write', outPkgJsonPath]);
|
||||
|
||||
// Generate declaration files
|
||||
|
||||
console.log('Generating declaration files');
|
||||
const outDir = resolve('src/gen');
|
||||
const declarationFileMapping = (
|
||||
await fs.readdir(pkgDir, {
|
||||
recursive: true,
|
||||
encoding: 'utf8',
|
||||
})
|
||||
)
|
||||
// Filter to .d.ts files
|
||||
.filter((file) => file.endsWith('.d.ts'))
|
||||
// Map to usable paths
|
||||
.map((file) => ({
|
||||
file: file.replaceAll(sep, posixSep),
|
||||
srcPath: join(pkgDir, file),
|
||||
destPath: join(outDir, file),
|
||||
}));
|
||||
|
||||
for (const { file, srcPath, destPath } of declarationFileMapping) {
|
||||
const content = await fs.readFile(srcPath, 'utf8');
|
||||
const transformedContent = transformFile(file, content);
|
||||
const destDir = dirname(destPath);
|
||||
await fs.mkdir(destDir, { recursive: true });
|
||||
await fs.writeFile(destPath, transformedContent);
|
||||
console.log(` \x1b[2m-\x1b[0m \x1b[36m${file}\x1b[0m`);
|
||||
}
|
||||
|
||||
// Done!
|
||||
|
||||
console.log(
|
||||
'\x1b[32m✔\x1b[0m Done in ' + performance.now().toFixed(0) + ' ms',
|
||||
);
|
||||
|
||||
// Transformations
|
||||
|
||||
function transformFile(file: string, content: string): string {
|
||||
return (
|
||||
// Add prefix
|
||||
`/* DO NOT EDIT - generated by scripts/generate.ts */\n\n${content}\n`
|
||||
// Remove global type declaration
|
||||
.replaceAll('chrome: typeof chrome;', '// chrome: typeof chrome;')
|
||||
// Rename `chrome` namespace to `Browser` and export it
|
||||
.replaceAll('declare namespace chrome', 'export namespace Browser')
|
||||
// Update references to `chrome` namespace to `Browser`
|
||||
.replaceAll('chrome.', 'Browser.')
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/// <reference types="chrome" />
|
||||
import { describe, expectTypeOf, it } from 'vitest';
|
||||
import { browser, type Browser } from '../index';
|
||||
|
||||
describe('browser', () => {
|
||||
describe('types', () => {
|
||||
it('should provide types via the Browser import', () => {
|
||||
expectTypeOf<Browser.runtime.MessageSender>().toMatchTypeOf<chrome.runtime.MessageSender>();
|
||||
expectTypeOf<Browser.storage.AreaName>().toMatchTypeOf<chrome.storage.AreaName>();
|
||||
expectTypeOf<Browser.i18n.LanguageDetectionResult>().toMatchTypeOf<chrome.i18n.LanguageDetectionResult>();
|
||||
});
|
||||
|
||||
it('should provide values via the browser import', () => {
|
||||
expectTypeOf(browser.runtime.id).toMatchTypeOf<string>();
|
||||
expectTypeOf(
|
||||
browser.storage.local,
|
||||
).toMatchTypeOf<Browser.storage.StorageArea>();
|
||||
expectTypeOf(
|
||||
browser.i18n.detectLanguage('Hello, world!'),
|
||||
).resolves.toMatchTypeOf<chrome.i18n.LanguageDetectionResult>();
|
||||
});
|
||||
});
|
||||
});
|
||||
+1159
File diff suppressed because it is too large
Load Diff
+9
@@ -0,0 +1,9 @@
|
||||
/* DO NOT EDIT - generated by scripts/generate.ts */
|
||||
|
||||
import { Entry, Log } from "har-format";
|
||||
|
||||
declare global {
|
||||
export type HARFormatEntry = Entry;
|
||||
export type HARFormatLog = Log;
|
||||
}
|
||||
|
||||
+15110
File diff suppressed because it is too large
Load Diff
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import { Browser } from './gen';
|
||||
|
||||
export const browser: typeof Browser;
|
||||
export { Browser };
|
||||
@@ -0,0 +1,5 @@
|
||||
// #region snippet
|
||||
export const browser = globalThis.browser?.runtime?.id
|
||||
? globalThis.browser
|
||||
: globalThis.chrome;
|
||||
// #endregion snippet
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@wxt-dev/browser",
|
||||
"description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome",
|
||||
"version": "{{chromeTypesVersion}}",
|
||||
"type": "module",
|
||||
"main": "src/index.mjs",
|
||||
"types": "src/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wxt-dev/wxt.git",
|
||||
"directory": "packages/browser"
|
||||
},
|
||||
"scripts": {
|
||||
"gen": "tsx scripts/generate.ts"
|
||||
},
|
||||
"author": {
|
||||
"name": "Aaron Klinker",
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "{{chromeTypesVersion}}",
|
||||
"fs-extra": "catalog:",
|
||||
"nano-spawn": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ However, it does have one major downside:
|
||||
helloWorld: Hello world!
|
||||
```
|
||||
|
||||
> `@wxt-dev/i18n` supports the standard messages format, so if you already have localization files at `<srcDir>/public/_locale/<lang>/messages.json`, you don't need to convert them to YAML or refactor them - just move them to `<srcDir>/locales/<lang>.json` and they'll just work out of the box!
|
||||
> `@wxt-dev/i18n` supports the standard messages format, so if you already have localization files at `<rootDir>/public/_locale/<lang>/messages.json`, you don't need to convert them to YAML or refactor them - just move them to `<srcDir>/locales/<lang>.json` and they'll just work out of the box!
|
||||
|
||||
4. To get a translation, use the auto-imported `i18n` object or import it manually:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineContentScript } from 'wxt/sandbox';
|
||||
import { ContentScriptContext, createShadowRootUi } from 'wxt/client';
|
||||
import {
|
||||
defineContentScript,
|
||||
ContentScriptContext,
|
||||
createShadowRootUi,
|
||||
} from '#imports';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ export default defineWxtModule<ReactModuleOptions>({
|
||||
|
||||
// Enable auto-imports for JSX files
|
||||
wxt.hook('config:resolved', (wxt) => {
|
||||
if (wxt.config.imports === false) return;
|
||||
// In older versions of WXT, `wxt.config.imports` could be false
|
||||
if (!wxt.config.imports) return;
|
||||
|
||||
wxt.config.imports.dirsScanOptions ??= {};
|
||||
wxt.config.imports.dirsScanOptions.filePatterns = [
|
||||
|
||||
@@ -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,17 +48,17 @@
|
||||
"wxt": ">=0.19.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "^4.3.4"
|
||||
"@vitejs/plugin-react": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"publint": "^0.2.12",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"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: {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { defineContentScript } from 'wxt/sandbox';
|
||||
import { ContentScriptContext, createShadowRootUi } from 'wxt/client';
|
||||
import {
|
||||
defineContentScript,
|
||||
ContentScriptContext,
|
||||
createShadowRootUi,
|
||||
} from '#imports';
|
||||
import { render } from 'solid-js/web';
|
||||
|
||||
export default defineContentScript({
|
||||
|
||||
@@ -19,7 +19,8 @@ export default defineWxtModule<SolidModuleOptions>({
|
||||
|
||||
// Enable auto-imports for JSX files
|
||||
wxt.hook('config:resolved', (wxt) => {
|
||||
if (wxt.config.imports === false) return;
|
||||
// In older versions of WXT, `wxt.config.imports` could be false
|
||||
if (!wxt.config.imports) return;
|
||||
|
||||
wxt.config.imports.dirsScanOptions ??= {};
|
||||
wxt.config.imports.dirsScanOptions.filePatterns = [
|
||||
|
||||
@@ -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,17 @@
|
||||
# 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)
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"version": "2.0.2",
|
||||
"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:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,43 @@
|
||||
# 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)
|
||||
@@ -31,4 +69,4 @@ It's still shipped inside WXT and accessible via `wxt/storage`, but now:
|
||||
|
||||
[Read the docs](https://wxt.dev/storage.html) for more details.
|
||||
|
||||
> This is apart of the v1.0 initiative for WXT.
|
||||
> This is apart of the v1.0 initiative for WXT.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/storage",
|
||||
"description": "Web extension storage API provided by WXT, supports all browsers.",
|
||||
"version": "1.0.1",
|
||||
"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 } });
|
||||
@@ -693,6 +705,30 @@ describe('Storage Utils', () => {
|
||||
expect(migrateToV3).toBeCalledWith(4);
|
||||
});
|
||||
|
||||
it('should call onMigrationComplete callback function if defined', async () => {
|
||||
await fakeBrowser.storage.local.set({
|
||||
count: 2,
|
||||
count$: { v: 1 },
|
||||
});
|
||||
const migrateToV2 = vi.fn((oldCount) => oldCount * 2);
|
||||
const migrateToV3 = vi.fn((oldCount) => oldCount * 3);
|
||||
const onMigrationComplete = vi.fn((count, _v) => count);
|
||||
|
||||
storage.defineItem<number, { v: number }>(`local:count`, {
|
||||
defaultValue: 0,
|
||||
version: 3,
|
||||
migrations: {
|
||||
2: migrateToV2,
|
||||
3: migrateToV3,
|
||||
},
|
||||
onMigrationComplete,
|
||||
});
|
||||
await waitForMigrations();
|
||||
|
||||
expect(onMigrationComplete).toBeCalledTimes(1);
|
||||
expect(onMigrationComplete).toBeCalledWith(12, 3);
|
||||
});
|
||||
|
||||
it("should not run migrations if the value doesn't exist yet", async () => {
|
||||
const migrateToV2 = vi.fn((oldCount) => oldCount * 2);
|
||||
const migrateToV3 = vi.fn((oldCount) => oldCount * 3);
|
||||
@@ -835,6 +871,73 @@ describe('Storage Utils', () => {
|
||||
|
||||
await expect(item.migrate()).rejects.toThrow(expectedError);
|
||||
});
|
||||
|
||||
it('should print migration logs if debug option is true', async () => {
|
||||
await fakeBrowser.storage.local.set({
|
||||
count: 2,
|
||||
count$: { v: 1 },
|
||||
});
|
||||
const migrateToV2 = vi.fn((oldCount) => oldCount * 2);
|
||||
const migrateToV3 = vi.fn((oldCount) => oldCount * 3);
|
||||
const consoleSpy = vi.spyOn(console, 'debug');
|
||||
|
||||
storage.defineItem<number, { v: number }>(`local:count`, {
|
||||
defaultValue: 0,
|
||||
version: 3,
|
||||
migrations: {
|
||||
2: migrateToV2,
|
||||
3: migrateToV3,
|
||||
},
|
||||
debug: true,
|
||||
});
|
||||
await waitForMigrations();
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledTimes(4);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
`[@wxt-dev/storage] Running storage migration for local:count: v1 -> v3`,
|
||||
);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
`[@wxt-dev/storage] Storage migration processed for version: v2`,
|
||||
);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
`[@wxt-dev/storage] Storage migration processed for version: v3`,
|
||||
);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
`[@wxt-dev/storage] Storage migration completed for local:count v3`,
|
||||
{ migratedValue: expect.any(Number) },
|
||||
);
|
||||
});
|
||||
it('should not print migration logs if debug option is undefined or false', async () => {
|
||||
await fakeBrowser.storage.local.set({
|
||||
count: 2,
|
||||
count$: { v: 1 },
|
||||
count2: 2,
|
||||
count2$: { v: 1 },
|
||||
});
|
||||
const migrateToV2 = vi.fn((oldCount) => oldCount * 2);
|
||||
const migrateToV3 = vi.fn((oldCount) => oldCount * 3);
|
||||
const consoleSpy = vi.spyOn(console, 'debug');
|
||||
|
||||
storage.defineItem<number, { v: number }>(`local:count`, {
|
||||
defaultValue: 0,
|
||||
version: 3,
|
||||
migrations: {
|
||||
2: migrateToV2,
|
||||
3: migrateToV3,
|
||||
},
|
||||
});
|
||||
|
||||
storage.defineItem<number, { v: number }>(`local:count2`, {
|
||||
defaultValue: 0,
|
||||
version: 2,
|
||||
migrations: {
|
||||
2: migrateToV2,
|
||||
},
|
||||
debug: false,
|
||||
});
|
||||
await waitForMigrations();
|
||||
expect(consoleSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getValue', () => {
|
||||
|
||||
@@ -267,7 +267,6 @@ function createStorage(): WxtStorage {
|
||||
async ([storageArea, updates]) => {
|
||||
const driver = getDriver(storageArea as StorageArea);
|
||||
const metaKeys = updates.map(({ key }) => getMetaKey(key));
|
||||
console.log(storageArea, metaKeys);
|
||||
const existingMetas = await driver.getItems(metaKeys);
|
||||
const existingMetaMap = Object.fromEntries(
|
||||
existingMetas.map(({ key, value }) => [key, getMetaValue(value)]),
|
||||
@@ -326,6 +325,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);
|
||||
@@ -355,7 +358,12 @@ function createStorage(): WxtStorage {
|
||||
defineItem: (key, opts?: WxtStorageItemOptions<any>) => {
|
||||
const { driver, driverKey } = resolveKey(key);
|
||||
|
||||
const { version: targetVersion = 1, migrations = {} } = opts ?? {};
|
||||
const {
|
||||
version: targetVersion = 1,
|
||||
migrations = {},
|
||||
onMigrationComplete,
|
||||
debug = false,
|
||||
} = opts ?? {};
|
||||
if (targetVersion < 1) {
|
||||
throw Error(
|
||||
'Storage item version cannot be less than 1. Initial versions should be set to 1, not 0.',
|
||||
@@ -375,10 +383,15 @@ 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}`,
|
||||
);
|
||||
if (debug === true) {
|
||||
console.debug(
|
||||
`[@wxt-dev/storage] Running storage migration for ${key}: v${currentVersion} -> v${targetVersion}`,
|
||||
);
|
||||
}
|
||||
const migrationsToRun = Array.from(
|
||||
{ length: targetVersion - currentVersion },
|
||||
(_, i) => currentVersion + i + 1,
|
||||
@@ -389,8 +402,13 @@ function createStorage(): WxtStorage {
|
||||
migratedValue =
|
||||
(await migrations?.[migrateToVersion]?.(migratedValue)) ??
|
||||
migratedValue;
|
||||
if (debug === true) {
|
||||
console.debug(
|
||||
`[@wxt-dev/storage] Storage migration processed for version: v${migrateToVersion}`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
throw Error(`v${migrateToVersion} migration failed for "${key}"`, {
|
||||
throw new MigrationError(key, migrateToVersion, {
|
||||
cause: err,
|
||||
});
|
||||
}
|
||||
@@ -399,10 +417,14 @@ function createStorage(): WxtStorage {
|
||||
{ key: driverKey, value: migratedValue },
|
||||
{ key: driverMetaKey, value: { ...meta, v: targetVersion } },
|
||||
]);
|
||||
console.debug(
|
||||
`[@wxt-dev/storage] Storage migration completed for ${key} v${targetVersion}`,
|
||||
{ migratedValue },
|
||||
);
|
||||
|
||||
if (debug === true) {
|
||||
console.debug(
|
||||
`[@wxt-dev/storage] Storage migration completed for ${key} v${targetVersion}`,
|
||||
{ migratedValue },
|
||||
);
|
||||
}
|
||||
onMigrationComplete?.(migratedValue, targetVersion);
|
||||
};
|
||||
const migrationsDone =
|
||||
opts?.migrations == null
|
||||
@@ -534,6 +556,9 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
|
||||
removeItems: async (keys) => {
|
||||
await getStorageArea().remove(keys);
|
||||
},
|
||||
clear: async () => {
|
||||
await getStorageArea().clear();
|
||||
},
|
||||
snapshot: async () => {
|
||||
return await getStorageArea().get();
|
||||
},
|
||||
@@ -674,6 +699,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.
|
||||
*
|
||||
@@ -712,7 +743,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,
|
||||
@@ -730,6 +761,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;
|
||||
@@ -845,6 +877,15 @@ export interface WxtStorageItemOptions<T> {
|
||||
* A map of version numbers to the functions used to migrate the data to that version.
|
||||
*/
|
||||
migrations?: Record<number, (oldValue: any) => any>;
|
||||
/**
|
||||
* Print debug logs, such as migration process.
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean;
|
||||
/**
|
||||
* A callback function that runs on migration complete.
|
||||
*/
|
||||
onMigrationComplete?: (migratedValue: T, targetVersion: number) => void;
|
||||
}
|
||||
|
||||
export type StorageAreaChanges = {
|
||||
|
||||
@@ -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:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# `@wxt-dev/webextension-polyfill`
|
||||
|
||||
Configures `wxt/browser` to import `browser` from [`webextension-polyfill`](https://github.com/mozilla/webextension-polyfill) instead of using the regular `chrome`/`browser` globals WXT normally provides.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
pnpm i @wxt-dev/webextension-polyfill webextension-polyfill
|
||||
```
|
||||
|
||||
Then add the module to your config:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/webextension-polyfill'],
|
||||
});
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user