Compare commits

..

13 Commits

Author SHA1 Message Date
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
49 changed files with 1063 additions and 74 deletions
+1
View File
@@ -4,3 +4,4 @@ dist
e2e/project
.wxt
docs/.vitepress/cache
pnpm-lock.yaml
+56
View File
@@ -1,5 +1,61 @@
# Changelog
## v0.2.2
[compare changes](https://github.com/aklinker1/wxt/compare/v0.2.1...v0.2.2)
### 🩹 Fixes
- Register content scripts correctly ([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)
+1 -1
View File
@@ -11,7 +11,7 @@
"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"
"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 |
| ---------------- | :----------------------: |
+15 -6
View File
@@ -6,10 +6,7 @@ Bootstrap a new project or start from scratch.
:::warning 🚧&ensp;The `wxt init` command is not implemented yet.
See [From Scratch](#from-scratch) or reference one of the templates on GitHub:
- [Vue](https://github.com/aklinker1/wxt/tree/main/templates/vue)
- [React](https://github.com/aklinker1/wxt/tree/main/templates/react)
See [From Scratch](#from-scratch) or reference one of the templates below.
:::
@@ -25,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:
@@ -71,7 +79,7 @@ Add your first entrypoint:
```ts
// entrypoints/background.ts
export default defineBackgroundScript(() => {
export default defineBackground(() => {
console.log(`Hello from ${browser.runtime.id}!`);
});
```
@@ -84,7 +92,8 @@ Finally, add scripts to your `package.json`:
"dev": "wxt", // [!code ++]
"dev:firefox": "wxt --browser firefox", // [!code ++]
"build": "wxt build", // [!code ++]
"build:firefox": "wxt build --browser firefox" // [!code ++]
"build:firefox": "wxt build --browser firefox", // [!code ++]
"postinstall": "wxt prepare" // [!code ++]
}
}
```
+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']
+4 -4
View File
@@ -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: () => {},
})`,
);
+2 -2
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'),
);`,
);
@@ -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();
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.1.6",
"version": "0.2.2",
"description": "Next gen framework for developing web extensions",
"engines": {
"node": ">=18.16.0",
@@ -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',
{
+3 -1
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;
@@ -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',
+5 -3
View File
@@ -6,7 +6,7 @@ import {
} 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';
@@ -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 {
+115 -5
View File
@@ -1,27 +1,96 @@
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;
}
@@ -83,7 +152,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 +257,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>;
}
@@ -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);
});
});
});
+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,
};
}
-1
View File
@@ -122,7 +122,6 @@ export async function getInternalConfig(
);
finalConfig.vite.plugins.push(plugins.devServerGlobals(finalConfig));
finalConfig.vite.plugins.push(plugins.tsconfigPaths(finalConfig));
finalConfig.vite.plugins.push(plugins.hmrLogger(finalConfig));
finalConfig.vite.define ??= {};
getGlobals(finalConfig).forEach((global) => {
+6 -2
View File
@@ -12,6 +12,10 @@ import fs from 'fs-extra';
import { resolve } from 'path';
import { getEntrypointBundlePath } from './entrypoints';
import { ContentSecurityPolicy } from './ContentSecurityPolicy';
import {
hashContentScriptOptions,
mapWxtOptionsToContentScript,
} from './content-scripts';
/**
* Writes the manifest to the output directory and the build output.
@@ -285,7 +289,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 +297,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(),
-25
View File
@@ -1,25 +0,0 @@
import { Plugin } from 'vite';
import { InternalConfig } from '../types';
import { relative } from 'path';
import pc from 'picocolors';
/**
* Log when HMR changes are fired
*/
export function hmrLogger(config: InternalConfig): Plugin {
return {
name: 'wxt:hmr-logger',
apply: 'serve',
handleHotUpdate(ctx) {
if (
ctx.file.startsWith(config.srcDir) &&
!ctx.file.startsWith(config.wxtDir) &&
!ctx.file.endsWith('.html')
) {
config.logger.info(
'Hot reload: ' + pc.dim(relative(process.cwd(), ctx.file)),
);
}
},
};
}
-1
View File
@@ -5,4 +5,3 @@ export * from './multipageMove';
export * from './unimport';
export * from './virtualEntrypoint';
export * from './tsconfigPaths';
export * from './hmrLogger';
+1 -1
View File
@@ -1,3 +1,3 @@
export default defineBackgroundScript(() => {
export default defineBackground(() => {
console.log('Hello background!', { id: browser.runtime.id });
});
+1 -1
View File
@@ -10,7 +10,7 @@
"build": "wxt build",
"build:firefox": "wxt build -b firefox",
"compile": "tsc --noEmit",
"prepare": "wxt prepare"
"postinstall": "wxt prepare"
},
"dependencies": {
"react": "^18.2.0",
+1 -1
View File
@@ -9,7 +9,7 @@
"build": "wxt build",
"build:firefox": "wxt build -b firefox",
"check": "svelte-check --tsconfig ./tsconfig.json",
"prepare": "wxt prepare"
"postinstall": "wxt prepare"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.2",
@@ -1,3 +1,3 @@
export default defineBackgroundScript(() => {
export default defineBackground(() => {
console.log('Hello background!', { id: browser.runtime.id });
});
+1 -1
View File
@@ -1,3 +1,3 @@
export default defineBackgroundScript(() => {
export default defineBackground(() => {
console.log('Hello background!', { id: browser.runtime.id });
});
+1 -1
View File
@@ -10,7 +10,7 @@
"build": "wxt build",
"build:firefox": "wxt build -b firefox",
"compile": "tsc --noEmit",
"prepare": "wxt prepare"
"postinstall": "wxt prepare"
},
"devDependencies": {
"typescript": "^5.1.6",
+1 -1
View File
@@ -1,3 +1,3 @@
export default defineBackgroundScript(() => {
export default defineBackground(() => {
console.log('Hello background!', { id: browser.runtime.id });
});
+1 -1
View File
@@ -10,7 +10,7 @@
"build": "wxt build",
"build:firefox": "wxt build -b firefox",
"compile": "vue-tsc --noEmit",
"prepare": "wxt prepare"
"postinstall": "wxt prepare"
},
"dependencies": {
"vue": "^3.3.4"