Compare commits

..

10 Commits

Author SHA1 Message Date
Aaron Klinker 8447f9aa7c Release version for testing 2023-12-17 09:53:27 -06:00
Aaron Klinker ee768f84bb Remove wxt/client inline for vitest 2023-12-17 09:49:38 -06:00
Aaron Klinker cfd4979a7a Fix docs 2023-12-17 09:41:43 -06:00
Aaron Klinker de5b4e16cd cleanup 2023-12-17 09:32:55 -06:00
Aaron Klinker 5c696da6e2 Fix build 2023-12-17 09:30:28 -06:00
Aaron Klinker 2363144bb6 Cleanup 2023-12-17 09:15:05 -06:00
Aaron Klinker 9e2d11d8db List imports by hand 2023-12-16 12:37:13 -06:00
Aaron Klinker f2523c778f Only export types for polyfill 2023-12-16 12:29:15 -06:00
Aaron Klinker 04a51581fc Fix tests, don't auto-import WebextensionPolyfill namespace 2023-12-16 12:28:25 -06:00
Aaron Klinker bb40db78e3 Import all dependencies from WXT 2023-12-16 12:20:15 -06:00
668 changed files with 17226 additions and 65534 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
-14
View File
@@ -1,16 +1,2 @@
# See https://git-scm.com/docs/gitattributes#_pattern_format for more about `.gitattributes`.
# Normalize EOL for all files that Git considers text files
* text=auto eol=lf
# Mark lock files as generated to avoid diffing
pnpm-lock.yaml linguist-generated
package-lock.json linguist-generated
bun.lockb linguist-generated
yarn.lock linguist-generated
# Exclude templates from language statistics
templates/**/* linguist-vendored
# Other generated files
packages/browser/src/gen/** linguist-generated
-9
View File
@@ -1,9 +0,0 @@
# Set default
* @aklinker1 @Timeraa
# Secure Directories
/.github/ @aklinker1
# Creator of specific wxt modules
/packages/auto-icons/ @Timeraa
/packages/unocss/ @Timeraa
-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
+42
View File
@@ -0,0 +1,42 @@
---
name: Bug report
about: Report an issue with WXT
title: ''
labels: triage
assignees: ''
---
### Describe the bug
A clear and concise description of what the bug is.
### To Reproduce
Upload a ZIP or share a link to a repo representing the minimal reproduction. **_If you don't upload a minimal reproduction, you bug report will be closed._**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
### Expected behavior
A clear and concise description of what you expected to happen.
### Screenshots
If applicable, add screenshots to help explain your problem.
### Environment
<!--- Run `npx envinfo --system --browsers --binaries --npmPackages wxt,vite` and paste the output below -->
```
Paste output here
```
### Additional context
Add any other context about the problem here, otherwise you can delete this section.
-75
View File
@@ -1,75 +0,0 @@
name: "\U0001F41E Bug report"
description: Report an issue with WXT
labels: [pending-triage]
type: Bug
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: bug-description
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks!
placeholder: I am doing ... What I expect is ... What actually happening is ...
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction
description: |
Please provide a minimal reproduction. This can include:
- A PR with a failing test case
- A link to a github repo
- A ZIP you upload to this issue
A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is required ([Why?](https://antfu.me/posts/why-reproductions-are-required)). If a report is vague (e.g. just a generic error message) and has no reproduction or a partial reproduction, it will be closed immediately and labeled with as "needs-reproduction". Once a reproduction is provided, it will be re-opened.
placeholder: Reproduction URL or attach a ZIP
validations:
required: true
- type: textarea
id: reproduction-steps
attributes:
label: Steps to reproduce
description: Please provide any reproduction steps that may need to be described. E.g. if it happens only when running the dev or build script make sure it's clear which one to use.
placeholder: Run `npm install` followed by `npm run dev`
- type: textarea
id: system-info
attributes:
label: System Info
description: Output of `npx envinfo --system --browsers --binaries --npmPackages wxt,vite`
render: shell
placeholder: System, Binaries, Browsers
validations:
required: true
- type: dropdown
id: package-manager
attributes:
label: Used Package Manager
description: Select the used package manager
options:
- npm
- yarn
- pnpm
- bun
validations:
required: true
- type: checkboxes
id: checkboxes
attributes:
label: Validations
description: Before submitting the issue, please make sure you do the following
options:
- label: Read the [Contributing Guidelines](https://github.com/wxt-dev/wxt/blob/main/CONTRIBUTING.md).
required: true
- label: Read the [docs](https://wxt.dev/guide/installation.html).
required: true
- label: Check that there isn't [already an issue](https://github.com/wxt-dev/wxt/issues) that reports the same bug to avoid creating a duplicate.
required: true
- label: Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/wxt-dev/wxt/discussions) or join our [Discord Chat Server](https://discord.gg/ZFsZqGery9).
required: true
- label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.
required: true
-8
View File
@@ -1,8 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Discord Chat
url: https://discord.gg/ZFsZqGery9
about: Ask questions and discuss with other WXT users in real time.
- name: Questions & Discussions
url: https://github.com/wxt-dev/wxt/discussions
about: Use GitHub discussions for message-board style questions and discussions.
+1 -1
View File
@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for WXT
title: ''
type: Feature
labels: feature
assignees: ''
---
+9 -21
View File
@@ -1,29 +1,17 @@
name: Basic Setup
description: Install PNPM, Node, and dependencies
inputs:
install:
default: 'true'
description: Whether or not to run 'pnpm install'
installArgs:
default: ''
description: Additional args to append to "pnpm install"
runs:
using: composite
steps:
- name: 🛠️ Setup PNPM
uses: pnpm/action-setup@f2b2b233b538f500472c7274c7012f57857d8ce0 # v4.1.0
- name: 🛠️ Setup NodeJS
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
- name: Setup PNPM
uses: pnpm/action-setup@v2
with:
node-version: 20
version: 8
- name: Setup NodeJS
uses: actions/setup-node@v4
with:
node-version: 18
cache: pnpm
- name: 📦 Install Dependencies
if: ${{ inputs.install == 'true' }}
- name: Install Dependencies
shell: bash
run: pnpm install ${{ inputs.installArgs }}
run: pnpm install
+1 -5
View File
@@ -6,10 +6,6 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: 'monthly'
- package-ecosystem: 'github-actions'
directory: '/'
directory: '/' # Location of package manifests
schedule:
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>
-111
View File
@@ -1,111 +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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
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 }}');
- name: Sync Labels
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
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?
-29
View File
@@ -1,29 +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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
- name: Build All Packages
run: pnpm buildc all
- name: Publish
run: pnpx pkg-pr-new publish --compact --pnpm './packages/*'
-29
View File
@@ -1,29 +0,0 @@
name: 🎉 PR closed
on:
pull_request_target:
types:
- closed
permissions:
contents: read
pull-requests: write
jobs:
thank-you:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
steps:
- name: Post Thank You Comment
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# Only fetch the config file from the repository
sparse-checkout-cone-mode: false
sparse-checkout: .commitlintrc.yml
- name: Install dependencies
run: npm install --global @commitlint/config-conventional commitlint
- name: Check PR title with commitlint
env:
PR_TITLE: ${{ github.event.pull_request.title }}
HELP_URL: https://github.com/wxt-dev/wxt/blob/main/CONTRIBUTING.md#conventional-pr-titles
run: echo "$PR_TITLE" | npx commitlint --help-url $HELP_URL
+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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
- name: Login to Docker Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- uses: docker/login-action@v3
with:
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
- name: Build docs
run: |
pnpm docs:build
docker build docs/.vitepress -t ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
- name: Push Image
run: docker push ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
- name: Deploy
run: curl -X POST -i ${{ secrets.UPDATE_DOCS_WEBHOOK }}
- run: pnpm docs:build
- run: docker build docs/.vitepress -t ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
- run: docker push ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
- run: curl -X POST -i ${{ secrets.UPDATE_DOCS_WEBHOOK }}
+12 -51
View File
@@ -1,73 +1,34 @@
name: 🚀 Release
name: Release
on:
workflow_dispatch:
inputs:
package:
description: Package to release
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
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@v3
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: |
pnpm tsx scripts/bump-package-version.ts ${{ inputs.package }}
git config user.email "github-actions@users.noreply.github.com"
git config user.name "GitHub Actions"
pnpx changelogen@latest --release
git push
git push --tags
- name: Publish to NPM
working-directory: packages/${{ inputs.package }}
- name: NPM
run: |
pnpm pack
sudo npm i -g npm@latest
/usr/local/bin/npm publish *.tgz
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
pnpm publish
- name: Create GitHub release
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: GitHub Release
run: pnpx changelogen@latest gh release --token ${{ secrets.GITHUB_TOKEN }}
+14 -36
View File
@@ -1,44 +1,22 @@
name: 🔄 Sync Releases
name: Sync Releases
on:
workflow_dispatch:
inputs:
package:
description: Package to sync
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
push:
branches:
- main
paths:
- CHANGELOG.md
jobs:
sync:
name: Sync Releases
runs-on: ubuntu-22.04
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
installArgs: --ignore-scripts
- name: Sync Releases
run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
version: 8
- uses: actions/setup-node@v4
with:
node-version: 18
cache: pnpm
- run: pnpm sync-releases all --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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
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: pnpm gen
- name: Run Checks
working-directory: packages/browser
run: pnpm check
- name: Commit Changes
id: commit
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.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: |
pnpm pack
sudo npm i -g npm@latest
/usr/local/bin/npm publish *.tgz
+48 -99
View File
@@ -1,4 +1,4 @@
name: 🛡️ Validate
name: Validate
on:
workflow_call:
pull_request:
@@ -6,95 +6,57 @@ on:
branches:
- main
permissions:
contents: read
jobs:
checks:
name: Checks
formatting:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
- name: Basic Checks
run: pnpm check
builds:
name: Builds
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: pnpm format:check
lint:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
- name: Build All Packages
run: pnpm buildc all
build-demo:
name: Build Demo
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: pnpm lint
type-check:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
- name: Build
run: pnpm build:all
working-directory: packages/wxt-demo
- name: ZIP
run: pnpm wxt zip
working-directory: packages/wxt-demo
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: pnpm compile
validate-demo:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: pnpm build:all
working-directory: demo
- run: pnpm tsc --noEmit
working-directory: demo
- run: pnpm wxt zip
working-directory: demo
- run: pnpm vitest run
working-directory: 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
- name: Setup Bun
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
- name: Run Tests
if: ${{ ! matrix.coverage }}
run: pnpm test
- name: Run Tests (Coverage)
if: matrix.coverage
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- name: pnpm test:coverage
run: pnpm test:coverage --reporter=default --reporter=hanging-process
- name: Upload Coverage
if: matrix.coverage
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
- uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
windows-tests:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: pnpm test run
template:
name: Template
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
template:
- react
@@ -103,34 +65,21 @@ jobs:
- vanilla
- vue
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
- name: Pack WXT package
run: pnpm pack
working-directory: packages/wxt
- name: Install Dependencies
run: npm i
- uses: actions/checkout@v3
- uses: ./.github/actions/setup
- run: pnpm pack
- run: npm i
working-directory: templates/${{ matrix.template }}
- name: Install Packed WXT
run: npm i -D ../../packages/wxt/wxt-*.tgz
- run: npm i -D ../../wxt-*.tgz
working-directory: templates/${{ matrix.template }}
- name: Type Check Template
run: pnpm compile
- run: pnpm compile
if: matrix.template != 'svelte'
working-directory: templates/${{ matrix.template }}
- name: Type Check Template
run: pnpm check
- run: pnpm check
if: matrix.template == 'svelte'
working-directory: templates/${{ matrix.template }}
- name: Build Template
run: pnpm build
- run: pnpm build
working-directory: templates/${{ matrix.template }}
env:
# Debug Vite 5's deprecated CJS support
VITE_CJS_TRACE: true
-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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup
uses: ./.github/actions/setup
with:
install: false
- name: Setup Go
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
with:
go-version: '1.25.1'
# This prevents pnpm dlx from downloading WXT in the video
- name: Pre-install WXT
run: |
pnpm store add wxt@latest
pnpm dlx wxt@latest --version
- 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@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commit_message: 'docs: Update `wxt init` GIF'
# https://github.com/charmbracelet/vhs#output
file_pattern: 'docs/assets/*.gif'
+2 -5
View File
@@ -5,10 +5,9 @@
.output
.webextrc
.wxt
.wxt-runner
*.log
/.vscode/
/docs/.vitepress/cache
docs/.vitepress/.temp
coverage
dist
node_modules
@@ -18,8 +17,6 @@ web-ext.config.ts
templates/*/pnpm-lock.yaml
templates/*/yarn.lock
templates/*/package-lock.json
docs/api/reference
docs/api
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
@@ -0,0 +1 @@
ignore-workspace-root-check=true
-1
View File
@@ -5,4 +5,3 @@ dist
docs/.vitepress/cache
pnpm-lock.yaml
CHANGELOG.md
packages/browser/src/gen
-7
View File
@@ -1,7 +0,0 @@
{
"recommendations": [
"davidanson.vscode-markdownlint",
"esbenp.prettier-vscode",
"github.vscode-github-actions"
]
}
-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
}
+1134
View File
File diff suppressed because it is too large Load Diff
-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>.
+9 -118
View File
@@ -1,50 +1,16 @@
# 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 availalbe, and if you just want to use WXT's built tool, you don't have to import any of WXT's utilities, you can use your own.
> The few runtime utils shipped inside WXT are things that should be used by 90% of extensions. That said, they're also legacy utils left in from before I started creating separate NPM packages, and in the future, they may be removed from the core package.
## 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
@@ -66,19 +32,18 @@ Here are some helpful commands:
```sh
# Build WXT package
cd packages/wxt
pnpm build
```
```sh
# Build WXT package, then build demo extension
cd packages/wxt-demo
cd demo
pnpm build
```
```sh
# Build WXT package, then start the demo extension in dev mode
cd packages/wxt-demo
cd demo
pnpm dev
```
@@ -92,32 +57,9 @@ pnpm test
pnpm docs:dev
```
## Profiling
```sh
# Build the latest version
pnpm --filter wxt build
# CD to the demo directory
cd packages/wxt-demo
# 1. Generate a flamechart with 0x
pnpm dlx 0x node_modules/wxt/bin/wxt.mjs build
# 2. Inspect the process with chrome @ chrome://inspect
pnpm node --inspect node_modules/wxt/bin/wxt.mjs build
```
## Updating Docs
Documentation is written with VitePress, and is located in the `docs/` directory.
The API reference is generated from JSDoc comments in the source code. If there's a typo or change you want to make in there, you'll need to update the source code instead of a file in the `docs/` directory.
## Testing
WXT has unit and E2E tests. When making a change or adding a feature, make sure to update the tests or add new ones, if they exist.
> If they don't exist, feel free to create them, but that's a lot for a one-time contributor. A maintainer might add them to your PR though.
WXT has unit and E2E tests. When making a change or adding a feature, make sure to update the tests or add new ones.
To run tests for a specific file, add the filename at the end of the test command:
@@ -125,7 +67,7 @@ To run tests for a specific file, add the filename at the end of the test comman
pnpm test manifest-contents
```
All test (unit and E2E) for all packages are ran together via [Vitest workspaces](https://vitest.dev/guide/#workspaces-support).
Unit and E2E tests are ran together via [Vitest workspaces](https://vitest.dev/guide/#workspaces-support).
If you want to manually test a change, you can modify the demo project for your test, but please don't leave those changes committed once you open a PR.
@@ -140,7 +82,7 @@ npm run dev
npm run build
```
Note that templates are hardcoded to a specific version of `wxt` from NPM, they do not use the local version. PR checks will test your PR's changes against the templates, but if you want to manually do it, update the package.json dependency:
Note that templates are hardcoded to a specific version of `wxt` from NPM, they do not use the local version. PR checks will test your changes against the templates, but if you want to manually do it, update the package.json dependency:
```diff
"devDependencies": {
@@ -158,58 +100,7 @@ Then run `npm i` again.
To add a template, copy the vanilla template and give it a new name.
```sh
cp -r templates/vanilla templates/<new-template-name>
cp -r templates/vailla templates/<new-template-name>
```
That's it. Once your template is merged, it will be available inside `wxt init` immediately. You don't need to release a new version of WXT to release a new template.
## Upgrading Dependencies
WXT has custom rules around what dependencies can be upgraded. Use the `scripts/upgrade-deps.ts` script to upgrade dependencies and follow these rules.
```sh
pnpm tsx scripts/upgrade-deps.ts
```
To see all the options, run:
```sh
pnpm tsx 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 questsions, review PRs, organize and prioritize work - there's lots of ways for you to help out.
-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>
pnpm 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.
+39 -36
View File
@@ -1,41 +1,49 @@
<div align="center">
<h1 align="center">
<img style="vertical-align:middle" width="44" src="./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>
<!-- Hide code coverage while it's broken -->
<!-- <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 Chrome 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>
<p align="center">
<a href="https://wxt.dev" target="_blank">Get Started</a>
&bull;
<a href="https://wxt.dev/guide/installation.html" target="_blank">Installation</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>
</p>
[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>
![Example CLI Output](https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/assets/cli-output.png)
![Example CLI Output](./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/07359e53-f491-43b6-8e8f-fae94aec8063
## Quick Start
Bootstrap a new project:
```sh
# npm
npx wxt@latest init
# pnpm
pnpm dlx wxt@latest init
# bun
bunx wxt@latest init
pnpx wxt@latest init <project-name>
```
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
@@ -48,22 +56,17 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
- 📂 File based entrypoints
- 🚔 TypeScript
- 🦾 Auto-imports
- 🤖 Automated publishing
- ⬇️ Download and bundle remote URL imports
- 🎨 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
### Coming Soon
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)
- 🤖 Automated publishing
## 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.
Executable
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env node
import '../dist/cli.js';
+32
View File
@@ -0,0 +1,32 @@
{
"name": "demo",
"version": "1.0.0",
"description": "Demo extension for WXT",
"type": "module",
"scripts": {
"dev": "pnpm -w build && wxt",
"build": "pnpm -w build && wxt build",
"build:all": "pnpm -w build && run-s -s 'build:all:*'",
"build:all:chrome-mv3": "wxt build",
"build:all:chrome-mv2": "wxt build --mv2",
"build:all:firefox-mv3": "wxt build -b firefox --mv3",
"build:all:firefox-mv2": "wxt build -b firefox",
"test": "pnpm -w build && vitest",
"zip": "pnpm -w build && wxt zip",
"compile": "pnpm -w build && tsc --noEmit",
"postinstall": "pnpm -w build && wxt prepare"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"vitest": "^0.34.6",
"webextension-polyfill": "^0.10.0"
},
"devDependencies": {
"@types/react": "^18.2.34",
"@types/react-dom": "^18.2.14",
"@types/webextension-polyfill": "^0.10.5",
"sass": "^1.69.5",
"wxt": "workspace:*"
}
}
+4
View File
@@ -0,0 +1,4 @@
body {
margin: 0;
padding: 0;
}
@@ -0,0 +1,18 @@
import { describe, expect, it, vi } from 'vitest';
import background from '../background';
browser.i18n.getMessage = () => 'fake-message';
const logMock = vi.fn();
console.log = logMock;
describe('Background Entrypoint', () => {
it("should log the extenion's runtime ID", () => {
const id = 'some-id';
fakeBrowser.runtime.id = id;
background.main();
expect(logMock).toBeCalledWith(id);
});
});
+33
View File
@@ -0,0 +1,33 @@
import messages from 'public/_locales/en/messages.json';
export default defineBackground(() => {
console.log(browser.runtime.id);
logId();
console.log({
browser: __BROWSER__,
chrome: __IS_CHROME__,
firefox: __IS_FIREFOX__,
manifestVersion: __MANIFEST_VERSION__,
messages,
});
// @ts-expect-error: should only accept entrypoints or public assets
browser.runtime.getURL('/');
browser.runtime.getURL('/background.js');
browser.runtime.getURL('/icon/128.png');
// @ts-expect-error: should only accept known message names
browser.i18n.getMessage('test');
browser.i18n.getMessage('prompt_for_name');
browser.i18n.getMessage('hello', 'Aaron');
browser.i18n.getMessage('bye', ['Aaron']);
browser.i18n.getMessage('@@extension_id');
console.log('WXT MODE:', {
MODE: import.meta.env.MODE,
DEV: import.meta.env.DEV,
PROD: import.meta.env.PROD,
});
storage.setItem('session:startTime', Date.now());
});
@@ -2,9 +2,9 @@ export default defineContentScript({
matches: ['*://*.google.com/*'],
main(ctx) {
const ui = createIframeUi(ctx, {
const ui = createContentScriptIframe(ctx, {
page: '/iframe-src.html',
position: 'overlay',
type: 'overlay',
anchor: 'form[action="/search"]',
});
ui.mount();
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Popup</title>
<link rel="stylesheet" href="uno.css" />
<link rel="stylesheet" href="../common/style.css" />
</head>
<body>
<p>Hello popup!</p>
+24
View File
@@ -0,0 +1,24 @@
import '../../common/style.css';
import './style.css';
export default defineContentScript({
matches: ['https://*.duckduckgo.com/*'],
cssInjectionMode: 'ui',
async main(ctx) {
const ui = await createContentScriptUi(ctx, {
name: 'demo-ui',
type: 'inline',
append: 'before',
anchor: 'form[role=search]',
mount: (container) => {
const app = document.createElement('div');
app.textContent = 'Custom content script UI';
container.append(app);
},
});
ui.mount();
setTimeout(ui.remove, 5000);
},
});
@@ -1,6 +1,11 @@
:root {
color-scheme: dark;
color: indianred;
}
html {
background-color: black;
}
div {
padding: 16px;
}
+29
View File
@@ -0,0 +1,29 @@
{
"prompt_for_name": {
"message": "What's your name?",
"description": "Ask for the user's name"
},
"hello": {
"message": "Hello, $USER$",
"description": "Greet the user",
"placeholders": {
"user": {
"content": "$1",
"example": "Cira"
}
}
},
"bye": {
"message": "Goodbye, $USER$. Come back to $OUR_SITE$ soon!",
"description": "Say goodbye to the user",
"placeholders": {
"our_site": {
"content": "Example.com"
},
"user": {
"content": "$1",
"example": "Cira"
}
}
}
}

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

+7
View File
@@ -0,0 +1,7 @@
{
"extends": ["../tsconfig.base.json", "./.wxt/tsconfig.json"],
"compilerOptions": {
"allowImportingTsExtensions": true,
"jsx": "react-jsx"
}
}
@@ -1,8 +1,10 @@
import { defineConfig } from 'vitest/config';
import { WxtVitest } from 'wxt/testing';
export default defineConfig({
test: {
mockReset: true,
restoreMocks: true,
},
plugins: [WxtVitest()],
});
+18
View File
@@ -0,0 +1,18 @@
import { defineConfig } from 'wxt';
export default defineConfig({
srcDir: 'src',
manifest: {
permissions: ['storage'],
default_locale: 'en',
web_accessible_resources: [
{
resources: ['/iframe-src.html'],
matches: ['*://*.google.com/*'],
},
],
},
alias: {
public: 'src/public',
},
});
-70
View File
@@ -1,70 +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';
const posts = computed(() =>
data
.map((post) => ({
...post,
...post.frontmatter,
date: new Date(post.frontmatter.date),
}))
.sort((a, b) => b.date.getTime() - a.date.getTime()),
);
</script>
<template>
<div class="container">
<div>
<div class="vp-doc">
<h1>Blog</h1>
</div>
<ul>
<BlogPostPreview v-for="post of posts" :key="post.url" :post />
</ul>
</div>
</div>
</template>
<style scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
}
.container > div {
padding: 32px;
max-width: 900px;
width: 100%;
min-width: 0;
}
h1 {
padding-bottom: 16px;
}
ul {
display: flex;
flex-direction: column;
list-style: none;
}
ul,
li {
padding: 0;
margin: 0;
}
ul li {
padding-top: 16px;
margin-top: 16px;
border-top: 1px solid var(--vp-c-default);
}
ul li:last-child {
padding-bottom: 16px;
margin-bottom: 16px;
border-bottom: 1px solid var(--vp-c-default);
}
</style>
-76
View File
@@ -1,76 +0,0 @@
<script lang="ts" setup>
import useBlogDate from '../composables/useBlogDate';
import { useData } from 'vitepress';
const { frontmatter } = useData();
const date = useBlogDate(() => frontmatter.value.date);
</script>
<template>
<div class="vp-doc">
<main class="container-content">
<h1 v-html="$frontmatter.title" />
<p class="meta-row">
<a
class="author"
v-for="author of $frontmatter.authors"
:key="author.github"
:href="`https://github.com/${author.github}`"
>
<img :src="`https://github.com/${author.github}.png?size=96`" />
<span>{{ author.name }}</span>
</a>
<span>&bull;</span>
<span>{{ date }}</span>
</p>
<Content />
</main>
</div>
</template>
<style scoped>
vp-doc {
display: flex;
}
main {
max-width: 1080px;
padding: 32px;
margin: auto;
}
@media (min-width: 768px) {
main {
padding: 64px;
}
}
.meta-row {
display: flex;
color: var(--vp-c-text-2);
gap: 16px;
overflow: hidden;
padding-bottom: 32px;
}
.meta-row > * {
flex-shrink: 0;
}
.author {
display: flex;
gap: 8px;
align-items: center;
color: var(--vp-c-text-2);
font-weight: normal;
text-decoration: none;
}
.author img {
width: 24px;
height: 24px;
border-radius: 100%;
}
.author span {
padding: 0;
margin: 0;
}
.author:hover {
text-decoration: underline;
color: var(--vp-c-text-2);
}
</style>
@@ -1,72 +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>
li {
padding: 0;
margin: 0;
}
p {
margin: 0;
}
h3 {
margin: 0;
padding: 0;
border: none;
}
li > a > div {
display: flex;
flex-direction: column;
margin: 0 -16px;
padding: 16px;
border-radius: 16px;
}
li > a > div:hover {
background: var(--vp-c-default);
}
li .title {
color: var(--vp-c-text);
margin-bottom: 12px;
}
li .description {
font-size: 16px;
color: var(--vp-c-text-2);
margin-bottom: 8px;
}
li .meta {
font-weight: 400;
font-size: 12px;
color: var(--vp-c-text-2);
}
</style>
@@ -8,14 +8,14 @@ const props = defineProps<{
<table class="no-vertical-dividers">
<thead>
<tr>
<th style="width: 100%">Filename</th>
<th>Input Pattern</th>
<th></th>
<th>Output Path</th>
</tr>
</thead>
<tbody>
<tr v-for="pattern of patterns">
<td style="white-space: nowrap; padding-right: 8px">
<td style="white-space: nowrap">
<code>entrypoints/{{ pattern[0] }}</code>
</td>
<td style="padding: 6px; opacity: 50%">
@@ -31,7 +31,7 @@ const props = defineProps<{
/>
</svg>
</td>
<td style="white-space: nowrap; padding-left: 8px">
<td style="white-space: nowrap">
<code>/{{ pattern[1] }}</code>
</td>
</tr>
@@ -1,226 +0,0 @@
<script lang="ts" setup>
import { ref, onMounted, computed, toRaw, Ref } from 'vue';
import ExampleSearchFilterByItem from './ExampleSearchFilterByItem.vue';
import ExampleSearchResult from './ExampleSearchResult.vue';
import { ExamplesMetadata, KeySelectedObject } from '../utils/types';
const props = defineProps<{
tag?: string;
}>();
const exampleMetadata = ref<ExamplesMetadata>();
onMounted(async () => {
const res = await fetch(
'https://raw.githubusercontent.com/wxt-dev/examples/main/metadata.json',
);
exampleMetadata.value = await res.json();
});
const searchText = ref('');
const selectedApis = ref<KeySelectedObject>({});
const selectedPermissions = ref<KeySelectedObject>({});
const selectedPackages = ref<KeySelectedObject>({});
function useRequiredItems(selectedItems: Ref<KeySelectedObject>) {
return computed(() =>
Array.from(
Object.entries(toRaw(selectedItems.value)).reduce(
(set, [pkg, checked]) => {
if (checked) set.add(pkg);
return set;
},
new Set<string>(),
),
),
);
}
const requiredApis = useRequiredItems(selectedApis);
const requiredPermissions = useRequiredItems(selectedPermissions);
const requiredPackages = useRequiredItems(selectedPackages);
function doesExampleMatchSelected(
exampleItems: string[],
requiredItems: Ref<string[]>,
) {
const exampleItemsSet = new Set(exampleItems);
return !requiredItems.value.find((item) => !exampleItemsSet.has(item));
}
const filteredExamples = computed(() => {
const text = searchText.value.toLowerCase();
return exampleMetadata.value.examples.filter((example) => {
const matchesText = example.searchText.toLowerCase().includes(text);
const matchesApis = doesExampleMatchSelected(example.apis, requiredApis);
const matchesPermissions = doesExampleMatchSelected(
example.permissions,
requiredPermissions,
);
const matchesPackages = doesExampleMatchSelected(
example.packages,
requiredPackages,
);
return matchesText && matchesApis && matchesPermissions && matchesPackages;
});
});
</script>
<template>
<div class="example-layout">
<div class="search">
<input v-model="searchText" placeholder="Search for an example..." />
</div>
<div class="filters">
<ExampleSearchFilterByItem
label="APIs"
:items="exampleMetadata?.allApis"
v-model="selectedApis"
/>
<ExampleSearchFilterByItem
label="Permissions"
:items="exampleMetadata?.allPermissions"
v-model="selectedPermissions"
/>
<ExampleSearchFilterByItem
label="Packages"
:items="exampleMetadata?.allPackages"
v-model="selectedPackages"
/>
</div>
<div class="results">
<p v-if="exampleMetadata == null">Loading examples...</p>
<template v-else>
<ul class="search-results">
<ExampleSearchResult
v-for="example of filteredExamples"
:key="example.name"
:example
/>
</ul>
<p v-if="filteredExamples.length === 0">No matching examples</p>
</template>
</div>
</div>
</template>
<style scoped>
.example-layout {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
grid-template-areas:
'search'
'results';
gap: 16px;
}
@media only screen and (min-width: 720px) {
.example-layout {
grid-template-columns: 256px 1fr;
grid-template-rows: auto 1fr;
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>
@@ -1,100 +0,0 @@
<script lang="ts" setup>
import { computed, toRaw } from 'vue';
import { KeySelectedObject } from '../utils/types';
const props = defineProps<{
label: string;
items?: string[];
}>();
const selectedItems = defineModel<KeySelectedObject>({
required: true,
});
const count = computed(() => {
return Object.values(toRaw(selectedItems.value)).filter(Boolean).length;
});
function toggleItem(pkg: string) {
selectedItems.value = {
...toRaw(selectedItems.value),
[pkg]: !selectedItems.value[pkg],
};
}
</script>
<template>
<div class="filter-container">
<p class="header">
<span>Filter by {{ label }}</span> <span v-if="count">({{ count }})</span>
</p>
<div class="scroll-container">
<ul>
<li v-for="item in items">
<label :title="item">
<input
type="checkbox"
:checked="selectedItems[item]"
@input="toggleItem(item)"
/>
<span>{{ item }}</span>
</label>
</li>
</ul>
</div>
</div>
</template>
<style scoped>
.filter-container {
height: 300px;
display: flex;
flex-direction: column;
background: var(--vp-c-bg-soft);
}
.scroll-container {
flex: 1;
overflow: hidden;
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;
}
.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,72 +0,0 @@
<script lang="ts" setup>
import { Example } from '../utils/types';
const props = defineProps<{
example: Example;
}>();
</script>
<template>
<li>
<a :href="example.url" target="_blank">
<p class="name">{{ example.name }}</p>
<p class="description">{{ example.description }}</p>
<p class="link">Open &rarr;</p>
</a>
</li>
</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;
}
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;
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;
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;
}
a:hover .link {
opacity: 100%;
}
</style>
+73 -157
View File
@@ -4,8 +4,7 @@ import useListExtensionDetails, {
ChromeExtension,
} from '../composables/useListExtensionDetails';
// Add extension IDs to end of the list. On the website, extensions will be sorted by a combination of weekly active users and rating.
// Change the commit message or PR title to: "docs: Added "[extension name]" to the homepage"
// Add extension IDs here. Order doesn't matter, will be sorted by weekly active users
const chromeExtensionIds = [
'ocfdgncpifmegplaglcnglhioflaimkd', // GitHub: Better Line Counts
'mgmdkjcljneegjfajchedjpdhbadklcf', // Anime Skip Player
@@ -13,126 +12,18 @@ const chromeExtensionIds = [
'elfaihghhjjoknimpccccmkioofjjfkf', // StayFree - Website Blocker & Web Analytics
'okifoaikfmpfcamplcfjkpdnhfodpkil', // Doozy: Ai Made Easy
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
'oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
'ceicccfeikoipigeghddpocceifjelph', // Plex Skipper
'aelkipgppclpfimeamgmlonimflbhlgf', // GitHub Custom Notifier
'djnlaiohfaaifbibleebjggkghlmcpcj', // Fluent Read
'nhclljcpfmmaiojbhhnkpjcfmacfcian', // Facebook Video Controls
'mblkhbaakhbhiimkbcnmeciblfhmafna', // ElemSnap - Quick capture of webpage elements and conversion to images,
'oajalfneblkfiejoadecnmodfpnaeblh', // MS Edge TTS (Text to Speech)
'nedcanggplmbbgmlpcjiafgjcpdimpea', // YTBlock - Block any content from YouTube™
'oadbjpccljkplmhnjekgjamejnbadlne', // demo.fun - Interactive product demos that convert
'iopdafdcollfgaoffingmahpffckmjni', // SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
'khjdmjcmpolknpccmaaipmidphjokhdf', // WorkFlowy MultiFlow
'fencadnndhdeggodopebjgdfdlhcimfk', // 香草布丁🌿🍮- https://github.com/Xdy1579883916/vanilla-pudding
'bnacincmbaknlbegecpioobkfgejlojp', // MaxFocus: Link Preview
'bcpgdpedphodjcjlminjbdeejccjbimp', // 汇率转换-中文版本
'loeilaonggnalkaiiaepbegccilkmjjp', // Currency Converter Plus
'npcnninnjghigjfiecefheeibomjpkak', // Respond Easy
'cfkdcideecefncbglkhneoflfnmhoicc', // mindful - stay focused on your goals
'lnhejcpclabmbgpiiomjbhalblnnbffg', // 1Proompt
'fonflmjnjbkigocpoommgmhljdpljain', // NiceTab - https://github.com/web-dahuyou/NiceTab
'fcffekbnfcfdemeekijbbmgmkognnmkd', // Draftly for LinkedIn
'nkndldfehcidpejfkokbeghpnlbppdmo', // YouTube Summarized - Summarize any YouTube video
'dbichmdlbjdeplpkhcejgkakobjbjalc', // 社媒助手 - https://github.com/iszhouhua/social-media-copilot
'opepfpjeogkbgeigkbepobceinnfmjdd', // Dofollow Links for SEO
'pdnenlnelpdomajfejgapbdpmjkfpjkp', // ChatGPT Writer: Use AI on Any Site (GPT-4o, Claude, Gemini, and More)
'jobnhifpphkgoelnhnopgkdhbdkiadmj', // discord message translator
'ncokhechhpjgjonhjnlaneglmdkfkcbj', // Habit Tracker app widget for daily habit tracking
'lnjaiaapbakfhlbjenjkhffcdpoompki', // Catppuccin for GitHub File Explorer Icons
'cpaedhbidlpnbdfegakhiamfpndhjpgf', // WebChat: Chat with anyone on any website
'fcphghnknhkimeagdglkljinmpbagone', // YouTube Auto HD + FPS
'lpomjgbicdemjkgmbnkjncgdebogkhlb', // MultiViewer Companion
'ggiafipgeeaaahnjamgpjcgkdpanhddg', // Sync Watch - Watch videos together on any site
'nmldnjcblcihmegipecakhmnieiofmgl', // Keyword Rank Checker
'gppllamhaciichleihemgilcpledblpn', // YouTube Simple View - Hide distractions & more
'pccbghdfdnnkkbcdcibchpbffdgednkf', // Propbar - Property Data Enhancer
'lfknakglefggmdkjdfhhofkjnnolffkh', // Text Search Pro - Search by case and whole-word match!
'mbenhbocjckkbaojacmaepiameldglij', // Invoice Generator
'phlfhkmdofajnbhgmbmjkbkdgppgoppb', // Monthly Bill Tracker
'macmkmchfoclhpbncclinhjflmdkaoom', // Wandpen - Instantly improve your writing with AI
'lhmgechokhmdekdpgkkemoeecelcaonm', // YouTube Hider - Remove Comments By Keywords, Usernames & Tools
'imgheieooppmahcgniieddodaliodeeg', // QA Compass - Record standardized bug reports easily
'npgghjedpchajflknnbngajkjkdhncdo', // aesthetic Notion, styled
'hmdcmlfkchdmnmnmheododdhjedfccka', // Eye Dropper
'eihpmapodnppeemkhkbhikmggfojdkjd', // Cursorful - Screen Recorder with Auto Zoom
'hjjkgbibknbahijglkffklflidncplkn', // Show IP Live View of Website IPs for Developers
'ilbikcehnpkmldojkcmlldkoelofnbde', // Strong Password Generator
'ocllfkhcdopiafndigclebelbecaiocp', // ZenGram: Mindful Instagram, Your Way
'odffpjnpocjfcaclnenaaaddghkgijdb', // Blync: Preview Links, Selection Search, AI Assistant
'kofbbilhmnkcmibjbioafflgmpkbnmme', // HTML to Markdown - Convert webpages to markdown
'boecmgggeigllcdocgioijmleimjbfkg', // Walmart WFS Profit Calculator
'dlnjcbkmomenmieechnmgglgcljhoepd', // Youtube Live Chat Fullscreen
'keiealdacakpnbbljlmhfgcebmaadieg', // Python Code Runner
'hafcajcllbjnoolpfngclfmmgpikdhlm', // Monochromate
'bmoggiinmnodjphdjnmpcnlleamkfedj', // AliasVault - Open-Source Password & (Email) Alias Manager
'hlnhhamckimoaiekbglafiebkfimhapb', // SnapThePrice: AI-Powered Real-time Lowest Price Finder
'gdjampjdgjmbifnhldgcnccdjkcoicmg', // radiofrance - news & broadcasts (French), music (international)
'jlnhphlghikichhgbnkepenehbmloenb', // Blens - Time Tracker and AI Insight
'njnammmpdodmfkodnfpammnpdcbhnlcm', // Always Light Mode - Setting website always in light mode
'lblmfclcfniabobmamfkdogcgdagbhhb', // DesignPicker - Color Picker & Font Detector
'pamnlaoeobcmhkliljfaofekeddpmfoh', // Web to PDF
'jmbcbeepjfenihlocplnbmbhimcoooka', // Online CSV Viewer
'nkjcoophmpcmmgadnljnlpbpfdfacgbo', // YouTube Video Transcript
'lcaieahkjgeggeiihblhcjbbjlppgieh', // NetSuite Record Scripts
'gmocfknjllodfiomnljmaehcplnekhlo', // VueTracker
'ggcfemmoabhhelfkhknhbnkmeahloiod', // CanCopy - A web extension that allow you to copy any content from website
'modkelfkcfjpgbfmnbnllalkiogfofhb', // Language Learning with AI
'npfopljnjbamegincfjelhjhnonnjloo', // Bilibili Feed History Helper
'edkhpdceeinkcacjdgebjehipmnbomce', // NZBDonkey - The ultimate NZB file download tool
'cckggnbnimdbbpmdinkkgbbncopbloob', // WeChat Markdown Editor(微信 Markdown 编辑器)
'jcblcjolcojmfopefcighfmkkefbaofg', // Tab Grab
'eehmoikadcijkapfjocnhjclpbaindlb', // BrowserLens - https://browserlens.com/
'hfhellofkjebbchcdffmicekjdomkcmc', // Epic Games Library Extension
'gknigcbhlammoakmmdddkblknanpjiac', // Zen Analytics Pixel Tracker - zapt.web.app
'cnklededohhcbmjjdlbjdkkihkgoggol', // Crypto Pulse - Compose your newtab with nature images, widgets & realtime Crypto Price & Bitcoin RSS.
'miponnamafdenpgjemkknimgjfibicdc', // Youtube Video Scheduler
'nhmbcmalgpkjbomhlhgdicanmkkaajmg', // Chatslator: Livestream Chat Translator
'mbamjfdjbcdgpopfnkkmlohadbbnplhm', // 公众号阅读增强器 - https://wxreader.honwhy.wang
'hannhecbnjnnbbafffmogdlnajpcomek', // 토탐정
'ehboaofjncodknjkngdggmpdinhdoijp', // 2FAS Pass - https://2fas.com/
'hnjamiaoicaepbkhdoknhhcedjdocpkd', // Quick Prompt - https://github.com/wenyuanw/quick-prompt
'kacblhilkacgfnkjfodalohcnllcgmjd', // Add QR Code Generator Icon Back To Address Bar
'fkbdlogfdjmpfepbbbjcgcfbgbcfcnne', // Piwik PRO Tracking Helper
'nkbikckldmljjiiajklecmgmajgapbfl', // PIPX - Take Control of Picture-in-Picture, Automatically
'hgppdobcpkfkmiegekaglonjajeojmdd', // Browsely - AI-powered browser extension
'ehmoihnjgkdimihkhokkmfjdgomohjgm', // Filmbudd Pro - Simple, private and synced ratings and watch notes across all your devices
'alglchohmdikgdjhafiicilegegieafa', // MultiField CopyCat - Copy, Paste & Autofill Web Forms Instantly
'aamihahiiogceidpbnfgehacgiecephe', // ChatSight - Add Table of Contents to ChatGPT
'cndibmoanboadcifjkjbdpjgfedanolh', // BetterCampus (prev. BetterCanvas)
'hinfimgacobnellbncbcpdlpaapcofaa', // Leetcode Fonts - Change fonts in leetcode effortlessly
'kbkbfefhhabpkibojinapkkgciiacggg', // TranslateManga - Manga Translator & Manga Tracker
'emeakbgdecgmdjgegnejpppcnkcnoaen', // SiteData - Free Website Traffic Checker & Reverse AdSense Tool
'gpnckbhgpnjciklpoehkmligeaebigaa', // Livestream Chat Reader - Text-to-Speech for YouTube/Twitch chat
'fjlalaedpfcojcfpkgkglbjjbbkofgnl', // ChatGPT Token Counter - Count tokens in real time on chatgpt conversation
'fbgblmjbeebanackldpbmpacppflgmlj', // LinuxDo Scripts - 为 linux.do 用户提供了一些增强功能
'dfacnjidgbagicaekenjgclfnhdnjjdi', // Zen Virtual Piano - https://zen-piano.web.app/
'naeibcegmgpofimedkmfgjgphfhfhlab', // Crypto Pulse price tracker - https://get-crypto-pulse.web.app/
'ffglckbhfbfmdkefdmjbhpnffkcmlhdh', // Redirect Web - Automatically redirect pages or open them in another app
'eglpfhbhmelampoihamjomgkeobgdofl', // Capture It - Capture & Edit Screenshots
'jmghclbfbbapimhbgnpffbimphlpolnm', // Teams Chat Exporter
'jdcppdokgfbnhiacbeplahgnciahnhck', // Lofi BGM Player - Free lofi focus music for work & study
'cgpmbiiagnehkikhcbnhiagfomajncpa', // Margin - Annotate and highlight any webpage, with your notes saved to the decentralized AT Protocol.
'mfjdonmgmgcijagclnkfhmjiblbfjaid', // KeyFloat - Floating multilingual keyboard with native key mappings, drag, dark mode, sounds, and dynamic layouts for macOS & Windows
'dhiekgdaipindoapjmcnpompdknjeijf', // Glossy New Tab - Say Goodbye to Boring Tabs with live wallpapers
'lapnciffpekdengooeolaienkeoilfeo', // All API Hub AI Relay & New API Manager - https://github.com/qixing-jk/all-api-hub
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
const { data } = useListExtensionDetails(chromeExtensionIds);
const sortedExtensions = computed(() => {
if (!data.value?.length) return [];
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);
});
@@ -144,56 +35,81 @@ function getStoreUrl(extension: ChromeExtension) {
</script>
<template>
<p v-if="isLoading" style="text-align: center; opacity: 50%">Loading...</p>
<p
v-else-if="err || sortedExtensions.length === 0"
style="text-align: center; opacity: 50%"
>
Failed to load extension details.
</p>
<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="relative">
<a
:href="getStoreUrl(extension)"
target="_blank"
:title="extension.name"
class="extension-name"
>{{ extension.name }}</a
>
<p class="description" :title="extension.shortDescription">
{{ extension.shortDescription }}
</p>
</div>
<p class="user-count">
<span>{{ extension.weeklyActiveUsers.toLocaleString() }} users</span
><template v-if="extension.rating != null"
>,
<span>{{ extension.rating }} stars</span>
</template>
<section class="vp-doc">
<div class="container">
<h2 id="whos-using-wxt">Who's Using WXT?</h2>
<p>
Battle tested and ready for production. Explore chrome extensions made
with WXT.
</p>
</li>
</ul>
<p class="centered pr">
<a
href="https://github.com/wxt-dev/wxt/edit/main/docs/.vitepress/components/UsingWxtSection.vue"
target="_blank"
>Open a PR</a
>
to add your extension to the list!
</p>
<ul>
<li
v-for="extension of sortedExtensions"
:key="extension.id"
class="relative"
>
<img
:src="extension.iconUrl"
:alt="`${extension.name} icon`"
referrerpolicy="no-referrer"
/>
<div class="relative">
<a
:href="getStoreUrl(extension)"
target="_blank"
:title="extension.name"
class="extension-name"
>{{ extension.name }}</a
>
<p class="description" :title="extension.shortDescription">
{{ extension.shortDescription }}
</p>
</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/.vitepress/components/UsingWxtSection.vue"
target="_blank"
>Open a PR</a
>
to add your extension to the list!
</p>
</div>
</section>
</template>
<style scoped>
.vp-doc {
padding: 0 24px;
}
@media (min-width: 640px) {
.vp-doc {
padding: 0 48px;
}
}
@media (min-width: 960px) {
.vp-doc {
padding: 0 64px;
}
}
.container {
max-width: 1152px;
margin: 0 auto;
display: flex;
flex-direction: column;
}
li img {
width: 116px;
height: 116px;
@@ -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()}`;
});
}
@@ -26,7 +26,6 @@ const query = `query ${operationName}($ids:[String!]!) {
export default function (ids: string[]) {
const data = ref<ChromeExtension[]>();
const err = ref<unknown>();
const isLoading = ref(true);
fetch('https://queue.wxt.dev/api', {
method: 'POST',
@@ -35,12 +34,8 @@ export default function (ids: string[]) {
query,
variables: { ids },
}),
headers: {
'Content-Type': 'application/json',
},
})
.then(async (res) => {
isLoading.value = false;
const {
data: { chromeExtensions },
} = await res.json();
@@ -48,7 +43,6 @@ export default function (ids: string[]) {
err.value = undefined;
})
.catch((error) => {
isLoading.value = false;
console.error(error);
data.value = undefined;
err.value = error;
@@ -57,6 +51,5 @@ export default function (ids: string[]) {
return {
data,
err,
isLoading,
};
}
+106 -222
View File
@@ -1,266 +1,150 @@
import { DefaultTheme, defineConfig } from 'vitepress';
import typedocSidebar from '../api/reference/typedoc-sidebar.json';
import {
menuGroup,
menuItem,
menuRoot,
navItem,
prepareTypedocSidebar,
} from './utils/menus';
import { meta, script } from './utils/head';
import footnote from 'markdown-it-footnote';
import { version as wxtVersion } from '../../packages/wxt/package.json';
import { version as i18nVersion } from '../../packages/i18n/package.json';
import { version as autoIconsVersion } from '../../packages/auto-icons/package.json';
import { version as unocssVersion } from '../../packages/unocss/package.json';
import { version as storageVersion } from '../../packages/storage/package.json';
import { version as analyticsVersion } from '../../packages/analytics/package.json';
import { version as runnerVersion } from '../../packages/runner/package.json';
import { version as isBackgroundVersion } from '../../packages/is-background/package.json';
import addKnowledge from 'vitepress-knowledge';
import {
groupIconMdPlugin,
groupIconVitePlugin,
localIconLoader,
} from 'vitepress-plugin-group-icons';
import { Feed } from 'feed';
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import llmstxt from 'vitepress-plugin-llms';
import { generateCliDocs } from './plugins/generate-cli-docs';
import typedocSidebar from '../api/typedoc-sidebar.json';
const origin = 'https://wxt.dev';
const filteredTypedocSidebar = typedocSidebar.filter(
(item) => item.text !== 'API',
);
// Typedoc's markdown theme adds collapse: true to all our items, event ones without any children,
// so they need to be removed.
function removeCollapsedWithNoItems(items: DefaultTheme.SidebarItem[]) {
for (const item of items) {
if (item.items) removeCollapsedWithNoItems(item.items);
else delete item.collapsed;
}
}
removeCollapsedWithNoItems(filteredTypedocSidebar);
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 utilties 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,
plugins: [
llmstxt(),
groupIconVitePlugin({
customIcon: {
'wxt.config.ts': localIconLoader(
import.meta.url,
'../public/logo.svg',
),
},
}),
],
plugins: [generateCliDocs()],
},
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 writeFile(join(site.outDir, 'rss.xml'), feed.rss2(), 'utf8');
hostname: 'https://wxt.dev',
},
head: [
meta('og:type', 'website'),
meta('og:title', ogTitle),
meta('og:image', ogImage),
meta('og:url', ogUrl),
meta('og:description', description),
meta('twitter:card', 'summary_large_image', { useName: true }),
script('https://umami.aklinker1.io/script.js', {
'data-website-id': 'c1840c18-a12c-4a45-a848-55ae85ef7915',
async: '',
}),
['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:title', content: ogTitle }],
['meta', { property: 'og:image', content: ogImage }],
['meta', { property: 'og:url', content: ogUrl }],
['meta', { property: 'og:description', content: description }],
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
[
'script',
{
async: '',
'data-website-id': 'c1840c18-a12c-4a45-a848-55ae85ef7915',
src: 'https://umami.aklinker1.io/script.js',
},
],
],
markdown: {
config: (md) => {
md.use(footnote);
md.use(groupIconMdPlugin);
},
languageAlias: {
mjs: 'js',
},
},
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
logo: {
src: '/logo.svg',
alt: 'WXT logo',
},
footer: {
message: [
'<a class="light-netlify" href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-bg.svg" alt="Deploys by Netlify" style="display: inline;" /></a>',
'<a class="dark-netlify" href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-accent.svg" alt="Deploys by Netlify" style="display: inline;" /></a>',
'Released under the <a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE">MIT License</a>.',
].join('<br/>'),
copyright:
'Copyright © 2023-present <a href="https://github.com/aklinker1">Aaron Klinker</a>',
},
editLink: {
pattern: 'https://github.com/wxt-dev/wxt/edit/main/docs/:path',
},
search: {
provider: 'local',
},
socialLinks: [
{ icon: 'discord', link: 'https://discord.gg/ZFsZqGery9' },
{ icon: 'github', link: 'https://github.com/wxt-dev/wxt' },
],
nav: [
navItem('Guide', '/guide/installation'),
navItem('Examples', '/examples'),
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}`),
),
),
]),
{ text: 'Guide', link: '/guide/installation.md' },
{ text: 'Entrypoints', link: '/entrypoints/background.md' },
{ text: 'Examples', link: '/examples.md' },
{ text: 'API', link: '/api/cli.md' },
],
sidebar: {
'/guide/': menuRoot([
menuGroup('Get Started', '/guide/', [
menuItem('Introduction', 'introduction.md'),
menuItem('Installation', 'installation.md'),
]),
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('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('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'),
]),
]),
'/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'),
'/guide/': [
{
text: 'Guide',
items: [
{ text: 'Introduction', link: '/guide/introduction.md' },
{ text: 'Installation', link: '/guide/installation.md' },
{ text: 'Configuration', link: '/guide/configuration.md' },
{ text: 'Entrypoints', link: '/guide/entrypoints.md' },
{ text: 'Assets', link: '/guide/assets.md' },
{ text: 'Multiple Browsers', link: '/guide/multiple-browsers.md' },
{ text: 'Publishing', link: '/guide/publishing.md' },
{ text: 'Auto-imports', link: '/guide/auto-imports.md' },
{ text: 'Manifest.json', link: '/guide/manifest.md' },
{ text: 'Extension APIs', link: '/guide/extension-apis.md' },
{ text: 'Storage', link: '/guide/storage.md' },
{ text: 'Content Script UI', link: '/guide/content-script-ui.md' },
{ text: 'Remote Code', link: '/guide/remote-code.md' },
{ text: 'Development', link: '/guide/development.md' },
{ text: 'Testing', link: '/guide/testing.md' },
{ text: 'Vite', link: '/guide/vite.md' },
],
true,
),
menuGroup('API Reference', prepareTypedocSidebar(typedocSidebar), true),
]),
},
{
text: 'Other',
items: [
{ text: 'Migrate to WXT', link: '/guide/migrate-to-wxt.md' },
{ text: 'Compare', link: '/guide/compare.md' },
],
},
],
'/entrypoints/': [
{
text: 'Entrypoints',
items: [
{ text: 'Background', link: '/entrypoints/background.md' },
{ text: 'Bookmarks', link: '/entrypoints/bookmarks.md' },
{
text: 'Content Scripts',
link: '/entrypoints/content-scripts.md',
},
{ text: 'CSS', link: '/entrypoints/css.md' },
{ text: 'Devtools', link: '/entrypoints/devtools.md' },
{ text: 'History', link: '/entrypoints/history.md' },
{ text: 'Newtab', link: '/entrypoints/newtab.md' },
{ text: 'Options', link: '/entrypoints/options.md' },
{ text: 'Popup', link: '/entrypoints/popup.md' },
{ text: 'Sandbox', link: '/entrypoints/sandbox.md' },
{ text: 'Sidepanel', link: '/entrypoints/sidepanel.md' },
{ text: 'Unlisted Pages', link: '/entrypoints/unlisted-pages.md' },
{
text: 'Unlisted Scripts',
link: '/entrypoints/unlisted-scripts.md',
},
].sort((l, r) => l.text.localeCompare(r.text)),
},
],
'/api/': [
{
items: [
{ text: 'CLI', link: '/api/cli.md' },
{
text: 'Modules',
items: filteredTypedocSidebar,
},
],
},
],
},
socialLinks: [{ icon: 'github', link: 'https://github.com/wxt-dev/wxt' }],
},
});
-3
View File
@@ -1,3 +0,0 @@
import { createContentLoader } from 'vitepress';
export default createContentLoader('blog/*.md');
-61
View File
@@ -1,61 +0,0 @@
import { resolve } from 'node:path';
import consola from 'consola';
import spawn from 'nano-spawn';
const cliDir = resolve('packages/wxt/src/cli/commands');
const cliDirGlob = resolve(cliDir, '**');
export default {
watch: [cliDirGlob],
async load() {
consola.info(`Generating CLI docs`);
const [wxt, build, zip, prepare, clean, init, submit, submitInit] =
await Promise.all([
getWxtHelp(''),
getWxtHelp('build'),
getWxtHelp('zip'),
getWxtHelp('prepare'),
getWxtHelp('clean'),
getWxtHelp('init'),
getPublishExtensionHelp(''),
getPublishExtensionHelp('init'),
]);
consola.success(`Generated CLI docs`);
return {
wxt,
build,
zip,
prepare,
clean,
init,
submit,
submitInit,
};
},
};
async function getHelp(command: string): Promise<string> {
const args = command.split(' ');
const res = await spawn(args[0], [...args.slice(1), '--help'], {
cwd: 'packages/wxt',
});
return res.stdout;
}
function getWxtHelp(command: string): Promise<string> {
return getHelp(`pnpm -s wxt ${command}`.trim());
}
async function getPublishExtensionHelp(command: string): Promise<string> {
const res = await getHelp(
`./node_modules/.bin/publish-extension ${command}`.trim(),
);
return res.replace(/\$ publish-extension/g, '$ wxt submit');
}
export interface Command {
name: string;
docs: string;
}
+5
View File
@@ -0,0 +1,5 @@
# CLI Reference
> Reference generated from `wxt <command> --help`
{{ DOCS }}
@@ -0,0 +1,108 @@
import { relative, resolve } from 'node:path';
import { readFileSync, writeFileSync } from 'node:fs';
import { format } from 'prettier';
import consola from 'consola';
import { execaCommand } from 'execa';
let hasGenerated = false;
const cliDir = resolve('src/cli/commands');
const cliDirGlob = resolve(cliDir, '**');
const cliTemplatePath = resolve('docs/.vitepress/plugins/cli.tpl.md');
const cliPath = resolve('docs/api/cli.md');
const PREFACE = `<!--
DO NOT EDIT
Generated by \`wxt/docs/.vitepress/plugins/generate-config-docs.ts\`
To make changes to the config reference, update the JSDoc in \`src/core/types/external.ts\`.
-->`;
export function generateCliDocs() {
writeFileSync(cliPath, '');
const generateDocs = async () => {
consola.info(`Generating ${relative(process.cwd(), cliPath)}`);
try {
const res = await execaCommand('pnpm -s wxt --help');
const dev = splitInfo(res.stdout);
const lines: Array<string | string[]> = [
`## \`wxt\``,
'```sh',
dev.rest,
'```',
];
const commands = await Promise.all(
extractCommands(dev.info).map(async (command) => {
const res = await execaCommand(`pnpm -s wxt ${command} --help`);
const { rest: docs } = splitInfo(res.stdout);
return [`## \`wxt ${command}\``, '```sh', docs, '```'];
}),
);
lines.push(...commands);
const text = await format(
PREFACE +
'\n\n' +
readFileSync(cliTemplatePath, 'utf-8').replace(
'{{ DOCS }}',
lines.flat().join('\n'),
),
{ parser: 'markdown' },
);
writeFileSync(cliPath, text);
consola.success(`Generated ${relative(process.cwd(), cliPath)}`);
} catch (err) {
consola.fail(`Failed to generate ${relative(process.cwd(), cliPath)}`);
consola.error(err.message);
}
};
return {
name: 'docs:generate-cli-docs',
async config() {
if (!hasGenerated) {
hasGenerated = true;
await generateDocs();
}
},
configureServer(server: any) {
server.watcher.add(cliDirGlob);
},
async handleHotUpdate(ctx: { file: string }) {
if (ctx.file === cliTemplatePath || ctx.file.includes(cliDir)) {
await generateDocs();
}
},
};
}
function splitInfo(text: string): { info: string; rest: string } {
const infoStart = text.indexOf('For more info,');
const infoEnd = text.indexOf('\n\nOptions:');
if (infoStart === -1 || infoEnd === -1) {
return { info: '', rest: text };
}
const info = text.substring(infoStart, infoEnd).trim();
const rest = text.replace(info, '').trim().replace('\n\n\n\n', '\n\n');
return { info, rest };
}
function extractCommands(info: string): string[] {
const commands: string[] = [];
// Split the info by line and iterate through each line
info.split('\n').forEach((line) => {
// Use regex to capture the command after "$ wxt " and before "--help"
const match = line.match(/\$ wxt (\w+) --help/);
if (match && match[1]) {
commands.push(match[1]);
}
});
return commands;
}
+56 -47
View File
@@ -1,68 +1,77 @@
/* 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);
}
.dark {
--wxt-c-green: #53bc4a;
--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);
--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;
}
/* Customize Individual Components */
.vp-doc a {
color: var(--wxt-c-green);
}
.dark {
--vp-c-bg: #131413;
--vp-c-bg-soft: #1a1b1a;
--vp-c-bg-soft-up: #1f201f;
--vp-c-bg-soft-down: #262926;
--vp-c-bg-soft-mute: #242424;
--vp-c-bg-alt: #171817;
--vp-c-default: #313136;
--vp-c-default-1: #3a3a3c;
--vp-c-default-2: #505053;
--vp-c-default-3: #2c2c30;
--vp-c-default-soft: #252529;
--vp-code-block-bg: #191a19;
--vp-code-copy-code-bg: #212321;
--vp-code-copy-code-hover-bg: #292d29;
--vp-custom-block-info-bg: #191a19;
--vp-code-color: #c9def1;
}
.vp-doc .no-vertical-dividers th,
.vp-doc .no-vertical-dividers td {
border: none;
}
.vp-doc .no-vertical-dividers tr {
border: 1px solid var(--vp-c-divider);
}
body {
overflow-y: scroll;
}
.VPSidebar {
user-select: none;
}
.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);
}
.light-netlify {
display: inline;
}
.dark .light-netlify {
display: none;
}
.dark-netlify {
display: none;
}
.dark .dark-netlify {
display: inline;
}
+3 -9
View File
@@ -2,19 +2,13 @@ import DefaultTheme from 'vitepress/theme';
import Icon from '../components/Icon.vue';
import EntrypointPatterns from '../components/EntrypointPatterns.vue';
import UsingWxtSection from '../components/UsingWxtSection.vue';
import ExampleSearch from '../components/ExampleSearch.vue';
import BlogLayout from '../components/BlogLayout.vue';
import './custom.css';
import 'virtual:group-icons.css';
export default {
extends: DefaultTheme,
enhanceApp(ctx) {
ctx.app
.component('Icon', Icon)
.component('EntrypointPatterns', EntrypointPatterns)
.component('UsingWxtSection', UsingWxtSection)
.component('ExampleSearch', ExampleSearch)
.component('blog', BlogLayout);
ctx.app.component('Icon', Icon);
ctx.app.component('EntrypointPatterns', EntrypointPatterns);
ctx.app.component('UsingWxtSection', UsingWxtSection);
},
};
-28
View File
@@ -1,28 +0,0 @@
import { HeadConfig } from 'vitepress/types/shared';
export function meta(
property: string,
content: string,
options?: { useName: boolean },
): HeadConfig {
return [
'meta',
{
[options?.useName ? 'name' : 'property']: property,
content,
},
];
}
export function script(
src: string,
props: Record<string, string> = {},
): HeadConfig {
return [
'script',
{
...props,
src,
},
];
}
-107
View File
@@ -1,107 +0,0 @@
import { DefaultTheme } from 'vitepress';
type SidebarItem = DefaultTheme.SidebarItem;
type NavItem = DefaultTheme.NavItem;
type NavItemWithLink = DefaultTheme.NavItemWithLink;
type NavItemWithChildren = DefaultTheme.NavItemWithChildren;
type NavItemChildren = DefaultTheme.NavItemChildren;
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 menuRoot(items: SidebarItem[]) {
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,
base: string,
items: SidebarItem[],
collapsible?: boolean,
): SidebarItem;
export function menuGroup(
text: string,
a: string | SidebarItem[],
b?: SidebarItem[] | boolean,
c?: boolean,
): SidebarItem {
if (typeof a === 'string' && Array.isArray(b)) {
return {
text,
base: a,
items: b,
collapsed: c,
};
}
if (typeof a !== 'string' && !Array.isArray(b))
return {
text,
items: a,
collapsed: b,
};
throw Error('Unknown overload');
}
export function menuItems(items: SidebarItem[]) {
return {
items,
};
}
export function menuItem(
text: string,
link: string,
items?: SidebarItem[],
): SidebarItem {
if (items) {
return { text, link, items };
}
return { text, link };
}
/**
* Clean up and add badges to typedoc leaf sections
*/
export function prepareTypedocSidebar(items: SidebarItem[]) {
// skip contents file
const filtered = items.slice(1);
// remove Typedoc's collapse: true from text nodes
const prepareItems = (items: DefaultTheme.SidebarItem[], depth = 0) => {
for (const item of items) {
if (item.items) {
prepareItems(item.items, depth + 1);
const hasLeaves = item.items.some((item) => !item.items);
if (hasLeaves) {
item.text += ` <span class="badge">${item.items.length}</span>`;
}
} else {
delete item.collapsed;
}
}
};
// process
prepareItems(filtered);
// return
return filtered;
}
-18
View File
@@ -1,18 +0,0 @@
export interface Example {
name: string;
description?: string;
url: string;
searchText: string;
apis: string[];
permissions: string[];
packages: string[];
}
export type ExamplesMetadata = {
examples: Example[];
allApis: string[];
allPermissions: string[];
allPackages: string[];
};
export type KeySelectedObject = Record<string, boolean | undefined>;
-1
View File
@@ -1 +0,0 @@
<!--@include: ../packages/analytics/README.md-->
-7
View File
@@ -1,7 +0,0 @@
# `wxt build`
<script setup>
import { data } from '../../.vitepress/loaders/cli.data.ts'
</script>
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.build }}</code></pre></div>
-7
View File
@@ -1,7 +0,0 @@
# `wxt clean`
<script setup>
import { data } from '../../.vitepress/loaders/cli.data.ts'
</script>
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.clean }}</code></pre></div>
-7
View File
@@ -1,7 +0,0 @@
# `wxt init`
<script setup>
import { data } from '../../.vitepress/loaders/cli.data.ts'
</script>
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.init }}</code></pre></div>
-7
View File
@@ -1,7 +0,0 @@
# `wxt prepare`
<script setup>
import { data } from '../../.vitepress/loaders/cli.data.ts'
</script>
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.prepare }}</code></pre></div>
-9
View File
@@ -1,9 +0,0 @@
# `wxt submit init`
> Alias for [`publish-browser-extension`](https://www.npmjs.com/package/publish-browser-extension)
<script setup>
import { data } from '../../.vitepress/loaders/cli.data.ts'
</script>
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.submitInit }}</code></pre></div>
-9
View File
@@ -1,9 +0,0 @@
# `wxt submit`
> Alias for [`publish-browser-extension`](https://www.npmjs.com/package/publish-browser-extension)
<script setup>
import { data } from '../../.vitepress/loaders/cli.data.ts'
</script>
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.submit }}</code></pre></div>
-7
View File
@@ -1,7 +0,0 @@
# `wxt zip`
<script setup>
import { data } from '../../.vitepress/loaders/cli.data.ts'
</script>
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.zip }}</code></pre></div>
-7
View File
@@ -1,7 +0,0 @@
# `wxt`
<script setup>
import { data } from '../../.vitepress/loaders/cli.data.ts'
</script>
<div class="language-sh vp-adaptive-theme active"><pre class="shiki shiki-themes github-light github-dark vp-code"><code>{{ data.wxt }}</code></pre></div>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 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 />

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