Compare commits

...

35 Commits

Author SHA1 Message Date
GitHub Actions a2de6cbe44 chore(release): @wxt-dev/module-vue v1.0.1 2024-08-06 04:23:00 +00:00
GitHub Actions e632a2964a chore(release): @wxt-dev/module-react v1.1.1 2024-08-06 01:55:29 +00:00
GitHub Actions f736a14651 chore(release): @wxt-dev/auto-icons v1.0.2 2024-08-05 23:52:51 +00:00
uncenter a8b0d40959 docs(auto-icons): fix configuration example typo (#905) 2024-08-05 18:48:54 -05:00
Aaron 4c5504d139 Update changelog 2024-08-04 10:18:02 -05:00
GitHub Actions fb86deb388 chore(release): wxt v0.19.4 2024-08-04 14:31:30 +00:00
Aaron 3a8e6135ac fix: Fix createIframeUi page option types 2024-08-04 09:27:46 -05:00
Aaron a38de0c03f feat: Add injectScript helper (#900) 2024-08-04 09:16:46 -05:00
Aaron ea97410fb4 fix(types): PublicPath type resolution with extensionApi: "chrome" (#901) 2024-08-04 08:59:45 -05:00
hikiko4ern e36549ddce fix: do not clear .wxt/tsconfig.json in findEntrypoints if it exists (#898) 2024-08-03 21:59:12 -05:00
KnightYoshi 10851ae01c docs: add missing extension-apis segment (#896) 2024-08-03 19:28:53 -05:00
GitHub Actions d553ef6b29 chore(release): wxt v0.19.3 2024-08-02 21:48:05 +00:00
Aaron 15bf0da82e fix(content-script-context): Fix initialization logic for Firefox (#895) 2024-08-02 16:43:40 -05:00
hikiko4ern fc246ffaac fix: add consola to wxt dependencies (#892) 2024-08-01 19:05:28 -05:00
Florian Metz 9785eff21e chore: add more metadata for npm (#885) 2024-08-01 18:00:00 -05:00
Himanshu Patil 52fbd2c99e docs: Improve prepare:types hook JSDoc (#886) 2024-08-01 17:11:20 -05:00
GitHub Actions 648ae4fb8d chore(release): @wxt-dev/auto-icons v1.0.1 2024-07-30 18:23:12 +00:00
Aaron Klinker bbeeabc9ea Revert version change for auto-icons module 2024-07-30 13:18:35 -05:00
web-dahuyou 35cf6e7bc0 docs: Add "NiceTab" to homepage (#882) 2024-07-30 13:12:00 -05:00
Florian Metz 78822acdac fix(auto-icons): path option (#880)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-07-30 13:06:41 -05:00
不游 aea123890f docs: Add "1Proompt" to homepage (#879) 2024-07-29 07:50:28 -05:00
Matt Strayer c369f4a955 docs: Add "mindful" to homepage (#878) 2024-07-28 16:33:58 -05:00
GitHub Actions 16d8240406 chore(release): wxt v0.19.2 2024-07-28 15:49:52 +00:00
Aaron 24cd01dd31 fix: Don't default to dev mode for production builds when using vite-node loader (#877) 2024-07-28 10:45:32 -05:00
Aaron fb22beadb8 fix: Remove unused top-level functions and variables when loading entrypoints with the vite-node loader (#875) 2024-07-28 09:27:29 -05:00
Aaron Klinker fa85177648 docs(modules): Adjust wording 2024-07-27 17:04:36 -05:00
Aaron Klinker ed07a49dd2 docs: Update README and homepage features with WXT modules 2024-07-27 16:57:33 -05:00
Aaron 09c9de0a60 docs: Fix environment variable reference (#870) 2024-07-27 16:15:28 -05:00
Aaron 523296b29a chore(deps): Upgrade all dependencies (#869) 2024-07-27 16:07:50 -05:00
Aaron Klinker ef67ef47aa chore: Update template package names 2024-07-27 15:37:48 -05:00
Aaron Klinker cb5eb68d62 Fix changelog 2024-07-27 15:11:27 -05:00
GitHub Actions 2e9ab75e2b chore(release): wxt v0.19.1 2024-07-27 20:09:05 +00:00
Aaron 0c2d795800 pref: Tree-shake unrelated code inside vite-node entrypoint loader (#867) 2024-07-27 15:03:23 -05:00
Aaron 97aa173f7e fix: Define web globals when importing entrypoints (#865) 2024-07-27 14:48:55 -05:00
Aaron 9eb64d94d6 docs: Add README for auto-icons module 2024-07-26 14:00:37 -05:00
59 changed files with 6185 additions and 4399 deletions
+1 -1
View File
@@ -59,10 +59,10 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
- 🦾 Auto-imports
- 🤖 Automated publishing
- 🎨 Frontend framework agnostic: works with Vue, React, Svelte, etc
- 📦 Modular architecture with [WXT modules](https://wxt.dev/guide/go-further/reusable-modules.html#overview)
- 🖍️ Quickly bootstrap a new project
- 📏 Bundle analysis
- ⬇️ Download and bundle remote URL imports
- 📦 Modular architecture with [modules](https://wxt.dev/guide/go-further/reusable-modules.html#overview)
## Contributors
@@ -35,6 +35,9 @@ const chromeExtensionIds = [
'bcpgdpedphodjcjlminjbdeejccjbimp', // 汇率转换-中文版本
'loeilaonggnalkaiiaepbegccilkmjjp', // Currency Converter Plus
'npcnninnjghigjfiecefheeibomjpkak', // Respond Easy
'cfkdcideecefncbglkhneoflfnmhoicc', // mindful - stay focused on your goals
'lnhejcpclabmbgpiiomjbhalblnnbffg', // 1Proompt
'fonflmjnjbkigocpoommgmhljdpljain', // NiceTab - https://github.com/web-dahuyou/NiceTab
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
+3 -3
View File
@@ -53,9 +53,9 @@ declare module 'wxt/sandbox' {
}
export default defineAppConfig({
bugReportingDisabled: process.env.VITE_BUG_REPORTING_DISABLED === 'true',
apiKey: process.env.VITE_API_KEY,
bugReportingDisabled: import.meta.env.VITE_BUG_REPORTING_DISABLED === 'true',
apiKey: import.meta.env.VITE_API_KEY,
});
```
> You don't have to do this, you can use `process.env.VITE_*` anywhere in your runtime code, but putting them here consolidates them to one place and defines what variables are expected.
> You don't have to do this, you can use `import.meta.env.VITE_*` anywhere in your runtime code, but putting them here consolidates them to one place and defines what variables are expected.
+4 -4
View File
@@ -25,12 +25,12 @@ There are two ways to add a module to your project:
});
```
2. **NPM package**: By convention, any package on NPM that starts with `@wxt-dev/module-*` or `wxt-module-*` can be installed and included in your project:
2. **NPM package**: Find WXT modules on NPM and include them in your project:
```ts
// wxt.config.ts
export default defineConfig({
// Add the module to your project
modules: ['wxt-module-example'],
modules: ['@wxt-dev/auto-icons'],
});
```
@@ -99,9 +99,9 @@ export default defineConfig({
### Actually Doing Something
The first argument of the setup function, `wxt`, provides full access to the current build's context. You can access the resolved configuration via `wxt.config`, or setup hooks to manipulate the build at different steps of the build process.
The first argument of the setup function, `wxt`, provides full access to the current build's context. You can access the resolved configuration via `wxt.config`, or setup hooks to manipulate the build at different steps of the build process with `wxt.hooks`.
Here's an example that shows you how to update the `outDir` based on the build mode. It does a good job showing how to access config, set up a hook, and do something when the hook is called.
Here's an example that updates the `outDir` based on the build mode. It's a very simple example of how to access config and setup a hook.
```ts
export default defineWxtModule((wxt) => {
+5
View File
@@ -53,6 +53,11 @@ features:
details: Works with any front-end framework with a Vite plugin.
link: /guide/key-concepts/frontend-frameworks
linkText: Add a framework
- icon: 📦
title: Modular Architecture
details: Reuse build-time and runtime-code across multiple extensions.
link: /guide/go-further/reusable-modules
linkText: Read docs
- icon: 🖍️
title: Bootstrap a New Project
details: Get started quickly with several awesome project templates.
+7 -8
View File
@@ -2,10 +2,9 @@
"private": true,
"type": "module",
"engines": {
"node": ">=18.20.3",
"pnpm": "^8.6.3"
"node": ">=18.20.3"
},
"packageManager": "pnpm@8.15.9",
"packageManager": "pnpm@9.6.0",
"scripts": {
"check": "check && pnpm -r --sequential run check",
"test": "pnpm -r --sequential run test run",
@@ -21,7 +20,7 @@
"@aklinker1/buildc": "^1.0.11",
"@aklinker1/check": "^1.3.1",
"@types/fs-extra": "^11.0.4",
"@vitest/coverage-v8": "^2.0.0",
"@vitest/coverage-v8": "^2.0.4",
"changelogen": "^0.5.5",
"consola": "^3.2.3",
"dependency-graph": "^1.0.0",
@@ -37,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.3",
"typescript": "^5.5.4",
"vitepress": "^1.3.1",
"vitest-mock-extended": "^1.3.2",
"vue": "^3.4.33",
"vitest-mock-extended": "^2.0.0",
"vue": "^3.4.34",
"wxt": "workspace:*",
"yaml": "^2.4.5"
"yaml": "^2.5.0"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
+34
View File
@@ -0,0 +1,34 @@
# Changelog
## v1.0.2
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.1...auto-icons-v1.0.2)
### 📖 Documentation
- **auto-icons:** Fix configuration example typo ([#905](https://github.com/wxt-dev/wxt/pull/905))
### 🏡 Chore
- Add more metadata for npm ([#885](https://github.com/wxt-dev/wxt/pull/885))
### ❤️ Contributors
- Uncenter ([@uncenter](http://github.com/uncenter))
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
## v1.0.1
[compare changes](https://github.com/wxt-dev/wxt/compare/auto-icons-v1.0.0...auto-icons-v1.0.1)
### 🩹 Fixes
- **auto-icons:** Path option ([#880](https://github.com/wxt-dev/wxt/pull/880))
### 🏡 Chore
- **deps:** Upgrade all dependencies ([#869](https://github.com/wxt-dev/wxt/pull/869))
### ❤️ Contributors
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
+42
View File
@@ -0,0 +1,42 @@
# WXT Auto Icons
## Features
- Generate extension icons with the correct sizes
- Make the icon greyscale during development
## Usage
Install the package:
```sh
npm i --save-dev @wxt-dev/auto-icons
pnpm i -D @wxt-dev/auto-icons
yarn add --dev @wxt-dev/auto-icons
bun i -D @wxt-dev/auto-icons
```
Add the module to `wxt.config.ts`:
```ts
export default defineConfig({
modules: ['@wxt-dev/auto-icons'],
});
```
And finally, save the base icon to `<srcDir>/assets/icon.png`.
## Configuration
The module can be configured via the `autoIcons` config:
```ts
export default defineConfig({
modules: ['@wxt-dev/auto-icons'],
autoIcons: {
// ...
},
});
```
Options have JSDocs available in your editor, or you can read them in the source code: [`AutoIconsOptions`](./src/index.ts).
+27 -3
View File
@@ -1,6 +1,30 @@
{
"name": "@wxt-dev/auto-icons",
"version": "1.0.0",
"description": "WXT module for automatically generating extension icons in different sizes",
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/auto-icons"
},
"homepage": "https://github.com/wxt-dev/wxt/blob/main/packages/auto-icons/README.md",
"keywords": [
"wxt",
"module",
"icons",
"manifest"
],
"author": {
"name": "Florian Metz",
"email": "me@timeraa.dev"
},
"contributors": [
{
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
}
],
"license": "MIT",
"version": "1.0.2",
"type": "module",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
@@ -15,7 +39,7 @@
],
"scripts": {
"build": "buildc -- unbuild",
"check": "buildc --deps-only -- check"
"check": "pnpm build && check"
},
"peerDependencies": {
"wxt": ">=0.19.0"
@@ -23,7 +47,7 @@
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.9",
"typescript": "^5.5.3",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
},
+16 -10
View File
@@ -9,20 +9,24 @@ export default defineWxtModule<AutoIconsOptions>({
name: '@wxt-dev/auto-icons',
configKey: 'autoIcons',
async setup(wxt, options) {
const parsedOptions = defu(options, {
enabled: true,
baseIconsPath: resolve(wxt.config.srcDir, 'assets/icon.png'),
grayscaleOnDevelopment: true,
sizes: [128, 48, 32, 16],
});
const parsedOptions = defu<Required<AutoIconsOptions>, AutoIconsOptions[]>(
options,
{
enabled: true,
baseIconPath: resolve(wxt.config.srcDir, 'assets/icon.png'),
grayscaleOnDevelopment: true,
sizes: [128, 48, 32, 16],
},
);
const resolvedPath = resolve(wxt.config.srcDir, parsedOptions.baseIconPath);
if (!parsedOptions.enabled)
return wxt.logger.warn(`\`[auto-icons]\` ${this.name} disabled`);
if (!(await exists(parsedOptions.baseIconsPath))) {
const relativePath = relative(process.cwd(), parsedOptions.baseIconsPath);
if (!(await exists(resolvedPath))) {
return wxt.logger.warn(
`\`[auto-icons]\` Skipping icon generation, no base icon found at ${relativePath}`,
`\`[auto-icons]\` Skipping icon generation, no base icon found at ${relative(process.cwd(), resolvedPath)}`,
);
}
@@ -38,7 +42,7 @@ export default defineWxtModule<AutoIconsOptions>({
});
wxt.hooks.hook('build:done', async (wxt, output) => {
const image = sharp(parsedOptions.baseIconsPath).png();
const image = sharp(resolvedPath).png();
if (
wxt.config.mode === 'development' &&
@@ -80,6 +84,8 @@ export interface AutoIconsOptions {
enabled?: boolean;
/**
* Path to the image to use.
*
* Path is relative to the project's src directory.
* @default "<srcDir>/assets/icon.png"
*/
baseIconPath?: string;
+14
View File
@@ -1,5 +1,19 @@
# Changelog
## v1.1.1
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.0...module-react-v1.1.1)
### 🏡 Chore
- **deps:** Bump all non-major dependencies ([#834](https://github.com/wxt-dev/wxt/pull/834))
- **deps:** Upgrade all dependencies ([#869](https://github.com/wxt-dev/wxt/pull/869))
- Add more metadata for npm ([#885](https://github.com/wxt-dev/wxt/pull/885))
### ❤️ Contributors
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
## v1.1.0
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.0.0...module-react-v1.1.0)
-1
View File
@@ -9,7 +9,6 @@ export default defineWxtModule<ReactModuleOptions>({
const { vite } = options ?? {};
addViteConfig(wxt, () => ({
// @ts-expect-error: Vite version conflicts
plugins: [react(vite)],
}));
+20 -3
View File
@@ -1,6 +1,23 @@
{
"name": "@wxt-dev/module-react",
"version": "1.1.0",
"description": "WXT module to enable React support",
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/module-react"
},
"homepage": "https://github.com/wxt-dev/wxt/blob/main/packages/module-react/README.md",
"keywords": [
"wxt",
"module",
"react"
],
"author": {
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
},
"license": "MIT",
"version": "1.1.1",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
@@ -22,7 +39,7 @@
],
"scripts": {
"dev": "wxt",
"check": "buildc --deps-only -- check",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"prepare": "buildc --deps-only -- wxt prepare"
},
@@ -39,7 +56,7 @@
"publint": "^0.2.9",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.5.3",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
-1
View File
@@ -9,7 +9,6 @@ export default defineWxtModule<SolidModuleOptions>({
const { vite } = options ?? {};
addViteConfig(wxt, () => ({
// @ts-expect-error: Vite version mismatch
plugins: [solid(vite)],
build: {
target: 'esnext',
+20 -3
View File
@@ -1,5 +1,22 @@
{
"name": "@wxt-dev/module-solid",
"description": "WXT module to enable SolidJS support",
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/module-solid"
},
"homepage": "https://github.com/wxt-dev/wxt/blob/main/packages/module-solid/README.md",
"keywords": [
"wxt",
"module",
"solidjs"
],
"author": {
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
},
"license": "MIT",
"version": "1.1.1",
"type": "module",
"main": "./dist/index.cjs",
@@ -22,7 +39,7 @@
],
"scripts": {
"dev": "wxt",
"check": "buildc --deps-only -- check",
"check": "pnpm build && check",
"build": "buildc -- unbuild",
"prepare": "buildc --deps-only -- wxt prepare"
},
@@ -35,8 +52,8 @@
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.9",
"solid-js": "^1.8.18",
"typescript": "^5.5.3",
"solid-js": "^1.8.19",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
+19 -2
View File
@@ -1,5 +1,22 @@
{
"name": "@wxt-dev/module-svelte",
"description": "WXT module to enable Svelte support",
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/module-svelte"
},
"homepage": "https://github.com/wxt-dev/wxt/blob/main/packages/module-svelte/README.md",
"keywords": [
"wxt",
"module",
"svelte"
],
"author": {
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
},
"license": "MIT",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.cjs",
@@ -22,7 +39,7 @@
],
"scripts": {
"build": "buildc -- unbuild",
"check": "buildc --deps-only -- check"
"check": "pnpm build && check"
},
"peerDependencies": {
"wxt": ">=0.18.6"
@@ -33,7 +50,7 @@
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.9",
"typescript": "^5.5.3",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
-1
View File
@@ -14,7 +14,6 @@ export default defineWxtModule<SvelteModuleOptions>({
addViteConfig(wxt, () => ({
plugins: [
// @ts-expect-error: Vite version mismatch
svelte({
// Using a svelte.config.js file causes a segmentation fault when importing the file
configFile: false,
+22 -1
View File
@@ -1,5 +1,26 @@
# Changelog
## v1.0.1
[compare changes](https://github.com/wxt-dev/wxt/compare/module-vue-v1.0.0...module-vue-v1.0.1)
### 🩹 Fixes
- Upgrade wxt peer to >= 0.18.6 ([7edf1c8](https://github.com/wxt-dev/wxt/commit/7edf1c8))
### 🏡 Chore
- Add changelog ([21e8ca0](https://github.com/wxt-dev/wxt/commit/21e8ca0))
- Extract build cache script to NPM package ([#737](https://github.com/wxt-dev/wxt/pull/737))
- **deps:** Upgrade non-major deps ([#778](https://github.com/wxt-dev/wxt/pull/778))
- **deps:** Bump all non-major dependencies ([#834](https://github.com/wxt-dev/wxt/pull/834))
- **deps:** Upgrade all dependencies ([#869](https://github.com/wxt-dev/wxt/pull/869))
- Add more metadata for npm ([#885](https://github.com/wxt-dev/wxt/pull/885))
### ❤️ Contributors
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
## v1.0.0
Initial release 🎉
Initial release 🎉
+21 -4
View File
@@ -1,6 +1,23 @@
{
"name": "@wxt-dev/module-vue",
"version": "1.0.0",
"description": "WXT module to enable Vue support",
"repository": {
"type": "git",
"url": "git+https://github.com/wxt-dev/wxt.git",
"directory": "packages/module-vue"
},
"homepage": "https://github.com/wxt-dev/wxt/blob/main/packages/module-vue/README.md",
"keywords": [
"wxt",
"module",
"vue"
],
"author": {
"name": "Aaron Klinker",
"email": "aaronklinker1+wxt@gmail.com"
},
"license": "MIT",
"version": "1.0.1",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
@@ -22,18 +39,18 @@
],
"scripts": {
"build": "buildc -- unbuild",
"check": "buildc --deps-only -- check"
"check": "pnpm build && check"
},
"peerDependencies": {
"wxt": ">=0.18.6"
},
"dependencies": {
"@vitejs/plugin-vue": "^5.0.5"
"@vitejs/plugin-vue": "^5.1.1"
},
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"publint": "^0.2.9",
"typescript": "^5.5.3",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"wxt": "workspace:*"
}
+3 -3
View File
@@ -21,13 +21,13 @@
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/chrome": "^0.0.268",
"@types/chrome": "^0.0.269",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@wxt-dev/auto-icons": "workspace:*",
"sass": "^1.77.8",
"typescript": "^5.5.3",
"vitest": "^2.0.3",
"typescript": "^5.5.4",
"vitest": "^2.0.4",
"vitest-plugin-random-seed": "^1.1.0",
"wxt": "workspace:*"
},
+1 -4
View File
@@ -6,8 +6,5 @@ export default defineProject({
mockReset: true,
restoreMocks: true,
},
plugins: [
// @ts-expect-error: Vite version mismatch
WxtVitest(),
],
plugins: [WxtVitest()],
});
+73
View File
@@ -1,5 +1,78 @@
# Changelog
## v0.19.4
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.3...wxt-v0.19.4)
### 🚀 Enhancements
- Add `injectScript` helper ([#900](https://github.com/wxt-dev/wxt/pull/900))
### 🩹 Fixes
- Do not clear `.wxt/tsconfig.json` in `findEntrypoints` if it exists ([#898](https://github.com/wxt-dev/wxt/pull/898))
- **types:** `PublicPath` type resolution with `extensionApi: "chrome"` ([#901](https://github.com/wxt-dev/wxt/pull/901))
- Fix `createIframeUi` `page` option types ([3a8e613](https://github.com/wxt-dev/wxt/commit/3a8e613))
### ❤️ Contributors
- Hikiko4ern ([@hikiko4ern](http://github.com/hikiko4ern))
## v0.19.3
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.2...wxt-v0.19.3)
### 🩹 Fixes
- Add `consola` to `wxt` dependencies ([#892](https://github.com/wxt-dev/wxt/pull/892))
- **content-script-context:** Fix initialization logic for Firefox ([#895](https://github.com/wxt-dev/wxt/pull/895))
### 📖 Documentation
- Improve `prepare:types` hook JSDoc ([#886](https://github.com/wxt-dev/wxt/pull/886))
### ❤️ Contributors
- Hikiko4ern ([@hikiko4ern](http://github.com/hikiko4ern))
- Himanshu Patil ([@mehimanshupatil](https://github.com/mehimanshupatil))
## v0.19.2
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.1...wxt-v0.19.2)
### 🩹 Fixes
- Remove unused top-level functions and variables when loading entrypoints with the `vite-node` loader ([#875](https://github.com/wxt-dev/wxt/pull/875))
- Don't default to dev mode for production builds when using `vite-node` loader ([#877](https://github.com/wxt-dev/wxt/pull/877))
### 📖 Documentation
- Update README and homepage features with WXT modules ([ed07a49](https://github.com/wxt-dev/wxt/commit/ed07a49))
### 🏡 Chore
- **deps:** Upgrade all dependencies ([#869](https://github.com/wxt-dev/wxt/pull/869))
## v0.19.1
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.0...wxt-v0.19.1)
### 🚀 Enhancements
- Auto Icons Module ([#851](https://github.com/wxt-dev/wxt/pull/851))
### 🔥 Performance
- Tree-shake unrelated code inside `vite-node` entrypoint loader ([#867](https://github.com/wxt-dev/wxt/pull/867))
### 🩹 Fixes
- Define web globals when importing entrypoints ([#865](https://github.com/wxt-dev/wxt/pull/865))
### ❤️ Contributors
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
## v0.19.0
[⚠️ breaking changes](https://wxt.dev/guide/upgrade-guide/wxt) &bull; [compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.18.15...wxt-v0.19.0)
+1
View File
@@ -59,6 +59,7 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
- 🦾 Auto-imports
- 🤖 Automated publishing
- 🎨 Frontend framework agnostic: works with Vue, React, Svelte, etc
- 📦 Modular architecture with [WXT modules](https://wxt.dev/guide/go-further/reusable-modules.html#overview)
- 🖍️ Quickly bootstrap a new project
- 📏 Bundle analysis
- ⬇️ Download and bundle remote URL imports
@@ -19,6 +19,7 @@ exports[`Auto Imports > eslintrc > "enabled: 8" should output a JSON config file
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"injectScript": true,
"storage": true,
"useAppConfig": true
}
@@ -44,6 +45,7 @@ const globals = {
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"injectScript": true,
"storage": true,
"useAppConfig": true
}
@@ -77,6 +79,7 @@ exports[`Auto Imports > eslintrc > "enabled: true" should output a JSON config f
"defineUnlistedScript": true,
"defineWxtPlugin": true,
"fakeBrowser": true,
"injectScript": true,
"storage": true,
"useAppConfig": true
}
@@ -103,6 +106,7 @@ exports[`Auto Imports > eslintrc > should allow customizing the output 1`] = `
"defineUnlistedScript": "readonly",
"defineWxtPlugin": "readonly",
"fakeBrowser": "readonly",
"injectScript": "readonly",
"storage": "readonly",
"useAppConfig": "readonly"
}
@@ -31,6 +31,7 @@ describe('Auto Imports', () => {
const defineUnlistedScript: typeof import('wxt/sandbox')['defineUnlistedScript']
const defineWxtPlugin: typeof import('wxt/sandbox')['defineWxtPlugin']
const fakeBrowser: typeof import('wxt/testing')['fakeBrowser']
const injectScript: typeof import('wxt/client')['injectScript']
const storage: typeof import('wxt/storage')['storage']
const useAppConfig: typeof import('wxt/client')['useAppConfig']
}
@@ -280,7 +280,7 @@ describe('Output Directory Structure', () => {
var _a, _b;
import { l as logHello, i as initPlugins } from "./chunks/_virtual_wxt-plugins-OjKtWpmY.js";
function defineBackground(arg) {
if (typeof arg === "function") return { main: arg };
if (arg == null || typeof arg === "function") return { main: arg };
return arg;
}
const definition = defineBackground({
@@ -366,7 +366,7 @@ describe('Output Directory Structure', () => {
"use strict";
var _a, _b;
function defineBackground(arg) {
if (typeof arg === "function") return { main: arg };
if (arg == null || typeof arg === "function") return { main: arg };
return arg;
}
function logHello(name) {
+2 -2
View File
@@ -3,7 +3,7 @@ import { TestProject } from '../utils';
describe('Remote Code', () => {
it('should download "url:*" modules and include them in the final bundle', async () => {
const url = 'https://code.jquery.com/jquery-3.7.1.slim.min.js';
const url = 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js';
const project = new TestProject();
project.addFile(
'entrypoints/popup.ts',
@@ -16,7 +16,7 @@ describe('Remote Code', () => {
const output = await project.serializeFile('.output/chrome-mv3/popup.js');
expect(output).toContain(
// Some text that will hopefully be in future versions of this script
'jQuery v3.7.1',
'lodash.com',
);
expect(output).not.toContain(url);
expect(
+10 -9
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.19.0",
"version": "0.19.4",
"description": "Next gen framework for developing web extensions",
"repository": {
"type": "git",
@@ -72,7 +72,7 @@
"scripts": {
"wxt": "tsx src/cli/index.ts",
"build": "buildc -- unbuild",
"check": "buildc --deps-only -- run-s -c check:*",
"check": "pnpm build && run-s -c check:*",
"check:default": "check",
"check:tsc-virtual": "tsc --noEmit -p src/virtual",
"test": "buildc --deps-only -- vitest",
@@ -91,6 +91,7 @@
"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",
@@ -116,20 +117,20 @@
"picocolors": "^1.0.1",
"prompts": "^2.4.2",
"publish-browser-extension": "^2.1.3",
"unimport": "^3.9.0",
"vite": "^5.3.4",
"vite-node": "^2.0.0",
"unimport": "^3.9.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",
"@faker-js/faker": "^8.4.1",
"@types/chrome": "^0.0.268",
"@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.11",
"@types/node": "^20.14.12",
"@types/normalize-path": "^3.0.2",
"@types/prompts": "^2.4.9",
"execa": "^9.3.0",
@@ -139,9 +140,9 @@
"p-map": "^7.0.2",
"publint": "^0.2.9",
"tsx": "4.15.7",
"typescript": "^5.5.3",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"vitest": "^2.0.3",
"vitest": "^2.0.4",
"vitest-plugin-random-seed": "^1.1.0"
},
"peerDependencies": {
+2 -7
View File
@@ -1,4 +1,6 @@
/// <reference types="chrome" />
import type { WxtRuntime, WxtI18n } from './index';
/**
* EXPERIMENTAL
*
@@ -7,13 +9,6 @@
* @module wxt/browser/chrome
*/
export interface WxtRuntime {
// Overriden per-project
}
export interface WxtI18n {
// Overriden per-project
}
export type Chrome = typeof chrome;
export type WxtBrowser = Omit<Chrome, 'runtime' | 'i18n'> & {
runtime: WxtRuntime & Omit<Chrome['runtime'], 'getURL'>;
+7 -7
View File
@@ -4,18 +4,18 @@
* @module wxt/browser
*/
import originalBrowser, { Browser, Runtime, I18n } from 'webextension-polyfill';
import originalBrowser, { Browser } from 'webextension-polyfill';
export interface AugmentedBrowser extends Browser {
runtime: WxtRuntime;
i18n: WxtI18n;
}
export type AugmentedBrowser = Omit<Browser, 'runtime' | 'i18n'> & {
runtime: WxtRuntime & Omit<Browser['runtime'], 'getURL'>;
i18n: WxtI18n & Omit<Browser['i18n'], 'getMessage'>;
};
export interface WxtRuntime extends Runtime.Static {
export interface WxtRuntime {
// Overriden per-project
}
export interface WxtI18n extends I18n.Static {
export interface WxtI18n {
// Overriden per-project
}
@@ -0,0 +1,59 @@
/** @vitest-environment happy-dom */
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ContentScriptContext } from '..';
import { fakeBrowser } from '@webext-core/fake-browser';
import { sleep } from '../../../core/utils/time';
describe('Content Script Context', () => {
beforeEach(() => {
vi.useRealTimers();
fakeBrowser.runtime.id = 'anything';
});
it("should recognize when the content script has lost it's connection to the extension API", () => {
const ctx = new ContentScriptContext('test');
const onInvalidated = vi.fn();
ctx.onInvalidated(onInvalidated);
// @ts-expect-error
delete fakeBrowser.runtime.id;
const isValid = ctx.isValid;
expect(onInvalidated).toBeCalled();
expect(isValid).toBe(false);
});
it('should invalidate the current content script when a new context is created', async () => {
const name = 'test';
const onInvalidated = vi.fn();
const ctx = new ContentScriptContext(name);
ctx.onInvalidated(onInvalidated);
// Wait for events to run before next tick next tick
await sleep(0);
// Create a new context after first is initialized, and wait for it to initialize
new ContentScriptContext(name);
await sleep(0);
expect(onInvalidated).toBeCalled();
expect(ctx.isValid).toBe(false);
});
it('should not invalidate the current content script when a new context is created with a different name', async () => {
const onInvalidated = vi.fn();
const ctx = new ContentScriptContext('test1');
ctx.onInvalidated(onInvalidated);
// Wait for events to run before next tick next tick
await sleep(0);
// Create a new context after first is initialized, and wait for it to initialize
new ContentScriptContext('test2');
await sleep(0);
expect(onInvalidated).not.toBeCalled();
expect(ctx.isValid).toBe(true);
});
});
@@ -46,13 +46,13 @@ export class ContentScriptContext implements AbortController {
public readonly options?: Omit<ContentScriptDefinition, 'main'>,
) {
this.#abortController = new AbortController();
if (this.#isTopFrame) {
this.#listenForNewerScripts({ ignoreFirstEvent: true });
this.#stopOldScripts();
}
this.setTimeout(() => {
// Run on next tick so the listener it adds isn't triggered by stopOldScript
} else {
this.#listenForNewerScripts();
});
}
}
get signal() {
@@ -224,12 +224,18 @@ export class ContentScriptContext implements AbortController {
);
}
#listenForNewerScripts() {
#listenForNewerScripts(options?: { ignoreFirstEvent?: boolean }) {
let isFirst = true;
const cb = (event: MessageEvent) => {
if (
event.data?.type === ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE &&
event.data?.contentScriptName === this.contentScriptName
) {
const wasFirst = isFirst;
isFirst = false;
if (wasFirst && options?.ignoreFirstEvent) return;
this.notifyInvalidated();
}
};
@@ -62,6 +62,7 @@ export function createIframeUi<TMounted>(
const wrapper = document.createElement('div');
wrapper.setAttribute('data-wxt-iframe', '');
const iframe = document.createElement('iframe');
// @ts-expect-error: getURL is defined per-project, but not inside the package
iframe.src = browser.runtime.getURL(options.page);
wrapper.appendChild(iframe);
@@ -258,6 +259,7 @@ function mountUi(
* Load the CSS for the current entrypoint.
*/
async function loadCss(): Promise<string> {
// @ts-expect-error: getURL is defined per-project, but not inside the package
const url = browser.runtime.getURL(
`/content-scripts/${import.meta.env.ENTRYPOINT}.css`,
);
@@ -83,7 +83,8 @@ export type IframeContentScriptUiOptions<TMounted> =
* The path to the HTML page that will be shown in the iframe. This string is passed into
* `browser.runtime.getURL`.
*/
page: PublicPath;
// @ts-expect-error: HtmlPublicPath is generated per-project
page: import('wxt/browser').HtmlPublicPath;
/**
* Callback executed when mounting the UI. Use this function to customize the iframe or wrapper
* element's appearance. It is called every time `ui.mount()` is called.
+1
View File
@@ -5,3 +5,4 @@
*/
export * from './content-scripts';
export * from './app-config';
export * from './inject-script';
+47
View File
@@ -0,0 +1,47 @@
import { browser } from 'wxt/browser';
export type ScriptPublicPath = Extract<
// @ts-expect-error: PublicPath is generated per-project
import('wxt/browser').PublicPath,
`${string}.js`
>;
/**
* This function can only be called inside content scripts.
*
* Inject an unlisted script into the page. Scripts are added to the `<head>`
* element or `document.documentElement` if there is no head.
*
* Make sure to add the injected script to your manifest's
* `web_accessible_resources`.
*/
export async function injectScript(
path: ScriptPublicPath,
options?: InjectScriptOptions,
): Promise<void> {
// @ts-expect-error: getURL is defined per-project, but not inside the package
const url = browser.runtime.getURL(path);
const script = document.createElement('script');
if (browser.runtime.getManifest().manifest_version === 2) {
// MV2 requires using an inline script
script.innerHTML = await fetch(url).then((res) => res.text());
} else {
// MV3 requires using src
script.src = url;
}
if (!options?.keepInDom) {
script.onload = () => script.remove();
}
(document.head ?? document.documentElement).append(script);
}
export interface InjectScriptOptions {
/**
* By default, the injected script is removed from the DOM after being
* injected. To disable this behavior, set this flag to true.
*/
keepInDom?: boolean;
}
+1 -4
View File
@@ -230,10 +230,7 @@ export async function createViteBuilder(
baseConfig.optimizeDeps.noDiscovery = true;
baseConfig.optimizeDeps.include = [];
const envConfig: vite.InlineConfig = {
plugins: [
wxtPlugins.extensionApiMock(wxtConfig),
wxtPlugins.removeEntrypointMainFunction(wxtConfig, path),
],
plugins: [wxtPlugins.removeEntrypointMainFunction(wxtConfig, path)],
};
const config = vite.mergeConfig(baseConfig, envConfig);
const server = await vite.createServer(config);
@@ -14,8 +14,17 @@ export function removeEntrypointMainFunction(
const absPath = normalizePath(resolve(config.root, path));
return {
name: 'wxt:remove-entrypoint-main-function',
transform(code, id) {
if (id === absPath) return removeMainFunctionCode(code);
transform: {
order: 'pre',
handler(code, id) {
if (id === absPath) {
const newCode = removeMainFunctionCode(code);
config.logger.debug('vite-node transformed entrypoint', path);
config.logger.debug(`Original:\n---\n${code}\n---`);
config.logger.debug(`Transformed:\n---\n${newCode.code}\n---`);
return newCode;
}
},
},
};
}
@@ -1,34 +1,41 @@
lockfileVersion: '6.0'
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
mime-types:
specifier: 2.1.35
version: 2.1.35
importers:
devDependencies:
flatten:
specifier: 1.0.3
version: 1.0.3
.:
dependencies:
mime-types:
specifier: 2.1.35
version: 2.1.35
devDependencies:
flatten:
specifier: 1.0.3
version: 1.0.3
packages:
/flatten@1.0.3:
flatten@1.0.3:
resolution: {integrity: sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==}
deprecated: flatten is deprecated in favor of utility frameworks such as lodash.
dev: true
/mime-db@1.52.0:
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: false
/mime-types@2.1.35:
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
snapshots:
flatten@1.0.3: {}
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
dev: false
@@ -6,23 +6,28 @@ describe('Transform Utils', () => {
it.each(['defineBackground', 'defineUnlistedScript'])(
'should remove the first arrow function argument for %s',
(def) => {
const input = `export default ${def}(() => {
const input = `
export default ${def}(() => {
console.log();
})`;
const expected = `export default ${def}(() => {})`;
})
`;
const expected = `export default ${def}();`;
const actual = removeMainFunctionCode(input).code;
expect(actual).toEqual(expected);
},
);
it.each(['defineBackground', 'defineUnlistedScript'])(
'should remove the first function argument for %s',
(def) => {
const input = `export default ${def}(function () {
const input = `
export default ${def}(function () {
console.log();
})`;
const expected = `export default ${def}(function () {})`;
})
`;
const expected = `export default ${def}();`;
const actual = removeMainFunctionCode(input).code;
@@ -35,10 +40,12 @@ describe('Transform Utils', () => {
'defineContentScript',
'defineUnlistedScript',
])('should remove the main field from %s', (def) => {
const input = `export default ${def}({
asdf: "asdf",
main: () => {},
})`;
const input = `
export default ${def}({
asdf: "asdf",
main: () => {},
})
`;
const expected = `export default ${def}({
asdf: "asdf"
})`;
@@ -47,5 +54,88 @@ describe('Transform Utils', () => {
expect(actual).toEqual(expected);
});
it('should remove unused imports', () => {
const input = `
import { defineBackground } from "wxt/sandbox"
import { test1 } from "somewhere1"
import test2 from "somewhere2"
export default defineBackground(() => {})
`;
const expected = `import { defineBackground } from "wxt/sandbox"
export default defineBackground();`;
const actual = removeMainFunctionCode(input).code;
expect(actual).toEqual(expected);
});
it('should remove explict side-effect imports', () => {
const input = `
import { defineBackground } from "wxt/sandbox"
import "my-polyfill"
import "./style.css"
export default defineBackground(() => {})
`;
const expected = `import { defineBackground } from "wxt/sandbox"
export default defineBackground();`;
const actual = removeMainFunctionCode(input).code;
expect(actual).toEqual(expected);
});
it("should remove any functions delcared outside the main function that aren't used", () => {
const input = `
function getMatches() {
return ["*://*/*"]
}
function unused1() {}
function unused2() {
unused1();
}
export default defineContentScript({
matches: getMatches(),
main: () => {},
})
`;
const expected = `function getMatches() {
return ["*://*/*"]
}
export default defineContentScript({
matches: getMatches()
})`;
const actual = removeMainFunctionCode(input).code;
expect(actual).toEqual(expected);
});
it("should remove any variables delcared outside the main function that aren't used", () => {
const input = `
const unused1 = "a", matches = ["*://*/*"];
let unused2 = unused1 + "b";
export default defineContentScript({
matches,
main: () => {}
})
`;
const expected = `const matches = ["*://*/*"];
export default defineContentScript({
matches
})`;
const actual = removeMainFunctionCode(input).code;
expect(actual).toEqual(expected);
});
});
});
@@ -28,6 +28,7 @@ import { VIRTUAL_NOOP_BACKGROUND_MODULE_ID } from '../../utils/constants';
import { CSS_EXTENSIONS_PATTERN } from '../../utils/paths';
import pc from 'picocolors';
import { wxt } from '../../wxt';
import { createExtensionEnvironment } from '../environments';
/**
* Return entrypoints and their configuration by looking through the project's files.
@@ -35,7 +36,17 @@ import { wxt } from '../../wxt';
export async function findEntrypoints(): Promise<Entrypoint[]> {
// Make sure required TSConfig file exists to load dependencies
await fs.mkdir(wxt.config.wxtDir, { recursive: true });
await fs.writeJson(resolve(wxt.config.wxtDir, 'tsconfig.json'), {});
try {
await fs.writeJson(
resolve(wxt.config.wxtDir, 'tsconfig.json'),
{},
{ flag: 'wx' },
);
} catch (err) {
if (!(err instanceof Error) || !('code' in err) || err.code !== 'EEXIST') {
throw err;
}
}
const relativePaths = await glob(Object.keys(PATH_GLOB_TO_TYPE_MAP), {
cwd: wxt.config.entrypointsDir,
@@ -72,47 +83,50 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
// Import entrypoints to get their config
let hasBackground = false;
const entrypoints: Entrypoint[] = await Promise.all(
entrypointInfos.map(async (info): Promise<Entrypoint> => {
const { type } = info;
switch (type) {
case 'popup':
return await getPopupEntrypoint(info);
case 'sidepanel':
return await getSidepanelEntrypoint(info);
case 'options':
return await getOptionsEntrypoint(info);
case 'background':
hasBackground = true;
return await getBackgroundEntrypoint(info);
case 'content-script':
return await getContentScriptEntrypoint(info);
case 'unlisted-page':
return await getUnlistedPageEntrypoint(info);
case 'unlisted-script':
return await getUnlistedScriptEntrypoint(info);
case 'content-script-style':
return {
...info,
type,
outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
options: {
include: undefined,
exclude: undefined,
},
};
default:
return {
...info,
type,
outputDir: wxt.config.outDir,
options: {
include: undefined,
exclude: undefined,
},
};
}
}),
const env = createExtensionEnvironment();
const entrypoints: Entrypoint[] = await env.run(() =>
Promise.all(
entrypointInfos.map(async (info): Promise<Entrypoint> => {
const { type } = info;
switch (type) {
case 'popup':
return await getPopupEntrypoint(info);
case 'sidepanel':
return await getSidepanelEntrypoint(info);
case 'options':
return await getOptionsEntrypoint(info);
case 'background':
hasBackground = true;
return await getBackgroundEntrypoint(info);
case 'content-script':
return await getContentScriptEntrypoint(info);
case 'unlisted-page':
return await getUnlistedPageEntrypoint(info);
case 'unlisted-script':
return await getUnlistedScriptEntrypoint(info);
case 'content-script-style':
return {
...info,
type,
outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
options: {
include: undefined,
exclude: undefined,
},
};
default:
return {
...info,
type,
outputDir: wxt.config.outDir,
options: {
include: undefined,
exclude: undefined,
},
};
}
}),
),
);
if (wxt.config.command === 'serve' && !hasBackground) {
@@ -94,9 +94,9 @@ async function getPathsDeclarationEntry(
.join('\n');
const template = `// Generated by wxt
import "${wxt.config.browserModule}";
import "wxt/browser";
declare module "${wxt.config.browserModule}" {
declare module "wxt/browser" {
export type PublicPath =
{{ union }}
type HtmlPublicPath = Extract<PublicPath, \`\${string}.html\`>
@@ -117,9 +117,9 @@ declare module "${wxt.config.browserModule}" {
async function getI18nDeclarationEntry(): Promise<WxtDirFileEntry> {
const defaultLocale = wxt.config.manifest.default_locale;
const template = `// Generated by wxt
import "${wxt.config.browserModule}";
import "wxt/browser";
declare module "${wxt.config.browserModule}" {
declare module "wxt/browser" {
/**
* See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage
*/
@@ -183,8 +183,6 @@ export async function resolveConfig(
userConfigMetadata: userConfigMetadata ?? {},
alias,
extensionApi,
browserModule:
extensionApi === 'chrome' ? 'wxt/browser/chrome' : 'wxt/browser',
entrypointLoader: mergedConfig.entrypointLoader ?? 'vite-node',
experimental: defu(mergedConfig.experimental, {}),
dev: {
@@ -0,0 +1,21 @@
import { parseHTML } from 'linkedom';
import { createEnvironment, Environment, EnvGlobals } from './environment';
export function createBrowserEnvironment(): Environment {
return createEnvironment(getBrowserEnvironmentGlobals);
}
export function getBrowserEnvironmentGlobals(): EnvGlobals {
const { window, document, global } = parseHTML(`
<html>
<head></head>
<body></body>
</html>
`);
return {
...global,
window,
document,
self: global,
};
}
@@ -0,0 +1,49 @@
export interface Environment {
setup: () => () => void;
run: <T>(fn: () => Promise<T>) => Promise<T>;
}
export function createEnvironment(getGlobals: () => EnvGlobals): Environment {
const setup = () => {
const envGlobals = getGlobals();
const ogGlobals = getOgGlobals(envGlobals);
applyGlobals(envGlobals);
return () => {
applyGlobals(ogGlobals);
};
};
const run = async (fn: () => any) => {
const teardown = setup();
try {
return await fn();
} finally {
teardown();
}
};
return {
setup,
run,
};
}
export type EnvGlobals = Record<string, any>;
export function getOgGlobals(envGlobals: EnvGlobals): EnvGlobals {
return Object.keys(envGlobals).reduce<typeof envGlobals>((acc, key) => {
// @ts-expect-error: Untyped key on globalThis
acc[key] = globalThis[key];
return acc;
}, {});
}
export function applyGlobals(globals: EnvGlobals): void {
Object.entries(globals).forEach(([key, envValue]) => {
try {
// @ts-expect-error: Untyped key on globalThis
globalThis[key] = envValue;
} catch (err) {
// ignore any globals that can't be set
}
});
}
@@ -0,0 +1,15 @@
import { fakeBrowser } from '@webext-core/fake-browser';
import { getBrowserEnvironmentGlobals } from './browser-environment';
import { createEnvironment, Environment } from './environment';
export function createExtensionEnvironment(): Environment {
return createEnvironment(getExtensionEnvironmentGlobals);
}
export function getExtensionEnvironmentGlobals() {
return {
...getBrowserEnvironmentGlobals(),
chrome: fakeBrowser,
browser: fakeBrowser,
};
}
@@ -0,0 +1,2 @@
export * from './browser-environment';
export * from './extension-environment';
@@ -297,7 +297,6 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
userConfigMetadata: {},
alias: {},
extensionApi: 'webextension-polyfill',
browserModule: 'wxt/browser',
entrypointLoader: 'vite-node',
experimental: {},
dev: {
+146 -6
View File
@@ -2,8 +2,10 @@ import { ProxifiedModule, parseModule } from 'magicast';
/**
* Removes any code used at runtime related to an entrypoint's main function.
* - Removes or clears out `main` function from returned object
* - TODO: Removes unused imports after main function has been removed to prevent importing runtime modules
* 1. Removes or clears out `main` function from returned object
* 2. Removes any unused functions/variables outside the definition that aren't being called/used
* 3. Removes unused imports
* 3. Removes value-less, side-effect only imports (like `import "./styles.css"` or `import "webextension-polyfill"`)
*/
export function removeMainFunctionCode(code: string): {
code: string;
@@ -11,16 +13,26 @@ export function removeMainFunctionCode(code: string): {
} {
const mod = parseModule(code);
emptyMainFunction(mod);
let removedCount = 0;
let depth = 0;
const maxDepth = 10;
do {
removedCount = 0;
removedCount += removeUnusedTopLevelVariables(mod);
removedCount += removeUnusedTopLevelFunctions(mod);
removedCount += removeUnusedImports(mod);
} while (removedCount > 0 && depth++ <= maxDepth);
removeSideEffectImports(mod);
return mod.generate();
}
function emptyMainFunction(mod: ProxifiedModule) {
function emptyMainFunction(mod: ProxifiedModule): void {
if (mod.exports?.default?.$type === 'function-call') {
if (mod.exports.default.$ast?.arguments?.[0]?.body) {
// Remove body from function
// ex: "fn(() => { ... })" to "fn(() => {})"
// ex: "fn(function () { ... })" to "fn(function () {})"
mod.exports.default.$ast.arguments[0].body.body = [];
// ex: "fn(() => { ... })" to "fn()"
// ex: "fn(function () { ... })" to "fn()"
delete mod.exports.default.$ast.arguments[0];
} else if (mod.exports.default.$ast?.arguments?.[0]?.properties) {
// Remove main field from object
// ex: "fn({ ..., main: () => {} })" to "fn({ ... })"
@@ -31,3 +43,131 @@ function emptyMainFunction(mod: ProxifiedModule) {
}
}
}
function removeUnusedTopLevelVariables(mod: ProxifiedModule): number {
const simpleAst = getSimpleAstJson(mod.$ast);
const usedMap = findUsedIdentifiers(simpleAst);
let deletedCount = 0;
const ast = mod.$ast as any;
for (let i = ast.body.length - 1; i >= 0; i--) {
if (ast.body[i].type === 'VariableDeclaration') {
for (let j = ast.body[i].declarations.length - 1; j >= 0; j--) {
if (!usedMap.get(ast.body[i].declarations[j].id.name)) {
ast.body[i].declarations.splice(j, 1);
deletedCount++;
}
}
if (ast.body[i].declarations.length === 0) {
ast.body.splice(i, 1);
}
}
}
return deletedCount;
}
function removeUnusedTopLevelFunctions(mod: ProxifiedModule): number {
const simpleAst = getSimpleAstJson(mod.$ast);
const usedMap = findUsedIdentifiers(simpleAst);
let deletedCount = 0;
const ast = mod.$ast as any;
for (let i = ast.body.length - 1; i >= 0; i--) {
if (
ast.body[i].type === 'FunctionDeclaration' &&
!usedMap.get(ast.body[i].id.name)
) {
ast.body.splice(i, 1);
deletedCount++;
}
}
return deletedCount;
}
function removeUnusedImports(mod: ProxifiedModule): number {
const simpleAst = getSimpleAstJson(mod.$ast);
const usedMap = findUsedIdentifiers(simpleAst);
const importSymbols = Object.keys(mod.imports);
let deletedCount = 0;
importSymbols.forEach((name) => {
if (usedMap.get(name)) return;
delete mod.imports[name];
deletedCount++;
});
return deletedCount;
}
// TODO: Do a more complex declaration analysis where shadowed variables are detected and ignored.
// Right now, this code assumes there are no shadowed variables.
function findUsedIdentifiers(simpleAst: any) {
const usedMap = new Map<string, boolean>();
const queue: any[] = [simpleAst];
for (const item of queue) {
if (!item) {
} else if (Array.isArray(item)) {
queue.push(...item);
} else if (item.type === 'ImportDeclaration') {
// Don't look inside imports, identifiers are only used for declaration
continue;
} else if (item.type === 'Identifier') {
usedMap.set(item.name, true);
} else if (typeof item === 'object') {
const filterFns: Record<string, (entry: [string, any]) => boolean> = {
// Ignore the function declaration's name
FunctionDeclaration: ([key]) => key !== 'id',
// Ignore object property names
ObjectProperty: ([key]) => key !== 'key',
// Ignore variable declaration's name
VariableDeclarator: ([key]) => key !== 'id',
};
queue.push(
Object.entries(item)
.filter(filterFns[item.type] ?? (() => true))
.map(([_, value]) => value),
);
}
}
return usedMap;
}
function deleteImportAst(
mod: ProxifiedModule,
shouldDelete: (node: any) => boolean,
): void {
const importIndexesToDelete: number[] = [];
(mod.$ast as any).body.forEach((node: any, index: number) => {
if (node.type === 'ImportDeclaration' && shouldDelete(node)) {
importIndexesToDelete.push(index);
}
});
importIndexesToDelete.reverse().forEach((i) => {
delete (mod.$ast as any).body[i];
});
}
function removeSideEffectImports(mod: ProxifiedModule): void {
deleteImportAst(mod, (node) => node.specifiers.length === 0);
}
/**
* Util to get the AST as a simple JSON object, stripping out large objects and file locations to keep it readible
*/
function getSimpleAstJson(ast: any): any {
if (!ast) {
return ast;
} else if (Array.isArray(ast)) {
return ast.map(getSimpleAstJson);
} else if (typeof ast === 'object') {
return Object.fromEntries(
Object.entries(ast)
.filter(
([key, value]) => key !== 'loc' && key !== 'start' && key !== 'end',
)
.map(([key, value]) => [key, getSimpleAstJson(value)]),
);
} else {
return ast;
}
}
+4
View File
@@ -27,6 +27,10 @@ export async function registerWxt(
inlineConfig: InlineConfig = {},
getServer?: (config: ResolvedConfig) => Promise<WxtDevServer>,
): Promise<void> {
// Default NODE_ENV environment variable before other packages, like vite, do it
// See https://github.com/wxt-dev/wxt/issues/873#issuecomment-2254555523
process.env.NODE_ENV ??= command === 'serve' ? 'development' : 'production';
const hooks = createHooks<WxtHooks>();
const config = await resolveConfig(inlineConfig, command);
const server = await getServer?.(config);
@@ -7,6 +7,6 @@ export function defineBackground(
export function defineBackground(
arg: (() => void) | BackgroundDefinition,
): BackgroundDefinition {
if (typeof arg === 'function') return { main: arg };
if (arg == null || typeof arg === 'function') return { main: arg };
return arg;
}
@@ -9,6 +9,6 @@ export function defineUnlistedScript(
export function defineUnlistedScript(
arg: (() => void) | UnlistedScriptDefinition,
): UnlistedScriptDefinition {
if (typeof arg === 'function') return { main: arg };
if (arg == null || typeof arg === 'function') return { main: arg };
return arg;
}
+10 -2
View File
@@ -1046,9 +1046,14 @@ export interface WxtHooks {
* // variable called "example" in the TS project.
* entries.push({
* path: "types/example.d.ts",
* textContent: "declare const a: string;",
* text: "declare const a: string;",
* tsReference: true,
* });
* // use module to add Triple-Slash Directive in .wxt/wxt.d.ts
* // eg: /// <reference types="@types/example" />
* entries.push({
* module: '@types/example'
* });
* })
*/
'prepare:types': (wxt: Wxt, entries: WxtDirEntry[]) => HookResult;
@@ -1210,7 +1215,6 @@ export interface ResolvedConfig {
*/
alias: Record<string, string>;
extensionApi: 'webextension-polyfill' | 'chrome';
browserModule: 'wxt/browser' | 'wxt/browser/chrome';
entrypointLoader: 'vite-node' | 'jiti';
experimental: {};
dev: {
@@ -1409,6 +1413,10 @@ export type WxtDirEntry = WxtDirTypeReferenceEntry | WxtDirFileEntry;
* Represents type reference to a node module to be added to `.wxt/wxt.d.ts` file
*/
export interface WxtDirTypeReferenceEntry {
/**
* Specifies the module name that will be used in the `/// <reference types="..." />` directive.
* This value will be added to the `.wxt/wxt.d.ts` file to include type definitions from the specified module.
*/
module: string;
}
+5239 -4207
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,5 +1,5 @@
{
"name": "wxt-starter",
"name": "wxt-react-starter",
"description": "manifest.json description",
"private": true,
"version": "0.0.0",
@@ -22,7 +22,7 @@
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@wxt-dev/module-react": "^1.1.0",
"typescript": "^5.5.3",
"wxt": "^0.18.15"
"typescript": "^5.5.4",
"wxt": "^0.19.1"
}
}
+4 -4
View File
@@ -1,5 +1,5 @@
{
"name": "wxt-starter",
"name": "wxt-solid-starter",
"description": "manifest.json description",
"private": true,
"version": "0.0.0",
@@ -15,11 +15,11 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"solid-js": "^1.8.18"
"solid-js": "^1.8.19"
},
"devDependencies": {
"@wxt-dev/module-solid": "^1.1.1",
"typescript": "^5.5.3",
"wxt": "^0.18.15"
"typescript": "^5.5.4",
"wxt": "^0.19.1"
}
}
+3 -3
View File
@@ -1,5 +1,5 @@
{
"name": "wxt-starter",
"name": "wxt-svelte-starter",
"description": "manifest.json description",
"private": true,
"version": "0.0.0",
@@ -19,7 +19,7 @@
"svelte": "^4.2.18",
"svelte-check": "^3.8.4",
"tslib": "^2.6.3",
"typescript": "^5.5.3",
"wxt": "^0.18.15"
"typescript": "^5.5.4",
"wxt": "^0.19.1"
}
}
+2 -2
View File
@@ -15,7 +15,7 @@
"postinstall": "wxt prepare"
},
"devDependencies": {
"typescript": "^5.5.3",
"wxt": "^0.18.15"
"typescript": "^5.5.4",
"wxt": "^0.19.1"
}
}
+5 -5
View File
@@ -1,5 +1,5 @@
{
"name": "wxt-starter",
"name": "wxt-vue-starter",
"description": "manifest.json description",
"private": true,
"version": "0.0.0",
@@ -15,12 +15,12 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"vue": "^3.4.33"
"vue": "^3.4.34"
},
"devDependencies": {
"@wxt-dev/module-vue": "^1.0.0",
"typescript": "^5.5.3",
"vue-tsc": "^2.0.26",
"wxt": "^0.18.15"
"typescript": "^5.5.4",
"vue-tsc": "^2.0.29",
"wxt": "^0.19.1"
}
}