Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d270250633 | |||
| d75f64df79 | |||
| 5faa5d7537 | |||
| 177223c625 | |||
| a64ff22917 | |||
| 0bf08329b0 | |||
| e3f00d2797 | |||
| 09de72895e | |||
| c0aa12089e | |||
| 0248f58785 | |||
| 247cd66f74 | |||
| 4a1bd41df9 | |||
| efcb593718 | |||
| a82c7cbf71 | |||
| db60e79a99 | |||
| 722eff145f | |||
| 282458748d | |||
| 26197f11a3 | |||
| 9d560f9798 | |||
| 786292c95e | |||
| e760ee444e | |||
| 78f91fefb6 | |||
| fda1e18f48 | |||
| c94168e2d1 | |||
| 3eb505dbde | |||
| fbe502fddf | |||
| c143a9978d | |||
| e3555b663d | |||
| df934d252c | |||
| 447d011b8f | |||
| abe1263d27 | |||
| 143b5ac81f | |||
| 87fbb1f4c3 | |||
| 62f11bf605 | |||
| 3ead54b58e | |||
| 580f4f3cfe | |||
| da0accebe8 | |||
| eb65d0b42a | |||
| c2784135b0 | |||
| 61d54bd1f9 | |||
| 0bd94fce3b | |||
| 2a97c06443 | |||
| 8213aa776a | |||
| 026f78254d | |||
| 0ebb013ba6 | |||
| c5f78d0c8d | |||
| 8771676e69 | |||
| aebbbb0685 | |||
| 68183b648e | |||
| 45809c0198 | |||
| 13163c9ba1 | |||
| aedbba3f3d |
@@ -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
|
||||
|
||||
@@ -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/*'
|
||||
@@ -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@v4
|
||||
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
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
@@ -25,6 +25,10 @@ npx wxt@latest init <project-name>
|
||||
npx wxt@latest init <project-name>
|
||||
```
|
||||
|
||||
### Demo
|
||||
|
||||

|
||||
|
||||
:::
|
||||
|
||||
There are several starting templates available.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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`).
|
||||
|
||||
|
||||
@@ -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 → v0.19.0
|
||||
## v0.18.5 → 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 → 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 → v0.18.0
|
||||
|
||||
### Automatic MV3 `host_permissions` to MV2 `permissions`
|
||||
|
||||
@@ -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
|
||||
+3
-8
@@ -17,14 +17,14 @@
|
||||
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.0.11",
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"@aklinker1/buildc": "^1.1.1",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@vitest/coverage-v8": "^2.0.4",
|
||||
"changelogen": "^0.5.5",
|
||||
"consola": "^3.2.3",
|
||||
"dependency-graph": "^1.0.0",
|
||||
"execa": "^9.3.0",
|
||||
"execa": "^9.3.1",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fs-extra": "^11.2.0",
|
||||
"hasha": "^6.0.0",
|
||||
@@ -56,10 +56,5 @@
|
||||
"search-insights"
|
||||
]
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"excludeAuthors": [
|
||||
"aaronklinker1@gmail.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
"wxt": ">=0.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"oxlint": "^0.9.1",
|
||||
"publint": "^0.2.9",
|
||||
"typescript": "^5.5.4",
|
||||
"unbuild": "^2.0.0",
|
||||
@@ -54,6 +55,6 @@
|
||||
"dependencies": {
|
||||
"defu": "^6.1.4",
|
||||
"fs-extra": "^11.2.0",
|
||||
"sharp": "^0.33.4"
|
||||
"sharp": "^0.33.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,9 +38,10 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/chrome": "^0.0.268",
|
||||
"@types/node": "^20.14.2",
|
||||
"oxlint": "^0.9.1",
|
||||
"publint": "^0.2.8",
|
||||
"typescript": "^5.4.5",
|
||||
"unbuild": "^2.0.0",
|
||||
|
||||
@@ -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}`)),
|
||||
)
|
||||
);
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"@vitejs/plugin-react": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"publint": "^0.2.9",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"vite-plugin-solid": "^2.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"publint": "^0.2.9",
|
||||
"solid-js": "^1.8.19",
|
||||
"typescript": "^5.5.4",
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"publint": "^0.2.9",
|
||||
"typescript": "^5.5.4",
|
||||
"unbuild": "^2.0.0",
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"@vitejs/plugin-vue": "^5.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.3.1",
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"publint": "^0.2.9",
|
||||
"typescript": "^5.5.4",
|
||||
"unbuild": "^2.0.0",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
src/core/utils/building/__tests__/test-entrypoints
|
||||
@@ -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@gmail.com>
|
||||
- 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
@@ -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 -->
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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],
|
||||
}),
|
||||
|
||||
@@ -178,4 +178,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);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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,6 +82,7 @@
|
||||
},
|
||||
"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",
|
||||
@@ -94,8 +95,9 @@
|
||||
"consola": "^3.2.3",
|
||||
"defu": "^6.1.4",
|
||||
"dequal": "^2.0.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"esbuild": "^0.23.0",
|
||||
"execa": "^9.3.0",
|
||||
"execa": "^9.3.1",
|
||||
"fast-glob": "^3.3.2",
|
||||
"filesize": "^10.1.4",
|
||||
"fs-extra": "^11.2.0",
|
||||
@@ -114,20 +116,21 @@
|
||||
"nypm": "^0.3.9",
|
||||
"ohash": "^1.1.3",
|
||||
"open": "^10.1.0",
|
||||
"ora": "^8.0.1",
|
||||
"ora": "^8.1.0",
|
||||
"perfect-debounce": "^1.0.0",
|
||||
"picocolors": "^1.0.1",
|
||||
"prompts": "^2.4.2",
|
||||
"publish-browser-extension": "^2.1.3",
|
||||
"unimport": "^3.9.1",
|
||||
"scule": "^1.3.0",
|
||||
"unimport": "^3.11.1",
|
||||
"vite": "^5.3.5",
|
||||
"vite-node": "^2.0.4",
|
||||
"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",
|
||||
@@ -137,6 +140,7 @@
|
||||
"extract-zip": "^2.0.1",
|
||||
"happy-dom": "^14.12.3",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"oxlint": "^0.9.1",
|
||||
"p-map": "^7.0.2",
|
||||
"publint": "^0.2.9",
|
||||
"tsx": "4.15.7",
|
||||
@@ -145,9 +149,6 @@
|
||||
"vitest": "^2.0.4",
|
||||
"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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+8
-14
@@ -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};`),
|
||||
'}',
|
||||
@@ -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,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);
|
||||
}
|
||||
|
||||
+16
-8
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export function safeStringToNumber(str: string | undefined): number | null {
|
||||
const num = Number(str);
|
||||
return isNaN(num) ? null : num;
|
||||
}
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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,9 +28,7 @@ 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.dirname(process.cwd()));
|
||||
const applyTemplate = (template: string): string =>
|
||||
template
|
||||
.replaceAll('{{name}}', projectName)
|
||||
@@ -44,17 +43,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 +72,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 +82,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,
|
||||
|
||||
@@ -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),
|
||||
* });
|
||||
* });
|
||||
*/
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// @ts-expect-error
|
||||
globalThis.__ENTRYPOINT__ = 'test';
|
||||
Generated
+335
-214
File diff suppressed because it is too large
Load Diff
Executable
+9
@@ -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
|
||||
@@ -3,6 +3,7 @@
|
||||
"description": "manifest.json description",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
"dev:firefox": "wxt -b firefox",
|
||||
|
||||
Reference in New Issue
Block a user