Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 98bc1f2cd1 | |||
| f6a066f166 | |||
| c20478cde2 | |||
| ebd50079a7 | |||
| b17bd87865 | |||
| e8b471e23f | |||
| 32ffca4168 | |||
| e9c573d5a1 | |||
| ae5126d726 | |||
| 24a77ca9b5 | |||
| 174cbbc5eb | |||
| 607c70dd56 | |||
| 616d012326 | |||
| 75b6ca7172 | |||
| 1497b3099c |
@@ -1,10 +0,0 @@
|
||||
extends:
|
||||
- '@commitlint/config-conventional'
|
||||
rules:
|
||||
subject-case:
|
||||
- 0
|
||||
- always
|
||||
- - sentence-case
|
||||
- start-case
|
||||
- pascal-case
|
||||
- upper-case
|
||||
@@ -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
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Set default
|
||||
* @aklinker1 @Timeraa
|
||||
|
||||
# Secure Directories
|
||||
/.github/ @aklinker1
|
||||
|
||||
# Creator of specific wxt modules
|
||||
/packages/auto-icons/ @Timeraa
|
||||
/packages/unocss/ @Timeraa
|
||||
@@ -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
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
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
|
||||
|
||||
Share minimal reproduction. Examples of a minimal reproduction:
|
||||
|
||||
- PR with a failing test case
|
||||
- ZIP file containing a minimal WXT project (be sure to include your lockfile!!)
|
||||
|
||||
> **_⚠️ If you don't upload a minimal reproduction, your issue will be closed until a reproduction is added._**
|
||||
>
|
||||
> Why? https://antfu.me/posts/why-reproductions-are-required
|
||||
|
||||
Steps to reproduce the bug using the reproduction:
|
||||
|
||||
1. Install dependencies: `pnpm i`
|
||||
2. Start dev mode: `pnpm dev`
|
||||
3. Click this...
|
||||
4. Do that...
|
||||
5. Etc...
|
||||
|
||||
### 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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -2,7 +2,7 @@
|
||||
name: Feature request
|
||||
about: Suggest an idea for WXT
|
||||
title: ''
|
||||
type: Feature
|
||||
labels: feature
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
|
||||
@@ -1,31 +1,17 @@
|
||||
name: Basic Setup
|
||||
description: Install PNPM, Node, and dependencies
|
||||
|
||||
inputs:
|
||||
install:
|
||||
default: 'true'
|
||||
type: boolean
|
||||
description: Whether or not to run 'pnpm install'
|
||||
|
||||
installArgs:
|
||||
default: ''
|
||||
type: string
|
||||
description: Additional args to append to "pnpm install"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: 🛠️ Setup PNPM
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: 🛠️ Setup NodeJS
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
### Overview
|
||||
|
||||
<!-- Describe your changes and why you made them -->
|
||||
|
||||
### Manual Testing
|
||||
|
||||
<!-- Describe how to test your changes to make sure the PR works as intended -->
|
||||
|
||||
### Related Issue
|
||||
|
||||
<!-- If this PR is related to an issue, please link it here -->
|
||||
|
||||
This PR closes #<issue_number>
|
||||
@@ -1,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@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build All Packages
|
||||
run: pnpm buildc all
|
||||
|
||||
- name: Publish
|
||||
run: pnpx pkg-pr-new publish --compact --pnpm './packages/*'
|
||||
@@ -1,29 +0,0 @@
|
||||
name: 🎉 PR closed
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
thank-you:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged == true
|
||||
|
||||
steps:
|
||||
- name: Post Thank You Comment
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
env:
|
||||
comment: Thanks for helping make WXT better!
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: process.env.comment
|
||||
})
|
||||
@@ -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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
# Only fetch the config file from the repository
|
||||
sparse-checkout-cone-mode: false
|
||||
sparse-checkout: .commitlintrc.yml
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install --global @commitlint/config-conventional commitlint
|
||||
|
||||
- name: Check PR title with commitlint
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
HELP_URL: https://github.com/wxt-dev/wxt/blob/main/CONTRIBUTING.md#conventional-pr-titles
|
||||
run: echo "$PR_TITLE" | npx commitlint --help-url $HELP_URL
|
||||
@@ -1,4 +1,4 @@
|
||||
name: 📝 Publish Docs
|
||||
name: Publish Docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -10,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@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@v3
|
||||
- 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 }}
|
||||
|
||||
@@ -1,69 +1,34 @@
|
||||
name: 🚀 Release
|
||||
name: Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: Package to release
|
||||
default: wxt
|
||||
type: choice
|
||||
options:
|
||||
- analytics
|
||||
- auto-icons
|
||||
- i18n
|
||||
- module-react
|
||||
- module-solid
|
||||
- module-svelte
|
||||
- module-vue
|
||||
- storage
|
||||
- unocss
|
||||
- webextension-polyfill
|
||||
- wxt
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate
|
||||
uses: './.github/workflows/validate.yml'
|
||||
secrets: inherit
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
needs:
|
||||
- validate
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- 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: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
|
||||
pnpm build
|
||||
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 }}
|
||||
|
||||
@@ -1,42 +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
|
||||
- module-react
|
||||
- module-solid
|
||||
- module-svelte
|
||||
- module-vue
|
||||
- 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@v4
|
||||
|
||||
- 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,47 +0,0 @@
|
||||
name: 🔄 Update @wxt-dev/browser
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Every day at midnight
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: 'Sync with @types/chrome'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
installArgs: --ignore-scripts
|
||||
|
||||
- name: Generate Latest Code
|
||||
working-directory: packages/browser
|
||||
run: pnpm gen
|
||||
|
||||
- name: Run Checks
|
||||
working-directory: packages/browser
|
||||
run: pnpm check
|
||||
|
||||
- name: Commit Changes
|
||||
id: commit
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
commit_message: 'fix: Upgrade \`@wxt-dev/browser\` to latest \`@types/chrome\` version'
|
||||
file_pattern: 'packages/browser/**'
|
||||
|
||||
- name: Publish Package
|
||||
if: steps.commit.outputs.changes_detected == 'true'
|
||||
working-directory: packages/browser
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
|
||||
pnpm publish
|
||||
@@ -1,4 +1,4 @@
|
||||
name: 🛡️ Validate
|
||||
name: Validate
|
||||
on:
|
||||
workflow_call:
|
||||
pull_request:
|
||||
@@ -6,93 +6,57 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
name: Checks
|
||||
formatting:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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@v4
|
||||
|
||||
- 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@v4
|
||||
|
||||
- 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
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Run Tests
|
||||
run: pnpm test:coverage -- --reporter=default --reporter=hanging-process
|
||||
|
||||
- name: Upload Coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- name: pnpm test:coverage
|
||||
run: pnpm test:coverage --reporter=default --reporter=hanging-process
|
||||
- uses: codecov/codecov-action@v3
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
windows-tests:
|
||||
name: Windows Tests
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Run Tests
|
||||
run: pnpm test
|
||||
|
||||
- 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
|
||||
@@ -101,34 +65,18 @@ jobs:
|
||||
- vanilla
|
||||
- vue
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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 }}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
name: 📼 VHS
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'docs/tapes/*.tape'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
vhs:
|
||||
name: Create VHS
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ github.repository == 'wxt-dev/wxt' }}
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
install: false
|
||||
|
||||
# This prevents pnpm dlx from downloading WXT in the video
|
||||
- name: Pre-install WXT
|
||||
run: |
|
||||
pnpm store add wxt@latest
|
||||
pnpm dlx wxt@latest --version
|
||||
|
||||
- name: Record VHS
|
||||
uses: charmbracelet/vhs-action@v2.1.0
|
||||
with:
|
||||
path: 'docs/tapes/init-demo.tape'
|
||||
|
||||
- name: Save recorded GIF
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
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'
|
||||
@@ -6,6 +6,7 @@
|
||||
.webextrc
|
||||
.wxt
|
||||
*.log
|
||||
/.vscode/
|
||||
/docs/.vitepress/cache
|
||||
coverage
|
||||
dist
|
||||
@@ -16,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
|
||||
|
||||
@@ -5,4 +5,3 @@ dist
|
||||
docs/.vitepress/cache
|
||||
pnpm-lock.yaml
|
||||
CHANGELOG.md
|
||||
packages/browser/src/gen
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"davidanson.vscode-markdownlint",
|
||||
"esbenp.prettier-vscode",
|
||||
"github.vscode-github-actions"
|
||||
]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
// Set default formatter
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
|
||||
"[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||
"[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||
"[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||
"[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||
|
||||
// Additional guidelines for Copilot
|
||||
"github.copilot.chat.codeGeneration.instructions": [
|
||||
{ "file": "CONTRIBUTING.md" }
|
||||
]
|
||||
}
|
||||
@@ -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.
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
@@ -8,17 +8,9 @@ If you want to add a new feature, please create an issue or discussion first so
|
||||
|
||||
## Conventional Commits
|
||||
|
||||
This project uses [Conventional Commit format](https://www.conventionalcommits.org/en/v1.0.0/) to automatically generate a changelog and better understand the changes in the project
|
||||
This project uses [Conventional Commits](https://www.conventionalcommits.org/en) to automate versioning. If you're a new contributor, don't worry about this. When you open a PR, a maintainer will change the PR's title so it's in the style of conventional commits, but that's all.
|
||||
|
||||
Here are some examples of conventional commit messages:
|
||||
|
||||
- `feat: add new functionality`
|
||||
- `fix: correct typos in code`
|
||||
- `ci: add GitHub Actions for automated testing`
|
||||
|
||||
## Conventional PR Titles
|
||||
|
||||
The title of your pull request should follow the [conventional commit format](#conventional-commits). When a pull request is merged to the main branch, all changes are going to be squashed into a single commit. The message of this commit will be the title of the pull request. And for every release, the commit messages are used to generate the changelog.
|
||||
Maintainers, commits to the `main` branch (either directly or via PRs) must be valid conventional commits.
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -40,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
|
||||
```
|
||||
|
||||
@@ -66,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:
|
||||
|
||||
@@ -99,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.
|
||||
|
||||
@@ -114,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": {
|
||||
@@ -132,55 +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.
|
||||
|
||||
## Releasing 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.
|
||||
|
||||
## Upgrading Dependencies
|
||||
|
||||
Use [`taze`](https://www.npmjs.com/package/taze) to upgrade dependencies throughout the entire monorepo.
|
||||
|
||||
```sh
|
||||
pnpm dlx taze -r
|
||||
```
|
||||
|
||||
Configuration is in [`taze.config.ts`](./taze.config.ts).
|
||||
|
||||
## Install Unreleased Versions
|
||||
|
||||
This repo uses https://pkg.pr.new to publish versions of all it's packages for almost every commit. You can install them via:
|
||||
|
||||
```sh
|
||||
npm i https://pkg.pr.new/[package-name]@[ref]
|
||||
```
|
||||
|
||||
Or use one of the shorthands:
|
||||
|
||||
```sh
|
||||
# Install the latest build of `wxt` from a PR:
|
||||
npm i https://pkg.pr.new/wxt@1283
|
||||
|
||||
# Install the latest build of `@wxt-dev/module-react` on the `main` branch
|
||||
npm i https://pkg.pr.new/@wxt-dev/module-react@main
|
||||
|
||||
# Install `@wxt-dev/storage` from a specific commit:
|
||||
npm i https://pkg.pr.new/@wxt-dev/module-react@426f907
|
||||
```
|
||||
|
||||
## Blog Posts
|
||||
|
||||
Anyone is welcome to submit a blog post on https://wxt.dev/blog!
|
||||
|
||||
> [!NOTE]
|
||||
> Before starting on a blog post, please message Aaron on Discord or start a discussion on GitHub to get permission to write about a topic, but most topics are welcome: Major version updates, tutorials, etc.
|
||||
|
||||
- **English only**: Blog posts should be written in English. Unfortunately, our maintainers doesn't have the bandwidth right now to translate our docs, let alone blog posts. Sorry 😓
|
||||
- **AI**: Please only use AI to translate or proof-read your blog post. Don't generate the whole thing... We don't want to publish that.
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<h1 align="center">
|
||||
<img align="top" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
|
||||
<img style="vertical-align:middle" width="44" src="./docs/public/hero-logo.svg" alt="WXT Logo">
|
||||
<span>WXT</span>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<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="downloads" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<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="license | MIT" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="NPM" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<span> </span>
|
||||
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="coverage" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
|
||||
<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>
|
||||
|
||||
<p align="center">
|
||||
@@ -18,46 +18,33 @@
|
||||
<br/>
|
||||
<span>⚡</span>
|
||||
<br/>
|
||||
<q><i>It's like Nuxt, but for Web Extensions</i></q>
|
||||
<q><i>It's like Nuxt, but for Chrome Extensions</i></q>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://wxt.dev/guide/installation.html" target="_blank">Get Started</a>
|
||||
<a href="https://wxt.dev" target="_blank">Get Started</a>
|
||||
•
|
||||
<a href="https://wxt.dev/guide/installation.html" target="_blank">Installation</a>
|
||||
•
|
||||
<a href="https://wxt.dev/api/config.html" target="_blank">Configuration</a>
|
||||
•
|
||||
<a href="https://wxt.dev/examples.html" target="_blank">Examples</a>
|
||||
•
|
||||
<a href="https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md" target="_blank">Changelog</a>
|
||||
•
|
||||
<a href="https://discord.gg/ZFsZqGery9" target="_blank">Discord</a>
|
||||
</p>
|
||||
|
||||

|
||||

|
||||
|
||||
## 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:
|
||||
|
||||
<!-- automd:pm-x version="latest" name="wxt" args="init" -->
|
||||
|
||||
```sh
|
||||
# npm
|
||||
npx wxt@latest init
|
||||
|
||||
# pnpm
|
||||
pnpm dlx wxt@latest init
|
||||
|
||||
# bun
|
||||
bunx wxt@latest init
|
||||
pnpx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
<!-- /automd -->
|
||||
|
||||
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
|
||||
|
||||
## Features
|
||||
@@ -68,28 +55,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).
|
||||
|
||||
<a href="https://github.com/sponsors/wxt-dev"><img alt="WXT Sponsors" src="https://raw.githubusercontent.com/wxt-dev/static/refs/heads/main/sponsorkit/sponsors.svg"></a>
|
||||
- 🤖 Automated publishing
|
||||
|
||||
## Contributors
|
||||
|
||||
<!-- automd:contributors author="aklinker1" license="MIT" github="wxt-dev/wxt" -->
|
||||
|
||||
Published under the [MIT](https://github.com/wxt-dev/wxt/blob/main/LICENSE) license.
|
||||
Made by [@aklinker1](https://github.com/aklinker1) and [community](https://github.com/wxt-dev/wxt/graphs/contributors) 💛
|
||||
<br><br>
|
||||
<a href="https://github.com/wxt-dev/wxt/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
|
||||
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
|
||||
</a>
|
||||
|
||||
<!-- /automd -->
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
While WXT is in prerelease, only the latest version will receive security updates. The latest version is:
|
||||
|
||||
<img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048">
|
||||
|
||||
<!--
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
|
||||
Tell them where to go, how often they can expect to get an update on a
|
||||
reported vulnerability, what to expect if the vulnerability is accepted or
|
||||
declined, etc.
|
||||
-->
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
import '../dist/cli.js';
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"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": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.34",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"sass": "^1.69.5",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -11,7 +11,7 @@ describe('Background Entrypoint', () => {
|
||||
fakeBrowser.reset();
|
||||
});
|
||||
|
||||
it("should log the extension's runtime ID", () => {
|
||||
it("should log the extenion's runtime ID", () => {
|
||||
const id = 'some-id';
|
||||
fakeBrowser.runtime.id = id;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
export default defineBackground(() => {
|
||||
console.log(browser.runtime.id);
|
||||
logId();
|
||||
console.log({
|
||||
browser: __BROWSER__,
|
||||
chrome: __IS_CHROME__,
|
||||
firefox: __IS_FIREFOX__,
|
||||
manifestVersion: __MANIFEST_VERSION__,
|
||||
});
|
||||
|
||||
// @ts-expect-error: should only accept entrypoints or public assets
|
||||
browser.runtime.getURL('/');
|
||||
browser.runtime.getURL('/background.js');
|
||||
browser.runtime.getURL('/icon-128.png');
|
||||
|
||||
console.log([
|
||||
// @ts-expect-error: browser.i18n should only accept known message names
|
||||
browser.i18n.getMessage('test'),
|
||||
browser.i18n.getMessage('promptForName'),
|
||||
browser.i18n.getMessage('hello', ['Aaron']),
|
||||
browser.i18n.getMessage('bye', ['Aaron']),
|
||||
browser.i18n.getMessage('@@extension_id'),
|
||||
browser.i18n.getMessage('nItems'),
|
||||
]);
|
||||
|
||||
console.log([
|
||||
// @ts-expect-error: i18n should only accept known message names
|
||||
i18n.t('test'),
|
||||
i18n.t('promptForName'),
|
||||
i18n.t('hello', ['Aaron']),
|
||||
i18n.t('bye', ['Aaron']),
|
||||
i18n.t('@@extension_id'),
|
||||
i18n.tp('nItems', 0, ['0']),
|
||||
]);
|
||||
|
||||
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());
|
||||
});
|
||||
@@ -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>
|
||||
@@ -1,16 +1,11 @@
|
||||
import 'uno.css';
|
||||
import '../../common/style.css';
|
||||
import './style.css';
|
||||
import manualStyle from './manual-style.css?inline';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['https://*.duckduckgo.com/*'],
|
||||
cssInjectionMode: 'ui',
|
||||
|
||||
async main(ctx) {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = manualStyle;
|
||||
document.head.append(style);
|
||||
|
||||
const ui = await createShadowRootUi(ctx, {
|
||||
name: 'demo-ui',
|
||||
position: 'inline',
|
||||
@@ -18,8 +13,7 @@ export default defineContentScript({
|
||||
anchor: 'form[role=search]',
|
||||
onMount: (container) => {
|
||||
const app = document.createElement('div');
|
||||
app.classList.add('m-4', 'text-red-500');
|
||||
app.textContent = i18n.t('prompt_for_name');
|
||||
app.textContent = 'Custom content script UI';
|
||||
container.append(app);
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,11 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
color: indianred;
|
||||
}
|
||||
html {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
div {
|
||||
padding: 16px;
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
prompt_for_name:
|
||||
message: What's your name?
|
||||
description: Ask for the user's name
|
||||
promptForName: What's your name?
|
||||
hello:
|
||||
message: Hello, $USER$
|
||||
description: Greet the user
|
||||
placeholders:
|
||||
user:
|
||||
content: $1
|
||||
example: Cira
|
||||
example: Paul
|
||||
bye:
|
||||
message: Goodbye, $USER$. Come back to $OUR_SITE$ soon!
|
||||
description: Say goodbye to the user
|
||||
@@ -16,10 +14,8 @@ bye:
|
||||
content: Example.com
|
||||
user:
|
||||
content: $1
|
||||
example: Cira
|
||||
deep:
|
||||
example: 'this is deep'
|
||||
items:
|
||||
0: Zero items
|
||||
example: Paul
|
||||
nItems:
|
||||
0: 0 items
|
||||
1: 1 item
|
||||
n: $1 items
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 504 B |
|
After Width: | Height: | Size: 936 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": ["../tsconfig.base.json", "./.wxt/tsconfig.json"],
|
||||
"compilerOptions": {
|
||||
"allowImportingTsExtensions": true,
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineProject } from 'vitest/config';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { WxtVitest } from 'wxt/testing';
|
||||
|
||||
export default defineProject({
|
||||
export default defineConfig({
|
||||
test: {
|
||||
mockReset: true,
|
||||
restoreMocks: true,
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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>•</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(', ') }}
|
||||
•
|
||||
{{ 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>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
tag?: string;
|
||||
}>();
|
||||
|
||||
const examples = ref();
|
||||
onMounted(async () => {
|
||||
const res = await fetch(
|
||||
'https://raw.githubusercontent.com/wxt-dev/wxt-examples/main/examples.json',
|
||||
);
|
||||
examples.value = await res.json();
|
||||
});
|
||||
|
||||
const filteredExamples = computed(() => {
|
||||
if (props.tag == null) return examples.value;
|
||||
|
||||
return examples.value.filter((example) => {
|
||||
return example.tags?.includes(props.tag);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<li v-if="examples == null">Loading...</li>
|
||||
<template v-else>
|
||||
<li v-for="example of filteredExamples">
|
||||
<a :href="example.url" target="_blank">{{ example.name }}</a>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
@@ -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 →</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>
|
||||
@@ -4,8 +4,11 @@ import useListExtensionDetails, {
|
||||
ChromeExtension,
|
||||
} from '../composables/useListExtensionDetails';
|
||||
|
||||
// Add extension IDs here. Order doesn't matter, will be sorted by a combination of weekly active users and rating.
|
||||
// Change the commit message or PR title to: "docs: Added "[extension name]" to the homepage"
|
||||
// Add extension IDs here. Order doesn't matter, will be sorted by weekly active users
|
||||
// During the transition from chrome.google.com/webstore to
|
||||
// chromewebstore.google.com, queue.wxt.dev might return null for your
|
||||
// extension. If it does, use "<slug>/<id>" instead of just the ID. The slug
|
||||
// can be retrieved from the URL of the item on chromewebstore.google.com
|
||||
const chromeExtensionIds = [
|
||||
'ocfdgncpifmegplaglcnglhioflaimkd', // GitHub: Better Line Counts
|
||||
'mgmdkjcljneegjfajchedjpdhbadklcf', // Anime Skip Player
|
||||
@@ -13,71 +16,14 @@ 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
|
||||
'youtube中文配音/oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
];
|
||||
|
||||
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
|
||||
@@ -95,56 +41,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',
|
||||
@@ -37,7 +36,6 @@ export default function (ids: string[]) {
|
||||
}),
|
||||
})
|
||||
.then(async (res) => {
|
||||
isLoading.value = false;
|
||||
const {
|
||||
data: { chromeExtensions },
|
||||
} = await res.json();
|
||||
@@ -45,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;
|
||||
@@ -54,6 +51,5 @@ export default function (ids: string[]) {
|
||||
return {
|
||||
data,
|
||||
err,
|
||||
isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,247 +1,151 @@
|
||||
import { 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 knowledge from 'vitepress-knowledge';
|
||||
import {
|
||||
groupIconMdPlugin,
|
||||
groupIconVitePlugin,
|
||||
localIconLoader,
|
||||
} from 'vitepress-plugin-group-icons';
|
||||
import { Feed } from 'feed';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { DefaultTheme, defineConfig } from 'vitepress';
|
||||
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.";
|
||||
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,
|
||||
};
|
||||
const description =
|
||||
"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 = 'https://wxt.dev';
|
||||
const ogImage = 'https://wxt.dev/social-preview.png';
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
extends: knowledge({
|
||||
serverUrl: 'https://knowledge.wxt.dev',
|
||||
paths: {
|
||||
'/': 'docs',
|
||||
'/api/': 'api-reference',
|
||||
},
|
||||
pageSelectors: {
|
||||
'examples.md': '#VPContent > .VPPage',
|
||||
},
|
||||
}),
|
||||
|
||||
titleTemplate: `:title${titleSuffix}`,
|
||||
title: 'WXT',
|
||||
description,
|
||||
vite: {
|
||||
clearScreen: false,
|
||||
plugins: [
|
||||
groupIconVitePlugin({
|
||||
customIcon: {
|
||||
'wxt.config.ts': localIconLoader(
|
||||
import.meta.url,
|
||||
'../public/logo.svg',
|
||||
),
|
||||
},
|
||||
}),
|
||||
],
|
||||
plugins: [generateCliDocs()],
|
||||
},
|
||||
lastUpdated: true,
|
||||
sitemap: {
|
||||
hostname: origin,
|
||||
},
|
||||
|
||||
async buildEnd(site) {
|
||||
// Only construct the RSS document for production builds
|
||||
const { default: blogDataLoader } = await import('./loaders/blog.data');
|
||||
const posts = await blogDataLoader.load();
|
||||
const feed = new Feed({
|
||||
copyright: 'MIT',
|
||||
id: 'wxt',
|
||||
title: 'WXT Blog',
|
||||
link: `${origin}/blog`,
|
||||
});
|
||||
posts.forEach((post) => {
|
||||
feed.addItem({
|
||||
date: post.frontmatter.date,
|
||||
link: new URL(post.url, origin).href,
|
||||
title: post.frontmatter.title,
|
||||
description: post.frontmatter.description,
|
||||
});
|
||||
});
|
||||
console.log('rss.xml:');
|
||||
console.log(feed.rss2());
|
||||
await writeFile(join(site.outDir, 'rss.xml'), feed.rss2(), 'utf8');
|
||||
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);
|
||||
},
|
||||
},
|
||||
|
||||
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: 'Manifest.json', link: '/guide/manifest.md' },
|
||||
{ text: 'Extension APIs', link: '/guide/extension-apis.md' },
|
||||
{ text: 'Storage', link: '/guide/storage.md' },
|
||||
{ text: 'Assets', link: '/guide/assets.md' },
|
||||
{ text: 'Content Script UI', link: '/guide/content-script-ui.md' },
|
||||
{ text: 'Localization', link: '/guide/localization.md' },
|
||||
{ text: 'Multiple Browsers', link: '/guide/multiple-browsers.md' },
|
||||
{ text: 'Auto-imports', link: '/guide/auto-imports.md' },
|
||||
{ text: 'Vite', link: '/guide/vite.md' },
|
||||
{ text: 'Remote Code', link: '/guide/remote-code.md' },
|
||||
{ text: 'Publishing', link: '/guide/publishing.md' },
|
||||
{ text: 'Development', link: '/guide/development.md' },
|
||||
{ text: 'Testing', link: '/guide/testing.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' }],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { createContentLoader } from 'vitepress';
|
||||
|
||||
export default createContentLoader('blog/*.md');
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -2,19 +2,15 @@ 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 ExampleList from '../components/ExampleList.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);
|
||||
ctx.app.component('ExampleList', ExampleList);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 +0,0 @@
|
||||
<!--@include: ../packages/analytics/README.md-->
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
Before Width: | Height: | Size: 131 KiB |
@@ -1 +0,0 @@
|
||||
<!--@include: ../packages/auto-icons/README.md-->
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
layout: page
|
||||
---
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BlogHome from './.vitepress/components/BlogHome.vue';
|
||||
</script>
|
||||
|
||||
<BlogHome />
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
layout: blog
|
||||
title: Real World Messaging
|
||||
description: |
|
||||
The extension messaging APIs are difficult to learn. Let's go beyond the simple examples from Chrome and Firefox's documentation to build our own simple messaging system from scratch.
|
||||
authors:
|
||||
- name: Aaron Klinker
|
||||
github: aklinker1
|
||||
date: 2024-10-20T04:54:23.601Z
|
||||
---
|
||||
|
||||
Test content **bold** _italic_
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
layout: blog
|
||||
title: Introducing <code>#imports</code>
|
||||
description: Learn how WXT's new <code>#imports</code> module works and how to use it.
|
||||
authors:
|
||||
- name: Aaron Klinker
|
||||
github: aklinker1
|
||||
date: 2024-12-06T14:39:00.000Z
|
||||
---
|
||||
|
||||
WXT v0.20 introduced a new way of manually importing its APIs: **the `#imports` module**. This module was introduced to simplify import statements and provide more visibility into all the APIs WXT provides.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
import { browser } from 'wxt/browser'; // [!code --]
|
||||
import { createShadowRootUi } from 'wxt/utils/content-script-ui/shadow-root'; // [!code --]
|
||||
import { defineContentScript } from 'wxt/utils/define-content-script'; // [!code --]
|
||||
import { injectScript } from 'wxt/utils/inject-script'; // [!code --]
|
||||
import { // [!code ++]
|
||||
browser, createShadowRootUi, defineContentScript, injectScript // [!code ++]
|
||||
} from '#imports'; // [!code ++]
|
||||
```
|
||||
|
||||
The `#imports` module is considered a "virtual module", because the file doesn't actually exist. At build-time, imports are split into individual statements for each API:
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [What you write]
|
||||
import { defineContentScript, injectScript } from '#imports';
|
||||
```
|
||||
|
||||
```ts [What the bundler sees]
|
||||
import { defineContentScript } from 'wxt/utils/define-content-script';
|
||||
import { injectScript } from 'wxt/utils/inject-script';
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Think of `#imports` as a convenient way to access all of WXT's APIs from one place, without impacting performance or bundle size.
|
||||
|
||||
This enables better tree-shaking compared to v0.19 and below.
|
||||
|
||||
:::tip Need to lookup the full import path of an API?
|
||||
Open up your project's `.wxt/types/imports-module.d.ts` file.
|
||||
:::
|
||||
|
||||
## Mocking
|
||||
|
||||
When writing tests, you might need to mock APIs from the `#imports` module. While mocking these APIs is very easy, it may not be immediately clear how to accomplish it.
|
||||
|
||||
Let's look at an example using Vitest. When [configured with `wxt/testing`](/guide/essentials/unit-testing#vitest), Vitest sees the same transformed code as the bundler. That means to mock an API from `#imports`, you need to call `vi.mock` with the real import path, not `#imports`:
|
||||
|
||||
```ts
|
||||
import { injectScript } from '#imports';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
vi.mock('wxt/utils/inject-script')
|
||||
const injectScriptMock = vi.mocked(injectScript);
|
||||
|
||||
injectScriptMock.mockReturnValueOnce(...);
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
You don't have to use `#imports` if you don't like - you can continue importing APIs from their submodules. However, using `#imports` is the recommended approach moving forwards.
|
||||
|
||||
- As more APIs are added, you won't have to memorize additional import paths.
|
||||
- If breaking changes are made to import paths in future major versions, `#imports` won't break.
|
||||
|
||||
Happy Coding 😄
|
||||
|
||||
> P.S. Yes, this is exactly how [Nuxt's `#imports`](https://nuxt.com/docs/guide/concepts/auto-imports#explicit-imports) works! We use the exact same library, [`unimport`](https://github.com/unjs/unimport).
|
||||
@@ -0,0 +1,47 @@
|
||||
# Background
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/background/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/background)
|
||||
|
||||
For MV2, the background is added as a script to the background page. For MV3, the background becomes a service worker.
|
||||
|
||||
## Filenames
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['background.[jt]s', 'background.js'],
|
||||
['background/index.[jt]s', 'background.js'],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
:::warning
|
||||
The main function of the background **_CANNOT BE ASYNC_**. Event listeners must be added syncronously on background startup. If your main function returns a promise, WXT will log an error.
|
||||
:::
|
||||
|
||||
```ts
|
||||
export default defineBackground(() => {
|
||||
// Executed when background is loaded
|
||||
});
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```ts
|
||||
export default defineBackground({
|
||||
// Set manifest options
|
||||
persistent: undefined | true | false,
|
||||
type: undefined | 'module',
|
||||
|
||||
// Set include/exclude if the background should be removed from some builds
|
||||
include: undefined | string[],
|
||||
exclude: undefined | string[],
|
||||
|
||||
// Executed when background is loaded
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
> All manifest options default to `undefined`.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Bookmarks
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) • [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
|
||||
|
||||
## Filenames
|
||||
|
||||
<EntrypointPatterns
|
||||
:patterns="[
|
||||
['bookmarks.html', 'bookmarks.html'],
|
||||
['bookmarks/index.html', 'bookmarks.html'],
|
||||
]"
|
||||
/>
|
||||
|
||||
## Definition
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Title</title>
|
||||
<!-- Set include/exclude if the page should be removed from some builds -->
|
||||
<meta name="manifest.include" content="['chrome', ...]" />
|
||||
<meta name="manifest.exclude" content="['chrome', ...]" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- ... -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||