Compare commits
205 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cb851b1738 | |||
| 82825a5b35 | |||
| a63f2d2a33 | |||
| d3983a603f | |||
| 9304319f55 | |||
| e778449377 | |||
| 3ecafbe0f3 | |||
| 9ab345b6dc | |||
| ade1a6b670 | |||
| af840d7169 | |||
| f2ced3f0e4 | |||
| eb2e151520 | |||
| b235c8591e | |||
| 4c11f00176 | |||
| f75d1e1afc | |||
| 4f419da1dc | |||
| d9c25ac4ce | |||
| 8dfedf2a5a | |||
| 19f94e1e6a | |||
| fc269ed78d | |||
| cfc633202b | |||
| 6f1790deef | |||
| 331ce14170 | |||
| a4c6cd1cd0 | |||
| cb0e336812 | |||
| ba7c9cef60 | |||
| 70b30ae431 | |||
| 12c267a5d1 | |||
| 38625a8cd3 | |||
| 700cd8c3e8 | |||
| c272fbe0ae | |||
| f40005da36 | |||
| 34afac0165 | |||
| 7cbe9a477b | |||
| 081d472176 | |||
| d7f8ebec39 | |||
| bde65e2066 | |||
| 4896cd208e | |||
| 42a5b3cd67 | |||
| d60fd2516f | |||
| 6b2c27cf45 | |||
| 1a3f4f64e5 | |||
| 492cbb4644 | |||
| c8e93b15db | |||
| e3f4fe3e13 | |||
| 8cb973cf3c | |||
| 22d1a72752 | |||
| 8f90331229 | |||
| 9a638449d5 | |||
| 9ce9b714f6 | |||
| 22b7aba035 | |||
| 4cbcf282f7 | |||
| d8b1ddfb59 | |||
| 7905cb379b | |||
| 1b2463d52e | |||
| b567f9957a | |||
| c2fb8c786d | |||
| 4320d8e1fa | |||
| 344d14dafc | |||
| 3cb894c4e5 | |||
| dbbe14ddd6 | |||
| 9ee07f431a | |||
| d517e9a0b9 | |||
| d9adbb6f09 | |||
| 6b77277cea | |||
| bad24392fc | |||
| f24e62129b | |||
| f38cb855ea | |||
| 891a0d50a6 | |||
| cfb384e618 | |||
| 6f14aa1c71 | |||
| f771e6a122 | |||
| a0a23947b6 | |||
| 7b6f1dc88b | |||
| 16f9e1705f | |||
| 40e28b702d | |||
| 75f0b843f8 | |||
| 8793385325 | |||
| 4f86143853 | |||
| 498f74b21b | |||
| 300798eb11 | |||
| d482fe6832 | |||
| 955bb20914 | |||
| 22339d261b | |||
| 5d89a40c6d | |||
| c1fe602c8b | |||
| bea5e0da1e | |||
| 7907e56a31 | |||
| 4e43e6a315 | |||
| 78ef08390e | |||
| b5235ed4f0 | |||
| 868a567ebd | |||
| 0797041643 | |||
| 60b22be29d | |||
| 1a817e1367 | |||
| 2be3044f6a | |||
| 59f6f4b926 | |||
| 3d2d36954e | |||
| e44cb3950f | |||
| 06682a6edc | |||
| e6e513a34b | |||
| 13dd3742a8 | |||
| c27278ecfb | |||
| 2f68c6e344 | |||
| a7976d2e63 | |||
| c15167ca86 | |||
| e75e534882 | |||
| b21ec34a65 | |||
| e45b1fb8df | |||
| 0b3c3ab39d | |||
| 8b530bc1c9 | |||
| a2212f7a89 | |||
| e613b54d94 | |||
| d9c140114a | |||
| 9b59f38c0d | |||
| 2a8ec0d786 | |||
| e29fad16ea | |||
| 50d223a30a | |||
| 1dbdf4ea9e | |||
| 09b5a19577 | |||
| b5ea72e214 | |||
| 57e3748d79 | |||
| 155626fbed | |||
| e802aa211f | |||
| 54fe8c6687 | |||
| 5da6e4ff75 | |||
| 4090bb100e | |||
| 54ed99d91d | |||
| 3db3eac62a | |||
| 0315882fa4 | |||
| 9757e2634f | |||
| 42b55f66fb | |||
| e64058bc94 | |||
| 386503cce0 | |||
| 6224ff49f6 | |||
| 6578be00ce | |||
| 2f325d898f | |||
| b039c599c8 | |||
| bec4b73839 | |||
| e67bac0868 | |||
| 24ff92068d | |||
| 6e20036d6c | |||
| b6059cfe4a | |||
| 4a305a2726 | |||
| 4ae6d8135f | |||
| 225a94199c | |||
| 35ffa00bae | |||
| 58e11d3c06 | |||
| fddd7c14ee | |||
| 7d755f215f | |||
| 7e473375e5 | |||
| 08b9ea6e47 | |||
| 245aea4ce8 | |||
| e1e41c20ea | |||
| e16052cdc9 | |||
| de2b38217a | |||
| b2a099689f | |||
| 42c8a02ef9 | |||
| 32058027f3 | |||
| 99fc685e14 | |||
| 414117b9e6 | |||
| 799bd92b28 | |||
| d7cc8dc229 | |||
| f4fa36a0ab | |||
| 4d5eb28cb4 | |||
| a008426317 | |||
| 2a212790b6 | |||
| 09f27a902a | |||
| 6cbbca9e92 | |||
| f1fd8fc38b | |||
| a67e1b81e1 | |||
| 936c5f7fda | |||
| ab3151f924 | |||
| d4fb318381 | |||
| 31a7b5892e | |||
| c469f0e85a | |||
| 355d36a838 | |||
| 43e46477df | |||
| 34a235fc68 | |||
| ce9f9af980 | |||
| 5f799ca23e | |||
| 0159b4315e | |||
| 864d7a0bfd | |||
| fa698266b3 | |||
| ba96bbd8ff | |||
| 20d5e3310a | |||
| 70fdb04db5 | |||
| 65c3c7eba9 | |||
| 98e218042d | |||
| 8a56f73c7c | |||
| ffdd15e275 | |||
| 8f71e174cb | |||
| 6621aaf877 | |||
| c528361d07 | |||
| 5376cfd6a9 | |||
| 76615b9262 | |||
| ce551fbdb0 | |||
| 108e790cf8 | |||
| 0b5424cadb | |||
| 48571f5032 | |||
| c6c9f65040 | |||
| ace6c20164 | |||
| d64b64f39e | |||
| 41a687a63e | |||
| f4f6704510 |
@@ -7,6 +7,7 @@
|
||||
pnpm-lock.yaml linguist-generated
|
||||
package-lock.json linguist-generated
|
||||
bun.lockb linguist-generated
|
||||
bun.lock linguist-generated
|
||||
yarn.lock linguist-generated
|
||||
|
||||
# Exclude templates from language statistics
|
||||
|
||||
@@ -7,3 +7,6 @@
|
||||
# Creator of specific wxt modules
|
||||
/packages/auto-icons/ @Timeraa
|
||||
/packages/unocss/ @Timeraa
|
||||
|
||||
# Other Directories
|
||||
/docs @aklinker1 @PatrykKuniczak @nishu-murmu
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
name: Basic Setup
|
||||
description: Install PNPM, Node, and dependencies
|
||||
description: Install Bun and dependencies
|
||||
|
||||
inputs:
|
||||
install:
|
||||
default: 'true'
|
||||
description: Whether or not to run 'pnpm install'
|
||||
|
||||
installArgs:
|
||||
default: ''
|
||||
description: Additional args to append to "pnpm install"
|
||||
description: Whether or not to run 'bun install'
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: 🛠️ Setup PNPM
|
||||
uses: pnpm/action-setup@f2b2b233b538f500472c7274c7012f57857d8ce0 # v4.1.0
|
||||
|
||||
- name: 🛠️ Setup NodeJS
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
- name: 🛠️ Setup Bun
|
||||
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
|
||||
with:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
bun-version-file: '.tool-versions'
|
||||
|
||||
- name: 📦 Install Dependencies
|
||||
if: ${{ inputs.install == 'true' }}
|
||||
shell: bash
|
||||
run: pnpm install ${{ inputs.installArgs }}
|
||||
run: bun ci ${{ inputs.installArgs }}
|
||||
|
||||
+12
-7
@@ -1,15 +1,20 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
- package-ecosystem: bun
|
||||
directory: /
|
||||
schedule:
|
||||
interval: 'monthly'
|
||||
interval: 'daily'
|
||||
groups:
|
||||
production-updates:
|
||||
applies-to: version-updates
|
||||
dependency-type: production
|
||||
update-types: [minor, patch]
|
||||
dev-updates:
|
||||
applies-to: version-updates
|
||||
dependency-type: development
|
||||
update-types: [minor, patch]
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'monthly'
|
||||
interval: 'daily'
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
- name: Gather Info
|
||||
id: check
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
@@ -59,20 +59,24 @@ jobs:
|
||||
|
||||
- name: Sync Author
|
||||
if: steps.check.outputs.has_assignees == 'false'
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.addAssignees({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
assignees: ['${{ steps.check.outputs.author }}']
|
||||
});
|
||||
try {
|
||||
await github.rest.issues.addAssignees({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
assignees: ['${{ steps.check.outputs.author }}']
|
||||
});
|
||||
|
||||
console.log('Assigned PR author: ${{ steps.check.outputs.author }}');
|
||||
console.log('Assigned PR author: ${{ steps.check.outputs.author }}');
|
||||
} catch (err) {
|
||||
console.warn("Error assigning author:", err);
|
||||
}
|
||||
|
||||
- name: Sync Labels
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const newLabels = ${{ steps.check.outputs.labels }};
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
if: github.repository_owner == 'wxt-dev'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
@@ -17,13 +17,18 @@ jobs:
|
||||
if: ${{ github.repository == 'wxt-dev/wxt' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build All Packages
|
||||
run: pnpm buildc all
|
||||
run: bun run buildc all
|
||||
|
||||
- name: Publish
|
||||
run: pnpx pkg-pr-new publish --compact --pnpm './packages/*'
|
||||
run: bunx pkg-pr-new publish --compact --bun './packages/*'
|
||||
|
||||
- name: DEBUG
|
||||
if: always()
|
||||
run: ls -lhA
|
||||
working-directory: packages/analytics
|
||||
|
||||
@@ -15,8 +15,21 @@ jobs:
|
||||
if: github.event.pull_request.merged == true
|
||||
|
||||
steps:
|
||||
- name: Log PR Author Info
|
||||
run: |
|
||||
echo "PR Author Login: ${{ github.event.pull_request.user.login }}"
|
||||
echo "PR Author ID: ${{ github.event.pull_request.user.id }}"
|
||||
echo "PR Author Type: ${{ github.event.pull_request.user.type }}"
|
||||
echo "PR Author Node ID: ${{ github.event.pull_request.user.node_id }}"
|
||||
|
||||
- name: Post Thank You Comment
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
if: >-
|
||||
github.event.pull_request.user.login != 'aklinker1' &&
|
||||
github.event.pull_request.user.login != 'Timeraa' &&
|
||||
github.event.pull_request.user.login != 'PatrykKuniczak' &&
|
||||
github.event.pull_request.user.login != 'dependabot[bot]' &&
|
||||
github.event.pull_request.user.login != 'copilot-swe-agent[bot]'
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
comment: Thanks for helping make WXT better!
|
||||
with:
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
|
||||
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
|
||||
@@ -37,11 +37,11 @@ jobs:
|
||||
|
||||
- name: Build docs
|
||||
run: |
|
||||
pnpm docs:build
|
||||
bun run docs:build
|
||||
docker build docs/.vitepress -t ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
|
||||
|
||||
- name: Push Image
|
||||
run: docker push ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
|
||||
|
||||
- name: Deploy
|
||||
run: curl -X POST -i ${{ secrets.UPDATE_DOCS_WEBHOOK }}
|
||||
run: 'curl -X POST -i -H "X-API-Key: ${{ secrets.DOCS_WEBHOOK_API_KEY }}" https://${{ secrets.DOCS_WEBHOOK_DOMAIN }}/api/webhook/stacks/${{ secrets.DOCS_WEBHOOK_STACK_ID }}'
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
- validate
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }} # https://github.com/sbellone/release-workflow-example
|
||||
@@ -56,18 +56,18 @@ jobs:
|
||||
|
||||
- name: Bump and Tag
|
||||
run: |
|
||||
pnpm tsx scripts/bump-package-version.ts ${{ inputs.package }}
|
||||
bun run scripts/bump-package-version.ts ${{ inputs.package }}
|
||||
git push
|
||||
git push --tags
|
||||
|
||||
- name: Publish to NPM
|
||||
working-directory: packages/${{ inputs.package }}
|
||||
run: |
|
||||
pnpm pack
|
||||
bun pm pack
|
||||
sudo npm i -g npm@latest
|
||||
/usr/local/bin/npm publish *.tgz
|
||||
npm publish *.tgz
|
||||
|
||||
- name: Create GitHub release
|
||||
run: pnpm tsx scripts/create-github-release.ts ${{ inputs.package }}
|
||||
run: bun run scripts/create-github-release.ts ${{ inputs.package }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
@@ -39,6 +39,6 @@ jobs:
|
||||
installArgs: --ignore-scripts
|
||||
|
||||
- name: Sync Releases
|
||||
run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
|
||||
run: bun run scripts/sync-releases.ts ${{ inputs.package }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
id-token: write # OIDC for NPM publishing
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
@@ -28,15 +28,15 @@ jobs:
|
||||
|
||||
- name: Generate Latest Code
|
||||
working-directory: packages/browser
|
||||
run: pnpm gen
|
||||
run: bun run gen
|
||||
|
||||
- name: Run Checks
|
||||
working-directory: packages/browser
|
||||
run: pnpm check
|
||||
run: bun run check
|
||||
|
||||
- name: Commit Changes
|
||||
id: commit
|
||||
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||
uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -46,6 +46,6 @@ jobs:
|
||||
if: steps.commit.outputs.changes_detected == 'true'
|
||||
working-directory: packages/browser
|
||||
run: |
|
||||
pnpm pack
|
||||
bun pm pack
|
||||
sudo npm i -g npm@latest
|
||||
/usr/local/bin/npm publish *.tgz
|
||||
npm publish *.tgz
|
||||
|
||||
@@ -15,43 +15,43 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Basic Checks
|
||||
run: pnpm check
|
||||
run: bun run check
|
||||
|
||||
builds:
|
||||
name: Builds
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build All Packages
|
||||
run: pnpm buildc all
|
||||
run: bun run buildc all
|
||||
|
||||
build-demo:
|
||||
name: Build Demo
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build
|
||||
run: pnpm build:all
|
||||
run: bun run build:all
|
||||
working-directory: packages/wxt-demo
|
||||
|
||||
- name: ZIP
|
||||
run: pnpm wxt zip
|
||||
run: bun run wxt zip
|
||||
working-directory: packages/wxt-demo
|
||||
|
||||
tests:
|
||||
@@ -68,25 +68,27 @@ jobs:
|
||||
coverage: false
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Run Tests
|
||||
if: ${{ ! matrix.coverage }}
|
||||
run: pnpm test
|
||||
run: bun run test
|
||||
|
||||
- name: Run Tests (Coverage)
|
||||
if: matrix.coverage
|
||||
run: pnpm test:coverage --reporter=default --reporter=hanging-process
|
||||
run: bun run test:coverage --reporter=default --reporter=hanging-process
|
||||
|
||||
- name: Upload Coverage
|
||||
if: matrix.coverage
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -104,13 +106,13 @@ jobs:
|
||||
- vue
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Pack WXT package
|
||||
run: pnpm pack
|
||||
run: bun pm pack
|
||||
working-directory: packages/wxt
|
||||
|
||||
- name: Install Dependencies
|
||||
@@ -122,15 +124,15 @@ jobs:
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
|
||||
- name: Type Check Template
|
||||
run: pnpm compile
|
||||
run: bun run compile
|
||||
if: matrix.template != 'svelte'
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
|
||||
- name: Type Check Template
|
||||
run: pnpm check
|
||||
run: bun run check
|
||||
if: matrix.template == 'svelte'
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
|
||||
- name: Build Template
|
||||
run: pnpm build
|
||||
run: bun run build
|
||||
working-directory: templates/${{ matrix.template }}
|
||||
|
||||
@@ -17,25 +17,20 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
install: false
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
|
||||
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16
|
||||
with:
|
||||
go-version: '1.25.1'
|
||||
|
||||
# This prevents pnpm dlx from downloading WXT in the video
|
||||
- name: Pre-install WXT
|
||||
run: |
|
||||
pnpm store add wxt@latest
|
||||
pnpm dlx wxt@latest --version
|
||||
# This prevents bunx from downloading WXT in the video, and it uses the checked-out version
|
||||
- name: Build WXT
|
||||
working-directory: packages/wxt
|
||||
run: bun run build
|
||||
|
||||
- name: Record VHS
|
||||
run: |
|
||||
@@ -44,7 +39,7 @@ jobs:
|
||||
vhs docs/tapes/init-demo.tape
|
||||
|
||||
- name: Save recorded GIF
|
||||
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||
uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
+2
-1
@@ -1,12 +1,14 @@
|
||||
.DS_Store
|
||||
.env
|
||||
.env.*
|
||||
!packages/wxt/src/core/utils/__tests__/fixtures/.env
|
||||
.idea
|
||||
.output
|
||||
.webextrc
|
||||
.wxt
|
||||
.wxt-runner
|
||||
*.log
|
||||
*.tgz
|
||||
/docs/.vitepress/cache
|
||||
docs/.vitepress/.temp
|
||||
coverage
|
||||
@@ -20,6 +22,5 @@ templates/*/yarn.lock
|
||||
templates/*/package-lock.json
|
||||
docs/api/reference
|
||||
stats.html
|
||||
.tool-versions
|
||||
.cache
|
||||
*-stats.txt
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
bun 1.3.13
|
||||
nodejs 24.14.1
|
||||
+58
-35
@@ -15,7 +15,7 @@ WXT is two things:
|
||||
|
||||
The long term goal of WXT is provide an opinionated build tool that keeps WXT projects standard, while providing light-weight runtime utils that simplify a lot of the boilerplate/overhead when setting up a new extension.
|
||||
|
||||
I also want to provide a way for developers to use either one of those two things independently, and not require them to use both. This is why all of WXT's runtime utils are shipped as their own NPM packages, most of them not bundled inside the core `wxt` package. If you just want to use the packages, they're availalbe, and if you just want to use WXT's built tool, you don't have to import any of WXT's utilities, you can use your own.
|
||||
I also want to provide a way for developers to use either one of those two things independently, and not require them to use both. This is why all of WXT's runtime utils are shipped as their own NPM packages, most of them not bundled inside the core `wxt` package. If you just want to use the packages, they're available, and if you just want to use WXT's built tool, you don't have to import any of WXT's utilities, you can use your own.
|
||||
|
||||
> The few runtime utils shipped inside WXT are things that should be used by 90% of extensions. That said, they're also legacy utils left in from before I started creating separate NPM packages, and in the future, they may be removed from the core package.
|
||||
|
||||
@@ -48,16 +48,12 @@ To make a breaking change:
|
||||
|
||||
## Setup
|
||||
|
||||
WXT uses `pnpm`, so make sure you have it installed.
|
||||
WXT uses Bun for package management and development. Install it from: <https://bun.com/>
|
||||
|
||||
Then install dependencies:
|
||||
|
||||
```sh
|
||||
corepack enable
|
||||
```
|
||||
|
||||
Then, simply run the install command:
|
||||
|
||||
```sh
|
||||
pnpm i
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development
|
||||
@@ -65,48 +61,68 @@ pnpm i
|
||||
Here are some helpful commands:
|
||||
|
||||
```sh
|
||||
# Build WXT package
|
||||
cd packages/wxt
|
||||
pnpm build
|
||||
# Build WXT package and workspace dependencies
|
||||
bun run --filter wxt build
|
||||
```
|
||||
|
||||
```sh
|
||||
# Build WXT package, then build demo extension
|
||||
cd packages/wxt-demo
|
||||
pnpm build
|
||||
```
|
||||
|
||||
```sh
|
||||
# Build WXT package, then start the demo extension in dev mode
|
||||
cd packages/wxt-demo
|
||||
pnpm dev
|
||||
# Build workspace dependencies, then start the demo extension in dev mode
|
||||
bun run --filter wxt-demo dev
|
||||
```
|
||||
|
||||
```sh
|
||||
# Run unit and E2E tests
|
||||
pnpm test
|
||||
bun run test
|
||||
```
|
||||
|
||||
```sh
|
||||
# Start the docs website locally
|
||||
pnpm docs:dev
|
||||
bun run docs:dev
|
||||
```
|
||||
|
||||
> Above, we used bun's `--filter` flag to choose which package to run a command in, but there are other ways:
|
||||
>
|
||||
> ```sh
|
||||
> bun run -F @wxt-dev/i18n build
|
||||
> # or
|
||||
> bun run --cwd packages/i18n build
|
||||
> # or
|
||||
> cd packages/i18n
|
||||
> bun run build
|
||||
> ```
|
||||
>
|
||||
> Pick your poison!
|
||||
|
||||
## Profiling
|
||||
|
||||
```sh
|
||||
# Build the latest version
|
||||
pnpm --filter wxt build
|
||||
bun run --filter wxt build
|
||||
|
||||
# CD to the demo directory
|
||||
cd packages/wxt-demo
|
||||
|
||||
# 1. Generate a flamechart with 0x
|
||||
pnpm dlx 0x node_modules/wxt/bin/wxt.mjs build
|
||||
# 2. Inspect the process with chrome @ chrome://inspect
|
||||
pnpm node --inspect node_modules/wxt/bin/wxt.mjs build
|
||||
```
|
||||
|
||||
Then there are a few different ways to profile WXT commands:
|
||||
|
||||
- Generate a flamechart with 0x:
|
||||
|
||||
```sh
|
||||
bunx 0x node_modules/wxt/bin/wxt.mjs build
|
||||
```
|
||||
|
||||
- Create a CPU profile:
|
||||
|
||||
```sh
|
||||
bun run --cpu-prof node_modules/wxt/bin/wxt.mjs build
|
||||
```
|
||||
|
||||
- Debug the process:
|
||||
|
||||
```sh
|
||||
bun run --inspect node_modules/wxt/bin/wxt.mjs build
|
||||
```
|
||||
|
||||
## Updating Docs
|
||||
|
||||
Documentation is written with VitePress, and is located in the `docs/` directory.
|
||||
@@ -122,7 +138,7 @@ WXT has unit and E2E tests. When making a change or adding a feature, make sure
|
||||
To run tests for a specific file, add the filename at the end of the test command:
|
||||
|
||||
```sh
|
||||
pnpm test manifest-contents
|
||||
bun run --filter wxt test manifest-contents
|
||||
```
|
||||
|
||||
All test (unit and E2E) for all packages are ran together via [Vitest workspaces](https://vitest.dev/guide/#workspaces-support).
|
||||
@@ -131,7 +147,7 @@ If you want to manually test a change, you can modify the demo project for your
|
||||
|
||||
## Templates
|
||||
|
||||
Each directory inside `templates/` is it's own standalone project. Simply `cd` into the directory you're updating, install dependencies with `npm` (NOT `pnpm`), and run the relevant commands
|
||||
Each directory inside `templates/` is it's own standalone project. Simply `cd` into the directory you're updating, install dependencies with `npm` (NOT `bun`), and run the relevant commands
|
||||
|
||||
```sh
|
||||
cd templates/vue
|
||||
@@ -147,7 +163,7 @@ Note that templates are hardcoded to a specific version of `wxt` from NPM, they
|
||||
"typescript": "^5.3.2",
|
||||
"vite-plugin-solid": "^2.7.0",
|
||||
- "wxt": "^0.8.0"
|
||||
+ "wxt": "../.."
|
||||
+ "wxt": "../../packages/wxt"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -163,18 +179,25 @@ cp -r templates/vanilla templates/<new-template-name>
|
||||
|
||||
That's it. Once your template is merged, it will be available inside `wxt init` immediately. You don't need to release a new version of WXT to release a new template.
|
||||
|
||||
## Releasing Updates
|
||||
|
||||
Releases are done with GitHub actions:
|
||||
|
||||
- Use the [Release workflow](https://github.com/wxt-dev/wxt/actions/workflows/release.yml) to release a single package in the monorepo. This automatically detects the version change with conventional commits, builds and uploads the package to NPM, and creates a GitHub release.
|
||||
- Use the [Sync Releases workflow](https://github.com/wxt-dev/wxt/actions/workflows/sync-releases.yml) to sync the GitHub releases with changes to the changelog. To change a release, update the `CHANGELOG.md` file and run the workflow. It will sync the releases of a single package in the monorepo.
|
||||
|
||||
## Upgrading Dependencies
|
||||
|
||||
WXT has custom rules around what dependencies can be upgraded. Use the `scripts/upgrade-deps.ts` script to upgrade dependencies and follow these rules.
|
||||
|
||||
```sh
|
||||
pnpm tsx scripts/upgrade-deps.ts
|
||||
bun run scripts/upgrade-deps.ts
|
||||
```
|
||||
|
||||
To see all the options, run:
|
||||
|
||||
```sh
|
||||
pnpm tsx scripts/upgrade-deps.ts --help
|
||||
bun run scripts/upgrade-deps.ts --help
|
||||
```
|
||||
|
||||
## Install Unreleased Versions
|
||||
@@ -212,4 +235,4 @@ Anyone is welcome to submit a blog post on <https://wxt.dev/blog>!
|
||||
|
||||
If you're interested in becoming a maintainer, send an email to Aaron at <aaronklinker1@gmail.com> with your github username saying you're interested. The process is very informal, I will add you quickly if you've contributed code or answered questions and helped out the community!
|
||||
|
||||
Maintainers don't have to just write code - they can manage issues, answer questsions, review PRs, organize and prioritize work - there's lots of ways for you to help out.
|
||||
Maintainers don't have to just write code - they can manage issues, answer questions, review PRs, organize and prioritize work - there's lots of ways for you to help out.
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ Example PR: <https://github.com/wxt-dev/wxt/pull/2152>
|
||||
|
||||
```sh
|
||||
cd packages/<dir-name>
|
||||
pnpm publish --access public
|
||||
bun publish --access public
|
||||
```
|
||||
|
||||
7. Create a basic release on GitHub mentioning the new package is available.
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
version: '0.2'
|
||||
ignorePaths:
|
||||
- pnpm-lock.yaml
|
||||
- '**/node_modules/**'
|
||||
- '**/dist/**'
|
||||
- '**/coverage/**'
|
||||
- docs/api/**
|
||||
- '**/CHANGELOG.md'
|
||||
- docs/public/_redirects
|
||||
- packages/browser/src/gen/**
|
||||
- patches/**
|
||||
- '**/*.snap'
|
||||
- docs/assets/extension-showcase.yml
|
||||
- packages/*/stats.html
|
||||
words:
|
||||
- Aabid
|
||||
- aabidk
|
||||
- aklinker
|
||||
- automount
|
||||
- avenir
|
||||
- bidi
|
||||
- buildc
|
||||
- bunx
|
||||
- cachable
|
||||
- cacjs
|
||||
- charmbracelet
|
||||
- chromedriver
|
||||
- Cira
|
||||
- comctx
|
||||
- confbox
|
||||
- configfile
|
||||
- consolas
|
||||
- Cpath
|
||||
- crbug
|
||||
- cros
|
||||
- crunchyroll
|
||||
- cssinjs
|
||||
- CSUI
|
||||
- Csvg
|
||||
- defu
|
||||
- deinit
|
||||
- dequal
|
||||
- dtrace
|
||||
- duckduckgo
|
||||
- esbuild
|
||||
- esque
|
||||
- extglob
|
||||
- favicons
|
||||
- fieldtrials
|
||||
- firefoxdeveloperedition
|
||||
- flamechart
|
||||
- Florian
|
||||
- focusring
|
||||
- frontmatter
|
||||
- giget
|
||||
- grayscale
|
||||
- greyscale
|
||||
- gtag
|
||||
- hckhakegfgenefhikdcfkaaonnclljmf
|
||||
- hookable
|
||||
- iconify
|
||||
- jsons
|
||||
- Kenobi
|
||||
- keystyle
|
||||
- klinker
|
||||
- linkedom
|
||||
- llms
|
||||
- locationchange
|
||||
- magicast
|
||||
- Menlo
|
||||
- menupanel
|
||||
- metas
|
||||
- moderok
|
||||
- mktemp
|
||||
- mycompany
|
||||
- nacl
|
||||
- nanospinner
|
||||
- natsu
|
||||
- newtab
|
||||
- njsproj
|
||||
- nodebuffer
|
||||
- Noto
|
||||
- ntvs
|
||||
- nypm
|
||||
- obug
|
||||
- ohash
|
||||
- oklab
|
||||
- oxlint
|
||||
- pbbf
|
||||
- personaltoolbar
|
||||
- portaling
|
||||
- prebundled
|
||||
- prefs
|
||||
- proxified
|
||||
- pWXLO
|
||||
- repost
|
||||
- rxliuli
|
||||
- sandboxed
|
||||
- scule
|
||||
- Segoe
|
||||
- sidepanel
|
||||
- sidepanels
|
||||
- solidjs
|
||||
- styl
|
||||
- subdependency
|
||||
- tabstrip
|
||||
- teleporting
|
||||
- treemap
|
||||
- triaging
|
||||
- umami
|
||||
- ungh
|
||||
- unimport
|
||||
- unocss
|
||||
- unrun
|
||||
- unstub
|
||||
- unwatch
|
||||
- USSS
|
||||
- vitepress
|
||||
- webext
|
||||
- webextension
|
||||
- webextrc
|
||||
- xcrun
|
||||
- xlink
|
||||
@@ -3,8 +3,9 @@ import { computed } from 'vue';
|
||||
// @ts-expect-error: Vitepress data-loader magic, this import is correct
|
||||
import { data } from '../loaders/blog.data';
|
||||
import BlogPostPreview from './BlogPostPreview.vue';
|
||||
import { Post } from '../utils/types';
|
||||
|
||||
const posts = computed(() =>
|
||||
const posts = computed<Post[]>(() =>
|
||||
data
|
||||
.map((post) => ({
|
||||
...post,
|
||||
@@ -17,10 +18,8 @@ const posts = computed(() =>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div>
|
||||
<div class="vp-doc">
|
||||
<h1>Blog</h1>
|
||||
</div>
|
||||
<div class="inner-container">
|
||||
<h1>Blog</h1>
|
||||
|
||||
<ul>
|
||||
<BlogPostPreview v-for="post of posts" :key="post.url" :post />
|
||||
@@ -34,37 +33,33 @@ const posts = computed(() =>
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.container > div {
|
||||
padding: 32px;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
.inner-container {
|
||||
padding: 32px;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
list-style: none;
|
||||
}
|
||||
ul,
|
||||
li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
ul li {
|
||||
padding-top: 16px;
|
||||
margin-top: 16px;
|
||||
border-top: 1px solid var(--vp-c-default);
|
||||
}
|
||||
ul li:last-child {
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid var(--vp-c-default);
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
li {
|
||||
padding-top: 16px;
|
||||
margin-top: 16px;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
import { Ref } from 'vue';
|
||||
import useBlogDate from '../composables/useBlogDate';
|
||||
import { useData } from 'vitepress';
|
||||
import { Content, useData } from 'vitepress';
|
||||
import { PostFrontmatter } from '../utils/types';
|
||||
|
||||
const { frontmatter } = useData() as unknown as {
|
||||
frontmatter: Ref<PostFrontmatter>;
|
||||
};
|
||||
|
||||
const { frontmatter } = useData();
|
||||
const date = useBlogDate(() => frontmatter.value.date);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vp-doc">
|
||||
<main class="container-content">
|
||||
<h1 v-html="$frontmatter.title" />
|
||||
<main class="container">
|
||||
<h1 v-html="frontmatter.title" />
|
||||
<p class="meta-row">
|
||||
<a
|
||||
class="author"
|
||||
v-for="author of $frontmatter.authors"
|
||||
v-for="author of frontmatter.authors"
|
||||
:key="author.github"
|
||||
:href="`https://github.com/${author.github}`"
|
||||
class="author"
|
||||
>
|
||||
<img :src="`https://github.com/${author.github}.png?size=96`" />
|
||||
<img
|
||||
:src="`https://github.com/${author.github}.png?size=96`"
|
||||
alt="Author's avatar"
|
||||
/>
|
||||
<span>{{ author.name }}</span>
|
||||
</a>
|
||||
<span>•</span>
|
||||
@@ -29,48 +37,43 @@ const date = useBlogDate(() => frontmatter.value.date);
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
vp-doc {
|
||||
.vp-doc {
|
||||
display: flex;
|
||||
}
|
||||
main {
|
||||
max-width: 1080px;
|
||||
padding: 32px;
|
||||
margin: auto;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
main {
|
||||
padding: 64px;
|
||||
|
||||
.container {
|
||||
max-width: 1080px;
|
||||
padding: 32px;
|
||||
margin: auto;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
padding: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
.meta-row {
|
||||
display: flex;
|
||||
color: var(--vp-c-text-2);
|
||||
gap: 16px;
|
||||
overflow: hidden;
|
||||
padding-bottom: 32px;
|
||||
|
||||
.author {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
|
||||
:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.meta-row {
|
||||
display: flex;
|
||||
color: var(--vp-c-text-2);
|
||||
gap: 16px;
|
||||
overflow: hidden;
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
.meta-row > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.author {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
color: var(--vp-c-text-2);
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
}
|
||||
.author img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
.author span {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.author:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,42 +31,23 @@ const date = useBlogDate(() => props.post.date);
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.vp-doc {
|
||||
* {
|
||||
margin-block: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
.title {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
li > a > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 -16px;
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
li > a > div:hover {
|
||||
background: var(--vp-c-default);
|
||||
}
|
||||
li .title {
|
||||
color: var(--vp-c-text);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
li .description {
|
||||
font-size: 16px;
|
||||
color: var(--vp-c-text-2);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
li .meta {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
.description {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.meta {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,40 +1,65 @@
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
patterns: Array<[intput: string, output: string]>;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="no-vertical-dividers">
|
||||
<table class="no-vertical-dividers entrypoint-patterns">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 100%">Filename</th>
|
||||
<th class="col-filename">Filename</th>
|
||||
<th></th>
|
||||
<th>Output Path</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="pattern of patterns">
|
||||
<td style="white-space: nowrap; padding-right: 8px">
|
||||
<tr v-for="pattern of patterns" :key="pattern[0]">
|
||||
<td class="cell-input">
|
||||
<code>entrypoints/{{ pattern[0] }}</code>
|
||||
</td>
|
||||
<td style="padding: 6px; opacity: 50%">
|
||||
<td class="cell-arrow">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M4 11v2h12l-5.5 5.5l1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5L16 11H4Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</td>
|
||||
<td style="white-space: nowrap; padding-left: 8px">
|
||||
<td class="cell-output">
|
||||
<code>/{{ pattern[1] }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.entrypoint-patterns {
|
||||
width: 100%;
|
||||
|
||||
.col-filename {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cell-input {
|
||||
white-space: nowrap;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.cell-arrow {
|
||||
padding: 6px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.cell-output {
|
||||
white-space: nowrap;
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, computed, toRaw, Ref } from 'vue';
|
||||
import { computed, onMounted, ref, Ref, toRaw } from 'vue';
|
||||
import ExampleSearchFilterByItem from './ExampleSearchFilterByItem.vue';
|
||||
import ExampleSearchResult from './ExampleSearchResult.vue';
|
||||
import { ExamplesMetadata, KeySelectedObject } from '../utils/types';
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
tag?: string;
|
||||
}>();
|
||||
|
||||
const exampleMetadata = ref<ExamplesMetadata>();
|
||||
const exampleMetadata = ref<ExamplesMetadata>({
|
||||
allApis: [],
|
||||
allPackages: [],
|
||||
allPermissions: [],
|
||||
examples: [],
|
||||
});
|
||||
onMounted(async () => {
|
||||
const res = await fetch(
|
||||
'https://raw.githubusercontent.com/wxt-dev/examples/main/metadata.json',
|
||||
@@ -48,7 +53,7 @@ function doesExampleMatchSelected(
|
||||
|
||||
const filteredExamples = computed(() => {
|
||||
const text = searchText.value.toLowerCase();
|
||||
return exampleMetadata.value.examples.filter((example) => {
|
||||
return exampleMetadata.value?.examples.filter((example) => {
|
||||
const matchesText = example.searchText.toLowerCase().includes(text);
|
||||
const matchesApis = doesExampleMatchSelected(example.apis, requiredApis);
|
||||
const matchesPermissions = doesExampleMatchSelected(
|
||||
@@ -72,19 +77,19 @@ const filteredExamples = computed(() => {
|
||||
|
||||
<div class="filters">
|
||||
<ExampleSearchFilterByItem
|
||||
label="APIs"
|
||||
:items="exampleMetadata?.allApis"
|
||||
v-model="selectedApis"
|
||||
:items="exampleMetadata?.allApis"
|
||||
label="APIs"
|
||||
/>
|
||||
<ExampleSearchFilterByItem
|
||||
label="Permissions"
|
||||
:items="exampleMetadata?.allPermissions"
|
||||
v-model="selectedPermissions"
|
||||
:items="exampleMetadata?.allPermissions"
|
||||
label="Permissions"
|
||||
/>
|
||||
<ExampleSearchFilterByItem
|
||||
label="Packages"
|
||||
:items="exampleMetadata?.allPackages"
|
||||
v-model="selectedPackages"
|
||||
:items="exampleMetadata?.allPackages"
|
||||
label="Packages"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -107,120 +112,57 @@ const filteredExamples = computed(() => {
|
||||
<style scoped>
|
||||
.example-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-areas:
|
||||
'search'
|
||||
'results';
|
||||
'filters search'
|
||||
'filters results';
|
||||
gap: 16px;
|
||||
}
|
||||
@media only screen and (min-width: 720px) {
|
||||
.example-layout {
|
||||
|
||||
@media only screen and (min-width: 710px) {
|
||||
grid-template-columns: 256px 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-areas:
|
||||
'filters search'
|
||||
'filters results';
|
||||
}
|
||||
}
|
||||
.search {
|
||||
grid-area: search;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
border-radius: 16px;
|
||||
}
|
||||
.filters {
|
||||
display: none;
|
||||
grid-area: filters;
|
||||
}
|
||||
@media only screen and (min-width: 720px) {
|
||||
|
||||
.search {
|
||||
grid-area: search;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 20px;
|
||||
border-radius: 16px;
|
||||
|
||||
input {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.filters {
|
||||
grid-area: filters;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
align-self: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
@media only screen and (min-width: 710px) {
|
||||
border-radius: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.results {
|
||||
grid-area: results;
|
||||
}
|
||||
|
||||
.box {
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.results {
|
||||
grid-area: results;
|
||||
|
||||
.search input {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
}
|
||||
.search-results {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
|
||||
.checkbox-col {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
max-height: 200px;
|
||||
font-size: 14px;
|
||||
gap: 4px;
|
||||
}
|
||||
@media only screen and (min-width: 800px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
@media only screen and (min-width: 1024px) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.checkbox-col .header {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
.checkbox-col p {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: flex-start;
|
||||
text-wrap: wrap;
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
span {
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.checkbox-col input[type='checkbox'] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.checkbox-col-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.search-results {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
@media only screen and (min-width: 800px) {
|
||||
.search-results {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
a {
|
||||
background-color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 1024px) {
|
||||
.search-results {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { computed, toRaw } from 'vue';
|
||||
import { KeySelectedObject } from '../utils/types';
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
label: string;
|
||||
items?: string[];
|
||||
}>();
|
||||
@@ -29,12 +29,12 @@ function toggleItem(pkg: string) {
|
||||
<span>Filter by {{ label }}</span> <span v-if="count">({{ count }})</span>
|
||||
</p>
|
||||
<div class="scroll-container">
|
||||
<ul>
|
||||
<ul class="items">
|
||||
<li v-for="item in items">
|
||||
<label :title="item">
|
||||
<label :title="item" class="item-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="selectedItems[item]"
|
||||
type="checkbox"
|
||||
@input="toggleItem(item)"
|
||||
/>
|
||||
<span>{{ item }}</span>
|
||||
@@ -51,50 +51,45 @@ function toggleItem(pkg: string) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
border-radius: 20px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--vp-c-brand-3) transparent;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.scroll-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.header {
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
.scroll-container ul {
|
||||
position: absolute;
|
||||
overflow-y: auto;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-size: small;
|
||||
padding: 8px 16px 16px 16px;
|
||||
}
|
||||
.scroll-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.header {
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
opacity: 50%;
|
||||
}
|
||||
label {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: flex-start;
|
||||
text-wrap: wrap;
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 140%;
|
||||
cursor: pointer;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
span {
|
||||
padding-top: 1px;
|
||||
}
|
||||
input[type='checkbox'] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
.items {
|
||||
position: absolute;
|
||||
overflow-y: auto;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-size: small;
|
||||
padding: 8px 16px 16px;
|
||||
|
||||
.item-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
|
||||
input[type='checkbox'] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { Example } from '../utils/types';
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
example: Example;
|
||||
}>();
|
||||
</script>
|
||||
@@ -17,56 +17,45 @@ const props = defineProps<{
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
* {
|
||||
min-width: 0;
|
||||
}
|
||||
a {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 2px solid var(--vp-c-bg-soft);
|
||||
border-radius: 16px;
|
||||
color: var(--vp-c-text-1) !important;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
outline: 2px solid var(--vp-c-brand-2);
|
||||
}
|
||||
.name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-shrink: 0;
|
||||
&:hover {
|
||||
outline: 2px solid var(--vp-c-brand-2);
|
||||
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.description {
|
||||
height: 53px;
|
||||
opacity: 70%;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
line-height: 120%;
|
||||
min-height: 0;
|
||||
.link {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.name {
|
||||
font-weight: 500;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.link {
|
||||
opacity: 0;
|
||||
transition: 250ms;
|
||||
color: var(--vp-c-brand-2);
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
a:hover .link {
|
||||
opacity: 100%;
|
||||
.description {
|
||||
opacity: 70%;
|
||||
font-size: 14px;
|
||||
line-height: 130%;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.link {
|
||||
opacity: 0;
|
||||
transition: 250ms;
|
||||
color: var(--vp-c-brand-2);
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,174 +1,138 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import useListExtensionDetails, {
|
||||
ChromeExtension,
|
||||
} from '../composables/useListExtensionDetails';
|
||||
import useListExtensionDetails from '../composables/useListExtensionDetails';
|
||||
import _extensionEntries from '../../assets/extension-showcase.yml';
|
||||
|
||||
// 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
|
||||
'mgmdkjcljneegjfajchedjpdhbadklcf', // Anime Skip Player
|
||||
'bfbnagnphiehemkdgmmficmjfddgfhpl', // UltraWideo
|
||||
'elfaihghhjjoknimpccccmkioofjjfkf', // StayFree - Website Blocker & Web Analytics
|
||||
'okifoaikfmpfcamplcfjkpdnhfodpkil', // Doozy: Ai Made Easy
|
||||
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
|
||||
'oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
|
||||
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
|
||||
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
|
||||
'ceicccfeikoipigeghddpocceifjelph', // Plex Skipper
|
||||
'aelkipgppclpfimeamgmlonimflbhlgf', // GitHub Custom Notifier
|
||||
'djnlaiohfaaifbibleebjggkghlmcpcj', // Fluent Read
|
||||
'nhclljcpfmmaiojbhhnkpjcfmacfcian', // Facebook Video Controls
|
||||
'mblkhbaakhbhiimkbcnmeciblfhmafna', // ElemSnap - Quick capture of webpage elements and conversion to images,
|
||||
'oajalfneblkfiejoadecnmodfpnaeblh', // MS Edge TTS (Text to Speech)
|
||||
'nedcanggplmbbgmlpcjiafgjcpdimpea', // YTBlock - Block any content from YouTube™
|
||||
'oadbjpccljkplmhnjekgjamejnbadlne', // demo.fun - Interactive product demos that convert
|
||||
'iopdafdcollfgaoffingmahpffckmjni', // SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
|
||||
'khjdmjcmpolknpccmaaipmidphjokhdf', // WorkFlowy MultiFlow
|
||||
'fencadnndhdeggodopebjgdfdlhcimfk', // 香草布丁🌿🍮- https://github.com/Xdy1579883916/vanilla-pudding
|
||||
'bnacincmbaknlbegecpioobkfgejlojp', // MaxFocus: Link Preview
|
||||
'bcpgdpedphodjcjlminjbdeejccjbimp', // 汇率转换-中文版本
|
||||
'loeilaonggnalkaiiaepbegccilkmjjp', // Currency Converter Plus
|
||||
'npcnninnjghigjfiecefheeibomjpkak', // Respond Easy
|
||||
'cfkdcideecefncbglkhneoflfnmhoicc', // mindful - stay focused on your goals
|
||||
'lnhejcpclabmbgpiiomjbhalblnnbffg', // 1Proompt
|
||||
'fonflmjnjbkigocpoommgmhljdpljain', // NiceTab - https://github.com/web-dahuyou/NiceTab
|
||||
'fcffekbnfcfdemeekijbbmgmkognnmkd', // Draftly for LinkedIn
|
||||
'nkndldfehcidpejfkokbeghpnlbppdmo', // YouTube Summarized - Summarize any YouTube video
|
||||
'dbichmdlbjdeplpkhcejgkakobjbjalc', // 社媒助手 - https://github.com/iszhouhua/social-media-copilot
|
||||
'opepfpjeogkbgeigkbepobceinnfmjdd', // Dofollow Links for SEO
|
||||
'pdnenlnelpdomajfejgapbdpmjkfpjkp', // ChatGPT Writer: Use AI on Any Site (GPT-4o, Claude, Gemini, and More)
|
||||
'jobnhifpphkgoelnhnopgkdhbdkiadmj', // discord message translator
|
||||
'ncokhechhpjgjonhjnlaneglmdkfkcbj', // Habit Tracker app widget for daily habit tracking
|
||||
'lnjaiaapbakfhlbjenjkhffcdpoompki', // Catppuccin for GitHub File Explorer Icons
|
||||
'cpaedhbidlpnbdfegakhiamfpndhjpgf', // WebChat: Chat with anyone on any website
|
||||
'fcphghnknhkimeagdglkljinmpbagone', // YouTube Auto HD + FPS
|
||||
'lpomjgbicdemjkgmbnkjncgdebogkhlb', // MultiViewer Companion
|
||||
'ggiafipgeeaaahnjamgpjcgkdpanhddg', // Sync Watch - Watch videos together on any site
|
||||
'nmldnjcblcihmegipecakhmnieiofmgl', // Keyword Rank Checker
|
||||
'gppllamhaciichleihemgilcpledblpn', // YouTube Simple View - Hide distractions & more
|
||||
'pccbghdfdnnkkbcdcibchpbffdgednkf', // Propbar - Property Data Enhancer
|
||||
'lfknakglefggmdkjdfhhofkjnnolffkh', // Text Search Pro - Search by case and whole-word match!
|
||||
'mbenhbocjckkbaojacmaepiameldglij', // Invoice Generator
|
||||
'phlfhkmdofajnbhgmbmjkbkdgppgoppb', // Monthly Bill Tracker
|
||||
'macmkmchfoclhpbncclinhjflmdkaoom', // Wandpen - Instantly improve your writing with AI
|
||||
'lhmgechokhmdekdpgkkemoeecelcaonm', // YouTube Hider - Remove Comments By Keywords, Usernames & Tools
|
||||
'imgheieooppmahcgniieddodaliodeeg', // QA Compass - Record standardized bug reports easily
|
||||
'npgghjedpchajflknnbngajkjkdhncdo', // aesthetic Notion, styled
|
||||
'hmdcmlfkchdmnmnmheododdhjedfccka', // Eye Dropper
|
||||
'eihpmapodnppeemkhkbhikmggfojdkjd', // Cursorful - Screen Recorder with Auto Zoom
|
||||
'hjjkgbibknbahijglkffklflidncplkn', // Show IP – Live View of Website IPs for Developers
|
||||
'ilbikcehnpkmldojkcmlldkoelofnbde', // Strong Password Generator
|
||||
'ocllfkhcdopiafndigclebelbecaiocp', // ZenGram: Mindful Instagram, Your Way
|
||||
'odffpjnpocjfcaclnenaaaddghkgijdb', // Blync: Preview Links, Selection Search, AI Assistant
|
||||
'kofbbilhmnkcmibjbioafflgmpkbnmme', // HTML to Markdown - Convert webpages to markdown
|
||||
'boecmgggeigllcdocgioijmleimjbfkg', // Walmart WFS Profit Calculator
|
||||
'dlnjcbkmomenmieechnmgglgcljhoepd', // Youtube Live Chat Fullscreen
|
||||
'keiealdacakpnbbljlmhfgcebmaadieg', // Python Code Runner
|
||||
'hafcajcllbjnoolpfngclfmmgpikdhlm', // Monochromate
|
||||
'bmoggiinmnodjphdjnmpcnlleamkfedj', // AliasVault - Open-Source Password & (Email) Alias Manager
|
||||
'hlnhhamckimoaiekbglafiebkfimhapb', // SnapThePrice: AI-Powered Real-time Lowest Price Finder
|
||||
'gdjampjdgjmbifnhldgcnccdjkcoicmg', // radiofrance - news & broadcasts (French), music (international)
|
||||
'jlnhphlghikichhgbnkepenehbmloenb', // Blens - Time Tracker and AI Insight
|
||||
'njnammmpdodmfkodnfpammnpdcbhnlcm', // Always Light Mode - Setting website always in light mode
|
||||
'lblmfclcfniabobmamfkdogcgdagbhhb', // DesignPicker - Color Picker & Font Detector
|
||||
'pamnlaoeobcmhkliljfaofekeddpmfoh', // Web to PDF
|
||||
'jmbcbeepjfenihlocplnbmbhimcoooka', // Online CSV Viewer
|
||||
'nkjcoophmpcmmgadnljnlpbpfdfacgbo', // YouTube Video Transcript
|
||||
'lcaieahkjgeggeiihblhcjbbjlppgieh', // NetSuite Record Scripts
|
||||
'gmocfknjllodfiomnljmaehcplnekhlo', // VueTracker
|
||||
'ggcfemmoabhhelfkhknhbnkmeahloiod', // CanCopy - A web extension that allow you to copy any content from website
|
||||
'modkelfkcfjpgbfmnbnllalkiogfofhb', // Language Learning with AI
|
||||
'npfopljnjbamegincfjelhjhnonnjloo', // Bilibili Feed History Helper
|
||||
'edkhpdceeinkcacjdgebjehipmnbomce', // NZBDonkey - The ultimate NZB file download tool
|
||||
'cckggnbnimdbbpmdinkkgbbncopbloob', // WeChat Markdown Editor(微信 Markdown 编辑器)
|
||||
'jcblcjolcojmfopefcighfmkkefbaofg', // Tab Grab
|
||||
'eehmoikadcijkapfjocnhjclpbaindlb', // BrowserLens - https://browserlens.com/
|
||||
'hfhellofkjebbchcdffmicekjdomkcmc', // Epic Games Library Extension
|
||||
'gknigcbhlammoakmmdddkblknanpjiac', // Zen Analytics Pixel Tracker - zapt.web.app
|
||||
'cnklededohhcbmjjdlbjdkkihkgoggol', // Crypto Pulse - Compose your newtab with nature images, widgets & realtime Crypto Price & Bitcoin RSS.
|
||||
'miponnamafdenpgjemkknimgjfibicdc', // Youtube Video Scheduler
|
||||
'nhmbcmalgpkjbomhlhgdicanmkkaajmg', // Chatslator: Livestream Chat Translator
|
||||
'mbamjfdjbcdgpopfnkkmlohadbbnplhm', // 公众号阅读增强器 - https://wxreader.honwhy.wang
|
||||
'hannhecbnjnnbbafffmogdlnajpcomek', // 토탐정
|
||||
'ehboaofjncodknjkngdggmpdinhdoijp', // 2FAS Pass - https://2fas.com/
|
||||
'hnjamiaoicaepbkhdoknhhcedjdocpkd', // Quick Prompt - https://github.com/wenyuanw/quick-prompt
|
||||
'kacblhilkacgfnkjfodalohcnllcgmjd', // Add QR Code Generator Icon Back To Address Bar
|
||||
'fkbdlogfdjmpfepbbbjcgcfbgbcfcnne', // Piwik PRO Tracking Helper
|
||||
'nkbikckldmljjiiajklecmgmajgapbfl', // PIPX - Take Control of Picture-in-Picture, Automatically
|
||||
'hgppdobcpkfkmiegekaglonjajeojmdd', // Browsely - AI-powered browser extension
|
||||
'ehmoihnjgkdimihkhokkmfjdgomohjgm', // Filmbudd Pro - Simple, private – and synced ratings and watch notes across all your devices
|
||||
'alglchohmdikgdjhafiicilegegieafa', // MultiField CopyCat - Copy, Paste & Autofill Web Forms Instantly
|
||||
'aamihahiiogceidpbnfgehacgiecephe', // ChatSight - Add Table of Contents to ChatGPT
|
||||
'cndibmoanboadcifjkjbdpjgfedanolh', // BetterCampus (prev. BetterCanvas)
|
||||
'hinfimgacobnellbncbcpdlpaapcofaa', // Leetcode Fonts - Change fonts in leetcode effortlessly
|
||||
'kbkbfefhhabpkibojinapkkgciiacggg', // TranslateManga - Manga Translator & Manga Tracker
|
||||
'emeakbgdecgmdjgegnejpppcnkcnoaen', // SiteData - Free Website Traffic Checker & Reverse AdSense Tool
|
||||
'gpnckbhgpnjciklpoehkmligeaebigaa', // Livestream Chat Reader - Text-to-Speech for YouTube/Twitch chat
|
||||
'fjlalaedpfcojcfpkgkglbjjbbkofgnl', // ChatGPT Token Counter - Count tokens in real time on chatgpt conversation
|
||||
'fbgblmjbeebanackldpbmpacppflgmlj', // LinuxDo Scripts - 为 linux.do 用户提供了一些增强功能
|
||||
'dfacnjidgbagicaekenjgclfnhdnjjdi', // Zen Virtual Piano - https://zen-piano.web.app/
|
||||
'naeibcegmgpofimedkmfgjgphfhfhlab', // Crypto Pulse price tracker - https://get-crypto-pulse.web.app/
|
||||
'ffglckbhfbfmdkefdmjbhpnffkcmlhdh', // Redirect Web - Automatically redirect pages or open them in another app
|
||||
'eglpfhbhmelampoihamjomgkeobgdofl', // Capture It - Capture & Edit Screenshots
|
||||
'jmghclbfbbapimhbgnpffbimphlpolnm', // Teams Chat Exporter
|
||||
'jdcppdokgfbnhiacbeplahgnciahnhck', // Lofi BGM Player - Free lofi focus music for work & study
|
||||
'cgpmbiiagnehkikhcbnhiagfomajncpa', // Margin - Annotate and highlight any webpage, with your notes saved to the decentralized AT Protocol.
|
||||
'mfjdonmgmgcijagclnkfhmjiblbfjaid', // KeyFloat - Floating multilingual keyboard with native key mappings, drag, dark mode, sounds, and dynamic layouts for macOS & Windows
|
||||
'dhiekgdaipindoapjmcnpompdknjeijf', // Glossy New Tab - Say Goodbye to Boring Tabs with live wallpapers
|
||||
'lapnciffpekdengooeolaienkeoilfeo', // All API Hub – AI Relay & New API Manager - https://github.com/qixing-jk/all-api-hub
|
||||
'bhgobenflkkhfcgkikejaaejenoddcmo', // Scrape Similar - Extract data from websites into spreadsheets - https://github.com/zizzfizzix/scrape-similar
|
||||
'kinlknncggaihnhdcalijdmpbhbflalm', // isTrust - https://github.com/Internet-Society-Belgium/isTrust/
|
||||
'ojpakgiekphppgkcdihbjpafobhnhlkp', // Dymo
|
||||
'pmgehhllikbjmadpenhabejhpemplhmd', // Extension Rank Checker - Extension Ranker
|
||||
interface StoreLink {
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface ListedExtension {
|
||||
id: string;
|
||||
name: string;
|
||||
iconUrl: string;
|
||||
shortDescription: string;
|
||||
users: number;
|
||||
rating: number | undefined;
|
||||
stores: StoreLink[];
|
||||
}
|
||||
|
||||
const extensionEntries = _extensionEntries as Array<{
|
||||
chromeId?: string;
|
||||
firefoxSlug?: string;
|
||||
edgeId?: string;
|
||||
}>;
|
||||
|
||||
const chromeIds = extensionEntries.flatMap((e) =>
|
||||
e.chromeId ? [e.chromeId] : [],
|
||||
);
|
||||
const firefoxSlugs = [
|
||||
...new Set(
|
||||
extensionEntries.flatMap((e) => (e.firefoxSlug ? [e.firefoxSlug] : [])),
|
||||
),
|
||||
];
|
||||
const edgeIds = extensionEntries.flatMap((e) => (e.edgeId ? [e.edgeId] : []));
|
||||
|
||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||
const sortedExtensions = computed(() => {
|
||||
if (!data.value?.length) return [];
|
||||
const { data, isLoading } = useListExtensionDetails(
|
||||
chromeIds,
|
||||
firefoxSlugs,
|
||||
edgeIds,
|
||||
);
|
||||
|
||||
return [...data.value]
|
||||
.filter((item) => item != null)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
// Sort based on the user count weighted by the rating
|
||||
sortKey: ((item.rating ?? 5) / 5) * item.weeklyActiveUsers,
|
||||
}))
|
||||
.filter((item) => !!item)
|
||||
.sort((l, r) => r.sortKey - l.sortKey);
|
||||
});
|
||||
|
||||
function getStoreUrl(extension: ChromeExtension) {
|
||||
const url = new URL(extension.storeUrl);
|
||||
function addUtmSource(storeUrl: string): string {
|
||||
const url = new URL(storeUrl);
|
||||
url.searchParams.set('utm_source', 'wxt.dev');
|
||||
return url.href;
|
||||
}
|
||||
|
||||
const sortedExtensions = computed((): ListedExtension[] => {
|
||||
if (!data.value) return [];
|
||||
|
||||
const chromeById = new Map(
|
||||
(data.value.chrome ?? []).filter(Boolean).map((e) => [e.id, e]),
|
||||
);
|
||||
const firefoxBySlug = new Map(
|
||||
(data.value.firefox ?? []).filter(Boolean).map((e) => [e.id, e]),
|
||||
);
|
||||
const edgeById = new Map(
|
||||
(data.value.edge ?? []).filter(Boolean).map((e) => [e.id, e]),
|
||||
);
|
||||
|
||||
const results: ListedExtension[] = [];
|
||||
|
||||
for (const entry of extensionEntries) {
|
||||
const chrome = entry.chromeId ? chromeById.get(entry.chromeId) : undefined;
|
||||
const firefox = entry.firefoxSlug
|
||||
? firefoxBySlug.get(entry.firefoxSlug)
|
||||
: undefined;
|
||||
const edge = entry.edgeId ? edgeById.get(entry.edgeId) : undefined;
|
||||
|
||||
if (!chrome && !firefox && !edge) continue;
|
||||
|
||||
const primary = chrome ?? firefox ?? edge!;
|
||||
const users =
|
||||
(chrome?.users ?? 0) + (firefox?.users ?? 0) + (edge?.users ?? 0);
|
||||
const rating = chrome?.rating ?? firefox?.rating ?? edge?.rating;
|
||||
|
||||
const stores: StoreLink[] = [];
|
||||
if (chrome) {
|
||||
stores.push({ label: 'Chrome', url: addUtmSource(chrome.storeUrl) });
|
||||
}
|
||||
if (firefox) {
|
||||
stores.push({ label: 'Firefox', url: addUtmSource(firefox.storeUrl) });
|
||||
}
|
||||
if (edge) {
|
||||
stores.push({ label: 'Edge', url: addUtmSource(edge.storeUrl) });
|
||||
}
|
||||
|
||||
results.push({
|
||||
id:
|
||||
entry.chromeId ??
|
||||
(entry.firefoxSlug
|
||||
? `firefox:${entry.firefoxSlug}`
|
||||
: `edge:${entry.edgeId}`),
|
||||
name: primary.name,
|
||||
iconUrl: primary.iconUrl,
|
||||
shortDescription: primary.shortDescription,
|
||||
users,
|
||||
rating,
|
||||
stores,
|
||||
});
|
||||
}
|
||||
|
||||
const score = (e: ListedExtension) => ((e.rating ?? 5) / 5) * e.users;
|
||||
return results.sort((a, b) => score(b) - score(a));
|
||||
});
|
||||
|
||||
function formatUsers(n: number): string {
|
||||
return `${n.toLocaleString()} users`;
|
||||
}
|
||||
|
||||
function formatStars(r: number): string {
|
||||
const rounded = Math.round(r * 10) / 10;
|
||||
return `${rounded % 1 === 0 ? rounded.toFixed(0) : rounded.toFixed(1)} stars`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p v-if="isLoading" style="text-align: center; opacity: 50%">Loading...</p>
|
||||
<p
|
||||
v-else-if="err || sortedExtensions.length === 0"
|
||||
v-else-if="sortedExtensions.length === 0"
|
||||
style="text-align: center; opacity: 50%"
|
||||
>
|
||||
Failed to load extension details.
|
||||
</p>
|
||||
<ul v-else>
|
||||
<li
|
||||
v-for="extension of sortedExtensions"
|
||||
:key="extension.id"
|
||||
class="relative"
|
||||
>
|
||||
<ul v-else class="extension-grid">
|
||||
<li v-for="extension of sortedExtensions" :key="extension.id">
|
||||
<img
|
||||
:src="extension.iconUrl"
|
||||
:alt="`${extension.name} icon`"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<div class="relative">
|
||||
<div class="card-content">
|
||||
<a
|
||||
:href="getStoreUrl(extension)"
|
||||
:href="extension.stores[0]?.url ?? '#'"
|
||||
target="_blank"
|
||||
:title="extension.name"
|
||||
class="extension-name"
|
||||
@@ -177,19 +141,39 @@ function getStoreUrl(extension: ChromeExtension) {
|
||||
<p class="description" :title="extension.shortDescription">
|
||||
{{ extension.shortDescription }}
|
||||
</p>
|
||||
<div v-if="extension.stores.length > 0" class="store-stats">
|
||||
<p class="store-stats-info">
|
||||
<span>{{ formatUsers(extension.users) }}</span>
|
||||
<template v-if="extension.rating">
|
||||
<span class="store-stats-sep" aria-hidden="true">,</span>
|
||||
<span>{{ formatStars(extension.rating) }}</span>
|
||||
</template>
|
||||
</p>
|
||||
<p class="store-stats-sep" aria-hidden="true">·</p>
|
||||
<p class="store-links">
|
||||
<template v-for="(store, i) of extension.stores" :key="store.label">
|
||||
<a
|
||||
:href="store.url"
|
||||
target="_blank"
|
||||
class="store-link"
|
||||
:title="store.label"
|
||||
>{{ store.label }}</a
|
||||
>
|
||||
<span
|
||||
v-if="i < extension.stores.length - 1"
|
||||
class="store-stats-sep"
|
||||
aria-hidden="true"
|
||||
>, </span
|
||||
>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="user-count">
|
||||
<span>{{ extension.weeklyActiveUsers.toLocaleString() }} users</span
|
||||
><template v-if="extension.rating != null"
|
||||
>,
|
||||
<span>{{ extension.rating }} stars</span>
|
||||
</template>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="centered pr">
|
||||
<a
|
||||
href="https://github.com/wxt-dev/wxt/edit/main/docs/.vitepress/components/UsingWxtSection.vue"
|
||||
href="https://github.com/wxt-dev/wxt/edit/main/docs/assets/extension-showcase.yml"
|
||||
target="_blank"
|
||||
>Open a PR</a
|
||||
>
|
||||
@@ -198,7 +182,7 @@ function getStoreUrl(extension: ChromeExtension) {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
li img {
|
||||
.extension-grid > li > img {
|
||||
width: 116px;
|
||||
height: 116px;
|
||||
padding: 16px;
|
||||
@@ -206,7 +190,7 @@ li img {
|
||||
background-color: var(--vp-c-default-soft);
|
||||
}
|
||||
|
||||
ul {
|
||||
.extension-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
align-items: stretch;
|
||||
@@ -216,12 +200,12 @@ ul {
|
||||
padding: 0;
|
||||
}
|
||||
@media (min-width: 960px) {
|
||||
ul {
|
||||
.extension-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
.extension-grid > li {
|
||||
margin: 0 !important;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
@@ -229,28 +213,68 @@ li {
|
||||
border-radius: 12px;
|
||||
flex: 1;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
li a,
|
||||
li .user-count,
|
||||
li .description {
|
||||
.extension-grid > li .description {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
li .user-count {
|
||||
opacity: 70%;
|
||||
font-size: small;
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 16px;
|
||||
|
||||
.extension-grid > li .card-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
li a {
|
||||
.store-stats {
|
||||
margin-top: auto;
|
||||
padding-top: 6px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
align-items: baseline;
|
||||
column-gap: 6px;
|
||||
row-gap: 4px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
opacity: 0.72;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.store-stats-info {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.store-stats-sep {
|
||||
opacity: 0.45;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.store-links {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.store-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.store-link:hover {
|
||||
color: var(--vp-c-text-1);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.extension-grid > li .extension-name {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
@@ -259,33 +283,23 @@ li a {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
font-size: large;
|
||||
}
|
||||
li a:hover {
|
||||
|
||||
.extension-grid > li .extension-name:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
li div {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
li .description {
|
||||
.extension-grid > li .description {
|
||||
opacity: 90%;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
li .extension-name {
|
||||
font-size: large;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.pr {
|
||||
opacity: 70%;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,39 +1,71 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
export interface ChromeExtension {
|
||||
export interface Extension {
|
||||
id: string;
|
||||
name: string;
|
||||
iconUrl: string;
|
||||
weeklyActiveUsers: number;
|
||||
shortDescription: string;
|
||||
storeUrl: string;
|
||||
rating: number | undefined;
|
||||
users: number;
|
||||
}
|
||||
|
||||
export interface ExtensionResults {
|
||||
chrome: Extension[];
|
||||
firefox: Extension[];
|
||||
edge: Extension[];
|
||||
}
|
||||
|
||||
const operationName = 'WxtDocsUsedBy';
|
||||
const query = `query ${operationName}($ids:[String!]!) {
|
||||
chromeExtensions(ids: $ids) {
|
||||
const query = `query ${operationName}($chromeIds: [String!]!, $firefoxIds: [String!]!, $edgeIds: [String!]!) {
|
||||
chromeExtensions(ids: $chromeIds) {
|
||||
id
|
||||
name
|
||||
iconUrl
|
||||
weeklyActiveUsers
|
||||
shortDescription
|
||||
storeUrl
|
||||
rating
|
||||
...ExtensionData
|
||||
}
|
||||
firefoxAddons(ids: $firefoxIds) {
|
||||
id: slug
|
||||
...ExtensionData
|
||||
}
|
||||
edgeAddons(ids: $edgeIds) {
|
||||
id
|
||||
...ExtensionData
|
||||
}
|
||||
}
|
||||
|
||||
fragment ExtensionData on Extension {
|
||||
name
|
||||
iconUrl
|
||||
shortDescription
|
||||
storeUrl
|
||||
rating
|
||||
users
|
||||
}`;
|
||||
|
||||
export default function (ids: string[]) {
|
||||
const data = ref<ChromeExtension[]>();
|
||||
export default function (
|
||||
chromeIds: string[],
|
||||
firefoxSlugs: string[],
|
||||
edgeIds: string[],
|
||||
) {
|
||||
const data = ref<ExtensionResults>();
|
||||
const err = ref<unknown>();
|
||||
const isLoading = ref(true);
|
||||
|
||||
if (
|
||||
chromeIds.length === 0 &&
|
||||
firefoxSlugs.length === 0 &&
|
||||
edgeIds.length === 0
|
||||
) {
|
||||
data.value = { chrome: [], firefox: [], edge: [] };
|
||||
isLoading.value = false;
|
||||
return { data, err, isLoading };
|
||||
}
|
||||
|
||||
fetch('https://queue.wxt.dev/api', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
operationName,
|
||||
query,
|
||||
variables: { ids },
|
||||
variables: { chromeIds, firefoxIds: firefoxSlugs, edgeIds },
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -41,10 +73,12 @@ export default function (ids: string[]) {
|
||||
})
|
||||
.then(async (res) => {
|
||||
isLoading.value = false;
|
||||
const {
|
||||
data: { chromeExtensions },
|
||||
} = await res.json();
|
||||
data.value = chromeExtensions;
|
||||
const { data: responseData } = await res.json();
|
||||
data.value = {
|
||||
chrome: responseData.chromeExtensions ?? [],
|
||||
firefox: responseData.firefoxAddons ?? [],
|
||||
edge: responseData.edgeAddons ?? [],
|
||||
};
|
||||
err.value = undefined;
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -54,9 +88,5 @@ export default function (ids: string[]) {
|
||||
err.value = error;
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
err,
|
||||
isLoading,
|
||||
};
|
||||
return { data, err, isLoading };
|
||||
}
|
||||
|
||||
+38
-23
@@ -1,5 +1,26 @@
|
||||
import { Feed } from 'feed';
|
||||
// @ts-expect-error; It isn't TypeScript lib
|
||||
import footnote from 'markdown-it-footnote';
|
||||
import fs, { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { DefaultTheme, defineConfig } from 'vitepress';
|
||||
import addKnowledge from 'vitepress-knowledge';
|
||||
import {
|
||||
groupIconMdPlugin,
|
||||
groupIconVitePlugin,
|
||||
localIconLoader,
|
||||
} from 'vitepress-plugin-group-icons';
|
||||
import llmstxt from 'vitepress-plugin-llms';
|
||||
import { version as analyticsVersion } from '../../packages/analytics/package.json';
|
||||
import { version as autoIconsVersion } from '../../packages/auto-icons/package.json';
|
||||
import { version as i18nVersion } from '../../packages/i18n/package.json';
|
||||
import { version as runnerVersion } from '../../packages/runner/package.json';
|
||||
import { version as storageVersion } from '../../packages/storage/package.json';
|
||||
import { version as unocssVersion } from '../../packages/unocss/package.json';
|
||||
import { version as wxtVersion } from '../../packages/wxt/package.json';
|
||||
import { version as isBackgroundVersion } from '../../packages/is-background/package.json';
|
||||
import typedocSidebar from '../api/reference/typedoc-sidebar.json';
|
||||
import { meta, script } from './utils/head';
|
||||
import {
|
||||
menuGroup,
|
||||
menuItem,
|
||||
@@ -7,26 +28,6 @@ import {
|
||||
navItem,
|
||||
prepareTypedocSidebar,
|
||||
} from './utils/menus';
|
||||
import { meta, script } from './utils/head';
|
||||
import footnote from 'markdown-it-footnote';
|
||||
import { version as wxtVersion } from '../../packages/wxt/package.json';
|
||||
import { version as i18nVersion } from '../../packages/i18n/package.json';
|
||||
import { version as autoIconsVersion } from '../../packages/auto-icons/package.json';
|
||||
import { version as unocssVersion } from '../../packages/unocss/package.json';
|
||||
import { version as storageVersion } from '../../packages/storage/package.json';
|
||||
import { version as analyticsVersion } from '../../packages/analytics/package.json';
|
||||
import { version as runnerVersion } from '../../packages/runner/package.json';
|
||||
import { version as isBackgroundVersion } from '../../packages/is-background/package.json';
|
||||
import addKnowledge from 'vitepress-knowledge';
|
||||
import {
|
||||
groupIconMdPlugin,
|
||||
groupIconVitePlugin,
|
||||
localIconLoader,
|
||||
} from 'vitepress-plugin-group-icons';
|
||||
import { Feed } from 'feed';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import llmstxt from 'vitepress-plugin-llms';
|
||||
|
||||
const origin = 'https://wxt.dev';
|
||||
|
||||
@@ -73,8 +74,11 @@ export default defineConfig({
|
||||
description,
|
||||
vite: {
|
||||
clearScreen: false,
|
||||
//TODO: REMOVE THIS @TS-EXPECT-ERROR AFTER BUMP VITEPRESS TO V2.0
|
||||
plugins: [
|
||||
// @ts-expect-error: Vite version mismatch between this project and the plugin
|
||||
llmstxt(),
|
||||
// @ts-expect-error: Vite version mismatch between this project and the plugin
|
||||
groupIconVitePlugin({
|
||||
customIcon: {
|
||||
'wxt.config.ts': localIconLoader(
|
||||
@@ -83,6 +87,17 @@ export default defineConfig({
|
||||
),
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'yaml-loader',
|
||||
load: {
|
||||
handler: async (id) => {
|
||||
if (id.endsWith('.yml') || id.endsWith('.yaml')) {
|
||||
const obj = Bun.YAML.parse(await readFile(id, 'utf8'));
|
||||
return `export default ${JSON.stringify(obj)}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
lastUpdated: true,
|
||||
@@ -113,7 +128,7 @@ export default defineConfig({
|
||||
});
|
||||
// console.log('rss.xml:');
|
||||
// console.log(feed.rss2());
|
||||
await writeFile(join(site.outDir, 'rss.xml'), feed.rss2(), 'utf8');
|
||||
await fs.writeFile(join(site.outDir, 'rss.xml'), feed.rss2());
|
||||
},
|
||||
|
||||
head: [
|
||||
@@ -148,8 +163,7 @@ export default defineConfig({
|
||||
|
||||
footer: {
|
||||
message: [
|
||||
'<a class="light-netlify" href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-bg.svg" alt="Deploys by Netlify" style="display: inline;" /></a>',
|
||||
'<a class="dark-netlify" href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-accent.svg" alt="Deploys by Netlify" style="display: inline;" /></a>',
|
||||
'<span class="netlify-badge"><a class="light-netlify" href="https://www.netlify.com"><img src="/netlify-badge-light.svg" alt="Deploys by Netlify" /></a><a class="dark-netlify" href="https://www.netlify.com"><img src="/netlify-badge-dark.svg" alt="Deploys by Netlify" /></a></span>',
|
||||
'Released under the <a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE">MIT License</a>.',
|
||||
].join('<br/>'),
|
||||
copyright:
|
||||
@@ -224,6 +238,7 @@ export default defineConfig({
|
||||
menuItem('Storage', 'storage.md'),
|
||||
menuItem('Messaging', 'messaging.md'),
|
||||
menuItem('I18n', 'i18n.md'),
|
||||
menuItem('Favicons', 'favicons.md'),
|
||||
menuItem('Scripting', 'scripting.md'),
|
||||
menuItem('WXT Modules', 'wxt-modules.md'),
|
||||
menuItem('Frontend Frameworks', 'frontend-frameworks.md'),
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { createContentLoader } from 'vitepress';
|
||||
import { type ContentData, createContentLoader } from 'vitepress';
|
||||
import { PostFrontmatter } from '../utils/types';
|
||||
|
||||
export default createContentLoader('blog/*.md');
|
||||
declare const data: Array<ContentData & { frontmatter: PostFrontmatter }>;
|
||||
export { data };
|
||||
|
||||
export default createContentLoader<
|
||||
Array<ContentData & { frontmatter: PostFrontmatter }>
|
||||
>('blog/*.md');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { resolve } from 'node:path';
|
||||
import consola from 'consola';
|
||||
import spawn from 'nano-spawn';
|
||||
import { resolve } from 'node:path';
|
||||
import { version } from '../../../packages/wxt/package.json';
|
||||
|
||||
const cliDir = resolve('packages/wxt/src/cli/commands');
|
||||
const cliDirGlob = resolve(cliDir, '**');
|
||||
@@ -38,24 +39,20 @@ export default {
|
||||
|
||||
async function getHelp(command: string): Promise<string> {
|
||||
const args = command.split(' ');
|
||||
const res = await spawn(args[0], [...args.slice(1), '--help'], {
|
||||
const result = await spawn(args[0], [...args.slice(1), '--help'], {
|
||||
cwd: 'packages/wxt',
|
||||
});
|
||||
return res.stdout;
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
function getWxtHelp(command: string): Promise<string> {
|
||||
return getHelp(`pnpm -s wxt ${command}`.trim());
|
||||
async function getWxtHelp(command: string): Promise<string> {
|
||||
const res = await getHelp(`bun run --silent wxt ${command}`.trim());
|
||||
return res.replaceAll('{{version}}', version);
|
||||
}
|
||||
|
||||
async function getPublishExtensionHelp(command: string): Promise<string> {
|
||||
const res = await getHelp(
|
||||
`./node_modules/.bin/publish-extension ${command}`.trim(),
|
||||
`bun run --silent publish-extension ${command}`.trim(),
|
||||
);
|
||||
return res.replace(/\$ publish-extension/g, '$ wxt submit');
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
name: string;
|
||||
docs: string;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
/* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */
|
||||
/* Colors */
|
||||
:root {
|
||||
--wxt-c-green-1: #0b8a00;
|
||||
--wxt-c-green-2: #096600;
|
||||
--wxt-c-green-3: #096600;
|
||||
--wxt-c-green-soft: rgba(11, 138, 0, 0.14);
|
||||
|
||||
--vp-c-brand-1: var(--wxt-c-green-1);
|
||||
--vp-c-brand-2: var(--wxt-c-green-2);
|
||||
--vp-c-brand-3: var(--wxt-c-green-3);
|
||||
--vp-c-brand-soft: var(--wxt-c-green-soft);
|
||||
}
|
||||
|
||||
.dark {
|
||||
@@ -13,56 +19,65 @@
|
||||
--wxt-c-green-soft: rgba(103, 212, 94, 0.14);
|
||||
}
|
||||
|
||||
/* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */
|
||||
|
||||
:root {
|
||||
--vp-c-brand-1: var(--wxt-c-green-1);
|
||||
--vp-c-brand-2: var(--wxt-c-green-2);
|
||||
--vp-c-brand-3: var(--wxt-c-green-3);
|
||||
--vp-c-brand-soft: var(--wxt-c-green-soft);
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Customize Individual Components */
|
||||
.vp-doc .no-vertical-dividers {
|
||||
th,
|
||||
td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.vp-doc .no-vertical-dividers th,
|
||||
.vp-doc .no-vertical-dividers td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.vp-doc .no-vertical-dividers tr {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
tr {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.VPSidebar {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.VPSidebarItem .badge {
|
||||
display: inline-block;
|
||||
min-width: 1.6em;
|
||||
padding: 0.3em 0.4em 0.2em;
|
||||
border-radius: 1rem;
|
||||
font-size: 0.75em;
|
||||
line-height: 1;
|
||||
margin-left: 0.5rem;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
background-color: var(--vp-c-default-2);
|
||||
}
|
||||
|
||||
.light-netlify {
|
||||
display: inline;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.dark .light-netlify {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dark-netlify {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dark .dark-netlify {
|
||||
display: inline;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/* Add Aaron's favorite pic from Artemis II to the hero section because it's cool. */
|
||||
.dark .VPHome::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-image: url('../../assets/art002e009298.jpg');
|
||||
opacity: 50%;
|
||||
width: 100%;
|
||||
height: 110vh;
|
||||
background-size: cover;
|
||||
background-position: 90%;
|
||||
}
|
||||
|
||||
.dark .VPHome::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 10vh;
|
||||
top: 100vh;
|
||||
background: linear-gradient(to bottom, transparent, var(--vp-c-bg));
|
||||
}
|
||||
|
||||
.item {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@@ -6,15 +6,19 @@ import ExampleSearch from '../components/ExampleSearch.vue';
|
||||
import BlogLayout from '../components/BlogLayout.vue';
|
||||
import './custom.css';
|
||||
import 'virtual:group-icons.css';
|
||||
import type { EnhanceAppContext } from 'vitepress/client';
|
||||
import { setupCodeGroupSync } from '../utils/code-groups-sync';
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
enhanceApp(ctx) {
|
||||
enhanceApp(ctx: EnhanceAppContext) {
|
||||
ctx.app
|
||||
.component('Icon', Icon)
|
||||
.component('EntrypointPatterns', EntrypointPatterns)
|
||||
.component('UsingWxtSection', UsingWxtSection)
|
||||
.component('ExampleSearch', ExampleSearch)
|
||||
.component('blog', BlogLayout);
|
||||
|
||||
setupCodeGroupSync();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
const CODE_GROUP_SELECTORS = {
|
||||
root: '.vp-code-group',
|
||||
input: '.vp-code-group .tabs input',
|
||||
label: '.tabs label',
|
||||
} as const;
|
||||
|
||||
function getTabTitle(label: HTMLLabelElement) {
|
||||
return label.dataset.title?.trim() || label.textContent?.trim();
|
||||
}
|
||||
|
||||
function getCodeGroupTitle(input: HTMLInputElement) {
|
||||
const label = input.parentElement?.querySelector<HTMLLabelElement>(
|
||||
`label[for="${input.id}"]`,
|
||||
);
|
||||
|
||||
return label ? getTabTitle(label) : undefined;
|
||||
}
|
||||
|
||||
function findCodeGroupTabIndex(group: HTMLElement, title: string) {
|
||||
return Array.from(
|
||||
group.querySelectorAll<HTMLLabelElement>(CODE_GROUP_SELECTORS.label),
|
||||
).findIndex((label) => getTabTitle(label) === title);
|
||||
}
|
||||
|
||||
function syncCodeGroupSelection(group: HTMLElement, title: string) {
|
||||
const targetIndex = findCodeGroupTabIndex(group, title);
|
||||
|
||||
if (targetIndex < 0) return;
|
||||
|
||||
const blocks = group.querySelector('.blocks');
|
||||
const targetBlock = blocks?.children[targetIndex];
|
||||
if (!blocks || !targetBlock || targetBlock.classList.contains('active'))
|
||||
return;
|
||||
|
||||
blocks.querySelector('.active')?.classList.remove('active');
|
||||
targetBlock.classList.add('active');
|
||||
|
||||
const input = group.querySelectorAll<HTMLInputElement>(
|
||||
CODE_GROUP_SELECTORS.input,
|
||||
)[targetIndex];
|
||||
if (input) input.checked = true;
|
||||
}
|
||||
|
||||
function syncCodeGroupTabs(event: Event) {
|
||||
const target = event.target;
|
||||
if (
|
||||
!(target instanceof HTMLInputElement) ||
|
||||
!target.matches(CODE_GROUP_SELECTORS.input)
|
||||
)
|
||||
return;
|
||||
|
||||
const activeGroup = target.closest(CODE_GROUP_SELECTORS.root);
|
||||
const activeTitle = getCodeGroupTitle(target);
|
||||
if (!activeTitle) return;
|
||||
|
||||
document
|
||||
.querySelectorAll<HTMLElement>(CODE_GROUP_SELECTORS.root)
|
||||
.forEach((group) => {
|
||||
if (group === activeGroup) return;
|
||||
|
||||
syncCodeGroupSelection(group, activeTitle);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps every code group on the page in sync: selecting a package manager (or
|
||||
* any tab) in one group switches the matching tab in all the other groups.
|
||||
*/
|
||||
export function setupCodeGroupSync() {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
window.addEventListener('change', syncCodeGroupTabs, true);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HeadConfig } from 'vitepress/types/shared';
|
||||
import type { HeadConfig } from 'vitepress';
|
||||
|
||||
export function meta(
|
||||
property: string,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { DefaultTheme } from 'vitepress';
|
||||
|
||||
type SidebarItem = DefaultTheme.SidebarItem;
|
||||
type NavItem = DefaultTheme.NavItem;
|
||||
type NavItemWithLink = DefaultTheme.NavItemWithLink;
|
||||
type NavItemWithChildren = DefaultTheme.NavItemWithChildren;
|
||||
type NavItemChildren = DefaultTheme.NavItemChildren;
|
||||
|
||||
@@ -19,7 +17,7 @@ export function navItem(text: string, arg2?: unknown): any {
|
||||
}
|
||||
|
||||
export function menuRoot(items: SidebarItem[]) {
|
||||
return items.map((item, index) => {
|
||||
return items.map((item) => {
|
||||
// item.collapsed = false; // uncomment to expand all level-0 items
|
||||
return item;
|
||||
});
|
||||
@@ -60,12 +58,6 @@ export function menuGroup(
|
||||
throw Error('Unknown overload');
|
||||
}
|
||||
|
||||
export function menuItems(items: SidebarItem[]) {
|
||||
return {
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
export function menuItem(
|
||||
text: string,
|
||||
link: string,
|
||||
@@ -97,9 +89,7 @@ export function prepareTypedocSidebar(items: SidebarItem[]) {
|
||||
}
|
||||
};
|
||||
|
||||
// process
|
||||
prepareItems(filtered);
|
||||
|
||||
// return
|
||||
return filtered;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ContentData } from 'vitepress';
|
||||
|
||||
export interface Example {
|
||||
name: string;
|
||||
description?: string;
|
||||
@@ -16,3 +18,15 @@ export type ExamplesMetadata = {
|
||||
};
|
||||
|
||||
export type KeySelectedObject = Record<string, boolean | undefined>;
|
||||
|
||||
export interface PostFrontmatter {
|
||||
title: string;
|
||||
description?: string;
|
||||
date: Date;
|
||||
authors: { name: string; github: string }[];
|
||||
}
|
||||
|
||||
export interface Post
|
||||
extends Omit<ContentData, 'frontmatter'>, Omit<PostFrontmatter, 'date'> {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 237 KiB |
@@ -0,0 +1,520 @@
|
||||
# Add extension entries to end of the list. On the website, extensions will be sorted by a combination of users and rating.
|
||||
#
|
||||
# Change the commit message or PR title to:
|
||||
#
|
||||
# > "docs: Added "[extension name]" to showcase"
|
||||
#
|
||||
# Use:
|
||||
#
|
||||
# - `chromeId` for the Chrome Web Store listing,
|
||||
# - `firefoxSlug` for Firefox Add-ons (slug from https://addons.mozilla.org/firefox/addon/{slug}/),
|
||||
# - `edgeId` for the Edge Add-ons listing (ID from https://microsoftedge.microsoft.com/addons/detail/{slug}/{id})
|
||||
#
|
||||
# You may include one or more of these fields for your extension.
|
||||
|
||||
- # Anylang Translator
|
||||
chromeId: injdenmpjncknciccmjjmjlgbmnkince
|
||||
firefoxSlug: anylang-translator
|
||||
|
||||
- # GitHub: Better Line Counts
|
||||
chromeId: ocfdgncpifmegplaglcnglhioflaimkd
|
||||
firefoxSlug: github-better-line-counts
|
||||
|
||||
- # Anime Skip Player
|
||||
chromeId: mgmdkjcljneegjfajchedjpdhbadklcf
|
||||
firefoxSlug: anime-skip
|
||||
|
||||
- # UltraWideo
|
||||
chromeId: bfbnagnphiehemkdgmmficmjfddgfhpl
|
||||
firefoxSlug: ultrawideo
|
||||
|
||||
- # StayFree - Website Blocker & Web Analytics
|
||||
chromeId: elfaihghhjjoknimpccccmkioofjjfkf
|
||||
firefoxSlug: stayfree
|
||||
edgeId: aghmglalnfmjbipodadgbgpakneabgnp
|
||||
|
||||
- # Doozy: Ai Made Easy
|
||||
chromeId: okifoaikfmpfcamplcfjkpdnhfodpkil
|
||||
|
||||
- # tl;dv - Record, Transcribe & ChatGPT for Google Meet
|
||||
chromeId: lknmjhcajhfbbglglccadlfdjbaiifig
|
||||
|
||||
- # Youtube中文配音
|
||||
chromeId: oglffgiaiekgeicdgkdlnlkhliajdlja
|
||||
|
||||
- # PreMiD
|
||||
chromeId: agjnjboanicjcpenljmaaigopkgdnihi
|
||||
|
||||
- # Markdown Sticky Notes
|
||||
chromeId: aiakblgmlabokilgljkglggnpflljdgp
|
||||
firefoxSlug: markdown-sticky-notes
|
||||
|
||||
- # DocVersionRedirector
|
||||
chromeId: nomnkbngkijpffepcgbbofhcnafpkiep
|
||||
|
||||
- # Plex Skipper
|
||||
chromeId: ceicccfeikoipigeghddpocceifjelph
|
||||
firefoxSlug: plex-skipper
|
||||
|
||||
- # GitHub Custom Notifier
|
||||
chromeId: aelkipgppclpfimeamgmlonimflbhlgf
|
||||
firefoxSlug: github-custom-notifier
|
||||
|
||||
- # Fluent Read
|
||||
chromeId: djnlaiohfaaifbibleebjggkghlmcpcj
|
||||
|
||||
- # Facebook Video Controls
|
||||
chromeId: nhclljcpfmmaiojbhhnkpjcfmacfcian
|
||||
firefoxSlug: facebook-video-controls
|
||||
|
||||
- # ElemSnap - Quick capture of webpage elements and conversion to images,
|
||||
chromeId: mblkhbaakhbhiimkbcnmeciblfhmafna
|
||||
|
||||
- # MS Edge TTS (Text to Speech)
|
||||
chromeId: oajalfneblkfiejoadecnmodfpnaeblh
|
||||
firefoxSlug: ms-edge-tts-text-to-speech
|
||||
|
||||
- # YTBlock - Block any content from YouTube™
|
||||
chromeId: nedcanggplmbbgmlpcjiafgjcpdimpea
|
||||
|
||||
- # demo.fun - Interactive product demos that convert
|
||||
chromeId: oadbjpccljkplmhnjekgjamejnbadlne
|
||||
|
||||
- # SmartEReply: Elevate Your LinkedIn™ Engagement with AI 🚀📈
|
||||
chromeId: iopdafdcollfgaoffingmahpffckmjni
|
||||
|
||||
- # WorkFlowy MultiFlow
|
||||
chromeId: khjdmjcmpolknpccmaaipmidphjokhdf
|
||||
|
||||
- # 香草布丁🌿🍮- https://github.com/Xdy1579883916/vanilla-pudding
|
||||
chromeId: fencadnndhdeggodopebjgdfdlhcimfk
|
||||
|
||||
- # MaxFocus: Link Preview
|
||||
chromeId: bnacincmbaknlbegecpioobkfgejlojp
|
||||
firefoxSlug: maxfocus-link-preview
|
||||
|
||||
- # 汇率转换-中文版本
|
||||
chromeId: bcpgdpedphodjcjlminjbdeejccjbimp
|
||||
|
||||
- # Currency Converter Plus
|
||||
chromeId: loeilaonggnalkaiiaepbegccilkmjjp
|
||||
firefoxSlug: currency-converter-plus
|
||||
|
||||
- # Respond Easy
|
||||
chromeId: npcnninnjghigjfiecefheeibomjpkak
|
||||
|
||||
- # mindful - stay focused on your goals
|
||||
chromeId: cfkdcideecefncbglkhneoflfnmhoicc
|
||||
|
||||
- # 1Proompt
|
||||
chromeId: lnhejcpclabmbgpiiomjbhalblnnbffg
|
||||
|
||||
- # NiceTab - https://github.com/web-dahuyou/NiceTab
|
||||
chromeId: fonflmjnjbkigocpoommgmhljdpljain
|
||||
firefoxSlug: nice-tab-manager
|
||||
edgeId: ompjiaelpibiggcnanhbdblkhfdmkgnl
|
||||
|
||||
- # Draftly for LinkedIn
|
||||
chromeId: fcffekbnfcfdemeekijbbmgmkognnmkd
|
||||
|
||||
- # YouTube Summarized - Summarize any YouTube video
|
||||
chromeId: nkndldfehcidpejfkokbeghpnlbppdmo
|
||||
|
||||
- # 社媒助手 - https://github.com/iszhouhua/social-media-copilot
|
||||
chromeId: dbichmdlbjdeplpkhcejgkakobjbjalc
|
||||
|
||||
- # Dofollow Links for SEO
|
||||
chromeId: opepfpjeogkbgeigkbepobceinnfmjdd
|
||||
|
||||
- # ChatGPT Writer: Use AI on Any Site (GPT-4o, Claude, Gemini, and More)
|
||||
chromeId: pdnenlnelpdomajfejgapbdpmjkfpjkp
|
||||
|
||||
- # discord message translator
|
||||
chromeId: jobnhifpphkgoelnhnopgkdhbdkiadmj
|
||||
|
||||
- # Habit Tracker app widget for daily habit tracking
|
||||
chromeId: ncokhechhpjgjonhjnlaneglmdkfkcbj
|
||||
|
||||
- # Catppuccin for GitHub File Explorer Icons
|
||||
chromeId: lnjaiaapbakfhlbjenjkhffcdpoompki
|
||||
firefoxSlug: catppuccin-web-file-icons
|
||||
|
||||
- # WebChat: Chat with anyone on any website
|
||||
chromeId: cpaedhbidlpnbdfegakhiamfpndhjpgf
|
||||
|
||||
- # YouTube Auto HD + FPS
|
||||
chromeId: fcphghnknhkimeagdglkljinmpbagone
|
||||
firefoxSlug: youtube-auto-hd-fps
|
||||
|
||||
- # YouTube Auto Feed
|
||||
chromeId: jcdebdlnakhdinkindpogcehnhggbfad
|
||||
firefoxSlug: youtube-auto-feed
|
||||
|
||||
- # YouTube Like-Dislike Shortcut
|
||||
chromeId: fdkpkpelkkdkjhpacficichkfifijipc
|
||||
firefoxSlug: youtube-like-dislike-shortcut
|
||||
|
||||
- # Twitch Channel Points Bonus Collector
|
||||
chromeId: kbbdnbaghpcjpdhbjbccadodjejlkkgg
|
||||
firefoxSlug: twitch-cp-bonus-collector
|
||||
|
||||
- # Skillshare Player Control
|
||||
chromeId: agbhgcomfpcfboebbfmefbicfkpnlfeg
|
||||
firefoxSlug: skillshare-player-control
|
||||
|
||||
- # MultiViewer Companion
|
||||
chromeId: lpomjgbicdemjkgmbnkjncgdebogkhlb
|
||||
firefoxSlug: multiviewer-companion
|
||||
|
||||
- # Sync Watch - Watch videos together on any site
|
||||
chromeId: ggiafipgeeaaahnjamgpjcgkdpanhddg
|
||||
firefoxSlug: syncwatch
|
||||
|
||||
- # Keyword Rank Checker
|
||||
chromeId: nmldnjcblcihmegipecakhmnieiofmgl
|
||||
firefoxSlug: keyword-rank-checker-sa
|
||||
|
||||
- # YouTube Simple View - Hide distractions & more
|
||||
chromeId: gppllamhaciichleihemgilcpledblpn
|
||||
firefoxSlug: youtube-simple-view
|
||||
|
||||
- # Propbar - Property Data Enhancer
|
||||
chromeId: pccbghdfdnnkkbcdcibchpbffdgednkf
|
||||
|
||||
- # Text Search Pro - Search by case and whole-word match!
|
||||
chromeId: lfknakglefggmdkjdfhhofkjnnolffkh
|
||||
firefoxSlug: text-search-pro-ext
|
||||
|
||||
- # Invoice Generator
|
||||
chromeId: mbenhbocjckkbaojacmaepiameldglij
|
||||
firefoxSlug: quick-invoice-generator
|
||||
|
||||
- # Monthly Bill Tracker
|
||||
chromeId: phlfhkmdofajnbhgmbmjkbkdgppgoppb
|
||||
|
||||
- # Wandpen - Instantly improve your writing with AI
|
||||
chromeId: macmkmchfoclhpbncclinhjflmdkaoom
|
||||
|
||||
- # YouTube Hider - Remove Comments By Keywords, Usernames & Tools
|
||||
chromeId: lhmgechokhmdekdpgkkemoeecelcaonm
|
||||
|
||||
- # QA Compass - Record standardized bug reports easily
|
||||
chromeId: imgheieooppmahcgniieddodaliodeeg
|
||||
|
||||
- # aesthetic Notion, styled
|
||||
chromeId: npgghjedpchajflknnbngajkjkdhncdo
|
||||
|
||||
- # Eye Dropper
|
||||
chromeId: hmdcmlfkchdmnmnmheododdhjedfccka
|
||||
firefoxSlug: eye_dropper
|
||||
|
||||
- # Cursorful - Screen Recorder with Auto Zoom
|
||||
chromeId: eihpmapodnppeemkhkbhikmggfojdkjd
|
||||
|
||||
- # Show IP – Live View of Website IPs for Developers
|
||||
chromeId: hjjkgbibknbahijglkffklflidncplkn
|
||||
|
||||
- # Strong Password Generator
|
||||
chromeId: ilbikcehnpkmldojkcmlldkoelofnbde
|
||||
firefoxSlug: strongpasswordgenerator
|
||||
|
||||
- # ZenGram: Mindful Instagram, Your Way
|
||||
chromeId: ocllfkhcdopiafndigclebelbecaiocp
|
||||
|
||||
- # Blync: Preview Links, Selection Search, AI Assistant
|
||||
chromeId: odffpjnpocjfcaclnenaaaddghkgijdb
|
||||
|
||||
- # HTML to Markdown - Convert webpages to markdown
|
||||
chromeId: kofbbilhmnkcmibjbioafflgmpkbnmme
|
||||
|
||||
- # Walmart WFS Profit Calculator
|
||||
chromeId: boecmgggeigllcdocgioijmleimjbfkg
|
||||
firefoxSlug: walmart-wfs-profit-calculator
|
||||
|
||||
- # Youtube Live Chat Fullscreen
|
||||
chromeId: dlnjcbkmomenmieechnmgglgcljhoepd
|
||||
firefoxSlug: youtube-live-chat-fullscreen
|
||||
|
||||
- # Python Code Runner
|
||||
chromeId: keiealdacakpnbbljlmhfgcebmaadieg
|
||||
firefoxSlug: code-runner-manager
|
||||
|
||||
- # Monochromate
|
||||
chromeId: hafcajcllbjnoolpfngclfmmgpikdhlm
|
||||
firefoxSlug: monochromate
|
||||
|
||||
- # AliasVault - Open-Source Password & (Email) Alias Manager
|
||||
chromeId: bmoggiinmnodjphdjnmpcnlleamkfedj
|
||||
|
||||
- # SnapThePrice: AI-Powered Real-time Lowest Price Finder
|
||||
chromeId: hlnhhamckimoaiekbglafiebkfimhapb
|
||||
|
||||
- # radiofrance - news & broadcasts (French), music (international)
|
||||
chromeId: gdjampjdgjmbifnhldgcnccdjkcoicmg
|
||||
|
||||
- # Blens - Time Tracker and AI Insight
|
||||
chromeId: jlnhphlghikichhgbnkepenehbmloenb
|
||||
firefoxSlug: blens-timetracker-ai-insight
|
||||
|
||||
- # Always Light Mode - Setting website always in light mode
|
||||
chromeId: njnammmpdodmfkodnfpammnpdcbhnlcm
|
||||
|
||||
- # DesignPicker - Color Picker & Font Detector
|
||||
chromeId: lblmfclcfniabobmamfkdogcgdagbhhb
|
||||
firefoxSlug: design-colorpicker-fontdetect
|
||||
|
||||
- # Web to PDF
|
||||
chromeId: pamnlaoeobcmhkliljfaofekeddpmfoh
|
||||
firefoxSlug: export-web-to-pdf
|
||||
|
||||
- # Online CSV Viewer
|
||||
chromeId: jmbcbeepjfenihlocplnbmbhimcoooka
|
||||
firefoxSlug: csv-viewer
|
||||
|
||||
- # YouTube Video Transcript
|
||||
chromeId: nkjcoophmpcmmgadnljnlpbpfdfacgbo
|
||||
firefoxSlug: youtube-transcript-copy
|
||||
|
||||
- # NetSuite Record Scripts
|
||||
chromeId: lcaieahkjgeggeiihblhcjbbjlppgieh
|
||||
firefoxSlug: netsuite-scripts-manager
|
||||
|
||||
- # VueTracker
|
||||
chromeId: gmocfknjllodfiomnljmaehcplnekhlo
|
||||
|
||||
- # CanCopy - A web extension that allow you to copy any content from website
|
||||
chromeId: ggcfemmoabhhelfkhknhbnkmeahloiod
|
||||
|
||||
- # Read Frog
|
||||
chromeId: modkelfkcfjpgbfmnbnllalkiogfofhb
|
||||
firefoxSlug: read-frog-open-ai-translator
|
||||
|
||||
- # Bilibili Feed History Helper
|
||||
chromeId: npfopljnjbamegincfjelhjhnonnjloo
|
||||
|
||||
- # NZBDonkey - The ultimate NZB file download tool
|
||||
chromeId: edkhpdceeinkcacjdgebjehipmnbomce
|
||||
|
||||
- # WeChat Markdown Editor(微信 Markdown 编辑器)
|
||||
chromeId: cckggnbnimdbbpmdinkkgbbncopbloob
|
||||
|
||||
- # Tab Grab
|
||||
chromeId: jcblcjolcojmfopefcighfmkkefbaofg
|
||||
firefoxSlug: tab-grab
|
||||
|
||||
- # BrowserLens - https://browserlens.com/
|
||||
chromeId: eehmoikadcijkapfjocnhjclpbaindlb
|
||||
firefoxSlug: browserlens
|
||||
|
||||
- # Epic Games Library Extension
|
||||
chromeId: hfhellofkjebbchcdffmicekjdomkcmc
|
||||
firefoxSlug: epic-games-library-extension
|
||||
|
||||
- # Zen Analytics Pixel Tracker - zapt.web.app
|
||||
chromeId: gknigcbhlammoakmmdddkblknanpjiac
|
||||
|
||||
- # Crypto Pulse - Compose your newtab with nature images, widgets & realtime Crypto Price & Bitcoin RSS.
|
||||
chromeId: cnklededohhcbmjjdlbjdkkihkgoggol
|
||||
|
||||
- # Youtube Video Scheduler
|
||||
chromeId: miponnamafdenpgjemkknimgjfibicdc
|
||||
firefoxSlug: youtube-video-scheduler
|
||||
|
||||
- # Chatslator: Livestream Chat Translator
|
||||
chromeId: nhmbcmalgpkjbomhlhgdicanmkkaajmg
|
||||
firefoxSlug: chatslator
|
||||
|
||||
- # 公众号阅读增强器 - https://wxreader.honwhy.wang
|
||||
chromeId: mbamjfdjbcdgpopfnkkmlohadbbnplhm
|
||||
|
||||
- # 토탐정
|
||||
chromeId: hannhecbnjnnbbafffmogdlnajpcomek
|
||||
firefoxSlug: 토탐정
|
||||
|
||||
- # 2FAS Pass - https://2fas.com/
|
||||
chromeId: ehboaofjncodknjkngdggmpdinhdoijp
|
||||
firefoxSlug: 2fas-pass-browser-extension
|
||||
|
||||
- # Quick Prompt - https://github.com/wenyuanw/quick-prompt
|
||||
chromeId: hnjamiaoicaepbkhdoknhhcedjdocpkd
|
||||
firefoxSlug: quick-prompt
|
||||
|
||||
- # Add QR Code Generator Icon Back To Address Bar
|
||||
chromeId: kacblhilkacgfnkjfodalohcnllcgmjd
|
||||
|
||||
- # Piwik PRO Tracking Helper
|
||||
chromeId: fkbdlogfdjmpfepbbbjcgcfbgbcfcnne
|
||||
firefoxSlug: piwik-pro-tracking-helper
|
||||
|
||||
- # PIPX - Take Control of Picture-in-Picture, Automatically
|
||||
chromeId: nkbikckldmljjiiajklecmgmajgapbfl
|
||||
|
||||
- # Browsely - AI-powered browser extension
|
||||
chromeId: hgppdobcpkfkmiegekaglonjajeojmdd
|
||||
|
||||
- # Filmbudd Pro - Simple, private – and synced ratings and watch notes across all your devices
|
||||
chromeId: ehmoihnjgkdimihkhokkmfjdgomohjgm
|
||||
|
||||
- # MultiField CopyCat - Copy, Paste & Autofill Web Forms Instantly
|
||||
chromeId: alglchohmdikgdjhafiicilegegieafa
|
||||
|
||||
- # ChatSight - Add Table of Contents to ChatGPT
|
||||
chromeId: aamihahiiogceidpbnfgehacgiecephe
|
||||
|
||||
- # BetterCampus (prev. BetterCanvas)
|
||||
chromeId: cndibmoanboadcifjkjbdpjgfedanolh
|
||||
firefoxSlug: better-canvas
|
||||
|
||||
- # Leetcode Fonts - Change fonts in leetcode effortlessly
|
||||
chromeId: hinfimgacobnellbncbcpdlpaapcofaa
|
||||
|
||||
- # TranslateManga - Manga Translator & Manga Tracker
|
||||
chromeId: kbkbfefhhabpkibojinapkkgciiacggg
|
||||
|
||||
- # SiteData - Free Website Traffic Checker & Reverse AdSense Tool
|
||||
chromeId: emeakbgdecgmdjgegnejpppcnkcnoaen
|
||||
firefoxSlug: sitedata
|
||||
|
||||
- # Livestream Chat Reader - Text-to-Speech for YouTube/Twitch chat
|
||||
chromeId: gpnckbhgpnjciklpoehkmligeaebigaa
|
||||
|
||||
- # ChatGPT Token Counter - Count tokens in real time on chatgpt conversation
|
||||
chromeId: fjlalaedpfcojcfpkgkglbjjbbkofgnl
|
||||
firefoxSlug: chatgpt-token-counter
|
||||
|
||||
- # LinuxDo Scripts - 为 linux.do 用户提供了一些增强功能
|
||||
chromeId: fbgblmjbeebanackldpbmpacppflgmlj
|
||||
firefoxSlug: linux_do-scripts
|
||||
|
||||
- # Zen Virtual Piano - https://zen-piano.web.app/
|
||||
chromeId: dfacnjidgbagicaekenjgclfnhdnjjdi
|
||||
|
||||
- # Crypto Pulse price tracker - https://get-crypto-pulse.web.app/
|
||||
chromeId: naeibcegmgpofimedkmfgjgphfhfhlab
|
||||
|
||||
- # Redirect Web - Automatically redirect pages or open them in another app
|
||||
chromeId: ffglckbhfbfmdkefdmjbhpnffkcmlhdh
|
||||
|
||||
- # Capture It - Capture & Edit Screenshots
|
||||
chromeId: eglpfhbhmelampoihamjomgkeobgdofl
|
||||
|
||||
- # Teams Chat Exporter
|
||||
chromeId: jmghclbfbbapimhbgnpffbimphlpolnm
|
||||
firefoxSlug: teams-chat-exporter
|
||||
|
||||
- # Lofi BGM Player - Free lofi focus music for work & study
|
||||
chromeId: jdcppdokgfbnhiacbeplahgnciahnhck
|
||||
firefoxSlug: lofi-bgm-player
|
||||
|
||||
- # Margin - Annotate and highlight any webpage, with your notes saved to the decentralized AT Protocol.
|
||||
chromeId: cgpmbiiagnehkikhcbnhiagfomajncpa
|
||||
|
||||
- # KeyFloat - Floating multilingual keyboard with native key mappings, drag, dark mode, sounds, and dynamic layouts for macOS & Windows
|
||||
chromeId: mfjdonmgmgcijagclnkfhmjiblbfjaid
|
||||
|
||||
- # Glossy New Tab - Say Goodbye to Boring Tabs with live wallpapers
|
||||
chromeId: dhiekgdaipindoapjmcnpompdknjeijf
|
||||
firefoxSlug: glossy-new-tab
|
||||
|
||||
- # All API Hub – AI Relay & New API Manager - https://github.com/qixing-jk/all-api-hub
|
||||
chromeId: lapnciffpekdengooeolaienkeoilfeo
|
||||
firefoxSlug: all-api-hub
|
||||
|
||||
- # Scrape Similar - Extract data from websites into spreadsheets - https://github.com/zizzfizzix/scrape-similar
|
||||
chromeId: bhgobenflkkhfcgkikejaaejenoddcmo
|
||||
|
||||
- # isTrust - https://github.com/Internet-Society-Belgium/isTrust/
|
||||
chromeId: kinlknncggaihnhdcalijdmpbhbflalm
|
||||
|
||||
- # Dymo
|
||||
chromeId: ojpakgiekphppgkcdihbjpafobhnhlkp
|
||||
|
||||
- # Extension Rank Checker - Extension Ranker
|
||||
chromeId: pmgehhllikbjmadpenhabejhpemplhmd
|
||||
firefoxSlug: rank-checker
|
||||
|
||||
- # PlayFaster - Enhanced playback speed control for online videos and audio
|
||||
chromeId: fppcbkhpahkbgijkdcpjgjmhpfbmfiih
|
||||
firefoxSlug: playfaster
|
||||
|
||||
- # LatTab - Learn Latin With Every New Tab
|
||||
chromeId: eiocjaocpmackhbaffoejkcmnfbdpgpj
|
||||
firefoxSlug: lattab-learn-latin-new-tabs
|
||||
|
||||
- # Vim What? - Interactive Vim command reference
|
||||
chromeId: ngbehgnlcdjkbnihgpkgdangbhemidge
|
||||
firefoxSlug: vim-what
|
||||
|
||||
- # FRED - Fraud Recognition Easy Detection
|
||||
chromeId: bjdbcabacnlmbpcmiapcdfancfgcakfn
|
||||
firefoxSlug: fred
|
||||
|
||||
- # Clear Wisdom - Gems of wisdom from James Clear's 3-2-1 newsletter
|
||||
chromeId: jijfmgoijddfmlcdghopbkdpelbpmjdm
|
||||
firefoxSlug: clear-wisdom
|
||||
|
||||
- # Soundcloud Enhanced Pro
|
||||
chromeId: ggplcohodggmdfpopelnpplhgfjclomi
|
||||
|
||||
- # AlarmBot: ULTIMATE Web Monitoring & Smart Price Alerts
|
||||
chromeId: mpckalcodookackleecihhnngdibelif
|
||||
|
||||
- # TF2 Trader - TF2 & Steam Trading Extension - https://github.com/offish/tf2-trader
|
||||
chromeId: gmicpekfpbikhibodgokfpghadkclhoe
|
||||
|
||||
- # Mimik
|
||||
chromeId: jmfohdaflahliammccpiadmkcibohgha
|
||||
firefoxSlug: mimik
|
||||
|
||||
- # Modrinth Extras - https://github.com/creeperkatze/modrinth-extras
|
||||
chromeId: ajmkilipadfpaefpcjfgnkejalmhdlcj
|
||||
firefoxSlug: modrinth-extras
|
||||
|
||||
- # Pi-hole In One - https://github.com/creeperkatze/pihole-in-one
|
||||
chromeId: gaaobidjebianpcngcfpkniaocibidhe
|
||||
firefoxSlug: pihole-in-one
|
||||
|
||||
- # QIE Wallet – Non-Custodial Multi-Chain Crypto Wallet
|
||||
chromeId: oljchdcgmibnjbbopolafbjncfhdacjb
|
||||
|
||||
- # Headstart – Supercharge your browsing efficiency https://useheadstart.app/
|
||||
chromeId: fhhldbfombjdnjnpeojjohloohmbanda
|
||||
|
||||
- # Redmine Time Tracking
|
||||
chromeId: ldcanhhkffokndenejhafhlkapflgcjg
|
||||
firefoxSlug: redmine-time-tracking
|
||||
|
||||
- # Telsia: Eye Strain Reduction
|
||||
chromeId: iakkmikmbknfcffhalijnhijblikoldc
|
||||
firefoxSlug: telsia-eye-strain-reduction
|
||||
|
||||
- # Sound Booster & Equalizer
|
||||
chromeId: podnfipggfpfcpglhjebcefinglohbhn
|
||||
|
||||
- # Link Later
|
||||
chromeId: gpehbbegbcpjmipphokcmfhkchhcpfam
|
||||
|
||||
- # Google Cloud Skills Boost - Helper
|
||||
chromeId: lmbhjioadhcoebhgapaidogodllonbgg
|
||||
firefoxSlug: cloud-skills-boost-helper
|
||||
|
||||
- # Property Track - https://www.propertytrack.co
|
||||
chromeId: ahknifalnhmbanoclebllcolcdomfdop
|
||||
firefoxSlug: propertytrack
|
||||
|
||||
- # WageSlav3
|
||||
chromeId: anhkcmapjhiiofpcgglfljphdgppilof
|
||||
|
||||
- # LinkedIn Games Solver
|
||||
chromeId: jnhgapnkejaijibcdhcldhdfikjmdaph
|
||||
firefoxSlug: linkedin-games-solver-addon
|
||||
|
||||
- # Hayami: Anime comments & discussions
|
||||
chromeId: nhkggpiaeaeeeimohfpchnjobbamfcbg
|
||||
firefoxSlug: hayami
|
||||
|
||||
- # CopyApes Assistant
|
||||
chromeId: affmjifigldmicnbgpghddaneomejmfo
|
||||
|
||||
- # Leetcode Enhancer
|
||||
chromeId: cpoclfijojgjiafnlgnhjalkaiabcjch
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 132 KiB |
@@ -159,6 +159,30 @@ export default defineConfig({
|
||||
|
||||
Alternatively, you can use [`@wxt-dev/auto-icons`](https://www.npmjs.com/package/@wxt-dev/auto-icons) to let WXT generate your icon at the required sizes.
|
||||
|
||||
### Firefox `theme_icons`
|
||||
|
||||
Firefox supports a [`theme_icons`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action#theme_icons) field on the toolbar action that swaps between a light and dark variant based on the current browser theme.
|
||||
|
||||
When [targeting Firefox](/guide/essentials/target-different-browsers.md), WXT auto-discovers paired light/dark icons in the `public/` directory and attaches them to `action` (MV3) or `browser_action` (MV2). You only need to drop both variants next to your regular icons:
|
||||
|
||||
```plaintext
|
||||
public/
|
||||
├─ icon-16.png # regular (default_icon)
|
||||
├─ icon-light-16.png # Firefox light theme
|
||||
├─ icon-dark-16.png # Firefox dark theme
|
||||
├─ icon-32.png
|
||||
├─ icon-light-32.png
|
||||
└─ icon-dark-32.png
|
||||
```
|
||||
|
||||
A size is only included in `theme_icons` if **both** a light and a dark file are present. The following filename patterns are discovered:
|
||||
|
||||
<<< @/../packages/wxt/src/core/utils/theme-icons.ts#snippet
|
||||
|
||||
Only `.png` files are discovered today, even though Firefox supports `.svg` - follow [#1120](https://github.com/wxt-dev/wxt/issues/1120) for updates.
|
||||
|
||||
If you set `manifest.action.theme_icons` (or `manifest.browser_action.theme_icons`) explicitly in `wxt.config.ts`, WXT will not overwrite it.
|
||||
|
||||
## Permissions
|
||||
|
||||
> [Chrome docs](https://developer.chrome.com/docs/extensions/reference/permissions/)
|
||||
@@ -176,6 +200,23 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
:::warning
|
||||
|
||||
Different browsers support different permissions. You are responsible for passing only the permissions required for each browser:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: ({ browser }) => ({
|
||||
permissions:
|
||||
browser === 'chrome'
|
||||
? ['storage', 'favicon', 'declarativeNetRequest']
|
||||
: ['storage', 'webRequest'],
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Host Permissions
|
||||
|
||||
> [Chrome docs](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions#host-permissions)
|
||||
|
||||
@@ -17,28 +17,14 @@ Or if you're in a monorepo, you may not want to extend the config. If you don't
|
||||
/// <reference path="./.wxt/wxt.d.ts" />
|
||||
```
|
||||
|
||||
## Compiler Options
|
||||
|
||||
To specify custom compiler options, add them in `<rootDir>/tsconfig.json`:
|
||||
|
||||
```jsonc
|
||||
// <rootDir>/tsconfig.json
|
||||
{
|
||||
"extends": ".wxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## TSConfig Paths
|
||||
|
||||
WXT provides a default set of path aliases.
|
||||
|
||||
| Alias | To | Example |
|
||||
| ----- | ------------- | ----------------------------------------------- |
|
||||
| `~~` | `<rootDir>/*` | `import "~~/scripts"` |
|
||||
| `@@` | `<rootDir>/*` | `import "@@/scripts"` |
|
||||
| `~~` | `<rootDir>/*` | `import "~~/package.json"` |
|
||||
| `@@` | `<rootDir>/*` | `import "@@/package.json"` |
|
||||
| `~` | `<srcDir>/*` | `import { toLowerCase } from "~/utils/strings"` |
|
||||
| `@` | `<srcDir>/*` | `import { toLowerCase } from "@/utils/strings"` |
|
||||
|
||||
@@ -63,3 +49,34 @@ export default defineConfig({
|
||||
import { fakeTab } from 'testing/fake-objects';
|
||||
import { toLowerCase } from 'strings';
|
||||
```
|
||||
|
||||
## Custom Options
|
||||
|
||||
To specify custom compiler or top-level options, you have two options:
|
||||
|
||||
1. Override WXT's value completely by setting it in your root `tsconfig.json`:
|
||||
|
||||
```jsonc
|
||||
// <rootDir>/tsconfig.json
|
||||
{
|
||||
"extends": ".wxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
2. Merge custom values into values generated by WXT via the `prepare:tsconfig` hook:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
hooks: {
|
||||
'prepare:tsconfig': (wxt, { tsconfig }) => {
|
||||
tsconfig.compilerOptions.lib.push('WebWorker');
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Overriding is great for simple boolean values, but not for complex options like `paths` or `libs`, where you can't just "add" a value to the object from your `tsconfig.json` file. So WXT provides the hook that let's you add or merge or delete or do whatever you want to the config file before it's written to the `.wxt/` directory.
|
||||
|
||||
@@ -449,6 +449,15 @@ Full examples:
|
||||
- [react-content-script-ui](https://github.com/wxt-dev/examples/tree/main/examples/react-content-script-ui)
|
||||
- [tailwindcss](https://github.com/wxt-dev/examples/tree/main/examples/tailwindcss)
|
||||
|
||||
:::warning `rem` Units Are Not Fully Isolated
|
||||
|
||||
WXT resets most inherited styles via `all: initial`. This doesn't reset the `<html>` element's font size, which determines the relative size of `rem` units.
|
||||
|
||||
If your CSS framework uses `rem` units, like Tailwind CSS, you may notice your UI's scale changing on different websites.
|
||||
|
||||
See the [FAQ](/guide/resources/faq#my-content-script-ui-looks-different-on-certain-websites) for a fix.
|
||||
:::
|
||||
|
||||
### IFrame
|
||||
|
||||
If you don't need to run your UI in the same frame as the content script, you can use an IFrame to host your UI instead. Since an IFrame just hosts an HTML page, **_HMR is supported_**.
|
||||
|
||||
@@ -463,8 +463,8 @@ When you define a Newtab entrypoint, WXT will automatically update the manifest
|
||||
<meta
|
||||
name="manifest.default_icon"
|
||||
content="{
|
||||
16: '/icon-16.png',
|
||||
24: '/icon-24.png',
|
||||
'16': '/icon-16.png',
|
||||
'24': '/icon-24.png',
|
||||
...
|
||||
}"
|
||||
/>
|
||||
@@ -554,8 +554,8 @@ Firefox does not support sandboxed pages.
|
||||
<meta
|
||||
name="manifest.default_icon"
|
||||
content="{
|
||||
16: '/icon-16.png',
|
||||
24: '/icon-24.png',
|
||||
'16': '/icon-16.png',
|
||||
'24': '/icon-24.png',
|
||||
...
|
||||
}"
|
||||
/>
|
||||
|
||||
@@ -25,7 +25,7 @@ export default defineBackground({
|
||||
});
|
||||
```
|
||||
|
||||
This will change the output format to ESM, enable code-spliting between your background script and HTML pages, and set `"type": "module"` in your manifest.
|
||||
This will change the output format to ESM, enable code-splitting between your background script and HTML pages, and set `"type": "module"` in your manifest.
|
||||
|
||||
:::warning
|
||||
Only MV3 supports ESM background scripts/service workers. When targeting MV2, the `type` option is ignored and the background is always bundled into a single file as IIFE.
|
||||
|
||||
@@ -76,6 +76,28 @@ Alternatively, if you're trying to use similar APIs under different names (to su
|
||||
});
|
||||
```
|
||||
|
||||
### Augmenting the Browser Type
|
||||
|
||||
WXT's `browser` types are based on the `@types/chrome` package. That means some Firefox-specific APIs may not be typed, like `browser.sidebarAction`. If you want to add types for these APIs, you can augment the browser type to add them yourself:
|
||||
|
||||
```ts
|
||||
// <srcDir>/browser-types.d.ts
|
||||
import '@wxt-dev/browser';
|
||||
import type { SidebarAction } from 'webextension-polyfill';
|
||||
|
||||
declare module '@wxt-dev/browser' {
|
||||
namespace Browser {
|
||||
export const sidebarAction: SidebarAction.Static;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> For this to work, you may need to install `@wxt-dev/browser` as a direct dependency.
|
||||
>
|
||||
> ```sh
|
||||
> pnpm add @wxt-dev/browser
|
||||
> ```
|
||||
|
||||
## 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**.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Favicons
|
||||
|
||||
[Chrome Docs](https://developer.chrome.com/docs/extensions/how-to/ui/favicons)
|
||||
|
||||
Chromium-based browsers expose cached favicons through the `_favicon/` URL served from your extension. To use it, declare the `favicon` permission in your manifest and call `browser.runtime.getURL` with a `_favicon/` path:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['favicon'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
// Any entrypoint
|
||||
function getFaviconUrl(pageUrl: string, size = 16) {
|
||||
const url = new URL(browser.runtime.getURL('/_favicon/'));
|
||||
url.searchParams.set('pageUrl', pageUrl);
|
||||
url.searchParams.set('size', String(size));
|
||||
return url.toString();
|
||||
}
|
||||
```
|
||||
|
||||
::: warning Chromium only
|
||||
The favicon API is only available on Chromium-based browsers. Firefox has no equivalent, so `/_favicon/` URLs will not resolve at runtime there. If your extension supports both browsers, gate favicon usage behind `import.meta.env.CHROME` (or similar) and declare the permission per-browser.
|
||||
:::
|
||||
|
||||
## Usage from a content script
|
||||
|
||||
WXT does **not** add a `web_accessible_resources` entry for `_favicon/*` — not every extension needs one, and adding it unconditionally would expose internals to sites that don't need them. If you want to load a favicon inside a content script (for example, as an `<img src>`), add your own entry in `wxt.config.ts`:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
import { defineConfig } from 'wxt';
|
||||
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
permissions: ['favicon'],
|
||||
web_accessible_resources: [
|
||||
{
|
||||
resources: ['_favicon/*'],
|
||||
matches: ['<all_urls>'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -97,7 +97,7 @@ Instead, you need to configure the router to run in "hash" mode, where the routi
|
||||
|
||||
Refer to your router's docs for information about hash mode and how to enable it. Here's a non-extensive list of a few popular routers:
|
||||
|
||||
- [`react-router`](https://reactrouter.com/en/main/routers/create-hash-router)
|
||||
- [`react-router`](https://reactrouter.com/api/data-routers/createHashRouter#createhashrouter)
|
||||
- [`vue-router`](https://router.vuejs.org/guide/essentials/history-mode.html#Hash-Mode)
|
||||
- [`svelte-spa-router`](https://www.npmjs.com/package/svelte-spa-router#hash-based-routing)
|
||||
- [`solid-router`](https://github.com/solidjs/solid-router?tab=readme-ov-file#hash-mode-router)
|
||||
|
||||
@@ -163,8 +163,8 @@ yarn zip:firefox
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun i
|
||||
bun zip:firefox
|
||||
bun install
|
||||
bun run zip:firefox
|
||||
```
|
||||
|
||||
:::
|
||||
@@ -206,13 +206,13 @@ WXT uses the command `npm pack <package-name>` to download the package. That mea
|
||||
|
||||
WXT does not currently support automated publishing for Safari. Safari extensions require a native MacOS or iOS app wrapper, which WXT does not create yet. For now, if you want to publish to Safari, follow this guide:
|
||||
|
||||
- [Converting a web extension for Safari](https://developer.apple.com/documentation/safariservices/safari_web_extensions/converting_a_web_extension_for_safari) - "Convert your existing extension to a Safari web extension using Xcode’s command-line tool."
|
||||
- [Packaging a web extension for Safari](https://developer.apple.com/documentation/safariservices/packaging-a-web-extension-for-safari) - "Package your existing extension as a Safari web extension using Xcode’s command-line tool."
|
||||
|
||||
When running the `safari-web-extension-converter` CLI tool, pass the `.output/safari-mv2` or `.output/safari-mv3` directory, not your source code directory.
|
||||
When running the `safari-web-extension-packager` CLI tool, pass the `.output/safari-mv2` or `.output/safari-mv3` directory, not your source code directory.
|
||||
|
||||
```sh
|
||||
pnpm wxt build -b safari
|
||||
xcrun safari-web-extension-converter .output/safari-mv2
|
||||
xcrun safari-web-extension-packager .output/safari-mv2
|
||||
```
|
||||
|
||||
### Edge Addons
|
||||
|
||||
@@ -78,3 +78,28 @@ Here are some examples:
|
||||
```
|
||||
|
||||
Alternatively, you can use the [`filterEntrypoints` config](/api/reference/wxt/interfaces/InlineConfig#filterentrypoints) to list all the entrypoints you want to build.
|
||||
|
||||
## Per-Browser Options
|
||||
|
||||
Some entrypoint options can be customized per build target by passing an object keyed by the [target browser](/guide/essentials/target-different-browsers#target-a-browser) instead of a single value. This is useful when different browsers need different match patterns, run timings, or other entrypoint behavior:
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
matches: {
|
||||
chrome: ['*://chrome.example.com/*'],
|
||||
firefox: ['*://firefox.example.com/*'],
|
||||
},
|
||||
runAt: {
|
||||
chrome: 'document_start',
|
||||
firefox: 'document_end',
|
||||
},
|
||||
|
||||
world: {
|
||||
firefox: 'MAIN',
|
||||
},
|
||||
|
||||
main(ctx) {
|
||||
// ...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
@@ -4,33 +4,21 @@ outline: deep
|
||||
|
||||
# WXT Modules
|
||||
|
||||
WXT provides a "module system" that let's you run code at different steps in the build process to modify it.
|
||||
WXT provides a "module system" that lets you run code at different steps in the build process to modify it.
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Adding a Module
|
||||
## Installing a Module
|
||||
|
||||
There are two ways to add a module to your project:
|
||||
To use a published module from NPM, install the package and add it to your config:
|
||||
|
||||
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'],
|
||||
});
|
||||
```
|
||||
|
||||
```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.
|
||||
> Searching for ["wxt module"](https://www.npmjs.com/search?q=wxt%20module) on NPM is a good way to find published WXT modules.
|
||||
|
||||
## Module Options
|
||||
|
||||
@@ -49,7 +37,7 @@ Modules are loaded in the same order as hooks are executed. Refer to the [Hooks
|
||||
|
||||
## Writing Modules
|
||||
|
||||
Here's what a basic WXT module looks like:
|
||||
If you need custom build logic for your project, you can write your own local module. Here's what a basic WXT module looks like:
|
||||
|
||||
```ts
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
@@ -61,6 +49,14 @@ export default defineWxtModule({
|
||||
});
|
||||
```
|
||||
|
||||
To add it to your project, place the file in the `modules/` directory at the root of your project. Any module file in this directory is **automatically discovered and loaded** — no additional configuration is needed:
|
||||
|
||||
```plaintext
|
||||
<rootDir>/
|
||||
modules/
|
||||
my-module.ts ← loaded automatically
|
||||
```
|
||||
|
||||
Each module's setup function is executed after the `wxt.config.ts` file is loaded. The `wxt` object provides everything you need to write a module:
|
||||
|
||||
- Use `wxt.hook(...)` to hook into the build's lifecycle and make changes
|
||||
@@ -141,6 +137,35 @@ console.log(config.myModule);
|
||||
|
||||
This is very useful when [generating runtime code](#generate-runtime-module).
|
||||
|
||||
#### Logging
|
||||
|
||||
There are two "correct" ways to add console logs to your modules:
|
||||
|
||||
1. Use `wxt.logger` for info, warnings, and errors
|
||||
2. Install [`obug`](https://www.npmjs.com/package/obug) for debug messages
|
||||
|
||||
```ts
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
import { createDebug } from 'obug';
|
||||
|
||||
const debug = createDebug('my-module');
|
||||
|
||||
export default defineWxtModule({
|
||||
setup(wxt) {
|
||||
wxt.logger.info('Module loaded');
|
||||
debug('Debug details');
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
`wxt.logger` is great for formatted, pretty messages that match the rest of WXT's logs.
|
||||
|
||||
`obug` makes it easy for devs to enable and filter debug logs when necessary:
|
||||
|
||||
```sh
|
||||
DEBUG=my-module wxt dev
|
||||
```
|
||||
|
||||
#### Add custom entrypoint options
|
||||
|
||||
Modules can add custom options to entrypoints by augmenting the entrypoint options types. This allows you to add custom configuration that can be accessed during the build process.
|
||||
|
||||
+17
-17
@@ -10,14 +10,14 @@ Run the [`init` command](/api/cli/wxt-init), and follow the instructions.
|
||||
|
||||
:::code-group
|
||||
|
||||
```sh [PNPM]
|
||||
pnpm dlx wxt@latest init
|
||||
```
|
||||
|
||||
```sh [Bun]
|
||||
bunx wxt@latest init
|
||||
```
|
||||
|
||||
```sh [PNPM]
|
||||
pnpm dlx wxt@latest init
|
||||
```
|
||||
|
||||
```sh [NPM]
|
||||
npx wxt@latest init
|
||||
```
|
||||
@@ -46,16 +46,16 @@ 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 [PNPM]
|
||||
cd my-project
|
||||
pnpm init
|
||||
```
|
||||
|
||||
```sh [NPM]
|
||||
cd my-project
|
||||
npm init
|
||||
@@ -71,12 +71,12 @@ Once you've run the `dev` command, continue to [Next Steps](#next-steps)!
|
||||
2. Install WXT:
|
||||
:::code-group
|
||||
|
||||
```sh [PNPM]
|
||||
pnpm i -D wxt
|
||||
```sh [Bun]
|
||||
bun add -D wxt
|
||||
```
|
||||
|
||||
```sh [Bun]
|
||||
bun i -D wxt
|
||||
```sh [PNPM]
|
||||
pnpm i -D wxt
|
||||
```
|
||||
|
||||
```sh [NPM]
|
||||
@@ -119,14 +119,14 @@ 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 [PNPM]
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
```sh [NPM]
|
||||
npm run dev
|
||||
```
|
||||
|
||||
@@ -164,6 +164,36 @@ Both issues have the same cause: the library puts something outside the `ShadowR
|
||||
|
||||
Both issues have the same fix: tell the library to put elements inside the `ShadowRoot`, not outside it. See the details above for more information and example fixes for each problem.
|
||||
|
||||
## My content script UI looks different on certain websites
|
||||
|
||||
If your `createShadowRootUi` looks correct on most sites but appears at the wrong size on others (e.g., Reddit), it's because `rem` unit is relative to the `<html>` element's `font-size`, which lives outside the Shadow DOM. When a website overrides it, your UI scales incorrectly.
|
||||
|
||||
The fix is to convert `rem` units to `px` at build time using [`postcss-rem-to-responsive-pixel`](https://www.npmjs.com/package/postcss-rem-to-responsive-pixel). This eliminates the dependency on the host page's root font-size.
|
||||
|
||||
1. Install the package:
|
||||
|
||||
```sh
|
||||
bun i -D postcss-rem-to-responsive-pixel
|
||||
```
|
||||
|
||||
2. Configure your PostCSS config:
|
||||
|
||||
```js [postcss.config.mjs]
|
||||
import remToPx from 'postcss-rem-to-responsive-pixel';
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
remToPx({
|
||||
rootValue: 16,
|
||||
propList: ['*'],
|
||||
transformUnit: 'px',
|
||||
}),
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
See [Issue #678](https://github.com/wxt-dev/wxt/issues/678) for additional context on this behavior.
|
||||
|
||||
## Does WXT provide docs for LLMs?
|
||||
|
||||
Yes, WXT's documentation provides markdown files based on the [the /llms.txt proposal](https://llmstxt.org/).
|
||||
@@ -191,6 +221,18 @@ To run the WXT dev server in a devcontainer, but load the dev build of your exte
|
||||
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`.
|
||||
|
||||
4. **Enable polling if file changes are not detected**
|
||||
Some container, WSL, and network file system setups do not emit native file events reliably. Configure Chokidar polling in `wxt.config.ts`:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
watchOptions: {
|
||||
usePolling: true,
|
||||
interval: 1000,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## How do I use the new Prompt API in Chrome?
|
||||
|
||||
The service responsible for the [Prompt API](https://developer.chrome.com/docs/ai/prompt-api) is not enabled by default if you let WXT open the browser during dev mode. When checking `LanguageModel.availability`, you will always receive "unavailable".
|
||||
|
||||
@@ -476,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/essentials/config/environment-variables.html#built-in-environment-variables>
|
||||
|
||||
- `__BROWSER__` → `import.meta.env.BROWSER`
|
||||
- `__COMMAND__` → `import.meta.env.COMMAND`
|
||||
@@ -557,6 +557,8 @@ Unlisted scripts must now `export default defineUnlistedScript(...)`.
|
||||
|
||||
### `BackgroundDefinition` Type
|
||||
|
||||
<!-- cspell:disable -->
|
||||
|
||||
Rename `BackgroundScriptDefintition` to `BackgroundDefinition`.
|
||||
|
||||
## v0.6.0 → v0.7.0
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg width="114" height="50" viewBox="0 0 114 50" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path fill="#060B10" d="M0 0h114v51H0z"/><g clip-path="url(#b)" fill="#fff"><path d="M44.445 40.302v-7.695l.153-.16h1.853l.154.16v7.695l-.154.16h-1.853l-.153-.16ZM44.445 17.855v-7.696l.153-.159h1.853l.154.16v7.695l-.154.16h-1.853l-.153-.16ZM38.068 34.892h-.254l-1.275-1.323v-.264l2.388-2.482 1.349.002.18.185v1.4l-2.388 2.482ZM36.54 17.227v-.267l1.274-1.32h.254l2.388 2.479v1.398l-.18.19h-1.349l-2.388-2.48ZM28.154 24.107h10.5l.153.16v1.926l-.153.16h-10.5l-.154-.16v-1.927l.154-.16ZM99.844 24.11l.154.159v1.924l-.154.16H89.19l-.153-.16.77-1.924.154-.16h9.883ZM49 29.24h-1.853l-.153-.16v-4.502c0-.802-.303-1.423-1.234-1.443a24.2 24.2 0 0 0-1.613.025l-.089.092v5.826l-.153.16h-1.853l-.154-.16v-7.695l.154-.16h4.169c1.62 0 2.933 1.364 2.933 3.046v4.81l-.154.159v.002ZM57.974 25.871l-.154.16h-4.79l-.154.16c0 .32.31 1.283 1.546 1.283.463 0 .926-.16 1.082-.481l.154-.16h1.852l.154.16c-.154.962-.926 2.407-3.245 2.407-2.625 0-3.861-1.924-3.861-4.172 0-2.248 1.236-4.171 3.708-4.171 2.472 0 3.708 1.924 3.708 4.171v.643Zm-2.319-1.605c0-.16-.153-1.283-1.39-1.283-1.235 0-1.389 1.124-1.389 1.283l.154.16h2.472l.153-.16ZM62.298 26.833c0 .322.154.481.464.481h1.39l.153.16v1.604l-.154.16h-1.39c-1.389 0-2.625-.643-2.625-2.407v-3.529l-.154-.16H58.9l-.154-.159v-1.605l.154-.16h1.082l.154-.159v-1.443l.154-.16h1.852l.154.16v1.443l.154.16h1.699l.153.16v1.604l-.153.16h-1.7l-.153.159v3.529l.002.002ZM68.015 29.24h-1.853l-.153-.16V18.172l.153-.16h1.853l.154.16v10.907l-.154.16v.002ZM72.186 19.935h-1.852l-.154-.16v-1.604l.154-.16h1.852l.154.16v1.605l-.154.16Zm0 9.305h-1.852l-.154-.16v-7.7l.154-.159h1.852l.154.16v7.7l-.154.16ZM79.449 18.171v1.605l-.154.16h-1.39c-.309 0-.463.159-.463.48v.643l.154.16h1.546l.153.16v1.604l-.153.16h-1.546l-.154.159v5.774l-.153.16h-1.853l-.154-.16v-5.774l-.153-.16h-1.083l-.153-.159v-1.605l.153-.16h1.083l.153-.159v-.643c0-1.764 1.236-2.407 2.626-2.407h1.39l.153.16-.002.002ZM85.163 29.4c-.619 1.605-1.236 2.567-3.398 2.567h-.773l-.154-.16v-1.605l.154-.16h.773c.773 0 .926-.159 1.082-.64v-.16l-2.472-6.257v-1.604l.154-.16h1.39l.153.16 1.853 5.455h.153l1.853-5.455.154-.16h1.39l.153.16v1.604l-2.472 6.417.007-.002Z"/></g><path fill="#fff" d="M0 0v50h15V0z"/><path d="M6.518 45.367v-.774H11v.774H6.518Zm0-1.746c0-.44.098-.784.294-1.032.196-.248.474-.372.834-.372.348 0 .622.13.822.39.2.26.3.628.3 1.104l-.336.018c.004-.608.114-1.062.33-1.362.212-.3.524-.45.936-.45.424 0 .748.146.972.438.22.288.33.686.33 1.194v1.242h-.738v-1.152c0-.268-.05-.488-.15-.66a.498.498 0 0 0-.462-.258c-.224 0-.39.088-.498.264-.108.172-.162.39-.162.654v1.146h-.738V43.68c0-.192-.04-.36-.12-.504a.426.426 0 0 0-.396-.216c-.192 0-.33.068-.414.204a.955.955 0 0 0-.126.51v1.116h-.678v-1.17Zm0-2.925v-.774H9.17c.256 0 .472-.04.648-.12a.88.88 0 0 0 .39-.336.926.926 0 0 0 .126-.486.926.926 0 0 0-.126-.486.845.845 0 0 0-.39-.33 1.497 1.497 0 0 0-.648-.126H6.518v-.75h2.664c.604 0 1.068.146 1.392.438.324.288.486.71.486 1.266 0 .56-.162.984-.486 1.272-.324.288-.788.432-1.392.432H6.518Zm0-5.048v-.774H11v.774H6.518Zm0-2.475V32.4H11v.774H6.518Zm3.75-.546v-2.412H11v2.412h-.732Zm-3.69-3.903v-.773H11v.773H6.578Zm-.06 1.41v-3.6h.732v3.6h-.732Zm0-6.98v-.827l3.684-1.056v.318L6.518 20.43v-.888l3.684-1.122v.318L6.518 17.64v-.798L11 18.18v.81l-4.086 1.254v-.438L11 21.097v.756l-4.482 1.302Zm0-7.385v-.774H11v.774H6.518Zm.06-3.283v-.774H11v.774H6.578Zm-.06 1.41v-3.6h.732v3.6h-.732Zm0-4.7v-.773H11v.774H6.518Zm1.572-.545V6.179h.732V8.65H8.09ZM6.518 6.407v-.774H11v.774H6.518Z" fill="#060B10"/></g><rect x="1" y="1" width="112" height="48" rx="3" stroke="#060B10" stroke-width="2"/><defs><clipPath id="a"><rect width="114" height="50" rx="4" fill="#fff"/></clipPath><clipPath id="b"><path fill="#fff" d="M28 10h72v30.462H28z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="114" height="50" viewBox="0 0 114 50" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><rect width="114" height="50" rx="4" fill="#181A1C"/><path fill="#fff" d="M0 0h114v51H0z"/><g clip-path="url(#b)" fill="#181A1C"><path d="M44.445 40.3v-7.694l.153-.16h1.853l.154.16V40.3l-.154.16h-1.853l-.153-.16ZM44.445 17.854V10.16l.153-.16h1.853l.154.16v7.694l-.154.16h-1.853l-.153-.16ZM38.068 34.89h-.254l-1.275-1.322v-.264l2.388-2.482 1.349.002.18.184v1.4l-2.388 2.483ZM36.54 17.226v-.266l1.274-1.321h.254l2.388 2.48v1.397l-.18.19h-1.349l-2.388-2.48ZM28.154 24.106h10.5l.153.16v1.926l-.153.16h-10.5l-.154-.16v-1.926l.154-.16ZM99.844 24.109l.154.16v1.923l-.154.16H89.19l-.153-.16.77-1.924.154-.16h9.883ZM49 29.24h-1.853l-.153-.16v-4.503c0-.802-.303-1.423-1.234-1.443-.48-.012-1.027 0-1.613.025l-.089.093v5.825l-.153.16h-1.853l-.154-.16v-7.694l.154-.16h4.169c1.62 0 2.933 1.363 2.933 3.045v4.81l-.154.159v.002ZM57.974 25.87l-.154.16h-4.79l-.154.16c0 .32.31 1.283 1.546 1.283.463 0 .926-.16 1.082-.481l.154-.16h1.852l.154.16c-.154.962-.926 2.407-3.245 2.407-2.625 0-3.861-1.924-3.861-4.172 0-2.247 1.236-4.17 3.708-4.17 2.472 0 3.708 1.923 3.708 4.17v.643Zm-2.319-1.604c0-.16-.153-1.284-1.39-1.284-1.235 0-1.389 1.124-1.389 1.284l.154.16h2.472l.153-.16ZM62.298 26.832c0 .322.154.481.464.481h1.39l.153.16v1.604l-.154.16h-1.39c-1.389 0-2.625-.643-2.625-2.407V23.3l-.154-.16H58.9l-.154-.159v-1.604l.154-.16h1.082l.154-.16v-1.442l.154-.16h1.852l.154.16v1.443l.154.16h1.699l.153.159v1.604l-.153.16h-1.7l-.153.16v3.528l.002.002ZM68.015 29.24h-1.853l-.153-.16V18.17l.153-.159h1.853l.154.16v10.906l-.154.16v.002ZM72.186 19.935h-1.852l-.154-.16v-1.604l.154-.16h1.852l.154.16v1.604l-.154.16Zm0 9.304h-1.852l-.154-.16V21.38l.154-.16h1.852l.154.16v7.7l-.154.16ZM79.449 18.17v1.605l-.154.16h-1.39c-.309 0-.463.16-.463.48v.644l.154.16h1.546l.153.159v1.604l-.153.16h-1.546l-.154.16v5.773l-.153.16h-1.853l-.154-.16V23.3l-.153-.16h-1.083l-.153-.159v-1.604l.153-.16h1.083l.153-.16v-.642c0-1.764 1.236-2.407 2.626-2.407h1.39l.153.16-.002.002ZM85.163 29.399c-.619 1.604-1.236 2.566-3.398 2.566h-.773l-.154-.16v-1.604l.154-.16h.773c.773 0 .926-.159 1.082-.64v-.16l-2.472-6.256V21.38l.154-.16h1.39l.153.16 1.853 5.455h.153l1.853-5.455.154-.16h1.39l.153.16v1.605L85.156 29.4l.007-.002Z"/></g><path fill="#181A1C" d="M0 0h15v50H0z"/><path d="M5.518 45.367v-.774H10v.774H5.518Zm0-1.746c0-.44.098-.784.294-1.032.196-.248.474-.372.834-.372.348 0 .622.13.822.39.2.26.3.628.3 1.104l-.336.018c.004-.608.114-1.062.33-1.362.212-.3.524-.45.936-.45.424 0 .748.146.972.438.22.288.33.686.33 1.194v1.242h-.738v-1.152c0-.268-.05-.488-.15-.66a.498.498 0 0 0-.462-.258c-.224 0-.39.088-.498.264-.108.172-.162.39-.162.654v1.146h-.738V43.68c0-.192-.04-.36-.12-.504a.426.426 0 0 0-.396-.216c-.192 0-.33.068-.414.204a.955.955 0 0 0-.126.51v1.116h-.678v-1.17Zm0-2.925v-.774H8.17c.256 0 .472-.04.648-.12a.88.88 0 0 0 .39-.336.926.926 0 0 0 .126-.486.926.926 0 0 0-.126-.486.845.845 0 0 0-.39-.33 1.497 1.497 0 0 0-.648-.126H5.518v-.75h2.664c.604 0 1.068.146 1.392.438.324.288.486.71.486 1.266 0 .56-.162.984-.486 1.272-.324.288-.788.432-1.392.432H5.518Zm0-5.048v-.774H10v.774H5.518Zm0-2.475V32.4H10v.774H5.518Zm3.75-.546v-2.412H10v2.412h-.732Zm-3.69-3.903v-.773H10v.773H5.578Zm-.06 1.41v-3.6h.732v3.6h-.732Zm0-6.98v-.827l3.684-1.056v.318L5.518 20.43v-.888l3.684-1.122v.318L5.518 17.64v-.798L10 18.18v.81l-4.086 1.254v-.438L10 21.097v.756l-4.482 1.302Zm0-7.385v-.774H10v.774H5.518Zm.06-3.283v-.774H10v.774H5.578Zm-.06 1.41v-3.6h.732v3.6h-.732Zm0-4.7v-.773H10v.774H5.518Zm1.572-.545V6.179h.732V8.65H7.09ZM5.518 6.407v-.774H10v.774H5.518Z" fill="#fff"/></g><rect x="1" y="1" width="112" height="48" rx="3" stroke="#181A1C" stroke-width="2"/><defs><clipPath id="a"><rect width="114" height="50" rx="4" fill="#fff"/></clipPath><clipPath id="b"><path fill="#fff" d="M28 10h72v30.46H28z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
+3
-5
@@ -4,7 +4,7 @@ outline: deep
|
||||
|
||||
# WXT Storage
|
||||
|
||||
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md)
|
||||
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md) • [API Reference](/api/reference/wxt/utils/storage/interfaces/WxtStorage)
|
||||
|
||||
A simplified wrapper around the extension storage APIs.
|
||||
|
||||
@@ -74,7 +74,7 @@ await storage.watch<number>(
|
||||
await storage.getMeta<{ v: number }>('local:installDate');
|
||||
```
|
||||
|
||||
For a full list of methods available, see the [API reference](/api/reference/wxt/utils/storage/interfaces/WxtStorage).
|
||||
> This approach is fine for one-off storage fields or generic helpers, but [defining storage items](#defining-storage-items) is the recommended way to add type-safety.
|
||||
|
||||
## Watchers
|
||||
|
||||
@@ -146,7 +146,7 @@ const showChangelogOnUpdate = storage.defineItem<boolean>(
|
||||
);
|
||||
```
|
||||
|
||||
Now, instead of using the `storage` variable, you can use the helper functions on the storage item you created:
|
||||
Now, instead of using the `storage` variable, you can use the storage item instead:
|
||||
|
||||
```ts
|
||||
await showChangelogOnUpdate.getValue();
|
||||
@@ -157,8 +157,6 @@ const unwatch = showChangelogOnUpdate.watch((newValue) => {
|
||||
});
|
||||
```
|
||||
|
||||
For a full list of properties and methods available, see the [API reference](/api/reference/wxt/utils/storage/interfaces/WxtStorageItem).
|
||||
|
||||
### Versioning
|
||||
|
||||
You can add versioning to storage items if you expect them to grow or change over time. When defining the first version of an item, start with version 1.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
Output docs/assets/init-demo.gif
|
||||
|
||||
# The tools we will use
|
||||
Require pnpm
|
||||
Require bun
|
||||
|
||||
|
||||
# === Scene ====
|
||||
@@ -26,7 +26,7 @@ Set FontSize 32
|
||||
# Terminal settings
|
||||
Set Shell "bash"
|
||||
|
||||
# Terminal prompt. It looks like "● mycommand ..."
|
||||
# Terminal prompt. It looks like "● command ..."
|
||||
Env PS1 "\e[0;32m●\e[0m "
|
||||
|
||||
|
||||
@@ -35,19 +35,19 @@ Env PS1 "\e[0;32m●\e[0m "
|
||||
|
||||
Hide
|
||||
# Create a temporary folder for demo
|
||||
Type 'vhs_sandbox="$(mktemp -d)"' Enter
|
||||
Type 'cd "$vhs_sandbox"' Enter
|
||||
Type 'mkdir packages/wxt/init-demo' Enter
|
||||
Type 'cd packages/wxt/init-demo' Enter
|
||||
Type 'clear' Enter
|
||||
Show
|
||||
|
||||
|
||||
# ===== Actions =====
|
||||
|
||||
Type 'pnpm dlx wxt@latest init .' Sleep 1s Enter
|
||||
Type 'bunx wxt init .' Sleep 1s Enter
|
||||
Sleep 3.25s
|
||||
Down@750ms Enter@750ms # Select `vue` template
|
||||
Sleep 1.25s
|
||||
Down@750ms Enter@750ms # Select `pnpm` as a package manager
|
||||
Down@500ms Down@500ms Enter@750ms # Select `pnpm` as a package manager
|
||||
Sleep 5s
|
||||
|
||||
|
||||
@@ -55,5 +55,6 @@ Sleep 5s
|
||||
|
||||
Hide
|
||||
# Delete the temporary folder
|
||||
Type 'rm -rf "$vhs_sandbox"' Enter
|
||||
Type 'cd .."' Enter
|
||||
Type 'rm -rf init-demo' Enter
|
||||
Show
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
[build]
|
||||
command = "bun run docs:build"
|
||||
publish = "docs/.vitepress/dist"
|
||||
|
||||
[build.environment]
|
||||
BUN_VERSION = "1.3.5"
|
||||
+50
-25
@@ -2,54 +2,79 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.20.3"
|
||||
"bun": ">=1.3.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"check": "check && pnpm -r --sequential run check",
|
||||
"test": "pnpm -r --sequential run test run",
|
||||
"test:coverage": "pnpm -r --sequential run test:coverage",
|
||||
"check": "check && bun run --workspaces check",
|
||||
"test": "bun run --workspaces test run",
|
||||
"test:coverage": "bun run --workspaces test:coverage",
|
||||
"prepare": "simple-git-hooks",
|
||||
"docs:gen": "typedoc --options docs/typedoc.json",
|
||||
"docs:dev": "pnpm -s docs:gen && vitepress dev docs",
|
||||
"docs:build": "pnpm -s docs:gen && vitepress build docs",
|
||||
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
|
||||
"docs:dev": "bun run --silent docs:gen && bun run --bun vitepress dev docs",
|
||||
"docs:build": "bun run --silent docs:gen && bun run --bun vitepress build docs",
|
||||
"docs:preview": "bun run --silent docs:gen && bun run --bun vitepress preview docs"
|
||||
},
|
||||
"catalog": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@aklinker1/check": "^2.4.0",
|
||||
"@types/bun": "^1.3.5",
|
||||
"@types/node": "^20.17.6",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@webext-core/fake-browser": "^1.3.4",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"oxlint": "^1.63.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^6.0.3",
|
||||
"unocss": "^66.6.8",
|
||||
"vitest": "^4.1.5",
|
||||
"vitest-plugin-random-seed": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@aklinker1/check": "^2.2.0",
|
||||
"@commitlint/config-conventional": "^20.4.3",
|
||||
"@commitlint/types": "^20.4.3",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@commitlint/config-conventional": "^21.2.0",
|
||||
"@commitlint/types": "^21.2.0",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@vitest/coverage-v8": "^4.1.6",
|
||||
"changelogen": "^0.6.2",
|
||||
"consola": "^3.4.2",
|
||||
"feed": "^5.2.0",
|
||||
"cspell": "^9.7.0",
|
||||
"feed": "^5.2.1",
|
||||
"markdown-it-footnote": "^4.0.0",
|
||||
"markdownlint-cli": "^0.48.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"nano-staged": "^0.8.0",
|
||||
"markdownlint-cli": "^0.49.0",
|
||||
"nano-spawn": "catalog:",
|
||||
"nano-staged": "^1.0.2",
|
||||
"p-map": "^7.0.4",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-jsdoc": "^1.8.0",
|
||||
"semver": "^7.7.4",
|
||||
"semver": "^7.8.0",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"tsdown": "^0.21.0",
|
||||
"tinyglobby": "^0.2.16",
|
||||
"tsdown": "catalog:",
|
||||
"tsx": "4.21.0",
|
||||
"typedoc": "^0.25.4",
|
||||
"typedoc-plugin-frontmatter": "^1.3.1",
|
||||
"typedoc-plugin-markdown": "4.0.0-next.23",
|
||||
"typedoc-vitepress-theme": "1.0.0-next.3",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript": "catalog:",
|
||||
"vitepress": "^1.6.4",
|
||||
"vitepress-knowledge": "^0.4.1",
|
||||
"vitepress-plugin-group-icons": "^1.7.1",
|
||||
"vitepress-plugin-llms": "^1.11.0",
|
||||
"vitest-mock-extended": "^3.1.0",
|
||||
"vue": "^3.5.29",
|
||||
"vitepress-plugin-group-icons": "^1.7.3",
|
||||
"vitepress-plugin-llms": "^1.12.2",
|
||||
"vitest-mock-extended": "^4.0.0",
|
||||
"vue": "^3.5.34",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"markdown-it-footnote@4.0.0": "patches/markdown-it-footnote.patch",
|
||||
"simple-git-hooks@2.13.1": "patches/simple-git-hooks@2.13.1.patch"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "./node_modules/.bin/nano-staged"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
## v0.5.5
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.4...analytics-v0.5.5)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- **analytics:** Add Moderok analytics provider ([#2393](https://github.com/wxt-dev/wxt/pull/2393))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **analytics:** Change method return types from void to Promise<void> ([#2409](https://github.com/wxt-dev/wxt/pull/2409))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Add `prettier-plugin-jsdoc` to project ([#2171](https://github.com/wxt-dev/wxt/pull/2171))
|
||||
- **deps:** Upgrade deps ([#2175](https://github.com/wxt-dev/wxt/pull/2175))
|
||||
- Add prepack script to all packages ([032f7931](https://github.com/wxt-dev/wxt/commit/032f7931))
|
||||
- Use `deps.neverBundle` instead of `external` ([8f71e174](https://github.com/wxt-dev/wxt/commit/8f71e174))
|
||||
- Migrate monorepo to use Bun instead of PNPM ([#2009](https://github.com/wxt-dev/wxt/pull/2009))
|
||||
- **deps-dev:** Bump typescript from 5.9.3 to 6.0.3 ([#2325](https://github.com/wxt-dev/wxt/pull/2325))
|
||||
- Use `catalog:` for dev dependencies ([#2357](https://github.com/wxt-dev/wxt/pull/2357))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Muhammed Mustafa AKŞAM ([@muhammedaksam](https://github.com/muhammedaksam))
|
||||
- Vaughn Bosu ([@VaughnBosu](https://github.com/VaughnBosu))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v0.5.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/analytics-v0.5.3...analytics-v0.5.4)
|
||||
|
||||
@@ -5,6 +5,7 @@ Report analytics events from your web extension extension.
|
||||
## Supported Analytics Providers
|
||||
|
||||
- [Google Analytics 4 (Measurement Protocol)](#google-analytics-4-measurement-protocol)
|
||||
- [Moderok](#moderok)
|
||||
- [Umami](#umami)
|
||||
|
||||
## Install With WXT
|
||||
@@ -120,6 +121,41 @@ export default defineAppConfig({
|
||||
});
|
||||
```
|
||||
|
||||
### Moderok
|
||||
|
||||
[Moderok](https://moderok.dev) is an analytics platform built specifically for browser extensions. It requires no `host_permissions`, works in Manifest V3 service workers, and collects only anonymous usage data.
|
||||
|
||||
Sign up at [moderok.dev](https://moderok.dev) to get your app key, then save it to your `.env` file:
|
||||
|
||||
```dotenv
|
||||
WXT_MODEROK_APP_KEY='mk_...'
|
||||
```
|
||||
|
||||
Then add the `moderok` provider to your `<srcDir>/app.config.ts` file:
|
||||
|
||||
```ts
|
||||
import { moderok } from '@wxt-dev/analytics/providers/moderok';
|
||||
|
||||
export default defineAppConfig({
|
||||
analytics: {
|
||||
providers: [
|
||||
moderok({
|
||||
appKey: import.meta.env.WXT_MODEROK_APP_KEY,
|
||||
// Automatically track first open, install, update, and daily ping events (default: true)
|
||||
trackLifecycle: true,
|
||||
// Track when users uninstall the extension (default: false)
|
||||
trackUninstalls: false,
|
||||
// Optional: when trackUninstalls is on, redirect users to this page
|
||||
// after they uninstall (e.g. a feedback survey)
|
||||
uninstallUrl: 'https://example.com/uninstall',
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
For a full walkthrough — module setup, sending events, and all provider options — see the [Moderok WXT guide](https://docs.moderok.dev/guide/wxt).
|
||||
|
||||
### Umami
|
||||
|
||||
[Umami](https://umami.is/) is a privacy-first, open source analytics platform.
|
||||
|
||||
@@ -25,7 +25,7 @@ type AnalyticsMethod =
|
||||
|
||||
type MethodForwarder = <K extends keyof Analytics>(
|
||||
fn: K,
|
||||
) => (...args: Parameters<Analytics[K]>) => void;
|
||||
) => (...args: Parameters<Analytics[K]>) => Promise<void>;
|
||||
|
||||
const ANALYTICS_PORT = '@wxt-dev/analytics';
|
||||
|
||||
@@ -84,9 +84,13 @@ function createBackgroundAnalytics(
|
||||
// Cached values
|
||||
const platformInfo = browser.runtime.getPlatformInfo();
|
||||
const userAgent = UAParser();
|
||||
let userId = Promise.resolve(userIdStorage.getValue()).then(
|
||||
(id) => id ?? globalThis.crypto.randomUUID(),
|
||||
);
|
||||
let userId = Promise.resolve(userIdStorage.getValue()).then(async (id) => {
|
||||
if (id != null) return id;
|
||||
// Persist the generated ID so it's stable across service worker restarts.
|
||||
const generatedId = globalThis.crypto.randomUUID();
|
||||
await userIdStorage.setValue?.(generatedId);
|
||||
return generatedId;
|
||||
});
|
||||
let userProperties = userPropertiesStorage.getValue();
|
||||
const manifest = browser.runtime.getManifest();
|
||||
|
||||
@@ -238,6 +242,7 @@ function createFrontendAnalytics(): Analytics {
|
||||
(fn) =>
|
||||
(...args) => {
|
||||
port.postMessage({ fn, args: [...args, getFrontendMetadata()] });
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const analytics: Analytics = {
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
import { defineAnalyticsProvider } from '../client';
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
import type { BaseAnalyticsEvent } from '../types';
|
||||
|
||||
const SDK_VERSION = 'wxt/0.1.0';
|
||||
const DEFAULT_ENDPOINT = 'https://api.moderok.dev/v1/events';
|
||||
const PING_STORAGE_KEY = 'moderok:last-ping-date';
|
||||
const FIRST_OPEN_STORAGE_KEY = 'moderok:first-open';
|
||||
|
||||
export interface ModerokProviderOptions {
|
||||
appKey: string;
|
||||
endpoint?: string;
|
||||
trackLifecycle?: boolean;
|
||||
trackUninstalls?: boolean;
|
||||
uninstallUrl?: string;
|
||||
}
|
||||
|
||||
const OS_MAP: Record<string, string> = {
|
||||
mac: 'MacOS',
|
||||
win: 'Windows',
|
||||
linux: 'Linux',
|
||||
cros: 'ChromeOS',
|
||||
android: 'Android',
|
||||
};
|
||||
|
||||
const BROWSER_MAP: Record<string, string> = {
|
||||
chrome: 'chrome',
|
||||
edge: 'edge',
|
||||
firefox: 'firefox',
|
||||
chromium: 'other_chromium',
|
||||
};
|
||||
|
||||
function mapOs(wxtOs: string | undefined): string {
|
||||
if (!wxtOs) return 'unknown';
|
||||
return OS_MAP[wxtOs.toLowerCase()] ?? 'unknown';
|
||||
}
|
||||
|
||||
function mapBrowser(wxtBrowser: string | undefined): string {
|
||||
if (!wxtBrowser) return 'unknown';
|
||||
return BROWSER_MAP[wxtBrowser.toLowerCase()] ?? 'unknown';
|
||||
}
|
||||
|
||||
function detectSource(meta: BaseAnalyticsEvent['meta']): string {
|
||||
if (meta.sessionId == null) return 'background';
|
||||
|
||||
const url = meta.url;
|
||||
if (!url) return 'unknown';
|
||||
const isExtensionUrl =
|
||||
url.startsWith('chrome-extension://') || url.startsWith('moz-extension://');
|
||||
if (!isExtensionUrl) return 'content_script';
|
||||
|
||||
const path = url.toLowerCase();
|
||||
if (path.includes('popup')) return 'popup';
|
||||
if (path.includes('option')) return 'options';
|
||||
if (
|
||||
path.includes('sidepanel') ||
|
||||
path.includes('side-panel') ||
|
||||
path.includes('side_panel')
|
||||
)
|
||||
return 'side_panel';
|
||||
|
||||
return 'extension_page';
|
||||
}
|
||||
|
||||
function buildContext(event: BaseAnalyticsEvent, extensionId: string) {
|
||||
return {
|
||||
sdkVersion: SDK_VERSION,
|
||||
extensionId: extensionId || 'unknown',
|
||||
extensionVersion: event.user.properties.version || 'unknown',
|
||||
browser: mapBrowser(event.user.properties.browser),
|
||||
browserVersion: event.user.properties.browserVersion || 'unknown',
|
||||
os: mapOs(event.user.properties.os),
|
||||
locale: event.meta.language || 'unknown',
|
||||
source: detectSource(event.meta),
|
||||
};
|
||||
}
|
||||
|
||||
function sendEvent(
|
||||
endpoint: string,
|
||||
appKey: string,
|
||||
event: {
|
||||
name: string;
|
||||
userId: string;
|
||||
timestamp: number;
|
||||
context: ReturnType<typeof buildContext>;
|
||||
properties?: Record<string, string | number | boolean>;
|
||||
},
|
||||
debug: boolean,
|
||||
) {
|
||||
const payload = {
|
||||
appKey,
|
||||
sentAt: Date.now(),
|
||||
events: [
|
||||
{
|
||||
id: globalThis.crypto.randomUUID(),
|
||||
name: event.name,
|
||||
timestamp: event.timestamp,
|
||||
userId: event.userId,
|
||||
context: event.context,
|
||||
...(event.properties && Object.keys(event.properties).length > 0
|
||||
? { properties: event.properties }
|
||||
: {}),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (debug) {
|
||||
console.debug('[@wxt-dev/analytics][moderok] Sending:', payload);
|
||||
}
|
||||
|
||||
return fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
function cleanProperties(
|
||||
raw: Record<string, string | undefined> | undefined,
|
||||
): Record<string, string> | undefined {
|
||||
if (!raw) return undefined;
|
||||
const clean: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(raw)) {
|
||||
if (value != null) clean[key] = value;
|
||||
}
|
||||
return Object.keys(clean).length > 0 ? clean : undefined;
|
||||
}
|
||||
|
||||
function utcDateStamp(): string {
|
||||
return new Date().toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function buildUninstallUrl(
|
||||
endpoint: string,
|
||||
appKey: string,
|
||||
userId: string,
|
||||
redirectUrl?: string,
|
||||
): string {
|
||||
const url = new URL(endpoint);
|
||||
url.pathname = url.pathname.replace(/\/[^/]*$/, '/uninstall');
|
||||
url.searchParams.set('app', appKey);
|
||||
url.searchParams.set('uid', userId);
|
||||
if (redirectUrl) url.searchParams.set('redirect', redirectUrl);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
export const moderok = defineAnalyticsProvider<ModerokProviderOptions>(
|
||||
(analytics, config, options) => {
|
||||
const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
|
||||
const debug = config.debug ?? false;
|
||||
const trackLifecycle = options.trackLifecycle ?? true;
|
||||
const extensionId = browser.runtime.id ?? '';
|
||||
let firstOpenPromise: Promise<void> | undefined;
|
||||
|
||||
const maybeTrackFirstOpen = async (event: BaseAnalyticsEvent) => {
|
||||
if (!trackLifecycle) return;
|
||||
if (firstOpenPromise) return firstOpenPromise;
|
||||
|
||||
firstOpenPromise = (async () => {
|
||||
const stored = await browser.storage.local.get(FIRST_OPEN_STORAGE_KEY);
|
||||
if (stored[FIRST_OPEN_STORAGE_KEY]) return;
|
||||
|
||||
const response = await sendEvent(
|
||||
endpoint,
|
||||
options.appKey,
|
||||
{
|
||||
name: '__first_open',
|
||||
userId: event.user.id,
|
||||
timestamp: event.meta.timestamp,
|
||||
context: buildContext(event, extensionId),
|
||||
},
|
||||
debug,
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
await browser.storage.local.set({ [FIRST_OPEN_STORAGE_KEY]: true });
|
||||
} else {
|
||||
firstOpenPromise = undefined;
|
||||
}
|
||||
})().catch((error) => {
|
||||
firstOpenPromise = undefined;
|
||||
throw error;
|
||||
});
|
||||
|
||||
return firstOpenPromise;
|
||||
};
|
||||
|
||||
if (trackLifecycle) {
|
||||
browser.runtime.onInstalled.addListener((details) => {
|
||||
if (details.reason === 'install') {
|
||||
analytics.track('__install');
|
||||
} else if (details.reason === 'update') {
|
||||
analytics.track('__update', {
|
||||
previousVersion: (details as { previousVersion?: string })
|
||||
.previousVersion,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
void (async () => {
|
||||
const today = utcDateStamp();
|
||||
const stored = await browser.storage.local.get(PING_STORAGE_KEY);
|
||||
if (stored[PING_STORAGE_KEY] === today) return;
|
||||
await browser.storage.local.set({ [PING_STORAGE_KEY]: today });
|
||||
analytics.track('__daily_ping');
|
||||
})();
|
||||
}
|
||||
|
||||
let uninstallUrlSet = false;
|
||||
function maybeSetUninstallUrl(userId: string) {
|
||||
if (!options.trackUninstalls || uninstallUrlSet) return;
|
||||
uninstallUrlSet = true;
|
||||
|
||||
const url = buildUninstallUrl(
|
||||
endpoint,
|
||||
options.appKey,
|
||||
userId,
|
||||
options.uninstallUrl,
|
||||
);
|
||||
if (url.length <= 1023) {
|
||||
browser.runtime.setUninstallURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
identify: () => Promise.resolve(),
|
||||
|
||||
page: async (event) => {
|
||||
maybeSetUninstallUrl(event.user.id);
|
||||
await maybeTrackFirstOpen(event);
|
||||
await sendEvent(
|
||||
endpoint,
|
||||
options.appKey,
|
||||
{
|
||||
name: '__page_view',
|
||||
userId: event.user.id,
|
||||
timestamp: event.meta.timestamp,
|
||||
context: buildContext(event, extensionId),
|
||||
properties: {
|
||||
url: event.page.url,
|
||||
...(event.page.title ? { title: event.page.title } : {}),
|
||||
...(event.page.location ? { location: event.page.location } : {}),
|
||||
},
|
||||
},
|
||||
debug,
|
||||
);
|
||||
},
|
||||
|
||||
track: async (event) => {
|
||||
maybeSetUninstallUrl(event.user.id);
|
||||
await maybeTrackFirstOpen(event);
|
||||
await sendEvent(
|
||||
endpoint,
|
||||
options.appKey,
|
||||
{
|
||||
name: event.event.name.trim(),
|
||||
userId: event.user.id,
|
||||
timestamp: event.meta.timestamp,
|
||||
context: buildContext(event, extensionId),
|
||||
properties: cleanProperties(event.event.properties),
|
||||
},
|
||||
debug,
|
||||
);
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -1,20 +1,23 @@
|
||||
export interface Analytics {
|
||||
/** Report a page change. */
|
||||
page: (url: string) => void;
|
||||
page: (url: string) => Promise<void>;
|
||||
/** Report a custom event. */
|
||||
track: (
|
||||
eventName: string,
|
||||
eventProperties?: Record<string, string | undefined>,
|
||||
) => void;
|
||||
) => Promise<void>;
|
||||
/** Save information about the user. */
|
||||
identify: (userId: string, userProperties?: Record<string, string>) => void;
|
||||
identify: (
|
||||
userId: string,
|
||||
userProperties?: Record<string, string>,
|
||||
) => Promise<void>;
|
||||
/**
|
||||
* Automatically setup and track user interactions, returning a function to
|
||||
* remove any listeners that were setup.
|
||||
*/
|
||||
autoTrack: (root: Document | ShadowRoot | Element) => () => void;
|
||||
/** Calls `config.enabled.setValue`. */
|
||||
setEnabled: (enabled: boolean) => void;
|
||||
setEnabled: (enabled: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface AnalyticsConfig {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
{
|
||||
"name": "@wxt-dev/analytics",
|
||||
"version": "0.5.4",
|
||||
"version": "0.5.5",
|
||||
"description": "Add analytics to your web extension",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "buildc --deps-only -- wxt",
|
||||
"dev:build": "buildc --deps-only -- wxt build",
|
||||
"check": "pnpm build && check",
|
||||
"check": "bun run build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepack": "pnpm build",
|
||||
"prepare": "buildc --deps-only -- wxt prepare"
|
||||
"prepack": "bun run build",
|
||||
"postinstall": "buildc --deps-only -- wxt prepare",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/browser": "workspace:^",
|
||||
@@ -20,9 +22,11 @@
|
||||
"wxt": ">=0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"repository": {
|
||||
@@ -51,6 +55,10 @@
|
||||
"types": "./dist/providers/google-analytics-4.d.mts",
|
||||
"default": "./dist/providers/google-analytics-4.mjs"
|
||||
},
|
||||
"./providers/moderok": {
|
||||
"types": "./dist/providers/moderok.d.mts",
|
||||
"default": "./dist/providers/moderok.mjs"
|
||||
},
|
||||
"./providers/umami": {
|
||||
"types": "./dist/providers/umami.d.mts",
|
||||
"default": "./dist/providers/umami.mjs"
|
||||
|
||||
@@ -9,8 +9,11 @@ export default defineConfig({
|
||||
'providers/google-analytics-4':
|
||||
'./modules/analytics/providers/google-analytics-4.ts',
|
||||
'providers/umami': './modules/analytics/providers/umami.ts',
|
||||
'providers/moderok': './modules/analytics/providers/moderok.ts',
|
||||
},
|
||||
deps: {
|
||||
neverBundle: ['#analytics'],
|
||||
},
|
||||
external: ['#analytics'],
|
||||
define: {
|
||||
'process.env.NPM': 'true',
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ Install the package:
|
||||
npm i --save-dev @wxt-dev/auto-icons
|
||||
pnpm i -D @wxt-dev/auto-icons
|
||||
yarn add --dev @wxt-dev/auto-icons
|
||||
bun i -D @wxt-dev/auto-icons
|
||||
bun add -D @wxt-dev/auto-icons
|
||||
```
|
||||
|
||||
Add the module to `wxt.config.ts`:
|
||||
|
||||
@@ -39,9 +39,10 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "pnpm build && check",
|
||||
"check": "bun run build && check",
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"prepack": "pnpm build"
|
||||
"test:coverage": "bun run test run --coverage",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.0"
|
||||
@@ -51,10 +52,12 @@
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.1.38",
|
||||
"version": "0.2.0",
|
||||
"type": "module",
|
||||
"main": "src/index.mjs",
|
||||
"types": "src/index.d.ts",
|
||||
@@ -12,7 +12,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"check": "check",
|
||||
"gen": "tsx scripts/generate.ts"
|
||||
"gen": "bun run scripts/generate.ts",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'"
|
||||
},
|
||||
"author": {
|
||||
"name": "Aaron Klinker",
|
||||
@@ -23,11 +25,11 @@
|
||||
"src"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "0.1.38",
|
||||
"@types/node": "^20.0.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
"@types/chrome": "0.2.0",
|
||||
"@types/node": "catalog:",
|
||||
"nano-spawn": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/filesystem": "*",
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import spawn from 'nano-spawn';
|
||||
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join, resolve, sep } from 'node:path';
|
||||
import { sep as posixSep } from 'node:path/posix';
|
||||
import { readdir, mkdir } from 'node:fs/promises';
|
||||
import { styleText } from 'node:util';
|
||||
|
||||
// Fetch latest version
|
||||
|
||||
console.log('Getting latest version of \x1b[36m@types/chrome\x1b[0m');
|
||||
await spawn('pnpm', ['i', '--ignore-scripts', '-D', '@types/chrome@latest']);
|
||||
console.log(`Getting latest version of ${styleText('cyan', '@types/chrome')}`);
|
||||
await Bun.$`bun install --ignore-scripts -D @types/chrome@latest`;
|
||||
|
||||
// Generate new package.json
|
||||
|
||||
console.log('Generating new \x1b[36mpackage.json\x1b[0m');
|
||||
console.log(`Generating new ${styleText('cyan', 'package.json')}`);
|
||||
|
||||
const pkgJsonPath = fileURLToPath(
|
||||
import.meta.resolve('@types/chrome/package.json'),
|
||||
);
|
||||
const pkgDir = dirname(pkgJsonPath);
|
||||
const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8'));
|
||||
const pkgJsonTemplate = await readFile('templates/package.json', 'utf8');
|
||||
const pkgJson = await Bun.file(pkgJsonPath).json();
|
||||
const pkgJsonTemplate = await Bun.file('templates/package.json').text();
|
||||
const newPkgJson = JSON.parse(
|
||||
pkgJsonTemplate.replaceAll('{{chromeTypesVersion}}', pkgJson.version),
|
||||
);
|
||||
@@ -27,8 +27,8 @@ newPkgJson.peerDependencies = pkgJson.peerDependencies;
|
||||
newPkgJson.peerDependenciesMeta = pkgJson.peerDependenciesMeta;
|
||||
|
||||
const outPkgJsonPath = resolve('package.json');
|
||||
await writeFile(outPkgJsonPath, JSON.stringify(newPkgJson, null, 2));
|
||||
await spawn('pnpm', ['-w', 'prettier', '--write', outPkgJsonPath]);
|
||||
await Bun.write(outPkgJsonPath, JSON.stringify(newPkgJson));
|
||||
await Bun.$`bun run --cwd ../.. prettier --write "${outPkgJsonPath}"`;
|
||||
|
||||
// Generate declaration files
|
||||
|
||||
@@ -50,17 +50,21 @@ const declarationFileMapping = (
|
||||
}));
|
||||
|
||||
for (const { file, srcPath, destPath } of declarationFileMapping) {
|
||||
const content = await readFile(srcPath, 'utf8');
|
||||
const content = await Bun.file(srcPath).text();
|
||||
const transformedContent = transformFile(file, content);
|
||||
const destDir = dirname(destPath);
|
||||
await mkdir(destDir, { recursive: true });
|
||||
await writeFile(destPath, transformedContent);
|
||||
console.log(` \x1b[2m-\x1b[0m \x1b[36m${file}\x1b[0m`);
|
||||
await Bun.write(destPath, transformedContent);
|
||||
console.log(` ${styleText('dim', '-')} ${styleText('cyan', file)}`);
|
||||
}
|
||||
|
||||
await Bun.$`bun install --ignore-scripts`;
|
||||
|
||||
// Done!
|
||||
|
||||
console.log('\x1b[32m✔\x1b[0m Done in ' + performance.now().toFixed(0) + ' ms');
|
||||
console.log(
|
||||
`${styleText('green', '✔')} Done in ${performance.now().toFixed(0)} ms`,
|
||||
);
|
||||
|
||||
// Transformations
|
||||
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
/* DO NOT EDIT - generated by scripts/generate.ts */
|
||||
|
||||
/* eslint-disable @definitelytyped/strict-export-declare-modifiers */
|
||||
/* eslint-disable @typescript-eslint/no-wrapper-object-types */
|
||||
export namespace Browser {
|
||||
////////////////////
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
import { Entry, Log } from "har-format";
|
||||
|
||||
declare global {
|
||||
export type HARFormatEntry = Entry;
|
||||
export type HARFormatLog = Log;
|
||||
type HARFormatEntry = Entry;
|
||||
type HARFormatLog = Log;
|
||||
}
|
||||
|
||||
|
||||
+1855
-1819
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"check": "check",
|
||||
"gen": "tsx scripts/generate.ts"
|
||||
"gen": "bun run scripts/generate.ts",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'"
|
||||
},
|
||||
"author": {
|
||||
"name": "Aaron Klinker",
|
||||
@@ -24,9 +26,9 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "{{chromeTypesVersion}}",
|
||||
"@types/node": "^20.0.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
"@types/node": "catalog:",
|
||||
"nano-spawn": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
+18
-1
@@ -161,7 +161,7 @@ hello: Hello $1!
|
||||
order: Thanks for ordering your $1
|
||||
```
|
||||
|
||||
#### Escapting `$`
|
||||
#### Escaping `$`
|
||||
|
||||
To escape the dollar sign, put another `$` in front of it:
|
||||
|
||||
@@ -173,6 +173,23 @@ dollars: $$$1
|
||||
i18n.t('dollars', ['1.00']); // "$1.00"
|
||||
```
|
||||
|
||||
#### Named Substitutions
|
||||
|
||||
You can also use named substitutions with `{key}` placeholders:
|
||||
|
||||
```yml
|
||||
welcome: Hello {name}, welcome to {appName}!
|
||||
```
|
||||
|
||||
```ts
|
||||
i18n.t('welcome', {
|
||||
name: 'Ada',
|
||||
appName: 'WXT',
|
||||
}); // "Hello Ada, welcome to WXT!"
|
||||
```
|
||||
|
||||
Missing keys are left unchanged in the returned message.
|
||||
|
||||
### Plural Forms
|
||||
|
||||
> [!WARNING]
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# I18n Benchmarks
|
||||
|
||||
These benchmarks must be ran in an actual browser extension environment.
|
||||
|
||||
To run them, run `bench/run.ts` and load the `bench/` directory into a browser as an extension. Or just:
|
||||
|
||||
```sh
|
||||
bun --cwd packages/i18n bench
|
||||
```
|
||||
|
||||
Benchmarks are defined in `bench/background.ts`. Add or edit them there.
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"simple": {
|
||||
"message": "Just plain text"
|
||||
},
|
||||
"substitution": {
|
||||
"message": "Includes one substitution: $1"
|
||||
},
|
||||
"namedSubstitution": {
|
||||
"message": "Includes one named substitution: {value}"
|
||||
},
|
||||
"singular": {
|
||||
"message": "There is $1 item"
|
||||
},
|
||||
"plural": {
|
||||
"message": "There are $1 items"
|
||||
},
|
||||
"wxtPlural": {
|
||||
"message": "There is $1 item | There are $1 items"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { Bench } from 'tinybench';
|
||||
import { createI18n } from '../src';
|
||||
|
||||
declare const chrome: any;
|
||||
|
||||
export const i18n = createI18n();
|
||||
|
||||
// Keep service worker alive
|
||||
setTimeout(() => {
|
||||
chrome.runtime.getPlatformInfo();
|
||||
}, 20e3);
|
||||
|
||||
(async () => {
|
||||
const results: Record<string, any> = {};
|
||||
const runBench = async (bench: Bench) => {
|
||||
await reportProgress(`[start] ${bench.name}`);
|
||||
await bench.run();
|
||||
results[bench.name!] = bench.table();
|
||||
await reportProgress(`[done] ${bench.name}`);
|
||||
};
|
||||
|
||||
{
|
||||
const key = 'simple';
|
||||
const simple = new Bench({ name: 'Simple' })
|
||||
.add('Vanilla', () => void chrome.i18n.getMessage(key))
|
||||
.add('WXT I18n', () => void i18n.t(key));
|
||||
await runBench(simple);
|
||||
}
|
||||
|
||||
{
|
||||
const key = 'substitution';
|
||||
const value = 'test';
|
||||
const substitution = new Bench({ name: 'Substitution' })
|
||||
.add('Vanilla', () => void chrome.i18n.getMessage(key, [value]))
|
||||
.add('WXT I18n', () => void i18n.t(key, [value]));
|
||||
await runBench(substitution);
|
||||
}
|
||||
|
||||
{
|
||||
const key = 'namedSubstitution';
|
||||
const value = 'test';
|
||||
const namedSubstitution = new Bench({ name: 'Named substitution' })
|
||||
.add('Vanilla', () => void chrome.i18n.getMessage(key))
|
||||
.add('WXT I18n', () => void i18n.t(key, { value }));
|
||||
await runBench(namedSubstitution);
|
||||
}
|
||||
|
||||
{
|
||||
const vanillaPluralKey = 'plural';
|
||||
const wxtKey = 'wxtPlural';
|
||||
const one = 1;
|
||||
const pluralSingular = new Bench({
|
||||
name: 'Plural (singular form)',
|
||||
})
|
||||
.add(
|
||||
'Vanilla',
|
||||
() => void chrome.i18n.getMessage(vanillaPluralKey, [one]),
|
||||
)
|
||||
.add('WXT I18n', () => void i18n.t(wxtKey, one));
|
||||
await runBench(pluralSingular);
|
||||
|
||||
const two = 2;
|
||||
const vanillaSingularKey = 'singular';
|
||||
const pluralPlural = new Bench({
|
||||
name: 'Plural (plural form)',
|
||||
time: 1000,
|
||||
})
|
||||
.add(
|
||||
'Vanilla',
|
||||
() => void chrome.i18n.getMessage(vanillaSingularKey, [two]),
|
||||
)
|
||||
.add('WXT I18n', () => void i18n.t(wxtKey, two));
|
||||
await runBench(pluralPlural);
|
||||
}
|
||||
|
||||
// Report results
|
||||
await reportResults(results);
|
||||
})();
|
||||
|
||||
async function reportProgress(message: string) {
|
||||
const res = await fetch('http://localhost:3000/progress', {
|
||||
method: 'POST',
|
||||
body: message,
|
||||
});
|
||||
if (!res.ok) throw Error('Progress report failed...');
|
||||
}
|
||||
|
||||
async function reportResults(results: any) {
|
||||
const res = await fetch('http://localhost:3000/results', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(results, null, 2),
|
||||
});
|
||||
if (!res.ok) throw Error('Results report failed...');
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/chrome-manifest",
|
||||
"name": "WXT I18n Benchmark",
|
||||
"version": "1.0.0",
|
||||
"manifest_version": 3,
|
||||
"default_locale": "en",
|
||||
"background": {
|
||||
"type": "module",
|
||||
"service_worker": "dist/background.js"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { join, relative } from 'node:path';
|
||||
|
||||
const distDir = join(import.meta.dir, 'dist');
|
||||
const backgroundSrc = join(import.meta.dir, 'background.ts');
|
||||
|
||||
console.log('\nBuilding benchmark extension...');
|
||||
await Bun.build({
|
||||
entrypoints: [backgroundSrc],
|
||||
outdir: distDir,
|
||||
format: 'esm',
|
||||
});
|
||||
console.log('Done!');
|
||||
|
||||
console.log(
|
||||
`\nInstall the extension from ./${relative(process.cwd(), import.meta.dir)} (or reload it) to run benchmarks.`,
|
||||
);
|
||||
console.log('Waiting for benchmark results...');
|
||||
|
||||
const CORS_HEADERS = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
};
|
||||
|
||||
Bun.serve({
|
||||
fetch: async (request) => {
|
||||
// CORS support
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(undefined, { headers: CORS_HEADERS });
|
||||
}
|
||||
|
||||
const url = new URL(request.url);
|
||||
if (url.pathname === '/progress') {
|
||||
console.log(`\x1b[2m← ${await request.text()}\x1b[0m`);
|
||||
return new Response(undefined, { status: 202, headers: CORS_HEADERS });
|
||||
}
|
||||
|
||||
if (url.pathname === '/results') {
|
||||
const body = await request.json();
|
||||
for (const [name, table] of Object.entries(body)) {
|
||||
console.log('\n' + name);
|
||||
console.table(table);
|
||||
}
|
||||
setTimeout(() => void process.exit(0));
|
||||
return new Response(undefined, { status: 202, headers: CORS_HEADERS });
|
||||
}
|
||||
|
||||
return new Response(undefined, { status: 404, headers: CORS_HEADERS });
|
||||
},
|
||||
});
|
||||
@@ -24,13 +24,15 @@
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "buildc --deps-only -- check",
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"prepack": "pnpm build"
|
||||
"bench": "buildc --deps-only -- bun run bench/run.ts",
|
||||
"test:coverage": "bun run test run --coverage",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/browser": "workspace:^",
|
||||
"chokidar": "^5.0.0",
|
||||
"confbox": "^0.2.4",
|
||||
"tinyglobby": "^0.2.15"
|
||||
"tinyglobby": "^0.2.16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.7"
|
||||
@@ -41,12 +43,15 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.17.6",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18",
|
||||
"vitest-plugin-random-seed": "^1.1.2",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tinybench": "^6.0.1",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vitest-plugin-random-seed": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('Built Tools', () => {
|
||||
const fileText = stringifyYAML({
|
||||
simple: 'example',
|
||||
sub: 'Hello $1',
|
||||
named: 'Hello {name} from {tool}',
|
||||
nested: {
|
||||
example: 'This is nested',
|
||||
array: ['One', 'Two'],
|
||||
@@ -33,11 +34,19 @@ describe('Built Tools', () => {
|
||||
message: 'test 2',
|
||||
description: 'test',
|
||||
},
|
||||
chromeNamed: {
|
||||
message: 'test {value}',
|
||||
description: 'test',
|
||||
},
|
||||
plural0: {
|
||||
0: 'Zero items',
|
||||
1: 'One item',
|
||||
n: '$1 items',
|
||||
},
|
||||
pluralNamed: {
|
||||
1: 'One {item}',
|
||||
n: '$1 {item}',
|
||||
},
|
||||
plural1: {
|
||||
1: 'One item',
|
||||
n: '$1 items',
|
||||
@@ -68,6 +77,9 @@ describe('Built Tools', () => {
|
||||
"sub": {
|
||||
"message": "Hello $1"
|
||||
},
|
||||
"named": {
|
||||
"message": "Hello {name} from {tool}"
|
||||
},
|
||||
"nested_example": {
|
||||
"message": "This is nested"
|
||||
},
|
||||
@@ -84,9 +96,16 @@ describe('Built Tools', () => {
|
||||
"message": "test 2",
|
||||
"description": "test"
|
||||
},
|
||||
"chromeNamed": {
|
||||
"message": "test {value}",
|
||||
"description": "test"
|
||||
},
|
||||
"plural0": {
|
||||
"message": "Zero items | One item | $1 items"
|
||||
},
|
||||
"pluralNamed": {
|
||||
"message": "One {item} | $1 {item}"
|
||||
},
|
||||
"plural1": {
|
||||
"message": "One item | $1 items"
|
||||
},
|
||||
@@ -103,12 +122,15 @@ describe('Built Tools', () => {
|
||||
"export type GeneratedI18nStructure = {
|
||||
"simple": { substitutions: 0, plural: false };
|
||||
"sub": { substitutions: 1, plural: false };
|
||||
"named": { substitutions: 0, plural: false, namedSubstitutions: ["name","tool"] };
|
||||
"nested.example": { substitutions: 0, plural: false };
|
||||
"nested.array.0": { substitutions: 0, plural: false };
|
||||
"nested.array.1": { substitutions: 0, plural: false };
|
||||
"nested.notChrome.message": { substitutions: 0, plural: false };
|
||||
"chrome": { substitutions: 0, plural: false };
|
||||
"chromeNamed": { substitutions: 0, plural: false, namedSubstitutions: ["value"] };
|
||||
"plural0": { substitutions: 1, plural: true };
|
||||
"pluralNamed": { substitutions: 1, plural: true, namedSubstitutions: ["item"] };
|
||||
"plural1": { substitutions: 1, plural: true };
|
||||
"pluralN": { substitutions: 1, plural: true };
|
||||
"pluralSub": { substitutions: 2, plural: true };
|
||||
|
||||
@@ -68,4 +68,47 @@ describe('createI18n', () => {
|
||||
i18n.t('key', 3, ['custom']);
|
||||
expect(getMessageMock).toBeCalledWith('key', ['custom']);
|
||||
});
|
||||
|
||||
it('should replace named substitutions', () => {
|
||||
const i18n = createI18n();
|
||||
getMessageMock.mockReturnValue('My name is {name}, and I use {tool}.');
|
||||
|
||||
const actual = i18n.t('key', {
|
||||
name: 'Ada',
|
||||
tool: 'WXT',
|
||||
});
|
||||
|
||||
expect(actual).toBe('My name is Ada, and I use WXT.');
|
||||
expect(getMessageMock).toBeCalledTimes(1);
|
||||
expect(getMessageMock).toBeCalledWith('key');
|
||||
});
|
||||
|
||||
it('should leave unknown named substitutions unchanged', () => {
|
||||
const i18n = createI18n();
|
||||
getMessageMock.mockReturnValue('Hello {name} from {team}.');
|
||||
|
||||
const actual = i18n.t('key', { name: 'Ada' });
|
||||
|
||||
expect(actual).toBe('Hello Ada from {team}.');
|
||||
});
|
||||
|
||||
it('should combine positional and named substitutions', () => {
|
||||
const i18n = createI18n();
|
||||
getMessageMock.mockReturnValue('Hello Ada from {team}.');
|
||||
|
||||
const actual = i18n.t('key', ['Ada'], { team: 'WXT' });
|
||||
|
||||
expect(actual).toBe('Hello Ada from WXT.');
|
||||
expect(getMessageMock).toBeCalledWith('key', ['Ada']);
|
||||
});
|
||||
|
||||
it('should replace named substitutions after pluralization', () => {
|
||||
const i18n = createI18n();
|
||||
getMessageMock.mockReturnValue('One {item} | Multiple {item}');
|
||||
|
||||
const actual = i18n.t('key', 2, { item: 'files' });
|
||||
|
||||
expect(actual).toBe('Multiple files');
|
||||
expect(getMessageMock).toBeCalledWith('key', ['2']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,14 +35,36 @@ describe('I18n Types', () => {
|
||||
});
|
||||
|
||||
describe('With type-safety', () => {
|
||||
const i18n = createI18n<{
|
||||
type MyStructure = {
|
||||
simple: { plural: false; substitutions: 0 };
|
||||
simpleSub1: { plural: false; substitutions: 1 };
|
||||
simpleSub2: { plural: false; substitutions: 2 };
|
||||
named: {
|
||||
plural: false;
|
||||
substitutions: 0;
|
||||
namedSubstitutions: ['name', 'tool'];
|
||||
};
|
||||
mixed: {
|
||||
plural: false;
|
||||
substitutions: 1;
|
||||
namedSubstitutions: ['team'];
|
||||
};
|
||||
plural: { plural: true; substitutions: 0 };
|
||||
pluralSub1: { plural: true; substitutions: 1 };
|
||||
pluralSub2: { plural: true; substitutions: 2 };
|
||||
}>();
|
||||
pluralNamed: {
|
||||
plural: true;
|
||||
substitutions: 1;
|
||||
namedSubstitutions: ['item'];
|
||||
};
|
||||
pluralMixed: {
|
||||
plural: true;
|
||||
substitutions: 2;
|
||||
namedSubstitutions: ['item'];
|
||||
};
|
||||
};
|
||||
|
||||
const i18n = createI18n<MyStructure>();
|
||||
|
||||
describe('t', () => {
|
||||
it('should only allow passing valid combinations of arguments', () => {
|
||||
@@ -74,6 +96,22 @@ describe('I18n Types', () => {
|
||||
// @ts-expect-error
|
||||
i18n.t('simpleSub2', n);
|
||||
|
||||
i18n.t('named', { name: 'Ada', tool: 'WXT' });
|
||||
// @ts-expect-error
|
||||
i18n.t('named');
|
||||
// @ts-expect-error
|
||||
i18n.t('named', { name: 'Ada' });
|
||||
// @ts-expect-error
|
||||
i18n.t('named', ['Ada', 'WXT']);
|
||||
|
||||
i18n.t('mixed', ['Ada'], { team: 'WXT' });
|
||||
// @ts-expect-error
|
||||
i18n.t('mixed', { team: 'WXT' });
|
||||
// @ts-expect-error
|
||||
i18n.t('mixed', ['Ada']);
|
||||
// @ts-expect-error
|
||||
i18n.t('mixed', ['Ada'], {});
|
||||
|
||||
i18n.t('plural', n);
|
||||
// @ts-expect-error
|
||||
i18n.t('plural');
|
||||
@@ -107,6 +145,25 @@ describe('I18n Types', () => {
|
||||
i18n.t('pluralSub2', n, ['one', 'two', 'three']);
|
||||
// @ts-expect-error
|
||||
i18n.t('pluralSub2', n);
|
||||
|
||||
i18n.t('pluralNamed', n, { item: 'files' });
|
||||
i18n.t('pluralNamed', n, ['files'], { item: 'files' });
|
||||
// @ts-expect-error
|
||||
i18n.t('pluralNamed', n);
|
||||
// @ts-expect-error
|
||||
i18n.t('pluralNamed', n, {});
|
||||
// @ts-expect-error
|
||||
i18n.t('pluralNamed', n, ['files']);
|
||||
|
||||
i18n.t('pluralMixed', n, ['1', 'files'], { item: 'reports' });
|
||||
// @ts-expect-error
|
||||
i18n.t('pluralMixed', n, { item: 'reports' });
|
||||
// @ts-expect-error
|
||||
i18n.t('pluralMixed', n, ['1', 'files']);
|
||||
// @ts-expect-error
|
||||
i18n.t('pluralMixed', n, ['1'], { item: 'reports' });
|
||||
// @ts-expect-error
|
||||
i18n.t('pluralMixed', n, ['1', 'files'], {});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ChromeMessage } from '../build';
|
||||
import {
|
||||
applyNamedSubstitutions,
|
||||
applyChromeMessagePlaceholders,
|
||||
getNamedSubstitutionNames,
|
||||
getSubstitutionCount,
|
||||
standardizeLocale,
|
||||
} from '../utils';
|
||||
@@ -42,8 +44,36 @@ describe('Utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyNamedSubstitutions', () => {
|
||||
it('should replace named substitutions', () => {
|
||||
expect(
|
||||
applyNamedSubstitutions('Hello {name}, welcome to {tool}', {
|
||||
name: 'Ada',
|
||||
tool: 'WXT',
|
||||
}),
|
||||
).toBe('Hello Ada, welcome to WXT');
|
||||
});
|
||||
|
||||
it('should leave missing substitutions unchanged', () => {
|
||||
expect(applyNamedSubstitutions('Hello {name}', {})).toBe('Hello {name}');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNamedSubstitutionNames', () => {
|
||||
it('should return unique substitution names in order', () => {
|
||||
expect(getNamedSubstitutionNames('{name} uses {tool}: {name}')).toEqual([
|
||||
'name',
|
||||
'tool',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array when no named substitutions are present', () => {
|
||||
expect(getNamedSubstitutionNames('Hello $1')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSubstitutionCount', () => {
|
||||
it('should return the last substution present in the message', () => {
|
||||
it('should return the last substitution present in the message', () => {
|
||||
expect(getSubstitutionCount('I like $1, but I like $2 better')).toBe(2);
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { parseYAML, parseJSON5, parseTOML } from 'confbox';
|
||||
import { dirname, extname } from 'node:path';
|
||||
import { applyChromeMessagePlaceholders, getSubstitutionCount } from './utils';
|
||||
import {
|
||||
applyChromeMessagePlaceholders,
|
||||
getNamedSubstitutionNames,
|
||||
getSubstitutionCount,
|
||||
} from './utils';
|
||||
|
||||
export { SUPPORTED_LOCALES } from './supported-locales';
|
||||
|
||||
@@ -16,15 +20,29 @@ export { SUPPORTED_LOCALES } from './supported-locales';
|
||||
// TYPES
|
||||
//
|
||||
|
||||
export type SimpleMessage = string;
|
||||
|
||||
export type PluralMessage = Record<'n' | number, string>;
|
||||
|
||||
export interface ChromeMessage {
|
||||
message: string;
|
||||
description?: string;
|
||||
placeholders?: Record<string, { content: string; example?: string }>;
|
||||
}
|
||||
|
||||
export type Message =
|
||||
| SimpleMessage
|
||||
| PluralMessage
|
||||
| ChromeMessage
|
||||
| Message[]
|
||||
| { [key: string]: Message };
|
||||
|
||||
export type MessagesObject = Record<string, Message>;
|
||||
|
||||
export interface ParsedBaseMessage {
|
||||
key: string[];
|
||||
substitutions: number;
|
||||
namedSubstitutions: string[];
|
||||
}
|
||||
|
||||
export interface ParsedChromeMessage extends ParsedBaseMessage, ChromeMessage {
|
||||
@@ -58,7 +76,7 @@ const PREDEFINED_MESSAGES: Record<string, ChromeMessage> = {
|
||||
'@@extension_id': {
|
||||
message: '<browser.runtime.id>',
|
||||
description:
|
||||
"The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even unlocalized extensions can use this message.\nNote: You can't use this message in a manifest file.",
|
||||
"The extension or app ID; you might use this string to construct URLs for resources inside the extension. Even non-localized extensions can use this message.\nNote: You can't use this message in a manifest file.",
|
||||
},
|
||||
'@@ui_locale': {
|
||||
message: '<browser.i18n.getUiLocale()>',
|
||||
@@ -95,7 +113,7 @@ const EXT_FORMATS_MAP: Record<string, MessageFormat> = {
|
||||
'.toml': 'TOML',
|
||||
};
|
||||
|
||||
const PARSERS: Record<MessageFormat, (text: string) => any> = {
|
||||
const PARSERS: Record<MessageFormat, (text: string) => MessagesObject> = {
|
||||
YAML: parseYAML,
|
||||
JSON5: parseJSON5,
|
||||
TOML: parseTOML,
|
||||
@@ -117,6 +135,7 @@ export async function parseMessagesFile(
|
||||
): Promise<ParsedMessage[]> {
|
||||
const text = await readFile(file, 'utf8');
|
||||
const ext = extname(file).toLowerCase();
|
||||
|
||||
return parseMessagesText(text, EXT_FORMATS_MAP[ext] ?? 'JSON5');
|
||||
}
|
||||
|
||||
@@ -129,7 +148,7 @@ export function parseMessagesText(
|
||||
}
|
||||
|
||||
/** Given the JS object form of a raw messages file, extract the messages. */
|
||||
export function parseMessagesObject(object: any): ParsedMessage[] {
|
||||
export function parseMessagesObject(object: MessagesObject): ParsedMessage[] {
|
||||
return _parseMessagesObject(
|
||||
[],
|
||||
{
|
||||
@@ -142,7 +161,7 @@ export function parseMessagesObject(object: any): ParsedMessage[] {
|
||||
|
||||
function _parseMessagesObject(
|
||||
path: string[],
|
||||
object: any,
|
||||
object: Message,
|
||||
depth: number,
|
||||
): ParsedMessage[] {
|
||||
switch (typeof object) {
|
||||
@@ -153,49 +172,59 @@ function _parseMessagesObject(
|
||||
case 'symbol': {
|
||||
const message = String(object);
|
||||
const substitutions = getSubstitutionCount(message);
|
||||
const namedSubstitutions = getNamedSubstitutionNames(message);
|
||||
return [
|
||||
{
|
||||
type: 'simple',
|
||||
key: path,
|
||||
substitutions,
|
||||
namedSubstitutions,
|
||||
message,
|
||||
},
|
||||
];
|
||||
}
|
||||
case 'object':
|
||||
if ([null, undefined].includes(object)) {
|
||||
if (object == null) {
|
||||
throw new Error(
|
||||
`Messages file should not contain \`${object}\` (found at "${path.join('.')}")`,
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(object))
|
||||
return object.flatMap((item, i) =>
|
||||
_parseMessagesObject(path.concat(String(i)), item, depth + 1),
|
||||
);
|
||||
|
||||
if (isPluralMessage(object)) {
|
||||
const message = Object.values(object).join('|');
|
||||
const substitutions = getSubstitutionCount(message);
|
||||
const namedSubstitutions = getNamedSubstitutionNames(message);
|
||||
return [
|
||||
{
|
||||
type: 'plural',
|
||||
key: path,
|
||||
substitutions,
|
||||
namedSubstitutions,
|
||||
plurals: object,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (depth === 1 && isChromeMessage(object)) {
|
||||
const message = applyChromeMessagePlaceholders(object);
|
||||
const substitutions = getSubstitutionCount(message);
|
||||
const namedSubstitutions = getNamedSubstitutionNames(message);
|
||||
return [
|
||||
{
|
||||
type: 'chrome',
|
||||
key: path,
|
||||
substitutions,
|
||||
namedSubstitutions,
|
||||
...object,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return Object.entries(object).flatMap(([key, value]) =>
|
||||
_parseMessagesObject(path.concat(key), value, depth + 1),
|
||||
);
|
||||
@@ -204,13 +233,13 @@ function _parseMessagesObject(
|
||||
}
|
||||
}
|
||||
|
||||
function isPluralMessage(object: any): object is Record<number | 'n', string> {
|
||||
function isPluralMessage(object: Message): object is PluralMessage {
|
||||
return Object.keys(object).every(
|
||||
(key) => key === 'n' || isFinite(Number(key)),
|
||||
);
|
||||
}
|
||||
|
||||
function isChromeMessage(object: any): object is ChromeMessage {
|
||||
function isChromeMessage(object: Message): object is ChromeMessage {
|
||||
return Object.keys(object).every((key) =>
|
||||
ALLOWED_CHROME_MESSAGE_KEYS.has(key),
|
||||
);
|
||||
@@ -222,13 +251,18 @@ function isChromeMessage(object: any): object is ChromeMessage {
|
||||
|
||||
export function generateTypeText(messages: ParsedMessage[]): string {
|
||||
const renderMessageEntry = (message: ParsedMessage): string => {
|
||||
// Use . for deep keys at runtime and types
|
||||
// Use '.' for deep keys at runtime and types
|
||||
const key = message.key.join('.');
|
||||
|
||||
const features = [
|
||||
`substitutions: ${message.substitutions}`,
|
||||
`plural: ${message.type === 'plural'}`,
|
||||
];
|
||||
if (message.namedSubstitutions.length > 0) {
|
||||
features.push(
|
||||
`namedSubstitutions: ${JSON.stringify(message.namedSubstitutions)}`,
|
||||
);
|
||||
}
|
||||
return ` "${key}": { ${features.join(', ')} };`;
|
||||
};
|
||||
|
||||
|
||||
+40
-23
@@ -1,18 +1,21 @@
|
||||
/** @module @wxt-dev/i18n */
|
||||
import {
|
||||
import type {
|
||||
I18nStructure,
|
||||
DefaultI18nStructure,
|
||||
I18n,
|
||||
NamedSubstitutions,
|
||||
Substitution,
|
||||
UntypedI18n,
|
||||
} from './types';
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
import { applyNamedSubstitutions } from './utils';
|
||||
|
||||
export function createI18n<
|
||||
T extends I18nStructure = DefaultI18nStructure,
|
||||
>(): I18n<T> {
|
||||
const t = (key: string, ...args: any[]) => {
|
||||
export function createI18n(): UntypedI18n;
|
||||
export function createI18n<T extends I18nStructure>(): I18n<T>;
|
||||
export function createI18n(): UntypedI18n {
|
||||
const t: UntypedI18n['t'] = (key: string, ...args: unknown[]) => {
|
||||
// Resolve args
|
||||
let sub: Substitution[] | undefined;
|
||||
let namedSub: NamedSubstitutions | undefined;
|
||||
let count: number | undefined;
|
||||
args.forEach((arg, i) => {
|
||||
if (arg == null) {
|
||||
@@ -21,9 +24,11 @@ export function createI18n<
|
||||
count = arg;
|
||||
} else if (Array.isArray(arg)) {
|
||||
sub = arg;
|
||||
} else if (isNamedSubstitutions(arg)) {
|
||||
namedSub = arg;
|
||||
} else {
|
||||
throw Error(
|
||||
`Unknown argument at index ${i}. Must be a number for pluralization, substitution array, or options object.`,
|
||||
`Unknown argument at index ${i}. Must be a number for pluralization, substitution array, or named substitution object.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -45,24 +50,36 @@ export function createI18n<
|
||||
if (!message) {
|
||||
console.warn(`[i18n] Message not found: "${key}"`);
|
||||
}
|
||||
if (count == null) return message;
|
||||
|
||||
// Apply pluralization
|
||||
const plural = message.split(' | ');
|
||||
switch (plural.length) {
|
||||
// "n items"
|
||||
case 1:
|
||||
return plural[0];
|
||||
// "1 item | n items"
|
||||
case 2:
|
||||
return plural[count === 1 ? 0 : 1];
|
||||
// "0 items | 1 item | n items"
|
||||
case 3:
|
||||
return plural[count === 0 || count === 1 ? count : 2];
|
||||
default:
|
||||
throw Error('Unknown plural formatting');
|
||||
if (count != null) {
|
||||
// Apply pluralization
|
||||
const plural = message.split(' | ');
|
||||
switch (plural.length) {
|
||||
// "n items"
|
||||
case 1:
|
||||
message = plural[0];
|
||||
break;
|
||||
// "1 item | n items"
|
||||
case 2:
|
||||
message = plural[count === 1 ? 0 : 1];
|
||||
break;
|
||||
// "0 items | 1 item | n items"
|
||||
case 3:
|
||||
message = plural[count === 0 || count === 1 ? count : 2];
|
||||
break;
|
||||
default:
|
||||
throw Error('Unknown plural formatting');
|
||||
}
|
||||
}
|
||||
|
||||
return namedSub == null
|
||||
? message
|
||||
: applyNamedSubstitutions(message, namedSub);
|
||||
};
|
||||
|
||||
return { t } as I18n<T>;
|
||||
return { t };
|
||||
}
|
||||
|
||||
function isNamedSubstitutions(value: unknown): value is NamedSubstitutions {
|
||||
return typeof value === 'object' && value != null;
|
||||
}
|
||||
|
||||
+91
-14
@@ -1,15 +1,44 @@
|
||||
export interface I18nFeatures {
|
||||
plural: boolean;
|
||||
substitutions: SubstitutionCount;
|
||||
namedSubstitutions?: readonly string[];
|
||||
}
|
||||
|
||||
export type I18nStructure = {
|
||||
[K: string]: I18nFeatures;
|
||||
export interface UntypedI18n {
|
||||
t: UntypedTFunction;
|
||||
}
|
||||
|
||||
export type UntypedTFunction = {
|
||||
(key: string): string;
|
||||
(key: string, substitutions: Substitution[]): string;
|
||||
(key: string, substitutions: NamedSubstitutions): string;
|
||||
(
|
||||
key: string,
|
||||
substitutions: Substitution[],
|
||||
namedSubstitutions: NamedSubstitutions,
|
||||
): string;
|
||||
(key: string, n: number): string;
|
||||
(key: string, n: number, substitutions: Substitution[]): string;
|
||||
(key: string, n: number, substitutions: NamedSubstitutions): string;
|
||||
(
|
||||
key: string,
|
||||
n: number,
|
||||
substitutions: Substitution[],
|
||||
namedSubstitutions: NamedSubstitutions,
|
||||
): string;
|
||||
};
|
||||
|
||||
export type DefaultI18nStructure = {
|
||||
[K: string]: any;
|
||||
};
|
||||
export type I18nStructure = Record<string, I18nFeatures>;
|
||||
|
||||
export interface I18n<T extends I18nStructure> {
|
||||
t: TFunction<T>;
|
||||
}
|
||||
|
||||
type HasNamedSubstitutions<T extends I18nFeatures> = T extends {
|
||||
namedSubstitutions: readonly string[];
|
||||
}
|
||||
? true
|
||||
: false;
|
||||
|
||||
// prettier-ignore
|
||||
export type SubstitutionTuple<T extends SubstitutionCount> =
|
||||
@@ -28,22 +57,43 @@ export type TFunction<T extends I18nStructure> = {
|
||||
// Non-plural, no substitutions
|
||||
<K extends keyof T>(
|
||||
// prettier-ignore
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: false; substitutions: 0 } ? P : never; }[keyof T],
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: false; substitutions: 0 } ? HasNamedSubstitutions<T[P]> extends true ? never : P : never; }[keyof T],
|
||||
): string;
|
||||
|
||||
// Non-plural with substitutions
|
||||
<K extends keyof T>(
|
||||
// prettier-ignore
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: false; substitutions: SubstitutionCount } ? P : never; }[keyof T],
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: false; substitutions: SubstitutionCount } ? HasNamedSubstitutions<T[P]> extends true ? never : P : never; }[keyof T],
|
||||
substitutions: T[K] extends I18nFeatures
|
||||
? SubstitutionTuple<T[K]['substitutions']>
|
||||
: never,
|
||||
): string;
|
||||
|
||||
// Non-plural with named substitutions
|
||||
<K extends keyof T>(
|
||||
// prettier-ignore
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: false; substitutions: 0; namedSubstitutions: readonly string[] } ? P : never; }[keyof T],
|
||||
substitutions: T[K] extends I18nFeatures
|
||||
? NamedSubstitutionsFor<T[K]>
|
||||
: never,
|
||||
): string;
|
||||
|
||||
// Non-plural with positional and named substitutions
|
||||
<K extends keyof T>(
|
||||
// prettier-ignore
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: false; substitutions: PositiveSubstitutionCount; namedSubstitutions: readonly string[] } ? P : never; }[keyof T],
|
||||
substitutions: T[K] extends I18nFeatures
|
||||
? SubstitutionTuple<T[K]['substitutions']>
|
||||
: never,
|
||||
namedSubstitutions: T[K] extends I18nFeatures
|
||||
? NamedSubstitutionsFor<T[K]>
|
||||
: never,
|
||||
): string;
|
||||
|
||||
// Plural with 1 substitution
|
||||
<K extends keyof T>(
|
||||
// prettier-ignore
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: true; substitutions: 1 } ? P : never; }[keyof T],
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: true; substitutions: 1 } ? HasNamedSubstitutions<T[P]> extends true ? never : P : never; }[keyof T],
|
||||
n: number,
|
||||
substitutions?: SubstitutionTuple<1>,
|
||||
): string;
|
||||
@@ -51,25 +101,52 @@ export type TFunction<T extends I18nStructure> = {
|
||||
// Plural without substitutions
|
||||
<K extends keyof T>(
|
||||
// prettier-ignore
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: true; substitutions: 0 | 1 } ? P : never; }[keyof T],
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: true; substitutions: 0 | 1 } ? HasNamedSubstitutions<T[P]> extends true ? never : P : never; }[keyof T],
|
||||
n: number,
|
||||
): string;
|
||||
|
||||
// Plural with substitutions
|
||||
<K extends keyof T>(
|
||||
// prettier-ignore
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: true; substitutions: SubstitutionCount } ? P : never; }[keyof T],
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: true; substitutions: SubstitutionCount } ? HasNamedSubstitutions<T[P]> extends true ? never : P : never; }[keyof T],
|
||||
n: number,
|
||||
substitutions: T[K] extends I18nFeatures
|
||||
? SubstitutionTuple<T[K]['substitutions']>
|
||||
: never,
|
||||
): string;
|
||||
|
||||
// Plural with named substitutions
|
||||
<K extends keyof T>(
|
||||
// prettier-ignore
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: true; substitutions: 0 | 1; namedSubstitutions: readonly string[] } ? P : never; }[keyof T],
|
||||
n: number,
|
||||
substitutions: T[K] extends I18nFeatures
|
||||
? NamedSubstitutionsFor<T[K]>
|
||||
: never,
|
||||
): string;
|
||||
|
||||
// Plural with positional and named substitutions
|
||||
<K extends keyof T>(
|
||||
// prettier-ignore
|
||||
key: K & { [P in keyof T]: T[P] extends { plural: true; substitutions: PositiveSubstitutionCount; namedSubstitutions: readonly string[] } ? P : never; }[keyof T],
|
||||
n: number,
|
||||
substitutions: T[K] extends I18nFeatures
|
||||
? SubstitutionTuple<T[K]['substitutions']>
|
||||
: never,
|
||||
namedSubstitutions: T[K] extends I18nFeatures
|
||||
? NamedSubstitutionsFor<T[K]>
|
||||
: never,
|
||||
): string;
|
||||
};
|
||||
|
||||
export interface I18n<T extends DefaultI18nStructure> {
|
||||
t: TFunction<T>;
|
||||
}
|
||||
|
||||
export type Substitution = string | number;
|
||||
export type NamedSubstitutions = Record<string, Substitution>;
|
||||
|
||||
type SubstitutionCount = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
||||
type PositiveSubstitutionCount = Exclude<SubstitutionCount, 0>;
|
||||
|
||||
type NamedSubstitutionsFor<T extends I18nFeatures> = T extends {
|
||||
namedSubstitutions: readonly string[];
|
||||
}
|
||||
? { [K in T['namedSubstitutions'][number]]: Substitution }
|
||||
: never;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ChromeMessage } from './build';
|
||||
import type { ChromeMessage } from './build';
|
||||
import type { NamedSubstitutions } from './types';
|
||||
|
||||
const NAMED_SUBSTITUTION_RE = /\{([A-Za-z0-9_]+)\}/g;
|
||||
|
||||
export function applyChromeMessagePlaceholders(message: ChromeMessage): string {
|
||||
if (message.placeholders == null) return message.message;
|
||||
@@ -11,6 +14,24 @@ export function applyChromeMessagePlaceholders(message: ChromeMessage): string {
|
||||
);
|
||||
}
|
||||
|
||||
export function applyNamedSubstitutions(
|
||||
message: string,
|
||||
substitutions: NamedSubstitutions,
|
||||
): string {
|
||||
return message.replace(NAMED_SUBSTITUTION_RE, (match, key: string) => {
|
||||
return Object.prototype.hasOwnProperty.call(substitutions, key)
|
||||
? String(substitutions[key])
|
||||
: match;
|
||||
});
|
||||
}
|
||||
|
||||
export function getNamedSubstitutionNames(message: string): string[] {
|
||||
return Array.from(
|
||||
message.matchAll(NAMED_SUBSTITUTION_RE),
|
||||
([, name]) => name,
|
||||
).filter((name, i, names) => names.indexOf(name) === i);
|
||||
}
|
||||
|
||||
export function getSubstitutionCount(message: string): number {
|
||||
return (
|
||||
1 +
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node"]
|
||||
"types": ["bun"]
|
||||
},
|
||||
"exclude": ["node_modules/**", "dist/**"]
|
||||
}
|
||||
|
||||
@@ -6,19 +6,20 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "pnpm build && check",
|
||||
"check": "bun run build && check",
|
||||
"test": "buildc --deps-only -- vitest",
|
||||
"test:coverage": "pnpm test run --coverage",
|
||||
"prepack": "pnpm build"
|
||||
"test:coverage": "bun run test run --coverage",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/browser": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -40,10 +40,12 @@
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
"check": "pnpm build && check",
|
||||
"check": "bun run build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepare": "buildc --deps-only -- wxt prepare",
|
||||
"prepack": "pnpm build"
|
||||
"postinstall": "buildc --deps-only -- wxt prepare",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.4.19 || ^6.3.4 || ^7.0.0 || ^8.0.0-0",
|
||||
@@ -53,12 +55,14 @@
|
||||
"@vitejs/plugin-react": "^4.4.1 || ^5.0.0 || ^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"publint": "^0.3.18",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"typescript": "^5.9.3",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vite": "^7.0.0",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -40,10 +40,12 @@
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
"check": "pnpm build && check",
|
||||
"check": "bun run build && check",
|
||||
"build": "buildc -- tsdown",
|
||||
"prepare": "buildc --deps-only -- wxt prepare",
|
||||
"prepack": "pnpm build"
|
||||
"postinstall": "buildc --deps-only -- wxt prepare",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.16"
|
||||
@@ -52,9 +54,11 @@
|
||||
"vite-plugin-solid": "^2.11.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"publint": "^0.3.18",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"solid-js": "^1.9.11",
|
||||
"typescript": "^5.9.3",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# Changelog
|
||||
|
||||
## v2.0.5
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.4...module-svelte-v2.0.5)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Update `@sveltejs/vite-plugin-svelte` version range to support Vite 8 ([48571f50](https://github.com/wxt-dev/wxt/commit/48571f50))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Upgrade dev and non-major prod dependencies ([#2000](https://github.com/wxt-dev/wxt/pull/2000))
|
||||
- Use `tsdown` to build packages ([#2006](https://github.com/wxt-dev/wxt/pull/2006))
|
||||
- Move script-only dev dependencies to top-level `package.json` ([#2007](https://github.com/wxt-dev/wxt/pull/2007))
|
||||
- Update dependencies ([#2069](https://github.com/wxt-dev/wxt/pull/2069))
|
||||
- **deps:** Upgrade deps ([#2175](https://github.com/wxt-dev/wxt/pull/2175))
|
||||
- Add prepack script to all packages ([032f7931](https://github.com/wxt-dev/wxt/commit/032f7931))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v2.0.4
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.3...module-svelte-v2.0.4)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"funding": "https://github.com/sponsors/wxt-dev",
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.5",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -40,19 +40,23 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "pnpm build && check",
|
||||
"prepack": "pnpm build"
|
||||
"check": "bun run build && check",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.18.6",
|
||||
"svelte": ">=5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0"
|
||||
"@sveltejs/vite-plugin-svelte": ">=4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,10 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "buildc -- tsdown",
|
||||
"check": "pnpm build && check",
|
||||
"prepack": "pnpm build"
|
||||
"check": "bun run build && check",
|
||||
"test": "echo 'noop'",
|
||||
"test:coverage": "echo 'noop'",
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"wxt": ">=0.19.16"
|
||||
@@ -50,8 +52,10 @@
|
||||
"@vitejs/plugin-vue": "^5.2.3 || ^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//
|
||||
// USAGE:
|
||||
// pnpm dev
|
||||
// pnpm dev firefox-nightly
|
||||
// pnpm dev <target>
|
||||
// bun run dev
|
||||
// bun run dev firefox-nightly
|
||||
// bun run dev <target>
|
||||
//
|
||||
|
||||
import { run } from './src';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user