Compare commits

...

25 Commits

Author SHA1 Message Date
GitHub Actions 236a135ac2 chore(release): v0.2.4 2023-07-16 19:41:24 +00:00
Aaron dca43efe0d feat: Add wxt zip command (#47) 2023-07-16 14:38:56 -05:00
GitHub Actions eedb136e13 chore(release): v0.2.3 2023-07-16 17:05:39 +00:00
Aaron c71b5e12f2 fix: Some fields cannot be overridden from config.manifest (#46) 2023-07-16 12:03:14 -05:00
Aaron Klinker 495c5c8e4c fix: Read boolean maniest options from meta tags correctly 2023-07-16 11:29:34 -05:00
Aaron Klinker 63c4359d83 Fix changelog 2023-07-16 11:17:33 -05:00
Aaron efc3230674 fix: Correctly lookup open port (#45) 2023-07-16 11:14:50 -05:00
GitHub Actions 69e840b984 chore(release): v0.2.2 2023-07-16 15:47:47 +00:00
Aaron Klinker 2fb5a542d3 fix: Register content scripts correctly 2023-07-16 10:37:42 -05:00
GitHub Actions 84657e035a chore(release): v0.2.1 2023-07-16 15:14:44 +00:00
Aaron Klinker 6f5bf89645 feat: Support all content script options 2023-07-16 10:10:25 -05:00
Aaron Klinker 90fa6bfc40 fix: Remove HMR log
It was causing too many logs during development.
2023-07-15 14:11:22 -05:00
GitHub Actions 992452cdae chore(release): v0.2.0 2023-07-15 18:52:37 +00:00
Aaron Klinker 5c7e041f7f chore: Don't format lockfile 2023-07-15 13:50:05 -05:00
Aaron Klinker 3db5cec2ca feat: Recongize unnamed content scripts (content.ts and content/index.ts) 2023-07-15 13:48:56 -05:00
Aaron Klinker 375a2a6640 docs: Add inline JSDoc for public types 2023-07-14 22:41:46 -05:00
Aaron Klinker 77cbfc1985 docs: Add docs for each type of entrypoint 2023-07-14 22:28:08 -05:00
Aaron Klinker 5b48ae99f4 feat!: Rename defineBackgroundScript to defineBackground
BREAKING CHANGE: Rename `defineBackgroundScript` to `defineBackground`. Update your background entrypoints
2023-07-14 20:03:12 -05:00
Aaron Klinker c1ea9ba4a3 chore: Run wxt prepare on postinstall 2023-07-14 15:50:50 -05:00
Aaron Klinker f28a29e118 docs: Update templates 2023-07-14 15:46:11 -05:00
GitHub Actions 6f5fa9f51c chore(release): v0.1.6 2023-07-14 14:42:45 +00:00
Aaron Klinker 977246f9ed fix: Add logs when a hot reload happens 2023-07-14 09:39:40 -05:00
Aaron 602dd42e5c chore: Svelte template (#34) 2023-07-14 09:20:49 -05:00
Aaron Klinker ea92a2743b fix: Resolve tsconfig paths in vite 2023-07-14 08:35:48 -05:00
Aaron 2daa4d6900 chore: React and Vue starter templates (#33) 2023-07-13 18:50:07 -05:00
114 changed files with 3847 additions and 4010 deletions
+3
View File
@@ -15,3 +15,6 @@ node_modules
TODOs.md
web-ext.config.js
web-ext.config.ts
templates/*/pnpm-lock.yaml
templates/*/yarn.lock
templates/*/package-lock.json
+1
View File
@@ -4,3 +4,4 @@ dist
e2e/project
.wxt
docs/.vitepress/cache
pnpm-lock.yaml
+102
View File
@@ -1,5 +1,107 @@
# Changelog
## v0.2.4
[compare changes](https://github.com/aklinker1/wxt/compare/v0.2.3...v0.2.4)
### 🚀 Enhancements
- Add `wxt zip` command ([#47](https://github.com/aklinker1/wxt/pull/47))
### ❤️ Contributors
- Aaron
## v0.2.3
[compare changes](https://github.com/aklinker1/wxt/compare/v0.2.2...v0.2.3)
### 🩹 Fixes
- Correctly lookup open port ([#45](https://github.com/aklinker1/wxt/pull/45))
- Read boolean maniest options from meta tags correctly ([495c5c8](https://github.com/aklinker1/wxt/commit/495c5c8))
- Some fields cannot be overridden from `config.manifest` ([#46](https://github.com/aklinker1/wxt/pull/46))
### ❤️ Contributors
- Aaron
- Aaron Klinker
## v0.2.2
[compare changes](https://github.com/aklinker1/wxt/compare/v0.2.1...v0.2.2)
### 🩹 Fixes
- Register content scripts correctly in dev mode ([2fb5a54](https://github.com/aklinker1/wxt/commit/2fb5a54))
### ❤️ Contributors
- Aaron Klinker
## v0.2.1
[compare changes](https://github.com/aklinker1/wxt/compare/v0.2.0...v0.2.1)
### 🚀 Enhancements
- Support all content script options ([6f5bf89](https://github.com/aklinker1/wxt/commit/6f5bf89))
### 🩹 Fixes
- Remove HMR log ([90fa6bf](https://github.com/aklinker1/wxt/commit/90fa6bf))
### ❤️ Contributors
- Aaron Klinker
## v0.2.0
[compare changes](https://github.com/aklinker1/wxt/compare/v0.1.6...v0.2.0)
### 🚀 Enhancements
- ⚠️ Rename `defineBackgroundScript` to `defineBackground` ([5b48ae9](https://github.com/aklinker1/wxt/commit/5b48ae9))
- Recongize unnamed content scripts (`content.ts` and `content/index.ts`) ([3db5cec](https://github.com/aklinker1/wxt/commit/3db5cec))
### 📖 Documentation
- Update templates ([f28a29e](https://github.com/aklinker1/wxt/commit/f28a29e))
- Add docs for each type of entrypoint ([77cbfc1](https://github.com/aklinker1/wxt/commit/77cbfc1))
- Add inline JSDoc for public types ([375a2a6](https://github.com/aklinker1/wxt/commit/375a2a6))
### 🏡 Chore
- Run `wxt prepare` on `postinstall` ([c1ea9ba](https://github.com/aklinker1/wxt/commit/c1ea9ba))
- Don't format lockfile ([5c7e041](https://github.com/aklinker1/wxt/commit/5c7e041))
#### ⚠️ Breaking Changes
- ⚠️ Rename `defineBackgroundScript` to `defineBackground` ([5b48ae9](https://github.com/aklinker1/wxt/commit/5b48ae9))
### ❤️ Contributors
- Aaron Klinker
## v0.1.6
[compare changes](https://github.com/aklinker1/wxt/compare/v0.1.5...v0.1.6)
### 🩹 Fixes
- Resolve tsconfig paths in vite ([ea92a27](https://github.com/aklinker1/wxt/commit/ea92a27))
- Add logs when a hot reload happens ([977246f](https://github.com/aklinker1/wxt/commit/977246f))
### 🏡 Chore
- React and Vue starter templates ([#33](https://github.com/aklinker1/wxt/pull/33))
- Svelte template ([#34](https://github.com/aklinker1/wxt/pull/34))
### ❤️ Contributors
- Aaron Klinker
- Aaron
## v0.1.5
[compare changes](https://github.com/aklinker1/wxt/compare/v0.1.4...v0.1.5)
+3 -2
View File
@@ -1,5 +1,5 @@
{
"name": "Demo",
"name": "WXT Demo",
"version": "1.0.0",
"description": "Demo extension for WXT",
"type": "module",
@@ -11,7 +11,8 @@
"build:all:chrome-mv2": "wxt build --mv2",
"build:all:firefox-mv3": "wxt build -b firefox --mv3",
"build:all:firefox-mv2": "wxt build -b firefox",
"prepare": "pnpm -w build && wxt prepare"
"zip": "pnpm -w build && wxt zip",
"postinstall": "pnpm -w build && wxt prepare"
},
"dependencies": {
"webextension-polyfill": "^0.10.0"
+1 -1
View File
@@ -1,4 +1,4 @@
export default defineBackgroundScript(() => {
export default defineBackground(() => {
console.log(browser.runtime.id);
logId();
console.log({
+24
View File
@@ -0,0 +1,24 @@
<script lang="ts" setup>
defineProps<{
name: string;
icon?: string;
}>();
</script>
<template>
<img
:src="`https://raw.githubusercontent.com/PKief/vscode-material-icon-theme/main/icons/${
name?.toLowerCase() ?? icon
}.svg`"
:alt="`${name} Logo`"
/>
</template>
<style scoped>
img {
display: inline;
transform: translateY(5px);
margin-right: 8px;
width: 20px;
}
</style>
+39 -2
View File
@@ -4,18 +4,29 @@ import { defineConfig } from 'vitepress';
export default defineConfig({
title: 'WXT',
description: 'Next gen framework for developing web extensions',
lastUpdated: true,
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
// logo: '/logo.svg',
editLink: {
pattern: 'https://github.com/aklinker1/wxt/edit/main/docs/:path',
},
search: {
provider: 'local',
},
nav: [
{ text: 'Get Started', link: '/get-started/installation.md' },
{ text: 'Guide', link: '/guide.md' },
{ text: 'Guide', link: '/guide/auto-imports.md' },
{ text: 'Config', link: '/config.md' },
{ text: 'API', link: '/api.md' },
],
sidebar: {
'/get-started/': [
{
text: 'Guide',
text: 'Get Started',
items: [
{ text: 'Introduction', link: '/get-started/introduction.md' },
{ text: 'Installation', link: '/get-started/installation.md' },
@@ -28,6 +39,32 @@ export default defineConfig({
],
},
],
'/guide/': [
{
text: 'Guide',
items: [
{ text: 'Auto-imports', link: '/guide/auto-imports.md' },
{ text: 'Manifest.json', link: '/guide/manifest.md' },
{ text: 'Extension APIs', link: '/guide/extension-apis.md' },
],
},
{
text: 'Entrypoints',
items: [
{ text: 'Background', link: '/guide/background.md' },
{ text: 'Bookmarks', link: '/guide/bookmarks.md' },
{ text: 'Content Scripts', link: '/guide/content-scripts.md' },
{ text: 'Devtools', link: '/guide/devtools.md' },
{ text: 'History', link: '/guide/history.md' },
{ text: 'Newtab', link: '/guide/newtab.md' },
{ text: 'Options', link: '/guide/options.md' },
{ text: 'Popup', link: '/guide/popup.md' },
{ text: 'Sandbox', link: '/guide/sandbox.md' },
{ text: 'Unlisted Pages', link: '/guide/unlisted-pages.md' },
{ text: 'Unlisted Scripts', link: '/guide/unlisted-scripts.md' },
],
},
],
},
socialLinks: [{ icon: 'github', link: 'https://github.com/aklinker1/wxt' }],
+9
View File
@@ -0,0 +1,9 @@
import DefaultTheme from 'vitepress/theme';
import Icon from '../components/Icon.vue';
export default {
extends: DefaultTheme,
enhanceApp(ctx) {
ctx.app.component('Icon', Icon);
},
};
+1 -1
View File
@@ -1,4 +1,4 @@
# Guide
# Config
:::warning 🚧&ensp;Under construction
This documentation does not exist yet.
+2 -2
View File
@@ -2,7 +2,7 @@
You can build an extension for any combination of browser and manifest verison. Different browsers and manifest versions support different APIs and entrypoints, so be sure to check that your extension functions as expected for each target.
Separate build targets are to separate output directories:
Separate build targets are written to their own output directories:
```
<root>
@@ -24,7 +24,7 @@ wxt build --browser firefox
```
By default, it will build for `chrome`. When excluding the [`--mv2` or `--mv3` flags](#target-manifest-version), it will default to the commonly accepted version used with that browser.
By default, it will build for `chrome`. When excluding the [`--mv2` or `--mv3` flags](#target-manifest-version), it will default to the commonly accepted manifest version used with that browser.
| Browser | Default Manifest Version |
| ---------------- | :----------------------: |
+34 -4
View File
@@ -44,13 +44,19 @@ export default defineConfig({
## Vite Config
Vite is the bundler used to build each part of an extension. Vite can be configured via the `vite` option.
[Vite](https://vitejs.dev/) is the bundler used to build each entrypoint of your extension. Vite can be configured via the `vite` option.
### Example
See [Vite's documentation](https://vitejs.dev/config/) for configuring the bundler.
A common reason to configure Vite is to add plugins:
## Frontend Frameworks
```ts
Adding a framework like Vue, React, or Svelte is easy!
In the `wxt.config.ts` file, install and add the framework's Vite plugin to the config.
:::code-group
```ts [Vue]
import { defineConfig } from 'wxt';
import vue from '@vitejs/plugin-vue';
@@ -60,3 +66,27 @@ export default defineConfig({
},
});
```
```ts [React]
import { defineConfig } from 'wxt';
import react from '@vitejs/plugin-react';
export default defineConfig({
vite: {
plugins: [react()],
},
});
```
```ts [Svelte]
import { defineConfig } from 'wxt';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
vite: {
plugins: [svelte()],
},
});
```
:::
+32 -9
View File
@@ -4,9 +4,9 @@ Bootstrap a new project or start from scratch.
## Bootstrap Project
:::warning 🚧&ensp;This feature is not implemented yet!
:::warning 🚧&ensp;The `wxt init` command is not implemented yet.
See [From Scratch](#from-scratch) instead.
See [From Scratch](#from-scratch) or reference one of the templates below.
:::
@@ -22,6 +22,17 @@ npx wxt@latest init <project-name>
:::
There are several starting templates available.
| TypeScript |
| ---------------------------------------------------------------------------------------------------- |
| <Icon name="TypeScript" /> [`vanilla`](https://github.com/aklinker1/wxt/tree/main/templates/vanilla) |
| <Icon name="Vue" /> [`vue`](https://github.com/aklinker1/wxt/tree/main/templates/vue) |
| <Icon name="React" /> [`react`](https://github.com/aklinker1/wxt/tree/main/templates/react) |
| <Icon name="Svelte" /> [`svelte`](https://github.com/aklinker1/wxt/tree/main/templates/svelte) |
> All templates are in TypeScript. WXT does not support JS at this time.
## From Scratch
Create a new NPM project:
@@ -64,22 +75,34 @@ yarn add wxt
:::
Finally, add `package.json` scripts:
Add your first entrypoint:
```ts
// entrypoints/background.ts
export default defineBackground(() => {
console.log(`Hello from ${browser.runtime.id}!`);
});
```
Finally, add scripts to your `package.json`:
```json
{
"scripts": {
"dev": "wxt",
"dev:firefox": "wxt --browser firefox",
"build": "wxt build",
"build:firefox": "wxt build --browser firefox"
"dev": "wxt", // [!code ++]
"dev:firefox": "wxt --browser firefox", // [!code ++]
"build": "wxt build", // [!code ++]
"build:firefox": "wxt build --browser firefox", // [!code ++]
"postinstall": "wxt prepare" // [!code ++]
}
}
```
> You can skip `*:firefox` scripts if you don't want to support Firefox
## Development
Once you've installed WXT, you can start the development server using the `dev` command:
Once you've installed WXT, you can start the development server using the `dev` script.
```sh
pnpm dev
@@ -92,7 +115,7 @@ The dev command will build the extension for development, open the browser, and
## Next Steps
Now that your WXT project is setup, you're ready to build a out your web extension!
You're ready to build a out your web extension!
- Learn how to [add entrypoints](./entrypoints.md) like the popup, background, or content scripts
- [Configure WXT](./configuration.md) by creating a `wxt.config.ts` file
+66
View File
@@ -0,0 +1,66 @@
# Auto-imports
WXT uses the same tool as Nuxt for auto-imports, [`unimport`](https://github.com/unjs/unimport).
:::info Testing
To setup your test environment for auto-imports, see [Testing](/get-started/testing.md).
:::
## WXT Auto-imports
Some WXT APIs can be used without importing them:
- [`browser`](/config.md#browser) from `webextension-polyfill`
- [`defineContentScript`](/config.md#defiencontentscript) from `wxt/client`
- [`defineBackground`](/config.md#definebackgroundscript) from `wxt/client`
And more. All [`wxt/client`](/config.md#wxtclient) APIs can be used without imports.
## Project Auto-imports
In addition WXT APIs, default and named exports from inside the following directories can be used without listing them in imports.
- `<srcDir>/components/**/*`
- `<srcDir>/composables/**/*`
- `<srcDir>/hooks/**/*`
- `<srcDir>/utils/**/*`
## TypeScript
For TypeScript to work, you need to run the `wxt prepare` command. This will ensure types are generated for auto-imports.
This should be added to your `postinstall` script:
```json
// package.json
{
"scripts": {
"postinstall": "wxt prepare" // [!code ++]
}
}
```
## Customization
You can override the default auto-import behavior in your `wxt.config.ts` file.
See [`unimport`'s documentation](https://github.com/unjs/unimport#configurations) for a complete list of options.
```ts
import { defineConfig } from 'wxt';
export default defineConfig({
imports: {
// Add auto-imports for vue fuctions like createApp, ref, computed, watch, toRaw, etc...
preset: ['vue'],
},
});
```
To disable auto-imports, set `imports: false`
```ts
export default defineConfig({
imports: false,
});
```
+38
View File
@@ -0,0 +1,38 @@
# Background
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/background/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/background)
For MV2, the background is added as a script to the background page. For MV3, the background becomes a service worker.
## Filenames
`entrypoints/background.ts` is the only recoginzed filename for the background script.
## Definition
:::warning
The main function of the background **_CANNOT BE ASYNC_**. Event listeners must be added syncronously on background startup. If your main function returns a promise, WXT will log an error.
:::
```ts
export default defineBackground(() => {
// Executed when background is loaded
});
```
or
```ts
export default defineBackground({
// Set manifest options
persistent: undefined | true | false,
type: undefined | 'module',
// Executed when background is loaded
main() {
// ...
},
});
```
> All manifest options default to `undefined`.
+26
View File
@@ -0,0 +1,26 @@
# Bookmarks
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
## Filenames
- `entrypoints/bookmarks.html`
- `entrypoints/bookmarks/index.html`
## Definition
Plain old HTML file.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
</head>
<body>
<!-- ... -->
</body>
</html>
```
+60
View File
@@ -0,0 +1,60 @@
# Content Scripts
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/content_scripts/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts)
## Filenames
When a filename matches the pattern below, it is added as a content script in the `manifest.json`.
- `entrypoints/content.tsx?`
- `entrypoints/<name>.content.tsx?`
- `entrypoints/content/index.tsx?`
- `entrypoints/<name>.content/index.tsx?`
## Definition
```ts
export default defineContentScript({
// Set manifest options
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
excludeMatches: undefined | [],
includeGlobs: undefined | [],
excludeGlobs: undefined | [],
allFrames: undefined | [],
runAt: undefined | 'document_start' | 'document_end' | 'document_idle',
matchAboutBlank: undefined | true | false,
matchOriginAsFallback: undefined | true | false,
world: undefined | 'ISOLATED' | 'MAIN',
main() {
// Executed when content script is loaded
},
});
```
> All manifest options default to `undefined`.
## CSS
To include CSS with your content script, import the CSS file at the top of your entrypoint:
```
<srcDir>
└─ entrypoints/
└─ overlay.content/
├─ index.ts
└─ style.css
```
```ts
// entrypoints/overlay.content/index.ts
import './style.css';
export default defineContentScript({
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
main() {
// ...
},
});
```
+26
View File
@@ -0,0 +1,26 @@
# Devtools
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/devtools/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/devtools_page)
## Filenames
- `entrypoints/devtools.html`
- `entrypoints/devtools/index.html`
## Definition
Plain old HTML file.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
</head>
<body>
<!-- ... -->
</body>
</html>
```
+94
View File
@@ -0,0 +1,94 @@
# Extension APIs
WXT is built on top of [`webextension-polyfill`](https://www.npmjs.com/package/webextension-polyfill), which uses the standard `browser` global.
If you're used to Chrome's `chrome` global, its a simple switch:
1. Replace `chrome` with `browser`
2. [Replace callbacks with async/await](https://developer.chrome.com/docs/extensions/mv3/promises/)
And that's it! Your extension now supports Chrome, Firefox, Safari, Edge, and other Chromium browsers.
## Basic Usage
The `browser` variable is available globally via [auto-imports](/guide/auto-imports.md), or it can be imported manually.
```ts
import browser from 'webextension-polyfill';
```
### Example
Let's save the date the extension was installed. Just like `chrome`, some APIs require the permission is added to your manifest before the API is defined. Here, we need to add the `storage` permission to your manifest.
```ts
// wxt.config.ts
import { defineConfig } from 'wxt';
export default defineConfig({
manifest: {
permissions: ['storage'],
},
});
```
Then we can use `browser.storage` to save the install date to local storage.
```ts
// background.ts
export default defineBackground(() => {
browser.runtime.onInstall.addEventListener(({ reason }) => {
if (reason === 'install') {
browser.storage.local.setItem({ installDate: Date.now() });
}
});
});
```
## Messaging
Follow [Chrome's message passing guide](https://developer.chrome.com/docs/extensions/mv3/messaging/) to understand how message passing works in web extensions. In Google's examples, just replace `chrome` with `browser`, and it will work in WXT.
Here's a basic request/response example:
```ts
// background.ts
export default defineBackground(() => {
browser.runtime.onMessage.addEventListener(
(message, sender, sendResponse) => {
console.log(message); // "ping"
// Wait 1 second and respond with "pong"
setTimeout(() => sendResponse('pong'), 1000);
return true;
},
);
});
```
```ts
// popup/main.ts
const res = await browser.runtime.sendMessage('ping');
console.log('res'); // "pong"
```
There are a number of message passing libraries you can use to improve the message passing experience.
Here are some that are compatible with WXT (because they are based off `webextension-polyfill` as well):
- [`@webext-core/messaging`](https://webext-core.aklinker1.io/guide/proxy-service/) - "A light-weight, type-safe wrapper around the `browser.runtime` messaging APIs"
- [`@webext-core/proxy-service`](https://webext-core.aklinker1.io/guide/messaging/) - "Create TRPC-like services that can be called from anywhere but run in the background"
- [`webext-bridge`](https://github.com/zikaari/webext-bridge) - "Messaging in Web Extensions made super easy. Out of the box."
## Browser Differences
Some APIs are only available on certain browsers or manifest versions. You will have to check if an API exists at runtime if it is not in the `browser` standard.
```ts
if ('session' in browser.storage) {
// Do something with the non-standard session storage API
}
```
> If you're using TypeScript, knowing what is non-standard is easy! APIs that are not typed are non-standard.
+26
View File
@@ -0,0 +1,26 @@
# History
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
## Filenames
- `entrypoints/history.html`
- `entrypoints/history/index.html`
## Definition
Plain old HTML file.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
</head>
<body>
<!-- ... -->
</body>
</html>
```
+114
View File
@@ -0,0 +1,114 @@
# Manifest.json
The manifest.json is generated at build-time based on files in your `entrypoints` directory and your `wxt.config.ts`.
## Customization
While entrypoints are generated and added to the manifest at build-time, you can customize or add to your `manifest.json` in the config file.
```ts
import { defineConfig } from 'wxt';
export default defineConfig({
manifest: {
host_permissions: ['*://google.com/*'],
content_security_policy: {
// ...
},
},
});
```
## `name`
If not provided via the `manifest` config, the [manifest's `name`](https://developer.chrome.com/docs/extensions/mv3/manifest/name/) defaults to your package.json's `name` property.
## `version` and `version_name`
The [manifest's `version` and `version_name`](https://developer.chrome.com/docs/extensions/mv3/manifest/version/) fields are based on your package.json's `version` property.
- `version_name` is the exact string listed in your package.json
- `version` is the string cleaned up, with any invalid suffixes removed
### Example
```json
// package.json
{
"version": "1.3.0-alpha2"
}
```
```json
// .output/<dir>/manifest.json
{
"version": "1.3.0",
"version_name": "1.3.0-alpha2"
}
```
### `icons`
The [manifest's `icons`](https://developer.chrome.com/docs/extensions/mv3/manifest/icons/) property needs to be set in the config file. The files should be added to WXT's [`public` directory](/get-started/assets#public-directory).
```
public/
├─ icon-16.png
├─ icon-24.png
├─ icon-48.png
├─ icon-96.png
└─ icon-128.png
```
```ts
export default defineConfig({
manifest: {
icons: {
16: '/icon-16.png',
24: '/icon-24.png',
48: '/icon-48.png',
96: '/icon-96.png',
128: '/icon-128.png',
},
},
});
```
### Permissions
[Permissions](https://developer.chrome.com/docs/extensions/reference/permissions/) must be listed in the manifest config.
```ts
export default defineConfig({
manifest: {
permissions: ['storage', 'tabs'],
},
});
```
### Localization
Similar to the icon, the [`_locales` directory](https://developer.chrome.com/docs/extensions/reference/i18n/) should be placed inside the the WXT's [`public` directory](/get-started/assets#public-directory).
```
public/
└─ _locales/
├─ en/
│ └─ messages.json
├─ es/
│ └─ messages.json
└─ ko/
└─ messages.json
```
Then you'll need to explicitly override the `name` and `description` properties in your config for them to be localized.
```ts
export default defineConfig({
manifest: {
name: '__MSG_extName__',
description: '__MSG_extDescription__',
default_locale: 'en',
},
});
```
+26
View File
@@ -0,0 +1,26 @@
# Newtab
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/override/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides)
## Filenames
- `entrypoints/newtab.html`
- `entrypoints/newtab/index.html`
## Definition
Plain old HTML file.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
</head>
<body>
<!-- ... -->
</body>
</html>
```
+31
View File
@@ -0,0 +1,31 @@
# Options
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/options/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/options_ui)
## Filenames
- `entrypoints/options.html`
- `entrypoints/options/index.html`
## Definition
Plain old HTML file.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Options Title</title>
<meta name="manifest.open_in_tab" content="true|false" />
<meta name="manifest.chrome_style" content="true|false" />
<meta name="manifest.browser_style" content="true|false" />
</head>
<body>
<!-- ... -->
</body>
</html>
```
> All manifest options default to `undefined` when the `meta` tag is not present.
+37
View File
@@ -0,0 +1,37 @@
# Popup
[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/action/) &bull; [Firefox Docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/action)
## Filenames
- `entrypoints/popup.html`
- `entrypoints/popup/index.html`
## Definition
Plain old HTML file.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Default Popup Title</title>
<meta
name="manifest.default_icon"
content="{
16: '/icon-16.png',
24: '/icon-24.png',
...
}"
/>
<meta name="manifest.type" content="page_action|browser_action" />
</head>
<body>
<!-- ... -->
</body>
</html>
```
> All manifest options default to `undefined` when the `meta` tag is not present.
+32
View File
@@ -0,0 +1,32 @@
# Sandbox
[Chrome Docs](https://developer.chrome.com/docs/extensions/mv3/manifest/sandbox/)
:::tip Chromium Only
Firefox does not support sandboxed pages.
:::
## Filenames
- `entrypoints/sandbox.html`
- `entrypoints/<name>.sandbox.html`
- `entrypoints/sandbox/index.html`
- `entrypoints/<name>.sandbox/index.html`
## Definition
Plain old HTML file.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
</head>
<body>
<!-- ... -->
</body>
</html>
```
+32
View File
@@ -0,0 +1,32 @@
# Side Panel
[Chrome Docs](https://developer.chrome.com/docs/extensions/reference/sidePanel/)
:::tip Chromium Only
Firefox does not support sandboxed pages.
:::
## Filenames
- `entrypoints/sidepanel.html`
- `entrypoints/<name>.sidepanel.html`
- `entrypoints/sidepanel/index.html`
- `entrypoints/<name>.sidepanel/index.html`
## Definition
Plain old HTML file.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
</head>
<body>
<!-- ... -->
</body>
</html>
```
+42
View File
@@ -0,0 +1,42 @@
# Unlisted Pages
HTML pages that are built by Vite, but are not included in the manifest.
### Examples
- Onboarding
- Dashboard
- FAQ
- Help
- Changelog
## Filenames
- `entrypoints/<name>.html`
- `entrypoints/<name>/index.html`
Pages are accessible at `'/<name>.html'`:
```ts
const url = browser.runtime.getURL('/<name>.html');
console.log(url); // "chrome-extension://<id>/<name>.html"
```
## Definition
Plain old HTML file.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
</head>
<body>
<!-- ... -->
</body>
</html>
```
+16
View File
@@ -0,0 +1,16 @@
# Unlisted Scripts
TypeScript files that are built, but are not included in the manifest.
## Filenames
- `entrypoints/<name>.tsx?`
- `entrypoints/<name>/index.tsx?`
## Definition
Unlike the background and content scripts, you can define this script's logic in the main scope.
```ts
// Code goes here
```
+2 -2
View File
@@ -6,7 +6,7 @@ describe('Auto Imports', () => {
const project = new TestProject();
project.addFile(
'entrypoints/background.ts',
'export default defineBackgroundScript(() => {})',
'export default defineBackground(() => {})',
);
project.addFile(
'entrypoints/overlay.content.ts',
@@ -43,7 +43,7 @@ describe('Auto Imports', () => {
export {}
declare global {
const browser: typeof import('webextension-polyfill')
const defineBackgroundScript: typeof import('wxt/client')['defineBackgroundScript']
const defineBackground: typeof import('wxt/client')['defineBackground']
const defineConfig: typeof import('wxt')['defineConfig']
const defineContentScript: typeof import('wxt/client')['defineContentScript']
const mountContentScriptUi: typeof import('wxt/client')['mountContentScriptUi']
+7 -7
View File
@@ -57,7 +57,7 @@ describe('Manifest Content', () => {
const optionsContent = `
<html>
<head>
<meta name="manifest.open_in_tab" content="true">
<meta name="manifest.open_in_tab" content="false">
<meta name="manifest.chrome_style" content="true">
<meta name="manifest.browser_style" content="true">
</head>
@@ -72,7 +72,7 @@ describe('Manifest Content', () => {
const manifest = await project.getOutputManifest();
expect(manifest.options_ui).toEqual({
open_in_tab: true,
open_in_tab: false,
chrome_style: true,
page: 'options.html',
});
@@ -88,7 +88,7 @@ describe('Manifest Content', () => {
);
expect(manifest.options_ui).toEqual({
open_in_tab: true,
open_in_tab: false,
browser_style: true,
page: 'options.html',
});
@@ -97,7 +97,7 @@ describe('Manifest Content', () => {
describe('background', () => {
const backgroundContent = `
export default defineBackgroundScript({
export default defineBackground({
persistent: true,
type: "module",
main: () => {},
@@ -149,7 +149,7 @@ describe('Manifest Content', () => {
`import "./style.css";
export default defineContentScript({
matches: ["*://google.com/*"],
run_at: "document_end",
runAt: "document_end",
main: () => {},
})`,
);
@@ -162,7 +162,7 @@ describe('Manifest Content', () => {
`import "./style.css";
export default defineContentScript({
matches: ["*://google.com/*"],
run_at: "document_end",
runAt: "document_end",
main: () => {},
})`,
);
@@ -175,7 +175,7 @@ describe('Manifest Content', () => {
`import "./style.css";
export default defineContentScript({
matches: ["*://duckduckgo.com/*"],
run_at: "document_end",
runAt: "document_end",
main: () => {},
})`,
);
+3 -3
View File
@@ -13,7 +13,7 @@ describe('Output Directory Structure', () => {
expect(await project.serializeOutput()).toMatchInlineSnapshot(`
".output/chrome-mv3/manifest.json
----------------------------------------
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\"}"
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\"}"
`);
});
@@ -66,7 +66,7 @@ describe('Output Directory Structure', () => {
================================================================================
.output/chrome-mv3/manifest.json
----------------------------------------
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"*://*/*\\"],\\"css\\":[\\"assets/one.css\\",\\"assets/two.css\\"],\\"js\\":[\\"content-scripts/one.js\\",\\"content-scripts/two.js\\"]}]}"
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"*://*/*\\"],\\"css\\":[\\"assets/one.css\\",\\"assets/two.css\\"],\\"js\\":[\\"content-scripts/one.js\\",\\"content-scripts/two.js\\"]}]}"
`);
});
@@ -90,7 +90,7 @@ describe('Output Directory Structure', () => {
================================================================================
.output/chrome-mv3/manifest.json
----------------------------------------
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"*://*/*\\"],\\"js\\":[\\"content-scripts/overlay-one.js\\"]}]}"
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"content_scripts\\":[{\\"matches\\":[\\"*://*/*\\"],\\"js\\":[\\"content-scripts/overlay-one.js\\"]}]}"
`);
});
});
+5 -5
View File
@@ -11,7 +11,7 @@ describe('User Config', () => {
});
project.addFile(
'src/entrypoints/background.ts',
`export default defineBackgroundScript(
`export default defineBackground(
() => console.log('Hello background'),
);`,
);
@@ -27,7 +27,7 @@ describe('User Config', () => {
================================================================================
.output/chrome-mv3/manifest.json
----------------------------------------
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"background\\":{\\"service_worker\\":\\"background.js\\"}}"
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"background\\":{\\"service_worker\\":\\"background.js\\"}}"
`);
});
@@ -38,7 +38,7 @@ describe('User Config', () => {
});
project.addFile(
'entries/background.ts',
`export default defineBackgroundScript(() => console.log('Hello background'));`,
`export default defineBackground(() => console.log('Hello background'));`,
);
await project.build();
@@ -52,7 +52,7 @@ describe('User Config', () => {
================================================================================
.output/chrome-mv3/manifest.json
----------------------------------------
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"background\\":{\\"service_worker\\":\\"background.js\\"}}"
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"background\\":{\\"service_worker\\":\\"background.js\\"}}"
`);
});
@@ -80,7 +80,7 @@ describe('User Config', () => {
expect(output).toMatchInlineSnapshot(`
".output/chrome-mv3/manifest.json
----------------------------------------
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"example_customization\\":[\\"production\\",\\"chrome\\",\\"3\\",\\"build\\"]}"
{\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"version_name\\":\\"0.0.0-test\\",\\"example_customization\\":[\\"production\\",\\"chrome\\",\\"3\\",\\"build\\"]}"
`);
});
});
+6 -2
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.1.5",
"version": "0.2.4",
"description": "Next gen framework for developing web extensions",
"engines": {
"node": ">=18.16.0",
@@ -69,15 +69,19 @@
"fast-glob": "^3.2.12",
"filesize": "^10.0.7",
"fs-extra": "^11.1.1",
"get-port": "^7.0.0",
"jiti": "^1.18.2",
"json5": "^2.2.3",
"linkedom": "^0.14.26",
"minimatch": "^9.0.3",
"picocolors": "^1.0.0",
"picomatch": "^2.3.1",
"unimport": "^3.0.8",
"vite": "^4.3.9",
"vite-tsconfig-paths": "^4.2.0",
"web-ext": "^7.6.2",
"webextension-polyfill": "^0.10.0"
"webextension-polyfill": "^0.10.0",
"zip-dir": "^2.0.0"
},
"devDependencies": {
"@faker-js/faker": "^8.0.2",
+1361 -3807
View File
File diff suppressed because it is too large Load Diff
+3 -16
View File
@@ -1,7 +1,6 @@
import tsup from 'tsup';
import glob from 'fast-glob';
import pc from 'picocolors';
import path from 'path';
import { printFileList } from '../src/core/log/printFileList';
import { formatDuration } from '../src/core/utils/formatDuration';
import ora from 'ora';
import fs from 'fs-extra';
@@ -73,18 +72,6 @@ await Promise.all([
spinner.succeed();
const duration = Date.now() - startTime;
const outFiles = await glob(`${outDir}/**`);
outFiles.forEach((file, i) => {
const color = file.endsWith('.map')
? pc.dim
: file.endsWith('.d.ts')
? pc.blue
: pc.cyan;
const prefix = i === outFiles.length - 1 ? ' └─' : ' ├─';
console.log(
`${pc.gray(prefix)} ${pc.dim(outDir + path.sep)}${color(
path.relative(outDir, file),
)}`,
);
});
const outFiles = await glob(`${outDir}/**`, { absolute: true });
await printFileList(consola.log, outDir, outFiles);
consola.success(`Finished in ${formatDuration(duration)}`);
+16
View File
@@ -0,0 +1,16 @@
declare module 'zip-dir' {
// Represents the options object for zipdir function
interface ZipDirOptions {
saveTo?: string;
filter?: (path: string, stat: import('fs').Stats) => boolean;
each?: (path: string) => void;
}
function zipdir(
dirPath: string,
options?: ZipDirOptions,
callback?: (error: Error | null, buffer: Buffer) => void,
): Promise<Buffer>;
export = zipdir;
}
+1
View File
@@ -3,3 +3,4 @@ export * from './dev';
export * from './init';
export * from './prepare';
export * from './publish';
export * from './zip';
+31
View File
@@ -0,0 +1,31 @@
import * as wxt from '../..';
import { buildInternal } from '../../core/build';
import { getInternalConfig } from '../../core/utils/getInternalConfig';
import { zipExtension } from '../../core/zip';
import { defineCommand } from '../utils/defineCommand';
export const zip = defineCommand<
[
root: string | undefined,
flags: {
mode?: string;
config?: string;
browser?: wxt.TargetBrowser;
mv3?: boolean;
mv2?: boolean;
},
]
>(async (root, flags) => {
const mode = flags.mode ?? 'production';
const cliConfig: wxt.InlineConfig = {
root,
mode,
browser: flags.browser,
manifestVersion: flags.mv3 ? 3 : flags.mv2 ? 2 : undefined,
configFile: flags.config,
};
const config = await getInternalConfig(cliConfig, 'build');
const output = await buildInternal(config);
await zipExtension(config, output);
});
+10
View File
@@ -28,6 +28,16 @@ cli
.option('--mv2', 'target manifest v2')
.action(commands.build);
// ZIP
cli
.command('zip [root]', 'build for production and zip output')
.option('-c, --config <file>', 'use specified config file')
.option('-m, --mode <mode>', 'set env mode')
.option('-b, --browser <browser>', 'specify a browser')
.option('--mv3', 'target manifest v3')
.option('--mv2', 'target manifest v2')
.action(commands.zip);
// PREPARE
cli
.command('prepare [root]', 'prepare')
@@ -1,12 +1,10 @@
import { BackgroundScriptDefintition } from '..';
export function defineBackgroundScript(
main: () => void,
): BackgroundScriptDefintition;
export function defineBackgroundScript(
export function defineBackground(main: () => void): BackgroundScriptDefintition;
export function defineBackground(
definition: BackgroundScriptDefintition,
): BackgroundScriptDefintition;
export function defineBackgroundScript(
export function defineBackground(
arg: (() => void) | BackgroundScriptDefintition,
): BackgroundScriptDefintition {
if (typeof arg === 'function') return { main: arg };
+1 -1
View File
@@ -1,3 +1,3 @@
export * from './defineContentScript';
export * from './defineBackgroundScript';
export * from './defineBackground';
export * from './mountContentScriptUi';
@@ -137,6 +137,15 @@ describe('findEntrypoints', () => {
);
it.each<[string, Omit<ContentScriptEntrypoint, 'options'>]>([
[
'content.ts',
{
type: 'content-script',
name: 'content',
inputPath: resolve(config.entrypointsDir, 'content.ts'),
outputDir: resolve(config.outDir, 'content-scripts'),
},
],
[
'overlay.content.ts',
{
@@ -146,6 +155,15 @@ describe('findEntrypoints', () => {
outputDir: resolve(config.outDir, 'content-scripts'),
},
],
[
'content/index.ts',
{
type: 'content-script',
name: 'content',
inputPath: resolve(config.entrypointsDir, 'content/index.ts'),
outputDir: resolve(config.outDir, 'content-scripts'),
},
],
[
'overlay.content/index.ts',
{
+6 -4
View File
@@ -67,7 +67,7 @@ export async function findEntrypoints(
case 'content-script':
entrypoint = await getContentScriptEntrypoint(
config,
relativePath.split('.', 2)[0],
getEntrypointName(config.entrypointsDir, path),
path,
);
break;
@@ -162,21 +162,21 @@ async function getOptionsEntrypoint(
.querySelector("meta[name='manifest.open_in_tab']")
?.getAttribute('content');
if (openInTabContent) {
options.openInTab = Boolean(openInTabContent);
options.openInTab = openInTabContent === 'true';
}
const chromeStyleContent = document
.querySelector("meta[name='manifest.chrome_style']")
?.getAttribute('content');
if (chromeStyleContent) {
options.chromeStyle = Boolean(chromeStyleContent);
options.chromeStyle = chromeStyleContent === 'true';
}
const browserStyleContent = document
.querySelector("meta[name='manifest.browser_style']")
?.getAttribute('content');
if (browserStyleContent) {
options.browserStyle = Boolean(browserStyleContent);
options.browserStyle = browserStyleContent === 'true';
}
return {
@@ -258,6 +258,8 @@ const PATH_GLOB_TO_TYPE_MAP: Record<string, Entrypoint['type'] | 'ignored'> = {
'background.ts': 'background',
'content.ts?(x)': 'content-script',
'content/index.ts?(x)': 'content-script',
'*.content.ts?(x)': 'content-script',
'*.content/index.ts?(x)': 'content-script',
+4 -44
View File
@@ -1,9 +1,6 @@
import path, { relative, resolve } from 'path';
import { resolve } from 'path';
import { BuildOutput, InternalConfig } from '../types';
import { printTable } from './printTable';
import pc from 'picocolors';
import fs from 'fs-extra';
import { filesize } from 'filesize';
import { printFileList } from './printFileList';
export async function printBuildSummary(
output: BuildOutput,
@@ -20,31 +17,8 @@ export async function printBuildSummary(
return l.fileName.localeCompare(r.fileName);
});
let totalSize = 0;
const chunkRows: string[][] = await Promise.all(
chunks.map(async (chunk, i) => {
const file = [
relative(process.cwd(), config.outDir) + path.sep,
chunk.fileName,
];
const prefix = i === chunks.length - 1 ? ' └─' : ' ├─';
const color = getChunkColor(chunk.fileName);
const stats = await fs.lstat(resolve(config.outDir, chunk.fileName));
totalSize += stats.size;
const size = String(filesize(stats.size));
return [
`${pc.gray(prefix)} ${pc.dim(file[0])}${color(file[1])}`,
pc.dim(size),
];
}),
);
printTable(config.logger.log, chunkRows);
config.logger.log(
`${pc.cyan('Σ Total size:')} ${String(filesize(totalSize))}`,
);
const files = chunks.map((chunk) => resolve(config.outDir, chunk.fileName));
await printFileList(config.logger.log, config.outDir, files);
}
const DEFAULT_SORT_WEIGHT = 100;
@@ -62,17 +36,3 @@ function getChunkSortWeight(filename: string) {
)?.[1] ?? DEFAULT_SORT_WEIGHT
);
}
const DEFAULT_COLOR = pc.blue;
const CHUNK_COLORS: Record<string, (text: string) => string> = {
'.js.map': pc.gray,
'.html': pc.green,
'.css': pc.magenta,
'.js': pc.cyan,
};
function getChunkColor(filename: string) {
return (
Object.entries(CHUNK_COLORS).find(([key]) => filename.endsWith(key))?.[1] ??
DEFAULT_COLOR
);
}
+50
View File
@@ -0,0 +1,50 @@
import path from 'node:path';
import pc from 'picocolors';
import fs from 'fs-extra';
import { filesize } from 'filesize';
import { printTable } from './printTable';
export async function printFileList(
log: (message: string) => void,
baseDir: string,
files: string[],
): Promise<void> {
let totalSize = 0;
const fileRows: string[][] = await Promise.all(
files.map(async (file, i) => {
const parts = [
path.relative(process.cwd(), baseDir) + path.sep,
path.relative(baseDir, file),
];
const prefix = i === files.length - 1 ? ' └─' : ' ├─';
const color = getChunkColor(file);
const stats = await fs.lstat(file);
totalSize += stats.size;
const size = String(filesize(stats.size));
return [
`${pc.gray(prefix)} ${pc.dim(parts[0])}${color(parts[1])}`,
pc.dim(size),
];
}),
);
printTable(log, fileRows);
log(`${pc.cyan('Σ Total size:')} ${String(filesize(totalSize))}`);
}
const DEFAULT_COLOR = pc.blue;
const CHUNK_COLORS: Record<string, (text: string) => string> = {
'.js.map': pc.gray,
'.html': pc.green,
'.css': pc.magenta,
'.js': pc.cyan,
'.zip': pc.yellow,
};
function getChunkColor(filename: string) {
return (
Object.entries(CHUNK_COLORS).find(([key]) => filename.endsWith(key))?.[1] ??
DEFAULT_COLOR
);
}
+7 -5
View File
@@ -5,15 +5,15 @@ import {
WxtDevServer,
} from './types';
import * as vite from 'vite';
import { findOpenPort } from './utils/findOpenPort';
import { Manifest } from 'webextension-polyfill';
import { Scripting } from 'webextension-polyfill';
import { getEntrypointBundlePath } from './utils/entrypoints';
import { getContentScriptCssFiles } from './utils/manifest';
import { createWebExtRunner } from './runners/createWebExtRunner';
import { buildInternal } from './build';
export async function getServerInfo(): Promise<ServerInfo> {
const port = await findOpenPort(3000, 3010);
const { default: getPort, portNumbers } = await import('get-port');
const port = await getPort({ port: portNumbers(3000, 3010) });
const hostname = 'localhost';
const origin = `http://${hostname}:${port}`;
const serverConfig: vite.InlineConfig = {
@@ -58,7 +58,9 @@ export async function setupServer(
// paths ending in "/index.html"
viteServer.ws.send('wxt:reload-page', path);
};
const reloadContentScript = (contentScript: Manifest.ContentScript) => {
const reloadContentScript = (
contentScript: Omit<Scripting.RegisteredContentScript, 'id'>,
) => {
viteServer.ws.send('wxt:reload-content-script', contentScript);
};
@@ -102,9 +104,9 @@ export function reloadContentScripts(
const css = getContentScriptCssFiles([entry], server.currentOutput);
server.reloadContentScript({
...entry.options,
js,
css,
...entry.options,
});
});
} else {
+186 -28
View File
@@ -1,28 +1,148 @@
import * as vite from 'vite';
import { Manifest } from 'webextension-polyfill';
import { Manifest, Scripting } from 'webextension-polyfill';
import { UnimportOptions } from 'unimport';
import { EntrypointGroup } from '.';
export interface InlineConfig {
/**
* Project root directory.
*
* @default
* process.cwd()
*/
root?: string;
/**
* Directory containing all source code. Set to `"src"` to move all source code to a `src/`
* directory.
*
* @default
* "<root>"
*/
srcDir?: string;
/**
* Directory containing files that will be copied to the output directory as-is.
*
* @default
* "<srcDir>/publicDir"
*/
publicDir?: string;
/**
* @default
* "<srcDir>/entrypoints"
*/
entrypointsDir?: string;
/**
* Path to `"wxt.config.ts"` file or false to disable config file discovery.
*
* @default
* "wxt.config.ts"
*/
configFile?: string | false;
/**
* ID of the extension for each store. Used for publishing.
*/
storeIds?: {
chrome?: string;
firefox?: string;
edge?: string;
};
/**
* Explicitly set a mode to run in. This will override the default mode for each command, and can
* be overridden by the command line `--mode` option.
*/
mode?: string;
/**
* Customize auto-import options.
*/
imports?: Partial<UnimportOptions>;
/**
* Explicitly set a browser to target. This will override the default browser for each command,
* and can be overridden by the command line `--browser` option.
*
* @default
* "chrome"
*/
browser?: TargetBrowser;
/**
* Explicitly set a manifest version to target. This will override the default manifest version
* for each command, and can be overridden by the command line `--mv2` or `--mv3` option.
*/
manifestVersion?: TargetManifestVersion;
/**
* Override the logger used.
*
* @default
* consola
*/
logger?: Logger;
/**
* Custom Vite options.
*/
vite?: Omit<vite.UserConfig, 'root' | 'configFile' | 'mode'>;
/**
* Customize the `manifest.json` output. Can be an object, promise, or function that returns an
* object or promise.
*/
manifest?: UserManifest | Promise<UserManifest> | UserManifestFn;
/**
* Custom server options.
*/
server?: WxtDevServer;
/**
* Custom runner options. Options set here can be overridden in a `web-ext.config.ts` file.
*/
runner?: ExtensionRunnerConfig;
zip?: {
/**
* Configure the filename output when zipping files.
*
* Available template variables:
*
* - `{{name}}` - The project's name converted to kebab-case
* - `{{version}} - The version_name or version from the manifest
* - `{{browser}} - The target browser from the `--browser` CLI flag
* - `{{manifestVersion}}` - Either "2" or "3"
*
* @default "{{name}}-{{version}}-{{browser}}.zip"
*/
artifactTemplate?: string;
/**
* Configure the filename output when zipping files.
*
* Available template variables:
*
* - `{{name}}` - The project's name converted to kebab-case
* - `{{version}} - The version_name or version from the manifest
* - `{{browser}} - The target browser from the `--browser` CLI flag
* - `{{manifestVersion}}` - Either "2" or "3"
*
* @default "{{name}}-{{version}}-sources.zip"
*/
sourcesTemplate?: string;
/**
* Override the artifactTemplate's `{{name}}` template variable. Defaults to the package.json's
* name, or if that doesn't exist, the current working directories name.
*/
name?: string;
/**
* Root directory to ZIP. The ZIP can be uploaded to the Firefox Addon Store as your source
* code. Defaults to the `config.root` directory.
*/
sourcesRoot?: string;
/**
* [Minimatch](https://www.npmjs.com/package/minimatch) patterns of files to exclude when
* creating a ZIP of all your source code for Firfox. Patterns are relative to your
* `config.zip.sourcesRoot`.
*
* Hidden files, node_modules, and tests are ignored by default.
*
* @example
* [
* "coverage", // Ignore the coverage directory in the `sourcesRoot`
* ]
*/
ignoredSources?: string[];
};
}
export interface WxtInlineViteConfig
@@ -83,7 +203,9 @@ export interface WxtDevServer extends vite.ViteDevServer {
*
* @param contentScript The manifest definition for a content script
*/
reloadContentScript: (contentScript: Manifest.ContentScript) => void;
reloadContentScript: (
contentScript: Omit<Scripting.RegisteredContentScript, 'id'>,
) => void;
}
export type TargetBrowser = 'chrome' | 'firefox' | 'safari' | 'edge' | 'opera';
@@ -186,11 +308,50 @@ export type Entrypoint =
export type OnContentScriptStopped = (cb: () => void) => void;
export interface ContentScriptDefinition {
matches: string[];
runAt?: 'document_start' | 'document_end' | 'document_idle';
matchAboutBlank?: boolean;
matches: Manifest.ContentScript['matches'];
/**
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
* @default "documentIdle"
*/
runAt?: Manifest.ContentScript['run_at'];
/**
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
* @default false
*/
matchAboutBlank?: Manifest.ContentScript['match_about_blank'];
/**
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
* @default []
*/
excludeMatches?: Manifest.ContentScript['exclude_matches'];
/**
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
* @default []
*/
includeGlobs?: Manifest.ContentScript['include_globs'];
/**
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
* @default []
*/
excludeGlobs?: Manifest.ContentScript['exclude_globs'];
/**
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
* @default false
*/
allFrames?: Manifest.ContentScript['all_frames'];
/**
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
* @default false
*/
matchOriginAsFallback?: boolean;
/**
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
* @default "ISOLATED"
*/
world?: 'ISOLATED' | 'MAIN';
/**
* Main function executed when the content script is loaded.
*/
main(): void | Promise<void>;
}
@@ -200,30 +361,27 @@ export interface BackgroundScriptDefintition {
}
/**
* Manifest customization available in the `wxt.config.ts` file. Any missing fields like "name"
* and "version" are managed automatically, and don't need to be listed here.
* Manifest customization available in the `wxt.config.ts` file. You cannot configure entrypoints
* here, they are configured inline.
*/
export type UserManifest = Omit<
Manifest.WebExtensionManifest,
| 'action'
| 'background'
| 'browser_action'
| 'chrome_url_overrides'
| 'content_scripts'
| 'description'
| 'devtools_page'
| 'manifest_version'
| 'name'
| 'options_page'
| 'options_ui'
| 'sandbox'
| 'page_action'
| 'popup'
| 'short_name'
| 'sidepanel'
| 'sidebar_action'
| 'version'
| 'version_name'
export type UserManifest = Partial<
Omit<
Manifest.WebExtensionManifest,
| 'action'
| 'background'
| 'browser_action'
| 'chrome_url_overrides'
| 'content_scripts'
| 'devtools_page'
| 'manifest_version'
| 'options_page'
| 'options_ui'
| 'sandbox'
| 'page_action'
| 'popup'
| 'sidepanel'
| 'sidebar_action'
>
>;
export type UserManifestFn = (
+7
View File
@@ -36,6 +36,13 @@ export interface InternalConfig {
fsCache: FsCache;
server?: WxtDevServer;
runnerConfig: ResolvedConfig<ExtensionRunnerConfig>;
zip: {
name?: string;
artifactTemplate: string;
sourcesTemplate: string;
ignoredSources: string[];
sourcesRoot: string;
};
}
export type EntrypointGroup = Entrypoint | Entrypoint[];
@@ -0,0 +1,28 @@
import { describe, expect, it } from 'vitest';
import { hashContentScriptOptions } from '../content-scripts';
describe('Content Script Utils', () => {
describe('hashContentScriptOptions', () => {
it('should return a string containing all the options with defaults applied', () => {
const hash = hashContentScriptOptions({ matches: [] });
expect(hash).toMatchInlineSnapshot(
'"[[\\"allFrames\\",false],[\\"excludeGlobs\\",[]],[\\"excludeMatches\\",[]],[\\"includeGlobs\\",[]],[\\"matchAboutBlank\\",false],[\\"matches\\",[]],[\\"matchOriginAsFallback\\",false],[\\"runAt\\",\\"document_idle\\"],[\\"world\\",\\"ISOLATED\\"]]"',
);
});
it('should be consistent regardless of the object ordering and default values', () => {
const hash1 = hashContentScriptOptions({
allFrames: true,
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
matchAboutBlank: false,
});
const hash2 = hashContentScriptOptions({
matches: ['*://duckduckgo.com/*', '*://google.com/*'],
allFrames: true,
});
expect(hash1).toBe(hash2);
});
});
});
+17
View File
@@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest';
import { kebabCaseAlphanumeric } from '../strings';
describe('String utils', () => {
describe('kebabCaseAlphanumeric', () => {
it.each([
['HELLO', 'hello'],
['Hello, World!', 'hello-world'],
['hello123', 'hello123'],
['Hello World This Is A Test', 'hello-world-this-is-a-test'],
['Hello World', 'hello-world'],
['hello-world', 'hello-world'], // Ensure hyphens are preserved
])('should convert "%s" to "%s"', (input, expected) => {
expect(kebabCaseAlphanumeric(input)).toBe(expected);
});
});
});
+52
View File
@@ -0,0 +1,52 @@
import { Manifest } from 'webextension-polyfill';
import { ContentScriptEntrypoint } from '../types';
/**
* Returns a unique and consistent string hash based on a content scripts options.
*
* It is able to recognize default values,
*/
export function hashContentScriptOptions(
options: ContentScriptEntrypoint['options'],
): string {
const withDefaults: ContentScriptEntrypoint['options'] = {
excludeGlobs: [],
excludeMatches: [],
includeGlobs: [],
matchAboutBlank: false,
matchOriginAsFallback: false,
runAt: 'document_idle',
allFrames: false,
world: 'ISOLATED',
// TODO: strip undefined fields from options object to improve content script grouping.
...options,
};
return JSON.stringify(
Object.entries(withDefaults)
// Sort any arrays so their values are consistent
.map<[string, unknown]>(([key, value]) => {
if (Array.isArray(value)) return [key, value.sort()];
else return [key, value];
})
// Sort all the fields alphabetically
.sort((l, r) => l[0].localeCompare(r[0])),
);
}
export function mapWxtOptionsToContentScript(
options: ContentScriptEntrypoint['options'],
): Omit<Manifest.ContentScript, 'js' | 'css'> {
return {
matches: options.matches,
all_frames: options.allFrames,
match_about_blank: options.matchAboutBlank,
exclude_globs: options.excludeGlobs,
exclude_matches: options.excludeMatches,
include_globs: options.includeGlobs,
run_at: options.runAt,
// @ts-expect-error: untyped chrome options
match_origin_as_fallback: options.matchOriginAsFallback,
world: options.world,
};
}
-33
View File
@@ -1,33 +0,0 @@
import net from 'node:net';
/**
* Finds the first open port in a range of ports.
*/
export function findOpenPort(
startPort: number,
endPort: number,
): Promise<number> {
return findOpenPortRecursive(startPort, startPort, endPort);
}
function findOpenPortRecursive(
port: number,
startPort: number,
endPort: number,
): Promise<number> {
return new Promise((resolve, reject) => {
if (port > endPort)
return reject(
Error(`Could not find open port between ${startPort}-${endPort}`),
);
const server = net.createServer();
server.listen(port, () => {
server.once('close', () => resolve(port));
server.close();
});
server.on('error', () =>
resolve(findOpenPortRecursive(port + 1, startPort, endPort)),
);
});
}
+22
View File
@@ -99,6 +99,26 @@ export async function getInternalConfig(
typesDir,
fsCache: createFsCache(wxtDir),
manifest,
zip: {
sourcesTemplate: '{{name}}-{{version}}-sources.zip',
artifactTemplate: '{{name}}-{{version}}-{{browser}}.zip',
sourcesRoot: root,
...userConfig.zip,
...config.zip,
ignoredSources: [
'**/node_modules',
// WXT files
'**/web-ext.config.ts',
// Hidden files
'**/.*',
// Tests
'**/__tests__/**',
'**/*.+(test|spec).?(c|m)+(j|t)s?(x)',
// User config
...(userConfig.zip?.ignoredSources ?? []),
...(config.zip?.ignoredSources ?? []),
],
},
};
// Customize the default vite config
@@ -121,6 +141,7 @@ export async function getInternalConfig(
plugins.virtualEntrypoin('content-script', finalConfig),
);
finalConfig.vite.plugins.push(plugins.devServerGlobals(finalConfig));
finalConfig.vite.plugins.push(plugins.tsconfigPaths(finalConfig));
finalConfig.vite.define ??= {};
getGlobals(finalConfig).forEach((global) => {
@@ -143,6 +164,7 @@ type InternalConfigNoUserDirs = Omit<
| 'typesDir'
| 'fsCache'
| 'manifest'
| 'zip'
>;
async function resolveManifestConfig(
+34 -25
View File
@@ -12,6 +12,11 @@ import fs from 'fs-extra';
import { resolve } from 'path';
import { getEntrypointBundlePath } from './entrypoints';
import { ContentSecurityPolicy } from './ContentSecurityPolicy';
import {
hashContentScriptOptions,
mapWxtOptionsToContentScript,
} from './content-scripts';
import { getPackageJson } from './package';
/**
* Writes the manifest to the output directory and the build output.
@@ -47,36 +52,41 @@ export async function generateMainfest(
config: InternalConfig,
): Promise<Manifest.WebExtensionManifest> {
const pkg = await getPackageJson(config);
if (pkg.version == null)
throw Error('package.json does not include a version');
if (pkg.name == null) throw Error('package.json does not include a name');
if (pkg.description == null)
throw Error('package.json does not include a description');
const manifest: Manifest.WebExtensionManifest = {
manifest_version: config.manifestVersion,
name: pkg.name,
short_name: pkg.shortName,
version: simplifyVersion(pkg.version),
version_name: config.browser === 'firefox' ? undefined : pkg.version,
...config.manifest,
};
const manifest: Manifest.WebExtensionManifest = Object.assign(
{
manifest_version: config.manifestVersion,
name: pkg?.name,
description: pkg?.description,
version: pkg?.version && simplifyVersion(pkg.version),
// Only add the version name to chromium and if the user hasn't specified a custom version.
version_name:
config.browser !== 'firefox' && !config.manifest.version
? pkg?.version
: undefined,
short_name: pkg?.shortName,
},
config.manifest,
);
addEntrypoints(manifest, entrypoints, buildOutput, config);
if (config.command === 'serve') addDevModeCsp(manifest, config);
if (config.command === 'serve') addDevModePermissions(manifest, config);
return manifest;
}
// TODO: transform manifest here.
/**
* Read the package.json from the current directory.
*
* TODO: look in root and up directories until it's found
*/
async function getPackageJson(config: InternalConfig): Promise<any> {
return await fs.readJson(resolve(config.root, 'package.json'));
if (manifest.name == null)
throw Error(
"Manifest 'name' is missing. Either:\n1. Set the name in your <root>/package.json\n2. Set a name via the manifest option in your wxt.config.ts",
);
if (manifest.version == null) {
throw Error(
"Manifest 'version' is missing. Either:\n1. Add a version in your <root>/package.json\n2. Pass the version via the manifest option in your wxt.config.ts",
);
}
return manifest;
}
/**
@@ -85,7 +95,6 @@ async function getPackageJson(config: InternalConfig): Promise<any> {
*/
function simplifyVersion(versionName: string): string {
// Regex adapted from here: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/version#version_format
const version = /^((0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}).*$/.exec(
versionName,
)?.[1];
@@ -285,7 +294,7 @@ function addEntrypoints(
);
} else {
const hashToEntrypointsMap = contentScripts.reduce((map, script) => {
const hash = JSON.stringify(script.options);
const hash = hashContentScriptOptions(script.options);
if (map.has(hash)) map.get(hash)?.push(script);
else map.set(hash, [script]);
return map;
@@ -293,7 +302,7 @@ function addEntrypoints(
manifest.content_scripts = Array.from(hashToEntrypointsMap.entries()).map(
([, scripts]) => ({
...scripts[0].options,
...mapWxtOptionsToContentScript(scripts[0].options),
// TOOD: Sorting css and js arrays here so we get consistent test results... but we
// shouldn't have to. Where is the inconsistency coming from?
css: getContentScriptCssFiles(scripts, buildOutput)?.sort(),
+22
View File
@@ -0,0 +1,22 @@
import { resolve } from 'node:path';
import { InternalConfig } from '../types';
import fs from 'fs-extra';
/**
* Read the project's package.json.
*
* TODO: look in root and up directories until it's found
*/
export async function getPackageJson(
config: InternalConfig,
): Promise<Partial<Record<string, any>> | undefined> {
const file = resolve(config.root, 'package.json');
try {
return await fs.readJson(file);
} catch (err) {
config.logger.debug(
`Failed to read package.json at: ${file}. Returning undefined.`,
);
return {};
}
}
+6
View File
@@ -0,0 +1,6 @@
export function kebabCaseAlphanumeric(str: string): string {
return str
.toLowerCase()
.replace(/[^a-z0-9-\s]/g, '') // Remove all non-alphanumeric, non-hyphen characters
.replace(/\s+/g, '-'); // Replace spaces with hyphens
}
+1
View File
@@ -4,3 +4,4 @@ export * from './download';
export * from './multipageMove';
export * from './unimport';
export * from './virtualEntrypoint';
export * from './tsconfigPaths';
+10
View File
@@ -0,0 +1,10 @@
import { InternalConfig } from '../types';
import paths from 'vite-tsconfig-paths';
export function tsconfigPaths(config: InternalConfig) {
const fn: typeof paths =
typeof paths === 'function' ? paths : (paths as any).default;
return fn({
root: config.root,
});
}
+75
View File
@@ -0,0 +1,75 @@
import { BuildOutput, InternalConfig } from './types';
import zipdir from 'zip-dir';
import { dirname, relative, resolve } from 'node:path';
import fs from 'fs-extra';
import { kebabCaseAlphanumeric } from './utils/strings';
import { getPackageJson } from './utils/package';
import { minimatch } from 'minimatch';
import { formatDuration } from './utils/formatDuration';
import { printFileList } from './log/printFileList';
/**
* Zip the extension for distribution. Does not build, just zips the output that should exist for
* the given config.
*
* Returns a list of ZIP files that were created.
*/
export async function zipExtension(
config: InternalConfig,
buildOutput: BuildOutput,
): Promise<string[]> {
const start = Date.now();
config.logger.info('Zipping extension...');
const zipFiles: string[] = [];
const projectName =
config.zip.name ??
kebabCaseAlphanumeric(
(await getPackageJson(config))?.name || dirname(process.cwd()),
);
const applyTemplate = (template: string): string =>
template
.replaceAll('{{name}}', projectName)
.replaceAll('{{browser}}', config.browser)
.replaceAll(
'{{version}}',
buildOutput.manifest.version_name ?? buildOutput.manifest.version,
)
.replaceAll('{{manifestVersion}}', `mv${config.manifestVersion}`);
await fs.ensureDir(config.outBaseDir);
// ZIP output directory
const outZipFilename = applyTemplate(config.zip.artifactTemplate);
const outZipPath = resolve(config.outBaseDir, outZipFilename);
await zipdir(config.outDir, {
saveTo: outZipPath,
});
zipFiles.push(outZipPath);
// ZIP sources for Firefox
if (config.browser === 'firefox') {
const sourcesZipFilename = applyTemplate(config.zip.sourcesTemplate);
const sourcesZipPath = resolve(config.outBaseDir, sourcesZipFilename);
await zipdir(config.zip.sourcesRoot, {
saveTo: sourcesZipPath,
filter(path) {
const relativePath = relative(config.zip.sourcesRoot, path);
const matchedPattern = config.zip.ignoredSources.find((pattern) =>
minimatch(relativePath, pattern),
);
return matchedPattern == null;
},
});
zipFiles.push(sourcesZipPath);
}
config.logger.success(
`Zipped extension in ${formatDuration(Date.now() - start)}`,
);
await printFileList(config.logger.log, config.outBaseDir, zipFiles);
return zipFiles;
}
+1 -1
View File
@@ -64,7 +64,7 @@ export async function createServer(
if (changes.type === 'no-change') return;
// Log the entrypoints that were effected
consola.info(
internalConfig.logger.info(
`Changed: ${Array.from(new Set(fileChanges.map((change) => change[1])))
.map((file) => pc.dim(relative(internalConfig.root, file)))
.join(', ')}`,
+7
View File
@@ -218,4 +218,11 @@ export const fakeInternalConfig = fakeObjectCreator<InternalConfig>(() => ({
vite: {},
wxtDir: fakeDir(),
server: mock<WxtDevServer>(),
zip: {
artifactTemplate: '{{name}}-{{version}}.zip',
ignoredSources: [],
sourcesRoot: fakeDir(),
sourcesTemplate: '{{name}}-sources.zip',
name: faker.person.firstName().toLowerCase(),
},
}));
+5
View File
@@ -0,0 +1,5 @@
# WXT Templates
To add or change a template, open the template's folder (`template/<name>`) in your editor directly. Then, it's a normal NPM project.
Note that you should use `npm` instead of `pnpm` in the template directories so that it doesn't complict with the repo's PNPM workspace. Templates are standalone and should not depend on the local state of `wxt`.
+7
View File
@@ -0,0 +1,7 @@
# WXT + Vue 3
This template should help get you started developing with Vue 3 in WXT.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
+23
View File
@@ -0,0 +1,23 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.output
artifacts
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,3 @@
export default defineBackground(() => {
console.log('Hello background!', { id: browser.runtime.id });
});
+42
View File
@@ -0,0 +1,42 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}
+35
View File
@@ -0,0 +1,35 @@
import { useState } from 'react';
import reactLogo from '../../assets/react.svg';
import wxtLogo from '/wxt.svg';
import './App.css';
function App() {
const [count, setCount] = useState(0);
return (
<>
<div>
<a href="https://wxt.dev" target="_blank">
<img src={wxtLogo} className="logo" alt="WXT logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>WXT + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the WXT and React logos to learn more
</p>
</>
);
}
export default App;
@@ -0,0 +1,69 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Default Popup Title</title>
<meta name="manifest.type" content="browser_action" />
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
+26
View File
@@ -0,0 +1,26 @@
{
"name": "wxt-starter",
"description": "manifest.json description",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "wxt",
"dev:firefox": "wxt -b firefox",
"build": "wxt build",
"build:firefox": "wxt build -b firefox",
"compile": "tsc --noEmit",
"postinstall": "wxt prepare"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@vitejs/plugin-react": "^4.0.3",
"typescript": "^5.1.6",
"wxt": "^0.1.3"
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+7
View File
@@ -0,0 +1,7 @@
{
"extends": "./.wxt/tsconfig.json",
"compilerOptions": {
"allowImportingTsExtensions": true,
"jsx": "react-jsx"
}
}
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from 'wxt';
import react from '@vitejs/plugin-react';
// See https://wxt.dev/config.html
export default defineConfig({
vite: {
plugins: [react()],
},
});
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["svelte.svelte-vscode"]
}
+7
View File
@@ -0,0 +1,7 @@
# Svelte + Vite
This template should help get you started developing with Svelte in WXT.
## Recommended IDE Setup
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
+23
View File
@@ -0,0 +1,23 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.output
artifacts
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+23
View File
@@ -0,0 +1,23 @@
{
"name": "wxt-starter",
"description": "manifest.json description",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "wxt",
"dev:firefox": "wxt -b firefox",
"build": "wxt build",
"build:firefox": "wxt build -b firefox",
"check": "svelte-check --tsconfig ./tsconfig.json",
"postinstall": "wxt prepare"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.2",
"@tsconfig/svelte": "^5.0.0",
"svelte": "^4.0.5",
"svelte-check": "^3.4.6",
"tslib": "^2.6.0",
"typescript": "^5.1.6",
"wxt": "^0.1.5"
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -0,0 +1,3 @@
export default defineBackground(() => {
console.log('Hello background!', { id: browser.runtime.id });
});
@@ -0,0 +1,42 @@
<script lang="ts">
import svelteLogo from '../../assets/svelte.svg'
import Counter from '../../lib/Counter.svelte'
</script>
<main>
<div>
<a href="https://wxt.dev" target="_blank" rel="noreferrer">
<img src="/wxt.svg" class="logo" alt="WXT Logo" />
</a>
<a href="https://svelte.dev" target="_blank" rel="noreferrer">
<img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
</a>
</div>
<h1>WXT 2 + Svelte</h1>
<div class="card">
<Counter />
</div>
<p class="read-the-docs">
Click on the WXT and Svelte logos to learn more
</p>
</main>
<style>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.svelte:hover {
filter: drop-shadow(0 0 2em #ff3e00aa);
}
.read-the-docs {
color: #888;
}
</style>
@@ -0,0 +1,80 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Default Popup Title</title>
<meta name="manifest.type" content="browser_action" />
</head>
<body>
<div id="app"></div>
<script type="module" src="./main.ts"></script>
</body>
</html>
@@ -0,0 +1,8 @@
import './app.css';
import App from './App.svelte';
const app = new App({
target: document.getElementById('app')!,
});
export default app;
+10
View File
@@ -0,0 +1,10 @@
<script lang="ts">
let count: number = 0
const increment = () => {
count += 1
}
</script>
<button on:click={increment}>
count is {count}
</button>
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./src/.wxt/tsconfig.json",
"compilerOptions": {
"useDefineForClassFields": true
}
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="svelte" />
+17
View File
@@ -0,0 +1,17 @@
import { defineConfig } from 'wxt';
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte';
// See https://wxt.dev/config.html
export default defineConfig({
srcDir: 'src',
vite: {
logLevel: 'info',
plugins: [
svelte({
// Using a svelte.config.js file causes a segmentation fault when importing the file
configFile: false,
preprocess: [vitePreprocess()],
}),
],
},
});
+23
View File
@@ -0,0 +1,23 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.output
artifacts
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#007ACC" d="M0 128v128h256V0H0z"></path><path fill="#FFF" d="m56.612 128.85l-.081 10.483h33.32v94.68h23.568v-94.68h33.321v-10.28c0-5.69-.122-10.444-.284-10.566c-.122-.162-20.4-.244-44.983-.203l-44.74.122l-.121 10.443Zm149.955-10.742c6.501 1.625 11.459 4.51 16.01 9.224c2.357 2.52 5.851 7.111 6.136 8.208c.08.325-11.053 7.802-17.798 11.988c-.244.162-1.22-.894-2.317-2.52c-3.291-4.795-6.745-6.867-12.028-7.233c-7.76-.528-12.759 3.535-12.718 10.321c0 1.992.284 3.17 1.097 4.795c1.707 3.536 4.876 5.649 14.832 9.956c18.326 7.883 26.168 13.084 31.045 20.48c5.445 8.249 6.664 21.415 2.966 31.208c-4.063 10.646-14.14 17.879-28.323 20.276c-4.388.772-14.79.65-19.504-.203c-10.28-1.828-20.033-6.908-26.047-13.572c-2.357-2.6-6.949-9.387-6.664-9.874c.122-.163 1.178-.813 2.356-1.504c1.138-.65 5.446-3.129 9.509-5.485l7.355-4.267l1.544 2.276c2.154 3.29 6.867 7.801 9.712 9.305c8.167 4.307 19.383 3.698 24.909-1.26c2.357-2.153 3.332-4.388 3.332-7.68c0-2.966-.366-4.266-1.91-6.501c-1.99-2.845-6.054-5.242-17.595-10.24c-13.206-5.69-18.895-9.224-24.096-14.832c-3.007-3.25-5.852-8.452-7.03-12.8c-.975-3.617-1.22-12.678-.447-16.335c2.723-12.76 12.353-21.659 26.25-24.3c4.51-.853 14.994-.528 19.424.569Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+9
View File
@@ -0,0 +1,9 @@
export function setupCounter(element: HTMLButtonElement) {
let counter = 0;
const setCounter = (count: number) => {
counter = count;
element.innerHTML = `count is ${counter}`;
};
element.addEventListener('click', () => setCounter(counter + 1));
setCounter(0);
}
@@ -0,0 +1,3 @@
export default defineBackground(() => {
console.log('Hello background!', { id: browser.runtime.id });
});
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Default Popup Title</title>
<meta name="manifest.type" content="browser_action" />
</head>
<body>
<div id="app"></div>
<script type="module" src="./main.ts"></script>
</body>
</html>
@@ -0,0 +1,24 @@
import './style.css';
import typescriptLogo from '../../assets/typescript.svg';
import viteLogo from '/wxt.svg';
import { setupCounter } from '../../components/counter';
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<div>
<a href="https://wxt.dev" target="_blank">
<img src="${viteLogo}" class="logo" alt="WXT logo" />
</a>
<a href="https://www.typescriptlang.org/" target="_blank">
<img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" />
</a>
<h1>WXT + TypeScript</h1>
<div class="card">
<button id="counter" type="button"></button>
</div>
<p class="read-the-docs">
Click on the WXT and TypeScript logos to learn more
</p>
</div>
`;
setupCounter(document.querySelector<HTMLButtonElement>('#counter')!);
@@ -0,0 +1,97 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vanilla:hover {
filter: drop-shadow(0 0 2em #3178c6aa);
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"name": "wxt-starter",
"description": "manifest.json description",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "wxt",
"dev:firefox": "wxt -b firefox",
"build": "wxt build",
"build:firefox": "wxt build -b firefox",
"compile": "tsc --noEmit",
"postinstall": "wxt prepare"
},
"devDependencies": {
"typescript": "^5.1.6",
"wxt": "^0.1.3"
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+3
View File
@@ -0,0 +1,3 @@
{
"extends": "./.wxt/tsconfig.json"
}
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

Some files were not shown because too many files have changed in this diff Show More