Compare commits

..

6 Commits

Author SHA1 Message Date
Aaron Klinker 9fad20332f 0.19.1-alpha2 2024-07-27 08:58:58 -05:00
Aaron Klinker b530815065 Remove main function from entrypoint before auto-imports 2024-07-27 08:58:08 -05:00
Aaron Klinker 09a0bba756 0.19.1-alpha1 2024-07-26 15:09:39 -05:00
Aaron Klinker 05803b2d59 cleanup 2024-07-26 15:06:37 -05:00
Aaron Klinker 6cc7ecb252 Cleanup 2024-07-26 15:05:30 -05:00
Aaron Klinker f8804a12c2 fix: web globals not defined when importing entrypoints 2024-07-26 15:03:54 -05:00
563 changed files with 18519 additions and 50505 deletions
-10
View File
@@ -1,10 +0,0 @@
extends:
- '@commitlint/config-conventional'
rules:
subject-case:
- 0
- always
- - sentence-case
- start-case
- pascal-case
- upper-case
-12
View File
@@ -1,17 +1,5 @@
# See https://git-scm.com/docs/gitattributes#_pattern_format for more about `.gitattributes`.
# Normalize EOL for all files that Git considers text files
* text=auto eol=lf
# Mark lock files as generated to avoid diffing
pnpm-lock.yaml linguist-generated
package-lock.json linguist-generated
bun.lockb linguist-generated
bun.lock linguist-generated
yarn.lock linguist-generated
# Exclude templates from language statistics
templates/**/* linguist-vendored
# Other generated files
packages/browser/src/gen/** linguist-generated
-12
View File
@@ -1,12 +0,0 @@
# Set default
* @aklinker1
# Secure Directories
/.github/ @aklinker1
# Creator of specific wxt modules
/packages/auto-icons/ @Timeraa
/packages/unocss/ @Timeraa
# Other Directories
/docs @aklinker1 @PatrykKuniczak @nishu-murmu
-3
View File
@@ -1,3 +0,0 @@
# 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
-1
View File
@@ -1,7 +1,6 @@
name: "\U0001F41E Bug report"
description: Report an issue with WXT
labels: [pending-triage]
type: Bug
body:
- type: markdown
attributes:
+1 -1
View File
@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for WXT
title: ''
type: Feature
labels: feature
assignees: ''
---
+9 -15
View File
@@ -1,21 +1,15 @@
name: Basic Setup
description: Install Bun and dependencies
inputs:
install:
default: 'true'
description: Whether or not to run 'bun install'
description: Install PNPM, Node, and dependencies
runs:
using: composite
steps:
- name: 🛠️ Setup Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
- name: Setup PNPM
uses: pnpm/action-setup@v4
- name: Setup NodeJS
uses: actions/setup-node@v4
with:
bun-version-file: '.tool-versions'
- name: 📦 Install Dependencies
if: ${{ inputs.install == 'true' }}
node-version: 18
cache: pnpm
- name: Install Dependencies
shell: bash
run: bun ci ${{ inputs.installArgs }}
run: pnpm install
+8 -13
View File
@@ -1,20 +1,15 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: bun
directory: /
- package-ecosystem: npm
directory: '/' # Location of package manifests
schedule:
interval: 'daily'
groups:
production-updates:
applies-to: version-updates
dependency-type: production
update-types: [minor, patch]
dev-updates:
applies-to: version-updates
dependency-type: development
update-types: [minor, patch]
interval: 'monthly'
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'daily'
interval: 'monthly'
-13
View File
@@ -1,13 +0,0 @@
### Overview
<!-- Describe your changes and why you made them -->
### Manual Testing
<!-- Describe how to test your changes to make sure the PR works as intended -->
### Related Issue
<!-- If this PR is related to an issue, please link it here -->
This PR closes #<issue_number>
-115
View File
@@ -1,115 +0,0 @@
name: ✨ Auto-label PR
on:
pull_request_target:
types: [opened, synchronized, reopened]
jobs:
update-pr:
name: Update PR
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Gather Info
id: check
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number
});
// Check if PR has assignees
const hasAssignees = pr.assignees && pr.assignees.length > 0;
core.setOutput('has_assignees', hasAssignees);
core.setOutput('author', pr.user.login);
// Get list of changed files
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number
});
// Find all packages that were modified
const packagesRegex = /^packages\/([^\/]+)\//;
const affectedPackages = new Set();
for (const file of files) {
const match = file.filename.match(packagesRegex);
if (match) {
affectedPackages.add(match[1]);
}
}
const labels = Array.from(affectedPackages).map(pkg => `pkg/${pkg}`);
core.setOutput('labels', JSON.stringify(labels));
console.log('Detected package labels:', labels);
// Get current labels on the PR that match pkg/* pattern
const currentPkgLabels = pr.labels
.map(label => label.name)
.filter(name => name.startsWith('pkg/'));
core.setOutput('current_pkg_labels', JSON.stringify(currentPkgLabels));
console.log('Current pkg labels:', currentPkgLabels);
- name: Sync Author
if: steps.check.outputs.has_assignees == 'false'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
try {
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
assignees: ['${{ steps.check.outputs.author }}']
});
console.log('Assigned PR author: ${{ steps.check.outputs.author }}');
} catch (err) {
console.warn("Error assigning author:", err);
}
- name: Sync Labels
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const newLabels = ${{ steps.check.outputs.labels }};
const currentLabels = ${{ steps.check.outputs.current_pkg_labels }};
// Find labels to add (in newLabels but not in currentLabels)
const labelsToAdd = newLabels.filter(label => !currentLabels.includes(label));
// Find labels to remove (in currentLabels but not in newLabels)
const labelsToRemove = currentLabels.filter(label => !newLabels.includes(label));
// Add new labels
if (labelsToAdd.length > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: labelsToAdd
});
console.log('Added labels:', labelsToAdd);
}
// Remove obsolete labels
for (const label of labelsToRemove) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
name: label
});
console.log('Removed label:', label);
}
if (labelsToAdd.length === 0 && labelsToRemove.length === 0) {
console.log('No label changes needed');
}
@@ -1,36 +0,0 @@
name: 🔔 Notify Unreleased Commits
on:
workflow_dispatch:
schedule:
- cron: '0 20 * * 1' # Weekly at 8 PM UTC (3 PM CT)
jobs:
notify:
runs-on: ubuntu-22.04
if: github.repository_owner == 'wxt-dev'
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- name: List Commits
id: list-commits
run: |
./scripts/list-unreleased-commits.sh > summary.txt
- name: Discord notification
run: |
PAYLOAD=$(jq -n --arg content "${{ env.MESSAGE }}" '{"content": $content}')
curl -X POST \
-F "payload_json=${PAYLOAD}" \
-F "file1=@summary.txt" \
$DISCORD_WEBHOOK
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_UNRELEASED_COMMITS }}
MESSAGE: |
If a package needs released, please [run the workflow](<https://github.com/wxt-dev/wxt/actions/workflows/release.yml>).
Before running, consider:
- Are there any breaking changes? If so, prepare a list of breaking changes and update the changelog and release notes after the release.
- Are there any PRs open that we wait to release after they're merged?
-34
View File
@@ -1,34 +0,0 @@
name: ✨ pkg.pr.new
on:
push:
branches:
- main
pull_request:
branches:
- main
permissions:
contents: read
jobs:
build:
name: Publish Test Packages
runs-on: ubuntu-22.04
if: ${{ github.repository == 'wxt-dev/wxt' }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Build All Packages
run: bun run buildc all
- name: Publish
run: bunx pkg-pr-new publish --compact --bun './packages/*'
- name: DEBUG
if: always()
run: ls -lhA
working-directory: packages/analytics
-42
View File
@@ -1,42 +0,0 @@
name: 🎉 PR closed
on:
pull_request_target:
types:
- closed
permissions:
contents: read
pull-requests: write
jobs:
thank-you:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
steps:
- name: Log PR Author Info
run: |
echo "PR Author Login: ${{ github.event.pull_request.user.login }}"
echo "PR Author ID: ${{ github.event.pull_request.user.id }}"
echo "PR Author Type: ${{ github.event.pull_request.user.type }}"
echo "PR Author Node ID: ${{ github.event.pull_request.user.node_id }}"
- name: Post Thank You Comment
if: >-
github.event.pull_request.user.login != 'aklinker1' &&
github.event.pull_request.user.login != 'Timeraa' &&
github.event.pull_request.user.login != 'PatrykKuniczak' &&
github.event.pull_request.user.login != 'dependabot[bot]' &&
github.event.pull_request.user.login != 'copilot-swe-agent[bot]'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
comment: Thanks for helping make WXT better!
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: process.env.comment
})
-26
View File
@@ -1,26 +0,0 @@
name: 🛡️ Check PR Title
on:
pull_request:
types: [opened, edited]
jobs:
lint-pr-title:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Only fetch the config file from the repository
sparse-checkout-cone-mode: false
sparse-checkout: .commitlintrc.yml
- name: Install dependencies
run: npm install --global @commitlint/config-conventional commitlint
- name: Check PR title with commitlint
env:
PR_TITLE: ${{ github.event.pull_request.title }}
HELP_URL: https://github.com/wxt-dev/wxt/blob/main/CONTRIBUTING.md#conventional-pr-titles
run: echo "$PR_TITLE" | npx commitlint --help-url $HELP_URL
+8 -28
View File
@@ -1,4 +1,4 @@
name: 📝 Publish Docs
name: Publish Docs
on:
push:
branches:
@@ -10,38 +10,18 @@ on:
required: true
default: latest
permissions:
contents: read
jobs:
publish:
# Only run if it's the upstream repository, not forks
if: github.repository == 'wxt-dev/wxt'
name: Publish Docs
runs-on: ubuntu-22.04
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Login to Docker Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- uses: docker/login-action@v3
with:
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
- name: Build docs
run: |
bun run docs:build
docker build docs/.vitepress -t ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
- name: Push Image
run: docker push ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
- name: Deploy
run: 'curl -X POST -i -H "X-API-Key: ${{ secrets.DOCS_WEBHOOK_API_KEY }}" https://${{ secrets.DOCS_WEBHOOK_DOMAIN }}/api/webhook/stacks/${{ secrets.DOCS_WEBHOOK_STACK_ID }}'
- 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 }}
+19 -41
View File
@@ -1,4 +1,4 @@
name: 🚀 Release
name: Release
on:
workflow_dispatch:
inputs:
@@ -7,67 +7,45 @@ on:
default: wxt
type: choice
options:
- analytics
- auto-icons
- i18n
- is-background
- module-react
- module-solid
- module-svelte
- module-vue
- runner
- storage
- unocss
- webextension-polyfill
- wxt
permissions:
contents: read
- module-react
- module-vue
- module-svelte
- module-solid
- auto-icons
jobs:
validate:
name: Validate
uses: './.github/workflows/validate.yml'
secrets: inherit
publish:
name: Publish
runs-on: ubuntu-24.04
permissions:
contents: write # Push version changes
id-token: write # OIDC for NPM publishing
runs-on: ubuntu-22.04
needs:
- validate
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@v4
with:
fetch-depth: 0
ssh-key: ${{ secrets.DEPLOY_KEY }} # https://github.com/sbellone/release-workflow-example
- name: Setup
uses: ./.github/actions/setup
- name: Configure Git
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
git config --global push.followTags true
- uses: ./.github/actions/setup
- name: Bump and Tag
run: |
bun run scripts/bump-package-version.ts ${{ inputs.package }}
git config user.email "github-actions@users.noreply.github.com"
git config user.name "GitHub Actions"
pnpm tsx scripts/bump-package-version.ts ${{ inputs.package }}
git push
git push --tags
- name: Publish to NPM
working-directory: packages/${{ inputs.package }}
- name: NPM
run: |
bun pm pack
sudo npm i -g npm@latest
npm publish *.tgz
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
pnpm build
pnpm publish
working-directory: packages/${{ inputs.package }}
- name: Create GitHub release
run: bun run scripts/create-github-release.ts ${{ inputs.package }}
- name: GitHub Release
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+8 -28
View File
@@ -1,4 +1,4 @@
name: 🔄 Sync Releases
name: Sync Releases
on:
workflow_dispatch:
inputs:
@@ -7,38 +7,18 @@ on:
default: wxt
type: choice
options:
- analytics
- auto-icons
- i18n
- is-background
- module-react
- module-solid
- module-svelte
- module-vue
- runner
- storage
- webextension-polyfill
- wxt
permissions:
contents: read
- module-react
- module-vue
- module-svelte
- module-solid
jobs:
sync:
name: Sync Releases
runs-on: ubuntu-22.04
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup
uses: ./.github/actions/setup
with:
installArgs: --ignore-scripts
- name: Sync Releases
run: bun run scripts/sync-releases.ts ${{ inputs.package }}
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,51 +0,0 @@
name: 🔄 Update @wxt-dev/browser
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *' # Every day at midnight
permissions:
contents: read
jobs:
sync:
name: 'Sync with @types/chrome'
runs-on: ubuntu-latest
if: github.repository_owner == 'wxt-dev'
permissions:
contents: write # Push version changes
id-token: write # OIDC for NPM publishing
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ssh-key: ${{ secrets.DEPLOY_KEY }}
- name: Setup
uses: ./.github/actions/setup
with:
installArgs: --ignore-scripts
- name: Generate Latest Code
working-directory: packages/browser
run: bun run gen
- name: Run Checks
working-directory: packages/browser
run: bun run check
- name: Commit Changes
id: commit
uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commit_message: 'fix: Upgrade `@wxt-dev/browser` to latest `@types/chrome` version'
- name: Publish to NPM
if: steps.commit.outputs.changes_detected == 'true'
working-directory: packages/browser
run: |
bun pm pack
sudo npm i -g npm@latest
npm publish *.tgz
+32 -95
View File
@@ -1,4 +1,4 @@
name: 🛡️ Validate
name: Validate
on:
workflow_call:
pull_request:
@@ -6,94 +6,46 @@ on:
branches:
- main
permissions:
contents: read
jobs:
checks:
name: Checks
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Basic Checks
run: bun run check
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm check
builds:
name: Builds
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Build All Packages
run: bun run buildc all
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm buildc all
build-demo:
name: Build Demo
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Build
run: bun run build:all
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm build:all
working-directory: packages/wxt-demo
- name: ZIP
run: bun run wxt zip
- run: pnpm wxt zip
working-directory: packages/wxt-demo
tests:
name: Tests (${{ matrix.title }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- title: 'Linux'
os: ubuntu-22.04
coverage: true
- title: 'Windows'
os: windows-latest
coverage: false
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Setup PNPM
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: latest
- name: Run Tests
if: ${{ ! matrix.coverage }}
run: bun run test
- name: Run Tests (Coverage)
if: matrix.coverage
run: bun run test:coverage --reporter=default --reporter=hanging-process
- name: Upload Coverage
if: matrix.coverage
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- uses: oven-sh/setup-bun@v2
- name: pnpm test:coverage
run: pnpm test:coverage -- --reporter=default --reporter=hanging-process
- uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
windows-tests:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm test
template:
name: Template
runs-on: ubuntu-22.04
strategy:
fail-fast: false
@@ -105,34 +57,19 @@ jobs:
- vanilla
- vue
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Pack WXT package
run: bun pm pack
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm pack
working-directory: packages/wxt
- name: Install Dependencies
run: npm i
- run: npm i
working-directory: templates/${{ matrix.template }}
- name: Install Packed WXT
run: npm i -D ../../packages/wxt/wxt-*.tgz
- run: npm i -D ../../packages/wxt/wxt-*.tgz
working-directory: templates/${{ matrix.template }}
- name: Type Check Template
run: bun run compile
- run: pnpm compile
if: matrix.template != 'svelte'
working-directory: templates/${{ matrix.template }}
- name: Type Check Template
run: bun run check
- run: pnpm check
if: matrix.template == 'svelte'
working-directory: templates/${{ matrix.template }}
- name: Build Template
run: bun run build
- run: pnpm build
working-directory: templates/${{ matrix.template }}
-51
View File
@@ -1,51 +0,0 @@
name: 📼 VHS
on:
push:
paths:
- 'docs/tapes/*.tape'
workflow_dispatch:
permissions:
contents: read
jobs:
vhs:
name: Create VHS
runs-on: macos-latest
if: ${{ github.repository == 'wxt-dev/wxt' }}
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup
uses: ./.github/actions/setup
- name: Setup Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16
with:
go-version: '1.25.1'
# This prevents bunx from downloading WXT in the video, and it uses the checked-out version
- name: Build WXT
working-directory: packages/wxt
run: bun run build
- name: Record VHS
run: |
brew install ttyd ffmpeg
go install github.com/charmbracelet/vhs@517bcda0faf416728bcf6b7fe489eb0e2469d9b5 # v0.10.0
vhs docs/tapes/init-demo.tape
- name: Save recorded GIF
uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commit_message: 'docs: Update `wxt init` GIF'
commit_user_name: github-actions[bot]
commit_user_email: github-actions[bot]@users.noreply.github.com
commit_author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
# https://github.com/charmbracelet/vhs#output
file_pattern: 'docs/assets/*.gif'
+2 -5
View File
@@ -1,16 +1,13 @@
.DS_Store
.env
.env.*
!packages/wxt/src/core/utils/__tests__/fixtures/.env
.idea
.output
.webextrc
.wxt
.wxt-runner
*.log
*.tgz
/.vscode/
/docs/.vitepress/cache
docs/.vitepress/.temp
coverage
dist
node_modules
@@ -22,5 +19,5 @@ templates/*/yarn.lock
templates/*/package-lock.json
docs/api/reference
stats.html
.tool-versions
.cache
*-stats.txt
-6
View File
@@ -1,6 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/refs/heads/main/schema/markdownlint-config-schema.json",
"line-length": false,
"no-inline-html": false,
"first-line-heading": false
}
-9
View File
@@ -1,9 +0,0 @@
node_modules
.git
.output
dist
# Generated files
packages/wxt/README.md
packages/*/CHANGELOG.md
docs/api
-1
View File
@@ -5,4 +5,3 @@ dist
docs/.vitepress/cache
pnpm-lock.yaml
CHANGELOG.md
packages/browser/src/gen
-2
View File
@@ -1,4 +1,2 @@
singleQuote: true
endOfLine: lf
plugins:
- prettier-plugin-jsdoc
-2
View File
@@ -1,2 +0,0 @@
bun 1.3.13
nodejs 24.14.1
-7
View File
@@ -1,7 +0,0 @@
{
"recommendations": [
"davidanson.vscode-markdownlint",
"esbenp.prettier-vscode",
"github.vscode-github-actions"
]
}
-15
View File
@@ -1,15 +0,0 @@
{
// Set default formatter
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
// Additional guidelines for Copilot
"github.copilot.chat.codeGeneration.instructions": [
{ "file": "CONTRIBUTING.md" }
],
"oxc.enable": true
}
-128
View File
@@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<aaronklinker1@gmail.com>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
<https://www.contributor-covenant.org/version/2/0/code_of_conduct.html>.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
<https://www.contributor-covenant.org/faq>. Translations are available at
<https://www.contributor-covenant.org/translations>.
+31 -138
View File
@@ -1,59 +1,29 @@
# Contributing
Everyone is welcome to contribute to **WXT**!
Everyone is welcome to contribute to WXT!
If you are changing the docs or fixing a bug, feel free to fork and open a PR.
If you want to add a new feature, please create an issue or discussion first so we can decide if the feature is inline with the vision for WXT.
## WXT's Vision
WXT is two things:
1. A build tool
2. A set of runtime utilities
The long term goal of WXT is provide an opinionated build tool that keeps WXT projects standard, while providing light-weight runtime utils that simplify a lot of the boilerplate/overhead when setting up a new extension.
I also want to provide a way for developers to use either one of those two things independently, and not require them to use both. This is why all of WXT's runtime utils are shipped as their own NPM packages, most of them not bundled inside the core `wxt` package. If you just want to use the packages, they're available, and if you just want to use WXT's built tool, you don't have to import any of WXT's utilities, you can use your own.
> The few runtime utils shipped inside WXT are things that should be used by 90% of extensions. That said, they're also legacy utils left in from before I started creating separate NPM packages, and in the future, they may be removed from the core package.
## Conventional Commits
This project uses [Conventional Commit format](https://www.conventionalcommits.org/en/v1.0.0/) to automatically generate a changelog and better understand the changes in the project
This project uses [Conventional Commits](https://www.conventionalcommits.org/en) to automate versioning. If you're a new contributor, don't worry about this. When you open a PR, a maintainer will change the PR's title so it's in the style of conventional commits, but that's all.
Here are some examples of conventional commit messages:
- `feat: add new functionality`
- `fix: correct typos in code`
- `ci: add GitHub Actions for automated testing`
## Conventional PR Titles
The title of your pull request should follow the [conventional commit format](#conventional-commits). When a pull request is merged to the main branch, all changes are going to be squashed into a single commit. The message of this commit will be the title of the pull request. And for every release, the commit messages are used to generate the changelog.
## Breaking Changes Policy
A quick word on WXT's breaking changes policy. I am willing to make breaking changes, but they have to be for a good enough reason - they have to make WXT better as a whole, they can't be based on one opinion.
Breaking changes also require a major release. Major releases have happened once or twice a year, so after you merge your PR, you'll have to wait a little bit before it is released.
To make a breaking change:
1. Make sure you're PR is targeting the `major` branch
2. Add `!` after the conventional commit type (`fix!: ...`, `feat!: ...`, `chore!: ...`, etc) to indicate that it is a breaking change
3. At the top of the PR, provide documentation that will inform developers about the breaking change, why it was done, and how to migrate their extension so nothing breaks.
- This documentation will be put ["Upgrading WXT"](https://wxt.dev/guide/resources/upgrading.html) page in the docs, read through previous breaking change docs for an idea of what is required.
Maintainers, commits to the `main` branch (either directly or via PRs) must be valid conventional commits.
## Setup
WXT uses Bun for package management and development. Install it from: <https://bun.com/>
Then install dependencies:
WXT uses `pnpm`, so make sure you have it installed.
```sh
bun install
corepack enable
```
Then, simply run the install command:
```sh
pnpm i
```
## Development
@@ -61,68 +31,32 @@ bun install
Here are some helpful commands:
```sh
# Build WXT package and workspace dependencies
bun run --filter wxt build
# Build WXT package
pnpm build
```
```sh
# Build workspace dependencies, then start the demo extension in dev mode
bun run --filter wxt-demo dev
# Build WXT package, then build demo extension
cd packages/wxt-demo
pnpm build
```
```sh
# Build WXT package, then start the demo extension in dev mode
cd packages/wxt-demo
pnpm dev
```
```sh
# Run unit and E2E tests
bun run test
pnpm test
```
```sh
# Start the docs website locally
bun run docs:dev
pnpm docs:dev
```
> Above, we used bun's `--filter` flag to choose which package to run a command in, but there are other ways:
>
> ```sh
> bun run -F @wxt-dev/i18n build
> # or
> bun run --cwd packages/i18n build
> # or
> cd packages/i18n
> bun run build
> ```
>
> Pick your poison!
## Profiling
```sh
# Build the latest version
bun run --filter wxt build
# CD to the demo directory
cd packages/wxt-demo
```
Then there are a few different ways to profile WXT commands:
- Generate a flamechart with 0x:
```sh
bunx 0x node_modules/wxt/bin/wxt.mjs build
```
- Create a CPU profile:
```sh
bun run --cpu-prof node_modules/wxt/bin/wxt.mjs build
```
- Debug the process:
```sh
bun run --inspect node_modules/wxt/bin/wxt.mjs build
```
## Updating Docs
Documentation is written with VitePress, and is located in the `docs/` directory.
@@ -138,7 +72,7 @@ WXT has unit and E2E tests. When making a change or adding a feature, make sure
To run tests for a specific file, add the filename at the end of the test command:
```sh
bun run --filter wxt test manifest-contents
pnpm test manifest-contents
```
All test (unit and E2E) for all packages are ran together via [Vitest workspaces](https://vitest.dev/guide/#workspaces-support).
@@ -147,7 +81,7 @@ If you want to manually test a change, you can modify the demo project for your
## Templates
Each directory inside `templates/` is it's own standalone project. Simply `cd` into the directory you're updating, install dependencies with `npm` (NOT `bun`), and run the relevant commands
Each directory inside `templates/` is it's own standalone project. Simply `cd` into the directory you're updating, install dependencies with `npm` (NOT `pnpm`), and run the relevant commands
```sh
cd templates/vue
@@ -163,7 +97,7 @@ Note that templates are hardcoded to a specific version of `wxt` from NPM, they
"typescript": "^5.3.2",
"vite-plugin-solid": "^2.7.0",
- "wxt": "^0.8.0"
+ "wxt": "../../packages/wxt"
+ "wxt": "../.."
}
```
@@ -188,51 +122,10 @@ Releases are done with GitHub actions:
## Upgrading Dependencies
WXT has custom rules around what dependencies can be upgraded. Use the `scripts/upgrade-deps.ts` script to upgrade dependencies and follow these rules.
Use [`taze`](https://www.npmjs.com/package/taze) to upgrade dependencies throughout the entire monorepo.
```sh
bun run scripts/upgrade-deps.ts
```ts
pnpm dlx taze -r
```
To see all the options, run:
```sh
bun run scripts/upgrade-deps.ts --help
```
## Install Unreleased Versions
This repo uses <https://pkg.pr.new> to publish versions of all it's packages for almost every commit. You can install them via:
```sh
npm i https://pkg.pr.new/[package-name]@[ref]
```
Or use one of the shorthands:
```sh
# Install the latest build of `wxt` from a PR:
npm i https://pkg.pr.new/wxt@1283
# Install the latest build of `@wxt-dev/module-react` on the `main` branch
npm i https://pkg.pr.new/@wxt-dev/module-react@main
# Install `@wxt-dev/storage` from a specific commit:
npm i https://pkg.pr.new/@wxt-dev/module-react@426f907
```
## 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 don'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.
## Become a Maintainer
If you're interested in becoming a maintainer, send an email to Aaron at <aaronklinker1@gmail.com> with your github username saying you're interested. The process is very informal, I will add you quickly if you've contributed code or answered questions and helped out the community!
Maintainers don't have to just write code - they can manage issues, answer questions, review PRs, organize and prioritize work - there's lots of ways for you to help out.
Configuration is in [`taze.config.ts`](./taze.config.ts).
-90
View File
@@ -1,90 +0,0 @@
# Maintainers
A couple of things for you to consider before merging a PR or giving the go-ahead on a feature proposal.
## Be picky about new features and packages
We are responsible for maintaining them and fixing related bugs after the PR is merged. The community can always release their own WXT modules or packages, not everything needs to be built into WXT.
## Prefer standards over customization
Don't merge PRs that just add another way to do something, like [this one](https://github.com/wxt-dev/wxt/pull/2053#issuecomment-3857010196).
WXT is opinionated, if you have questions about what is WXT's opinion or we need to create a new one, create an issue and @ me to discuss.
## PRs should be small and targeted
A PR should make one change. They should not make any unrelated changes outside of accomplishing the one thing. This makes PRs easier to review and they get merged more quickly - a win-win for everyone.
## `@wxt-dev/*` packages are separate
We can't make changes to these packages assuming people are using them only with WXT. For example, I almost missed [this PR](https://github.com/wxt-dev/wxt/pull/2049#issuecomment-3861251599).
I want these packages to be usable on their own so if people don't like WXT's build tool, they can still use our other awesome packages.
## Minimize dependencies
I don't like how heavy lots of frameworks are. It's unavoidable to a certain extent, but if you can do something without another dependency, try to.
If you need a dependency, look for one with zero dependencies and that's respectable.
## Look at milestones
I've organized WXT's long term plans into [milestones](https://github.com/wxt-dev/wxt/milestones), check those out to get an idea of my priorities for some of the larger features.
## Require tests
If someone opens a PR to fix a bug but doesn't include tests, don't merge the PR. Tests are required to prevent regressions and maintain a codebase long term.
## Ask for reproductions for bugs
You don't need to triage bugs if someone doesn't give you enough information. You can always ask for a repo with a reproduction or wait for more details.
If there's not an easy way to reproduce a bug, you're wasting your time triaging it. Just be mindful of your own time!
Here's an example of how to ask for a reproduction: <https://github.com/wxt-dev/wxt/issues/2064#issuecomment-3862579110>. You could be more blunt than this.
## Add yourself as a code owner
If you want to be responsible for a specific package or directory, add yourself to the [`.github/CODEOWNERS`](https://github.com/wxt-dev/wxt/blob/main/.github/CODEOWNERS) file to get added as a reviewer to PRs automatically. You can also add yourself to the default list to be added as a reviewer on all PRs.
## Releasing Package Updates
Releases are done with GitHub actions:
- Use the [Release workflow](https://github.com/wxt-dev/wxt/actions/workflows/release.yml) to release a single package in the monorepo. This automatically detects the version change with conventional commits, builds and uploads the package to NPM, and creates a GitHub release.
- Use the [Sync Releases workflow](https://github.com/wxt-dev/wxt/actions/workflows/sync-releases.yml) to sync the GitHub releases with changes to the changelog. To change a release, update the `CHANGELOG.md` file and run the workflow. It will sync the releases of a single package in the monorepo.
## Creating New Packages
Example PR: <https://github.com/wxt-dev/wxt/pull/2152>
1. Create the package.
2. Update CI workflow inputs.
3. Add docs page and version for "Other Packages" dropdown.
4. Merge the PR.
5. Tag the commit (look at other tags for pattern):
```sh
git tag <dir-name>-v<version>
git push --tags
```
6. Publish the package to NPM:
```sh
cd packages/<dir-name>
bun publish --access public
```
7. Create a basic release on GitHub mentioning the new package is available.
A couple of things to note:
- pkg.pr.new will fail on the original PR. It's fine to ignore and merge your PR as long as it fails due to your new package not being published to NPM yet.
- The regular release workflow DOES NOT WORK for new packages. You have to have at least one `<dir-name>-v<version>` tag created before you can run that workflow for your new package.
- You don't need to create a CHANGELOG.md file for the package, it will be created automatically after future changes are released via the normal release workflow.
+37 -35
View File
@@ -1,41 +1,50 @@
<div align="center">
<h1 align="center">
<img style="vertical-align:middle" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
<span>WXT</span>
</h1>
# <img align="top" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo"> WXT
<p align="center">
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="NPM" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
</p>
[![npm version](https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048)](https://www.npmjs.com/package/wxt)
[![downloads](https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048)](https://www.npmjs.com/package/wxt)
[![license | MIT](https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048)](https://github.com/wxt-dev/wxt/blob/main/LICENSE)
[![coverage](https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048)](https://codecov.io/github/wxt-dev/wxt)
<p align="center">
<span>Next-gen framework for developing web extensions.</span>
<br/>
<span>⚡</span>
<br/>
<q><i>It's like Nuxt, but for Web Extensions</i></q>
</p>
Next-gen framework for developing web extensions.<br/>⚡<br/><q><i>It's like Nuxt, but for Web Extensions</i></q>
[Get Started](https://wxt.dev/guide/installation.html) •
[Configuration](https://wxt.dev/api/config.html) •
[Examples](https://wxt.dev/examples.html) •
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md) •
[Discord](https://discord.gg/ZFsZqGery9)
</div>
<p align="center">
<a href="https://wxt.dev/guide/installation.html" target="_blank">Get Started</a>
&bull;
<a href="https://wxt.dev/api/config.html" target="_blank">Configuration</a>
&bull;
<a href="https://wxt.dev/examples.html" target="_blank">Examples</a>
&bull;
<a href="https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md" target="_blank">Changelog</a>
&bull;
<a href="https://discord.gg/ZFsZqGery9" target="_blank">Discord</a>
</p>
![Example CLI Output](https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/assets/cli-output.png)
## Demo
<https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94>
https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94
## Quick Start
Bootstrap a new project:
```sh
# npm
npx wxt@latest init
# pnpm
pnpm dlx wxt@latest init
# bun
bunx wxt@latest init
pnpm dlx wxt@latest init <project-name>
```
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
@@ -50,20 +59,13 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
- 🦾 Auto-imports
- 🤖 Automated publishing
- 🎨 Frontend framework agnostic: works with Vue, React, Svelte, etc
- 📦 [Module system](https://wxt.dev/guide/essentials/wxt-modules.html#overview) for reusing code between extensions
- 🖍️ Quickly bootstrap a new project
- 📏 Bundle analysis
- ⬇️ Download and bundle remote URL imports
## Sponsors
WXT is a [MIT-licensed](https://github.com/wxt-dev/wxt/blob/main/LICENSE) open source project with its ongoing development made possible entirely by the support of these awesome backers. If you'd like to join them, please consider [sponsoring WXT's development](https://github.com/sponsors/wxt-dev).
[![WXT Sponsors](https://raw.githubusercontent.com/wxt-dev/static/refs/heads/main/sponsorkit/sponsors.svg)](https://github.com/sponsors/wxt-dev)
- 📦 Modular architecture with [modules](https://wxt.dev/guide/go-further/reusable-modules.html#overview)
## Contributors
Published under the [MIT](https://github.com/wxt-dev/wxt/blob/main/LICENSE) license.
Made by [@aklinker1](https://github.com/aklinker1) and [community](https://github.com/wxt-dev/wxt/graphs/contributors) 💛
[![WXT contributors](https://contrib.rocks/image?repo=wxt-dev/wxt)](https://github.com/wxt-dev/wxt/graphs/contributors)
<a href="https://github.com/wxt-dev/wxt/graphs/contributors">
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
</a>
-11
View File
@@ -1,11 +0,0 @@
# Security Policy
While WXT is in prerelease, only the latest version will receive security updates. The latest version is:
<img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048">
## Reporting a Vulnerability
If you discover a security vulnerability, please email me at <aaronklinker1@gmail.com>. I will respond within a few days to acknowledge receipt of your report.
If the vulnerability is accepted, I will open a public issue to track the fix. If the vulnerability is not accepted, no further action will be taken.
-3047
View File
File diff suppressed because it is too large Load Diff
-123
View File
@@ -1,123 +0,0 @@
version: '0.2'
ignorePaths:
- pnpm-lock.yaml
- '**/node_modules/**'
- '**/dist/**'
- '**/coverage/**'
- docs/api/**
- '**/CHANGELOG.md'
- docs/public/_redirects
- packages/browser/src/gen/**
- patches/**
- '**/*.snap'
- docs/assets/extension-showcase.yml
- packages/*/stats.html
words:
- Aabid
- aabidk
- aklinker
- automount
- avenir
- bidi
- buildc
- bunx
- cachable
- cacjs
- charmbracelet
- chromedriver
- Cira
- comctx
- confbox
- configfile
- consolas
- Cpath
- crbug
- cros
- crunchyroll
- cssinjs
- CSUI
- Csvg
- defu
- deinit
- dequal
- dtrace
- duckduckgo
- esbuild
- esque
- extglob
- favicons
- fieldtrials
- firefoxdeveloperedition
- flamechart
- Florian
- focusring
- frontmatter
- giget
- grayscale
- greyscale
- gtag
- hckhakegfgenefhikdcfkaaonnclljmf
- hookable
- iconify
- jsons
- Kenobi
- keystyle
- klinker
- linkedom
- llms
- locationchange
- magicast
- Menlo
- menupanel
- metas
- moderok
- mktemp
- mycompany
- nacl
- nanospinner
- natsu
- newtab
- njsproj
- nodebuffer
- Noto
- ntvs
- nypm
- obug
- ohash
- oklab
- oxlint
- pbbf
- personaltoolbar
- portaling
- prebundled
- prefs
- proxified
- pWXLO
- repost
- rxliuli
- sandboxed
- scule
- Segoe
- sidepanel
- sidepanels
- solidjs
- styl
- subdependency
- tabstrip
- teleporting
- treemap
- triaging
- umami
- ungh
- unimport
- unocss
- unrun
- unstub
- unwatch
- USSS
- vitepress
- webext
- webextension
- webextrc
- xcrun
- xlink
+116
View File
@@ -0,0 +1,116 @@
# Development
WXT's main goal is providing the best DX it possibly can. When running your extension in dev mode, each part of your extension is reloaded separately when possible.
| | HMR | Reloaded individually | Reload extension | Restart browser |
| ------------------- | :-: | :-------------------: | :--------------: | :----------------------------------------------------: |
| HTML File | | ✅ |
| HTML Dependency | ✅ |
| MV3 Content Script | | ✅ |
| MV2 Content Script | | | ✅ |
| Background | | | ✅ |
| manifest.json | | | | 🟡 See [#16](https://github.com/wxt-dev/wxt/issues/16) |
| `wxt.config.ts` | | | | 🟡 See [#10](https://github.com/wxt-dev/wxt/issues/10) |
| `web-ext.config.ts` | | | | 🟡 See [#10](https://github.com/wxt-dev/wxt/issues/10) |
## Dev Mode vs Production Builds
There are some notable differences between the development and production versions of an extension. During development:
1. **Content scripts are not listed in the `manifest.json`** when targeting MV3. Instead, the [`scripting`](https://developer.chrome.com/docs/extensions/reference/api/scripting) permission is used to register content scripts at runtime so they can be reloaded individually.
To get the list of content scripts during development, run the following in the background's console:
```ts
await chrome.scripting.getRegisteredContentScripts();
```
2. **The CSP is modified to allow loading scripts from the dev server**. Make sure you're using Chrome v110 or above for HMR to work.
3. If you don't include a background script/service worker, one will be created to perform various tasks in dev mode, mostly related to reloading different parts of the extension on change.
For production builds, none of the above modifications will be applied, and you're extension/manifest will only include what you have defined.
## Configure Browser Startup
WXT uses [`web-ext` by Mozilla](https://github.com/mozilla/web-ext) to automatically open a browser with the extension installed. You can configure the runner's behavior via the [`runner`](/api/reference/wxt/interfaces/InlineConfig#runner) option, or in a separate gitignored file, `web-ext.config.ts`.
:::code-group
```ts [wxt.config.ts]
import { defineConfig } from 'wxt';
export default defineConfig({
runner: {
// Runner config
},
});
```
```ts [web-ext.config.ts]
import { defineRunnerConfig } from 'wxt';
export default defineRunnerConfig({
// Runner config
});
```
:::
### Browser Binaries
`web-ext`'s browser discovery is very limited. By default, it only guesses at where Chrome and Firefox are installed. If you've customized your install locations, you may need to tell `web-ext` where the binaries/executables are located using the [`binaries` option](/api/reference/wxt/interfaces/ExtensionRunnerConfig#binaries). For other Chromium based browsers, like Edge or Opera, you'll need to explicitly list them in the `binaries` option as well, otherwise they will open in Chrome by default.
```ts
// ~/web-ext.config.ts
import { defineRunnerConfig } from 'wxt';
export default defineRunnerConfig({
binaries: {
chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome
firefox: 'firefoxdeveloperedition', // Use Firefox Developer Edition instead of regular Firefox
edge: '/path/to/edge', // Open MS Edge when running "wxt -b edge"
},
});
```
:::tip
When configuring browser binaries, it's helpful to put them in `~/web-ext.config.ts` instead of the project directory's `web-ext.config.ts` file. When placed in your home directory (`~/`), this config will be used by all WXT projects, so you only need to configure the binaries once.
:::
### Other options
You can customize other options as well, like startup URLs, profiles, or additional command line arguments:
```ts
// web-ext.config.ts
import { defineRunnerConfig } from 'wxt';
export default defineRunnerConfig({
startUrls: ['https://google.com', 'https://duckduckgo.com'],
chromiumProfile: '/path/to/profile/to/use',
chromiumArgs: ['--window-size=400,300'],
});
```
For a full list of options, see the [API Reference](/api/reference/wxt/interfaces/ExtensionRunnerConfig).
## Reload the Extension
Normally, to manually reload an extension, you have to visit `chrome://extensions` and click the reload button for your extension.
When running `wxt` command to start the dev server, WXT adds a keyboard shortcut `Alt+R`, that reloads the extension when pressed, without visiting `chrome://extensions`. This can also be customized or disabled:
```ts [wxt.config.ts]
import { defineConfig } from 'wxt';
export default defineConfig({
dev: {
reloadCommand: 'Alt+T', // false, to disable
},
});
```
:::info
This shortcut is only available during development, and is not be added to your extension when running `wxt build` or `wxt-zip`.
:::
-65
View File
@@ -1,65 +0,0 @@
<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';
import { Post } from '../utils/types';
const posts = computed<Post[]>(() =>
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 class="inner-container">
<h1>Blog</h1>
<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;
.inner-container {
padding: 32px;
max-width: 900px;
width: 100%;
h1 {
font-size: 32px;
font-weight: 600;
margin-top: 10px;
padding-bottom: 16px;
}
ul {
display: flex;
flex-direction: column;
li {
padding-top: 16px;
margin-top: 16px;
&:last-child {
padding-bottom: 16px;
margin-bottom: 16px;
}
}
}
}
}
</style>
-79
View File
@@ -1,79 +0,0 @@
<script lang="ts" setup>
import { Ref } from 'vue';
import useBlogDate from '../composables/useBlogDate';
import { Content, useData } from 'vitepress';
import { PostFrontmatter } from '../utils/types';
const { frontmatter } = useData() as unknown as {
frontmatter: Ref<PostFrontmatter>;
};
const date = useBlogDate(() => frontmatter.value.date);
</script>
<template>
<div class="vp-doc">
<main class="container">
<h1 v-html="frontmatter.title" />
<p class="meta-row">
<a
v-for="author of frontmatter.authors"
:key="author.github"
:href="`https://github.com/${author.github}`"
class="author"
>
<img
:src="`https://github.com/${author.github}.png?size=96`"
alt="Author's avatar"
/>
<span>{{ author.name }}</span>
</a>
<span>&bull;</span>
<span>{{ date }}</span>
</p>
<Content />
</main>
</div>
</template>
<style scoped>
.vp-doc {
display: flex;
.container {
max-width: 1080px;
padding: 32px;
margin: auto;
@media (min-width: 768px) {
padding: 64px;
}
}
.meta-row {
display: flex;
color: var(--vp-c-text-2);
gap: 16px;
overflow: hidden;
padding-bottom: 32px;
.author {
display: flex;
gap: 8px;
align-items: center;
color: var(--vp-c-text-2);
font-weight: normal;
text-decoration: none;
:hover {
text-decoration: underline;
}
img {
width: 24px;
border-radius: 100%;
}
}
}
}
</style>
@@ -1,53 +0,0 @@
<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(', ') }}
&bull;
{{ date }}
</p>
</div>
</a>
</li>
</template>
<style scoped>
.vp-doc {
* {
margin-block: 10px;
}
.title {
color: var(--vp-c-text-1);
}
.description {
color: var(--vp-c-text-2);
}
.meta {
font-weight: 400;
font-size: 12px;
color: var(--vp-c-text-2);
}
}
</style>
@@ -1,65 +1,40 @@
<script lang="ts" setup>
defineProps<{
const props = defineProps<{
patterns: Array<[intput: string, output: string]>;
}>();
</script>
<template>
<table class="no-vertical-dividers entrypoint-patterns">
<table class="no-vertical-dividers">
<thead>
<tr>
<th class="col-filename">Filename</th>
<th>Input Pattern</th>
<th></th>
<th>Output Path</th>
</tr>
</thead>
<tbody>
<tr v-for="pattern of patterns" :key="pattern[0]">
<td class="cell-input">
<tr v-for="pattern of patterns">
<td style="white-space: nowrap">
<code>entrypoints/{{ pattern[0] }}</code>
</td>
<td class="cell-arrow">
<td style="padding: 6px; opacity: 50%">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4 11v2h12l-5.5 5.5l1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5L16 11H4Z"
fill="currentColor"
d="M4 11v2h12l-5.5 5.5l1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5L16 11H4Z"
/>
</svg>
</td>
<td class="cell-output">
<td style="white-space: nowrap">
<code>/{{ pattern[1] }}</code>
</td>
</tr>
</tbody>
</table>
</template>
<style scoped>
.entrypoint-patterns {
width: 100%;
.col-filename {
width: 100%;
}
.cell-input {
white-space: nowrap;
padding-right: 8px;
}
.cell-arrow {
padding: 6px;
opacity: 0.5;
}
.cell-output {
white-space: nowrap;
padding-left: 8px;
}
}
</style>
+120 -62
View File
@@ -1,19 +1,14 @@
<script lang="ts" setup>
import { computed, onMounted, ref, Ref, toRaw } from 'vue';
import { ref, onMounted, computed, toRaw, Ref } from 'vue';
import ExampleSearchFilterByItem from './ExampleSearchFilterByItem.vue';
import ExampleSearchResult from './ExampleSearchResult.vue';
import { ExamplesMetadata, KeySelectedObject } from '../utils/types';
defineProps<{
const props = defineProps<{
tag?: string;
}>();
const exampleMetadata = ref<ExamplesMetadata>({
allApis: [],
allPackages: [],
allPermissions: [],
examples: [],
});
const exampleMetadata = ref<ExamplesMetadata>();
onMounted(async () => {
const res = await fetch(
'https://raw.githubusercontent.com/wxt-dev/examples/main/metadata.json',
@@ -53,7 +48,7 @@ function doesExampleMatchSelected(
const filteredExamples = computed(() => {
const text = searchText.value.toLowerCase();
return exampleMetadata.value?.examples.filter((example) => {
return exampleMetadata.value.examples.filter((example) => {
const matchesText = example.searchText.toLowerCase().includes(text);
const matchesApis = doesExampleMatchSelected(example.apis, requiredApis);
const matchesPermissions = doesExampleMatchSelected(
@@ -77,19 +72,19 @@ const filteredExamples = computed(() => {
<div class="filters">
<ExampleSearchFilterByItem
v-model="selectedApis"
:items="exampleMetadata?.allApis"
label="APIs"
:items="exampleMetadata?.allApis"
v-model="selectedApis"
/>
<ExampleSearchFilterByItem
v-model="selectedPermissions"
:items="exampleMetadata?.allPermissions"
label="Permissions"
:items="exampleMetadata?.allPermissions"
v-model="selectedPermissions"
/>
<ExampleSearchFilterByItem
v-model="selectedPackages"
:items="exampleMetadata?.allPackages"
label="Packages"
:items="exampleMetadata?.allPackages"
v-model="selectedPackages"
/>
</div>
@@ -112,57 +107,120 @@ const filteredExamples = computed(() => {
<style scoped>
.example-layout {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
grid-template-areas:
'filters search'
'filters results';
'search'
'results';
gap: 16px;
@media only screen and (min-width: 710px) {
}
@media only screen and (min-width: 720px) {
.example-layout {
grid-template-columns: 256px 1fr;
grid-template-rows: auto 1fr;
}
.search {
grid-area: search;
background: var(--vp-c-bg-soft);
padding: 20px;
border-radius: 16px;
input {
font-size: 16px;
}
}
.filters {
grid-area: filters;
display: flex;
flex-direction: column;
gap: 10px;
@media only screen and (min-width: 710px) {
border-radius: 16px;
}
}
.results {
grid-area: results;
.search-results {
display: grid;
gap: 16px;
@media only screen and (min-width: 800px) {
grid-template-columns: repeat(2, 1fr);
}
@media only screen and (min-width: 1024px) {
grid-template-columns: repeat(3, 1fr);
}
a {
background-color: red;
}
}
grid-template-areas:
'filters search'
'filters results';
}
}
.search {
grid-area: search;
background: var(--vp-c-bg-soft);
padding: 20px;
width: 100%;
display: flex;
border-radius: 16px;
}
.filters {
display: none;
grid-area: filters;
}
@media only screen and (min-width: 720px) {
.filters {
display: flex;
flex-direction: column;
gap: 2px;
border-radius: 16px;
overflow: hidden;
align-self: flex-start;
}
}
.results {
grid-area: results;
}
.box {
border-radius: 16px;
overflow: hidden;
}
.search input {
min-width: 0;
flex: 1;
font-size: 16px;
}
.checkbox-col {
flex: 1;
padding: 16px;
display: flex;
flex-direction: column;
overflow-y: auto;
max-height: 200px;
font-size: 14px;
gap: 4px;
}
.filter-btn {
color: var(--vp-c-brand-1);
}
.checkbox-col .header {
font-size: 12px;
font-weight: bold;
opacity: 50%;
}
.checkbox-col p {
display: flex;
gap: 4px;
align-items: flex-start;
text-wrap: wrap;
overflow-wrap: anywhere;
line-height: 140%;
}
span {
padding-top: 1px;
}
.checkbox-col input[type='checkbox'] {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.checkbox-col-container {
display: flex;
}
.search-results {
display: grid;
grid-template-columns: repeat(1, 1fr);
gap: 16px;
}
@media only screen and (min-width: 800px) {
.search-results {
grid-template-columns: repeat(2, 1fr);
}
}
@media only screen and (min-width: 1024px) {
.search-results {
grid-template-columns: repeat(3, 1fr);
}
}
a {
background-color: red;
}
</style>
@@ -2,7 +2,7 @@
import { computed, toRaw } from 'vue';
import { KeySelectedObject } from '../utils/types';
defineProps<{
const props = defineProps<{
label: string;
items?: string[];
}>();
@@ -29,12 +29,12 @@ function toggleItem(pkg: string) {
<span>Filter by {{ label }}</span> <span v-if="count">({{ count }})</span>
</p>
<div class="scroll-container">
<ul class="items">
<ul>
<li v-for="item in items">
<label :title="item" class="item-label">
<label :title="item">
<input
:checked="selectedItems[item]"
type="checkbox"
:checked="selectedItems[item]"
@input="toggleItem(item)"
/>
<span>{{ item }}</span>
@@ -51,45 +51,50 @@ function toggleItem(pkg: string) {
display: flex;
flex-direction: column;
background: var(--vp-c-bg-soft);
border-radius: 20px;
scrollbar-width: thin;
scrollbar-color: var(--vp-c-brand-3) transparent;
padding-bottom: 10px;
}
.header {
padding: 8px 16px;
font-size: 12px;
font-weight: bold;
opacity: 50%;
}
.scroll-container {
flex: 1;
overflow: hidden;
position: relative;
}
.scroll-container {
flex: 1;
position: relative;
.scroll-container ul {
position: absolute;
overflow-y: auto;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
gap: 4px;
font-size: small;
padding: 8px 16px 16px 16px;
}
.items {
position: absolute;
overflow-y: auto;
inset: 0;
display: flex;
flex-direction: column;
gap: 4px;
font-size: small;
padding: 8px 16px 16px;
.item-label {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
input[type='checkbox'] {
width: 16px;
height: 16px;
flex-shrink: 0;
}
}
}
}
.header {
padding: 8px 16px;
font-size: 12px;
font-weight: bold;
opacity: 50%;
}
label {
display: flex;
gap: 4px;
align-items: flex-start;
text-wrap: wrap;
overflow-wrap: anywhere;
line-height: 140%;
cursor: pointer;
text-wrap: nowrap;
}
span {
padding-top: 1px;
}
input[type='checkbox'] {
width: 16px;
height: 16px;
flex-shrink: 0;
}
</style>
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { Example } from '../utils/types';
defineProps<{
const props = defineProps<{
example: Example;
}>();
</script>
@@ -17,45 +17,56 @@ defineProps<{
</template>
<style scoped>
* {
min-width: 0;
}
a {
padding: 16px;
display: flex;
flex-direction: column;
border: 2px solid var(--vp-c-bg-soft);
border-radius: 16px;
color: var(--vp-c-text-1) !important;
gap: 8px;
}
&:hover {
outline: 2px solid var(--vp-c-brand-2);
a:hover {
outline: 2px solid var(--vp-c-brand-2);
}
.name {
font-size: 16px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
.link {
opacity: 100%;
}
}
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.description {
height: 53px;
opacity: 70%;
font-size: 14px;
font-weight: normal;
line-height: 120%;
min-height: 0;
.name {
font-weight: 500;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.description {
opacity: 70%;
font-size: 14px;
line-height: 130%;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.link {
opacity: 0;
transition: 250ms;
color: var(--vp-c-brand-2);
font-weight: bold;
text-align: right;
}
.link {
opacity: 0;
transition: 250ms;
color: var(--vp-c-brand-2);
font-weight: bold;
text-align: right;
}
a:hover .link {
opacity: 100%;
}
</style>
+97 -199
View File
@@ -1,138 +1,86 @@
<script lang="ts" setup>
import { computed } from 'vue';
import useListExtensionDetails from '../composables/useListExtensionDetails';
import _extensionEntries from '../../assets/extension-showcase.yml';
import useListExtensionDetails, {
ChromeExtension,
} from '../composables/useListExtensionDetails';
interface StoreLink {
label: string;
url: string;
}
interface ListedExtension {
id: string;
name: string;
iconUrl: string;
shortDescription: string;
users: number;
rating: number | undefined;
stores: StoreLink[];
}
const extensionEntries = _extensionEntries as Array<{
chromeId?: string;
firefoxSlug?: string;
edgeId?: string;
}>;
const chromeIds = extensionEntries.flatMap((e) =>
e.chromeId ? [e.chromeId] : [],
);
const firefoxSlugs = [
...new Set(
extensionEntries.flatMap((e) => (e.firefoxSlug ? [e.firefoxSlug] : [])),
),
// Add extension IDs here. Order doesn't matter, will be sorted by weekly active users
// During the transition from chrome.google.com/webstore to
// chromewebstore.google.com, queue.wxt.dev might return null for your
// extension. If it does, use "<slug>/<id>" instead of just the ID. The slug
// can be retrieved from the URL of the item on chromewebstore.google.com
const chromeExtensionIds = [
'ocfdgncpifmegplaglcnglhioflaimkd', // GitHub: Better Line Counts
'mgmdkjcljneegjfajchedjpdhbadklcf', // Anime Skip Player
'bfbnagnphiehemkdgmmficmjfddgfhpl', // UltraWideo
'elfaihghhjjoknimpccccmkioofjjfkf', // StayFree - Website Blocker & Web Analytics
'okifoaikfmpfcamplcfjkpdnhfodpkil', // Doozy: Ai Made Easy
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
'youtube中文配音/oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
'ceicccfeikoipigeghddpocceifjelph', // Plex Skipper
'aelkipgppclpfimeamgmlonimflbhlgf', // GitHub Custom Notifier
'djnlaiohfaaifbibleebjggkghlmcpcj', // Fluent Read
'nhclljcpfmmaiojbhhnkpjcfmacfcian', // Facebook Video Controls
'mblkhbaakhbhiimkbcnmeciblfhmafna', // ElemSnap - Quick capture of webpage elements and conversion to images,
'oajalfneblkfiejoadecnmodfpnaeblh', // MS Edge TTS (Text to Speech)
'nedcanggplmbbgmlpcjiafgjcpdimpea', // YTBlock - Block any content from YouTube™
'oadbjpccljkplmhnjekgjamejnbadlne', // demo.fun - Interactive product demos that convert
'iopdafdcollfgaoffingmahpffckmjni', // SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
'khjdmjcmpolknpccmaaipmidphjokhdf', // WorkFlowy MultiFlow
'fencadnndhdeggodopebjgdfdlhcimfk', // 香草布丁🌿🍮- https://github.com/Xdy1579883916/vanilla-pudding
'bnacincmbaknlbegecpioobkfgejlojp', // MaxFocus: Link Preview
'bcpgdpedphodjcjlminjbdeejccjbimp', // 汇率转换-中文版本
'loeilaonggnalkaiiaepbegccilkmjjp', // Currency Converter Plus
'npcnninnjghigjfiecefheeibomjpkak', // Respond Easy
];
const edgeIds = extensionEntries.flatMap((e) => (e.edgeId ? [e.edgeId] : []));
const { data, isLoading } = useListExtensionDetails(
chromeIds,
firefoxSlugs,
edgeIds,
);
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
const sortedExtensions = computed(() => {
if (!data.value?.length) return [];
function addUtmSource(storeUrl: string): string {
const url = new URL(storeUrl);
url.searchParams.set('utm_source', 'wxt.dev');
return url.href;
}
const sortedExtensions = computed((): ListedExtension[] => {
if (!data.value) return [];
const chromeById = new Map(
(data.value.chrome ?? []).filter(Boolean).map((e) => [e.id, e]),
);
const firefoxBySlug = new Map(
(data.value.firefox ?? []).filter(Boolean).map((e) => [e.id, e]),
);
const edgeById = new Map(
(data.value.edge ?? []).filter(Boolean).map((e) => [e.id, e]),
);
const results: ListedExtension[] = [];
for (const entry of extensionEntries) {
const chrome = entry.chromeId ? chromeById.get(entry.chromeId) : undefined;
const firefox = entry.firefoxSlug
? firefoxBySlug.get(entry.firefoxSlug)
: undefined;
const edge = entry.edgeId ? edgeById.get(entry.edgeId) : undefined;
if (!chrome && !firefox && !edge) continue;
const primary = chrome ?? firefox ?? edge!;
const users =
(chrome?.users ?? 0) + (firefox?.users ?? 0) + (edge?.users ?? 0);
const rating = chrome?.rating ?? firefox?.rating ?? edge?.rating;
const stores: StoreLink[] = [];
if (chrome) {
stores.push({ label: 'Chrome', url: addUtmSource(chrome.storeUrl) });
}
if (firefox) {
stores.push({ label: 'Firefox', url: addUtmSource(firefox.storeUrl) });
}
if (edge) {
stores.push({ label: 'Edge', url: addUtmSource(edge.storeUrl) });
}
results.push({
id:
entry.chromeId ??
(entry.firefoxSlug
? `firefox:${entry.firefoxSlug}`
: `edge:${entry.edgeId}`),
name: primary.name,
iconUrl: primary.iconUrl,
shortDescription: primary.shortDescription,
users,
rating,
stores,
});
}
const score = (e: ListedExtension) => ((e.rating ?? 5) / 5) * e.users;
return results.sort((a, b) => score(b) - score(a));
return [...data.value]
.filter((item) => item != null)
.map((item) => ({
...item,
// Sort based on the user count weighted by the rating
sortKey: ((item.rating ?? 5) / 5) * item.weeklyActiveUsers,
}))
.filter((item) => !!item)
.sort((l, r) => r.sortKey - l.sortKey);
});
function formatUsers(n: number): string {
return `${n.toLocaleString()} users`;
}
function formatStars(r: number): string {
const rounded = Math.round(r * 10) / 10;
return `${rounded % 1 === 0 ? rounded.toFixed(0) : rounded.toFixed(1)} stars`;
function getStoreUrl(extension: ChromeExtension) {
const url = new URL(extension.storeUrl);
url.searchParams.set('utm_source', 'wxt.dev');
return url.href;
}
</script>
<template>
<p v-if="isLoading" style="text-align: center; opacity: 50%">Loading...</p>
<p
v-else-if="sortedExtensions.length === 0"
v-else-if="err || sortedExtensions.length === 0"
style="text-align: center; opacity: 50%"
>
Failed to load extension details.
</p>
<ul v-else class="extension-grid">
<li v-for="extension of sortedExtensions" :key="extension.id">
<ul v-else>
<li
v-for="extension of sortedExtensions"
:key="extension.id"
class="relative"
>
<img
:src="extension.iconUrl"
:alt="`${extension.name} icon`"
referrerpolicy="no-referrer"
/>
<div class="card-content">
<div class="relative">
<a
:href="extension.stores[0]?.url ?? '#'"
:href="getStoreUrl(extension)"
target="_blank"
:title="extension.name"
class="extension-name"
@@ -141,39 +89,19 @@ function formatStars(r: number): string {
<p class="description" :title="extension.shortDescription">
{{ extension.shortDescription }}
</p>
<div v-if="extension.stores.length > 0" class="store-stats">
<p class="store-stats-info">
<span>{{ formatUsers(extension.users) }}</span>
<template v-if="extension.rating">
<span class="store-stats-sep" aria-hidden="true">,</span>
<span>{{ formatStars(extension.rating) }}</span>
</template>
</p>
<p class="store-stats-sep" aria-hidden="true">&middot;</p>
<p class="store-links">
<template v-for="(store, i) of extension.stores" :key="store.label">
<a
:href="store.url"
target="_blank"
class="store-link"
:title="store.label"
>{{ store.label }}</a
>
<span
v-if="i < extension.stores.length - 1"
class="store-stats-sep"
aria-hidden="true"
>,&nbsp;</span
>
</template>
</p>
</div>
</div>
<p class="user-count">
<span>{{ extension.weeklyActiveUsers.toLocaleString() }} users</span
><template v-if="extension.rating != null"
>,
<span>{{ extension.rating }} stars</span>
</template>
</p>
</li>
</ul>
<p class="centered pr">
<a
href="https://github.com/wxt-dev/wxt/edit/main/docs/assets/extension-showcase.yml"
href="https://github.com/wxt-dev/wxt/edit/main/docs/.vitepress/components/UsingWxtSection.vue"
target="_blank"
>Open a PR</a
>
@@ -182,7 +110,7 @@ function formatStars(r: number): string {
</template>
<style scoped>
.extension-grid > li > img {
li img {
width: 116px;
height: 116px;
padding: 16px;
@@ -190,7 +118,7 @@ function formatStars(r: number): string {
background-color: var(--vp-c-default-soft);
}
.extension-grid {
ul {
display: grid;
grid-template-columns: repeat(1, 1fr);
align-items: stretch;
@@ -200,12 +128,12 @@ function formatStars(r: number): string {
padding: 0;
}
@media (min-width: 960px) {
.extension-grid {
ul {
grid-template-columns: repeat(2, 1fr);
}
}
.extension-grid > li {
li {
margin: 0 !important;
padding: 16px;
display: flex;
@@ -213,68 +141,28 @@ function formatStars(r: number): string {
border-radius: 12px;
flex: 1;
gap: 24px;
align-items: stretch;
align-items: center;
}
.centered {
text-align: center;
}
.extension-grid > li .description {
li a,
li .user-count,
li .description {
padding: 0;
margin: 0;
}
.extension-grid > li .card-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
padding-right: 0;
li .user-count {
opacity: 70%;
font-size: small;
position: absolute;
bottom: 12px;
right: 16px;
}
.store-stats {
margin-top: auto;
padding-top: 6px;
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
align-items: baseline;
column-gap: 6px;
row-gap: 4px;
text-align: right;
font-size: 12px;
line-height: 1.45;
opacity: 0.72;
color: var(--vp-c-text-2);
}
.store-stats-info {
display: inline;
}
.store-stats-sep {
opacity: 0.45;
user-select: none;
}
.store-links {
display: inline;
}
.store-link {
text-decoration: none;
color: inherit;
cursor: pointer;
}
.store-link:hover {
color: var(--vp-c-text-1);
text-decoration: underline;
}
.extension-grid > li .extension-name {
li a {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
@@ -283,23 +171,33 @@ function formatStars(r: number): string {
padding: 0;
margin: 0;
text-decoration: none;
font-size: large;
}
.extension-grid > li .extension-name:hover {
li a:hover {
text-decoration: underline;
}
.extension-grid > li .description {
li div {
flex: 1;
}
li .description {
opacity: 90%;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
margin-bottom: 0 !important;
margin-bottom: 16px;
}
li .extension-name {
font-size: large;
}
.pr {
opacity: 70%;
}
.relative {
position: relative;
}
</style>
@@ -1,15 +0,0 @@
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()}`;
});
}
@@ -1,84 +1,47 @@
import { ref } from 'vue';
export interface Extension {
export interface ChromeExtension {
id: string;
name: string;
iconUrl: string;
weeklyActiveUsers: number;
shortDescription: string;
storeUrl: string;
rating: number | undefined;
users: number;
}
export interface ExtensionResults {
chrome: Extension[];
firefox: Extension[];
edge: Extension[];
}
const operationName = 'WxtDocsUsedBy';
const query = `query ${operationName}($chromeIds: [String!]!, $firefoxIds: [String!]!, $edgeIds: [String!]!) {
chromeExtensions(ids: $chromeIds) {
const query = `query ${operationName}($ids:[String!]!) {
chromeExtensions(ids: $ids) {
id
...ExtensionData
name
iconUrl
weeklyActiveUsers
shortDescription
storeUrl
rating
}
firefoxAddons(ids: $firefoxIds) {
id: slug
...ExtensionData
}
edgeAddons(ids: $edgeIds) {
id
...ExtensionData
}
}
fragment ExtensionData on Extension {
name
iconUrl
shortDescription
storeUrl
rating
users
}`;
export default function (
chromeIds: string[],
firefoxSlugs: string[],
edgeIds: string[],
) {
const data = ref<ExtensionResults>();
export default function (ids: string[]) {
const data = ref<ChromeExtension[]>();
const err = ref<unknown>();
const isLoading = ref(true);
if (
chromeIds.length === 0 &&
firefoxSlugs.length === 0 &&
edgeIds.length === 0
) {
data.value = { chrome: [], firefox: [], edge: [] };
isLoading.value = false;
return { data, err, isLoading };
}
fetch('https://queue.wxt.dev/api', {
method: 'POST',
body: JSON.stringify({
operationName,
query,
variables: { chromeIds, firefoxIds: firefoxSlugs, edgeIds },
variables: { ids },
}),
headers: {
'Content-Type': 'application/json',
},
})
.then(async (res) => {
isLoading.value = false;
const { data: responseData } = await res.json();
data.value = {
chrome: responseData.chromeExtensions ?? [],
firefox: responseData.firefoxAddons ?? [],
edge: responseData.edgeAddons ?? [],
};
const {
data: { chromeExtensions },
} = await res.json();
data.value = chromeExtensions;
err.value = undefined;
})
.catch((error) => {
@@ -88,5 +51,9 @@ export default function (
err.value = error;
});
return { data, err, isLoading };
return {
data,
err,
isLoading,
};
}
+93 -207
View File
@@ -1,26 +1,5 @@
import { Feed } from 'feed';
// @ts-expect-error; It isn't TypeScript lib
import footnote from 'markdown-it-footnote';
import fs, { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { DefaultTheme, defineConfig } from 'vitepress';
import addKnowledge from 'vitepress-knowledge';
import {
groupIconMdPlugin,
groupIconVitePlugin,
localIconLoader,
} from 'vitepress-plugin-group-icons';
import llmstxt from 'vitepress-plugin-llms';
import { version as analyticsVersion } from '../../packages/analytics/package.json';
import { version as autoIconsVersion } from '../../packages/auto-icons/package.json';
import { version as i18nVersion } from '../../packages/i18n/package.json';
import { version as runnerVersion } from '../../packages/runner/package.json';
import { version as storageVersion } from '../../packages/storage/package.json';
import { version as unocssVersion } from '../../packages/unocss/package.json';
import { version as wxtVersion } from '../../packages/wxt/package.json';
import { version as isBackgroundVersion } from '../../packages/is-background/package.json';
import { defineConfig } from 'vitepress';
import typedocSidebar from '../api/reference/typedoc-sidebar.json';
import { meta, script } from './utils/head';
import {
menuGroup,
menuItem,
@@ -28,107 +7,27 @@ import {
navItem,
prepareTypedocSidebar,
} from './utils/menus';
const origin = 'https://wxt.dev';
import { meta, script } from './utils/head';
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.";
"WXT provides the best developer experience, making it quick, easy, and fun to develop chrome extensions for all browsers. With built-in utilities for building, zipping, and publishing your extension, it's easy to get started.";
const ogTitle = `${title}${titleSuffix}`;
const ogUrl = origin;
const ogImage = `${origin}/social-preview.png`;
const otherPackages = {
analytics: analyticsVersion,
'auto-icons': autoIconsVersion,
i18n: i18nVersion,
storage: storageVersion,
unocss: unocssVersion,
runner: runnerVersion,
'is-background': isBackgroundVersion,
};
const knowledge = addKnowledge<DefaultTheme.Config>({
serverUrl: 'https://knowledge.wxt.dev',
paths: {
'/': 'docs',
'/api/': 'api-reference',
'/blog/': 'blog',
},
layoutSelectors: {
blog: '.container-content',
},
pageSelectors: {
'examples.md': '#VPContent > .VPPage',
'blog.md': '#VPContent > .VPPage',
},
});
const ogUrl = 'https://wxt.dev';
const ogImage = 'https://wxt.dev/social-preview.png';
// https://vitepress.dev/reference/site-config
export default defineConfig({
extends: knowledge,
titleTemplate: `:title${titleSuffix}`,
title: 'WXT',
description,
vite: {
clearScreen: false,
//TODO: REMOVE THIS @TS-EXPECT-ERROR AFTER BUMP VITEPRESS TO V2.0
plugins: [
// @ts-expect-error: Vite version mismatch between this project and the plugin
llmstxt(),
// @ts-expect-error: Vite version mismatch between this project and the plugin
groupIconVitePlugin({
customIcon: {
'wxt.config.ts': localIconLoader(
import.meta.url,
'../public/logo.svg',
),
},
}),
{
name: 'yaml-loader',
load: {
handler: async (id) => {
if (id.endsWith('.yml') || id.endsWith('.yaml')) {
const obj = Bun.YAML.parse(await readFile(id, 'utf8'));
return `export default ${JSON.stringify(obj)}`;
}
},
},
},
],
},
lastUpdated: true,
sitemap: {
hostname: origin,
},
async buildEnd(site) {
// @ts-expect-error: knowledge.buildEnd is not typed, but it exists.
await knowledge.buildEnd(site);
// Only construct the RSS document for production builds
const { default: blogDataLoader } = await import('./loaders/blog.data');
const posts = await blogDataLoader.load();
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 fs.writeFile(join(site.outDir, 'rss.xml'), feed.rss2());
hostname: 'https://wxt.dev',
},
head: [
@@ -144,16 +43,6 @@ export default defineConfig({
}),
],
markdown: {
config: (md) => {
md.use(footnote);
md.use(groupIconMdPlugin);
},
languageAlias: {
mjs: 'js',
},
},
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
logo: {
@@ -161,15 +50,6 @@ export default defineConfig({
alt: 'WXT logo',
},
footer: {
message: [
'<span class="netlify-badge"><a class="light-netlify" href="https://www.netlify.com"><img src="/netlify-badge-light.svg" alt="Deploys by Netlify" /></a><a class="dark-netlify" href="https://www.netlify.com"><img src="/netlify-badge-dark.svg" alt="Deploys by Netlify" /></a></span>',
'Released under the <a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE">MIT License</a>.',
].join('<br/>'),
copyright:
'Copyright © 2023-present <a href="https://github.com/aklinker1">Aaron Klinker</a>',
},
editLink: {
pattern: 'https://github.com/wxt-dev/wxt/edit/main/docs/:path',
},
@@ -184,97 +64,103 @@ export default defineConfig({
],
nav: [
navItem('Guide', '/guide/installation'),
navItem('Examples', '/examples'),
navItem('Get Started', '/get-started/introduction'),
navItem('Guide', '/guide/key-concepts/manifest'),
navItem('API', '/api/reference/wxt'),
navItem('Blog', '/blog'),
navItem(`v${wxtVersion}`, [
navItem('wxt', [
navItem(`v${wxtVersion}`, '/'),
navItem(
`Changelog`,
'https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md',
),
]),
navItem(
'Other Packages',
Object.entries(otherPackages).map(([name, version]) =>
navItem(`@wxt-dev/${name}${version}`, `/${name}`),
),
),
]),
navItem('Examples', '/examples'),
],
sidebar: {
'/get-started/': menuRoot([
menuGroup('Get Started', '/get-started/', [
menuItem('Introduction', 'introduction'),
menuItem('Installation', 'installation'),
menuItem('Configuration', 'configuration'),
menuItem('Entrypoints', 'entrypoints'),
menuItem('Assets', 'assets'),
menuItem('Publishing', 'publishing'),
menuItem('Migrate to WXT', 'migrate-to-wxt'),
menuItem('Compare', 'compare'),
]),
]),
'/guide/': menuRoot([
menuGroup('Get Started', '/guide/', [
menuItem('Introduction', 'introduction.md'),
menuItem('Installation', 'installation.md'),
menuGroup('Key Concepts', '/guide/key-concepts/', [
menuItem('Manifest', 'manifest'),
menuItem('Auto-imports', 'auto-imports'),
menuItem('Web Extension Polyfill', 'web-extension-polyfill'),
menuItem('Frontend Frameworks', 'frontend-frameworks'),
menuItem('Content Script UI', 'content-script-ui'),
]),
menuGroup('Essentials', '/guide/essentials/', [
menuItem('Project Structure', 'project-structure.md'),
menuItem('Entrypoints', 'entrypoints.md'),
menuGroup(
'Configuration',
'/guide/essentials/config/',
[
menuItem('Manifest', 'manifest.md'),
menuItem('Browser Startup', 'browser-startup.md'),
menuItem('Auto-imports', 'auto-imports.md'),
menuItem('Environment Variables', 'environment-variables.md'),
menuItem('Runtime Config', 'runtime.md'),
menuItem('Vite', 'vite.md'),
menuItem('Build Mode', 'build-mode.md'),
menuItem('TypeScript', 'typescript.md'),
menuItem('Hooks', 'hooks.md'),
menuItem('Entrypoint Loaders', 'entrypoint-loaders.md'),
],
true,
),
menuItem('Extension APIs', 'extension-apis.md'),
menuItem('Assets', 'assets.md'),
menuItem('Target Different Browsers', 'target-different-browsers.md'),
menuItem('Content Scripts', 'content-scripts.md'),
menuItem('Storage', 'storage.md'),
menuItem('Messaging', 'messaging.md'),
menuItem('I18n', 'i18n.md'),
menuItem('Favicons', 'favicons.md'),
menuItem('Scripting', 'scripting.md'),
menuItem('WXT Modules', 'wxt-modules.md'),
menuItem('Frontend Frameworks', 'frontend-frameworks.md'),
menuItem('ES Modules', 'es-modules.md'),
menuItem('Remote Code', 'remote-code.md'),
menuItem('Unit Testing', 'unit-testing.md'),
menuItem('E2E Testing', 'e2e-testing.md'),
menuItem('Publishing', 'publishing.md'),
menuItem('Testing Updates', 'testing-updates.md'),
menuGroup('Directory Structure', '/guide/directory-structure/', [
// Folders
menuItem('.output/', 'output'),
menuItem('.wxt/', 'wxt'),
menuItem('assets/', 'assets'),
menuItem('components/', 'components'),
menuItem('composables/', 'composables'),
menuGroup('entrypoints/', '/guide/directory-structure/entrypoints/', [
menuItem('background', 'background.md'),
menuItem('bookmarks', 'bookmarks.md'),
menuItem('*.content.ts', 'content-scripts.md'),
menuItem('*.css', 'css.md'),
menuItem('devtools', 'devtools.md'),
menuItem('history', 'history.md'),
menuItem('newtab', 'newtab.md'),
menuItem('options', 'options.md'),
menuItem('popup', 'popup.md'),
menuItem('sandbox', 'sandbox.md'),
menuItem('sidepanel', 'sidepanel.md'),
menuItem('*.html', 'unlisted-pages.md'),
menuItem('*.ts', 'unlisted-scripts.md'),
]),
menuItem('hooks/', 'hooks'),
menuItem('public/', 'public/', [
menuItem('_locales/', 'public/locales'),
]),
menuItem('utils/', 'utils'),
// Files
menuItem('.env', 'env'),
menuItem('app.config.ts', 'app-config'),
menuItem('package.json', 'package'),
menuItem('tsconfig.json', 'tsconfig'),
menuItem('web-ext.config.ts', 'web-ext-config'),
menuItem('wxt.config.ts', 'wxt-config'),
]),
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'),
menuGroup('Extension APIs', '/guide/extension-apis/', [
menuItem('Storage', 'storage'),
menuItem('Messaging', 'messaging'),
menuItem('Scripting', 'scripting'),
menuItem('Others', 'others'),
]),
menuGroup('Go Further', '/guide/go-further/', [
menuItem('Testing', 'testing'),
menuItem('ES Modules', 'es-modules'),
menuItem('Debugging', 'debugging'),
menuItem('Handling Updates', 'handling-updates'),
menuItem('Vite', 'vite'),
menuItem('Custom Events', 'custom-events'),
menuItem('Reusable Modules', 'reusable-modules'),
menuItem('Remote Code', 'remote-code'),
menuItem('Entrypoint Loaders', 'entrypoint-loaders'),
menuItem('How WXT Works', 'how-wxt-works'),
]),
menuGroup('Upgrade Guide', '/guide/upgrade-guide/', [
menuItem('wxt', 'wxt'),
]),
]),
'/api/': menuRoot([
menuGroup(
'CLI Reference',
'/api/cli/',
[
menuItem('wxt', 'wxt.md'),
menuItem('wxt build', 'wxt-build.md'),
menuItem('wxt zip', 'wxt-zip.md'),
menuItem('wxt prepare', 'wxt-prepare.md'),
menuItem('wxt clean', 'wxt-clean.md'),
menuItem('wxt init', 'wxt-init.md'),
menuItem('wxt submit', 'wxt-submit.md'),
menuItem('wxt submit init', 'wxt-submit-init.md'),
],
true,
),
menuGroup('API Reference', prepareTypedocSidebar(typedocSidebar), true),
menuGroup('CLI', '/api/cli/', [
menuItem('wxt', 'wxt.md'),
menuItem('wxt build', 'wxt-build.md'),
menuItem('wxt zip', 'wxt-zip.md'),
menuItem('wxt prepare', 'wxt-prepare.md'),
menuItem('wxt clean', 'wxt-clean.md'),
menuItem('wxt init', 'wxt-init.md'),
menuItem('wxt submit', 'wxt-submit.md'),
menuItem('wxt submit init', 'wxt-submit-init.md'),
]),
menuGroup('API Reference', prepareTypedocSidebar(typedocSidebar)),
]),
},
},
-9
View File
@@ -1,9 +0,0 @@
import { type ContentData, createContentLoader } from 'vitepress';
import { PostFrontmatter } from '../utils/types';
declare const data: Array<ContentData & { frontmatter: PostFrontmatter }>;
export { data };
export default createContentLoader<
Array<ContentData & { frontmatter: PostFrontmatter }>
>('blog/*.md');
+12 -10
View File
@@ -1,7 +1,6 @@
import { resolve, join } from 'node:path';
import consola from 'consola';
import spawn from 'nano-spawn';
import { resolve } from 'node:path';
import { version } from '../../../packages/wxt/package.json';
import { execaCommand } from 'execa';
const cliDir = resolve('packages/wxt/src/cli/commands');
const cliDirGlob = resolve(cliDir, '**');
@@ -38,21 +37,24 @@ export default {
};
async function getHelp(command: string): Promise<string> {
const args = command.split(' ');
const result = await spawn(args[0], [...args.slice(1), '--help'], {
const res = await execaCommand(command + ' --help', {
cwd: 'packages/wxt',
});
return result.stdout;
return res.stdout;
}
async function getWxtHelp(command: string): Promise<string> {
const res = await getHelp(`bun run --silent wxt ${command}`.trim());
return res.replaceAll('{{version}}', version);
function getWxtHelp(command: string): Promise<string> {
return getHelp(`pnpm -s wxt ${command}`.trim());
}
async function getPublishExtensionHelp(command: string): Promise<string> {
const res = await getHelp(
`bun run --silent publish-extension ${command}`.trim(),
`./node_modules/.bin/publish-extension ${command}`.trim(),
);
return res.replace(/\$ publish-extension/g, '$ wxt submit');
}
export interface Command {
name: string;
docs: string;
}
+86 -53
View File
@@ -1,83 +1,116 @@
/* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */
/* Colors */
:root {
--wxt-c-green-1: #0b8a00;
--wxt-c-green-2: #096600;
--wxt-c-green-3: #096600;
--wxt-c-green-soft: rgba(11, 138, 0, 0.14);
--wxt-c-green: #53bc4a;
--wxt-c-green-1: #67d45e;
--wxt-c-green-2: #4fa048;
--wxt-c-green-3: #447e3f;
}
/* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */
:root {
--vp-c-brand: var(--wxt-c-green);
--vp-c-brand-1: var(--wxt-c-green-1);
--vp-c-brand-2: var(--wxt-c-green-2);
--vp-c-brand-3: var(--wxt-c-green-3);
--vp-c-brand-soft: var(--wxt-c-green-soft);
--vp-button-brand-bg: var(--wxt-c-green);
--vp-button-brand-hover-bg: var(--wxt-c-green-2);
--vp-button-brand-active-bg: var(--wxt-c-green-3);
--vp-code-link-color: var(--wxt-c-green);
/* --vp-c-text-1: var(--wxt-c-green-1); */
--vp-button-brand-text: var(--vp-c-black);
--vp-button-brand-hover-text: var(--vp-c-black);
--vp-button-brand-active-text: var(--vp-c-black);
--vp-custom-block-tip-border: var(--wxt-c-green);
--vp-custom-block-tip-text: var(--wxt-c-green);
/* --vp-code-block-bg: #222422;
--vp-code-copy-code-bg: #313431;
--vp-code-copy-code-hover-bg: #3c403c; */
--vp-custom-block-tip-bg: var(--vp-code-block-bg);
--vp-custom-block-info-bg: var(--vp-code-block-bg);
--vp-code-color: #476582;
}
.vp-doc a {
color: var(--wxt-c-green);
}
.dark {
--wxt-c-green-1: #67d45e;
--wxt-c-green-2: #329929;
--wxt-c-green-3: #21651b;
--wxt-c-green-soft: rgba(103, 212, 94, 0.14);
--vp-c-bg: #131413;
--vp-c-bg-soft: #1a1b1a;
--vp-c-bg-soft-up: #1f201f;
--vp-c-bg-soft-down: #262926;
--vp-c-bg-soft-mute: #242424;
--vp-c-bg-alt: #171817;
--vp-c-default: #313136;
--vp-c-default-1: #3a3a3c;
--vp-c-default-2: #505053;
--vp-c-default-3: #2c2c30;
--vp-c-default-soft: #252529;
--vp-code-block-bg: #191a19;
--vp-code-copy-code-bg: #212321;
--vp-code-copy-code-hover-bg: #292d29;
--vp-custom-block-info-bg: #191a19;
--vp-code-color: #c9def1;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
.vp-doc .no-vertical-dividers th,
.vp-doc .no-vertical-dividers td {
border: none;
}
/* Customize Individual Components */
.vp-doc .no-vertical-dividers {
th,
td {
border: none;
}
tr {
border: 1px solid var(--vp-c-divider);
}
.vp-doc .no-vertical-dividers tr {
border: 1px solid var(--vp-c-divider);
}
body {
overflow-y: scroll;
}
.light-netlify {
display: inline-flex;
.VPSidebar {
user-select: none;
}
.dark .light-netlify {
display: none;
.VPSidebarItem.level-0.collapsible {
padding-bottom: 0.5rem;
}
.dark-netlify {
display: none;
.VPSidebarItem.level-0.collapsible .items {
/*border-left: 1px solid var(--vp-c-divider);*/
}
.dark .dark-netlify {
display: inline-flex;
.VPSidebarItem.level-1 {
padding-left: 0.75rem;
}
/* Add Aaron's favorite pic from Artemis II to the hero section because it's cool. */
.dark .VPHome::before {
content: '';
position: absolute;
background-image: url('../../assets/art002e009298.jpg');
opacity: 50%;
width: 100%;
height: 110vh;
background-size: cover;
background-position: 90%;
.VPSidebar .group + .group {
border-top: none;
padding-top: 0;
}
.dark .VPHome::after {
content: '';
position: absolute;
width: 100%;
height: 10vh;
top: 100vh;
background: linear-gradient(to bottom, transparent, var(--vp-c-bg));
}
.item {
z-index: 1;
.VPSidebarItem .badge {
display: inline-block;
min-width: 1.6em;
padding: 0.3em 0.4em 0.2em;
border-radius: 1rem;
font-size: 0.75em;
line-height: 1;
margin-left: 0.5rem;
text-align: center;
vertical-align: middle;
background-color: var(--vp-c-default-2);
}
+5 -13
View File
@@ -3,22 +3,14 @@ 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';
import type { EnhanceAppContext } from 'vitepress/client';
import { setupCodeGroupSync } from '../utils/code-groups-sync';
export default {
extends: DefaultTheme,
enhanceApp(ctx: EnhanceAppContext) {
ctx.app
.component('Icon', Icon)
.component('EntrypointPatterns', EntrypointPatterns)
.component('UsingWxtSection', UsingWxtSection)
.component('ExampleSearch', ExampleSearch)
.component('blog', BlogLayout);
setupCodeGroupSync();
enhanceApp(ctx) {
ctx.app.component('Icon', Icon);
ctx.app.component('EntrypointPatterns', EntrypointPatterns);
ctx.app.component('UsingWxtSection', UsingWxtSection);
ctx.app.component('ExampleSearch', ExampleSearch);
},
};
-73
View File
@@ -1,73 +0,0 @@
const CODE_GROUP_SELECTORS = {
root: '.vp-code-group',
input: '.vp-code-group .tabs input',
label: '.tabs label',
} as const;
function getTabTitle(label: HTMLLabelElement) {
return label.dataset.title?.trim() || label.textContent?.trim();
}
function getCodeGroupTitle(input: HTMLInputElement) {
const label = input.parentElement?.querySelector<HTMLLabelElement>(
`label[for="${input.id}"]`,
);
return label ? getTabTitle(label) : undefined;
}
function findCodeGroupTabIndex(group: HTMLElement, title: string) {
return Array.from(
group.querySelectorAll<HTMLLabelElement>(CODE_GROUP_SELECTORS.label),
).findIndex((label) => getTabTitle(label) === title);
}
function syncCodeGroupSelection(group: HTMLElement, title: string) {
const targetIndex = findCodeGroupTabIndex(group, title);
if (targetIndex < 0) return;
const blocks = group.querySelector('.blocks');
const targetBlock = blocks?.children[targetIndex];
if (!blocks || !targetBlock || targetBlock.classList.contains('active'))
return;
blocks.querySelector('.active')?.classList.remove('active');
targetBlock.classList.add('active');
const input = group.querySelectorAll<HTMLInputElement>(
CODE_GROUP_SELECTORS.input,
)[targetIndex];
if (input) input.checked = true;
}
function syncCodeGroupTabs(event: Event) {
const target = event.target;
if (
!(target instanceof HTMLInputElement) ||
!target.matches(CODE_GROUP_SELECTORS.input)
)
return;
const activeGroup = target.closest(CODE_GROUP_SELECTORS.root);
const activeTitle = getCodeGroupTitle(target);
if (!activeTitle) return;
document
.querySelectorAll<HTMLElement>(CODE_GROUP_SELECTORS.root)
.forEach((group) => {
if (group === activeGroup) return;
syncCodeGroupSelection(group, activeTitle);
});
}
/**
* Keeps every code group on the page in sync: selecting a package manager (or
* any tab) in one group switches the matching tab in all the other groups.
*/
export function setupCodeGroupSync() {
if (typeof window === 'undefined') return;
window.addEventListener('change', syncCodeGroupTabs, true);
}
+1 -1
View File
@@ -1,4 +1,4 @@
import type { HeadConfig } from 'vitepress';
import { HeadConfig } from 'vitepress/types/shared';
export function meta(
property: string,
+24 -31
View File
@@ -1,61 +1,50 @@
import { DefaultTheme } from 'vitepress';
type SidebarItem = DefaultTheme.SidebarItem;
type NavItemWithChildren = DefaultTheme.NavItemWithChildren;
type NavItemChildren = DefaultTheme.NavItemChildren;
type NavItem = DefaultTheme.NavItem;
export function navItem(text: string): NavItemChildren;
export function navItem(text: string, link: string): NavItemChildren;
export function navItem(text: string, items: any[]): NavItemWithChildren;
export function navItem(text: string, arg2?: unknown): any {
if (typeof arg2 === 'string') {
return { text, link: arg2 };
} else if (Array.isArray(arg2)) {
return { text, items: arg2 };
}
return { text };
export function navItem(text: string, link: string): NavItem {
return { text, link };
}
export function menuRoot(items: SidebarItem[]) {
return items.map((item) => {
return items.map((item, index) => {
// item.collapsed = false; // uncomment to expand all level-0 items
return item;
});
}
export function menuGroup(
text: string,
items: SidebarItem[],
collapsible?: boolean,
): SidebarItem;
export function menuGroup(text: string, items: SidebarItem[]): SidebarItem;
export function menuGroup(
text: string,
base: string,
items: SidebarItem[],
collapsible?: boolean,
): SidebarItem;
export function menuGroup(
text: string,
a: string | SidebarItem[],
b?: SidebarItem[] | boolean,
c?: boolean,
b?: SidebarItem[],
): SidebarItem {
if (typeof a === 'string' && Array.isArray(b)) {
const collapsed = true;
if (typeof a === 'string') {
return {
text,
base: a,
items: b,
collapsed: c,
collapsed,
};
}
if (typeof a !== 'string' && !Array.isArray(b))
return {
text,
items: a,
collapsed: b,
};
return {
text,
items: a,
collapsed,
};
}
throw Error('Unknown overload');
export function menuItems(items: SidebarItem[]) {
return {
items,
};
}
export function menuItem(
@@ -69,7 +58,9 @@ export function menuItem(
return { text, link };
}
/** Clean up and add badges to typedoc leaf sections */
/**
* Clean up and add badges to typedoc leaf sections
*/
export function prepareTypedocSidebar(items: SidebarItem[]) {
// skip contents file
const filtered = items.slice(1);
@@ -89,7 +80,9 @@ export function prepareTypedocSidebar(items: SidebarItem[]) {
}
};
// process
prepareItems(filtered);
// return
return filtered;
}
-14
View File
@@ -1,5 +1,3 @@
import { ContentData } from 'vitepress';
export interface Example {
name: string;
description?: string;
@@ -18,15 +16,3 @@ export type ExamplesMetadata = {
};
export type KeySelectedObject = Record<string, boolean | undefined>;
export interface PostFrontmatter {
title: string;
description?: string;
date: Date;
authors: { name: string; github: string }[];
}
export interface Post
extends Omit<ContentData, 'frontmatter'>, Omit<PostFrontmatter, 'date'> {
date: Date;
}
-1
View File
@@ -1 +0,0 @@
<!--@include: ../packages/analytics/README.md-->
Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

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

Before

Width:  |  Height:  |  Size: 132 KiB

-1
View File
@@ -1 +0,0 @@
<!--@include: ../packages/auto-icons/README.md-->
-9
View File
@@ -1,9 +0,0 @@
---
layout: page
---
<script lang="ts" setup>
import BlogHome from './.vitepress/components/BlogHome.vue';
</script>
<BlogHome />
@@ -1,12 +0,0 @@
---
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_
@@ -1,76 +0,0 @@
---
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).
---
[Discuss this blog post on Github](https://github.com/wxt-dev/wxt/discussions/1543).
+33
View File
@@ -0,0 +1,33 @@
# Assets
WXT has two directories for storing assets like CSS, images, or fonts.
- [public directory](/guide/directory-structure/public/): Store files that will be copied into the output directory as-is
- [assets directory](/guide/directory-structure/assets): Store files that will be processed by Vite during the build process
To learn more about how to use assets at runtime from either of these directories, visit their guides linked above.
## Public Directory
Place static files like the extension icon or `_locales/` directory here. These files will be copied over to the output directory without being transformed by Vite.
```
<srcDir>
└─ public/
├─ icon-16.png
├─ icon-32.png
├─ icon-48.png
├─ icon-96.png
└─ icon-128.png
```
## Assets Directory
Files in the assets directory will be processed by Vite. They are imported in your source code, and will be transformed or renamed in the output directory.
```
<srcDir>
└─ assets/
├─ tailwind.css
└─ illustration.svg
```
+47
View File
@@ -0,0 +1,47 @@
# Compare
Lets compare the features of WXT vs [Plasmo](https://docs.plasmo.com/framework) (another framework) and [CRXJS](https://crxjs.dev/vite-plugin) (a bundler plugin).
## Overview
| Features | WXT | Plasmo | CRXJS |
| ---------------------------------------------------- | :--------------: | :-------------: | :--------------: |
| Supports all browsers | ✅ | ✅ | 🟡 <sup>10</sup> |
| MV2 Support | ✅ | ✅ | 🟡 <sup>1</sup> |
| MV3 Support | ✅ | ✅ | 🟡 <sup>1</sup> |
| Create Extension ZIPs | ✅ | ✅ | ❌ |
| Create Firefox Sources ZIP | ✅ | ❌ | ❌ |
| First-class TypeScript support | ✅ | ✅ | ✅ |
| Entrypoint discovery | ✅ <sup>2</sup> | ✅ <sup>2</sup> | ❌ |
| Inline entrypoint config | ✅ | ✅ | ❌ <sup>9</sup> |
| Auto-imports | ✅ | ❌ | ❌ |
| Supports all frontend frameworks | ✅ | 🟡 <sup>3</sup> | ✅ |
| Framework specific entrypoints (like `Popup.tsx`) | 🟡 <sup>4</sup> | ✅ <sup>5</sup> | ❌ |
| Automated publishing | ✅ | ✅ | ❌ |
| Remote Code Bundling (Google Analytics) | ✅ | ✅ | ❌ |
| <strong style="opacity: 50%">Dev Mode</strong> | | |
| `.env` Files | ✅ | ✅ | ✅ |
| Opens browser with extension installed | ✅ | ❌ | ❌ |
| HMR for UIs | ✅ | 🟡 <sup>6</sup> | ✅ |
| Reload HTML Files on Change | ✅ | 🟡 <sup>7</sup> | ✅ |
| Reload Content Scripts on Change | ✅ | 🟡 <sup>7</sup> | ✅ |
| Reload Background on Change | 🟡 <sup>7</sup> | 🟡 <sup>7</sup> | 🟡 <sup>7</sup> |
| Respects Content Script `run_at` | ✅ | ✅ | ❌ <sup>8</sup> |
| <strong style="opacity: 50%">Built-in Utils</strong> | | | |
| Storage | ✅ | ✅ | ❌ <sup>11</sup> |
| Messaging | ❌ <sup>11</sup> | ✅ | ❌ <sup>11</sup> |
| Content Script UI | ✅ | ✅ | ❌ <sup>11</sup> |
<small>
<sup>1</sup>: Either MV2 or MV3, not both.
<br/><sup>2</sup>: File based.
<br/><sup>3</sup>: Only React, Vue, and Svelte.
<br/><sup>4</sup>: <code>.html</code> <code>.ts</code> <code>.tsx</code>.
<br/><sup>5</sup>: <code>.html</code> <code>.ts</code> <code>.tsx</code>. <code>.vue</code> <code>.svelte</code>.
<br/><sup>6</sup>: React only.
<br/><sup>7</sup>: Reloads entire extension.
<br/><sup>8</sup>: ESM-style loaders run asynchronously.
<br/><sup>9</sup>: Entrypoint options all configured in `manifest.json`.
<br/><sup>10</sup>: As of <code>v2.0.0-beta.23</code>, but v2 stable hasn't been released yet.
<br/><sup>11</sup>: There is no built-in wrapper around this API. However, you can still access the standard APIs via <code>chrome</code>/<code>browser</code> globals or use any 3rd party NPM package.
</small>
+57
View File
@@ -0,0 +1,57 @@
# Configuration
By default, WXT provides sensible configuration for bundling web extensions with Vite.
## Config File
To configure WXT, create a `wxt.config.ts` file in your project root. It should have the following contents:
```ts
import { defineConfig } from 'wxt';
export default defineConfig({
// My WXT config
});
```
:::info
For more information on configuring WXT via the `wxt.config.ts` file, read the dedicated [`wxt.config.ts` guide](/guide/directory-structure/wxt-config).
:::
## Manifest.json
WXT generates your extension's `manifest.json` based on the project structure. To add additional properties, like permissions, use the [`manifest` property](/api/reference/wxt/interfaces/InlineConfig#manifest).
```ts
import { defineConfig } from 'wxt';
export default defineConfig({
manifest: {
permissions: ['storage'],
},
});
```
:::info
For more information on configuring the manifest, read the dedicated [Manifest guide](/guide/key-concepts/manifest).
:::
## Environment
WXT can read `.env` files, and variables are accessible via `import.meta.env.*`.
:::code-group
```sh [.env]
VITE_OAUTH_CLIENT_ID=abc123
```
```ts [JS]
import.meta.env.VITE_OAUTH_CLIENT_ID;
```
:::
:::info
For more information on using .env files, read the dedicated [`.env` guide](/guide/directory-structure/env).
:::
+60
View File
@@ -0,0 +1,60 @@
# Entrypoints
An "entrypoint" is any HTML, JS, or CSS file that needs to be bundled and included with your extension, which will be loaded and executed by the browser.
## Defining Entrypoints
In WXT, you create an entrypoint by adding a file to the `entrypoints/` directory.
```
<rootDir>
└─ entrypoints/
├─ background.ts
├─ content.ts
├─ injected.ts
└─ popup.html
```
Some entrypoint filesname patterns are reserved by WXT and effect how the manifest is generated.
- `popup` adds an `action` to the manifest
- `background` adds a background script/service worker
- `*.content.ts` adds a content script
- ...
> For a full list of recognized filenames, see the the [Entrypoints Directory guide](/guide/directory-structure/entrypoints/background).
Any other files, whether JS, CSS, or HTML, is considered "unlisted". Unlisted files, like `injected.ts` from above, are just bundled to the output directory and not added to the manifest. You can still access or load them at runtime.
## Entrypoint Options
Most entrypoints allow customizing their options in the file you define them in. This differs from regular web extension development, where all options are placed in the `manifest.json`.
WXT looks for custom options in the entrypoint, and adds them to the manifest when generated.
In HTML files, options are listed as `meta` tags:
```html
<html>
<head>
<!-- Defining the popup's "default_icon" field -->
<meta name="manifest.default_icon" content="{ '16': '/icon/16.png' }" />
</head>
</html>
```
In TS files, options are apart of the file's default export:
```ts
export default defineContentScript({
matches: ['*://*.google.com/*'],
runAt: 'document_start',
main() {
// ...
},
});
```
:::info
All options for each entrypoint type is listed in the [entrypoints directory docs](/guide/directory-structure/entrypoints/background).
:::
+116
View File
@@ -0,0 +1,116 @@
# Installation
Bootstrap a new project, start from scratch, or [migrate an existing project](/get-started/migrate-to-wxt).
## Bootstrap Project
:::code-group
```sh [pnpm]
pnpm dlx wxt@latest init <project-name>
```
```sh [npm]
npx wxt@latest init <project-name>
```
```sh [bun]
# The "wxt init" command currently fails when ran with bunx.
# Use NPX as a workaround, and select "bun" as your package
# manager. To stay up to date with this issue, follow
# https://github.com/wxt-dev/wxt/issues/707
#
# bunx wxt@latest init <project-name>
npx wxt@latest init <project-name>
```
:::
There are several starting templates available.
| TypeScript |
| ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <Icon name="TypeScript" /> [`vanilla`](https://github.com/wxt-dev/wxt/tree/main/templates/vanilla) |
| <Icon name="Vue" /> [`vue`](https://github.com/wxt-dev/wxt/tree/main/templates/vue) |
| <Icon name="React" /> [`react`](https://github.com/wxt-dev/wxt/tree/main/templates/react) |
| <Icon name="Svelte" /> [`svelte`](https://github.com/wxt-dev/wxt/tree/main/templates/svelte) |
| <Icon name="Solid" icon="https://www.solidjs.com/img/favicons/favicon-32x32.png" /> [`solid`](https://github.com/wxt-dev/wxt/tree/main/templates/solid) |
:::info
All templates default to TypeScript. Rename the file extensions to `.js` to use JavaScript instead.
:::
## From Scratch
Initialize a project and install `wxt`:
:::code-group
```sh [pnpm]
pnpm init
pnpm add -D wxt
```
```sh [npm]
npm init
npm i --save-dev wxt
```
```sh [yarn]
yarn init
yarn add --dev wxt
```
```sh [bun]
bun init
bun add --dev wxt
```
:::
Add your first entrypoint:
```ts
// entrypoints/background.ts
export default defineBackground(() => {
console.log(`Hello from ${browser.runtime.id}!`);
});
```
And add scripts to your `package.json`:
```json
{
"scripts": {
"dev": "wxt", // [!code ++]
"dev:firefox": "wxt --browser firefox", // [!code ++]
"build": "wxt build", // [!code ++]
"build:firefox": "wxt build --browser firefox", // [!code ++]
"zip": "wxt zip", // [!code ++]
"zip:firefox": "wxt zip --browser firefox", // [!code ++]
"postinstall": "wxt prepare" // [!code ++]
}
}
```
## Development
Once the project is setup, you can start the development server using the `dev` script.
```sh
pnpm dev
```
:::tip 🎉&ensp;Well done!
The dev command will build the extension for development, open the browser, and reload the different parts of the extension when you save changes.
:::
## Next Steps
You're ready to build your web extension!
- Read the rest of the "Get Started" pages for a high-overview of what WXT can do
- Read the [Guide](/guide/key-concepts/manifest) to learn in-depth about each feature WXT supports
- [Configure WXT](./configuration) by creating a `wxt.config.ts` file
- Checkout [example projects](https://github.com/wxt-dev/examples) to see how to perform common tasks with WXT
+47
View File
@@ -0,0 +1,47 @@
---
head:
- - link
- rel: canonical
href: https://wxt.dev
---
# Introduction
## Overview
WXT is a free and open source framework for building web extensions in an conventional, intuitive, and safe way **_for all browsers_**.
WXT is based on [Nuxt](https://nuxt.com), and aims to provide the same great DX with TypeScript, auto-imports, and an opinionated project structure.
![Example build output](../assets/cli-output.png)
## Conventions
WXT is an opinionated framework. This helps keep projects consistent and easy to pick up.
- **Generated manifest**: Based on your project's file structure
- **Entrypoint configuration**: Configure entrypoints from the same file they're declare in
- **Type-safety is a priority**: Out-of-the-box TypeScript support with improved browser API typing
- **Simple output file structure**: Output file paths minimize the path at runtime
## Development
WXT's dev server supports modern features like HMR to provide a lighting fast dev mode.
When changes can't be hot-reloaded, like content scripts or background scripts, they're reloaded individually to prevent reloading the entire extension and slowing down your development cycle.
## Production-ready
Production builds are optimized for store review, changing as few files as possible between builds.
In addition, WXT fully supports Firefox's source code requirements when using a bundler. It will automatically create and upload a ZIP file of your source code.
:::info
See [Publishing](./publishing) for more info around production builds.
:::
## New to Extension Development?
Most of these docs assume you have a basic understanding of how to write a chrome or web extension.
If you've never written a web extension before or need a refresher, follow Google's ["Hello, World!" tutorial](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world) to understand the basics.
+65
View File
@@ -0,0 +1,65 @@
---
outline: deep
---
# Migrate to WXT
> If you have problems migrating to WXT, feel free to ask for help in GitHub by [starting a discussion](https://github.com/wxt-dev/wxt/discussions/new?category=q-a) or in [Discord](https://discord.gg/ZFsZqGery9)!
## Overview
Always start by generating a new vanilla project and merging it into your project one file at a time.
```sh
cd path/to/your/project
pnpm dlx wxt@latest init example-wxt --template vanilla
```
In general, you'll need to:
&ensp;<input type="checkbox" /> Install `wxt`<br />
&ensp;<input type="checkbox" /> Update/create `package.json` scripts to use `wxt` (don't forget about `postinstall`)<br />
&ensp;<input type="checkbox" /> Move entrypoints into `entrypoints/` directory<br />
&ensp;<input type="checkbox" /> Move assets into either the `assets/` or `public/` directories<br />
&ensp;<input type="checkbox" /> Move manifest.json content into `wxt.config.ts`<br />
&ensp;<input type="checkbox" /> Convert custom import syntax to be compatible with Vite<br />
&ensp;<input type="checkbox" /> Add a default export to JS entrypoints<br />
&ensp;<input type="checkbox" /> Use the `browser` global instead of `chrome`<br />
&ensp;<input type="checkbox" /> Compare final `manifest.json` files, making sure permissions and host permissions are unchanged. Use [Google's update testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool) if your extension is already live on the Chrome Web Store<br />
&ensp;<input type="checkbox" /> Extension output by `wxt build` works the same way as before the migration<br />
Every project is different, so there's no one-solution-fits-all to migrating your project. Just make sure `wxt dev` runs, `wxt build` results in a working extension, and the list of permissions in the `manifest.json` hasn't changed. If all that looks good, you've finished migrating your extension!
## Popular Tools/Frameworks
Here's specific steps for other popular frameworks/build tools.
### `vite-plugin-web-extension`
Since you're already using Vite, it's a simple refactor.
1. Install `wxt`
2. Move and refactor your entrypoints to WXT's style (with a default export)
3. Update package.json scripts to use `wxt`
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.
### `plasmo`
1. Install `wxt`
2. Move entrypoints into `entrypoints/` directory, merging the named exports used to configure your JS entrypoints into WXT's default export
3. Move public `assets/*` into the `public/` directory
4. If you use CSUI, migrate to WXT's `createContentScriptUi`
5. Convert Plasmo's custom import resolutions to Vite's
6. If importing remote code via a URL, add a `url:` prefix so it works with WXT
7. 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.
<!--
## Migrate an Existing Project
:::info
Since projects vary greatly in setup, [start a discussion on GitHub](https://github.com/wxt-dev/wxt/discussions/new/choose) if you need help migrating your project to WXT.
:::
-->
@@ -4,7 +4,7 @@ outline: deep
# Publishing
WXT can ZIP your extension and submit it to various stores for review or for self-hosting.
WXT will help you ZIP your extensions and submit them to the stores for review.
## First Time Publishing
@@ -18,16 +18,16 @@ For specific details about each store, see the stores sections below.
## Automation
WXT provides two commands to help automate submitting a new version for review and publishing:
WXT provides two commands to help automate the release process:
- `wxt submit init`: Setup all the required secrets and options for the `wxt submit` command
- `wxt submit`: Submit new versions of your extension for review (and publish them automatically once approved)
To get started, run `wxt submit init` and follow the prompts, or run `wxt submit --help` to view all available options. Once finished, you should have a `.env.submit` file! WXT will use this file to submit your updates.
To get started, run `wxt submit init` and follow the prompts. Once finished, you should have a `.env.submit` file! WXT will use this file to submit your updates.
> In CI, make sure you add all the environment variables to the submit step.
To submit a new version for publishing, build all the ZIPs you plan on releasing:
To release an update, build all the ZIPs you plan on releasing:
```sh
wxt zip
@@ -36,25 +36,29 @@ wxt zip -b firefox
Then run the `wxt submit` command, passing in all the ZIP files you want to release. In this case, we'll do a release for all 3 major stores: Chrome Web Store, Edge Addons, and Firefox Addons Store.
If it's your first time running the command or you recently made changes to the release process, you'll want to test your secrets by passing the `--dry-run` flag.
If it's your first time running the command, you'll want to test your secrets by passing the `--dry-run` flag:
```sh
wxt submit --dry-run \
--chrome-zip .output/{your-extension}-{version}-chrome.zip \
--firefox-zip .output/{your-extension}-{version}-firefox.zip --firefox-sources-zip .output/{your-extension}-{version}-sources.zip \
--edge-zip .output/{your-extension}-{version}-chrome.zip
--chrome-zip .output/<your-extension>-<version>-chrome.zip \
--firefox-zip .output/<your-extension>-<version>-firefox.zip --firefox-sources-zip .output/<your-extension>-<version>-sources.zip \
--edge-zip .output/<your-extension>-<version>-chrome.zip
```
If the dry run passes, remove the flag and do the actual release:
```sh
wxt submit \
--chrome-zip .output/{your-extension}-{version}-chrome.zip \
--firefox-zip .output/{your-extension}-{version}-firefox.zip --firefox-sources-zip .output/{your-extension}-{version}-sources.zip \
--edge-zip .output/{your-extension}-{version}-chrome.zip
--chrome-zip .output/<your-extension>-<version>-chrome.zip \
--firefox-zip .output/<your-extension>-<version>-firefox.zip --firefox-sources-zip .output/<your-extension>-<version>-sources.zip \
--edge-zip .output/<your-extension>-<version>-chrome.zip
```
:::warning
:::tip
If you only need to release to a single store, only pass that store's ZIP flag.
:::
:::tip
See the [Firefox Addon Store](#firefox-addon-store) section for more details about the `--firefox-sources-zip` option.
:::
@@ -72,13 +76,13 @@ jobs:
submit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
node-version: 20
cache: 'pnpm'
- name: Install dependencies
@@ -129,11 +133,14 @@ wxt zip
Firefox requires you to upload a ZIP of your source code. This allows them to rebuild your extension and review the code in a readable way. More details can be found in [Firefox's docs](https://extensionworkshop.com/documentation/publish/source-code-submission/).
When running `wxt zip -b firefox`, WXT will zip both your extension and sources. Certain files (such as config files, hidden files, tests, and excluded entrypoints) are automatically excluded from your sources. However, it's important to manually check the ZIP to ensure it only contains the files necessary to rebuild your extension.
WXT fully supports generating and automatically submitting a source code ZIP.
When you run `wxt zip -b firefox`, your sources are zipped into the `.output` directory alongside the extension. WXT will automatically exclude certain files such as config files, hidden files, and tests. However, it's important to manually check the ZIP to ensure it only contains the files necessary to rebuild your extension.
To customize which files are zipped, add the `zip` option to your config file.
```ts [wxt.config.ts]
```ts
// wxt.config.ts
import { defineConfig } from 'wxt';
export default defineConfig({
@@ -163,8 +170,8 @@ yarn zip:firefox
```
```sh [bun]
bun install
bun run zip:firefox
bun i
bun zip:firefox
```
:::
@@ -174,7 +181,7 @@ Ensure that you have a `README.md` or `SOURCE_CODE_REVIEW.md` file with the abov
Make sure the build output is the exact same when running `wxt build -b firefox` in your main project and inside the zipped sources.
:::warning
If you use a `.env` files, they can affect the chunk hashes in the output directory. Either delete the .env file before running `wxt zip -b firefox`, or include it in your sources zip with the [`zip.includeSources`](/api/reference/wxt/interfaces/InlineConfig#includesources) option. Be careful to not include any secrets in your `.env` files.
If you use a `.env` files, they can effect the chunk hashes in the output directory. Either delete the .env file before running `wxt zip -b firefox`, or include it in your sources zip with the [`zip.includeSources`](/api/reference/wxt/interfaces/InlineConfig#includesources) option. Be careful to not include any secrets in your `.env` files.
See Issue [#377](https://github.com/wxt-dev/wxt/issues/377) for more details.
:::
@@ -183,7 +190,8 @@ See Issue [#377](https://github.com/wxt-dev/wxt/issues/377) for more details.
If you use private packages and you don't want to provide your auth token to the Firefox team during the review process, you can use `zip.downloadPackages` to download any private packages and include them in the zip.
```ts [wxt.config.ts]
```ts
// wxt.config.ts
export default defineConfig({
zip: {
downloadPackages: [
@@ -197,7 +205,7 @@ export default defineConfig({
Depending on your package manager, the `package.json` in the sources zip will be modified to use the downloaded dependencies via the `overrides` or `resolutions` field.
:::warning
WXT uses the command `npm pack <package-name>` to download the package. That means regardless of your package manager, you need to properly setup a `.npmrc` file. NPM and PNPM both respect `.npmrc` files, but Yarn and Bun have their own ways of authorizing private registries, so you'll need to add a `.npmrc` file.
WXT uses the command `npm pack <package-name>` to download the package. That means regardless of your package manager, you need to properly setup a `.npmrc` file. NPM and PNPM both respect `.npmrc` files, but Yarn and Bun have their own ways of authorizing private registries, so you'll need to add an `.npmrc` file.
:::
### Safari
@@ -206,13 +214,13 @@ WXT uses the command `npm pack <package-name>` to download the package. That mea
WXT does not currently support automated publishing for Safari. Safari extensions require a native MacOS or iOS app wrapper, which WXT does not create yet. For now, if you want to publish to Safari, follow this guide:
- [Packaging a web extension for Safari](https://developer.apple.com/documentation/safariservices/packaging-a-web-extension-for-safari) - "Package your existing extension as a Safari web extension using Xcodes command-line tool."
- [Converting a web extension for Safari](https://developer.apple.com/documentation/safariservices/safari_web_extensions/converting_a_web_extension_for_safari) - "Convert your existing extension to a Safari web extension using Xcodes command-line tool."
When running the `safari-web-extension-packager` CLI tool, pass the `.output/safari-mv2` or `.output/safari-mv3` directory, not your source code directory.
When running the `safari-web-extension-converter` CLI tool, pass the `.output/safari-mv2` or `.output/safari-mv3` directory, not your source code directory.
```sh
pnpm wxt build -b safari
xcrun safari-web-extension-packager .output/safari-mv2
xcrun safari-web-extension-converter .output/safari-mv2
```
### Edge Addons
@@ -0,0 +1,61 @@
# `<srcDir>/app.config.ts`
:::warning Nuxt Users
If you're familiar with Nuxt, this file is meant to be a direct equivalent to Nuxt's `app.config.ts` file.
However, some of Nuxt's features, like overriding the app config based on a `.env` file or automatically generating the config's types, are not implemented. They are planned, just not implemented yet. Feel free to open a PR!
:::
## Overview
Define runtime configuration in a single place.
```ts
// <srcDir>/app.config.ts
import { defineAppConfig } from 'wxt/sandbox';
// Define types for your config
declare module 'wxt/sandbox' {
export interface WxtAppConfig {
theme?: 'light' | 'dark';
}
}
export default defineAppConfig({
theme: 'dark',
});
```
Then access the config in your extension by calling `useAppConfig`:
```ts
console.log(useAppConfig()); // { theme: "dark" }
```
## Environment Variables
If you have a `.env` file, you can access any variables defined in it here. You can convert them to better types (like booleans), add types for them, or leave them as is.
```txt
# .env
VITE_BUG_REPORTING_DISABLED=true
VITE_API_KEY=...
```
```ts
// <srcDir>/app.config.ts
declare module 'wxt/sandbox' {
export interface WxtAppConfig {
bugReportingDisabled: boolean;
apiKey?: string;
}
}
export default defineAppConfig({
bugReportingDisabled: process.env.VITE_BUG_REPORTING_DISABLED === 'true',
apiKey: process.env.VITE_API_KEY,
});
```
> You don't have to do this, you can use `process.env.VITE_*` anywhere in your runtime code, but putting them here consolidates them to one place and defines what variables are expected.
+42
View File
@@ -0,0 +1,42 @@
# `<srcDir>/assets`
Files in the assets directory will be processed by Vite. They are imported in your source code, and will be transformed or renamed in the output directory.
```
<srcDir>
└─ assets/
├─ style.css
└─ illustration.svg
```
### Example
:::code-group
```html [popup.html]
<html>
<head>
<link rel="stylesheet" href="~/assets/style.css" />
<!-- ... -->
</head>
<body>
<img src="~/assets/illustration.svg" />
<!-- ... -->
</body>
</html>
```
```ts [content.ts]
import '~/assets/style.css';
import illustration from '~/assets/style.svg';
defineContentScript({
main() {
const image = document.createElement('img');
image.src = illustration;
document.body.append(image);
},
});
```
:::
@@ -0,0 +1,5 @@
# `<srcDir>/components`
:::warning 🚧 Under construction
These docs will be coming soon!
:::
@@ -0,0 +1,5 @@
# `<srcDir>/composables`
:::warning 🚧 Under construction
These docs will be coming soon!
:::
@@ -0,0 +1,47 @@
# Background
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/background/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/background)
For MV2, the background is added as a script to the background page. For MV3, the background becomes a service worker.
## Filenames
<EntrypointPatterns
:patterns="[
['background.[jt]s', 'background.js'],
['background/index.[jt]s', 'background.js'],
]"
/>
## Definition
:::warning
The main function of the background **_CANNOT BE ASYNC_**. Event listeners must be added synchronously on background startup. If your main function returns a promise, WXT will log an error.
:::
```ts
export default defineBackground(() => {
// Executed when background is loaded
});
```
or
```ts
export default defineBackground({
// Set manifest options
persistent: undefined | true | false,
type: undefined | 'module',
// Set include/exclude if the background should be removed from some builds
include: undefined | string[],
exclude: undefined | string[],
// Executed when background is loaded
main() {
// ...
},
});
```
> All manifest options default to `undefined`.
@@ -0,0 +1,31 @@
# Bookmarks
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
## Filenames
<EntrypointPatterns
:patterns="[
['bookmarks.html', 'bookmarks.html'],
['bookmarks/index.html', 'bookmarks.html'],
]"
/>
## Definition
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
```
@@ -0,0 +1,107 @@
# Content Scripts
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/content_scripts/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts)
When creating content script entrypoints, they are automatically included in the `manifest.json` along with any CSS files they import.
## Filenames
<EntrypointPatterns
:patterns="[
['content.[jt]sx?', 'content-scripts/content.js'],
['content/index.[jt]sx?', 'content-scripts/content.js'],
['<name>.content.[jt]sx?', 'content-scripts/<name>.js'],
['<name>.content/index.[jt]sx?', 'content-scripts/<name>.js'],
]"
/>
## Definition
```ts
export default defineContentScript({
// Set manifest options
matches: string[],
excludeMatches: undefined | [],
includeGlobs: undefined | [],
excludeGlobs: undefined | [],
allFrames: undefined | [],
runAt: undefined | 'document_start' | 'document_end' | 'document_idle',
matchAboutBlank: undefined | true | false,
matchOriginAsFallback: undefined | true | false,
world: undefined | 'ISOLATED' | 'MAIN',
// Set include/exclude if the background should be removed from some builds
include: undefined | string[],
exclude: undefined | string[],
// Configure how CSS is injected onto the page
cssInjectionMode: undefined | "manifest" | "manual" | "ui",
// Configure how/when content script will be registered
registration: undefined | "manifest" | "runtime",
main(ctx: ContentScriptContext) {
// Executed when content script is loaded
},
});
```
> All manifest options default to `undefined`.
When defining multiple content scripts, content script entrypoints that have the same set of options will be merged into a single `content_script` item in the manifest.
## CSS
To include CSS with your content script, import the CSS file at the top of your entrypoint.
```
<srcDir>/
└─ entrypoints/
└─ overlay.content/
├─ index.ts
└─ style.css
```
```ts
// entrypoints/overlay.content/index.ts
import './style.css';
export default defineContentScript({
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
main(ctx) {
// ...
},
});
```
Any styles imported in your content script will be added to that content script's `css` array in your `manifest.json`:
```json
// .output/chrome-mv3/manifest.json
{
"content_scripts": [
{
"matches": ["*://google.com/*", "*://duckduckgo.com/*"],
"js": ["content-scripts/overlay.js"],
"css": ["content-scripts/overlay.css"]
}
]
}
```
To disable this behavior, set `cssInjectionMode` to `"manual"` or `"ui"`.
```ts
export default defineContentScript({
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
cssInjectionMode: 'manual',
main(ctx) {
// ...
},
});
```
See [Content Script UI](/guide/key-concepts/content-script-ui) for more info on creating UIs and including CSS in content scripts.
@@ -0,0 +1,44 @@
# CSS
WXT can build CSS entrypoints individually. CSS entrypoints are always unlisted.
See [Content Script CSS](/guide/directory-structure/entrypoints/content-scripts#css) documentation for the recommended approach to include CSS with a content script.
:::info
If the recommended approach doesn't work for your use case, you can use any of the filename patterns below to build the styles separate from the JS and use the [`transformManifest` hook](/api/reference/wxt/interfaces/InlineConfig#transformmanifest) to manually add your CSS file to the manifest.
:::
## Filenames
<EntrypointPatterns
:patterns="[
['<name>.(css|scss|sass|less|styl|stylus)', '<name>.css'],
['<name>/index.(css|scss|sass|less|styl|stylus)', '<name>.css'],
['content.(css|scss|sass|less|styl|stylus)', 'content-scripts/content.css'],
['content/index.(css|scss|sass|less|styl|stylus)', 'content-scripts/content.css'],
['<name>.content.(css|scss|sass|less|styl|stylus)', 'content-scripts/<name>.css'],
['<name>.content/index.(css|scss|sass|less|styl|stylus)', 'content-scripts/<name>.css'],
]"
/>
## Definition
```css
body {
/* Plain CSS file */
}
```
Follow Vite's guide to setup a preprocessor: https://vitejs.dev/guide/features.html#css-pre-processors
```sh
pnpm i sass
```
```scss
body {
h1 {
/* ...*/
}
}
```
@@ -0,0 +1,40 @@
# Devtools
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/devtools/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/devtools_page)
## Filenames
<EntrypointPatterns
:patterns="[
['devtools.html', 'devtools.html'],
['devtools/index.html', 'devtools.html'],
]"
/>
## Definition
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
```
## Adding UI Elements
Chrome extensions allow you to add panels and side panes to the devtools window.
![DevTools window showing Elements panel and Styles sidebar pane.](https://developer.chrome.com/static/docs/extensions/how-to/devtools/extend-devtools/image/devtools-window-showing-e-9051f7f0347cd_1920.png)
See the WXT's examples for a full walkthrough of extending the devtools window:
- [Devtools Setup](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vanilla-devtools#readme)
@@ -0,0 +1,31 @@
# History
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
## Filenames
<EntrypointPatterns
:patterns="[
['history.html', 'history.html'],
['history/index.html', 'history.html'],
]"
/>
## Definition
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
```
@@ -0,0 +1,31 @@
# Newtab
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
## Filenames
<EntrypointPatterns
:patterns="[
['newtab.html', 'newtab.html'],
['newtab/index.html', 'newtab.html'],
]"
/>
## Definition
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
```
@@ -0,0 +1,36 @@
# Options
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/options/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/options_ui)
## Filenames
<EntrypointPatterns
:patterns="[
['options.html', 'options.html'],
['options/index.html', 'options.html'],
]"
/>
## Definition
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Options Title</title>
<meta name="manifest.open_in_tab" content="true|false" />
<meta name="manifest.chrome_style" content="true|false" />
<meta name="manifest.browser_style" content="true|false" />
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
```
> All manifest options default to `undefined` when the `meta` tag is not present.
@@ -0,0 +1,43 @@
# Popup
[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/action/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action)
## Filenames
<EntrypointPatterns
:patterns="[
['popup.html', 'popup.html'],
['popup/index.html', 'popup.html'],
]"
/>
## Definition
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Default Popup Title</title>
<meta
name="manifest.default_icon"
content="{
16: '/icon-16.png',
24: '/icon-24.png',
...
}"
/>
<meta name="manifest.type" content="page_action|browser_action" />
<meta name="manifest.browser_style" content="true|false" />
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
```
> All manifest options default to `undefined` when the `meta` tag is not present.
@@ -0,0 +1,37 @@
# Sandbox
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/sandbox/)
:::tip Chromium Only
Firefox does not support sandboxed pages.
:::
## Filenames
<EntrypointPatterns
:patterns="[
['sandbox.html', 'sandbox.html'],
['sandbox/index.html', 'sandbox.html'],
['<name>.sandbox.html', '<name>.html'],
['<name>.sandbox/index.html', '<name>.html'],
]"
/>
## Definition
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
```
@@ -0,0 +1,49 @@
# Side Panel
[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/sidePanel/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/user_interface/Sidebars)
In Chrome, side panels use the "side_panel" API, while Firefox uses the "sidebar_action" API.
:::warning
Chrome added support for sidepanels in Manifest V3, they are not available in Manifest V2.
:::
## Filenames
<EntrypointPatterns
:patterns="[
['sidepanel.html', 'sidepanel.html'],
['sidepanel/index.html', 'sidepanel.html'],
['<name>.sidepanel.html', '<name>.html` '],
['<name>.sidepanel/index.html', '<name>.html` '],
]"
/>
## Definition
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Default Side Panel Title</title>
<meta
name="manifest.default_icon"
content="{
16: '/icon-16.png',
24: '/icon-24.png',
...
}"
/>
<meta name="manifest.open_at_install" content="true|false" />
<meta name="manifest.browser_style" content="true|false" />
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
```
@@ -0,0 +1,49 @@
# Unlisted Pages
HTML pages that are bundled and shipped with the extension, but are not included in the manifest.
If you plan on using the page in an iframe, don't forget to add the page to [`web_accessible_resources`](https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources).
### Examples
- Onboarding
- Dashboard
- FAQ
- Help
- Changelog
## Filenames
<EntrypointPatterns
:patterns="[
['<name>.html', '<name>.html'],
['<name>/index.html', '<name>.html'],
]"
/>
Pages are accessible at `'/<name>.html'`:
```ts
const url = browser.runtime.getURL('/<name>.html', '<html></html>');
console.log(url); // "chrome-extension://<id>/<name>.html"
```
## Definition
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
```
@@ -0,0 +1,37 @@
# Unlisted Scripts
TypeScript files that are bundled and shipped with the extension, but are not included in the manifest.
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).
## Filenames
<EntrypointPatterns
:patterns="[
['<name>.[jt]sx?', '<name>.js'],
['<name>/index.[jt]sx?', '<name>.js'],
]"
/>
## Definition
```ts
export default defineUnlistedScript(() => {
// Executed when script is loaded
});
```
or
```ts
export default defineUnlistedScript({
// Set include/exclude if the script should be removed from some builds
include: undefined | string[],
exclude: undefined | string[],
// Executed when script is loaded
main() {
// ...
},
});
```
+5
View File
@@ -0,0 +1,5 @@
# `<root>/.env`
:::warning 🚧 Under construction
These docs will be coming soon!
:::
+5
View File
@@ -0,0 +1,5 @@
# `<srcDir>/hooks`
:::warning 🚧 Under construction
These docs will be coming soon!
:::
+5
View File
@@ -0,0 +1,5 @@
# `<root>/.output`
:::warning 🚧 Under construction
These docs will be coming soon!
:::
@@ -0,0 +1,5 @@
# `<root>/package.json`
:::warning 🚧 Under construction
These docs will be coming soon!
:::
@@ -0,0 +1,35 @@
# `public/`
Place static files like the extension icon or `_locales/` directory here. These files will be copied over to the output directory without being transformed by Vite.
```
<srcDir>
└─ public/
├─ icon-16.png
├─ icon-32.png
├─ icon-48.png
├─ icon-96.png
└─ icon-128.png
```
### Example
You can reference these files by using absolute paths in HTML files or `browser.runtime.getURL` in content scripts.
:::code-group
```html [popup.html]
<img src="/icon-128.png" />
```
```ts [content.ts]
defineContentScript({
main() {
const image = document.createElement('img');
image.src = browser.runtime.getURL('/icon-128.png');
document.body.append(image);
},
});
```
:::
@@ -0,0 +1,5 @@
# `public/_locales/`
:::warning 🚧 Under construction
These docs will be coming soon!
:::
@@ -0,0 +1,5 @@
# `<root>/tsconfig.json`
:::warning 🚧 Under construction
These docs will be coming soon!
:::
+5
View File
@@ -0,0 +1,5 @@
# `<srcDir>/utils`
:::warning 🚧 Under construction
These docs will be coming soon!
:::
@@ -0,0 +1,65 @@
# `web-ext.config.ts`
This file lets you configure the browser startup when running `wxt dev`.
```ts
import { defineRunnerConfig } from 'wxt';
export default defineRunnerConfig({
startUrls: ['https://google.com', 'https://youtube.com'],
});
```
There are three places you can customize the runner:
- `<root>/wxt.config.ts` - Use the `runner` option. Changes here will be committed and shared with everyone developing the project.
- `<root>/web-ext.config.ts` - A gitignored file for you to customize the startup behavior to your liking without effecting others
- `$HOME/web-ext.config.ts` - Stores system-wide config effecting all projects running on your machine.
See below examples on how to accomplish common configuration:
[[toc]]
## Configuring Binaries
`web-ext`'s browser discovery is very limited. By default, it only guesses at where Chrome and Firefox are installed. If you've customized your install locations, you may need to tell `web-ext` where the binaries/executables are located using the [`binaries` option](/api/reference/wxt/interfaces/ExtensionRunnerConfig#binaries). For other Chromium based browsers, like Edge or Opera, you'll need to explicitly list them in the `binaries` option as well, otherwise they will open in Chrome by default.
```ts
import { defineRunnerConfig } from 'wxt';
export default defineRunnerConfig({
binaries: {
chrome: '/path/to/chrome-beta', // Use Chrome Beta instead of regular Chrome
firefox: 'firefoxdeveloperedition', // Use Firefox Developer Edition instead of regular Firefox
edge: '/path/to/edge', // Open MS Edge when running "wxt -b edge"
},
});
```
## Disable Opening Browser
Disabling the browser can be useful if it's difficult to develop your extension with fresh profiles. Maybe you need to sign into a website to see a content script run, and a fresh profile isn't helpful because it doesn't save your login info.
To disable opening the extension automatically in a new window, just disable the runner:
```ts
export default defineRunnerConfig({
disabled: true,
});
```
## Profile Customization
Another option, instead of disabling the runner, to stay logged into websites is to use a custom profile.
`web-ext` comes with some built-in ways of using an existing profile, but it's not really using the same profile. It copies the profile to a temp directory, and uses that.
Instead, I've found it's better to pass Chrome's `--user-data-dir` argument. This let's you use a fresh profile initially, and customize it to your liking. You can install devtool extensions, set custom flags, and log into websites. Next time you run the extension in dev mode, all that will be remembered!
```ts
export default defineRunnerConfig({
chromiumArgs: ['--user-data-dir=./chrome-data'],
});
```
> This only works for Chrome. You'll have to use `firefoxProfile` option instead, which has the same limitations mentioned above, where you won't be signed into websites automatically.
@@ -0,0 +1,5 @@
# `<root>/wxt.config.ts`
:::warning 🚧 Under construction
These docs will be coming soon!
:::
+5
View File
@@ -0,0 +1,5 @@
# `.wxt/`
:::warning 🚧 Under construction
These docs will be coming soon!
:::
-188
View File
@@ -1,188 +0,0 @@
# Assets
## `/assets` Directory
Any assets imported or referenced inside the `<srcDir>/assets/` directory will be processed by WXT's bundler.
Here's how you access them:
:::code-group
```ts [JS]
import imageUrl from '~/assets/image.png';
const img = document.createElement('img');
img.src = imageUrl;
```
```html [HTML]
<!-- In HTML tags, you must use the relative path --->
<img src="../assets/image.png" />
```
```css [CSS]
.bg-image {
background-image: url(~/assets/image.png);
}
```
```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 `<rootDir>/public/` are copied into the output folder as-is, without being processed by WXT's bundler.
Here's how you access them:
:::code-group
```ts [JS]
import imageUrl from '/image.png';
const img = document.createElement('img');
img.src = imageUrl;
```
```html [HTML]
<img src="/image.png" />
```
```css [CSS]
.bg-image {
background-image: url(/image.png);
}
```
```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
Assets inside content scripts are a little different. By default, when you import an asset, it returns just the path to the asset. This is because Vite assumes you're loading assets from the same hostname.
But, inside content scripts, the hostname is whatever the tab is set to. So if you try to fetch the asset, manually or as an `<img>`'s `src`, it will be loaded from the tab's website, not your extension.
To fix this, you need to convert the image to a full URL using `browser.runtime.getURL`:
```ts [entrypoints/content.ts]
import iconUrl from '/icon/128.png';
export default defineContentScript({
matches: ['*://*.google.com/*'],
main() {
console.log(iconUrl); // "/icon/128.png"
console.log(browser.runtime.getURL(iconUrl)); // "chrome-extension://<id>/icon/128.png"
},
});
```
## WASM
How a `.wasm` file is loaded varies greatly between packages, but most follow a basic setup: Use a JS API to load and execute the `.wasm` file.
For an extension, that means two things:
1. The `.wasm` file needs to be present in output folder so it can be loaded.
2. You must import the JS API to load and initialize the `.wasm` file, usually provided by the NPM package.
For an example, let's say you have a content script needs to parse TS code into AST. We'll use [`@oxc-parser/wasm`](https://www.npmjs.com/package/@oxc-parser/wasm) to do it!
First, we need to copy the `.wasm` file to the output directory. We'll do it with a [WXT module](/guide/essentials/wxt-modules):
```ts
// modules/oxc-parser-wasm.ts
import { resolve } from 'node:path';
export default defineWxtModule((wxt) => {
wxt.hook('build:publicAssets', (_, assets) => {
assets.push({
absoluteSrc: resolve(
'node_modules/@oxc-parser/wasm/web/oxc_parser_wasm_bg.wasm',
),
relativeDest: 'oxc_parser_wasm_bg.wasm',
});
});
});
```
Run `wxt build`, and you should see the WASM file copied into your `.output/chrome-mv3` folder!
Next, since this is in a content script and we'll be fetching the WASM file over the network to load it, we need to add the file to the `web_accessible_resources`:
```ts [wxt.config.ts]
export default defineConfig({
manifest: {
web_accessible_resources: [
{
// We'll use this matches in the content script as well
matches: ['*://*.github.com/*'],
// Use the same path as `relativeDest` from the WXT module
resources: ['/oxc_parser_wasm_bg.wasm'],
},
],
},
});
```
And finally, we need to load and initialize the `.wasm` file inside the content script to use it:
```ts [entrypoints/content.ts]
import initWasm, { parseSync } from '@oxc-parser/wasm';
export default defineContentScript({
matches: '*://*.github.com/*',
async main(ctx) {
if (!location.pathname.endsWith('.ts')) return;
// Get text from GitHub
const code = document.getElementById(
'read-only-cursor-text-area',
)?.textContent;
if (!code) return;
const sourceFilename = document.getElementById('file-name-id')?.textContent;
if (!sourceFilename) return;
// Load the WASM file:
await initWasm({
module_or_path: browser.runtime.getURL('/oxc_parser_wasm_bg.wasm'),
});
// Once loaded, we can use `parseSync`!
const ast = parseSync(code, { sourceFilename });
console.log(ast);
},
});
```
This code is taken directly from `@oxc-parser/wasm` docs with one exception: We manually pass in a file path. In a standard NodeJS or web project, the default path works just fine so you don't have to pass anything in. However, extensions are different. You should always explicitly pass in the full URL to the WASM file in your output directory, which is what `browser.runtime.getURL` returns.
Run your extension, and you should see OXC parse the TS file!

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