Compare commits

...

70 Commits

Author SHA1 Message Date
GitHub Actions 1c3009a48f chore(release): @wxt-dev/i18n v0.2.0
vhs / vhs (push) Waiting to run
2024-10-04 23:12:34 +00:00
Aaron 6c6087249f fix!: Remove invalid options argument (#1048) 2024-10-04 18:07:57 -05:00
Aaron 539d482f71 chore: Remove TS compilerOptions.noUnusedLocals because Oxlint already does it (#1047) 2024-10-04 16:48:35 -05:00
Aaron cab97bca1e perf: Downgrade esbuild so a single version is shared between sub-dependencies (#1045) 2024-10-04 16:48:25 -05:00
Florian Metz 6020083b89 perf: Replace execa with nano-spawn for smaller package size (#1042) 2024-10-04 16:38:06 -05:00
GitHub Actions 947b24f1b0 chore(release): @wxt-dev/i18n v0.1.1
vhs / vhs (push) Waiting to run
2024-10-04 18:24:18 +00:00
windmillcode0 2c5ba64b61 fix: Friendly error messages for null and undefined values inside message files (#1041)
Co-authored-by: windmillcode0 <michaelodumosu57@gmail.com>
2024-10-04 13:19:35 -05:00
Aaron faa4fcb718 chore: Upgrade all non-major dependencies (#1040) 2024-10-03 18:47:16 -05:00
dependabot[bot] 8e9b67fea2 chore(deps): bump execa from 9.3.1 to 9.4.0 (#1031)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 18:24:43 -05:00
dependabot[bot] 659bdd46a0 chore(deps): bump linkedom from 0.18.4 to 0.18.5 (#1034)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 13:25:47 -05:00
dependabot[bot] edcc411d26 chore(deps-dev): bump changelogen from 0.5.5 to 0.5.7 (#1019)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 13:25:11 -05:00
dependabot[bot] e022423bad chore(deps): bump @vitejs/plugin-vue from 5.1.1 to 5.1.4 (#1020)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 13:13:24 -05:00
dependabot[bot] 84e1fd44eb chore(deps): bump esbuild from 0.23.0 to 0.24.0 (#1018)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 13:13:00 -05:00
dependabot[bot] b68d1ff828 chore(deps): bump magicast from 0.3.4 to 0.3.5 (#1017)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 13:11:46 -05:00
dependabot[bot] c31a5c30ed chore(deps): bump stefanzweifel/git-auto-commit-action from 4 to 5 (#1022)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 13:10:26 -05:00
Aaron 5b408c2bc2 chore: Update dependabot configuration 2024-10-03 13:09:01 -05:00
mezannic ca29419db6 chore: remove email from changelog (#1027) 2024-10-03 13:04:33 -05:00
Aaron ab26bdc7c1 fix: Use directory name when zip.name and package.json#name are not provided (#1028) 2024-10-03 12:26:36 -05:00
GitHub Actions d270250633 chore(release): wxt v0.19.11
vhs / vhs (push) Waiting to run
2024-10-02 13:59:29 +00:00
Florian Metz d75f64df79 feat(zip): wxt zip --sources and auto sources for opera (#1014)
Co-authored-by: Timeraa <Timeraa@users.noreply.github.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-10-02 08:55:08 -05:00
Gurvir Singh 5faa5d7537 fix: #1005 fixed, by updating type-definations to getItem method. (#1007)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-10-02 08:20:32 -05:00
mezannic 177223c625 fix: Reverse env files priority (#1016)
Co-authored-by: Mathieu Ezannic <mathieu.ezannic@plebicom.com>
2024-10-01 16:00:52 -05:00
deyu a64ff22917 docs: Add "Discord Message Translator" extension to homepage (#1011) 2024-09-29 21:57:23 -05:00
Gourav Goyal 0bf08329b0 docs: Add "ChatGPT Writer" to homepage (#1006) 2024-09-27 20:50:21 -05:00
Guilherme Oenning e3f00d2797 docs: Add "Dofollow Links for SEO" to homepage (#1001)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-09-27 08:03:06 -05:00
Andy Zhou 09de72895e docs: Add "social-media-copilot" to homepage (#1000) 2024-09-27 08:02:23 -05:00
Aaron c0aa12089e chore: Move some files around (#996) 2024-09-25 20:54:40 -05:00
Aaron 0248f58785 chore: Add Aaron back to changelog contributions 2024-09-25 20:11:02 -05:00
Florian Metz 247cd66f74 feat(zip): hooks (#993)
Co-authored-by: Timeraa <Timeraa@users.noreply.github.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-09-25 20:08:54 -05:00
GitHub Actions 4a1bd41df9 chore(release): wxt v0.19.10
vhs / vhs (push) Waiting to run
2024-09-22 14:20:12 +00:00
1natsu efcb593718 perf: Reduce hypersensitive onChange of watcher (#978)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-09-22 09:12:29 -05:00
Okinea Dev a82c7cbf71 docs: Improved docs and links (#970)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-09-22 08:45:30 -05:00
renxinyan db60e79a99 docs: Correct the type example of allFrames (#988) 2024-09-21 19:03:07 -05:00
Jarle Mathiesen 722eff145f docs: Add "YouTube Summarized" to homepage (#986) 2024-09-20 07:55:58 -05:00
Zain Hasan 282458748d docs: Fix small typo in introduction (#983) 2024-09-16 21:20:53 -05:00
the-syndrome 26197f11a3 docs: examples reference outDir vs. outputDir (#982) 2024-09-15 07:58:52 -05:00
aklinker1 9d560f9798 docs: Update wxt init GIF 2024-09-14 20:45:33 +00:00
Okinea Dev 786292c95e docs: Add wxt init demo GIF (#974)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
Co-authored-by: okineadev <okineadev@users.noreply.github.com>
2024-09-14 15:44:15 -05:00
rxliuli e760ee444e chore(template): set type to "module" for Svelte template (#977) 2024-09-12 08:07:11 -05:00
kongmoumou 78f91fefb6 chore: Continuous publish using pkg.pr.new (#955) 2024-09-11 00:40:42 -05:00
Aaron Klinker fda1e18f48 types: Fix ExtensionRunnerConfig.chromiumPref type
Closes #964
2024-09-10 16:29:36 -05:00
hikiko4ern c94168e2d1 fix: Fix config manifest type (#973) 2024-09-10 16:13:08 -05:00
GitHub Actions 3eb505dbde chore(release): wxt v0.19.9 2024-09-04 21:39:24 +00:00
1natsu fbe502fddf chore: Cleanup leftover E2E test artifacts (#968) 2024-09-04 16:35:18 -05:00
1natsu c143a9978d fix: Properly overload import.meta.env with WXT's own environment globals (#966) 2024-09-03 14:35:10 -05:00
Aaron Klinker e3555b663d Update description after 447d011 2024-09-03 14:34:32 -05:00
Aaron Klinker df934d252c Correct version number after 447d011 2024-09-03 14:31:27 -05:00
Aaron Klinker 447d011b8f docs: Retroactively mention breaking change in v0.18.5
See https://github.com/wxt-dev/wxt/discussions/950#discussioncomment-10512337
2024-09-03 14:30:10 -05:00
Aaron abe1263d27 feat: Use @types/chrome for config manifest type (#969) 2024-09-03 14:11:24 -05:00
Aaron Klinker 143b5ac81f docs: Add docs around importEntrypoint to relevant functions 2024-09-03 13:40:59 -05:00
dependabot[bot] 87fbb1f4c3 chore(deps): bump execa from 9.3.0 to 9.3.1 (#957)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-03 12:12:22 -05:00
dependabot[bot] 62f11bf605 chore(deps): bump sharp from 0.33.4 to 0.33.5 (#959)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-03 12:10:02 -05:00
dependabot[bot] 3ead54b58e chore(deps): bump unimport from 3.9.1 to 3.11.1 (#960)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-03 12:08:44 -05:00
dependabot[bot] 580f4f3cfe chore(deps): bump ora from 8.0.1 to 8.1.0 (#961)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-03 12:08:04 -05:00
1natsu da0accebe8 fix: duplicate BuildOutput.publicAssets (#951)
Co-authored-by: Aaron Klinker <aaronklinker1@gmail.com>
2024-09-03 12:06:21 -05:00
Aaron eb65d0b42a docs: Add link to testing permission changes in Firefox 2024-09-03 07:34:43 -05:00
Alex Nguyen c2784135b0 docs: Fix typo in frontend-frameworks.md (#952) 2024-09-01 19:43:11 -05:00
Aaron 61d54bd1f9 fix: Allow adding multiple hyphens in an entrypoint name (#949) 2024-08-30 04:56:23 -05:00
Aaron 0bd94fce3b chore: Add oxlint for linting (#947) 2024-08-30 04:14:20 -05:00
Amal Chandran 2a97c06443 docs: Add "Draftly" to homepage (#941)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-08-30 02:51:32 -05:00
Aaron 8213aa776a chore: Fix type error (#946) 2024-08-30 02:45:36 -05:00
Aaron 026f78254d chore: Upgrade unbuild from 1.1.0 to 1.1.1 2024-08-29 08:19:56 -05:00
Aaron 0ebb013ba6 types(modules): Use NestedHooks instead of Partial for hooks object 2024-08-29 07:12:58 -05:00
Aaron c5f78d0c8d feat(modules): Add wxt.hook alias for wxt.hooks.hook 2024-08-29 07:12:13 -05:00
GitHub Actions 8771676e69 chore(release): wxt v0.19.8 2024-08-29 11:53:13 +00:00
1natsu aebbbb0685 fix: Fallback to GitHub API for listing templates when ungh is down (#944)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-08-29 06:48:38 -05:00
Aaron 68183b648e chore(types): Cleanup types in wxt/browser/chrome (#932) 2024-08-23 08:49:39 -05:00
Aaron Klinker 45809c0198 chore: Update lockfile 2024-08-19 23:08:40 -05:00
Aaron Klinker 13163c9ba1 chore(dev-deps): Upgrade @aklinker1/buildc from v1.0.11 to v1.1.0 2024-08-19 21:49:01 -05:00
1natsu aedbba3f3d perf: Ignore output directories for all vite.watcher (#924) 2024-08-19 21:44:27 -05:00
96 changed files with 2697 additions and 1638 deletions
+5
View File
@@ -1,5 +1,9 @@
name: Basic Setup
description: Install PNPM, Node, and dependencies
inputs:
install:
default: 'true'
description: Whether or not to run 'pnpm install'
runs:
using: composite
steps:
@@ -11,5 +15,6 @@ runs:
node-version: 18
cache: pnpm
- name: Install Dependencies
if: ${{ inputs.install == 'true' }}
shell: bash
run: pnpm install
+3 -1
View File
@@ -6,7 +6,9 @@
version: 2
updates:
- package-ecosystem: npm
directory: '/' # Location of package manifests
directories:
- /
- packages/*
schedule:
interval: 'monthly'
- package-ecosystem: 'github-actions'
+17
View File
@@ -0,0 +1,17 @@
name: Continuous Publish
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm buildc all
- run: pnpx pkg-pr-new publish --compact './packages/*'
+29
View File
@@ -0,0 +1,29 @@
name: vhs
on:
push:
paths:
- 'docs/tapes/*.tape'
workflow_dispatch:
jobs:
vhs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
with:
install: 'false'
- name: Preinstall WXT
run: |
pnpm store add wxt@latest
pnpm dlx wxt@latest --version
- uses: charmbracelet/vhs-action@v2.1.0
with:
path: 'docs/tapes/init-demo.tape'
- uses: stefanzweifel/git-auto-commit-action@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commit_message: 'docs: Update `wxt init` GIF'
# https://github.com/charmbracelet/vhs#output
file_pattern: 'docs/assets/*.gif'
+2 -2
View File
@@ -1,6 +1,6 @@
# Contributing
Everyone is welcome to contribute to WXT!
Everyone is welcome to contribute to **WXT**!
If you are changing the docs or fixing a bug, feel free to fork and open a PR.
@@ -139,7 +139,7 @@ Releases are done with GitHub actions:
Use [`taze`](https://www.npmjs.com/package/taze) to upgrade dependencies throughout the entire monorepo.
```ts
```sh
pnpm dlx taze -r
```
+25 -7
View File
@@ -1,16 +1,16 @@
<h1 align="center">
<img style="vertical-align:middle" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
<img align="top" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
<span>WXT</span>
</h1>
<p align="center">
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="downloads" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="NPM" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="license | MIT" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="coverage" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
</p>
<p align="center">
@@ -43,10 +43,21 @@ https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84
Bootstrap a new project:
<!-- automd:pm-x version="latest" name="wxt" args="init" -->
```sh
pnpm dlx wxt@latest init <project-name>
# npm
npx wxt@latest init
# pnpm
pnpm dlx wxt@latest init
# bun
bunx wxt@latest init
```
<!-- /automd -->
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
## Features
@@ -66,6 +77,13 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
## Contributors
<!-- automd:contributors author="aklinker1" license="MIT" github="wxt-dev/wxt" -->
Published under the [MIT](https://github.com/wxt-dev/wxt/blob/main/LICENSE) license.
Made by [@aklinker1](https://github.com/aklinker1) and [community](https://github.com/wxt-dev/wxt/graphs/contributors) 💛
<br><br>
<a href="https://github.com/wxt-dev/wxt/graphs/contributors">
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
</a>
<!-- /automd -->
@@ -38,6 +38,12 @@ const chromeExtensionIds = [
'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
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
+4 -3
View File
@@ -1,6 +1,6 @@
import { resolve, join } from 'node:path';
import { resolve } from 'node:path';
import consola from 'consola';
import { execaCommand } from 'execa';
import spawn from 'nano-spawn';
const cliDir = resolve('packages/wxt/src/cli/commands');
const cliDirGlob = resolve(cliDir, '**');
@@ -37,7 +37,8 @@ export default {
};
async function getHelp(command: string): Promise<string> {
const res = await execaCommand(command + ' --help', {
const args = command.split(' ');
const res = await spawn(args[0], [...args.slice(1), '--help'], {
cwd: 'packages/wxt',
});
return res.stdout;
Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

+4
View File
@@ -25,6 +25,10 @@ npx wxt@latest init <project-name>
npx wxt@latest init <project-name>
```
### Demo
![wxt init demo](/assets/init-demo.gif)
:::
There are several starting templates available.
+1 -1
View File
@@ -9,7 +9,7 @@ head:
## Overview
WXT is a free and open source framework for building web extensions in an conventional, intuitive, and safe way **_for all browsers_**.
WXT is a free and open source framework for building web extensions in a conventional, intuitive, and safe way **_for all browsers_**.
WXT is based on [Nuxt](https://nuxt.com), and aims to provide the same great DX with TypeScript, auto-imports, and an opinionated project structure.
@@ -24,7 +24,7 @@ export default defineContentScript({
excludeMatches: undefined | [],
includeGlobs: undefined | [],
excludeGlobs: undefined | [],
allFrames: undefined | [],
allFrames: undefined | true | false,
runAt: undefined | 'document_start' | 'document_end' | 'document_idle',
matchAboutBlank: undefined | true | false,
matchOriginAsFallback: undefined | true | false,
+2 -1
View File
@@ -56,7 +56,8 @@ When `permissions`/`host_permissions` change during an update, depending on what
You can test if your permission changes will result in a disabled extension:
- Chromium: Use [Google's Extension Update Testing tool](https://github.com/GoogleChromeLabs/extension-update-testing-tool)
- Firefox/Safari: Everyone breaks something in production eventually... 🫡 Good luck soldier
- Firefox: See their [Test Permission Requests](https://extensionworkshop.com/documentation/develop/test-permission-requests/) page
- Safari: Everyone breaks something in production eventually... 🫡 Good luck soldier
## Update Event
+1 -1
View File
@@ -139,7 +139,7 @@ export default defineWxtModule({
name: 'changelog',
// Point to the "modules/changelog.html" file
inputPath: resolve(__dirname, 'changelog.html'),
outputDir: wxt.config.outputDir,
outputDir: wxt.config.outDir,
options: {},
});
},
@@ -100,7 +100,7 @@ Usually, this means each entrypoint should be a directory with it's own files in
## Configuring Routers
Lots of frameworks come with routers for building a multi-page app using the URL's path. Chrome extensions don't don't work like this. Since HTML files are static, `chrome-extension://{id}/popup.html`, there's no way to change the entire path for routing.
Lots of frameworks come with routers for building a multi-page app using the URL's path. Chrome extensions don't work like this. Since HTML files are static, `chrome-extension://{id}/popup.html`, there's no way to change the entire path for routing.
Instead, you need to configure the router to run in "hash" mode, where the routing information is apart of the URL's hash, not the path (ie: `popup.html#/` and `popup.html#/account/settings`).
+22 -1
View File
@@ -10,7 +10,7 @@ pnpm i wxt@latest
If there was a major version change, follow the steps below to fix breaking changes.
## v0.18.0 &rarr; v0.19.0
## v0.18.5 &rarr; v0.19.0
### `vite-node` Entrypoint Loader
@@ -86,6 +86,27 @@ WXT no longer ships with Common JS support. If you're using CJS, here's your mig
Vite also provides steps for migrating to ESM. Check them out for more details: https://vitejs.dev/guide/migration#deprecate-cjs-node-api
## v0.18.0 &rarr; v0.18.5
> When this version was released, it was not considered a breaking change... but it should have been.
### New `modules/` Directory
WXT now recognizes the `modules/` directory as a folder containing [WXT modules](/guide/go-further/reusable-modules).
If you already have `<srcDir>/modules` or `<srcDir>/Modules` directory, `wxt prepare` and other commands will fail.
You have two options:
1. [Recommended] Keep your files where they are and tell WXT to look in a different folder:
```ts
// wxt.config.ts
export default defineConfig({
modulesDir: 'wxt-modules', // defaults to "modules"
});
```
2. Rename your `modules` directory to something else.
## v0.17.0 &rarr; v0.18.0
### Automatic MV3 `host_permissions` to MV2 `permissions`
+59
View File
@@ -0,0 +1,59 @@
# VHS documentation
#
# You can view all VHS documentation on the command line with `vhs manual`.
# Or see https://github.com/charmbracelet/vhs#vhs-command-reference
# Output file
Output docs/assets/init-demo.gif
# The tools we will use
Require pnpm
# === Scene ====
Set Width 1400
Set Height 800
# The maximum FPS for GIF is `50` FPS.
Set Framerate 50
# Terminal theme with WXT brand colors (which was taken from the website)
# Based on the standard charmbracelet/vhs theme:
# https://github.com/charmbracelet/vhs/blob/88e634f4a10bbe305b6aea9a12b4d8dc3dd7f31c/style.go#L7-L28
Set Theme {"background": "#1a1b1a", "foreground": "#dddddd", "black": "#282a2e", "brightBlack": "#4d4d4d", "red": "#D74E6F", "brightRed": "#FE5F86", "green": "#67d45e", "brightGreen": "#67d45e", "yellow": "#D3E561", "brightYellow": "#EBFF71", "blue": "#8056FF", "brightBlue": "#9B79FF", "magenta": "#ED61D7", "brightMagenta": "#FF7AEA", "cyan": "#04D7D7", "brightCyan": "#00FEFE", "white": "#bfbfbf", "brightWhite": "#e6e6e6", "indigo": "#5B56E0"}
Set FontSize 32
# Terminal settings
Set Shell "bash"
# Terminal prompt. It looks like "● mycommand ..."
Env PS1 "\e[0;32m●\e[0m "
# ===== Preparation =====
# Steps to prepare the recording environment, ensuring a clean and isolated setup.
Hide
# Create a temporary folder for demo
Type 'vhs_sandbox="$(mktemp -d)"' Enter
Type 'cd "$vhs_sandbox"' Enter
Type 'clear' Enter
Show
# ===== Actions =====
Type 'pnpm dlx wxt@latest 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
Sleep 5s
# ===== Cleaning =====
Hide
# Delete the temporary folder
Type 'rm -rf "$vhs_sandbox"' Enter
Show
+12 -17
View File
@@ -4,7 +4,7 @@
"engines": {
"node": ">=18.20.3"
},
"packageManager": "pnpm@9.6.0",
"packageManager": "pnpm@9.12.0",
"scripts": {
"check": "check && pnpm -r --sequential run check",
"test": "pnpm -r --sequential run test run",
@@ -17,18 +17,18 @@
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
},
"devDependencies": {
"@aklinker1/buildc": "^1.0.11",
"@aklinker1/check": "^1.3.1",
"@aklinker1/buildc": "^1.1.4",
"@aklinker1/check": "^1.4.5",
"@types/fs-extra": "^11.0.4",
"@vitest/coverage-v8": "^2.0.4",
"changelogen": "^0.5.5",
"@vitest/coverage-v8": "^2.1.2",
"changelogen": "^0.5.7",
"consola": "^3.2.3",
"dependency-graph": "^1.0.0",
"execa": "^9.3.0",
"fast-glob": "^3.3.2",
"fs-extra": "^11.2.0",
"hasha": "^6.0.0",
"lint-staged": "^15.2.7",
"lint-staged": "^15.2.10",
"nano-spawn": "^0.1.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3",
"simple-git-hooks": "^2.11.1",
@@ -36,12 +36,12 @@
"typedoc": "^0.25.4",
"typedoc-plugin-markdown": "4.0.0-next.23",
"typedoc-vitepress-theme": "1.0.0-next.3",
"typescript": "^5.5.4",
"vitepress": "^1.3.1",
"vitest-mock-extended": "^2.0.0",
"vue": "^3.4.34",
"typescript": "^5.6.2",
"vitepress": "^1.3.4",
"vitest-mock-extended": "^2.0.2",
"vue": "^3.5.11",
"wxt": "workspace:*",
"yaml": "^2.5.0"
"yaml": "^2.5.1"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
@@ -56,10 +56,5 @@
"search-insights"
]
}
},
"changelog": {
"excludeAuthors": [
"aaronklinker1@gmail.com"
]
}
}
+5 -4
View File
@@ -45,15 +45,16 @@
"wxt": ">=0.19.0"
},
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.9",
"typescript": "^5.5.4",
"@aklinker1/check": "^1.4.5",
"oxlint": "^0.9.9",
"publint": "^0.2.11",
"typescript": "^5.6.2",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
},
"dependencies": {
"defu": "^6.1.4",
"fs-extra": "^11.2.0",
"sharp": "^0.33.4"
"sharp": "^0.33.5"
}
}
+35
View File
@@ -0,0 +1,35 @@
# Changelog
## v0.2.0
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.1.1...i18n-v0.2.0)
### 🩹 Fixes
- ⚠️ Remove invalid options argument ([#1048](https://github.com/wxt-dev/wxt/pull/1048))
#### ⚠️ Breaking Changes
- ⚠️ Remove invalid options argument ([#1048](https://github.com/wxt-dev/wxt/pull/1048))
### ❤️ Contributors
- Aaron ([@aklinker1](http://github.com/aklinker1))
## v0.1.1
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.1.0...i18n-v0.1.1)
### 🩹 Fixes
- Friendly error messages for `null` and `undefined` values inside message files ([#1041](https://github.com/wxt-dev/wxt/pull/1041))
### 🏡 Chore
- Add `oxlint` for linting ([#947](https://github.com/wxt-dev/wxt/pull/947))
- Upgrade all non-major dependencies ([#1040](https://github.com/wxt-dev/wxt/pull/1040))
### ❤️ Contributors
- Windmillcode0 <shieldmousetower734@gmail.com>
- Aaron ([@aklinker1](http://github.com/aklinker1))
+7 -6
View File
@@ -1,7 +1,7 @@
{
"name": "@wxt-dev/i18n",
"description": "Type-safe wrapper around browser.i18n.getMessage with additional features",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"repository": {
"type": "git",
@@ -38,13 +38,14 @@
}
},
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"@aklinker1/check": "^1.4.5",
"@types/chrome": "^0.0.268",
"@types/node": "^20.14.2",
"publint": "^0.2.8",
"typescript": "^5.4.5",
"@types/node": "^20.16.10",
"oxlint": "^0.9.9",
"publint": "^0.2.11",
"typescript": "^5.6.2",
"unbuild": "^2.0.0",
"vitest": "^1.6.0",
"vitest": "^2.0.0",
"vitest-plugin-random-seed": "^1.1.0",
"wxt": "workspace:*"
},
+13
View File
@@ -161,4 +161,17 @@ describe('Built Tools', () => {
);
expect(mockWriteFile).toBeCalledWith('output.d.ts', expectedDts, 'utf8');
});
it('should throw an error if messages file contains null or undefined', async () => {
const invalidFileContent = stringifyYAML({
simple: 'example',
invalidField: null,
});
mockReadFile.mockResolvedValue(invalidFileContent);
await expect(parseMessagesFile('invalid.yml')).rejects.toThrowError(
'Messages file should not contain `null` (found at "invalidField")',
);
});
});
+3 -19
View File
@@ -1,6 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createI18n } from '../index';
import { GetMessageOptions } from '../types';
const getMessageMock = vi.fn();
@@ -28,7 +27,7 @@ describe('createI18n', () => {
expect(actual).toBe(expectedValue);
expect(getMessageMock).toBeCalledTimes(1);
expect(getMessageMock).toBeCalledWith(expectedKey, undefined);
expect(getMessageMock).toBeCalledWith(expectedKey);
});
it.each([
@@ -55,28 +54,13 @@ describe('createI18n', () => {
expect(actual).toBe(expected);
expect(getMessageMock).toBeCalledTimes(1);
expect(getMessageMock).toBeCalledWith(key, [String(count)], undefined);
expect(getMessageMock).toBeCalledWith(key, [String(count)]);
},
);
it('should allow overriding the plural substitutions', () => {
const i18n = createI18n();
i18n.t('key', 3, ['custom']);
expect(getMessageMock).toBeCalledWith('key', ['custom'], undefined);
});
it('should pass options into browser.i18n.getMessage', () => {
const i18n = createI18n();
const options: GetMessageOptions = {
escapeLt: true,
};
i18n.t('key', options);
i18n.t('key', [''], options);
i18n.t('key', 1, options);
i18n.t('key', 1, [''], options);
getMessageMock.mock.calls.forEach((call) => {
expect(call.pop()).toEqual(options);
});
expect(getMessageMock).toBeCalledWith('key', ['custom']);
});
});
+1 -12
View File
@@ -22,13 +22,9 @@ describe('I18n Types', () => {
describe('t', () => {
it('should allow passing any combination of arguments', () => {
i18n.t('any');
i18n.t('any', { escapeLt: true });
i18n.t('any', ['one']);
i18n.t('any', ['one'], { escapeLt: true });
i18n.t('any', ['one', 'two']);
i18n.t('any', ['one', 'two'], { escapeLt: true });
i18n.t('any', n, ['one', 'two']);
i18n.t('any', n, ['one', 'two'], { escapeLt: true });
});
});
});
@@ -46,7 +42,6 @@ describe('I18n Types', () => {
describe('t', () => {
it('should only allow passing valid combinations of arguments', () => {
i18n.t('simple');
i18n.t('simple', { escapeLt: true });
// @ts-expect-error
i18n.t('simple', []);
// @ts-expect-error
@@ -55,7 +50,6 @@ describe('I18n Types', () => {
i18n.t('simple', n);
i18n.t('simpleSub1', ['one']);
i18n.t('simpleSub1', ['one'], { escapeLt: true });
// @ts-expect-error
i18n.t('simpleSub1');
// @ts-expect-error
@@ -66,7 +60,6 @@ describe('I18n Types', () => {
i18n.t('simpleSub1', n);
i18n.t('simpleSub2', ['one', 'two']);
i18n.t('simpleSub2', ['one', 'two'], { escapeLt: true });
// @ts-expect-error
i18n.t('simpleSub2');
// @ts-expect-error
@@ -77,7 +70,6 @@ describe('I18n Types', () => {
i18n.t('simpleSub2', n);
i18n.t('plural', n);
i18n.t('plural', n, { escapeLt: true });
// @ts-expect-error
i18n.t('plural');
// @ts-expect-error
@@ -88,10 +80,8 @@ describe('I18n Types', () => {
i18n.t('plural', n, ['sub']);
i18n.t('pluralSub1', n);
i18n.t('pluralSub1', n, { escapeLt: true });
i18n.t('pluralSub1', n, undefined, { escapeLt: true });
i18n.t('pluralSub1', n, undefined);
i18n.t('pluralSub1', n, ['one']);
i18n.t('pluralSub1', n, ['one'], { escapeLt: true });
// @ts-expect-error
i18n.t('pluralSub1');
// @ts-expect-error
@@ -102,7 +92,6 @@ describe('I18n Types', () => {
i18n.t('pluralSub1', n, ['one', 'two']);
i18n.t('pluralSub2', n, ['one', 'two']);
i18n.t('pluralSub2', n, ['one', 'two'], { escapeLt: true });
// @ts-expect-error
i18n.t('pluralSub2');
// @ts-expect-error
+5
View File
@@ -166,6 +166,11 @@ function _parseMessagesObject(
];
}
case 'object':
if ([null, undefined].includes(object)) {
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),
+2 -15
View File
@@ -6,7 +6,6 @@ import {
DefaultI18nStructure,
I18n,
Substitution,
GetMessageOptions,
} from './types';
export function createI18n<
@@ -16,7 +15,6 @@ export function createI18n<
// Resolve args
let sub: Substitution[] | undefined;
let count: number | undefined;
let options: GetMessageOptions | undefined;
args.forEach((arg, i) => {
if (arg == null) {
// ignore nullish args
@@ -24,8 +22,6 @@ export function createI18n<
count = arg;
} else if (Array.isArray(arg)) {
sub = arg;
} else if (typeof arg === 'object') {
options = arg;
} else {
throw Error(
`Unknown argument at index ${i}. Must be a number for pluralization, substitution array, or options object.`,
@@ -43,18 +39,9 @@ export function createI18n<
if (sub?.length) {
// Convert all substitutions to strings
const stringSubs = sub?.map((sub) => String(sub));
message = chrome.i18n.getMessage(
key.replaceAll('.', '_'),
stringSubs,
// @ts-ignore - @types/chrome doesn't type the options object, but it's there
options,
);
message = chrome.i18n.getMessage(key.replaceAll('.', '_'), stringSubs);
} else {
message = chrome.i18n.getMessage(
key.replaceAll('.', '_'),
// @ts-ignore - @types/chrome doesn't type the options object, but it's there
options,
);
message = chrome.i18n.getMessage(key.replaceAll('.', '_'));
}
if (!message) {
console.warn(`[i18n] Message not found: "${key}"`);
-14
View File
@@ -29,7 +29,6 @@ export type TFunction<T extends I18nStructure> = {
<K extends keyof T>(
// prettier-ignore
key: K & { [P in keyof T]: T[P] extends { plural: false; substitutions: 0 } ? P : never; }[keyof T],
options?: GetMessageOptions,
): string;
// Non-plural with substitutions
@@ -39,7 +38,6 @@ export type TFunction<T extends I18nStructure> = {
substitutions: T[K] extends I18nFeatures
? SubstitutionTuple<T[K]['substitutions']>
: never,
options?: GetMessageOptions,
): string;
// Plural with 1 substitution
@@ -48,7 +46,6 @@ export type TFunction<T extends I18nStructure> = {
key: K & { [P in keyof T]: T[P] extends { plural: true; substitutions: 1 } ? P : never; }[keyof T],
n: number,
substitutions?: SubstitutionTuple<1>,
options?: GetMessageOptions,
): string;
// Plural without substitutions
@@ -56,7 +53,6 @@ export type TFunction<T extends I18nStructure> = {
// prettier-ignore
key: K & { [P in keyof T]: T[P] extends { plural: true; substitutions: 0 | 1 } ? P : never; }[keyof T],
n: number,
options?: GetMessageOptions,
): string;
// Plural with substitutions
@@ -67,7 +63,6 @@ export type TFunction<T extends I18nStructure> = {
substitutions: T[K] extends I18nFeatures
? SubstitutionTuple<T[K]['substitutions']>
: never,
options?: GetMessageOptions,
): string;
};
@@ -77,13 +72,4 @@ export interface I18n<T extends DefaultI18nStructure> {
export type Substitution = string | number;
export interface GetMessageOptions {
/**
* Escape `<` in translation to `&lt;`. This applies only to the message itself, not to the placeholders. Developers might want to use this if the translation is used in an HTML context. Closure Templates used with Closure Compiler generate this automatically.
*
* See https://developer.chrome.com/docs/extensions/reference/api/i18n#type-getMessage-options
*/
escapeLt?: boolean;
}
type SubstitutionCount = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
+1 -1
View File
@@ -14,7 +14,7 @@ export function applyChromeMessagePlaceholders(message: ChromeMessage): string {
export function getSubstitionCount(message: string): number {
return (
1 +
new Array(MAX_SUBSTITUTIONS).findLastIndex((_, i) =>
Array.from({ length: MAX_SUBSTITUTIONS }).findLastIndex((_, i) =>
message.match(new RegExp(`(?<!\\$)\\$${i + 1}`)),
)
);
+5 -5
View File
@@ -47,16 +47,16 @@
"wxt": ">=0.18.6"
},
"dependencies": {
"@vitejs/plugin-react": "^4.3.1"
"@vitejs/plugin-react": "^4.3.2"
},
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"@types/react": "^18.3.3",
"@aklinker1/check": "^1.4.5",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"publint": "^0.2.9",
"publint": "^0.2.11",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.5.4",
"typescript": "^5.6.2",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
+4 -4
View File
@@ -50,10 +50,10 @@
"vite-plugin-solid": "^2.10.2"
},
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.9",
"solid-js": "^1.8.19",
"typescript": "^5.5.4",
"@aklinker1/check": "^1.4.5",
"publint": "^0.2.11",
"solid-js": "^1.9.1",
"typescript": "^5.6.2",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
+4 -4
View File
@@ -45,12 +45,12 @@
"wxt": ">=0.18.6"
},
"dependencies": {
"@sveltejs/vite-plugin-svelte": "^3.1.1"
"@sveltejs/vite-plugin-svelte": "^3.1.2"
},
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.9",
"typescript": "^5.5.4",
"@aklinker1/check": "^1.4.5",
"publint": "^0.2.11",
"typescript": "^5.6.2",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
+4 -4
View File
@@ -45,12 +45,12 @@
"wxt": ">=0.18.6"
},
"dependencies": {
"@vitejs/plugin-vue": "^5.1.1"
"@vitejs/plugin-vue": "^5.1.4"
},
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.9",
"typescript": "^5.5.4",
"@aklinker1/check": "^1.4.5",
"publint": "^0.2.11",
"typescript": "^5.6.2",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
+4 -4
View File
@@ -23,12 +23,12 @@
},
"devDependencies": {
"@types/chrome": "^0.0.269",
"@types/react": "^18.3.3",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@wxt-dev/auto-icons": "workspace:*",
"sass": "^1.77.8",
"typescript": "^5.5.4",
"vitest": "^2.0.4",
"sass": "^1.79.4",
"typescript": "^5.6.2",
"vitest": "^2.1.2",
"vitest-plugin-random-seed": "^1.1.0",
"wxt": "workspace:*"
},
+1
View File
@@ -0,0 +1 @@
src/core/utils/building/__tests__/test-entrypoints
+109
View File
@@ -1,5 +1,114 @@
# Changelog
## v0.19.11
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.10...wxt-v0.19.11)
### 🚀 Enhancements
- **zip:** Hooks ([#993](https://github.com/wxt-dev/wxt/pull/993))
- **zip:** `wxt zip --sources` and auto sources for opera ([#1014](https://github.com/wxt-dev/wxt/pull/1014))
### 🩹 Fixes
- Reverse env files priority ([#1016](https://github.com/wxt-dev/wxt/pull/1016))
- #1005 fixed, by updating type-definations to getItem method. ([#1007](https://github.com/wxt-dev/wxt/pull/1007), [#1005](https://github.com/wxt-dev/wxt/issues/1005))
### 🏡 Chore
- Move some files around ([#996](https://github.com/wxt-dev/wxt/pull/996))
### ❤️ Contributors
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
- Gurvir Singh ([@baraich](http://github.com/baraich))
- Mezannic ([@mezannic](http://github.com/mezannic))
- Aaron ([@aklinker1](http://github.com/aklinker1))
## v0.19.10
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.9...wxt-v0.19.10)
### 🔥 Performance
- Reduce hypersensitive onChange of watcher ([#978](https://github.com/wxt-dev/wxt/pull/978))
### 🩹 Fixes
- Fix config manifest type ([#973](https://github.com/wxt-dev/wxt/pull/973))
### 📖 Documentation
- Examples reference outDir vs. outputDir ([#982](https://github.com/wxt-dev/wxt/pull/982))
- Improved docs and links ([#970](https://github.com/wxt-dev/wxt/pull/970))
### 🌊 Types
- Fix `ExtensionRunnerConfig.chromiumPref` type ([fda1e18](https://github.com/wxt-dev/wxt/commit/fda1e18))
### ❤️ Contributors
- 1natsu ([@1natsu172](http://github.com/1natsu172))
- Okinea Dev ([@okineadev](http://github.com/okineadev))
- The-syndrome <meltdown-syndrome@proton.me>
- Hikiko4ern ([@hikiko4ern](http://github.com/hikiko4ern))
## v0.19.9
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.8...wxt-v0.19.9)
### 🚀 Enhancements
- **modules:** Add `wxt.hook` alias for `wxt.hooks.hook` ([c5f78d0](https://github.com/wxt-dev/wxt/commit/c5f78d0))
- Use `@types/chrome` for config manifest type ([#969](https://github.com/wxt-dev/wxt/pull/969))
### 🩹 Fixes
- Allow adding multiple hyphens in an entrypoint name ([#949](https://github.com/wxt-dev/wxt/pull/949))
- Duplicate `BuildOutput.publicAssets` ([#951](https://github.com/wxt-dev/wxt/pull/951))
- Properly overload `import.meta.env` with WXT's own environment globals ([#966](https://github.com/wxt-dev/wxt/pull/966))
### 📖 Documentation
- Add docs around `importEntrypoint` to relevant functions ([143b5ac](https://github.com/wxt-dev/wxt/commit/143b5ac))
### 🌊 Types
- **modules:** Use `NestedHooks` instead of `Partial` for hooks object ([0ebb013](https://github.com/wxt-dev/wxt/commit/0ebb013))
### 🏡 Chore
- Fix type error ([#946](https://github.com/wxt-dev/wxt/pull/946))
- Add `oxlint` for linting ([#947](https://github.com/wxt-dev/wxt/pull/947))
- **deps:** Bump ora from 8.0.1 to 8.1.0 ([#961](https://github.com/wxt-dev/wxt/pull/961))
- **deps:** Bump unimport from 3.9.1 to 3.11.1 ([#960](https://github.com/wxt-dev/wxt/pull/960))
- **deps:** Bump execa from 9.3.0 to 9.3.1 ([#957](https://github.com/wxt-dev/wxt/pull/957))
- Cleanup leftover E2E test artifacts ([#968](https://github.com/wxt-dev/wxt/pull/968))
### ❤️ Contributors
- 1natsu ([@1natsu172](http://github.com/1natsu172))
## v0.19.8
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.7...wxt-v0.19.8)
### 🔥 Performance
- Ignore output directories for all `vite.watcher` ([#924](https://github.com/wxt-dev/wxt/pull/924))
### 🩹 Fixes
- Fallback to GitHub API for listing templates when ungh is down ([#944](https://github.com/wxt-dev/wxt/pull/944))
### 🏡 Chore
- **types:** Cleanup types in wxt/browser/chrome ([#932](https://github.com/wxt-dev/wxt/pull/932))
### ❤️ Contributors
- 1natsu
## v0.19.7
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.6...wxt-v0.19.7)
+27 -8
View File
@@ -1,16 +1,17 @@
<!-- DO NOT EDIT, THIS FILE WAS GENERATED BY '../../scripts/generate-readmes.sh' -->
<h1 align="center">
<img style="vertical-align:middle" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
<img align="top" width="44" src="https://raw.githubusercontent.com/wxt-dev/wxt/HEAD/docs/public/hero-logo.svg" alt="WXT Logo">
<span>WXT</span>
</h1>
<p align="center">
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="npm" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
<a href="https://www.npmjs.com/package/wxt" target="_blank"><img alt="downloads" src="https://img.shields.io/npm/dm/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="NPM" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
<a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE" target="_blank"><img alt="license | MIT" src="https://img.shields.io/npm/l/wxt?labelColor=black&color=%234fa048"></a>
<span> </span>
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="Codecov" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
<a href="https://codecov.io/github/wxt-dev/wxt" target="_blank"><img alt="coverage" src="https://img.shields.io/codecov/c/github/wxt-dev/wxt?labelColor=black&color=%234fa048"></a>
</p>
<p align="center">
@@ -18,7 +19,7 @@
<br/>
<span>⚡</span>
<br/>
<q><i>It's like Nuxt, but for Chrome Extensions</i></q>
<q><i>It's like Nuxt, but for Web Extensions</i></q>
</p>
<p align="center">
@@ -43,10 +44,21 @@ https://github.com/wxt-dev/wxt/assets/10101283/4d678939-1bdb-495c-9c36-3aa281d84
Bootstrap a new project:
<!-- automd:pm-x version="latest" name="wxt" args="init" -->
```sh
pnpm dlx wxt@latest init <project-name>
# npm
npx wxt@latest init
# pnpm
pnpm dlx wxt@latest init
# bun
bunx wxt@latest init
```
<!-- /automd -->
Or see the [installation guide](https://wxt.dev/guide/installation.html) to get started with WXT.
## Features
@@ -66,6 +78,13 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
## Contributors
<!-- automd:contributors author="aklinker1" license="MIT" github="wxt-dev/wxt" -->
Published under the [MIT](https://github.com/wxt-dev/wxt/blob/main/LICENSE) license.
Made by [@aklinker1](https://github.com/aklinker1) and [community](https://github.com/wxt-dev/wxt/graphs/contributors) 💛
<br><br>
<a href="https://github.com/wxt-dev/wxt/graphs/contributors">
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
<img src="https://contrib.rocks/image?repo=wxt-dev/wxt" />
</a>
<!-- /automd -->
+6 -4
View File
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest';
import { TestProject } from '../utils';
import { execaCommand } from 'execa';
import spawn from 'nano-spawn';
describe('Auto Imports', () => {
describe('imports: { ... }', () => {
@@ -180,7 +180,7 @@ describe('Auto Imports', () => {
await project.prepare({
imports: { eslintrc: { enabled: version } },
});
return await execaCommand('pnpm eslint entrypoints/background.js', {
return await spawn('pnpm', ['eslint', 'entrypoints/background.js'], {
cwd: project.root,
});
}
@@ -205,7 +205,8 @@ describe('Auto Imports', () => {
);
await expect(runEslint(project, 9)).rejects.toMatchObject({
message: expect.stringContaining(
exitCode: 1,
stdout: expect.stringContaining(
"'defineBackground' is not defined",
),
});
@@ -253,7 +254,8 @@ describe('Auto Imports', () => {
);
await expect(runEslint(project, 8)).rejects.toMatchObject({
message: expect.stringContaining(
exitCode: 1,
stdout: expect.stringContaining(
"'defineBackground' is not defined",
),
});
+57
View File
@@ -14,6 +14,12 @@ const hooks: WxtHooks = {
'entrypoints:grouped': vi.fn(),
'vite:build:extendConfig': vi.fn(),
'vite:devServer:extendConfig': vi.fn(),
'zip:start': vi.fn(),
'zip:extension:start': vi.fn(),
'zip:extension:done': vi.fn(),
'zip:sources:start': vi.fn(),
'zip:sources:done': vi.fn(),
'zip:done': vi.fn(),
};
function expectHooksToBeCalled(
@@ -53,6 +59,12 @@ describe('Hooks', () => {
'entrypoints:resolved': true,
'vite:build:extendConfig': false,
'vite:devServer:extendConfig': false,
'zip:start': false,
'zip:extension:start': false,
'zip:extension:done': false,
'zip:sources:start': false,
'zip:sources:done': false,
'zip:done': false,
});
});
@@ -74,6 +86,12 @@ describe('Hooks', () => {
'entrypoints:resolved': true,
'vite:build:extendConfig': 1,
'vite:devServer:extendConfig': false,
'zip:start': false,
'zip:extension:start': false,
'zip:extension:done': false,
'zip:sources:start': false,
'zip:sources:done': false,
'zip:done': false,
});
});
@@ -95,6 +113,39 @@ describe('Hooks', () => {
'entrypoints:resolved': true,
'vite:build:extendConfig': 1,
'vite:devServer:extendConfig': false,
'zip:start': true,
'zip:extension:start': true,
'zip:extension:done': true,
'zip:sources:start': false,
'zip:sources:done': false,
'zip:done': true,
});
});
it('zip -b firefox should call hooks', async () => {
const project = new TestProject();
project.addFile('entrypoints/popup.html', '<html></html>');
await project.zip({ hooks, browser: 'firefox' });
expectHooksToBeCalled({
ready: true,
'prepare:types': true,
'prepare:publicPaths': true,
'build:before': true,
'build:done': true,
'build:publicAssets': true,
'build:manifestGenerated': true,
'entrypoints:grouped': true,
'entrypoints:resolved': true,
'vite:build:extendConfig': 1,
'vite:devServer:extendConfig': false,
'zip:start': true,
'zip:extension:start': true,
'zip:extension:done': true,
'zip:sources:start': true,
'zip:sources:done': true,
'zip:done': true,
});
});
@@ -122,6 +173,12 @@ describe('Hooks', () => {
'entrypoints:resolved': true,
'vite:build:extendConfig': 2,
'vite:devServer:extendConfig': 1,
'zip:start': false,
'zip:extension:start': false,
'zip:extension:done': false,
'zip:sources:start': false,
'zip:sources:done': false,
'zip:done': false,
});
});
});
+19 -11
View File
@@ -1,6 +1,6 @@
import { describe, it, expect } from 'vitest';
import { TestProject, WXT_PACKAGE_DIR } from '../utils';
import { execaCommand } from 'execa';
import spawn from 'nano-spawn';
import glob from 'fast-glob';
import { mkdir, writeJson } from 'fs-extra';
@@ -8,11 +8,15 @@ describe('Init command', () => {
it('should download and create a template', async () => {
const project = new TestProject();
await execaCommand(`pnpm -s wxt init ${project.root} -t vue --pm npm`, {
env: { ...process.env, CI: 'true' },
stdio: 'ignore',
cwd: WXT_PACKAGE_DIR,
});
await spawn(
'pnpm',
['-s', 'wxt', 'init', project.root, '-t', 'vue', '--pm', 'npm'],
{
env: { CI: 'true' },
stdio: 'ignore',
cwd: WXT_PACKAGE_DIR,
},
);
const files = await glob('**/*', {
cwd: project.root,
onlyFiles: true,
@@ -51,11 +55,15 @@ describe('Init command', () => {
await writeJson(project.resolvePath('package.json'), {});
await expect(() =>
execaCommand(`pnpm -s wxt init ${project.root} -t vue --pm npm`, {
env: { ...process.env, CI: 'true' },
stdio: 'ignore',
cwd: WXT_PACKAGE_DIR,
}),
spawn(
'pnpm',
['-s', 'wxt', 'init', project.root, '-t', 'vue', '--pm', 'npm'],
{
env: { CI: 'true' },
stdio: 'ignore',
cwd: WXT_PACKAGE_DIR,
},
),
).rejects.toThrowError('Command failed with exit code 1:');
});
});
@@ -0,0 +1,40 @@
import { test, expect } from 'vitest';
import spawn from 'nano-spawn';
import {
NpmListDependency,
NpmListProject,
} from '../../src/core/package-managers/npm';
// Tests to ensure the total size of the WXT module is as small as possible
// https://pkg-size.dev/wxt
test('Only one version of esbuild should be installed (each version is ~20mb of node_modules)', async () => {
const { stdout } = await spawn('pnpm', [
'why',
'esbuild',
'--prod',
'--json',
]);
const projects: NpmListProject[] = JSON.parse(stdout);
const esbuildVersions = new Set<string>();
iterateDependencies(projects, (name, meta) => {
if (name === 'esbuild') esbuildVersions.add(meta.version);
});
expect([...esbuildVersions]).toHaveLength(1);
});
function iterateDependencies(
projects: NpmListProject[],
cb: (name: string, meta: NpmListDependency) => void,
) {
const recurse = (dependencies: Record<string, NpmListDependency>) => {
Object.entries(dependencies).forEach(([name, meta]) => {
cb(name, meta);
if (meta.dependencies) recurse(meta.dependencies);
});
};
projects.forEach((project) => {
if (project.dependencies) recurse(project.dependencies);
});
}
@@ -362,7 +362,7 @@ describe('Output Directory Structure', () => {
.toMatchInlineSnapshot(`
".output/chrome-mv3/background.js
----------------------------------------
var _background = function() {
var background = function() {
"use strict";
var _a, _b;
function defineBackground(arg) {
@@ -372,13 +372,13 @@ describe('Output Directory Structure', () => {
function logHello(name) {
console.log(\`Hello \${name}!\`);
}
_background;
background;
const definition = defineBackground({
main() {
logHello("background");
}
});
_background;
background;
function initPlugins() {
}
// @ts-expect-error
@@ -411,7 +411,7 @@ describe('Output Directory Structure', () => {
const result$1 = result;
return result$1;
}();
_background;
background;
"
`);
});
@@ -13,7 +13,6 @@ describe('TypeScript Project', () => {
".wxt/types/globals.d.ts
----------------------------------------
// Generated by wxt
export {}
interface ImportMetaEnv {
readonly MANIFEST_VERSION: 2 | 3;
readonly BROWSER: string;
+1 -1
View File
@@ -74,7 +74,7 @@ describe('User Config', () => {
);
await project.build({
// @ts-expect-error: Specifically setting an invalid field for the test - it should show up in the snapshot
// Specifically setting an invalid field for the test - it should show up in the snapshot
manifest: ({ manifestVersion, command }) => ({
example_customization: [String(manifestVersion), command],
}),
+76 -5
View File
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest';
import { TestProject } from '../utils';
import extract from 'extract-zip';
import { execaCommand } from 'execa';
import spawn from 'nano-spawn';
import { readFile, writeFile } from 'fs-extra';
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
@@ -41,13 +41,15 @@ describe('Zipping', () => {
// Build zipped extension
await expect(
execaCommand('pnpm i --ignore-workspace --frozen-lockfile false', {
spawn('pnpm', ['i', '--ignore-workspace', '--frozen-lockfile', 'false'], {
cwd: unzipDir,
}),
).resolves.toMatchObject({ exitCode: 0 });
).resolves.not.toHaveProperty('exitCode');
await expect(
execaCommand('pnpm wxt build -b firefox', { cwd: unzipDir }),
).resolves.toMatchObject({ exitCode: 0 });
spawn('pnpm', ['wxt', 'build', '-b', 'firefox'], {
cwd: unzipDir,
}),
).resolves.not.toHaveProperty('exitCode');
await expect(project.fileExists(unzipDir, '.output')).resolves.toBe(true);
expect(
@@ -178,4 +180,73 @@ describe('Zipping', () => {
true,
);
});
it.each(['firefox', 'opera'])(
'should create sources zip for "%s" browser when sourcesZip is undefined',
async (browser) => {
const project = new TestProject({
name: 'test',
version: '1.0.0',
});
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
);
const sourcesZip = project.resolvePath('.output/test-1.0.0-sources.zip');
await project.zip({
browser,
});
expect(await project.fileExists(sourcesZip)).toBe(true);
},
);
it.each(['firefox', 'chrome'])(
'should create sources zip for "%s" when sourcesZip is true',
async (browser) => {
const project = new TestProject({
name: 'test',
version: '1.0.0',
});
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
);
const sourcesZip = project.resolvePath('.output/test-1.0.0-sources.zip');
await project.zip({
browser,
zip: {
zipSources: true,
},
});
expect(await project.fileExists(sourcesZip)).toBe(true);
},
);
it.each(['firefox', 'chrome'])(
'should not create sources zip for "%s" when sourcesZip is false',
async (browser) => {
const project = new TestProject({
name: 'test',
version: '1.0.0',
});
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
);
const sourcesZip = project.resolvePath('.output/test-1.0.0-sources.zip');
await project.zip({
browser,
zip: {
zipSources: false,
},
});
expect(await project.fileExists(sourcesZip)).toBe(false);
},
);
});
+2 -2
View File
@@ -1,7 +1,7 @@
import { dirname, relative, resolve } from 'path';
import fs, { mkdir } from 'fs-extra';
import glob from 'fast-glob';
import { execaCommand } from 'execa';
import spawn from 'nano-spawn';
import {
InlineConfig,
UserConfig,
@@ -119,7 +119,7 @@ export class TestProject {
await fs.writeFile(filePath, content ?? '', 'utf-8');
}
await execaCommand('pnpm --ignore-workspace i --ignore-scripts', {
await spawn('pnpm', ['--ignore-workspace', 'i', '--ignore-scripts'], {
cwd: this.root,
});
await mkdir(resolve(this.root, 'public'), { recursive: true }).catch(
+25 -24
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.19.7",
"version": "0.19.11",
"description": "Next gen framework for developing web extensions",
"repository": {
"type": "git",
@@ -82,22 +82,23 @@
},
"dependencies": {
"@aklinker1/rollup-plugin-visualizer": "5.12.0",
"@types/chrome": "^0.0.269",
"@types/webextension-polyfill": "^0.10.7",
"@webext-core/fake-browser": "^1.3.1",
"@webext-core/isolated-element": "^1.1.2",
"@webext-core/match-patterns": "^1.0.3",
"async-mutex": "^0.5.0",
"c12": "^1.11.1",
"c12": "^1.11.2",
"cac": "^6.7.14",
"chokidar": "^3.6.0",
"ci-info": "^4.0.0",
"consola": "^3.2.3",
"defu": "^6.1.4",
"dequal": "^2.0.3",
"esbuild": "^0.23.0",
"execa": "^9.3.0",
"dotenv": "^16.4.5",
"esbuild": "^0.21.5",
"fast-glob": "^3.3.2",
"filesize": "^10.1.4",
"filesize": "^10.1.6",
"fs-extra": "^11.2.0",
"get-port": "^7.1.0",
"giget": "^1.2.3",
@@ -106,48 +107,48 @@
"jiti": "^1.21.6",
"json5": "^2.2.3",
"jszip": "^3.10.1",
"linkedom": "^0.18.4",
"magicast": "^0.3.4",
"linkedom": "^0.18.5",
"magicast": "^0.3.5",
"minimatch": "^10.0.1",
"nano-spawn": "^0.1.0",
"natural-compare": "^1.4.0",
"normalize-path": "^3.0.0",
"nypm": "^0.3.9",
"ohash": "^1.1.3",
"nypm": "^0.3.12",
"ohash": "^1.1.4",
"open": "^10.1.0",
"ora": "^8.0.1",
"picocolors": "^1.0.1",
"ora": "^8.1.0",
"perfect-debounce": "^1.0.0",
"picocolors": "^1.1.0",
"prompts": "^2.4.2",
"publish-browser-extension": "^2.1.3",
"unimport": "^3.9.1",
"vite": "^5.3.5",
"vite-node": "^2.0.4",
"publish-browser-extension": "^2.2.2",
"scule": "^1.3.0",
"unimport": "^3.13.1",
"vite": "^5.4.8",
"vite-node": "^2.1.2",
"web-ext-run": "^0.2.1",
"webextension-polyfill": "^0.12.0"
},
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"@aklinker1/check": "^1.4.5",
"@faker-js/faker": "^8.4.1",
"@types/chrome": "^0.0.269",
"@types/fs-extra": "^11.0.4",
"@types/lodash.merge": "^4.6.9",
"@types/natural-compare": "^1.4.3",
"@types/node": "^20.14.12",
"@types/node": "^20.16.10",
"@types/normalize-path": "^3.0.2",
"@types/prompts": "^2.4.9",
"extract-zip": "^2.0.1",
"happy-dom": "^14.12.3",
"lodash.merge": "^4.6.2",
"oxlint": "^0.9.9",
"p-map": "^7.0.2",
"publint": "^0.2.9",
"publint": "^0.2.11",
"tsx": "4.15.7",
"typescript": "^5.5.4",
"typescript": "^5.6.2",
"unbuild": "^2.0.0",
"vitest": "^2.0.4",
"vitest": "^2.1.2",
"vitest-plugin-random-seed": "^1.1.0"
},
"peerDependencies": {
"@types/chrome": "*"
},
"peerDependenciesMeta": {
"@types/chrome": {
"optional": true
@@ -903,5 +903,24 @@ describe('Storage Utils', () => {
// @ts-expect-error
await storage.getItem('loca:test').catch(() => {});
});
it('should return a nullable type when getItem is called without a fallback', async () => {
const res = await storage.getItem<string>('local:test');
expectTypeOf(res).toBeNullable();
});
it('should return a non-null type when getItem is called with a fallback', async () => {
const res = await storage.getItem('local:test', {
fallback: 'test',
});
expectTypeOf(res).not.toBeNullable();
});
it('should return a non-null type when getItem is called with a fallback and the first type parameter is passed', async () => {
const res = await storage.getItem<string>('local:test', {
fallback: 'test',
});
expectTypeOf(res).not.toBeNullable();
});
});
});
+4 -9
View File
@@ -1,9 +1,4 @@
/// <reference types="chrome" />
/**
* @module wxt/browser/chrome
*/
import type { WxtRuntime, WxtI18n } from './index';
/**
* EXPERIMENTAL
*
@@ -11,11 +6,11 @@ import type { WxtRuntime, WxtI18n } from './index';
*
* @module wxt/browser/chrome
*/
import type { WxtRuntime, WxtI18n } from './index';
export type Chrome = typeof chrome;
export type WxtBrowser = Omit<Chrome, 'runtime' | 'i18n'> & {
runtime: WxtRuntime & Omit<Chrome['runtime'], 'getURL'>;
i18n: WxtI18n & Omit<Chrome['i18n'], 'getMessage'>;
export type WxtBrowser = Omit<typeof chrome, 'runtime' | 'i18n'> & {
runtime: WxtRuntime & Omit<(typeof chrome)['runtime'], 'getURL'>;
i18n: WxtI18n & Omit<(typeof chrome)['i18n'], 'getMessage'>;
};
export const browser: WxtBrowser =
+10 -6
View File
@@ -11,13 +11,17 @@ export type AugmentedBrowser = Omit<Browser, 'runtime' | 'i18n'> & {
i18n: WxtI18n & Omit<Browser['i18n'], 'getMessage'>;
};
export interface WxtRuntime {
// Overriden per-project
}
/**
* This interface is empty because it is generated per-project when running `wxt prepare`. See:
* - `.wxt/types/paths.d.ts`
*/
export interface WxtRuntime {}
export interface WxtI18n {
// Overriden per-project
}
/**
* This interface is empty because it is generated per-project when running `wxt prepare`. See:
* - `.wxt/types/i18n.d.ts`
*/
export interface WxtI18n {}
export const browser: AugmentedBrowser = originalBrowser;
+43 -1
View File
@@ -237,7 +237,9 @@ describe('CLI', () => {
mockArgv('zip');
await importCli();
expect(zipMock).toBeCalledWith({});
expect(zipMock).toBeCalledWith({
zip: {},
});
});
it('should respect passing a custom root', async () => {
@@ -246,6 +248,7 @@ describe('CLI', () => {
expect(zipMock).toBeCalledWith({
root: 'path/to/root',
zip: {},
});
});
@@ -255,6 +258,7 @@ describe('CLI', () => {
expect(zipMock).toBeCalledWith({
configFile: './path/to/config.ts',
zip: {},
});
});
@@ -264,6 +268,7 @@ describe('CLI', () => {
expect(zipMock).toBeCalledWith({
mode: 'development',
zip: {},
});
});
@@ -273,6 +278,7 @@ describe('CLI', () => {
expect(zipMock).toBeCalledWith({
browser: 'firefox',
zip: {},
});
});
@@ -282,6 +288,7 @@ describe('CLI', () => {
expect(zipMock).toBeCalledWith({
manifestVersion: 2,
zip: {},
});
});
@@ -291,6 +298,7 @@ describe('CLI', () => {
expect(zipMock).toBeCalledWith({
manifestVersion: 3,
zip: {},
});
});
@@ -300,6 +308,40 @@ describe('CLI', () => {
expect(zipMock).toBeCalledWith({
debug: true,
zip: {},
});
});
it('should pass undefined for zipSources when --sources is not passed', async () => {
mockArgv('zip');
await importCli();
expect(zipMock).toBeCalledWith({
zip: {
zipSources: undefined,
},
});
});
it('should pass true for zipSources when --sources is passed', async () => {
mockArgv('zip', '--sources');
await importCli();
expect(zipMock).toBeCalledWith({
zip: {
zipSources: true,
},
});
});
it('should pass false for zipSources when --sources=false is passed', async () => {
mockArgv('zip', '--sources=false');
await importCli();
expect(zipMock).toBeCalledWith({
zip: {
zipSources: false,
},
});
});
});
+2 -2
View File
@@ -5,6 +5,7 @@ import { printHeader } from '../core/utils/log';
import { formatDuration } from '../core/utils/time';
import { ValidationError } from '../core/utils/validation';
import { registerWxt } from '../core/wxt';
import spawn from 'nano-spawn';
/**
* Wrap an action handler to add a timer, error handling, and maybe enable debug mode.
@@ -87,8 +88,7 @@ export function createAliasedCommand(
const args = process.argv.slice(
process.argv.indexOf(aliasedCommand.name) + 1,
);
const { execa } = await import('execa');
await execa(bin, args, {
await spawn(bin, args, {
stdio: 'inherit',
});
} catch {
+4
View File
@@ -95,6 +95,7 @@ cli
.option('-b, --browser <browser>', 'specify a browser')
.option('--mv3', 'target manifest v3')
.option('--mv2', 'target manifest v2')
.option('--sources', 'always create sources zip')
.action(
wrapAction(async (root, flags) => {
await zip({
@@ -104,6 +105,9 @@ cli
manifestVersion: flags.mv3 ? 3 : flags.mv2 ? 2 : undefined,
configFile: flags.config,
debug: flags.debug,
zip: {
zipSources: flags.sources,
},
});
}),
);
+5 -3
View File
@@ -59,6 +59,11 @@ export async function createViteBuilder(
config.build.sourcemap = 'inline';
}
config.server ??= {};
config.server.watch = {
ignored: [`${wxtConfig.outBaseDir}/**`, `${wxtConfig.wxtDir}/**`],
};
config.plugins ??= [];
config.plugins.push(
wxtPlugins.download(wxtConfig),
@@ -287,9 +292,6 @@ export async function createViteBuilder(
strictPort: true,
host: info.hostname,
origin: info.origin,
watch: {
ignored: [`${wxtConfig.outBaseDir}/**`, `${wxtConfig.wxtDir}/**`],
},
},
};
const baseConfig = await getBaseConfig();
+8 -2
View File
@@ -1,3 +1,4 @@
import { debounce } from 'perfect-debounce';
import {
BuildStepOutput,
EntrypointGroup,
@@ -144,7 +145,7 @@ function createFileReloader(server: WxtDevServer) {
const fileChangedMutex = new Mutex();
const changeQueue: Array<[string, string]> = [];
return async (event: string, path: string) => {
const cb = async (event: string, path: string) => {
changeQueue.push([event, path]);
await fileChangedMutex.runExclusive(async () => {
@@ -211,11 +212,16 @@ function createFileReloader(server: WxtDevServer) {
wxt.logger.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
break;
}
} catch (err) {
} catch {
// Catch build errors instead of crashing. Don't log error either, builder should have already logged it
}
});
};
return debounce(cb, wxt.config.dev.server!.watchDebounce, {
leading: true,
trailing: false,
});
}
/**
@@ -1,23 +1,18 @@
import { Entrypoint, WxtDirEntry, WxtDirFileEntry } from '../../../types';
import { Entrypoint, WxtDirEntry, WxtDirFileEntry } from '../types';
import fs from 'fs-extra';
import { dirname, relative, resolve } from 'node:path';
import {
getEntrypointBundlePath,
isHtmlEntrypoint,
} from '../../utils/entrypoints';
import { getEntrypointGlobals, getGlobals } from '../../utils/globals';
import { normalizePath } from '../../utils/paths';
import { getEntrypointBundlePath, isHtmlEntrypoint } from './utils/entrypoints';
import { getEntrypointGlobals, getGlobals } from './utils/globals';
import { normalizePath } from './utils/paths';
import path from 'node:path';
import { Message, parseI18nMessages } from '../../utils/i18n';
import { writeFileIfDifferent, getPublicFiles } from '../../utils/fs';
import { wxt } from '../../wxt';
import { Message, parseI18nMessages } from './utils/i18n';
import { writeFileIfDifferent, getPublicFiles } from './utils/fs';
import { wxt } from './wxt';
/**
* Generate and write all the files inside the `InternalConfig.typesDir` directory.
*/
export async function generateTypesDir(
entrypoints: Entrypoint[],
): Promise<void> {
export async function generateWxtDir(entrypoints: Entrypoint[]): Promise<void> {
await fs.ensureDir(wxt.config.typesDir);
const entries: WxtDirEntry[] = [
@@ -201,7 +196,6 @@ async function getGlobalsDeclarationEntry(): Promise<WxtDirFileEntry> {
path: 'types/globals.d.ts',
text: [
'// Generated by wxt',
'export {}',
'interface ImportMetaEnv {',
...globals.map((global) => ` readonly ${global.name}: ${global.type};`),
'}',
+55 -32
View File
@@ -99,53 +99,74 @@ interface Template {
}
async function listTemplates(): Promise<Template[]> {
try {
const res = await fetch('https://ungh.cc/repos/wxt-dev/wxt/files/main');
if (res.status >= 300)
throw Error(`Request failed with status ${res.status} ${res.statusText}`);
const templates = await listTemplatesUngh().catch((err) => {
consola.debug('Failed to load templates via ungh:', err);
return listTemplatesGithub();
});
return templates.sort((l, r) => {
const lWeight = TEMPLATE_SORT_WEIGHT[l.name] ?? Number.MAX_SAFE_INTEGER;
const rWeight = TEMPLATE_SORT_WEIGHT[r.name] ?? Number.MAX_SAFE_INTEGER;
const diff = lWeight - rWeight;
if (diff !== 0) return diff;
return l.name.localeCompare(r.name);
});
}
const data = (await res.json()) as {
meta: {
sha: string;
};
files: Array<{
path: string;
mode: string;
sha: string;
size: number;
}>;
async function listTemplatesUngh(): Promise<Template[]> {
const res = await fetch('https://ungh.cc/repos/wxt-dev/wxt/files/main');
if (res.status !== 200)
throw Error(
`Request failed with status ${res.status} ${res.statusText}: ${await res.text()}`,
);
const data = (await res.json()) as {
meta: {
sha: string;
};
return data.files
.map((item) => item.path.match(/templates\/(.+)\/package\.json/)?.[1])
.filter((name) => name != null)
.map((name) => ({ name: name!, path: `templates/${name}` }))
.sort((l, r) => {
const lWeight = TEMPLATE_SORT_WEIGHT[l.name] ?? Number.MAX_SAFE_INTEGER;
const rWeight = TEMPLATE_SORT_WEIGHT[r.name] ?? Number.MAX_SAFE_INTEGER;
const diff = lWeight - rWeight;
if (diff !== 0) return diff;
return l.name.localeCompare(r.name);
});
} catch (err) {
consola.error(err);
throw Error(`Failed to load templates`);
}
files: Array<{
path: string;
mode: string;
sha: string;
size: number;
}>;
};
return data.files
.map((item) => item.path.match(/templates\/(.+)\/package\.json/)?.[1])
.filter((name) => name != null)
.map((name) => ({ name: name!, path: `templates/${name}` }));
}
async function listTemplatesGithub(): Promise<Template[]> {
const res = await fetch(
`https://api.github.com/repos/${REPO}/contents/templates`,
{ headers: { Accept: 'application/vnd.github+json' } },
);
if (res.status !== 200)
throw Error(
`Request failed with status ${res.status} ${res.statusText}: ${await res.text()}`,
);
// Schema is Example4 of https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#get-repository-content
return (await res.json()) as Array<{
name: string;
path: string;
sha: string;
size: number;
}>;
}
async function cloneProject({
directory,
template,
packageManager,
}: {
directory: string;
template: Template;
packageManager: string;
}) {
const { default: ora } = await import('ora');
const spinner = ora('Downloading template').start();
try {
// 1. Clone repo
await downloadTemplate(`gh:wxt-dev/wxt/${template.path}`, {
await downloadTemplate(`gh:${REPO}/${template.path}`, {
dir: directory,
force: true,
});
@@ -180,3 +201,5 @@ const TEMPLATE_SORT_WEIGHT: Record<string, number> = {
vue: 1,
react: 2,
};
const REPO = 'wxt-dev/wxt';
@@ -1,7 +1,7 @@
import { beforeAll, describe, expect, it } from 'vitest';
import path from 'node:path';
import { npm } from '../npm';
import { execaCommand } from 'execa';
import spawn from 'nano-spawn';
import { exists } from 'fs-extra';
describe('NPM Package Management Utils', () => {
@@ -9,7 +9,7 @@ describe('NPM Package Management Utils', () => {
const cwd = path.resolve(__dirname, 'fixtures/simple-npm-project');
beforeAll(async () => {
// NPM needs the modules installed for 'npm ls' to work
await execaCommand('npm i', { cwd });
await spawn('npm', ['i'], { cwd });
}, 60e3);
it('should list direct dependencies', async () => {
@@ -1,7 +1,7 @@
import { beforeAll, describe, expect, it } from 'vitest';
import path from 'node:path';
import { pnpm } from '../pnpm';
import { execaCommand } from 'execa';
import spawn from 'nano-spawn';
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
@@ -10,7 +10,7 @@ describe('PNPM Package Management Utils', () => {
const cwd = path.resolve(__dirname, 'fixtures/simple-pnpm-project');
beforeAll(async () => {
// PNPM needs the modules installed, or 'pnpm ls' will return a blank list.
await execaCommand('pnpm i --ignore-workspace', { cwd });
await spawn('pnpm', ['i', '--ignore-workspace'], { cwd });
});
it('should list direct dependencies', async () => {
@@ -1,5 +1,6 @@
import { dedupeDependencies, npm } from './npm';
import { WxtPackageManagerImpl } from './types';
import spawn from 'nano-spawn';
export const bun: WxtPackageManagerImpl = {
overridesKey: 'overrides', // But also supports "resolutions"
@@ -11,8 +12,7 @@ export const bun: WxtPackageManagerImpl = {
if (options?.all) {
args.push('--all');
}
const { execa } = await import('execa');
const res = await execa('bun', args, { cwd: options?.cwd });
const res = await spawn('bun', args, { cwd: options?.cwd });
return dedupeDependencies(
res.stdout
.split('\n')
@@ -2,13 +2,13 @@ import { Dependency } from '../../types';
import { WxtPackageManagerImpl } from './types';
import path from 'node:path';
import { ensureDir } from 'fs-extra';
import spawn from 'nano-spawn';
export const npm: WxtPackageManagerImpl = {
overridesKey: 'overrides',
async downloadDependency(id, downloadDir) {
await ensureDir(downloadDir);
const { execa } = await import('execa');
const res = await execa('npm', ['pack', id, '--json'], {
const res = await spawn('npm', ['pack', id, '--json'], {
cwd: downloadDir,
});
const packed: PackedDependency[] = JSON.parse(res.stdout);
@@ -19,8 +19,7 @@ export const npm: WxtPackageManagerImpl = {
if (options?.all) {
args.push('--depth', 'Infinity');
}
const { execa } = await import('execa');
const res = await execa('npm', args, { cwd: options?.cwd });
const res = await spawn('npm', args, { cwd: options?.cwd });
const project: NpmListProject = JSON.parse(res.stdout);
return flattenNpmListOutput([project]);
@@ -1,5 +1,6 @@
import { NpmListProject, flattenNpmListOutput, npm } from './npm';
import { WxtPackageManagerImpl } from './types';
import spawn from 'nano-spawn';
export const pnpm: WxtPackageManagerImpl = {
overridesKey: 'resolutions', // "pnpm.overrides" has a higher priority, but I don't want to deal with nesting
@@ -19,8 +20,7 @@ export const pnpm: WxtPackageManagerImpl = {
) {
args.push('--ignore-workspace');
}
const { execa } = await import('execa');
const res = await execa('pnpm', args, { cwd: options?.cwd });
const res = await spawn('pnpm', args, { cwd: options?.cwd });
const projects: NpmListProject[] = JSON.parse(res.stdout);
return flattenNpmListOutput(projects);
@@ -1,6 +1,7 @@
import { Dependency } from '../../types';
import { WxtPackageManagerImpl } from './types';
import { dedupeDependencies, npm } from './npm';
import spawn from 'nano-spawn';
export const yarn: WxtPackageManagerImpl = {
overridesKey: 'resolutions',
@@ -12,8 +13,7 @@ export const yarn: WxtPackageManagerImpl = {
if (options?.all) {
args.push('--depth', 'Infinity');
}
const { execa } = await import('execa');
const res = await execa('yarn', args, { cwd: options?.cwd });
const res = await spawn('yarn', args, { cwd: options?.cwd });
const tree = res.stdout
.split('\n')
.map<JsonLine>((line) => JSON.parse(line))
+3 -2
View File
@@ -1,5 +1,6 @@
import { InlineConfig } from '../types';
import { findEntrypoints, generateTypesDir } from './utils/building';
import { findEntrypoints } from './utils/building';
import { generateWxtDir } from './generate-wxt-dir';
import { registerWxt, wxt } from './wxt';
export async function prepare(config: InlineConfig) {
@@ -7,5 +8,5 @@ export async function prepare(config: InlineConfig) {
wxt.logger.info('Generating types...');
const entrypoints = await findEntrypoints();
await generateTypesDir(entrypoints);
await generateWxtDir(entrypoints);
}
@@ -14,17 +14,19 @@ import {
WxtModuleWithMetadata,
ResolvedEslintrc,
Eslintrc,
} from '../../../types';
} from '../types';
import path from 'node:path';
import { createFsCache } from '../../utils/cache';
import { createFsCache } from './utils/cache';
import consola, { LogLevels } from 'consola';
import defu from 'defu';
import { NullablyRequired } from '../types';
import { NullablyRequired } from './utils/types';
import fs from 'fs-extra';
import { normalizePath } from '../paths';
import { normalizePath } from './utils/paths';
import glob from 'fast-glob';
import { builtinModules } from '../../../builtin-modules';
import { getEslintVersion } from '../eslint';
import { builtinModules } from '../builtin-modules';
import { getEslintVersion } from './utils/eslint';
import { safeStringToNumber } from './utils/number';
import { loadEnv } from './utils/env';
/**
* Given an inline config, discover the config file if necessary, merge the results, resolve any
@@ -72,6 +74,8 @@ export async function resolveConfig(
const mode = mergedConfig.mode ?? COMMAND_MODES[command];
const env: ConfigEnv = { browser, command, manifestVersion, mode };
loadEnv(mode); // Load any environment variables used below
const root = path.resolve(
inlineConfig.root ?? userConfig.root ?? process.cwd(),
);
@@ -86,7 +90,7 @@ export async function resolveConfig(
if (await isDirMissing(entrypointsDir)) {
logMissingDir(logger, 'Entrypoints', entrypointsDir);
}
const filterEntrypoints = !!mergedConfig.filterEntrypoints?.length
const filterEntrypoints = mergedConfig.filterEntrypoints?.length
? new Set(mergedConfig.filterEntrypoints)
: undefined;
const publicDir = path.resolve(srcDir, mergedConfig.publicDir ?? 'public');
@@ -124,6 +128,7 @@ export async function resolveConfig(
devServerConfig = {
port,
hostname: mergedConfig.dev?.server?.hostname ?? 'localhost',
watchDebounce: safeStringToNumber(process.env.WXT_WATCH_DEBOUNCE) ?? 800,
};
}
@@ -174,7 +179,7 @@ export async function resolveConfig(
srcDir,
typesDir,
wxtDir,
zip: resolveZipConfig(root, outBaseDir, mergedConfig),
zip: resolveZipConfig(root, browser, outBaseDir, mergedConfig),
transformManifest: mergedConfig.transformManifest,
analysis: resolveAnalysisConfig(root, mergedConfig),
userConfigMetadata: userConfigMetadata ?? {},
@@ -253,6 +258,7 @@ async function mergeInlineConfig(
function resolveZipConfig(
root: string,
browser: string,
outBaseDir: string,
mergedConfig: InlineConfig,
): NullablyRequired<ResolvedConfig['zip']> {
@@ -265,6 +271,8 @@ function resolveZipConfig(
includeSources: [],
compressionLevel: 9,
...mergedConfig.zip,
zipSources:
mergedConfig.zip?.zipSources ?? ['firefox', 'opera'].includes(browser),
exclude: mergedConfig.zip?.exclude ?? [],
excludeSources: [
'**/node_modules',
@@ -0,0 +1,18 @@
import { describe, it, expect } from 'vitest';
import { safeStringToNumber } from '../number';
describe('Number Utils', () => {
describe('safeStringToNumber', () => {
it.each([
{ arg: '1000', expected: 1000 },
{ arg: '', expected: 0 },
{ arg: '12abc', expected: null },
{ arg: undefined, expected: null },
])(
'should be safely converted from string to number: safeStringToNumber($arg) -> $expected',
({ arg, expected }) => {
expect(safeStringToNumber(arg)).toBe(expected);
},
);
});
});
@@ -21,9 +21,10 @@ describe('String utils', () => {
describe('safeVarName', () => {
it.each([
['Hello world!', '_hello_world'],
['Hello world!', 'helloWorld'],
['123', '_123'],
['abc-123', '_abc_123'],
['abc-123', 'abc123'],
['abc-123-xyz', 'abc123Xyz'],
['', '_'],
[' ', '_'],
['_', '_'],
@@ -1,8 +1,6 @@
export * from './build-entrypoints';
export * from './detect-dev-changes';
export * from './find-entrypoints';
export * from './generate-wxt-dir';
export * from './resolve-config';
export * from './group-entrypoints';
export * from './import-entrypoint';
export * from './internal-build';
@@ -1,6 +1,6 @@
import type { Manifest } from 'wxt/browser';
import { BuildOutput, Entrypoint, EntrypointGroup } from '../../../types';
import { generateTypesDir } from './generate-wxt-dir';
import { generateWxtDir } from '../../generate-wxt-dir';
import { buildEntrypoints } from './build-entrypoints';
import { generateManifest, writeManifest } from '../../utils/manifest';
import { wxt } from '../../wxt';
@@ -37,7 +37,7 @@ export async function rebuild(
const spinner = ora(`Preparing...`).start();
// Update types directory with new files and types
await generateTypesDir(allEntrypoints).catch((err) => {
await generateWxtDir(allEntrypoints).catch((err) => {
wxt.logger.warn('Failed to update .wxt directory:', err);
// Throw the error if doing a regular build, don't for dev mode.
if (wxt.config.command === 'build') throw err;
@@ -47,14 +47,16 @@ export async function rebuild(
const newOutput = await buildEntrypoints(entrypointGroups, spinner);
const mergedOutput: Omit<BuildOutput, 'manifest'> = {
steps: [...existingOutput.steps, ...newOutput.steps],
publicAssets: [...existingOutput.publicAssets, ...newOutput.publicAssets],
// Do not merge existing because all publicAssets copied everytime
publicAssets: newOutput.publicAssets,
};
const { manifest: newManifest, warnings: manifestWarnings } =
await generateManifest(allEntrypoints, mergedOutput);
const finalOutput: BuildOutput = {
manifest: newManifest,
...newOutput,
...mergedOutput,
};
// Write manifest
@@ -64,14 +66,7 @@ export async function rebuild(
spinner.clear().stop();
return {
output: {
manifest: newManifest,
steps: [...existingOutput.steps, ...finalOutput.steps],
publicAssets: [
...existingOutput.publicAssets,
...finalOutput.publicAssets,
],
},
output: finalOutput,
manifest: newManifest,
warnings: manifestWarnings,
};
+1 -1
View File
@@ -14,7 +14,7 @@ export function getEntrypointName(
): string {
const relativePath = path.relative(entrypointsDir, inputPath);
// Grab the string up to the first . or / or \\
const name = relativePath.split(/[\.\/\\]/, 2)[0];
const name = relativePath.split(/[./\\]/, 2)[0];
return name;
}
+10
View File
@@ -0,0 +1,10 @@
import { config } from 'dotenv';
/**
* Load environment files based on the current mode.
*/
export function loadEnv(mode: string) {
return config({
path: [`.env.${mode}.local`, `.env.${mode}`, `.env.local`, `.env`],
});
}
@@ -42,7 +42,7 @@ export function applyGlobals(globals: EnvGlobals): void {
try {
// @ts-expect-error: Untyped key on globalThis
globalThis[key] = envValue;
} catch (err) {
} catch {
// ignore any globals that can't be set
}
});
+2 -1
View File
@@ -5,7 +5,8 @@ export async function getEslintVersion(): Promise<string[]> {
);
const { ESLint } = require('eslint');
return ESLint.version?.split('.') ?? [];
} catch (error) {
} catch {
// Return an empty version when there's an error importing ESLint
return [];
}
}
+4 -4
View File
@@ -272,14 +272,14 @@ function addEntrypoints(
options.browser_style = popup.options.browserStyle;
if (manifest.manifest_version === 3) {
manifest.action = {
...(manifest.action ?? {}),
...manifest.action,
...options,
default_popup,
};
} else {
const key = popup.options.mv2Key ?? 'browser_action';
manifest[key] = {
...(manifest[key] ?? {}),
...manifest[key],
...options,
default_popup,
};
@@ -434,8 +434,8 @@ function discoverIcons(
/^icon@([0-9]+)w\.png$/, // icon@16w.png
/^icon@([0-9]+)h\.png$/, // icon@16h.png
/^icon@([0-9]+)\.png$/, // icon@16.png
/^icons?[\/\\]([0-9]+)\.png$/, // icon/16.png | icons/16.png
/^icons?[\/\\]([0-9]+)x[0-9]+\.png$/, // icon/16x16.png | icons/16x16.png
/^icons?[/\\]([0-9]+)\.png$/, // icon/16.png | icons/16.png
/^icons?[/\\]([0-9]+)x[0-9]+\.png$/, // icon/16x16.png | icons/16x16.png
];
// #endregion snippet
+4
View File
@@ -0,0 +1,4 @@
export function safeStringToNumber(str: string | undefined): number | null {
const num = Number(str);
return isNaN(num) ? null : num;
}
+1
View File
@@ -16,6 +16,7 @@ export async function getPackageJson(): Promise<
} catch (err) {
wxt.logger.debug(
`Failed to read package.json at: ${file}. Returning undefined.`,
err,
);
return {};
}
+13 -2
View File
@@ -1,3 +1,5 @@
import { camelCase } from 'scule';
export function kebabCaseAlphanumeric(str: string): string {
return str
.toLowerCase()
@@ -9,8 +11,17 @@ export function kebabCaseAlphanumeric(str: string): string {
* Return a safe variable name for a given string.
*/
export function safeVarName(str: string): string {
// _ prefix to ensure it doesn't start with a number
return '_' + kebabCaseAlphanumeric(str.trim()).replace('-', '_');
const name = camelCase(kebabCaseAlphanumeric(str));
if (name.match(/^[a-z]/)) return name;
// _ prefix to ensure it doesn't start with a number or other invalid symbol
return '_' + name;
}
/**
* Converts a string to a valid filename (NOT path), stripping out invalid characters.
*/
export function safeFilename(str: string): string {
return kebabCaseAlphanumeric(str);
}
/**
@@ -293,6 +293,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
downloadedPackagesDir: fakeDir(),
downloadPackages: [],
compressionLevel: 9,
zipSources: false,
},
transformManifest: () => {},
userConfigMetadata: {},
@@ -311,6 +312,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
export const fakeWxt = fakeObjectCreator<Wxt>(() => ({
config: fakeResolvedConfig(),
hook: vi.fn(),
hooks: mock(),
logger: mock(),
reloadConfig: vi.fn(),
+1 -3
View File
@@ -162,9 +162,7 @@ function getSimpleAstJson(ast: any): any {
} else if (typeof ast === 'object') {
return Object.fromEntries(
Object.entries(ast)
.filter(
([key, value]) => key !== 'loc' && key !== 'start' && key !== 'end',
)
.filter(([key]) => key !== 'loc' && key !== 'start' && key !== 'end')
.map(([key, value]) => [key, getSimpleAstJson(value)]),
);
} else {
+2 -1
View File
@@ -7,7 +7,7 @@ import {
WxtHooks,
WxtModule,
} from '../types';
import { resolveConfig } from './utils/building';
import { resolveConfig } from './resolve-config';
import { createHooks } from 'hookable';
import { createWxtPackageManager } from './package-managers';
import { createViteBuilder } from './builders/vite';
@@ -40,6 +40,7 @@ export async function registerWxt(
wxt = {
config,
hooks,
hook: hooks.hook.bind(hooks),
get logger() {
return config.logger;
},
+12 -7
View File
@@ -1,7 +1,7 @@
import { InlineConfig } from '../types';
import path from 'node:path';
import fs from 'fs-extra';
import { kebabCaseAlphanumeric } from './utils/strings';
import { safeFilename } from './utils/strings';
import { getPackageJson } from './utils/package';
import { minimatch } from 'minimatch';
import { formatDuration } from './utils/time';
@@ -20,6 +20,7 @@ import { normalizePath } from './utils/paths';
export async function zip(config?: InlineConfig): Promise<string[]> {
await registerWxt('build', config);
const output = await internalBuild();
await wxt.hooks.callHook('zip:start', wxt);
const start = Date.now();
wxt.logger.info('Zipping extension...');
@@ -27,8 +28,8 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
const projectName =
wxt.config.zip.name ??
kebabCaseAlphanumeric(
(await getPackageJson())?.name || path.dirname(process.cwd()),
safeFilename(
(await getPackageJson())?.name || path.basename(process.cwd()),
);
const applyTemplate = (template: string): string =>
template
@@ -44,17 +45,17 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
await fs.ensureDir(wxt.config.outBaseDir);
// ZIP output directory
await wxt.hooks.callHook('zip:extension:start', wxt);
const outZipFilename = applyTemplate(wxt.config.zip.artifactTemplate);
const outZipPath = path.resolve(wxt.config.outBaseDir, outZipFilename);
await zipDir(wxt.config.outDir, outZipPath, {
exclude: wxt.config.zip.exclude,
});
zipFiles.push(outZipPath);
await wxt.hooks.callHook('zip:extension:done', wxt, outZipPath);
// ZIP sources for Firefox
if (wxt.config.browser === 'firefox') {
if (wxt.config.zip.zipSources) {
await wxt.hooks.callHook('zip:sources:start', wxt);
const { overrides, files: downloadedPackages } =
await downloadPrivatePackages();
const sourcesZipFilename = applyTemplate(wxt.config.zip.sourcesTemplate);
@@ -73,6 +74,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
additionalFiles: downloadedPackages,
});
zipFiles.push(sourcesZipPath);
await wxt.hooks.callHook('zip:sources:done', wxt, sourcesZipPath);
}
await printFileList(
@@ -82,8 +84,11 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
zipFiles,
);
await wxt.hooks.callHook('zip:done', wxt, zipFiles);
return zipFiles;
}
async function zipDir(
directory: string,
outputPath: string,
+10 -6
View File
@@ -34,17 +34,21 @@ export function defineWxtModule<TOptions extends WxtModuleOptions>(
* the output directory. This will speed up project builds since it just has to
* copy some files instead of bundling them.
*
* To extract entrypoint options from a JS/TS file, use
* `wxt.builder.importEntrypoint` (see example).
*
* @param wxt The wxt instance provided by the module's setup function.
* @param entrypoint The entrypoint to be bundled along with the extension.
*
* @example
* export default defineWxtModule((wxt, options) => {
* export default defineWxtModule(async (wxt, options) => {
* const entrypointPath = "/path/to/my-entrypoint.ts";
* addEntrypoint(wxt, {
* type: "unlisted-page",
* name: "changelog",
* inputPath: "wxt-module-changelog/index.html"
* outputDir: wxt.config.outputDir,
* options: {},
* type: "content-script",
* name: "some-name",
* inputPath: entrypointPath,
* outputDir: wxt.config.outDir,
* options: await wxt.builder.importEntrypoint(entrypointPath),
* });
* });
*/
+10 -1
View File
@@ -461,7 +461,16 @@ export interface WxtStorage {
* @example
* await storage.getItem<number>("local:installDate");
*/
getItem<T>(key: StorageItemKey, opts?: GetItemOptions<T>): Promise<T | null>;
getItem<TValue>(
key: StorageItemKey,
opts: GetItemOptions<TValue> & { fallback: TValue },
): Promise<TValue>;
getItem<TValue>(
key: StorageItemKey,
opts?: GetItemOptions<TValue>,
): Promise<TValue | null>;
/**
* Get multiple items from storage. The return order is guaranteed to be the same as the order
* requested.
+122 -7
View File
@@ -130,6 +130,16 @@ export interface InlineConfig {
* @default "{{name}}-{{version}}-{{browser}}.zip"
*/
artifactTemplate?: string;
/**
* When zipping the extension, also zip sources.
*
* - `undefined`: zip sources if the target browser is "firefox" or "opera"
* - `true`: always zip sources
* - `false`: never zip sources
*
* @default undefined
*/
zipSources?: boolean;
/**
* Configure the filename output when zipping files.
*
@@ -830,18 +840,55 @@ export type ResolvedPerBrowserOptions<T, TOmitted extends keyof T = never> = {
* Manifest customization available in the `wxt.config.ts` file. You cannot configure entrypoints
* here, they are configured inline.
*/
export type UserManifest = Partial<
Omit<
Manifest.WebExtensionManifest,
export type UserManifest = {
[key in keyof chrome.runtime.ManifestV3 as key extends
| 'action'
| 'background'
| 'chrome_url_overrides'
| 'devtools_page'
| 'manifest_version'
| 'options_page'
| 'options_ui'
| 'permissions'
| 'sandbox'
>
>;
| 'web_accessible_resources'
? never
: key]?: chrome.runtime.ManifestV3[key];
} & {
// Add any Browser-specific or MV2 properties that WXT supports here
action?: chrome.runtime.ManifestV3['action'] & {
browser_style?: boolean;
};
browser_action?: chrome.runtime.ManifestV2['browser_action'] & {
browser_style?: boolean;
};
page_action?: chrome.runtime.ManifestV2['page_action'] & {
browser_style?: boolean;
};
browser_specific_settings?: {
gecko?: {
id?: string;
strict_min_version?: string;
strict_max_version?: string;
update_url?: string;
};
gecko_android?: {
strict_min_version?: string;
strict_max_version?: string;
};
safari?: {
strict_min_version?: string;
strict_max_version?: string;
};
};
permissions?: (
| chrome.runtime.ManifestPermissions
| (string & Record<never, never>)
)[];
web_accessible_resources?:
| string[]
| chrome.runtime.ManifestV3['web_accessible_resources'];
};
export type UserManifestFn = (
env: ConfigEnv,
@@ -930,7 +977,7 @@ export interface ExtensionRunnerConfig {
* }
* }
*/
chromiumPref?: string;
chromiumPref?: Record<string, any>;
/**
* By default, chrome opens a random port for debugging. Set this value to use a specific port.
*/
@@ -1107,6 +1154,9 @@ export interface WxtHooks {
) => HookResult;
/**
* Called once all entrypoints have been loaded from the `entrypointsDir`.
* Use `wxt.builder.importEntrypoint` to load entrypoint options from the
* file, or manually define them.
*
* @param wxt The configured WXT object
* @param entrypoints The list of entrypoints to be built
*/
@@ -1124,11 +1174,53 @@ export interface WxtHooks {
* @param entrypoints The list of files that will be copied into the output directory
*/
'build:publicAssets': (wxt: Wxt, files: ResolvedPublicFile[]) => HookResult;
/**
* Called before the zip process starts.
* @param wxt The configured WXT object
*/
'zip:start': (wxt: Wxt) => HookResult;
/**
* Called before zipping the extension files.
* @param wxt The configured WXT object
*/
'zip:extension:start': (wxt: Wxt) => HookResult;
/**
* Called after zipping the extension files.
* @param wxt The configured WXT object
* @param zipPath The path to the created extension zip file
*/
'zip:extension:done': (wxt: Wxt, zipPath: string) => HookResult;
/**
* Called before zipping the source files (for Firefox).
* @param wxt The configured WXT object
*/
'zip:sources:start': (wxt: Wxt) => HookResult;
/**
* Called after zipping the source files (for Firefox).
* @param wxt The configured WXT object
* @param zipPath The path to the created sources zip file
*/
'zip:sources:done': (wxt: Wxt, zipPath: string) => HookResult;
/**
* Called after the entire zip process is complete.
* @param wxt The configured WXT object
* @param zipFiles An array of paths to all created zip files
*/
'zip:done': (wxt: Wxt, zipFiles: string[]) => HookResult;
}
export interface Wxt {
config: ResolvedConfig;
hooks: Hookable<WxtHooks>;
/**
* Alias for `wxt.hooks.hook(...)`.
*/
hook: Hookable<WxtHooks>['hook'];
/**
* Alias for config.logger
*/
@@ -1203,6 +1295,10 @@ export interface ResolvedConfig {
downloadPackages: string[];
compressionLevel: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
exclude: string[];
/**
* If true, when zipping the extension, also zip the sources.
*/
zipSources: boolean;
};
/**
* @deprecated Use `build:manifestGenerated` hook instead.
@@ -1233,6 +1329,25 @@ export interface ResolvedConfig {
server?: {
port: number;
hostname: string;
/**
* The milliseconds to debounce when a file is saved before reloading.
* The only way to set this option is to set the `WXT_WATCH_DEBOUNCE`
* environment variable, either globally (like in `.bashrc` file) or
* per-project (in `.env` file).
*
* For example:
* ```
* # ~/.zshrc
* export WXT_WATCH_DEBOUNCE=1000
* ```
* or
* ```
* # .env
* WXT_WATCH_DEBOUNCE=1000
* ```
* @default 800
*/
watchDebounce: number;
};
reloadCommand: string | false;
};
@@ -1375,7 +1490,7 @@ export interface WxtModule<TOptions extends WxtModuleOptions> {
* Alternative to adding hooks in setup function with `wxt.hooks`. Hooks are
* added before the `setup` function is called.
*/
hooks?: Partial<WxtHooks>;
hooks?: NestedHooks<WxtHooks>;
/**
* A custom function that can be used to setup hooks and call module-specific
* APIs.
+1 -1
View File
@@ -6,12 +6,12 @@ export default defineConfig({
test: {
mockReset: true,
restoreMocks: true,
setupFiles: ['vitest.setup.ts'],
testTimeout: 120e3,
coverage: {
include: ['src/**'],
exclude: ['**/dist', '**/__tests__', 'src/utils/testing'],
},
globalSetup: ['./vitest.globalSetup.ts'],
},
server: {
watch: {
+19
View File
@@ -0,0 +1,19 @@
import { exists, rm } from 'fs-extra';
let setupHappened = false;
export async function setup() {
if (setupHappened) {
throw new Error('setup called twice');
}
setupHappened = true;
// @ts-expect-error
globalThis.__ENTRYPOINT__ = 'test';
const e2eDistPath = './e2e/dist/';
if (await exists(e2eDistPath)) {
await rm(e2eDistPath, { recursive: true, force: true });
}
}
-2
View File
@@ -1,2 +0,0 @@
// @ts-expect-error
globalThis.__ENTRYPOINT__ = 'test';
+1566 -1274
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -5,7 +5,7 @@ import {
generateMarkDown,
parseChangelogMarkdown,
} from 'changelogen';
import { execa } from 'execa';
import spawn from 'nano-spawn';
import { getPkgTag, grabPackageDetails, listCommitsInDir } from './git';
import { consola } from 'consola';
import fs from 'fs-extra';
@@ -36,7 +36,7 @@ if (currentVersion.startsWith('0.')) {
bumpType = 'patch';
}
}
await execa('pnpm', ['version', bumpType], {
await spawn('pnpm', ['version', bumpType], {
cwd: pkgDir,
});
const updatedPkgJson = await fs.readJson(pkgJsonPath);
@@ -82,11 +82,11 @@ await fs.writeFile(changelogPath, newChangelog, 'utf8');
consola.success('Updated changelog');
// Commit changes
await execa('git', ['add', pkgJsonPath, changelogPath]);
await execa('git', [
await spawn('git', ['add', pkgJsonPath, changelogPath]);
await spawn('git', [
'commit',
'-m',
`chore(release): ${pkgName} v${newVersion}`,
]);
await execa('git', ['tag', newTag]);
await spawn('git', ['tag', newTag]);
consola.success('Committed version and changelog');
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
#
# Generate code in README.md and sync it with wxt/packages/README.md for NPM
#
pnpm dlx automd
echo "<!-- DO NOT EDIT, THIS FILE WAS GENERATED BY '../../scripts/generate-readmes.sh' -->" > packages/wxt/README.md
cat README.md >> packages/wxt/README.md
+8 -1
View File
@@ -1,4 +1,11 @@
// https://github.com/antfu-collective/taze?tab=readme-ov-file#config-file
export default {
exclude: ['typedoc', 'typedoc-plugin-markdown', 'typedoc-vitepress-theme'],
exclude: [
// Very touchy, don't change:
'typedoc',
'typedoc-plugin-markdown',
'typedoc-vitepress-theme',
// Manually manage version so a single version is used:
'esbuild',
],
};
+4 -4
View File
@@ -19,10 +19,10 @@
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@wxt-dev/module-react": "^1.1.0",
"typescript": "^5.5.4",
"wxt": "^0.19.1"
"@wxt-dev/module-react": "^1.1.1",
"typescript": "^5.6.2",
"wxt": "^0.19.11"
}
}
+4 -4
View File
@@ -15,11 +15,11 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"solid-js": "^1.8.19"
"solid-js": "^1.9.1"
},
"devDependencies": {
"@wxt-dev/module-solid": "^1.1.1",
"typescript": "^5.5.4",
"wxt": "^0.19.1"
"@wxt-dev/module-solid": "^1.1.2",
"typescript": "^5.6.2",
"wxt": "^0.19.11"
}
}
+7 -6
View File
@@ -3,6 +3,7 @@
"description": "manifest.json description",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "wxt",
"dev:firefox": "wxt -b firefox",
@@ -15,11 +16,11 @@
},
"devDependencies": {
"@tsconfig/svelte": "^5.0.4",
"@wxt-dev/module-svelte": "^1.0.0",
"svelte": "^4.2.18",
"svelte-check": "^3.8.4",
"tslib": "^2.6.3",
"typescript": "^5.5.4",
"wxt": "^0.19.1"
"@wxt-dev/module-svelte": "^1.0.1",
"svelte": "^4.2.19",
"svelte-check": "^3.8.6",
"tslib": "^2.7.0",
"typescript": "^5.6.2",
"wxt": "^0.19.11"
}
}
+2 -2
View File
@@ -15,7 +15,7 @@
"postinstall": "wxt prepare"
},
"devDependencies": {
"typescript": "^5.5.4",
"wxt": "^0.19.1"
"typescript": "^5.6.2",
"wxt": "^0.19.11"
}
}
+5 -5
View File
@@ -15,12 +15,12 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"vue": "^3.4.34"
"vue": "^3.5.11"
},
"devDependencies": {
"@wxt-dev/module-vue": "^1.0.0",
"typescript": "^5.5.4",
"vue-tsc": "^2.0.29",
"wxt": "^0.19.1"
"@wxt-dev/module-vue": "^1.0.1",
"typescript": "^5.6.2",
"vue-tsc": "^2.1.6",
"wxt": "^0.19.11"
}
}
-3
View File
@@ -11,9 +11,6 @@
"strict": true,
"lib": ["DOM", "WebWorker", "ESNext"],
/* Linting */
"noUnusedLocals": true,
/* Completeness */
"skipLibCheck": true
}