Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fefe601541 | |||
| 7291c60413 | |||
| bb679102b1 | |||
| 16027c8971 | |||
| 84405a33e3 | |||
| bdf795c3f2 | |||
| ab73084bb5 | |||
| e9fb4c5414 | |||
| bc54f525e4 | |||
| 9170159bc1 | |||
| fa657a0366 | |||
| 24920ddd52 | |||
| eb0b372113 | |||
| 8f5f4f674e | |||
| 38c1dae9c7 | |||
| f92bcd1d14 | |||
| df09b86d23 | |||
| bd5cfa10ad | |||
| 94914dd1ad | |||
| dfbb58a2f1 | |||
| 54a1c3576f | |||
| 9ddf73b4fe | |||
| 4ceec1a6ab | |||
| ba314d7434 | |||
| 55fed67067 | |||
| 852390092e | |||
| 067be3fed3 | |||
| 1aecdbb71c | |||
| d8e517cb7b | |||
| 9da5b4cacc | |||
| 84af32242b | |||
| 7a81472407 | |||
| 9399d8df42 | |||
| 459f73fd5c | |||
| c1c3d3b7c5 | |||
| 380a9630e5 | |||
| 984568e00c | |||
| 5fefd8e083 | |||
| a6eef643f6 | |||
| a01928e010 | |||
| d901db2316 | |||
| fdda812078 | |||
| 7c59e9d5b5 | |||
| c9b0c4da34 | |||
| 9f214141b4 | |||
| 4638e90f02 | |||
| d1c50ab6f8 | |||
| 489aa0ffd8 | |||
| ebcc605961 | |||
| bb630049d5 | |||
| 39321adfe1 | |||
| ea2b8df906 | |||
| 74a9b1aef4 | |||
| 8b3e35de94 | |||
| 9d4724fa99 | |||
| a8379cbd80 | |||
| bdfb6c5a84 | |||
| 87414d7137 | |||
| f9d0b64d7a | |||
| 97918710bd | |||
| ac340b3f1e | |||
| a2097e148b | |||
| 190be7f732 | |||
| 0333ce5c46 | |||
| 47df623fd4 | |||
| c609efbd9a | |||
| c8cd41a833 | |||
| 02a0110091 | |||
| 6c572c56a3 | |||
| 2db516ef3b | |||
| 9f3787cd95 | |||
| 5511943237 | |||
| 626838eba6 | |||
| 587328f54c | |||
| 1aa88b1fb6 | |||
| e7db119545 | |||
| da79536456 | |||
| aaaff43731 | |||
| 5d77142758 | |||
| 586ea0002b | |||
| e642d6fe76 | |||
| 7148f288d5 | |||
| 40abf3fb5b | |||
| adea6c7177 | |||
| 96a307e99f | |||
| f0201f72ef | |||
| 0b1b677b3d | |||
| b1dcec86cd | |||
| 7c783fe565 | |||
| 9a0207d7cf | |||
| 1b0e047d6b | |||
| 7ab3c7e5d9 | |||
| 9ece49d226 | |||
| 1272e2ec74 | |||
| db16602018 | |||
| 5c202805b9 | |||
| bfc8866b69 | |||
| 9aedc29289 | |||
| e1f3d75ac4 | |||
| 030f23d2e9 | |||
| 6be9a4e71b | |||
| eb0bffb5dd | |||
| 240bbb3676 | |||
| 7d25110a6e | |||
| 598eea703b | |||
| 8793f7bff4 | |||
| c1b3911622 | |||
| b0bf05109d | |||
| d9cb0ebcc8 | |||
| 1eda21f63f | |||
| f2dbcde124 | |||
| 60f00db2c5 | |||
| a9332453fc | |||
| b99da14799 | |||
| f02400a1fb | |||
| bbcb7f7cdd | |||
| 30cf8e6d54 | |||
| 4f772fc0f7 | |||
| 2cf2582138 | |||
| ad4de4b92c | |||
| 27ed73a6ae | |||
| a1570c8917 | |||
| 7e419ddd43 | |||
| b045d3616d | |||
| f6b50e2e77 | |||
| 9d1381cbb3 | |||
| 99b0242d86 | |||
| db27628211 | |||
| 9b9d86229a | |||
| 46f830dacf | |||
| 3dd9bc7772 | |||
| c7335add19 | |||
| c133498208 | |||
| 6a52bb22a3 | |||
| 6f970efda5 | |||
| c69350a0eb | |||
| 4add8820eb | |||
| 033dd4811e | |||
| da5cd32502 | |||
| edf33fdec9 | |||
| 448cbf16c9 | |||
| e45b77b528 | |||
| b61fd1716e | |||
| d584b5cc7e | |||
| faf9cedfbd | |||
| 3a38ac9339 |
@@ -4,12 +4,10 @@ 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:
|
||||
@@ -17,12 +15,12 @@ runs:
|
||||
|
||||
steps:
|
||||
- name: 🛠️ Setup PNPM
|
||||
uses: pnpm/action-setup@v4
|
||||
uses: pnpm/action-setup@f2b2b233b538f500472c7274c7012f57857d8ce0 # v4.1.0
|
||||
|
||||
- name: 🛠️ Setup NodeJS
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@b7234cc9fe124f0f4932554b4e5284543083ae7b # v4.4.0
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
|
||||
- name: 📦 Install Dependencies
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
name: 🔔 Notify Unreleased Commits
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 20 * * 1' # Weekly at 8 PM UTC (3 PM CT)
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: List Commits
|
||||
id: list-commits
|
||||
run: |
|
||||
./scripts/list-unreleased-commits.sh > summary.txt
|
||||
|
||||
- name: Discord notification
|
||||
run: |
|
||||
PAYLOAD=$(jq -n --arg content "${{ env.MESSAGE }}" '{"content": $content}')
|
||||
curl -X POST \
|
||||
-F "payload_json=${PAYLOAD}" \
|
||||
-F "file1=@summary.txt" \
|
||||
$DISCORD_WEBHOOK
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_UNRELEASED_COMMITS }}
|
||||
MESSAGE: |
|
||||
If a package needs released, please [run the workflow](<https://github.com/wxt-dev/wxt/actions/workflows/release.yml>).
|
||||
|
||||
Before running, consider:
|
||||
- Are there any breaking changes? If so, prepare a list of breaking changes and update the changelog and release notes after the release.
|
||||
- Are there any PRs open that we wait to release after they're merged?
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
if: ${{ github.repository == 'wxt-dev/wxt' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
with:
|
||||
# Only fetch the config file from the repository
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
@@ -23,13 +23,13 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||
with:
|
||||
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
|
||||
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
|
||||
|
||||
@@ -14,6 +14,7 @@ on:
|
||||
- module-solid
|
||||
- module-svelte
|
||||
- module-vue
|
||||
- runner
|
||||
- storage
|
||||
- unocss
|
||||
- webextension-polyfill
|
||||
@@ -32,14 +33,16 @@ jobs:
|
||||
name: Publish
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
contents: write # Push version changes
|
||||
id-token: write # OIDC for NPM publishing
|
||||
needs:
|
||||
- validate
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }} # https://github.com/sbellone/release-workflow-example
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -59,9 +62,8 @@ jobs:
|
||||
- name: Publish to NPM
|
||||
working-directory: packages/${{ inputs.package }}
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
|
||||
pnpm build
|
||||
pnpm publish
|
||||
sudo npm i -g npm@latest
|
||||
npm publish
|
||||
|
||||
- name: Create GitHub release
|
||||
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
|
||||
|
||||
@@ -14,6 +14,7 @@ on:
|
||||
- module-solid
|
||||
- module-svelte
|
||||
- module-vue
|
||||
- runner
|
||||
- storage
|
||||
- webextension-polyfill
|
||||
- wxt
|
||||
@@ -29,7 +30,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
@@ -12,10 +12,11 @@ jobs:
|
||||
name: 'Sync with @types/chrome'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
contents: write # Push version changes
|
||||
id-token: write # OIDC for NPM publishing
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -32,7 +33,7 @@ jobs:
|
||||
|
||||
- name: Commit Changes
|
||||
id: commit
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
uses: stefanzweifel/git-auto-commit-action@01d77ca6cb089da1360e540865f7d035c95aa199 # v6.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -42,5 +43,5 @@ jobs:
|
||||
if: steps.commit.outputs.changes_detected == 'true'
|
||||
working-directory: packages/browser
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
|
||||
pnpm publish
|
||||
sudo npm i -g npm@latest
|
||||
npm publish
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -55,39 +55,41 @@ jobs:
|
||||
working-directory: packages/wxt-demo
|
||||
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-22.04
|
||||
name: Tests (${{ matrix.title }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- title: 'Linux'
|
||||
os: ubuntu-22.04
|
||||
coverage: true
|
||||
- title: 'Windows'
|
||||
os: windows-latest
|
||||
coverage: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
uses: oven-sh/setup-bun@22457c87c1b161cf7dde222c3e82b2b5f8d2bed2 # v2.0.2
|
||||
|
||||
- name: Run Tests
|
||||
run: pnpm test:coverage -- --reporter=default --reporter=hanging-process
|
||||
if: ${{ ! matrix.coverage }}
|
||||
run: pnpm test
|
||||
|
||||
- name: Run Tests (Coverage)
|
||||
if: matrix.coverage
|
||||
run: pnpm test:coverage --reporter=default --reporter=hanging-process
|
||||
|
||||
- name: Upload Coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
if: matrix.coverage
|
||||
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0
|
||||
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
|
||||
|
||||
template:
|
||||
name: Template
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -102,7 +104,7 @@ jobs:
|
||||
- vue
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -31,12 +31,12 @@ jobs:
|
||||
pnpm dlx wxt@latest --version
|
||||
|
||||
- name: Record VHS
|
||||
uses: charmbracelet/vhs-action@v2.1.0
|
||||
uses: charmbracelet/vhs-action@59641cdc7fadf3978db65eb8c6937ea2752f4ec3 # v2.1.0
|
||||
with:
|
||||
path: 'docs/tapes/init-demo.tape'
|
||||
|
||||
- name: Save recorded GIF
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
uses: stefanzweifel/git-auto-commit-action@01d77ca6cb089da1360e540865f7d035c95aa199 # v6.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
.output
|
||||
.webextrc
|
||||
.wxt
|
||||
.wxt-runner
|
||||
*.log
|
||||
/docs/.vitepress/cache
|
||||
docs/.vitepress/.temp
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/refs/heads/main/schema/markdownlint-config-schema.json",
|
||||
"line-length": false,
|
||||
"no-inline-html": false,
|
||||
"first-line-heading": false
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
node_modules
|
||||
.git
|
||||
.output
|
||||
dist
|
||||
|
||||
# Generated files
|
||||
packages/wxt/README.md
|
||||
packages/*/CHANGELOG.md
|
||||
docs/api
|
||||
+4
-4
@@ -60,7 +60,7 @@ representative at an online or offline event.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
aaronklinker1@gmail.com.
|
||||
<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
|
||||
@@ -116,7 +116,7 @@ the community.
|
||||
|
||||
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.
|
||||
<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).
|
||||
@@ -124,5 +124,5 @@ 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.
|
||||
<https://www.contributor-covenant.org/faq>. Translations are available at
|
||||
<https://www.contributor-covenant.org/translations>.
|
||||
|
||||
+10
-6
@@ -146,17 +146,21 @@ Releases are done with GitHub actions:
|
||||
|
||||
## Upgrading Dependencies
|
||||
|
||||
Use [`taze`](https://www.npmjs.com/package/taze) to upgrade dependencies throughout the entire monorepo.
|
||||
WXT has custom rules around what dependencies can be upgraded. Use the `scripts/upgrade-deps.ts` script to upgrade dependencies and follow these rules.
|
||||
|
||||
```sh
|
||||
pnpm dlx taze -r
|
||||
pnpm tsx scripts/upgrade-deps.ts
|
||||
```
|
||||
|
||||
Configuration is in [`taze.config.ts`](./taze.config.ts).
|
||||
To see all the options, run:
|
||||
|
||||
```sh
|
||||
pnpm tsx scripts/upgrade-deps.ts --help
|
||||
```
|
||||
|
||||
## Install Unreleased Versions
|
||||
|
||||
This repo uses https://pkg.pr.new to publish versions of all it's packages for almost every commit. You can install them via:
|
||||
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]
|
||||
@@ -177,10 +181,10 @@ 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!
|
||||
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 😓
|
||||
- **English only**: Blog posts should be written in English. Unfortunately, our maintainers don't have the bandwidth right now to translate our docs, let alone blog posts. Sorry 😓
|
||||
- **AI**: Please only use AI to translate or proof-read your blog post. Don't generate the whole thing... We don't want to publish that.
|
||||
|
||||
@@ -1,50 +1,32 @@
|
||||
<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">
|
||||
<span>WXT</span>
|
||||
</h1>
|
||||
<div align="center">
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</p>
|
||||
# <img align="top" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo"> WXT
|
||||
|
||||
<p align="center">
|
||||
<span>Next-gen framework for developing web extensions.</span>
|
||||
<br/>
|
||||
<span>⚡</span>
|
||||
<br/>
|
||||
<q><i>It's like Nuxt, but for Web Extensions</i></q>
|
||||
</p>
|
||||
[](https://www.npmjs.com/package/wxt)
|
||||
[](https://www.npmjs.com/package/wxt)
|
||||
[](https://github.com/wxt-dev/wxt/blob/main/LICENSE)
|
||||
[](https://codecov.io/github/wxt-dev/wxt)
|
||||
|
||||
<p align="center">
|
||||
<a href="https://wxt.dev/guide/installation.html" target="_blank">Get Started</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>
|
||||
Next-gen framework for developing web extensions.<br/>⚡<br/><q><i>It's like Nuxt, but for Web Extensions</i></q>
|
||||
|
||||
[Get Started](https://wxt.dev/guide/installation.html) •
|
||||
[Configuration](https://wxt.dev/api/config.html) •
|
||||
[Examples](https://wxt.dev/examples.html) •
|
||||
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md) •
|
||||
[Discord](https://discord.gg/ZFsZqGery9)
|
||||
|
||||
</div>
|
||||
|
||||

|
||||
|
||||
## Demo
|
||||
|
||||
https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94
|
||||
<https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94>
|
||||
|
||||
## Quick Start
|
||||
|
||||
Bootstrap a new project:
|
||||
|
||||
<!-- automd:pm-x version="latest" name="wxt" args="init" -->
|
||||
|
||||
```sh
|
||||
# npm
|
||||
npx wxt@latest init
|
||||
@@ -56,8 +38,6 @@ pnpm dlx wxt@latest init
|
||||
bunx wxt@latest init
|
||||
```
|
||||
|
||||
<!-- /automd -->
|
||||
|
||||
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
|
||||
|
||||
## Features
|
||||
@@ -79,17 +59,11 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
|
||||
|
||||
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>
|
||||
[](https://github.com/sponsors/wxt-dev)
|
||||
|
||||
## 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" />
|
||||
</a>
|
||||
|
||||
<!-- /automd -->
|
||||
[](https://github.com/wxt-dev/wxt/graphs/contributors)
|
||||
|
||||
@@ -4,7 +4,7 @@ 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.
|
||||
// Add extension IDs to end of the list. On the website, extensions will be sorted by a combination of weekly active users and rating.
|
||||
// Change the commit message or PR title to: "docs: Added "[extension name]" to the homepage"
|
||||
const chromeExtensionIds = [
|
||||
'ocfdgncpifmegplaglcnglhioflaimkd', // GitHub: Better Line Counts
|
||||
@@ -73,6 +73,35 @@ const chromeExtensionIds = [
|
||||
'gdjampjdgjmbifnhldgcnccdjkcoicmg', // radiofrance - news & broadcasts (French), music (international)
|
||||
'jlnhphlghikichhgbnkepenehbmloenb', // Blens - Time Tracker and AI Insight
|
||||
'njnammmpdodmfkodnfpammnpdcbhnlcm', // Always Light Mode - Setting website always in light mode
|
||||
'lblmfclcfniabobmamfkdogcgdagbhhb', // DesignPicker - Color Picker & Font Detector
|
||||
'pamnlaoeobcmhkliljfaofekeddpmfoh', // Web to PDF
|
||||
'jmbcbeepjfenihlocplnbmbhimcoooka', // Online CSV Viewer
|
||||
'nkjcoophmpcmmgadnljnlpbpfdfacgbo', // YouTube Video Transcript
|
||||
'lcaieahkjgeggeiihblhcjbbjlppgieh', // NetSuite Record Scripts
|
||||
'gmocfknjllodfiomnljmaehcplnekhlo', // VueTracker
|
||||
'ggcfemmoabhhelfkhknhbnkmeahloiod', // CanCopy - A web extension that allow you to copy any content from website
|
||||
'modkelfkcfjpgbfmnbnllalkiogfofhb', // Language Learning with AI
|
||||
'npfopljnjbamegincfjelhjhnonnjloo', // Bilibili Feed History Helper
|
||||
'edkhpdceeinkcacjdgebjehipmnbomce', // NZBDonkey - The ultimate NZB file download tool
|
||||
'cckggnbnimdbbpmdinkkgbbncopbloob', // WeChat Markdown Editor(微信 Markdown 编辑器)
|
||||
'jcblcjolcojmfopefcighfmkkefbaofg', // Tab Grab
|
||||
'eehmoikadcijkapfjocnhjclpbaindlb', // BrowserLens - https://browserlens.com/
|
||||
'hfhellofkjebbchcdffmicekjdomkcmc', // Epic Games Library Extension
|
||||
'gknigcbhlammoakmmdddkblknanpjiac', // Zen Analytics Pixel Tracker - zapt.web.app
|
||||
'cnklededohhcbmjjdlbjdkkihkgoggol', // Crypto Pulse - Compose your newtab with nature images, widgets & realtime Crypto Price & Bitcoin RSS.
|
||||
'miponnamafdenpgjemkknimgjfibicdc', // Youtube Video Scheduler
|
||||
'nhmbcmalgpkjbomhlhgdicanmkkaajmg', // Chatslator: Livestream Chat Translator
|
||||
'mbamjfdjbcdgpopfnkkmlohadbbnplhm', // 公众号阅读增强器 - https://wxreader.honwhy.wang
|
||||
'hannhecbnjnnbbafffmogdlnajpcomek', // 토탐정
|
||||
'ehboaofjncodknjkngdggmpdinhdoijp', // 2FAS Pass - https://2fas.com/
|
||||
'hnjamiaoicaepbkhdoknhhcedjdocpkd', // Quick Prompt - https://github.com/wenyuanw/quick-prompt
|
||||
'kacblhilkacgfnkjfodalohcnllcgmjd', // Add QR Code Generator Icon Back To Address Bar
|
||||
'fkbdlogfdjmpfepbbbjcgcfbgbcfcnne', // Piwik PRO Tracking Helper
|
||||
'nkbikckldmljjiiajklecmgmajgapbfl', // PIPX - Take Control of Picture-in-Picture, Automatically
|
||||
'hgppdobcpkfkmiegekaglonjajeojmdd', // Browsely - AI-powered browser extension
|
||||
'ehmoihnjgkdimihkhokkmfjdgomohjgm', // Filmbudd Pro - Simple, private – and synced ratings and watch notes across all your devices
|
||||
'alglchohmdikgdjhafiicilegegieafa', // MultiField CopyCat - Copy, Paste & Autofill Web Forms Instantly
|
||||
'aamihahiiogceidpbnfgehacgiecephe', // ChatSight - Add Table of Contents to ChatGPT
|
||||
];
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
|
||||
@@ -35,6 +35,9 @@ export default function (ids: string[]) {
|
||||
query,
|
||||
variables: { ids },
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(async (res) => {
|
||||
isLoading.value = false;
|
||||
|
||||
@@ -15,6 +15,7 @@ import { version as autoIconsVersion } from '../../packages/auto-icons/package.j
|
||||
import { version as unocssVersion } from '../../packages/unocss/package.json';
|
||||
import { version as storageVersion } from '../../packages/storage/package.json';
|
||||
import { version as analyticsVersion } from '../../packages/analytics/package.json';
|
||||
import { version as runnerVersion } from '../../packages/runner/package.json';
|
||||
import addKnowledge from 'vitepress-knowledge';
|
||||
import {
|
||||
groupIconMdPlugin,
|
||||
@@ -41,6 +42,7 @@ const otherPackages = {
|
||||
i18n: i18nVersion,
|
||||
storage: storageVersion,
|
||||
unocss: unocssVersion,
|
||||
runner: runnerVersion,
|
||||
};
|
||||
|
||||
const knowledge = addKnowledge<DefaultTheme.Config>({
|
||||
|
||||
@@ -8,16 +8,6 @@ outline: deep
|
||||
|
||||
During development, WXT uses [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext) to automatically open a browser window with your extension installed.
|
||||
|
||||
:::danger
|
||||
Chrome 137 removed support for the `--load-extension` CLI flag, which WXT relied on to open the browser with an extension installed. So this feature will not work for Chrome.
|
||||
|
||||
You have two options:
|
||||
|
||||
1. Install [Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (which still supports the `--load-extension` flag) and [point the `chrome` binary to it](#set-browser-binaries), or
|
||||
2. [Disable this feature](#disable-opening-browser) and manually load your extension
|
||||
|
||||
:::
|
||||
|
||||
## Config Files
|
||||
|
||||
You can configure browser startup in 3 places:
|
||||
|
||||
@@ -10,7 +10,7 @@ WXT does several pre-processing steps to try and prevent errors during this proc
|
||||
2. Use `@webext-core/fake-browser` to create a fake version of the `chrome` and `browser` globals expected by extensions.
|
||||
3. Pre-process the JS/TS code, stripping out the `main` function then tree-shaking unused code from the file
|
||||
|
||||
However, this process is not perfect. It doesn't setup all the globals found in the browser and the APIs may behave differently. As such, **_you should avoid using browser or extension APIs outside the `main` function of your entrypoints!_**
|
||||
However, this process is not perfect. It doesn't setup all the globals found in the browser and the APIs may behave differently. As such, **_you should avoid using browser or extension APIs outside the `main` function of your entrypoints!_** See [Entrypoint Limitations](/guide/essentials/extension-apis#entrypoint-limitations) for more details.
|
||||
|
||||
:::tip
|
||||
If you're running into errors while importing entrypoints, run `wxt prepare --debug` to see more details about this process. When debugging, WXT will print out the pre-processed code to help you identify issues.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
WXT supports [dotenv files the same way as Vite](https://vite.dev/guide/env-and-mode.html#env-files). Create any of the following files:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
.env
|
||||
.env.local
|
||||
.env.[mode]
|
||||
@@ -65,7 +65,6 @@ To use environment variables in the manifest, you need to use the function synta
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
extensionApi: 'chrome',
|
||||
modules: ['@wxt-dev/module-vue'],
|
||||
manifest: { // [!code --]
|
||||
oauth2: { // [!code --]
|
||||
@@ -81,3 +80,16 @@ export default defineConfig({
|
||||
```
|
||||
|
||||
WXT can't load your `.env` files until after the config file has been loaded. So by using the function syntax for `manifest`, it defers creating the object until after the `.env` files are loaded into the process.
|
||||
|
||||
Note that Vite's runtime environment variables, like `import.meta.env.DEV`, will not be defined. Instead, access the `mode` like this:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: ({ mode }) => {
|
||||
const isDev = mode === 'development';
|
||||
console.log('Is development mode:', isDev);
|
||||
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -32,7 +32,7 @@ Because hooks can be defined in multiple places, including [WXT Modules](/guide/
|
||||
|
||||
To see the order for your project, run `wxt prepare --debug` flag and search for the "Hook execution order":
|
||||
|
||||
```
|
||||
```plaintext
|
||||
⚙ Hook execution order:
|
||||
⚙ 1. wxt:built-in:unimport
|
||||
⚙ 2. src/modules/auto-icons.ts
|
||||
@@ -50,7 +50,9 @@ Changing execution order is simple:
|
||||
📄 0.my-module.ts
|
||||
📄 1.another-module.ts
|
||||
```
|
||||
|
||||
- If you need to run an NPM module after user modules, just make it a user module and prefix the filename with a number!
|
||||
|
||||
```ts
|
||||
// modules/2.i18n.ts
|
||||
export { default } from '@wxt-dev/i18n/module';
|
||||
|
||||
@@ -128,7 +128,7 @@ If a version is not present in your `package.json`, it defaults to `"0.0.0"`.
|
||||
|
||||
WXT automatically discovers your extension's icon by looking at files in the `public/` directory:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
public/
|
||||
├─ icon-16.png
|
||||
├─ icon-24.png
|
||||
@@ -235,6 +235,7 @@ If you want to use the `activeTab` permission or the `browser.action.onClicked`
|
||||
|
||||
1. Delete the [Popup entrypoint](/guide/essentials/entrypoints#popup) if it exists
|
||||
2. Add the `action` key to your manifest:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
|
||||
@@ -20,7 +20,7 @@ export default defineAppConfig({
|
||||
```
|
||||
|
||||
:::warning
|
||||
This file is committed to the repo, so don't put any secrets here. Instead, use [Environment Variables](#environment-variables)
|
||||
This file is committed to the repo, so don't put any secrets here. Instead, use [Environment Variables](/guide/essentials/config/environment-variables)
|
||||
:::
|
||||
|
||||
To access runtime config, WXT provides the `useAppConfig` function:
|
||||
|
||||
@@ -19,7 +19,7 @@ export default defineContentScript({
|
||||
|
||||
This object is responsible for tracking whether or not the content script's context is "invalidated". Most browsers, by default, do not stop content scripts if the extension is uninstalled, updated, or disabled. When this happens, content scripts start reporting this error:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
Error: Extension context invalidated.
|
||||
```
|
||||
|
||||
@@ -76,6 +76,7 @@ To create a standalone content script that only includes a CSS file:
|
||||
|
||||
1. Create the CSS file: `entrypoints/example.content.css`
|
||||
2. Use the `build:manifestGenerated` hook to add the content script to the manifest:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
hooks: {
|
||||
@@ -210,9 +211,7 @@ export default defineContentScript({
|
||||
anchor: 'body',
|
||||
onMount: (container) => {
|
||||
// Create the Svelte app inside the UI container
|
||||
mount(App, {
|
||||
target: container,
|
||||
});
|
||||
return mount(App, { target: container });
|
||||
},
|
||||
onRemove: (app) => {
|
||||
// Destroy the app when the UI is removed
|
||||
@@ -395,11 +394,9 @@ export default defineContentScript({
|
||||
anchor: 'body',
|
||||
onMount: (container) => {
|
||||
// Create the Svelte app inside the UI container
|
||||
mount(App, {
|
||||
target: container,
|
||||
});
|
||||
return mount(App, { target: container });
|
||||
},
|
||||
onRemove: () => {
|
||||
onRemove: (app) => {
|
||||
// Destroy the app when the UI is removed
|
||||
unmount(app);
|
||||
},
|
||||
@@ -459,6 +456,7 @@ If you don't need to run your UI in the same frame as the content script, you ca
|
||||
WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/utils/content-script-ui/iframe/functions/createIframeUi), which simplifies setting up the IFrame.
|
||||
|
||||
1. Create an HTML page that will be loaded into your IFrame:
|
||||
|
||||
```html
|
||||
<!-- entrypoints/example-iframe.html -->
|
||||
<!doctype html>
|
||||
@@ -473,7 +471,9 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/utils/cont
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
1. Add the page to the manifest's `web_accessible_resources`:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
@@ -486,6 +486,7 @@ WXT provides a helper function, [`createIframeUi`](/api/reference/wxt/utils/cont
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
1. Create and mount the IFrame:
|
||||
|
||||
```ts
|
||||
|
||||
@@ -300,14 +300,14 @@ When defining content script entrypoints, keep in mind that WXT will import this
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
browser.runtime.onMessage.addListener((message) => { // [!code --]
|
||||
// ... // [!code --]
|
||||
}); // [!code --]
|
||||
const container = document.createElement('div'); // [!code --]
|
||||
document.body.append(container); // [!code --]
|
||||
|
||||
export default defineBackground(() => {
|
||||
browser.runtime.onMessage.addListener((message) => { // [!code ++]
|
||||
// ... // [!code ++]
|
||||
}); // [!code ++]
|
||||
export default defineContentScript({
|
||||
main: function () {
|
||||
const container = document.createElement('div'); // [!code ++]
|
||||
document.body.append(container); // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -580,7 +580,7 @@ body {
|
||||
}
|
||||
```
|
||||
|
||||
Follow Vite's guide to setup your preprocessor of choice: https://vitejs.dev/guide/features.html#css-pre-processors
|
||||
Follow Vite's guide to setup your preprocessor of choice: <https://vitejs.dev/guide/features.html#css-pre-processors>
|
||||
|
||||
CSS entrypoints are always unlisted. To add CSS to a content script, see the [Content Script](/guide/essentials/content-scripts#css) docs.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ES Modules
|
||||
|
||||
You source code should always be written as ESM. However, you have some control whether an entrypoint is bundled as ESM.
|
||||
Your source code should always be written as ESM. However, you have some control whether an entrypoint is bundled as ESM.
|
||||
|
||||
## HTML Pages <Badge type="warning" text="≥0.0.1" />
|
||||
|
||||
|
||||
@@ -75,3 +75,63 @@ Alternatively, if you're trying to use similar APIs under different names (to su
|
||||
//
|
||||
});
|
||||
```
|
||||
|
||||
## Entrypoint Limitations
|
||||
|
||||
Because WXT imports your entrypoint files into a NodeJS, non-extension environment, the `chrome`/`browser` variables provided to extensions by the browser **will not be available**.
|
||||
|
||||
To prevent some basic errors, WXT polyfills these globals with the same in-memory, fake implementation it uses for testing: [`@webext-core/fake-browser`](https://webext-core.aklinker1.io/fake-browser/installation/). However, not all the APIs have been implemented.
|
||||
|
||||
So it is extremely important to NEVER use `browser.*` extension APIs outside the main function of any JS/TS entrypoints (background, content scripts, and unlisted scripts). If you do, you'll see an error like this:
|
||||
|
||||
```plaintext
|
||||
✖ Command failed after 440 ms
|
||||
|
||||
ERROR Browser.action.onClicked.addListener not implemented.
|
||||
```
|
||||
|
||||
The fix is simple, just move your API usage into the entrypoint's main function:
|
||||
|
||||
:::code-group
|
||||
|
||||
```ts [background.ts]
|
||||
browser.action.onClicked.addListener(() => {
|
||||
/* ... */
|
||||
}); // [!code --]
|
||||
|
||||
export default defineBackground(() => {
|
||||
browser.action.onClicked.addListener(() => {
|
||||
/* ... */
|
||||
}); // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
```ts [content.ts]
|
||||
browser.runtime.onMessage.addListener(() => {
|
||||
/* ... */
|
||||
}); // [!code --]
|
||||
|
||||
export default defineContentScript({
|
||||
main() {
|
||||
browser.runtime.onMessage.addListener(() => {
|
||||
/* ... */
|
||||
}); // [!code ++]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts [unlisted.ts]
|
||||
browser.runtime.onMessage.addListener(() => {
|
||||
/* ... */
|
||||
}); // [!code --]
|
||||
|
||||
export default defineUnlistedScript(() => {
|
||||
browser.runtime.onMessage.addListener(() => {
|
||||
/* ... */
|
||||
}); // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Read [Entrypoint Loaders](/guide/essentials/config/entrypoint-loaders) for more technical details about this limitation.
|
||||
|
||||
@@ -9,6 +9,7 @@ This page discusses how to setup internationalization using the vanilla `browser
|
||||
## Usage
|
||||
|
||||
1. Add `default_locale` to your manifest:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
@@ -16,11 +17,12 @@ This page discusses how to setup internationalization using the vanilla `browser
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
2. Create `messages.json` files in the `public/` directory:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
📂 {srcDir}/
|
||||
📂 {rootDir}/
|
||||
📂 public/
|
||||
📂 _locales/
|
||||
📂 en/
|
||||
@@ -41,9 +43,11 @@ This page discusses how to setup internationalization using the vanilla `browser
|
||||
```
|
||||
|
||||
3. Get the translation:
|
||||
|
||||
```ts
|
||||
browser.i18n.getMessage('helloWorld');
|
||||
```
|
||||
|
||||
4. _Optional_: Add translations for extension name and description:
|
||||
|
||||
```json
|
||||
|
||||
@@ -72,13 +72,13 @@ jobs:
|
||||
submit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -62,6 +62,7 @@ Here are some examples:
|
||||
```
|
||||
|
||||
- HTML file only built for all targets other than `chrome`:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
@@ -9,7 +9,7 @@ WXT provides first class support for Vitest for unit testing:
|
||||
```ts
|
||||
// vitest.config.ts
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { WxtVitest } from 'wxt/testing';
|
||||
import { WxtVitest } from 'wxt/testing/vitest-plugin';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [WxtVitest()],
|
||||
@@ -36,7 +36,7 @@ This example demonstrates that you don't have to mock `browser.storage` (used by
|
||||
|
||||
```ts
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { fakeBrowser } from 'wxt/testing';
|
||||
import { fakeBrowser } from 'wxt/testing/fake-browser';
|
||||
|
||||
const accountStorage = storage.defineItem<Account>('local:account');
|
||||
|
||||
@@ -105,4 +105,4 @@ To use a different framework, you will likely have to disable auto-imports, setu
|
||||
|
||||
It is possible to do, but will require a bit more setup. Refer to Vitest's setup for an example of how to setup a test environment:
|
||||
|
||||
https://github.com/wxt-dev/wxt/blob/main/packages/wxt/src/testing/wxt-vitest-plugin.ts
|
||||
<https://github.com/wxt-dev/wxt/blob/main/packages/wxt/src/testing/wxt-vitest-plugin.ts>
|
||||
|
||||
@@ -9,18 +9,23 @@ WXT provides a "module system" that let's you run code at different steps in the
|
||||
There are two ways to add a module to your project:
|
||||
|
||||
1. **NPM**: install an NPM package, like [`@wxt-dev/auto-icons`](https://www.npmjs.com/package/@wxt-dev/auto-icons) and add it to your config:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/auto-icons'],
|
||||
});
|
||||
```
|
||||
|
||||
> Searching for ["wxt module"](https://www.npmjs.com/search?q=wxt%20module) on NPM is a good way to find published WXT modules.
|
||||
|
||||
2. **Local**: add a file to your project's `modules/` directory:
|
||||
```
|
||||
|
||||
```plaintext
|
||||
<rootDir>/
|
||||
modules/
|
||||
my-module.ts
|
||||
```
|
||||
|
||||
> To learn more about writing your own modules, read the [Writing Modules](/guide/essentials/wxt-modules) docs.
|
||||
|
||||
## Module Options
|
||||
|
||||
@@ -45,47 +45,63 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
|
||||
|
||||
1. Create a new project
|
||||
:::code-group
|
||||
|
||||
```sh [PNPM]
|
||||
cd my-project
|
||||
pnpm init
|
||||
```
|
||||
|
||||
```sh [Bun]
|
||||
cd my-project
|
||||
bun init
|
||||
```
|
||||
|
||||
```sh [NPM]
|
||||
cd my-project
|
||||
npm init
|
||||
```
|
||||
|
||||
```sh [Yarn]
|
||||
cd my-project
|
||||
yarn init
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
2. Install WXT:
|
||||
:::code-group
|
||||
|
||||
```sh [PNPM]
|
||||
pnpm i -D wxt
|
||||
```
|
||||
|
||||
```sh [Bun]
|
||||
bun i -D wxt
|
||||
```
|
||||
|
||||
```sh [NPM]
|
||||
npm i -D wxt
|
||||
```
|
||||
|
||||
```sh [Yarn]
|
||||
yarn add --dev wxt
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
3. Add an entrypoint, `my-project/entrypoints/background.ts`:
|
||||
:::code-group
|
||||
|
||||
```ts
|
||||
export default defineBackground(() => {
|
||||
console.log('Hello world!');
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
4. Add scripts to your `package.json`:
|
||||
|
||||
```json [package.json]
|
||||
{
|
||||
"scripts": {
|
||||
@@ -99,20 +115,26 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. Run your extension in dev mode
|
||||
:::code-group
|
||||
|
||||
```sh [PNPM]
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
```sh [Bun]
|
||||
bun run dev
|
||||
```
|
||||
|
||||
```sh [NPM]
|
||||
npm run dev
|
||||
```
|
||||
|
||||
```sh [Yarn]
|
||||
yarn dev
|
||||
```
|
||||
|
||||
:::
|
||||
WXT will automatically open a browser window with your extension installed.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Welcome to WXT!
|
||||
# Welcome to WXT
|
||||
|
||||
WXT is a modern, open-source framework for building web extensions. Inspired by Nuxt, its goals are to:
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ await chrome.scripting.getRegisteredContentScripts();
|
||||
|
||||
## How do I disable opening the browser automatically during development?
|
||||
|
||||
See https://wxt.dev/guide/essentials/config/browser-startup.html#disable-opening-browser
|
||||
See <https://wxt.dev/guide/essentials/config/browser-startup.html#disable-opening-browser>
|
||||
|
||||
## How do I stay logged into a website during development?
|
||||
|
||||
See https://wxt.dev/guide/essentials/config/browser-startup.html#persist-data
|
||||
See <https://wxt.dev/guide/essentials/config/browser-startup.html#persist-data>
|
||||
|
||||
## My component library doesn't work in content scripts!
|
||||
## My component library doesn't work in content scripts
|
||||
|
||||
This is usually caused by one of two things (or both) when using `createShadowRootUi`:
|
||||
|
||||
@@ -36,7 +36,6 @@ This is usually caused by one of two things (or both) when using `createShadowRo
|
||||
Some component libraries manually add CSS to the page by adding a `<style>` or `<link>` element. They place this element in the document's `<head>` by default. This causes your styles to be placed outside the `ShadowRoot` and it's isolation blocks the styles from being applied to your UI.
|
||||
|
||||
When a library does this, **you need to tell the library where to put its styles**. Here's the documentation for a few popular component libraries:
|
||||
|
||||
- Ant Design: [`StyleProvider`](https://ant.design/docs/react/compatible-style#shadow-dom-usage)
|
||||
- Mantine: [`MantineProvider#getRootElement` and `MantineProvider#cssVariablesSelector`](https://mantine.dev/theming/mantine-provider/)
|
||||
|
||||
@@ -167,11 +166,11 @@ Both issues have the same fix: tell the library to put elements inside the `Shad
|
||||
|
||||
## Is there an LLM trained on WXT's docs that I chat with?
|
||||
|
||||
Yes! There's a "Ask AI" button in the bottom right of the page, try it out! Or visit https://knowledge.wxt.dev/ for a fullscreen experience.
|
||||
Yes! There's a "Ask AI" button in the bottom right of the page, try it out! Or visit <https://knowledge.wxt.dev/> for a fullscreen experience.
|
||||
|
||||
Additionally, if you want to train your own model or provide context to your editor, you can use the LLM knowledge files hosted by the site:
|
||||
|
||||
https://wxt.dev/knowledge/index.json
|
||||
<https://wxt.dev/knowledge/index.json>
|
||||
|
||||
You don't need to crawl the entire website, these files already contain all the relevant docs for training a LLM on WXT. But feel free to crawl it and generate your own files if you want!
|
||||
|
||||
@@ -179,11 +178,11 @@ You don't need to crawl the entire website, these files already contain all the
|
||||
|
||||
To run the WXT dev server in a devcontainer, but load the dev build of your extension in your browser:
|
||||
|
||||
1. **Bind-mount your project directory to your host**
|
||||
1. **Bind-mount your project directory to your host**
|
||||
If you're using VS Code, you can open your project folder with the `Dev Containers: Open Folder in Container...` command. This keeps the folder synchronized between your host and the devcontainer, ensuring that the extension `dist` directory remains accessible from the host.
|
||||
|
||||
2. **Disable auto-opening the browser**
|
||||
WXT automatically opens your browser during development, but since you're running inside a container, it won't be able to access it. Follow the instructions [here](https://wxt.dev/guide/essentials/config/browser-startup.html#disable-opening-browser) to disable browser auto-opening in your `wxt.config.ts`.
|
||||
2. **Disable auto-opening the browser**
|
||||
WXT automatically opens your browser during development, but since you're running inside a container, it won't be able to access it. Follow [the guide here](https://wxt.dev/guide/essentials/config/browser-startup.html#disable-opening-browser) to disable browser auto-opening in your `wxt.config.ts`.
|
||||
|
||||
3. **Tell WXT to listen on all network interfaces**
|
||||
3. **Tell WXT to listen on all network interfaces**
|
||||
To enable hot-reloading, your extension has to connect to the WXT dev server running inside your container. WXT will only listen on `localhost` by default, which prevents connections from outside the devcontainer. To fix this you can instruct WXT to listen on all interfaces with `wxt --host 0.0.0.0`.
|
||||
|
||||
@@ -9,14 +9,18 @@ outline: deep
|
||||
To upgrade WXT to the latest major version:
|
||||
|
||||
1. Install it, skipping scripts so `wxt prepare` doesn't run - it will probably throw an error after a major version change (we'll run it later).
|
||||
|
||||
```sh
|
||||
pnpm i wxt@latest --ignore-scripts
|
||||
```
|
||||
|
||||
2. Follow the upgrade steps below to fix any breaking changes.
|
||||
3. Run `wxt prepare`. It should succeed and type errors will go away afterwords.
|
||||
|
||||
```sh
|
||||
pnpm wxt prepare
|
||||
```
|
||||
|
||||
4. Manually test to make sure both dev mode and production builds work.
|
||||
|
||||
For minor or patch version updates, there are no special steps. Just update it with your package manager:
|
||||
@@ -44,19 +48,36 @@ Read through all the changes once before updating your code.
|
||||
WXT's `browser` no longer uses the `webextension-polyfill`!
|
||||
|
||||
:::details Why?
|
||||
See https://github.com/wxt-dev/wxt/issues/784
|
||||
See <https://github.com/wxt-dev/wxt/issues/784>
|
||||
:::
|
||||
|
||||
To upgrade, you have two options:
|
||||
|
||||
1. **Stop using the polyfill** - No changes necessary, though you may want to do some manual testing to make sure everything continues to work. None of the early testers of this feature reported any runtime issues once they stopped using the polyfill.
|
||||
- If you're already using `extensionApi: "chrome"`, then you don't need to test anything! You're already using the same `browser` object v0.20 provides by default.
|
||||
1. **Stop using the polyfill**
|
||||
- If you're already using `extensionApi: "chrome"`, then you're not using the polyfill and there is nothing to change!
|
||||
- Otherwise there is only one change: `browser.runtime.onMessage` no longer supports using promises to return a response:
|
||||
|
||||
```ts
|
||||
browser.runtime.onMessage.addListener(async () => { // [!code --]
|
||||
const res = await someAsyncWork(); // [!code --]
|
||||
return res; // [!code --]
|
||||
browser.runtime.onMessage.addListener(async (_message, _sender, sendResponse) => { // [!code ++]
|
||||
someAsyncWork().then((res) => { // [!code ++]
|
||||
sendResponse(res); // [!code ++]
|
||||
}); // [!code ++]
|
||||
return true; // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
2. **Continue using the polyfill** - If you want to keep using the polyfill, you can! One less thing to worry about during this upgrade.
|
||||
- Install `webextension-polyfill` and WXT's [new polyfill module](https://www.npmjs.com/package/@wxt-dev/webextension-polyfill):
|
||||
|
||||
```sh
|
||||
pnpm i webextension-polyfill @wxt-dev/webextension-polyfill
|
||||
```
|
||||
|
||||
- Add the WXT module to your config:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/webextension-polyfill'],
|
||||
@@ -106,9 +127,9 @@ The default location for the `public/` and `modules/` directories have changed t
|
||||
|
||||
- If you follow the default folder structure, you don't need to make any changes.
|
||||
- If you set a custom `srcDir`, you have two options:
|
||||
1. Move the your `public/` and `modules/` directories to the project root:
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
1. Move the your `public/` and `modules/` directories to the project root:
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
📂 {rootDir}/
|
||||
📁 modules/ <!-- [!code ++] -->
|
||||
📁 public/ <!-- [!code ++] -->
|
||||
@@ -121,14 +142,16 @@ The default location for the `public/` and `modules/` directories have changed t
|
||||
📄 app.config.ts
|
||||
📄 wxt.config.ts
|
||||
```
|
||||
2. Keep the folders in the same place and update your project config:
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
srcDir: 'src',
|
||||
publicDir: 'src/public', // [!code ++]
|
||||
modulesDir: 'src/modules', // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
2. Keep the folders in the same place and update your project config:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
srcDir: 'src',
|
||||
publicDir: 'src/public', // [!code ++]
|
||||
modulesDir: 'src/modules', // [!code ++]
|
||||
});
|
||||
```
|
||||
|
||||
### Import Path Changes and `#imports`
|
||||
|
||||
@@ -230,6 +253,7 @@ If you've previously loaded the extension into your browser manually for develop
|
||||
### Deprecated APIs Removed
|
||||
|
||||
- `entrypointLoader` option: WXT now uses `vite-node` for importing entrypoints during the build process.
|
||||
<!-- markdownlint-disable-next-line MD051 -->
|
||||
> This was deprecated in v0.19.0, see the [v0.19 section](#v0-18-5-rarr-v0-19-0) for migration steps.
|
||||
- `transformManifest` option: Use the `build:manifestGenerated` hook to transform the manifest instead:
|
||||
<!-- prettier-ignore -->
|
||||
@@ -251,6 +275,7 @@ If you've previously loaded the extension into your browser manually for develop
|
||||
To improve consistency with the `web-ext.config.ts` filename, the "runner" API and config options have been renamed. You can continue using the old names, but they have been deprecated and will be removed in a future version:
|
||||
|
||||
1. The `runner` option has been renamed to `webExt`:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
runner: { // [!code --]
|
||||
@@ -259,12 +284,16 @@ To improve consistency with the `web-ext.config.ts` filename, the "runner" API a
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
2. `defineRunnerConfig` has been renamed to `defineWebExtConfig`:
|
||||
|
||||
```ts [web-ext.config.ts]
|
||||
import { defineRunnerConfig } from 'wxt'; // [!code --]
|
||||
import { defineWebExtConfig } from 'wxt'; // [!code ++]
|
||||
```
|
||||
|
||||
3. The `ExtensionRunnerConfig` type has been renamed to `WebExtConfig`
|
||||
|
||||
```ts
|
||||
import type { ExtensionRunnerConfig } from 'wxt'; // [!code --]
|
||||
import type { WebExtConfig } from 'wxt'; // [!code ++]
|
||||
@@ -342,7 +371,7 @@ WXT no longer ships with Common JS support. If you're using CJS, here's your mig
|
||||
1. Add [`"type": "module"`](https://nodejs.org/api/packages.html#type) to your `package.json`.
|
||||
2. Change the file extension of any `.js` files that use CJS syntax to `.cjs`, or update them to use EMS syntax.
|
||||
|
||||
Vite also provides steps for migrating to ESM. Check them out for more details: https://vitejs.dev/guide/migration#deprecate-cjs-node-api
|
||||
Vite also provides steps for migrating to ESM. Check them out for more details: <https://vitejs.dev/guide/migration#deprecate-cjs-node-api>
|
||||
|
||||
## v0.18.0 → v0.18.5
|
||||
|
||||
@@ -357,11 +386,13 @@ If you already have `<srcDir>/modules` or `<srcDir>/Modules` directory, `wxt pre
|
||||
You have two options:
|
||||
|
||||
1. [Recommended] Keep your files where they are and tell WXT to look in a different folder:
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
modulesDir: 'wxt-modules', // defaults to "modules"
|
||||
});
|
||||
```
|
||||
|
||||
2. Rename your `modules` directory to something else.
|
||||
|
||||
## v0.17.0 → v0.18.0
|
||||
@@ -415,7 +446,7 @@ item.watch((newValue: number, oldValue: number) => { // [!code ++]
|
||||
|
||||
JS entrypoints in the output directory have been moved. Unless you're doing some kind of post-build work referencing files, you don't have to make any changes.
|
||||
|
||||
```
|
||||
```plaintext
|
||||
.output/
|
||||
<target>/
|
||||
chunks/
|
||||
@@ -445,7 +476,7 @@ export default defineConfig({
|
||||
|
||||
### Renamed Undocumented Constants
|
||||
|
||||
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: https://wxt.dev/guide/multiple-browsers.html#runtime
|
||||
Renamed undocumented constants for detecting the build config at runtime in [#380](https://github.com/wxt-dev/wxt/pull/380). Now documented here: <https://wxt.dev/guide/multiple-browsers.html#runtime>
|
||||
|
||||
- `__BROWSER__` → `import.meta.env.BROWSER`
|
||||
- `__COMMAND__` → `import.meta.env.COMMAND`
|
||||
@@ -472,7 +503,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
### New `wxt/storage` APIs
|
||||
|
||||
`wxt/storage` no longer relies on [`unstorage`](https://www.npmjs.com/package/unstorage). Some `unstorage` APIs, like `prefixStorage`, have been removed, while others, like `snapshot`, are methods on the new `storage` object. Most of the standard usage remains the same. See https://wxt.dev/guide/storage and https://wxt.dev/api/reference/wxt/storage/ for more details ([#300](https://github.com/wxt-dev/wxt/pull/300))
|
||||
`wxt/storage` no longer relies on [`unstorage`](https://www.npmjs.com/package/unstorage). Some `unstorage` APIs, like `prefixStorage`, have been removed, while others, like `snapshot`, are methods on the new `storage` object. Most of the standard usage remains the same. See <https://wxt.dev/guide/storage> and <https://wxt.dev/api/reference/wxt/storage/> for more details ([#300](https://github.com/wxt-dev/wxt/pull/300))
|
||||
|
||||
## v0.11.0 → v0.12.0
|
||||
|
||||
@@ -482,6 +513,7 @@ Renamed undocumented constants for detecting the build config at runtime in [#38
|
||||
|
||||
- If you use auto-imports, no changes are required.
|
||||
- If you have disabled auto-imports, you'll need to manually update your import statements:
|
||||
|
||||
```ts
|
||||
import { defineBackground, defineContentScript } from 'wxt/client'; // [!code --]
|
||||
import { defineBackground, defineContentScript } from 'wxt/sandbox'; // [!code ++]
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<!--@include: ../packages/runner/README.md-->
|
||||
+31
-28
@@ -4,7 +4,7 @@
|
||||
"engines": {
|
||||
"node": ">=18.20.3"
|
||||
},
|
||||
"packageManager": "pnpm@10.8.0",
|
||||
"packageManager": "pnpm@10.15.1",
|
||||
"scripts": {
|
||||
"check": "check && pnpm -r --sequential run check",
|
||||
"test": "pnpm -r --sequential run test run",
|
||||
@@ -17,33 +17,36 @@
|
||||
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@commitlint/config-conventional": "catalog:",
|
||||
"@commitlint/types": "catalog:",
|
||||
"@types/fs-extra": "catalog:",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"changelogen": "catalog:",
|
||||
"consola": "catalog:",
|
||||
"fast-glob": "catalog:",
|
||||
"feed": "catalog:",
|
||||
"fs-extra": "catalog:",
|
||||
"lint-staged": "catalog:",
|
||||
"markdown-it-footnote": "catalog:",
|
||||
"nano-spawn": "catalog:",
|
||||
"prettier": "catalog:",
|
||||
"simple-git-hooks": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
"typedoc": "catalog:",
|
||||
"typedoc-plugin-frontmatter": "catalog:",
|
||||
"typedoc-plugin-markdown": "catalog:",
|
||||
"typedoc-vitepress-theme": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitepress": "catalog:",
|
||||
"vitepress-knowledge": "catalog:",
|
||||
"vitepress-plugin-group-icons": "catalog:",
|
||||
"vitest-mock-extended": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"@aklinker1/buildc": "^1.1.5",
|
||||
"@aklinker1/check": "^2.1.0",
|
||||
"@commitlint/config-conventional": "^19.8.1",
|
||||
"@commitlint/types": "^19.8.1",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"changelogen": "^0.6.2",
|
||||
"consola": "^3.4.2",
|
||||
"fast-glob": "^3.3.3",
|
||||
"feed": "^5.1.0",
|
||||
"fs-extra": "^11.3.1",
|
||||
"lint-staged": "^16.1.6",
|
||||
"markdown-it-footnote": "^4.0.0",
|
||||
"markdownlint-cli": "^0.45.0",
|
||||
"nano-spawn": "^1.0.2",
|
||||
"prettier": "^3.6.2",
|
||||
"semver": "^7.7.2",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"tsx": "4.20.5",
|
||||
"typedoc": "^0.25.4",
|
||||
"typedoc-plugin-frontmatter": "^1.3.0",
|
||||
"typedoc-plugin-markdown": "4.0.0-next.23",
|
||||
"typedoc-vitepress-theme": "1.0.0-next.3",
|
||||
"typescript": "^5.9.2",
|
||||
"vitepress": "^1.6.4",
|
||||
"vitepress-knowledge": "^0.4.1",
|
||||
"vitepress-plugin-group-icons": "^1.6.3",
|
||||
"vitest-mock-extended": "^3.1.0",
|
||||
"vue": "^3.5.21",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
|
||||
@@ -1,6 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
## v0.5.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.0...analytics-v0.5.1)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Integrate latest measurement protocol changes ([#1767](https://github.com/wxt-dev/wxt/pull/1767))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Use `@wxt-dev/browser` instead of `@types/chrome` ([#1645](https://github.com/wxt-dev/wxt/pull/1645))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
|
||||
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
|
||||
- Change browser workspace dependency to `^` ([c7335add](https://github.com/wxt-dev/wxt/commit/c7335add))
|
||||
- Fix auto-fixable `markdownlint` errors ([#1710](https://github.com/wxt-dev/wxt/pull/1710))
|
||||
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
|
||||
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
|
||||
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Tanishq-aggarwal ([@tanishq-aggarwal](https://github.com/tanishq-aggarwal))
|
||||
|
||||
## v0.5.0
|
||||
|
||||
[⚠️ breaking changes](https://wxt.dev/guide/resources/upgrading.html) • [compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.4.1...analytics-v0.5.0)
|
||||
|
||||
### 🩹 Fixes
|
||||
@@ -9,4 +37,4 @@
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
@@ -10,15 +10,19 @@ Report analytics events from your web extension extension.
|
||||
## Install With WXT
|
||||
|
||||
1. Install the NPM package:
|
||||
|
||||
```bash
|
||||
pnpm i @wxt-dev/analytics
|
||||
```
|
||||
|
||||
2. In your `wxt.config.ts`, add the WXT module:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/analytics/module'],
|
||||
});
|
||||
```
|
||||
|
||||
3. In your `<srcDir>/app.config.ts`, add a provider:
|
||||
|
||||
```ts
|
||||
@@ -49,9 +53,11 @@ Report analytics events from your web extension extension.
|
||||
## Install Without WXT
|
||||
|
||||
1. Install the NPM package:
|
||||
|
||||
```bash
|
||||
pnpm i @wxt-dev/analytics
|
||||
```
|
||||
|
||||
2. Create an `analytics` instance:
|
||||
|
||||
```ts
|
||||
@@ -66,10 +72,12 @@ Report analytics events from your web extension extension.
|
||||
```
|
||||
|
||||
3. Import your analytics module in the background to initialize the message listener:
|
||||
|
||||
```ts
|
||||
// background.ts
|
||||
import './utils/analytics';
|
||||
```
|
||||
|
||||
4. Then use your `analytics` instance to report events:
|
||||
|
||||
```ts
|
||||
|
||||
@@ -9,11 +9,12 @@ import type {
|
||||
AnalyticsEventMetadata,
|
||||
AnalyticsProvider,
|
||||
} from './types';
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
|
||||
const ANALYTICS_PORT = '@wxt-dev/analytics';
|
||||
|
||||
export function createAnalytics(config?: AnalyticsConfig): Analytics {
|
||||
if (typeof chrome === 'undefined' || !chrome?.runtime?.id)
|
||||
if (!browser?.runtime?.id)
|
||||
throw Error(
|
||||
'Cannot use WXT analytics in contexts without access to the browser.runtime APIs',
|
||||
);
|
||||
@@ -51,13 +52,13 @@ function createBackgroundAnalytics(
|
||||
defineStorageItem<boolean>('local:wxt-analytics:enabled', false);
|
||||
|
||||
// Cached values
|
||||
const platformInfo = chrome.runtime.getPlatformInfo();
|
||||
const platformInfo = browser.runtime.getPlatformInfo();
|
||||
const userAgent = UAParser();
|
||||
let userId = Promise.resolve(userIdStorage.getValue()).then(
|
||||
(id) => id ?? globalThis.crypto.randomUUID(),
|
||||
);
|
||||
let userProperties = userPropertiesStorage.getValue();
|
||||
const manifest = chrome.runtime.getManifest();
|
||||
const manifest = browser.runtime.getManifest();
|
||||
|
||||
const getBackgroundMeta = () => ({
|
||||
timestamp: Date.now(),
|
||||
@@ -178,7 +179,7 @@ function createBackgroundAnalytics(
|
||||
config?.providers?.map((provider) => provider(analytics, config)) ?? [];
|
||||
|
||||
// Listen for messages from the rest of the extension
|
||||
chrome.runtime.onConnect.addListener((port) => {
|
||||
browser.runtime.onConnect.addListener((port) => {
|
||||
if (port.name === ANALYTICS_PORT) {
|
||||
port.onMessage.addListener(({ fn, args }) => {
|
||||
// @ts-expect-error: Untyped fn key
|
||||
@@ -194,7 +195,7 @@ function createBackgroundAnalytics(
|
||||
* Creates an analytics client for non-background contexts.
|
||||
*/
|
||||
function createFrontendAnalytics(): Analytics {
|
||||
const port = chrome.runtime.connect({ name: ANALYTICS_PORT });
|
||||
const port = browser.runtime.connect({ name: ANALYTICS_PORT });
|
||||
const sessionId = Date.now();
|
||||
const getFrontendMetadata = (): AnalyticsEventMetadata => ({
|
||||
sessionId,
|
||||
@@ -252,8 +253,8 @@ function defineStorageItem<T>(
|
||||
): AnalyticsStorageItem<T> {
|
||||
return {
|
||||
getValue: async () =>
|
||||
(await chrome.storage.local.get(key))[key] ?? defaultValue,
|
||||
setValue: (newValue) => chrome.storage.local.set({ [key]: newValue }),
|
||||
(await browser.storage.local.get(key))[key] ?? defaultValue,
|
||||
setValue: (newValue) => browser.storage.local.set({ [key]: newValue }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ export const googleAnalytics4 =
|
||||
ad_personalization: 'DENIED',
|
||||
},
|
||||
user_properties: mappedUserProperties,
|
||||
user_agent: navigator.userAgent,
|
||||
events: [
|
||||
{
|
||||
name: eventName,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wxt-dev/analytics",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"description": "Add analytics to your web extension",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -51,15 +51,15 @@
|
||||
"wxt": ">=0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@types/chrome": "catalog:",
|
||||
"@types/ua-parser-js": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"@aklinker1/check": "^2.1.0",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"publint": "^0.3.12",
|
||||
"typescript": "^5.9.2",
|
||||
"unbuild": "^3.6.1",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"ua-parser-js": "catalog:"
|
||||
"@wxt-dev/browser": "workspace:^",
|
||||
"ua-parser-js": "^1.0.40"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"#analytics": ["./.wxt/analytics/index.ts"]
|
||||
},
|
||||
"types": ["chrome"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## v1.1.0
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.2...auto-icons-v1.1.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add overlay option for dev icons ([#1825](https://github.com/wxt-dev/wxt/pull/1825))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Rewrite and restructure the documentation website ([#933](https://github.com/wxt-dev/wxt/pull/933))
|
||||
- Use full URLs in README so they work on the docs site ([d20793d5](https://github.com/wxt-dev/wxt/commit/d20793d5))
|
||||
- Add SVG compatibility note ([#1830](https://github.com/wxt-dev/wxt/pull/1830))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add `oxlint` for linting ([#947](https://github.com/wxt-dev/wxt/pull/947))
|
||||
- **deps:** Bump sharp from 0.33.4 to 0.33.5 ([#959](https://github.com/wxt-dev/wxt/pull/959))
|
||||
- Upgrade all non-major dependencies ([#1040](https://github.com/wxt-dev/wxt/pull/1040))
|
||||
- **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164))
|
||||
- **deps:** Bump dev and non-breaking major dependencies ([#1167](https://github.com/wxt-dev/wxt/pull/1167))
|
||||
- Use PNPM 10's new catelog feature ([#1493](https://github.com/wxt-dev/wxt/pull/1493))
|
||||
- Move production dependencies to PNPM 10 catelog ([#1494](https://github.com/wxt-dev/wxt/pull/1494))
|
||||
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
|
||||
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Typed SIGTERM ([@typed-sigterm](https://github.com/typed-sigterm))
|
||||
- Kuba ([@zizzfizzix](https://github.com/zizzfizzix))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v1.0.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.1...auto-icons-v1.0.2)
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
## Features
|
||||
|
||||
- Generate extension icons with the correct sizes
|
||||
- Make the icon greyscale during development
|
||||
- Make the icon greyscale or include a visible overlay during development
|
||||
- SVG is supported
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"version": "1.0.2",
|
||||
"version": "1.1.0",
|
||||
"type": "module",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -45,16 +45,16 @@
|
||||
"wxt": ">=0.19.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"defu": "catalog:",
|
||||
"fs-extra": "catalog:",
|
||||
"sharp": "catalog:"
|
||||
"defu": "^6.1.4",
|
||||
"fs-extra": "^11.3.1",
|
||||
"sharp": "^0.34.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"@aklinker1/check": "^2.1.0",
|
||||
"oxlint": "^1.14.0",
|
||||
"publint": "^0.3.12",
|
||||
"typescript": "^5.9.2",
|
||||
"unbuild": "^3.6.1",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,24 @@ export default defineWxtModule<AutoIconsOptions>({
|
||||
{
|
||||
enabled: true,
|
||||
baseIconPath: resolve(wxt.config.srcDir, 'assets/icon.png'),
|
||||
grayscaleOnDevelopment: true,
|
||||
developmentIndicator: 'grayscale',
|
||||
sizes: [128, 48, 32, 16],
|
||||
},
|
||||
);
|
||||
|
||||
// Backward compatibility for the deprecated option
|
||||
if (options?.grayscaleOnDevelopment !== undefined) {
|
||||
wxt.logger.warn(
|
||||
'`[auto-icons]` "grayscaleOnDevelopment" is deprecated. Use "developmentIndicator" instead.',
|
||||
);
|
||||
|
||||
if (options?.developmentIndicator === undefined) {
|
||||
parsedOptions.developmentIndicator = options!.grayscaleOnDevelopment
|
||||
? 'grayscale'
|
||||
: false;
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedPath = resolve(wxt.config.srcDir, parsedOptions.baseIconPath);
|
||||
|
||||
if (!parsedOptions.enabled)
|
||||
@@ -42,21 +55,44 @@ export default defineWxtModule<AutoIconsOptions>({
|
||||
});
|
||||
|
||||
wxt.hooks.hook('build:done', async (wxt, output) => {
|
||||
const image = sharp(resolvedPath).png();
|
||||
|
||||
if (
|
||||
wxt.config.mode === 'development' &&
|
||||
parsedOptions.grayscaleOnDevelopment
|
||||
) {
|
||||
image.grayscale();
|
||||
}
|
||||
|
||||
const outputFolder = wxt.config.outDir;
|
||||
|
||||
for (const size of parsedOptions.sizes) {
|
||||
const resized = image.resize(size);
|
||||
const resizedImage = sharp(resolvedPath).resize(size).png();
|
||||
|
||||
if (wxt.config.mode === 'development') {
|
||||
if (parsedOptions.developmentIndicator === 'grayscale') {
|
||||
resizedImage.grayscale();
|
||||
} else if (parsedOptions.developmentIndicator === 'overlay') {
|
||||
// Helper to build an overlay that places a yellow rectangle at the bottom
|
||||
// of the icon with the text "DEV" in black. The overlay has the same
|
||||
// dimensions as the icon so we can composite it with default gravity.
|
||||
const buildDevOverlay = (size: number) => {
|
||||
const rectHeight = Math.round(size * 0.5);
|
||||
const fontSize = Math.round(size * 0.35);
|
||||
|
||||
return Buffer.from(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0" y="${size - rectHeight}" width="${size}" height="${rectHeight}" fill="#ffff00" />
|
||||
<text x="${size / 2}" y="${size - rectHeight / 2}" font-family="Arial, Helvetica, sans-serif" font-size="${fontSize}" font-weight="bold" fill="black" text-anchor="middle" dominant-baseline="middle">DEV</text>
|
||||
</svg>`);
|
||||
};
|
||||
const overlayBuffer = await sharp(buildDevOverlay(size))
|
||||
.png()
|
||||
.toBuffer();
|
||||
|
||||
resizedImage.composite([
|
||||
{
|
||||
input: overlayBuffer,
|
||||
left: 0,
|
||||
top: 0,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
ensureDir(resolve(outputFolder, 'icons'));
|
||||
await resized.toFile(resolve(outputFolder, `icons/${size}.png`));
|
||||
await resizedImage.toFile(resolve(outputFolder, `icons/${size}.png`));
|
||||
|
||||
output.publicAssets.push({
|
||||
type: 'asset',
|
||||
@@ -89,9 +125,20 @@ export interface AutoIconsOptions {
|
||||
* @default "<srcDir>/assets/icon.png"
|
||||
*/
|
||||
baseIconPath?: string;
|
||||
/**
|
||||
* Apply a visual indicator to the icon when running in development mode.
|
||||
*
|
||||
* "grayscale" converts the icon to grayscale.
|
||||
* "overlay" covers the bottom half with a yellow rectangle and writes "DEV" in black text.
|
||||
* Set to `false` to disable any indicator.
|
||||
*
|
||||
* @default "grayscale"
|
||||
*/
|
||||
developmentIndicator?: 'grayscale' | 'overlay' | false;
|
||||
/**
|
||||
* Grayscale the image when in development mode to indicate development
|
||||
* @default true
|
||||
* @deprecated Use `developmentIndicator` instead
|
||||
*/
|
||||
grayscaleOnDevelopment?: boolean;
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/browser",
|
||||
"description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome",
|
||||
"version": "0.0.317",
|
||||
"version": "0.1.4",
|
||||
"type": "module",
|
||||
"main": "src/index.mjs",
|
||||
"types": "src/index.d.ts",
|
||||
@@ -23,12 +23,12 @@
|
||||
"src"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "0.0.317",
|
||||
"fs-extra": "catalog:",
|
||||
"nano-spawn": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
"@types/chrome": "0.1.6",
|
||||
"fs-extra": "^11.3.1",
|
||||
"nano-spawn": "^1.0.2",
|
||||
"tsx": "4.20.5",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/filesystem": "*",
|
||||
|
||||
@@ -76,5 +76,7 @@ function transformFile(file: string, content: string): string {
|
||||
.replaceAll('declare namespace chrome', 'export namespace Browser')
|
||||
// Update references to `chrome` namespace to `Browser`
|
||||
.replaceAll('chrome.', 'Browser.')
|
||||
// Fix links to developer.chrome.com
|
||||
.replaceAll('developer.Browser.com', 'developer.chrome.com')
|
||||
);
|
||||
}
|
||||
|
||||
+26
-25
@@ -1,5 +1,6 @@
|
||||
/* DO NOT EDIT - generated by scripts/generate.ts */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-wrapper-object-types */
|
||||
export namespace Browser {
|
||||
////////////////////
|
||||
// Cast
|
||||
@@ -112,7 +113,7 @@ export namespace Browser {
|
||||
*/
|
||||
export function initialize(
|
||||
apiConfig: Browser.cast.ApiConfig,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -160,7 +161,7 @@ export namespace Browser {
|
||||
*/
|
||||
export function setCustomReceivers(
|
||||
receivers: Browser.cast.Receiver[],
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -171,7 +172,7 @@ export namespace Browser {
|
||||
*/
|
||||
export function setReceiverDisplayStatus(
|
||||
receiver: Browser.cast.Receiver,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -294,7 +295,7 @@ export namespace Browser {
|
||||
*/
|
||||
setReceiverVolumeLevel(
|
||||
newLevel: number,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -305,7 +306,7 @@ export namespace Browser {
|
||||
*/
|
||||
setReceiverMuted(
|
||||
muted: boolean,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -313,13 +314,13 @@ export namespace Browser {
|
||||
* @param successCallback
|
||||
* @param errorCallback
|
||||
*/
|
||||
leave(successCallback: Function, errorCallback: (error: Browser.cast.Error) => void): void;
|
||||
leave(successCallback: () => void, errorCallback: (error: Browser.cast.Error) => void): void;
|
||||
|
||||
/**
|
||||
* @param successCallback
|
||||
* @param errorCallback
|
||||
*/
|
||||
stop(successCallback: Function, errorCallback: (error: Browser.cast.Error) => void): void;
|
||||
stop(successCallback: () => void, errorCallback: (error: Browser.cast.Error) => void): void;
|
||||
|
||||
/**
|
||||
* @param namespace
|
||||
@@ -330,7 +331,7 @@ export namespace Browser {
|
||||
sendMessage(
|
||||
namespace: string,
|
||||
message: string | object,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -887,7 +888,7 @@ export namespace Browser {
|
||||
*/
|
||||
getStatus(
|
||||
getStatusRequest: Browser.cast.media.GetStatusRequest,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -898,7 +899,7 @@ export namespace Browser {
|
||||
*/
|
||||
play(
|
||||
playRequest: Browser.cast.media.PlayRequest,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -909,7 +910,7 @@ export namespace Browser {
|
||||
*/
|
||||
pause(
|
||||
pauseRequest: Browser.cast.media.PauseRequest,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -920,7 +921,7 @@ export namespace Browser {
|
||||
*/
|
||||
seek(
|
||||
seekRequest: Browser.cast.media.SeekRequest,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -931,7 +932,7 @@ export namespace Browser {
|
||||
*/
|
||||
stop(
|
||||
stopRequest: Browser.cast.media.StopRequest,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -942,7 +943,7 @@ export namespace Browser {
|
||||
*/
|
||||
setVolume(
|
||||
volumeRequest: Browser.cast.media.VolumeRequest,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -953,7 +954,7 @@ export namespace Browser {
|
||||
*/
|
||||
editTracksInfo(
|
||||
editTracksInfoRequest: Browser.cast.media.EditTracksInfoRequest,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -986,7 +987,7 @@ export namespace Browser {
|
||||
*/
|
||||
queueAppendItem(
|
||||
item: Browser.cast.media.QueueItem,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -997,7 +998,7 @@ export namespace Browser {
|
||||
*/
|
||||
queueInsertItems(
|
||||
queueInsertItemsRequest: Browser.cast.media.QueueInsertItemsRequest,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -1008,7 +1009,7 @@ export namespace Browser {
|
||||
*/
|
||||
queueJumpToItem(
|
||||
itemId: number,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -1021,7 +1022,7 @@ export namespace Browser {
|
||||
queueMoveItemToNewIndex(
|
||||
itemId: number,
|
||||
newIndex: number,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -1029,13 +1030,13 @@ export namespace Browser {
|
||||
* @param successCallback
|
||||
* @param errorCallback
|
||||
*/
|
||||
queueNext(successCallback: Function, errorCallback: (error: Browser.cast.Error) => void): void;
|
||||
queueNext(successCallback: () => void, errorCallback: (error: Browser.cast.Error) => void): void;
|
||||
|
||||
/**
|
||||
* @param successCallback
|
||||
* @param errorCallback
|
||||
*/
|
||||
queuePrev(successCallback: Function, errorCallback: (error: Browser.cast.Error) => void): void;
|
||||
queuePrev(successCallback: () => void, errorCallback: (error: Browser.cast.Error) => void): void;
|
||||
|
||||
/**
|
||||
* @param itemId
|
||||
@@ -1044,7 +1045,7 @@ export namespace Browser {
|
||||
*/
|
||||
queueRemoveItem(
|
||||
itemId: number,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -1055,7 +1056,7 @@ export namespace Browser {
|
||||
*/
|
||||
queueReorderItems(
|
||||
queueReorderItemsRequest: Browser.cast.media.QueueReorderItemsRequest,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -1066,7 +1067,7 @@ export namespace Browser {
|
||||
*/
|
||||
queueSetRepeatMode(
|
||||
repeatMode: Browser.cast.media.RepeatMode,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
|
||||
@@ -1077,7 +1078,7 @@ export namespace Browser {
|
||||
*/
|
||||
queueUpdateItems(
|
||||
queueUpdateItemsRequest: Browser.cast.media.QueueUpdateItemsRequest,
|
||||
successCallback: Function,
|
||||
successCallback: () => void,
|
||||
errorCallback: (error: Browser.cast.Error) => void,
|
||||
): void;
|
||||
}
|
||||
|
||||
+2830
-3345
File diff suppressed because it is too large
Load Diff
@@ -24,10 +24,10 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "{{chromeTypesVersion}}",
|
||||
"fs-extra": "catalog:",
|
||||
"nano-spawn": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
"fs-extra": "^11.3.1",
|
||||
"nano-spawn": "^1.0.2",
|
||||
"tsx": "4.19.4",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# Changelog
|
||||
|
||||
## v0.2.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.3...i18n-v0.2.4)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Standardize locale codes and warn about unsupported ones ([#1617](https://github.com/wxt-dev/wxt/pull/1617))
|
||||
- Use `@wxt-dev/browser` instead of `@types/chrome` ([#1645](https://github.com/wxt-dev/wxt/pull/1645))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Add react language ID to README ([#1347](https://github.com/wxt-dev/wxt/pull/1347))
|
||||
- Fix public path reference ([bcb20874](https://github.com/wxt-dev/wxt/commit/bcb20874))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164))
|
||||
- **deps:** Bump dev and non-breaking major dependencies ([#1167](https://github.com/wxt-dev/wxt/pull/1167))
|
||||
- Add funding links to `package.json` files ([#1446](https://github.com/wxt-dev/wxt/pull/1446))
|
||||
- Use PNPM 10's new catelog feature ([#1493](https://github.com/wxt-dev/wxt/pull/1493))
|
||||
- Move production dependencies to PNPM 10 catelog ([#1494](https://github.com/wxt-dev/wxt/pull/1494))
|
||||
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
|
||||
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
|
||||
- Change browser workspace dependency to `^` ([c7335add](https://github.com/wxt-dev/wxt/commit/c7335add))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Okinea Dev ([@okineadev](https://github.com/okineadev))
|
||||
- Redwoodlid ([@redwoodlid](https://github.com/redwoodlid))
|
||||
|
||||
## v0.2.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.2...i18n-v0.2.3)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
1. Simple messages file format
|
||||
2. Plural form support
|
||||
3. Integrates with the [I18n Ally VS Code extension](#vscode)
|
||||
3. Integrates with the [I18n Ally VS Code extension](#vs-code)
|
||||
|
||||
It also provides several benefits over other non-web extension specific i18n packages:
|
||||
|
||||
@@ -81,7 +81,7 @@ And you're done! Using WXT, you get type-safety out of the box.
|
||||
|
||||
> [!NOTE]
|
||||
> For the best DX, you should to integrate `@wxt-dev/i18n` into your build process. This enables:
|
||||
|
||||
>
|
||||
> 1. Plural forms
|
||||
> 2. Simple messages file format
|
||||
> 3. Type safety
|
||||
|
||||
+13
-13
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/i18n",
|
||||
"description": "Type-safe wrapper around browser.i18n.getMessage with additional features",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.4",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -26,9 +26,10 @@
|
||||
"test": "buildc --deps-only -- vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"chokidar": "catalog:",
|
||||
"confbox": "catalog:",
|
||||
"fast-glob": "catalog:"
|
||||
"@wxt-dev/browser": "workspace:^",
|
||||
"chokidar": "^4.0.3",
|
||||
"confbox": "^0.1.8 || ^0.2.2",
|
||||
"fast-glob": "^3.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.7"
|
||||
@@ -39,15 +40,14 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@types/chrome": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vitest-plugin-random-seed": "catalog:",
|
||||
"@aklinker1/check": "^2.1.0",
|
||||
"@types/node": "^20.17.6",
|
||||
"oxlint": "^1.14.0",
|
||||
"publint": "^0.3.12",
|
||||
"typescript": "^5.9.2",
|
||||
"unbuild": "^3.6.1",
|
||||
"vitest": "^3.2.4",
|
||||
"vitest-plugin-random-seed": "^1.1.1",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { createI18n } from '../index';
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
|
||||
const getMessageMock = vi.fn();
|
||||
|
||||
vi.stubGlobal('chrome', {
|
||||
i18n: {
|
||||
getMessage: getMessageMock,
|
||||
},
|
||||
vi.mock('@wxt-dev/browser', async () => {
|
||||
const { vi } = await import('vitest');
|
||||
return {
|
||||
browser: {
|
||||
i18n: {
|
||||
getMessage: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
const getMessageMock = vi.mocked(browser.i18n.getMessage);
|
||||
|
||||
describe('createI18n', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { beforeEach, describe, it, vi } from 'vitest';
|
||||
import { createI18n } from '..';
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
|
||||
const getMessageMock = vi.fn();
|
||||
|
||||
vi.stubGlobal('chrome', {
|
||||
i18n: {
|
||||
getMessage: getMessageMock,
|
||||
},
|
||||
vi.mock('@wxt-dev/browser', async () => {
|
||||
const { vi } = await import('vitest');
|
||||
return {
|
||||
browser: {
|
||||
i18n: {
|
||||
getMessage: vi.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
const getMessageMock = vi.mocked(browser.i18n.getMessage);
|
||||
|
||||
const n: number = 1;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
I18n,
|
||||
Substitution,
|
||||
} from './types';
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
|
||||
export function createI18n<
|
||||
T extends I18nStructure = DefaultI18nStructure,
|
||||
@@ -39,9 +40,9 @@ export function createI18n<
|
||||
if (sub?.length) {
|
||||
// Convert all substitutions to strings
|
||||
const stringSubs = sub?.map((sub) => String(sub));
|
||||
message = chrome.i18n.getMessage(key.replaceAll('.', '_'), stringSubs);
|
||||
message = browser.i18n.getMessage(key.replaceAll('.', '_'), stringSubs);
|
||||
} else {
|
||||
message = chrome.i18n.getMessage(key.replaceAll('.', '_'));
|
||||
message = browser.i18n.getMessage(key.replaceAll('.', '_'));
|
||||
}
|
||||
if (!message) {
|
||||
console.warn(`[i18n] Message not found: "${key}"`);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["chrome", "node"]
|
||||
"types": ["node"]
|
||||
},
|
||||
"exclude": ["node_modules/**", "dist/**"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
# Changelog
|
||||
|
||||
## v1.1.5
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.4...module-react-v1.1.5)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps:** Support @vitejs/plugin-react@5 ([9170159b](https://github.com/wxt-dev/wxt/commit/9170159b))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v1.1.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.3...module-react-v1.1.4)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Add support for WXT v0.20.0 ([c9dca022](https://github.com/wxt-dev/wxt/commit/c9dca022))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add funding links to `package.json` files ([#1446](https://github.com/wxt-dev/wxt/pull/1446))
|
||||
- Use PNPM 10's new catelog feature ([#1493](https://github.com/wxt-dev/wxt/pull/1493))
|
||||
- Move production dependencies to PNPM 10 catelog ([#1494](https://github.com/wxt-dev/wxt/pull/1494))
|
||||
- **deps:** Upgrade to Vite 6 and related dependencies ([#1496](https://github.com/wxt-dev/wxt/pull/1496))
|
||||
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
|
||||
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
|
||||
- **deps:** Update all dependencies ([#1648](https://github.com/wxt-dev/wxt/pull/1648))
|
||||
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
|
||||
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
|
||||
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Okinea Dev <hi@okinea.dev>
|
||||
|
||||
## v1.1.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.2...module-react-v1.1.3)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"version": "1.1.3",
|
||||
"version": "1.1.5",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -48,17 +48,17 @@
|
||||
"wxt": ">=0.19.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "catalog:"
|
||||
"@vitejs/plugin-react": "^4.4.1 || ^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"@aklinker1/check": "^2.1.0",
|
||||
"@types/react": "^19.1.12",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"publint": "^0.3.12",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"typescript": "^5.9.2",
|
||||
"unbuild": "^3.6.1",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
## v1.1.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-solid-v1.1.3...module-solid-v1.1.4)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Add support for WXT v0.20.0 ([c9dca022](https://github.com/wxt-dev/wxt/commit/c9dca022))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps-dev:** Bump solid-js from 1.9.3 to 1.9.4 ([#1391](https://github.com/wxt-dev/wxt/pull/1391))
|
||||
- Add funding links to `package.json` files ([#1446](https://github.com/wxt-dev/wxt/pull/1446))
|
||||
- **deps:** Bump vite-plugin-solid from 2.10.2 to 2.11.6 ([#1491](https://github.com/wxt-dev/wxt/pull/1491))
|
||||
- Use PNPM 10's new catelog feature ([#1493](https://github.com/wxt-dev/wxt/pull/1493))
|
||||
- Move production dependencies to PNPM 10 catelog ([#1494](https://github.com/wxt-dev/wxt/pull/1494))
|
||||
- **deps:** Upgrade to Vite 6 and related dependencies ([#1496](https://github.com/wxt-dev/wxt/pull/1496))
|
||||
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
|
||||
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
|
||||
- **deps:** Update all dependencies ([#1648](https://github.com/wxt-dev/wxt/pull/1648))
|
||||
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
|
||||
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
|
||||
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Okinea Dev <hi@okinea.dev>
|
||||
|
||||
## v1.1.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-solid-v1.1.2...module-solid-v1.1.3)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"version": "1.1.3",
|
||||
"version": "1.1.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -48,14 +48,14 @@
|
||||
"wxt": ">=0.19.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"vite-plugin-solid": "catalog:"
|
||||
"vite-plugin-solid": "^2.11.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"@aklinker1/check": "^2.1.0",
|
||||
"publint": "^0.3.12",
|
||||
"solid-js": "^1.9.9",
|
||||
"typescript": "^5.9.2",
|
||||
"unbuild": "^3.6.1",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## v2.0.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.3...module-svelte-v2.0.4)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Add support for @sveltejs/vite-plugin-svelte@6 ([459f73fd](https://github.com/wxt-dev/wxt/commit/459f73fd))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add funding links to `package.json` files ([#1446](https://github.com/wxt-dev/wxt/pull/1446))
|
||||
- Use PNPM 10's new catelog feature ([#1493](https://github.com/wxt-dev/wxt/pull/1493))
|
||||
- Move production dependencies to PNPM 10 catelog ([#1494](https://github.com/wxt-dev/wxt/pull/1494))
|
||||
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
|
||||
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
|
||||
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
|
||||
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
|
||||
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Okinea Dev <hi@okinea.dev>
|
||||
|
||||
## v2.0.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.2...module-svelte-v2.0.3)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"version": "2.0.3",
|
||||
"version": "2.0.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -47,13 +47,13 @@
|
||||
"svelte": ">=5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "catalog:"
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"@aklinker1/check": "^2.1.0",
|
||||
"publint": "^0.3.12",
|
||||
"typescript": "^5.9.2",
|
||||
"unbuild": "^3.6.1",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## v1.0.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-vue-v1.0.2...module-vue-v1.0.3)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add funding links to `package.json` files ([#1446](https://github.com/wxt-dev/wxt/pull/1446))
|
||||
- Use PNPM 10's new catelog feature ([#1493](https://github.com/wxt-dev/wxt/pull/1493))
|
||||
- Move production dependencies to PNPM 10 catelog ([#1494](https://github.com/wxt-dev/wxt/pull/1494))
|
||||
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
|
||||
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
|
||||
- Wxt & @wxt-dev/module-vue support Vite 7 ([#1771](https://github.com/wxt-dev/wxt/pull/1771))
|
||||
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
|
||||
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
|
||||
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Ayu ([@ayu-exorcist](https://github.com/ayu-exorcist))
|
||||
- Okinea Dev <hi@okinea.dev>
|
||||
|
||||
## v1.0.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-vue-v1.0.1...module-vue-v1.0.2)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -46,13 +46,13 @@
|
||||
"wxt": ">=0.19.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-vue": "catalog:"
|
||||
"@vitejs/plugin-vue": "^5.2.3 || ^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"@aklinker1/check": "^2.1.0",
|
||||
"publint": "^0.3.12",
|
||||
"typescript": "^5.9.2",
|
||||
"unbuild": "^3.6.1",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
## v0.1.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/runner-v0.1.1...runner-v0.1.2)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **config:** Add browser path for Zen via Homebrew ([#1813](https://github.com/wxt-dev/wxt/pull/1813))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Improve Chrome path search ([#1823](https://github.com/wxt-dev/wxt/pull/1823))
|
||||
- **paths:** Add browser paths for Arc & Dia on macos ([#1814](https://github.com/wxt-dev/wxt/pull/1814))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Fix auto-fixable `markdownlint` errors ([#1710](https://github.com/wxt-dev/wxt/pull/1710))
|
||||
- Manually fix markdownlint errors ([#1711](https://github.com/wxt-dev/wxt/pull/1711))
|
||||
- **deps:** Upgrade oxlint from 0.16.8 to 1.14.0 ([a01928e0](https://github.com/wxt-dev/wxt/commit/a01928e0))
|
||||
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
|
||||
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
|
||||
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Sam Carlton ([@ThatGuySam](https://github.com/ThatGuySam))
|
||||
- Alexander Kachkaev <alexander@kachkaev.ru>
|
||||
|
||||
## v0.1.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/runner-v0.1.0...runner-v0.1.1)
|
||||
@@ -0,0 +1,227 @@
|
||||
# `@wxt-dev/runner`
|
||||
|
||||
Programmatically open a browser and install a web extension from a local directory.
|
||||
|
||||
## Usage
|
||||
|
||||
### With WXT
|
||||
|
||||
> [!WARNING]
|
||||
> This package is intended to replace [`web-ext`](https://github.com/mozilla/web-ext) in the future, but it is not ready at the moment. Once it's ready for testing in WXT, more details will be added here.
|
||||
|
||||
```ts
|
||||
// ~/wxt.runner.config.ts OR <project>/wxt.runner.config.ts
|
||||
import { defineRunnerConfig } from 'wxt';
|
||||
|
||||
export default defineRunnerConfig({
|
||||
// Options go here
|
||||
});
|
||||
```
|
||||
|
||||
### JS API
|
||||
|
||||
```ts
|
||||
import { run } from '@wxt-dev/runner';
|
||||
|
||||
await run({
|
||||
extensionDir: '/path/to/extension',
|
||||
// Other options...
|
||||
});
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Supports all Chromium and Firefox based browsers
|
||||
- Zero dependencies
|
||||
- One-line config for persisting data between launches
|
||||
|
||||
## Requirements
|
||||
|
||||
`@wxt-dev/runner` requires a JS runtime that implements the `WebSocket` standard:
|
||||
|
||||
| JS Runtime | Version |
|
||||
| ---------- | ----------- |
|
||||
| NodeJS | ≥ 22.4.0 |
|
||||
| Bun | ≥ 1.2.0 |
|
||||
|
||||
You also need to have a specific version of the browser installed that supports the latest features so extensions can be loaded:
|
||||
|
||||
| Browser | Version |
|
||||
| -------- | -------- |
|
||||
| Chromium | Unknown |
|
||||
| Firefox | ≥ 139 |
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] Provide install functions to allow hooking into already running instances of Chrome/Firefox
|
||||
- [ ] Try to setup E2E tests on Firefox with Puppeteer using this approach
|
||||
- [ ] Try to setup E2E tests on Chrome with Puppeteer using this approach
|
||||
|
||||
## Options
|
||||
|
||||
### Target
|
||||
|
||||
To open a specific browser, use the `target` option:
|
||||
|
||||
```ts
|
||||
import { run } from '@wxt-dev/runner';
|
||||
|
||||
await run({
|
||||
extensionDir: 'path/to/extension',
|
||||
target: 'firefox',
|
||||
});
|
||||
```
|
||||
|
||||
Defaults to opening `chrome`. You may see type-hints for a list of popular browsers, but you can enter any string you want here.
|
||||
|
||||
### Data Persistence
|
||||
|
||||
Browsers block you from using your normal browser profiles when using the [BiDi and CDP protocols](#implementation-details) for security reasons.
|
||||
|
||||
To change how the new profile's data is saved between sessions, use the `dataPersistence` option:
|
||||
|
||||
```ts
|
||||
import { run } from '@wxt-dev/runner';
|
||||
|
||||
await run({
|
||||
dataPersistence: 'user',
|
||||
});
|
||||
```
|
||||
|
||||
- `"none"` (default): Use a brand new browser profile every time the browser is opened (stored in the system's tmp directory)
|
||||
- `"project"`: Create a new profile that is re-used for your current directory (by default stored in `.wxt-runner` or `.wxt/runner` for WXT projects)
|
||||
- `"user"`: Create a new profile that is re-used for all projects using `@wxt-dev/runner` (by default stored in `$HOME/.wxt-runner`)
|
||||
|
||||
These presets configure different flags for different operating systems when spawning the browser process.
|
||||
|
||||
If you want to customize your data persistence beyond what these presets define, [you can override the browser flags yourself](#arguments) to configure persistence.
|
||||
|
||||
### Browser Binaries
|
||||
|
||||
`@wxt-dev/runner` will look for browser binaries/executables in [a hard-coded list of paths](https://github.com/wxt-dev/wxt/blob/main/packages/runner/src/browser-paths.ts). It does not and will not explore your filesystem/`$PATH` to find where the browser is installed. That means there are times you will need to specify the path to a browser's binary on your system:
|
||||
|
||||
- Your browser's path is non-standard or missing from the hard-coded list.
|
||||
- You want to use a specific version/release of the browser.
|
||||
- You're using a less popular browser and `@wxt-dev/runner` doesn't have hard-coded paths for it.
|
||||
|
||||
To do this, use the `browserBinaries` option and set the path to the browser's binary:
|
||||
|
||||
```ts
|
||||
import { run } from '@wxt-dev/runner';
|
||||
|
||||
await run({
|
||||
extensionDir: 'path/to/extension',
|
||||
browserBinaries: {
|
||||
chrome: '/path/to/chrome',
|
||||
firefox: '/path/to/firefox',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Arguments
|
||||
|
||||
To pass custom arguments to the browser on startup, use the `chromiumArgs` or `firefoxArgs` options:
|
||||
|
||||
```ts
|
||||
import { run } from '@wxt-dev/runner';
|
||||
|
||||
await run({
|
||||
extensionDir: 'path/to/extension',
|
||||
chromiumArgs: ['--window-size=1920,1080'],
|
||||
firefoxArgs: ['--window-size', '1920,1080'],
|
||||
});
|
||||
```
|
||||
|
||||
### Start URLs
|
||||
|
||||
To open specific URLs in tabs by default, you also use the `chromiumArgs` or `firefoxArgs` options.
|
||||
|
||||
Any URLs passed as a CLI argument will be opened in the browser when it starts.
|
||||
|
||||
```ts
|
||||
import { run } from '@wxt-dev/runner';
|
||||
|
||||
await run({
|
||||
extensionDir: 'path/to/extension',
|
||||
chromiumArgs: ['https://example.com'],
|
||||
firefoxArgs: ['https://example.com'],
|
||||
});
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
To see debug logs, set the `DEBUG` env var to `"@wxt-dev/runner"`. This will print the resolved config, commands used to spawn the browser, any messages sent on the browser's communication protocol, and more for you to debug.
|
||||
|
||||
<details>
|
||||
<summary>Example debug output</summary>
|
||||
|
||||
```plaintext
|
||||
@wxt-dev/runner:options User options: { extensionDir: 'demo-extension', target: undefined }
|
||||
@wxt-dev/runner:options Resolved options: {
|
||||
browserBinary: '/usr/bin/chromium',
|
||||
chromiumArgs: [
|
||||
'--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculateNativeWinOcclusion,InterestFeedContentSuggestions,CertificateTransparencyComponentUpdater,AutofillServerCommunication,PrivacySandboxSettings4',
|
||||
'--disable-component-extensions-with-background-pages',
|
||||
'--disable-background-networking',
|
||||
'--disable-component-update',
|
||||
'--disable-client-side-phishing-detection',
|
||||
'--disable-sync',
|
||||
'--metrics-recording-only',
|
||||
'--disable-default-apps',
|
||||
'--no-default-browser-check',
|
||||
'--no-first-run',
|
||||
'--disable-background-timer-throttling',
|
||||
'--disable-ipc-flooding-protection',
|
||||
'--password-store=basic',
|
||||
'--use-mock-keychain',
|
||||
'--force-fieldtrials=*BackgroundTracing/default/',
|
||||
'--disable-hang-monitor',
|
||||
'--disable-prompt-on-repost',
|
||||
'--disable-domain-reliability',
|
||||
'--propagate-iph-for-testing',
|
||||
'--remote-debugging-port=0',
|
||||
'--remote-debugging-pipe',
|
||||
'--user-data-dir=/tmp/wxt-runner-pWXLO1',
|
||||
'--enable-unsafe-extension-debugging'
|
||||
],
|
||||
dataDir: '/tmp/wxt-runner-pWXLO1',
|
||||
dataPersistence: 'none',
|
||||
chromiumRemoteDebuggingPort: 0,
|
||||
extensionDir: '/home/aklinker1/Development/github.com/wxt-dev/wxt/packages/runner/demo-extension',
|
||||
firefoxArgs: [
|
||||
'--new-instance',
|
||||
'--no-remote',
|
||||
'--profile',
|
||||
'/tmp/wxt-runner-pWXLO1',
|
||||
'--remote-debugging-port=0',
|
||||
'about:debugging#/runtime/this-firefox'
|
||||
],
|
||||
firefoxRemoteDebuggingPort: 0,
|
||||
target: 'chrome'
|
||||
}
|
||||
@wxt-dev/runner:chrome:stderr DevTools listening on ws://127.0.0.1:38397/devtools/browser/93dc4de5-64cb-4e0b-a9d3-7549527015f0
|
||||
@wxt-dev/runner:cdp Sending command: {
|
||||
id: 1,
|
||||
method: 'Extensions.loadUnpacked',
|
||||
params: {
|
||||
path: '/home/aklinker1/Development/github.com/wxt-dev/wxt/packages/runner/demo-extension'
|
||||
}
|
||||
}
|
||||
@wxt-dev/runner:cdp Received response: { id: 1, result: { id: 'hckhakegfgenefhikdcfkaaonnclljmf' } }
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Implementation Details
|
||||
|
||||
All this package does is spawn a child process to open the browser with some default flags before using remote protocols to install the extension.
|
||||
|
||||
### Firefox
|
||||
|
||||
We use the new [WebDriver BiDi protocol](https://www.w3.org/TR/webdriver-bidi) to install the extension. This just involves connecting to a web socket and sending a few messages.
|
||||
|
||||
### Chrome
|
||||
|
||||
We use the [CDP](https://chromedevtools.github.io/devtools-protocol/) with `--remote-debugging-pipe` and `--enable-unsafe-extension-debugging` to install the extension by sending a message via IO pipes 3 and 4.
|
||||
|
||||
We don't use Webdriver Bidi because it's not built into Chrome yet. It requires us instantiating a separate child process for `chromedriver`. This is slower and more difficult than just using the CDP built into Chrome.
|
||||
@@ -0,0 +1 @@
|
||||
console.log('Hello background!');
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "Test",
|
||||
"version": "1.0.0",
|
||||
"manifest_version": 3,
|
||||
"background": {
|
||||
"service_worker": "background.js",
|
||||
"scripts": ["background.js"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// USAGE:
|
||||
// pnpm dev
|
||||
// pnpm dev firefox-nightly
|
||||
// pnpm dev <target>
|
||||
//
|
||||
|
||||
import { run } from './src';
|
||||
|
||||
// Uncomment to enable debug logs
|
||||
process.env.DEBUG = '@wxt-dev/runner';
|
||||
|
||||
await run({
|
||||
extensionDir: 'demo-extension',
|
||||
target: process.argv[2],
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@wxt-dev/runner",
|
||||
"description": "Launch Chrome and Firefox with a web extension installed",
|
||||
"version": "0.1.2",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wxt-dev/wxt.git",
|
||||
"directory": "packages/runner"
|
||||
},
|
||||
"homepage": "https://github.com/wxt-dev/wxt/tree/main/packages/runner#readme",
|
||||
"keywords": [
|
||||
"web-extension",
|
||||
"chrome-extension",
|
||||
"wxt"
|
||||
],
|
||||
"author": {
|
||||
"name": "Aaron Klinker",
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"scripts": {
|
||||
"check": "pnpm build && check",
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"dev": "tsx --trace-warnings dev.ts",
|
||||
"build": "buildc -- unbuild",
|
||||
"prepublishOnly": "pnpm build"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^2.1.0",
|
||||
"oxlint": "^1.14.0",
|
||||
"publint": "^0.3.12",
|
||||
"tsx": "4.20.5",
|
||||
"typescript": "^5.9.2",
|
||||
"unbuild": "^3.6.1",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"main": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { createCdpConnection, type CDPConnection } from '../cdp';
|
||||
import { createBidiConnection, type BidiConnection } from '../bidi';
|
||||
import type { ChildProcess } from 'node:child_process';
|
||||
import { installChromium, installFirefox } from '../install';
|
||||
|
||||
vi.mock('../cdp');
|
||||
const createCdpConnectionMock = vi.mocked(createCdpConnection);
|
||||
|
||||
vi.mock('../bidi');
|
||||
const createBidiConnectionMock = vi.mocked(createBidiConnection);
|
||||
|
||||
describe('Install', () => {
|
||||
describe('Chromium', () => {
|
||||
it('Should send the install command to the process', async () => {
|
||||
const browserProcess = mock<ChildProcess>();
|
||||
const connection = mock<CDPConnection>({
|
||||
[Symbol.dispose]: vi.fn(),
|
||||
});
|
||||
const extensionDir = '/path/to/extension';
|
||||
const expectedExtensionId = 'chromium-extension-id';
|
||||
|
||||
createCdpConnectionMock.mockReturnValue(connection);
|
||||
connection.send.mockImplementation(async (method) => {
|
||||
if (method === 'Extensions.loadUnpacked')
|
||||
return { id: expectedExtensionId };
|
||||
throw Error('Unknown method');
|
||||
});
|
||||
|
||||
const res = await installChromium(browserProcess, extensionDir);
|
||||
|
||||
expect(createCdpConnectionMock).toBeCalledTimes(1);
|
||||
expect(createCdpConnectionMock).toBeCalledWith(browserProcess);
|
||||
|
||||
expect(connection.send).toBeCalledTimes(1);
|
||||
expect(connection.send).toBeCalledWith('Extensions.loadUnpacked', {
|
||||
path: extensionDir,
|
||||
});
|
||||
|
||||
expect(res).toEqual({ id: expectedExtensionId });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Firefox', () => {
|
||||
it('Should connect to the server, start a session, then install the extension', async () => {
|
||||
const debuggerUrl = 'http://127.0.0.1:9222';
|
||||
const extensionDir = '/path/to/extension';
|
||||
const expectedExtensionId = 'firefox-extension-id';
|
||||
const connection = mock<BidiConnection>({
|
||||
[Symbol.dispose]: vi.fn(),
|
||||
});
|
||||
|
||||
createBidiConnectionMock.mockResolvedValue(connection);
|
||||
connection.send.mockImplementation(async (method) => {
|
||||
if (method === 'session.new') return { sessionId: 'session-id' };
|
||||
if (method === 'webExtension.install')
|
||||
return { extension: expectedExtensionId };
|
||||
});
|
||||
|
||||
const res = await installFirefox(debuggerUrl, extensionDir);
|
||||
|
||||
expect(createBidiConnectionMock).toBeCalledTimes(1);
|
||||
expect(createBidiConnectionMock).toBeCalledWith(debuggerUrl);
|
||||
|
||||
expect(connection.send).toBeCalledTimes(2);
|
||||
expect(connection.send).toBeCalledWith('session.new', {
|
||||
capabilities: {},
|
||||
});
|
||||
expect(connection.send).toBeCalledWith('webExtension.install', {
|
||||
extensionData: {
|
||||
type: 'path',
|
||||
path: extensionDir,
|
||||
},
|
||||
});
|
||||
|
||||
expect(res).toEqual({ extension: expectedExtensionId });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,306 @@
|
||||
import { describe, it, expect, vi, beforeAll } from 'vitest';
|
||||
import { ResolvedRunOptions, resolveRunOptions } from '../options';
|
||||
import { resolve, join } from 'node:path';
|
||||
import { tmpdir, homedir } from 'node:os';
|
||||
import { mkdir } from 'node:fs/promises';
|
||||
|
||||
vi.mock('node:os', async () => {
|
||||
const { vi } = await import('vitest');
|
||||
const os: any = await vi.importActual('node:os');
|
||||
const { join } = await import('node:path');
|
||||
return {
|
||||
...os,
|
||||
tmpdir: () => join(os.tmpdir(), 'tmpdir-mock'),
|
||||
homedir: () => join(os.tmpdir(), 'homedir-mock'),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Options', () => {
|
||||
beforeAll(async () => {
|
||||
// Make sure mock test directories exist
|
||||
await mkdir(tmpdir(), { recursive: true });
|
||||
await mkdir(homedir(), { recursive: true });
|
||||
});
|
||||
|
||||
describe('extensionDir', () => {
|
||||
it('should default to the current working directory', async () => {
|
||||
const actual = await resolveRunOptions({});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
extensionDir: process.cwd(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve relative to the current working directory', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: './path/to/extension',
|
||||
});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
extensionDir: resolve(process.cwd(), './path/to/extension'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('target', () => {
|
||||
it('should be "chrome" by default', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: 'path/to/extension',
|
||||
});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
target: 'chrome',
|
||||
});
|
||||
});
|
||||
|
||||
it('should be what is passed in', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: 'path/to/extension',
|
||||
target: 'custom',
|
||||
browserBinaries: {
|
||||
custom: '/path/to/custom/browser',
|
||||
},
|
||||
});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
target: 'custom',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if the target binary could not be found', async () => {
|
||||
const actual = resolveRunOptions({
|
||||
extensionDir: 'path/to/extension',
|
||||
target: 'custom',
|
||||
});
|
||||
await expect(actual).rejects.toThrow('Could not find "custom" binary.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('browserBinary', () => {
|
||||
it('should denormalize the browserBinary', async () => {
|
||||
const path =
|
||||
process.platform === 'win32'
|
||||
? 'C:/path/to/custom/browser.exe'
|
||||
: '/path/to/custom/browser';
|
||||
const expectedPath =
|
||||
process.platform === 'win32'
|
||||
? 'C:\\path\\to\\custom\\browser.exe'
|
||||
: path;
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: 'path/to/extension',
|
||||
target: 'custom',
|
||||
browserBinaries: {
|
||||
custom: path,
|
||||
},
|
||||
});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
browserBinary: expectedPath,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('chromiumArgs', () => {
|
||||
it('should log a warning when --user-data-dir is passed in', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn');
|
||||
await resolveRunOptions({
|
||||
chromiumArgs: ['--user-data-dir=some/custom/path'],
|
||||
});
|
||||
expect(warnSpy).toBeCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'Custom Chromium --user-data-dir argument ignored',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log a warning when --remote-debugging-port is passed in', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn');
|
||||
await resolveRunOptions({
|
||||
chromiumArgs: ['--remote-debugging-port=9222'],
|
||||
});
|
||||
expect(warnSpy).toBeCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'Custom Chromium --remote-debugging-port argument ignored',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should combine default args with user provided ones', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
chromiumArgs: ['--window-size=1920,1080'],
|
||||
});
|
||||
expect(actual.chromiumArgs).toEqual([
|
||||
// Defaults
|
||||
'--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculateNativeWinOcclusion,InterestFeedContentSuggestions,CertificateTransparencyComponentUpdater,AutofillServerCommunication,PrivacySandboxSettings4',
|
||||
'--disable-component-extensions-with-background-pages',
|
||||
'--disable-background-networking',
|
||||
'--disable-component-update',
|
||||
'--disable-client-side-phishing-detection',
|
||||
'--disable-sync',
|
||||
'--metrics-recording-only',
|
||||
'--disable-default-apps',
|
||||
'--no-default-browser-check',
|
||||
'--no-first-run',
|
||||
'--disable-background-timer-throttling',
|
||||
'--disable-ipc-flooding-protection',
|
||||
'--password-store=basic',
|
||||
'--use-mock-keychain',
|
||||
'--force-fieldtrials=*BackgroundTracing/default/',
|
||||
'--disable-hang-monitor',
|
||||
'--disable-prompt-on-repost',
|
||||
'--disable-domain-reliability',
|
||||
'--propagate-iph-for-testing',
|
||||
// Debugging
|
||||
expect.stringContaining('--remote-debugging-port='),
|
||||
'--remote-debugging-pipe',
|
||||
expect.stringContaining('--user-data-dir='), // See dataPersistence tests
|
||||
'--enable-unsafe-extension-debugging',
|
||||
// User provided
|
||||
'--window-size=1920,1080',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('firefoxArgs', () => {
|
||||
it('should log a warning when --profile is passed in', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn');
|
||||
await resolveRunOptions({
|
||||
firefoxArgs: ['--profile=some/custom/path'],
|
||||
});
|
||||
expect(warnSpy).toBeCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Custom Firefox --profile argument ignored'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log a warning when --remote-debugging-port is passed in', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn');
|
||||
await resolveRunOptions({
|
||||
firefoxArgs: ['--remote-debugging-port=9222'],
|
||||
});
|
||||
expect(warnSpy).toBeCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'Custom Firefox --remote-debugging-port argument ignored',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should combine default args with user provided ones', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
firefoxArgs: ['--window-size=1920,1080'],
|
||||
});
|
||||
expect(actual.firefoxArgs).toEqual([
|
||||
// Defaults
|
||||
'--new-instance',
|
||||
'--no-remote',
|
||||
'--profile',
|
||||
expect.any(String), // See dataPersistence tests
|
||||
expect.stringContaining('--remote-debugging-port='),
|
||||
'about:debugging#/runtime/this-firefox',
|
||||
// User provided
|
||||
'--window-size=1920,1080',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('chromiumRemoteDebuggingPort', () => {
|
||||
it('should default to 0', async () => {
|
||||
const actual = await resolveRunOptions({});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
chromiumRemoteDebuggingPort: 0,
|
||||
chromiumArgs: expect.arrayContaining([`--remote-debugging-port=0`]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should respect user provided port', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
chromiumRemoteDebuggingPort: 9222,
|
||||
});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
chromiumRemoteDebuggingPort: 9222,
|
||||
chromiumArgs: expect.arrayContaining([`--remote-debugging-port=9222`]),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('firefoxRemoteDebuggingPort', () => {
|
||||
it('should default to 0', async () => {
|
||||
const actual = await resolveRunOptions({});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
firefoxRemoteDebuggingPort: 0,
|
||||
firefoxArgs: expect.arrayContaining([`--remote-debugging-port=0`]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should respect user provided port', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
firefoxRemoteDebuggingPort: 9222,
|
||||
});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
firefoxRemoteDebuggingPort: 9222,
|
||||
firefoxArgs: expect.arrayContaining([`--remote-debugging-port=9222`]),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dataPersistence', () => {
|
||||
it('should default to "none"', async () => {
|
||||
const actual = await resolveRunOptions({});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
dataPersistence: 'none',
|
||||
});
|
||||
});
|
||||
|
||||
it('should use a temporary directory when set to "none"', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
dataPersistence: 'none',
|
||||
});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
dataPersistence: 'none',
|
||||
dataDir: expect.stringContaining(join(tmpdir(), 'wxt-runner-')),
|
||||
chromiumArgs: expect.arrayContaining([
|
||||
expect.stringContaining(
|
||||
`--user-data-dir=${join(tmpdir(), 'wxt-runner-')}`,
|
||||
),
|
||||
]),
|
||||
firefoxArgs: expect.arrayContaining([
|
||||
expect.stringContaining(join(tmpdir(), 'wxt-runner-')),
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should use a directory in the current working directory when set to "project"', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
dataPersistence: 'project',
|
||||
});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
dataPersistence: 'project',
|
||||
dataDir: expect.stringContaining(join(process.cwd(), '.wxt-runner')),
|
||||
chromiumArgs: expect.arrayContaining([
|
||||
expect.stringContaining(
|
||||
`--user-data-dir=${join(process.cwd(), '.wxt-runner')}`,
|
||||
),
|
||||
]),
|
||||
firefoxArgs: expect.arrayContaining([
|
||||
expect.stringContaining(join(process.cwd(), '.wxt-runner')),
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should use a directory in the user\'s home directory when set to "user"', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
dataPersistence: 'user',
|
||||
});
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
dataPersistence: 'user',
|
||||
dataDir: expect.stringContaining(join(homedir(), '.wxt-runner')),
|
||||
chromiumArgs: expect.arrayContaining([
|
||||
expect.stringContaining(
|
||||
`--user-data-dir=${join(homedir(), '.wxt-runner')}`,
|
||||
),
|
||||
]),
|
||||
firefoxArgs: expect.arrayContaining([
|
||||
expect.stringContaining(join(homedir(), '.wxt-runner')),
|
||||
]),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import { openWebSocket } from './web-socket';
|
||||
import { debug } from './debug';
|
||||
|
||||
const debugBidi = debug.scoped('bidi');
|
||||
|
||||
export interface BidiConnection extends Disposable {
|
||||
send<T>(method: string, params: any, timeout?: number): Promise<T>;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export async function createBidiConnection(
|
||||
baseUrl: string,
|
||||
): Promise<BidiConnection> {
|
||||
const url = new URL('/session', baseUrl);
|
||||
debugBidi('Connecting to BiDi server @', url.href);
|
||||
|
||||
const webSocket = await openWebSocket(url.href);
|
||||
debugBidi('Connected');
|
||||
|
||||
let requestId = 0;
|
||||
|
||||
return {
|
||||
send(method, params, timeout = 10e3) {
|
||||
const id = ++requestId;
|
||||
const command = { id, method, params };
|
||||
debugBidi('Sending command:', command);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const cleanup = () => {
|
||||
webSocket.removeEventListener('message', onMessage);
|
||||
webSocket.removeEventListener('error', onError);
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
cleanup();
|
||||
reject(
|
||||
new Error(
|
||||
`Timed out after ${timeout}ms waiting for ${method} response`,
|
||||
),
|
||||
);
|
||||
}, timeout);
|
||||
|
||||
const onMessage = (event: MessageEvent) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.id === id) {
|
||||
debugBidi('Received response:', data);
|
||||
cleanup();
|
||||
if (data.type === 'success') resolve(data.result);
|
||||
else reject(Error(data.message, { cause: data }));
|
||||
}
|
||||
};
|
||||
const onError = (error: any) => {
|
||||
cleanup();
|
||||
reject(new Error('Error sending request', { cause: error }));
|
||||
};
|
||||
|
||||
webSocket.addEventListener('message', onMessage);
|
||||
webSocket.addEventListener('error', onError);
|
||||
|
||||
webSocket.send(JSON.stringify(command));
|
||||
});
|
||||
},
|
||||
|
||||
close() {
|
||||
debugBidi('Closing connection...');
|
||||
webSocket.close();
|
||||
debugBidi('Closed connection');
|
||||
},
|
||||
[Symbol.dispose]() {
|
||||
debugBidi('Disposing connection...');
|
||||
webSocket.close();
|
||||
debugBidi('Disposed connection');
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
export type BrowserPlatform = 'windows' | 'mac' | 'linux';
|
||||
|
||||
export type KnownTarget =
|
||||
| 'arc'
|
||||
| 'chromium'
|
||||
| 'chrome'
|
||||
| 'chrome-beta'
|
||||
| 'chrome-dev'
|
||||
| 'chrome-canary'
|
||||
| 'dia'
|
||||
| 'edge'
|
||||
| 'edge-beta'
|
||||
| 'edge-dev'
|
||||
| 'edge-canary'
|
||||
| 'firefox'
|
||||
| 'firefox-nightly'
|
||||
| 'firefox-developer-edition'
|
||||
| 'zen';
|
||||
|
||||
export const KNOWN_BROWSER_PATHS: Record<
|
||||
KnownTarget,
|
||||
Record<BrowserPlatform, string[]>
|
||||
> = {
|
||||
// Chromium based targets
|
||||
|
||||
arc: {
|
||||
mac: ['/Applications/Arc.app/Contents/MacOS/Arc'],
|
||||
linux: [],
|
||||
windows: [],
|
||||
},
|
||||
chromium: {
|
||||
mac: [],
|
||||
linux: [
|
||||
// Arch
|
||||
'/usr/bin/chromium',
|
||||
],
|
||||
windows: [],
|
||||
},
|
||||
chrome: {
|
||||
mac: [
|
||||
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||||
'/Applications/Chrome.app/Contents/MacOS/Google Chrome',
|
||||
],
|
||||
linux: [],
|
||||
windows: ['C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'],
|
||||
},
|
||||
'chrome-beta': {
|
||||
mac: [],
|
||||
linux: [],
|
||||
windows: [],
|
||||
},
|
||||
'chrome-canary': {
|
||||
mac: [
|
||||
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
||||
'/Applications/Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
||||
],
|
||||
linux: [],
|
||||
windows: [],
|
||||
},
|
||||
'chrome-dev': {
|
||||
mac: [],
|
||||
linux: [],
|
||||
windows: [],
|
||||
},
|
||||
dia: {
|
||||
mac: ['/Applications/Dia.app/Contents/MacOS/Dia'],
|
||||
linux: [],
|
||||
windows: [],
|
||||
},
|
||||
edge: {
|
||||
mac: [],
|
||||
linux: [],
|
||||
windows: [],
|
||||
},
|
||||
'edge-beta': {
|
||||
mac: [],
|
||||
linux: [],
|
||||
windows: [],
|
||||
},
|
||||
'edge-canary': {
|
||||
mac: [],
|
||||
linux: [],
|
||||
windows: [],
|
||||
},
|
||||
'edge-dev': {
|
||||
mac: [],
|
||||
linux: [],
|
||||
windows: [],
|
||||
},
|
||||
|
||||
// Firefox based targets
|
||||
|
||||
firefox: {
|
||||
mac: ['/Applications/Firefox.app/Contents/MacOS/firefox'],
|
||||
linux: [
|
||||
// Arch
|
||||
'/usr/bin/firefox',
|
||||
],
|
||||
windows: [],
|
||||
},
|
||||
'firefox-nightly': {
|
||||
mac: ['/Applications/Firefox Nightly.app/Contents/MacOS/firefox'],
|
||||
linux: [],
|
||||
windows: ['C:\\Program Files\\Firefox Nightly\\firefox.exe'],
|
||||
},
|
||||
'firefox-developer-edition': {
|
||||
mac: ['/Applications/Firefox Developer Edition.app/Contents/MacOS/firefox'],
|
||||
linux: [
|
||||
// Arch
|
||||
'/usr/bin/firefox-developer-edition',
|
||||
],
|
||||
windows: [],
|
||||
},
|
||||
zen: {
|
||||
mac: [
|
||||
'/Applications/Zen Browser.app/Contents/MacOS/zen',
|
||||
// Homebrew Cask
|
||||
// https://github.com/Homebrew/homebrew-cask/blob/main/Casks/z/zen.rb#L23C13-L23C19
|
||||
'/Applications/Zen.app/Contents/MacOS/zen',
|
||||
],
|
||||
linux: [],
|
||||
windows: [],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* When targeting a browser, this map contains the other targets to fall back on when a binary could not be found for the primary target.
|
||||
*/
|
||||
export const FALLBACK_TARGETS: Partial<Record<KnownTarget, KnownTarget[]>> = {
|
||||
chrome: [
|
||||
'arc',
|
||||
'chromium',
|
||||
'chrome-canary',
|
||||
'chrome-beta',
|
||||
'chrome-dev',
|
||||
'dia',
|
||||
'edge',
|
||||
'edge-canary',
|
||||
'edge-beta',
|
||||
'edge-dev',
|
||||
],
|
||||
firefox: ['firefox-developer-edition', 'firefox-nightly', 'zen'],
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { ChildProcess } from 'node:child_process';
|
||||
import type { Readable, Writable } from 'node:stream';
|
||||
import { debug } from './debug';
|
||||
|
||||
const debugCdp = debug.scoped('cdp');
|
||||
|
||||
export interface CDPConnection extends Disposable {
|
||||
send<T>(method: string, params: any, timeout?: number): Promise<T>;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export function createCdpConnection(
|
||||
browserProcess: ChildProcess,
|
||||
): CDPConnection {
|
||||
const inputStream = browserProcess.stdio[3] as Writable;
|
||||
const outputStream = browserProcess.stdio[4] as Readable;
|
||||
|
||||
let requestId = 0;
|
||||
|
||||
return {
|
||||
send(method, params, timeout = 10e3) {
|
||||
const id = ++requestId;
|
||||
const command = { id, method, params };
|
||||
debugCdp('Sending command:', command);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
reject(new Error(`CDP command timed out: ${method}`));
|
||||
}, timeout);
|
||||
|
||||
const onData = (data: Buffer) => {
|
||||
// Trim the trailing null character
|
||||
const text = data.toString().slice(0, -1);
|
||||
const res = JSON.parse(text);
|
||||
|
||||
if (res.id !== id) return;
|
||||
|
||||
debugCdp('Received response:', res);
|
||||
clearTimeout(timer);
|
||||
outputStream.removeListener('data', onData);
|
||||
|
||||
if ('error' in res) {
|
||||
reject(new Error(res.error.message, { cause: res.error }));
|
||||
} else {
|
||||
resolve(res.result);
|
||||
}
|
||||
};
|
||||
|
||||
outputStream.addListener('data', onData);
|
||||
|
||||
inputStream.write(JSON.stringify(command) + '\0');
|
||||
});
|
||||
},
|
||||
close() {},
|
||||
[Symbol.dispose]() {},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export interface Debug {
|
||||
(...args: any[]): void;
|
||||
scoped: (scope: string) => Debug;
|
||||
}
|
||||
|
||||
function createDebug(scopes: string[]): Debug {
|
||||
const debug = (...args: any[]) => {
|
||||
const scope = scopes.join(':');
|
||||
if (
|
||||
process.env.DEBUG === '1' ||
|
||||
process.env.DEBUG === 'true' ||
|
||||
scope.startsWith(process.env.DEBUG ?? '@NOT')
|
||||
) {
|
||||
const params = scope ? [`\x1b[31m${scope}\x1b[0m`, ...args] : args;
|
||||
console.log(...params);
|
||||
}
|
||||
};
|
||||
|
||||
debug.scoped = (scope: string) => createDebug([...scopes, scope]);
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
||||
export const debug = createDebug(['@wxt-dev/runner']);
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './run';
|
||||
export * from './options';
|
||||
export * from './install';
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { ChildProcess } from 'node:child_process';
|
||||
import { createBidiConnection } from './bidi';
|
||||
import { createCdpConnection } from './cdp';
|
||||
|
||||
/**
|
||||
* Install an extension to an already running instance of Firefox.
|
||||
* @param debuggerUrl The URL of the Firefox BiDi server (ex: `ws://127.0.0.1:45912`).
|
||||
* @param extensionDir Absolute path to the directory containing the extension to be installed.
|
||||
*/
|
||||
export async function installFirefox(
|
||||
debuggerUrl: string,
|
||||
extensionDir: string,
|
||||
): Promise<BidiWebExtensionInstallResponse> {
|
||||
using bidi = await createBidiConnection(debuggerUrl);
|
||||
|
||||
// Start a session
|
||||
await bidi.send<unknown>('session.new', { capabilities: {} });
|
||||
|
||||
// Install the extension
|
||||
return await bidi.send<BidiWebExtensionInstallResponse>(
|
||||
'webExtension.install',
|
||||
{
|
||||
extensionData: {
|
||||
type: 'path',
|
||||
path: extensionDir,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export type BidiWebExtensionInstallResponse = {
|
||||
extension: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a child process of Chrome, install an extension. The process must be started with the following flags:
|
||||
*
|
||||
* - `--remote-debugging-pipe`
|
||||
* - `--user-data-dir=...`
|
||||
* - `--enable-unsafe-extension-debugging`
|
||||
*
|
||||
* Otherwise it the CDP doesn't have permission to install extensions.
|
||||
*/
|
||||
export async function installChromium(
|
||||
browserProcess: ChildProcess,
|
||||
extensionDir: string,
|
||||
): Promise<CdpExtensionsLoadUnpackedResponse> {
|
||||
using cdp = createCdpConnection(browserProcess);
|
||||
return await cdp.send<CdpExtensionsLoadUnpackedResponse>(
|
||||
'Extensions.loadUnpacked',
|
||||
{
|
||||
path: extensionDir,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export type CdpExtensionsLoadUnpackedResponse = {
|
||||
id: string;
|
||||
};
|
||||
@@ -0,0 +1,286 @@
|
||||
import {
|
||||
FALLBACK_TARGETS,
|
||||
KNOWN_BROWSER_PATHS,
|
||||
KnownTarget,
|
||||
type BrowserPlatform,
|
||||
} from './browser-paths';
|
||||
import { resolve, join } from 'node:path';
|
||||
import { homedir, tmpdir } from 'node:os';
|
||||
import { debug } from './debug';
|
||||
import { mkdtemp, open } from 'node:fs/promises';
|
||||
|
||||
const debugOptions = debug.scoped('options');
|
||||
|
||||
export type UnknownTarget = string & {};
|
||||
export type Target = KnownTarget | UnknownTarget;
|
||||
|
||||
export type RunOptions = {
|
||||
/** Paths to binaries to use for each target. */
|
||||
browserBinaries?: Record<string, string>;
|
||||
/** Customize the arguments passed to the chromium binary. Conflicting arguments with required ones to install extensions are ignored. */
|
||||
chromiumArgs?: string[];
|
||||
/** Control how data is persisted between launches. Either save data at a user level, project level, or don't persist data at all. Defaults to `project`. */
|
||||
dataPersistence?: 'user' | 'project' | 'none';
|
||||
/** Customize where your profile's data is stored when using `dataPersistence: 'project'`. Can be absolute or relative to the current working directory. */
|
||||
projectDataDir?: string;
|
||||
/** Customize the port Chrome's debugger is listening on. Defaults to a random open port. */
|
||||
chromiumRemoteDebuggingPort?: number;
|
||||
/** Directory where the extension will be installed from. Should contain a `manifest.json` file. Can be relative to the current working directory. Defaults to the current working directory. */
|
||||
extensionDir?: string;
|
||||
/** Customize the arguments passed to the firefox binary. Conflicting arguments with required ones to install extensions are ignored. */
|
||||
firefoxArgs?: string[];
|
||||
/** Customize the port Firefox's debugger is listening on. Defaults to a random open port. */
|
||||
firefoxRemoteDebuggingPort?: number;
|
||||
/** Specify the browser to open. Defaults to `"chrome"`, but you can pass any string. */
|
||||
target?: Target;
|
||||
};
|
||||
|
||||
export type ResolvedRunOptions = {
|
||||
/** Absolute path to the browser binary. */
|
||||
browserBinary: string;
|
||||
chromiumArgs: string[];
|
||||
chromiumRemoteDebuggingPort: number;
|
||||
/** Absolute path to the directory where browser data will be stored. */
|
||||
dataDir: string;
|
||||
dataPersistence: 'user' | 'project' | 'none';
|
||||
/** Absolute path to the extension directory. */
|
||||
extensionDir: string;
|
||||
firefoxArgs: string[];
|
||||
firefoxRemoteDebuggingPort: number;
|
||||
target: string;
|
||||
};
|
||||
|
||||
export async function resolveRunOptions(
|
||||
options: RunOptions | undefined,
|
||||
): Promise<ResolvedRunOptions> {
|
||||
debugOptions('User options:', options);
|
||||
|
||||
const target = options?.target || 'chrome';
|
||||
|
||||
const _browserBinary =
|
||||
options?.browserBinaries?.[target] ?? (await findBrowserBinary(target));
|
||||
if (!_browserBinary)
|
||||
throw Error(
|
||||
`Could not find "${target}" binary.\n\nIf it is installed in a custom location, you can specify the path with the browserPaths option.`,
|
||||
);
|
||||
|
||||
// Denormalize the path so it uses the correct path separator for the OS
|
||||
const browserBinary = resolve(_browserBinary);
|
||||
|
||||
const chromiumRemoteDebuggingPort = options?.chromiumRemoteDebuggingPort ?? 0;
|
||||
const firefoxRemoteDebuggingPort = options?.firefoxRemoteDebuggingPort ?? 0;
|
||||
const dataPersistence = options?.dataPersistence ?? 'none';
|
||||
const dataDir =
|
||||
dataPersistence === 'user'
|
||||
? join(homedir(), '.wxt-runner', target)
|
||||
: dataPersistence === 'project'
|
||||
? options?.projectDataDir
|
||||
? resolve(options.projectDataDir)
|
||||
: resolve('.wxt-runner', target)
|
||||
: dataPersistence === 'none'
|
||||
? await mkdtemp(join(tmpdir(), 'wxt-runner-'))
|
||||
: resolve(dataPersistence);
|
||||
|
||||
const resolved: ResolvedRunOptions = {
|
||||
browserBinary,
|
||||
chromiumArgs: resolveChromiumArgs(
|
||||
options?.chromiumArgs,
|
||||
chromiumRemoteDebuggingPort,
|
||||
dataDir,
|
||||
),
|
||||
dataDir,
|
||||
dataPersistence,
|
||||
chromiumRemoteDebuggingPort,
|
||||
extensionDir: resolve(options?.extensionDir ?? '.'),
|
||||
firefoxArgs: resolveFirefoxArgs(
|
||||
options?.firefoxArgs,
|
||||
firefoxRemoteDebuggingPort,
|
||||
dataDir,
|
||||
),
|
||||
firefoxRemoteDebuggingPort,
|
||||
target,
|
||||
};
|
||||
debugOptions('Resolved options:', resolved);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
async function findBrowserBinary(target: string): Promise<string | undefined> {
|
||||
const targets = new Set<KnownTarget>([target as KnownTarget]);
|
||||
FALLBACK_TARGETS[target as KnownTarget]?.forEach((fallback) =>
|
||||
targets.add(fallback),
|
||||
);
|
||||
const platform = getPlatform();
|
||||
|
||||
for (const target of targets) {
|
||||
const potentialPaths = KNOWN_BROWSER_PATHS[target]?.[platform] ?? [];
|
||||
for (const path of potentialPaths) {
|
||||
if (await exists(path)) return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getPlatform(): BrowserPlatform {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
return 'windows';
|
||||
case 'darwin':
|
||||
return 'mac';
|
||||
default:
|
||||
return 'linux';
|
||||
}
|
||||
}
|
||||
|
||||
function resolveChromiumArgs(
|
||||
userArgs: string[] | undefined,
|
||||
chromiumRemoteDebuggingPort: ResolvedRunOptions['chromiumRemoteDebuggingPort'],
|
||||
dataDir: string,
|
||||
): string[] {
|
||||
return deduplicateArgs(
|
||||
[
|
||||
// Limit features to improve performance
|
||||
...CHROME_LAUNCHER_DEFAULT_FLAGS,
|
||||
// Enable debugging
|
||||
`--remote-debugging-port=${chromiumRemoteDebuggingPort}`,
|
||||
// Required for installing extensions
|
||||
`--remote-debugging-pipe`,
|
||||
`--user-data-dir=${dataDir}`,
|
||||
`--enable-unsafe-extension-debugging`,
|
||||
],
|
||||
userArgs,
|
||||
{
|
||||
'--remote-debugging-port':
|
||||
'\x1b[1m\x1b[33mCustom Chromium --remote-debugging-port argument ignored.\x1b[0m Use \x1b[36mchromiumRemoteDebuggingPort\x1b[0m option instead.',
|
||||
'--user-data-dir':
|
||||
'\x1b[1m\x1b[33mCustom Chromium --user-data-dir argument ignored.\x1b[0m Use \x1b[36mdataPersistence\x1b[0m option instead.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function resolveFirefoxArgs(
|
||||
userArgs: string[] | undefined,
|
||||
firefoxRemoteDebuggingPort: ResolvedRunOptions['firefoxRemoteDebuggingPort'],
|
||||
dataDir: string,
|
||||
): string[] {
|
||||
return deduplicateArgs(
|
||||
[
|
||||
// Allows opening multiple instances of Firefox at the same time
|
||||
`--new-instance`,
|
||||
`--no-remote`,
|
||||
`--profile`,
|
||||
dataDir,
|
||||
// Required for installing extensions
|
||||
`--remote-debugging-port=${firefoxRemoteDebuggingPort}`,
|
||||
// Default URL to start with
|
||||
`about:debugging#/runtime/this-firefox`,
|
||||
],
|
||||
userArgs,
|
||||
{
|
||||
'--remote-debugging-port':
|
||||
'\x1b[1m\x1b[33mCustom Firefox --remote-debugging-port argument ignored.\x1b[0m Use \x1b[36mfirefoxDebuggerPort\x1b[0m option instead.',
|
||||
'--profile':
|
||||
'\x1b[1m\x1b[33mCustom Firefox --profile argument ignored.\x1b[0m Use \x1b[36mdataPersistence\x1b[0m option instead.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function deduplicateArgs(
|
||||
requiredArgs: string[],
|
||||
userArgs: string[] | undefined,
|
||||
warnings: Record<string, string>,
|
||||
): string[] {
|
||||
const getKey = (arg: string) => {
|
||||
return arg.startsWith('--') ? arg.split('=')[0] : arg;
|
||||
};
|
||||
const alreadyAdded = new Set<string>(requiredArgs.map(getKey));
|
||||
|
||||
const args = [...requiredArgs];
|
||||
userArgs?.forEach((arg) => {
|
||||
const key = getKey(arg);
|
||||
if (alreadyAdded.has(key)) {
|
||||
if (warnings[key]) console.warn(`[@wxt-dev/runner] ${warnings[key]}`);
|
||||
} else {
|
||||
alreadyAdded.add(key);
|
||||
args.push(arg);
|
||||
}
|
||||
});
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
async function exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await open(path, 'r');
|
||||
return true;
|
||||
} catch (err) {
|
||||
// @ts-expect-error: Unknown error type
|
||||
if (err?.code === 'ENOENT') return false;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from https://github.com/GoogleChrome/chrome-launcher/blob/main/src/flags.ts
|
||||
* with some flags commented out. Run tests after updating to compare.
|
||||
*/
|
||||
const CHROME_LAUNCHER_DEFAULT_FLAGS = [
|
||||
'--disable-features=' +
|
||||
[
|
||||
// Disable built-in Google Translate service
|
||||
'Translate',
|
||||
// Disable the Chrome Optimization Guide background networking
|
||||
'OptimizationHints',
|
||||
// Disable the Chrome Media Router (cast target discovery) background networking
|
||||
'MediaRouter',
|
||||
/// Avoid the startup dialog for _Do you want the application “Chromium.app” to accept incoming network connections?_. This is a sub-component of the MediaRouter.
|
||||
'DialMediaRouteProvider',
|
||||
// Disable the feature of: Calculate window occlusion on Windows will be used in the future to throttle and potentially unload foreground tabs in occluded windows.
|
||||
'CalculateNativeWinOcclusion',
|
||||
// Disables the Discover feed on NTP
|
||||
'InterestFeedContentSuggestions',
|
||||
// Don't update the CT lists
|
||||
'CertificateTransparencyComponentUpdater',
|
||||
// Disables autofill server communication. This feature isn't disabled via other 'parent' flags.
|
||||
'AutofillServerCommunication',
|
||||
// Disables "Enhanced ad privacy in Chrome" dialog (though as of 2024-03-20 it shouldn't show up if the profile has no stored country).
|
||||
'PrivacySandboxSettings4',
|
||||
].join(','),
|
||||
|
||||
// Disable some extensions that aren't affected by --disable-extensions
|
||||
'--disable-component-extensions-with-background-pages',
|
||||
// Disable various background network services, including extension updating,
|
||||
// safe browsing service, upgrade detector, translate, UMA
|
||||
'--disable-background-networking',
|
||||
// Don't update the browser 'components' listed at chrome://components/
|
||||
'--disable-component-update',
|
||||
// Disables client-side phishing detection.
|
||||
'--disable-client-side-phishing-detection',
|
||||
// Disable syncing to a Google account
|
||||
'--disable-sync',
|
||||
// Disable reporting to UMA, but allows for collection
|
||||
'--metrics-recording-only',
|
||||
// Disable installation of default apps on first run
|
||||
'--disable-default-apps',
|
||||
// Disable the default browser check, do not prompt to set it as such
|
||||
'--no-default-browser-check',
|
||||
// Skip first run wizards
|
||||
'--no-first-run',
|
||||
// Disable task throttling of timer tasks from background pages.
|
||||
'--disable-background-timer-throttling',
|
||||
// Disable the default throttling of IPC between renderer & browser processes.
|
||||
'--disable-ipc-flooding-protection',
|
||||
// Avoid potential instability of using Gnome Keyring or KDE wallet. crbug.com/571003 crbug.com/991424
|
||||
'--password-store=basic',
|
||||
// Use mock keychain on Mac to prevent blocking permissions dialogs
|
||||
'--use-mock-keychain',
|
||||
// Disable background tracing (aka slow reports & deep reports) to avoid 'Tracing already started'
|
||||
'--force-fieldtrials=*BackgroundTracing/default/',
|
||||
|
||||
// Suppresses hang monitor dialogs in renderer processes. This flag may allow slow unload handlers on a page to prevent the tab from closing.
|
||||
'--disable-hang-monitor',
|
||||
// Reloading a page that came from a POST normally prompts the user.
|
||||
'--disable-prompt-on-repost',
|
||||
// Disables Domain Reliability Monitoring, which tracks whether the browser has difficulty contacting Google-owned sites and uploads reports to Google.
|
||||
'--disable-domain-reliability',
|
||||
// Disable the in-product Help (IPH) system.
|
||||
'--propagate-iph-for-testing',
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
export function promiseWithResolvers<T>(): {
|
||||
promise: Promise<T>;
|
||||
resolve: (value: T) => void;
|
||||
reject: (error: unknown) => void;
|
||||
} {
|
||||
let resolve: (value: T) => void;
|
||||
let reject: (error: unknown) => void;
|
||||
|
||||
const promise = new Promise<T>((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
|
||||
return {
|
||||
promise,
|
||||
resolve: resolve!,
|
||||
reject: reject!,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import { debug } from './debug';
|
||||
import {
|
||||
resolveRunOptions,
|
||||
type ResolvedRunOptions,
|
||||
type RunOptions,
|
||||
} from './options';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { installChromium, installFirefox } from './install';
|
||||
import { promiseWithResolvers } from './promises';
|
||||
|
||||
const debugFirefox = debug.scoped('firefox');
|
||||
const debugChrome = debug.scoped('chrome');
|
||||
|
||||
export interface Runner {
|
||||
stop(): void;
|
||||
}
|
||||
|
||||
export async function run(options: RunOptions): Promise<Runner> {
|
||||
const resolvedOptions = await resolveRunOptions(options);
|
||||
|
||||
if (
|
||||
resolvedOptions.target.includes('firefox') ||
|
||||
resolvedOptions.target.includes('zen')
|
||||
) {
|
||||
return runFirefox(resolvedOptions);
|
||||
} else {
|
||||
return runChromium(resolvedOptions);
|
||||
}
|
||||
}
|
||||
|
||||
async function runFirefox(options: ResolvedRunOptions): Promise<Runner> {
|
||||
const urlRes = promiseWithResolvers<string>();
|
||||
const urlTimeout = setTimeout(() => {
|
||||
urlRes.reject(Error('Timed out after 10s waiting for the browser to open'));
|
||||
}, 10e3);
|
||||
|
||||
// Firefox notifies the user if an instance is already running, so we don't add any logs for it.
|
||||
|
||||
const browserProcess = spawn(
|
||||
`"${options.browserBinary}"`,
|
||||
options.firefoxArgs,
|
||||
{
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
shell: true,
|
||||
},
|
||||
);
|
||||
const debugFirefoxStderr = debugFirefox.scoped('stderr');
|
||||
browserProcess.stderr.on('data', (data: string) => {
|
||||
const message = data.toString().trim();
|
||||
debugFirefoxStderr(message);
|
||||
|
||||
if (message.startsWith('WebDriver BiDi listening on ws://')) {
|
||||
clearTimeout(urlTimeout);
|
||||
urlRes.resolve(message.slice(28));
|
||||
}
|
||||
});
|
||||
const debugFirefoxStdout = debugFirefox.scoped('stdout');
|
||||
browserProcess.stdout.on('data', (data: string) => {
|
||||
const message = data.toString().trim();
|
||||
debugFirefoxStdout(message);
|
||||
});
|
||||
|
||||
const baseUrl = await urlRes.promise;
|
||||
await installFirefox(baseUrl, options.extensionDir);
|
||||
|
||||
return {
|
||||
stop() {
|
||||
browserProcess.kill('SIGINT');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function runChromium(options: ResolvedRunOptions): Promise<Runner> {
|
||||
const browserProcess = spawn(
|
||||
`"${options.browserBinary}"`,
|
||||
options.chromiumArgs,
|
||||
{
|
||||
stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'],
|
||||
shell: true,
|
||||
},
|
||||
);
|
||||
|
||||
const opened = promiseWithResolvers<void>();
|
||||
const openedTimeout = setTimeout(() => {
|
||||
opened.reject(Error('Timed out after 10s waiting for browser to open.'));
|
||||
}, 10e3);
|
||||
|
||||
const debugChromeStderr = debugChrome.scoped('stderr');
|
||||
browserProcess.stderr!.on('data', (data: string) => {
|
||||
const message = data.toString().trim();
|
||||
debugChromeStderr(message);
|
||||
|
||||
// This message signifies Chrome started up correctly.
|
||||
if (message.startsWith('DevTools listening on')) {
|
||||
clearTimeout(openedTimeout);
|
||||
opened.resolve();
|
||||
}
|
||||
});
|
||||
const debugChromeStdout = debugChrome.scoped('stdout');
|
||||
browserProcess.stdout!.on('data', (data: string) => {
|
||||
const message = data.toString().trim();
|
||||
debugChromeStdout(message);
|
||||
|
||||
// This message signifies Chrome was already open, and thus we couldn't open the required new instance.
|
||||
if (message === 'Opening in existing browser session.') {
|
||||
clearTimeout(openedTimeout);
|
||||
opened.reject(
|
||||
Error(
|
||||
'An instance of the browser is already running. Close it and try again.',
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the browser to open before proceeding.
|
||||
await opened.promise;
|
||||
|
||||
await installChromium(browserProcess, options.extensionDir);
|
||||
|
||||
return {
|
||||
stop() {
|
||||
browserProcess.kill('SIGINT');
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
export function openWebSocket(url: string): Promise<WebSocket> {
|
||||
if (typeof WebSocket === 'undefined') {
|
||||
throw new Error(
|
||||
'To open Firefox, your JS runtime must support the standard WebSocket API (NodeJS >=22.4.0, Bun, etc).',
|
||||
);
|
||||
}
|
||||
|
||||
return new Promise<WebSocket>((resolve, reject) => {
|
||||
const webSocket = new WebSocket(url);
|
||||
|
||||
const cleanup = () => {
|
||||
webSocket.removeEventListener('open', onOpen);
|
||||
webSocket.removeEventListener('error', onError);
|
||||
webSocket.removeEventListener('close', onClose);
|
||||
};
|
||||
const onOpen = async () => {
|
||||
cleanup();
|
||||
resolve(webSocket);
|
||||
};
|
||||
const onClose = (event: CloseEvent) => {
|
||||
cleanup();
|
||||
reject(
|
||||
new Error(
|
||||
`Connection closed: code=${event.code}, reason=${event.reason}`,
|
||||
),
|
||||
);
|
||||
};
|
||||
const onError = (error: any) => {
|
||||
cleanup();
|
||||
reject(new Error('Error connecting to WebSocket', { cause: error }));
|
||||
};
|
||||
|
||||
webSocket.addEventListener('open', onOpen);
|
||||
webSocket.addEventListener('error', onError);
|
||||
webSocket.addEventListener('close', onClose);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { defineProject } from 'vitest/config';
|
||||
|
||||
export default defineProject({
|
||||
test: {
|
||||
mockReset: true,
|
||||
restoreMocks: true,
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,40 @@
|
||||
# Changelog
|
||||
|
||||
## v1.2.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.2.0...storage-v1.2.1)
|
||||
|
||||
## v1.2.0
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.1.1...storage-v1.2.0)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **storage:** Add `onMigrationComplete` callback ([#1514](https://github.com/wxt-dev/wxt/pull/1514))
|
||||
- **storage:** Add `debug` option to enable migration logs ([#1513](https://github.com/wxt-dev/wxt/pull/1513))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Fix typescript error on `defineItem` fallback ([#1601](https://github.com/wxt-dev/wxt/pull/1601))
|
||||
- Use `@wxt-dev/browser` instead of `@types/chrome` ([#1645](https://github.com/wxt-dev/wxt/pull/1645))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps:** Update all dependencies ([#1568](https://github.com/wxt-dev/wxt/pull/1568))
|
||||
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
|
||||
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
|
||||
- Change browser workspace dependency to `^` ([c7335add](https://github.com/wxt-dev/wxt/commit/c7335add))
|
||||
- **deps:** Upgrade oxlint from 0.16.8 to 1.14.0 ([a01928e0](https://github.com/wxt-dev/wxt/commit/a01928e0))
|
||||
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
|
||||
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
|
||||
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Anh71me ([@iyume](https://github.com/iyume))
|
||||
- Ergou <ma2808203259@hotmail.com>
|
||||
|
||||
## v1.1.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.1.0...storage-v1.1.1)
|
||||
@@ -69,4 +104,4 @@ It's still shipped inside WXT and accessible via `wxt/storage`, but now:
|
||||
|
||||
[Read the docs](https://wxt.dev/storage.html) for more details.
|
||||
|
||||
> This is apart of the v1.0 initiative for WXT.
|
||||
> This is apart of the v1.0 initiative for WXT.
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/storage",
|
||||
"description": "Web extension storage API provided by WXT, supports all browsers.",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.1",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -30,18 +30,18 @@
|
||||
"test": "buildc --deps-only -- vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"async-mutex": "catalog:",
|
||||
"dequal": "catalog:"
|
||||
"@wxt-dev/browser": "workspace:^",
|
||||
"async-mutex": "^0.5.0",
|
||||
"dequal": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@types/chrome": "catalog:",
|
||||
"@webext-core/fake-browser": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
"@aklinker1/check": "^2.1.0",
|
||||
"@webext-core/fake-browser": "^1.3.2",
|
||||
"oxlint": "^1.14.0",
|
||||
"publint": "^0.3.12",
|
||||
"typescript": "^5.9.2",
|
||||
"unbuild": "^3.6.1",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { fakeBrowser } from '@webext-core/fake-browser';
|
||||
import { describe, it, expect, beforeEach, vi, expectTypeOf } from 'vitest';
|
||||
import { MigrationError, type WxtStorageItem, storage } from '../index';
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
|
||||
/**
|
||||
* This works because fakeBrowser is synchronous, and is will finish any number of chained
|
||||
@@ -222,7 +223,7 @@ describe('Storage Utils', () => {
|
||||
describe('setMeta', () => {
|
||||
it('should set metadata at key+$', async () => {
|
||||
const existing = { v: 1 };
|
||||
await chrome.storage[storageArea].set({ count$: existing });
|
||||
await browser.storage[storageArea].set({ count$: existing });
|
||||
const newValues = {
|
||||
date: Date.now(),
|
||||
};
|
||||
@@ -238,7 +239,7 @@ describe('Storage Utils', () => {
|
||||
'should remove any properties set to %s',
|
||||
async (version) => {
|
||||
const existing = { v: 1 };
|
||||
await chrome.storage[storageArea].set({ count$: existing });
|
||||
await browser.storage[storageArea].set({ count$: existing });
|
||||
const expected = {};
|
||||
|
||||
await storage.setMeta(`${storageArea}:count`, { v: version });
|
||||
@@ -1265,7 +1266,7 @@ describe('Storage Utils', () => {
|
||||
|
||||
await item.removeValue();
|
||||
// Make sure it's actually blank before running the test
|
||||
expect(await chrome.storage.local.get()).toEqual({});
|
||||
expect(await browser.storage.local.get()).toEqual({});
|
||||
init.mockClear();
|
||||
|
||||
const [value1, value2] = await Promise.all([
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/// <reference types="chrome" />
|
||||
/**
|
||||
* Simplified storage APIs with support for versioned fields, snapshots, metadata, and item definitions.
|
||||
*
|
||||
@@ -7,13 +6,7 @@
|
||||
*/
|
||||
import { dequal } from 'dequal/lite';
|
||||
import { Mutex } from 'async-mutex';
|
||||
|
||||
const browser: typeof chrome =
|
||||
// @ts-expect-error
|
||||
globalThis.browser?.runtime?.id == null
|
||||
? globalThis.chrome
|
||||
: // @ts-expect-error
|
||||
globalThis.browser;
|
||||
import { browser, type Browser } from '@wxt-dev/browser';
|
||||
|
||||
export const storage = createStorage();
|
||||
|
||||
@@ -897,7 +890,7 @@ export interface WxtStorageItemOptions<T> {
|
||||
}
|
||||
|
||||
export type StorageAreaChanges = {
|
||||
[key: string]: chrome.storage.StorageChange;
|
||||
[key: string]: Browser.storage.StorageChange;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"verbatimModuleSyntax": true,
|
||||
"types": ["chrome"]
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"exclude": ["node_modules/**", "dist/**"]
|
||||
}
|
||||
|
||||
@@ -40,16 +40,16 @@
|
||||
"wxt": ">=0.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"unocss": "catalog:",
|
||||
"@aklinker1/check": "^2.1.0",
|
||||
"oxlint": "^1.14.0",
|
||||
"publint": "^0.3.12",
|
||||
"typescript": "^5.9.2",
|
||||
"unbuild": "^3.6.1",
|
||||
"unocss": "^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"defu": "catalog:",
|
||||
"fast-glob": "catalog:"
|
||||
"defu": "^6.1.4",
|
||||
"fast-glob": "^3.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,12 +45,12 @@
|
||||
"wxt": ">=0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@types/webextension-polyfill": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"webextension-polyfill": "catalog:",
|
||||
"@aklinker1/check": "^2.1.0",
|
||||
"@types/webextension-polyfill": "^0.12.3",
|
||||
"publint": "^0.3.12",
|
||||
"typescript": "^5.9.2",
|
||||
"unbuild": "^3.6.1",
|
||||
"webextension-polyfill": "^0.12.0",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,19 +18,19 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/i18n": "workspace:*",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:"
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"@types/react": "^19.1.12",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@wxt-dev/auto-icons": "workspace:*",
|
||||
"@wxt-dev/unocss": "workspace:*",
|
||||
"sass": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unocss": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vitest-plugin-random-seed": "catalog:",
|
||||
"sass": "^1.92.0",
|
||||
"typescript": "^5.9.2",
|
||||
"unocss": "^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0",
|
||||
"vitest": "^3.2.4",
|
||||
"vitest-plugin-random-seed": "^1.1.1",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"buildc": {
|
||||
|
||||
+131
-2
@@ -1,5 +1,134 @@
|
||||
# Changelog
|
||||
|
||||
## v0.20.11
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.10...wxt-v0.20.11)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Split `wxt/testing` into separate modules to fix issues with `jsdom` and `happy-dom` ([#1844](https://github.com/wxt-dev/wxt/pull/1844))
|
||||
- `input_components` is supported by mv3 ([#1881](https://github.com/wxt-dev/wxt/pull/1881))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps:** Upgrade project subdependencies ([#1882](https://github.com/wxt-dev/wxt/pull/1882))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Qijia Liu <liumeo@pku.edu.cn>
|
||||
- Marcellino Ornelas ([@marcellino-ornelas](https://github.com/marcellino-ornelas))
|
||||
|
||||
## v0.20.10
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.9...wxt-v0.20.10)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps:** Upgrade non-breaking production dependencies ([#1877](https://github.com/wxt-dev/wxt/pull/1877))
|
||||
- **deps:** Upgrade web-ext-run to support CDP ([#1879](https://github.com/wxt-dev/wxt/pull/1879))
|
||||
- **deps:** `publish-browser-extension` upgrade ([#1880](https://github.com/wxt-dev/wxt/pull/1880))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v0.20.9
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.8...wxt-v0.20.9)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **types:** Use TType for DocumentEventMap key in ctx.addEventListener ([#1863](https://github.com/wxt-dev/wxt/pull/1863))
|
||||
- Prevent Unlisted CSS from being excluded in the build when using CSS preprocessor ([#1590](https://github.com/wxt-dev/wxt/pull/1590))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps:** Upgrade oxlint from 0.16.8 to 1.14.0 ([a01928e0](https://github.com/wxt-dev/wxt/commit/a01928e0))
|
||||
- **deps:** Upgrade typescript from 5.8.3 to 5.9.2 ([a6eef643](https://github.com/wxt-dev/wxt/commit/a6eef643))
|
||||
- Upgrade nano-spawn to v1 ([5fefd8e0](https://github.com/wxt-dev/wxt/commit/5fefd8e0))
|
||||
- Upgrade faker to v10 ([984568e0](https://github.com/wxt-dev/wxt/commit/984568e0))
|
||||
- Upgrade dotenv to v17.2.2 ([380a9630](https://github.com/wxt-dev/wxt/commit/380a9630))
|
||||
- Upgrade happy-dom to v18.0.1 ([c1c3d3b7](https://github.com/wxt-dev/wxt/commit/c1c3d3b7))
|
||||
- Create script for managing dependency upgrades ([#1875](https://github.com/wxt-dev/wxt/pull/1875))
|
||||
- **deps:** Upgrade all dev dependencies ([#1876](https://github.com/wxt-dev/wxt/pull/1876))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Kim Gyeongjae ([@PortalCube](https://github.com/PortalCube))
|
||||
- Atomie CHEN <atomic_cwh@163.com>
|
||||
|
||||
## v0.20.8
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.7...wxt-v0.20.8)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Support http server websocket inside containers ([#1707](https://github.com/wxt-dev/wxt/pull/1707))
|
||||
- Recreating keyboardShortcuts interface on file changes. ([#1465](https://github.com/wxt-dev/wxt/pull/1465))
|
||||
- Fix bad regex from #1707 ([#1712](https://github.com/wxt-dev/wxt/pull/1712), [#1707](https://github.com/wxt-dev/wxt/issues/1707))
|
||||
- Fix bundle variable assignment error with `rolldown-vite` ([#1715](https://github.com/wxt-dev/wxt/pull/1715))
|
||||
- Support negation patterns when including/excluding files from ZIP files ([#1517](https://github.com/wxt-dev/wxt/pull/1517))
|
||||
- **rolldown-compat:** Only apply footer to content and unlisted scripts via plugin ([#1793](https://github.com/wxt-dev/wxt/pull/1793))
|
||||
- Init project should allow the .git folder to exist ([#1731](https://github.com/wxt-dev/wxt/pull/1731))
|
||||
- Adding missing chromiumPort parameter in web-ext.ts to enable fixed port debugging ([#1818](https://github.com/wxt-dev/wxt/pull/1818))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Fix API reference sidebar by naming new module ([eb0bffb5](https://github.com/wxt-dev/wxt/commit/eb0bffb5))
|
||||
- Update JSDoc with ways to cancel timeouts and intervals set by content script context ([030f23d2](https://github.com/wxt-dev/wxt/commit/030f23d2))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Fix auto-fixable `markdownlint` errors ([#1710](https://github.com/wxt-dev/wxt/pull/1710))
|
||||
- Remove automd ([7d25110a](https://github.com/wxt-dev/wxt/commit/7d25110a))
|
||||
- Wxt & @wxt-dev/module-vue support Vite 7 ([#1771](https://github.com/wxt-dev/wxt/pull/1771))
|
||||
- Fix type error ([0333ce5c](https://github.com/wxt-dev/wxt/commit/0333ce5c))
|
||||
|
||||
### 🤖 CI
|
||||
|
||||
- Use matrix for test jobs ([#1708](https://github.com/wxt-dev/wxt/pull/1708))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Sylva <sylvain.lavabre@live.fr>
|
||||
- Btea ([@btea](https://github.com/btea))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Ayu ([@ayu-exorcist](https://github.com/ayu-exorcist))
|
||||
- Nishu ([@nishu-murmu](https://github.com/nishu-murmu))
|
||||
|
||||
## v0.20.7
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.6...wxt-v0.20.7)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add `@font-face` to be processed by `splitShadowRootCss` ([#1635](https://github.com/wxt-dev/wxt/pull/1635))
|
||||
- Add DisableLoadExtensionCommandLineSwitch flag to Chromium runner ([#1698](https://github.com/wxt-dev/wxt/pull/1698))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Improve CSS reset inside shadow roots ([da5cd325](https://github.com/wxt-dev/wxt/commit/da5cd325))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
|
||||
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
|
||||
- **deps:** Update all dependencies ([#1648](https://github.com/wxt-dev/wxt/pull/1648))
|
||||
- Change browser workspace dependency to `^` ([c7335add](https://github.com/wxt-dev/wxt/commit/c7335add))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Richard Lee ([@dlackty](https://github.com/dlackty))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Seaders ([@seaders](https://github.com/seaders))
|
||||
|
||||
## v0.20.6
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.5...wxt-v0.20.6)
|
||||
|
||||
## v0.20.5
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.4...wxt-v0.20.5)
|
||||
@@ -33,8 +162,8 @@
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Yunsup Sim <pedogunu@gmail.com>
|
||||
- ТΞNSΛI <tensai@gmx.net>
|
||||
- Yunsup Sim ([@SimYunSup](https://github.com/SimYunSup))
|
||||
- ТΞNSΛI ([@Tensai75](https://github.com/Tensai75))
|
||||
- Nishu ([@nishu-murmu](https://github.com/nishu-murmu))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
|
||||
+18
-44
@@ -1,51 +1,33 @@
|
||||
<!-- DO NOT EDIT, THIS FILE WAS GENERATED BY '../../scripts/generate-readmes.sh' -->
|
||||
<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">
|
||||
<span>WXT</span>
|
||||
</h1>
|
||||
<div align="center">
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</p>
|
||||
# <img align="top" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo"> WXT
|
||||
|
||||
<p align="center">
|
||||
<span>Next-gen framework for developing web extensions.</span>
|
||||
<br/>
|
||||
<span>⚡</span>
|
||||
<br/>
|
||||
<q><i>It's like Nuxt, but for Web Extensions</i></q>
|
||||
</p>
|
||||
[](https://www.npmjs.com/package/wxt)
|
||||
[](https://www.npmjs.com/package/wxt)
|
||||
[](https://github.com/wxt-dev/wxt/blob/main/LICENSE)
|
||||
[](https://codecov.io/github/wxt-dev/wxt)
|
||||
|
||||
<p align="center">
|
||||
<a href="https://wxt.dev/guide/installation.html" target="_blank">Get Started</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>
|
||||
Next-gen framework for developing web extensions.<br/>⚡<br/><q><i>It's like Nuxt, but for Web Extensions</i></q>
|
||||
|
||||
[Get Started](https://wxt.dev/guide/installation.html) •
|
||||
[Configuration](https://wxt.dev/api/config.html) •
|
||||
[Examples](https://wxt.dev/examples.html) •
|
||||
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md) •
|
||||
[Discord](https://discord.gg/ZFsZqGery9)
|
||||
|
||||
</div>
|
||||
|
||||

|
||||
|
||||
## Demo
|
||||
|
||||
https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94
|
||||
<https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84c94>
|
||||
|
||||
## Quick Start
|
||||
|
||||
Bootstrap a new project:
|
||||
|
||||
<!-- automd:pm-x version="latest" name="wxt" args="init" -->
|
||||
|
||||
```sh
|
||||
# npm
|
||||
npx wxt@latest init
|
||||
@@ -57,8 +39,6 @@ pnpm dlx wxt@latest init
|
||||
bunx wxt@latest init
|
||||
```
|
||||
|
||||
<!-- /automd -->
|
||||
|
||||
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
|
||||
|
||||
## Features
|
||||
@@ -80,17 +60,11 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
|
||||
|
||||
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>
|
||||
[](https://github.com/sponsors/wxt-dev)
|
||||
|
||||
## 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" />
|
||||
</a>
|
||||
|
||||
<!-- /automd -->
|
||||
[](https://github.com/wxt-dev/wxt/graphs/contributors)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user