Compare commits

...

40 Commits

Author SHA1 Message Date
GitHub Actions 665b9194cf chore(release): @wxt-dev/storage v1.1.0
vhs / vhs (push) Waiting to run
2025-01-28 06:40:20 +00:00
Gabriel Roldán 4690418c94 Fix: Remove duplicated css rule from vue template (#1369) 2025-01-27 16:00:09 -06:00
Chengxi 20eea2a7d1 feat: Add storage.clear (#1368)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-01-27 07:22:19 -06:00
rxliuli ac7b6760f2 docs: Add missing bracket in example (#1367) 2025-01-25 01:14:48 -06:00
GitHub Actions a593cfbbb6 chore(release): wxt v0.19.25
vhs / vhs (push) Waiting to run
2025-01-22 05:08:46 +00:00
Aaron a205f2a0a8 chore: Update lockfile 2025-01-21 23:03:09 -06:00
Aaron 8e2badc92b fix: Drop support for vite 6.0.9+ until websocket fix is finished 2025-01-21 23:02:22 -06:00
redwoodlid 7c18d9c0b8 docs: Add react language ID to README (#1347) 2025-01-15 10:25:40 -06:00
Aaron 55a1f39450 Update changelog 2025-01-15 10:23:36 -06:00
GitHub Actions d708eca78e chore(release): wxt v0.19.24
vhs / vhs (push) Waiting to run
2025-01-12 22:48:50 +00:00
1natsu 5d7e2516b6 fix: autoMount not working if anchor is already present (#1350) 2025-01-12 16:43:07 -06:00
Eric fb6a29872b docs: Add "HTML to Markdown" to the homepage (#1345) 2025-01-10 11:28:27 -06:00
rxliuli 187206062e docs: Update content script UI examples to use Svelte v5 (#1342) 2025-01-07 23:51:46 -06:00
Aaron 2615e29e52 docs: Add section to FAQ about teleports/portals 2025-01-07 23:37:53 -06:00
Aaron 8ca176d37e docs: Update i18n.md 2025-01-06 17:40:57 -06:00
Aaron cb2bf43f30 docs: Update content-scripts.md 2025-01-06 17:36:43 -06:00
Aaron 1719d17cff docs: Fix typo 2025-01-06 17:28:31 -06:00
Aaron 32afb8d97c docs: Improve manifest docs, mention entrypoint-specific options 2025-01-06 17:28:09 -06:00
Aaron 72b8a29d76 docs: Improve entrypoint docs 2025-01-06 17:09:20 -06:00
Aaron af0a32d478 docs: Add missing tabs in project structure 2025-01-06 16:59:23 -06:00
Nikolay Ponomarev 060e85f90e docs: Add missing web_accessible_resources to injectScript example (#1337) 2025-01-06 16:23:41 -06:00
Aaron 654a54ac79 docs: Update link 2025-01-05 10:14:49 -06:00
ntnyq ef24358942 docs: Fix unocss readme (#1329)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-01-04 17:01:43 -06:00
Aaron e5e67e39bd docs: Add paragraph on browser binary auto-discovery 2025-01-01 17:23:04 -06:00
Ahmed Dinar 228304bef9 docs: Added "Blync" to the homepage (#1319) 2025-01-01 08:56:07 -06:00
Paulo Pinto 78f97f32a2 fix(web-ext): Correctly pass browserConsole option to open devtools in Firefox (#1308) 2024-12-29 12:35:38 -06:00
Abhigyan Trips 459662a6c1 docs: Added "ZenGram" to the homepage (#1313) 2024-12-29 12:26:14 -06:00
Aaron b150a52471 fix(init): Remove "experimental" from bun package manager 2024-12-25 12:52:13 -06:00
Aaron 028c601515 chore: Fix typo in CLI docs 2024-12-25 12:51:41 -06:00
Aaron af382ef065 Fix changelog 2024-12-25 09:47:41 -06:00
GitHub Actions 91ef7f2ab5 chore(release): wxt v0.19.23
vhs / vhs (push) Waiting to run
2024-12-25 15:44:50 +00:00
Aaron 0bf7279c8f fix: Properly close readline instance on close (#1278) 2024-12-25 09:40:39 -06:00
Aaron 62525d33c6 fix: Allow runtime registered content scripts to not have matches (#1306) 2024-12-25 09:39:37 -06:00
Aaron 88ff40e301 docs: Fix typo in FAQ 2024-12-23 09:09:08 -06:00
Aaron 1a72a0cdc6 docs: Add paragraph on adding permissions 2024-12-22 09:14:31 -06:00
Amit Yadav 2b07b4bc12 docs: Fix invalid CSS color (#1299) 2024-12-22 09:09:49 -06:00
Abhigyan Trips 06c09e7c87 feat(hooks): Move entrypoints:resolved before debug logs and add entrypoints:found (#1292)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2024-12-20 21:09:31 -06:00
Amit Yadav 30b96ac87c docs: Added "Strong Password Generator" to the homepage (#1290) 2024-12-19 00:20:09 -06:00
Amit Yadav 2e9b5cffe8 docs: Added "Show IP Extension" to the homepage (#1289) 2024-12-18 17:13:11 -06:00
Aaron 3e1641a6ec docs: Add more comments on entrypoint options 2024-12-17 14:05:37 -06:00
31 changed files with 455 additions and 131 deletions
@@ -59,6 +59,11 @@ const chromeExtensionIds = [
'npgghjedpchajflknnbngajkjkdhncdo', // aesthetic Notion, styled
'hmdcmlfkchdmnmnmheododdhjedfccka', // Eye Dropper
'eihpmapodnppeemkhkbhikmggfojdkjd', // Cursorful - Screen Recorder with Auto Zoom
'hjjkgbibknbahijglkffklflidncplkn', // Show IP Live View of Website IPs for Developers
'ilbikcehnpkmldojkcmlldkoelofnbde', // Strong Password Generator
'ocllfkhcdopiafndigclebelbecaiocp', // ZenGram: Mindful Instagram, Your Way
'odffpjnpocjfcaclnenaaaddghkgijdb', // Blync: Preview Links, Selection Search, AI Assistant
'kofbbilhmnkcmibjbioafflgmpkbnmme', // HTML to Markdown - Convert webpages to markdown
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
+2 -2
View File
@@ -3,14 +3,14 @@
--wxt-c-green-1: #0b8a00;
--wxt-c-green-2: #096600;
--wxt-c-green-3: #096600;
--wxt-c-green-soft: rgba(#0b8a00 / 0.14);
--wxt-c-green-soft: rgba(11, 138, 0, 0.14);
}
.dark {
--wxt-c-green-1: #67d45e;
--wxt-c-green-2: #329929;
--wxt-c-green-3: #21651b;
--wxt-c-green-soft: rgba(#67d45e / 0.14);
--wxt-c-green-soft: rgba(103, 212, 94, 0.14);
}
/* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */
@@ -41,6 +41,8 @@ export default defineRunnerConfig({
});
```
By default, WXT will try to automatically discover where Chrome/Firefox are installed. However, if you have chrome installed in a non-standard location, you need to set it manually as shown above.
### Persist Data
By default, to keep from modifying your browser's existing profiles, `web-ext` creates a brand new profile every time you run the `dev` script.
+17 -20
View File
@@ -1,10 +1,17 @@
# Manifest
In WXT, there is no `manifest.json` file in your source code. Instead, WXT generates it during the build process based off files in your project.
In WXT, there is no `manifest.json` file in your source code. Instead, WXT generates the manifest from multiple sources:
## Manifest Config
- Global options [defined in your `wxt.config.ts` file](#global-options)
- Entrypoint-specific options [defined in your entrypoints](/guide/essentials/entrypoints#defining-manifest-options)
- [WXT Modules](/guide/essentials/wxt-modules) added to your project can modify your manifest
- [Hooks](/guide/essentials/config/hooks) defined in your project can modify your manifest
To manually add a property to the `manifest.json` output during builds, use the `manifest` config inside `wxt.config.ts`:
Your extension's `manifest.json` will be output to `.output/{target}/manifest.json` when running `wxt build`.
## Global Options
To add a property to your manifest, use the `manifest` config inside your `wxt.config.ts`:
```ts
export default defineConfig({
@@ -130,7 +137,7 @@ public/
└─ icon-128.png
```
Specifically, if an icon must match one of these regex to be discovered:
Specifically, an icon must match one of these regex to be discovered:
<<< @/../packages/wxt/src/core/utils/manifest.ts#snippet
@@ -156,6 +163,11 @@ Alternatively, you can use [`@wxt-dev/auto-icons`](https://www.npmjs.com/package
> [Chrome docs](https://developer.chrome.com/docs/extensions/reference/permissions/)
Most of the time, you need to manually add permissions to your manifest. Only in a few specific situations are permissions added automatically:
- During development: the `tabs` and `scripting` permissions will be added to enable hot reloading.
- When a `sidepanel` entrypoint is present: The `sidepanel` permission is added.
```ts
export default defineConfig({
manifest: {
@@ -211,22 +223,7 @@ By default, whenever an `action` is generated, WXT falls back to `browser_action
### Action With Popup
To generate a manifest where a UI appears after clicking the icon, just create a [Popup entrypoint](/guide/essentials/entrypoints#popup).
```ts
export default defineConfig({
hooks: {
build: {
manifestGenerated(manifest) {
// Update the manifest variable by reference
manifest.name = 'Overriden name';
},
},
},
});
```
If you want to use a `page_action` for MV2, add the following meta tag to the HTML document's head:
To generate a manifest where a UI appears after clicking the icon, just create a [Popup entrypoint](/guide/essentials/entrypoints#popup). If you want to use a `page_action` for MV2, add the following meta tag to the HTML document's head:
```html
<meta name="manifest.type" content="page_action" />
+32 -16
View File
@@ -4,12 +4,14 @@ outline: deep
# Content Scripts
> To create a content script, see [Entrypoint Types](/guide/essentials/entrypoints#content-scripts).
## Context
The first argument to a content script's `main` function is it's "context".
```ts
// entrypoints/content.ts
// entrypoints/example.content.ts
export default defineContentScript({
main(ctx) {},
});
@@ -62,7 +64,7 @@ In regular web extensions, CSS for content scripts is usually a separate CSS fil
In WXT, to add CSS to a content script, simply import the CSS file into your JS entrypoint, and WXT will automatically add the bundled CSS output to the `css` array.
```ts
// entrypoints/content/index.ts
// entrypoints/example.content/index.ts
import './style.css';
export default defineContentScript({
@@ -78,16 +80,16 @@ To create a standalone content script that only includes a CSS file:
// wxt.config.ts
export default defineConfig({
hooks: {
"build:manifestGenerated": (wxt, manifest) => {
'build:manifestGenerated': (wxt, manifest) => {
manifest.content_scripts ??= [];
manifest.content_scripts.push({
// Build extension once to see where your CSS get's written to
css: ["content-scripts/example.css"],
matches: ["*://*/*"]
)
}
}
})
css: ['content-scripts/example.css'],
matches: ['*://*/*'],
});
},
},
});
```
## UI
@@ -198,6 +200,7 @@ export default defineContentScript({
```ts [Svelte]
// entrypoints/example-ui.content/index.ts
import App from './App.svelte';
import { mount, unmount } from 'svelte';
export default defineContentScript({
matches: ['<all_urls>'],
@@ -208,14 +211,13 @@ export default defineContentScript({
anchor: 'body',
onMount: (container) => {
// Create the Svelte app inside the UI container
const app = new App({
mount(App, {
target: container,
});
return app;
},
onRemove: (app) => {
// Destroy the app when the UI is removed
app.$destroy();
unmount(app);
},
});
@@ -379,6 +381,7 @@ export default defineContentScript({
// 1. Import the style
import './style.css';
import App from './App.svelte';
import { mount, unmount } from 'svelte';
export default defineContentScript({
matches: ['<all_urls>'],
@@ -393,14 +396,13 @@ export default defineContentScript({
anchor: 'body',
onMount: (container) => {
// Create the Svelte app inside the UI container
const app = new App({
mount(App, {
target: container,
});
return app;
},
onRemove: (app) => {
onRemove: () => {
// Destroy the app when the UI is removed
app?.$destroy();
unmount(app);
},
});
@@ -566,6 +568,20 @@ export default defineContentScript({
});
```
```json
export default defineConfig({
manifest: {
// ...
web_accessible_resources: [
{
resources: ["example-main-world.js"],
matches: ["*://*/*"],
}
]
}
});
```
`injectScript` works by creating a `script` element on the page pointing to your script. This loads the script into the page's context so it runs in the main world.
`injectScript` returns a promise, that when resolved, means the script has been evaluated by the browser and you can start communicating with it.
+27 -3
View File
@@ -28,7 +28,7 @@ For web extensions, there are two types of entrypoints:
- **Listed**: Referenced in the `manifest.json`
- **Unlisted**: Not referenced in the `manifest.json`
Throughout the rest of WXT's documentation, listed files are referred to by name. For example:
Throughout the rest of WXT's documentation, listed entrypoints are referred to by name. For example:
- Popup
- Options
@@ -42,7 +42,7 @@ Some examples of "unlisted" entrypoints:
- JS files injected by content scripts into the page's main world
:::tip
Regardless of whether a entrypoint is listed or unlisted, it will still be bundled into your extension and be available at runtime.
Regardless of whether an entrypoint is listed or unlisted, it will still be bundled into your extension and be available at runtime.
:::
## Adding Entrypoints
@@ -72,7 +72,7 @@ Refer to the [Entrypoint Types](#entrypoint-types) section for the full list of
## Defining Manifest Options
Most listed entrypoints have options that need to be added to the `manifest.json`. With WXT however, instead of defining the options in a separate file, _you define these options inside the entrypoint file itself_.
Most listed entrypoints have options that need to be added to the `manifest.json`. However with WXT, instead of defining the options in a separate file, _you define these options inside the entrypoint file itself_.
For example, here's how to define `matches` for content scripts:
@@ -86,6 +86,17 @@ export default defineContentScript({
});
```
For HTML entrypoints, options are configured as `<meta>` tags. For example, to use a `page_action` for your MV2 popup:
```html
<!doctype html>
<html lang="en">
<head>
<meta name="manifest.type" content="page_action" />
</head>
</html>
```
> Refer to the [Entrypoint Types](#entrypoint-types) sections for a list of options configurable inside each entrypoint, and how to define them.
When building your extension, WXT will look at the options defined in your entrypoints, and generate the manifest accordingly.
@@ -306,9 +317,12 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Options Title</title>
<!-- Customize the manifest options -->
<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" />
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
@@ -336,7 +350,11 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Set the `action.default_title` in the manifest -->
<title>Default Popup Title</title>
<!-- Customize the manifest options -->
<meta
name="manifest.default_icon"
content="{
@@ -347,6 +365,7 @@ Follow the [Devtools Example](https://github.com/wxt-dev/examples/tree/main/exam
/>
<meta name="manifest.type" content="page_action|browser_action" />
<meta name="manifest.browser_style" content="true|false" />
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
@@ -381,6 +400,7 @@ Firefox does not support sandboxed pages.
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
@@ -413,6 +433,8 @@ In Chrome, side panels use the `side_panel` API, while Firefox uses the `sidebar
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Default Side Panel Title</title>
<!-- Customize the manifest options -->
<meta
name="manifest.default_icon"
content="{
@@ -423,6 +445,7 @@ In Chrome, side panels use the `side_panel` API, while Firefox uses the `sidebar
/>
<meta name="manifest.open_at_install" content="true|false" />
<meta name="manifest.browser_style" content="true|false" />
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
@@ -472,6 +495,7 @@ body {
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
+4
View File
@@ -81,6 +81,10 @@ However, it is recommended you stick with the vanilla API (or a package based on
- Translations are not bundled multiple times, keeping your extension small
- Zero configuration
However, there is one major downside to the vanilla API and any packages built on top of it:
- Language cannot be changed without changing your browser/system language
Here are some examples of how to setup a third party i18n library:
- [vue-i18n](https://github.com/wxt-dev/wxt-examples/tree/main/examples/vue-i18n)
+18 -18
View File
@@ -59,24 +59,24 @@ After enabling it, your project structure should look like this:
<!-- prettier-ignore -->
```html
📂 {rootDir}/
📁 .output/
📁 .wxt/
📂 src/
📁 assets/
📁 components/
📁 composables/
📁 entrypoints/
📁 hooks/
📁 modules/
📁 public/
📁 utils/
📄 app.config.ts
📄 .env
📄 .env.publish
📄 package.json
📄 tsconfig.json
📄 web-ext.config.ts
📄 wxt.config.ts
📁 .output/
📁 .wxt/
📂 src/
📁 assets/
📁 components/
📁 composables/
📁 entrypoints/
📁 hooks/
📁 modules/
📁 public/
📁 utils/
📄 app.config.ts
📄 .env
📄 .env.publish
📄 package.json
📄 tsconfig.json
📄 web-ext.config.ts
📄 wxt.config.ts
```
## Customizing Other Directories
+25
View File
@@ -167,6 +167,31 @@ This file could then be loaded at runtime:
const res = await fetch(browser.runtime.getURL('/some-text.txt'));
```
#### Add custom entrypoints
Once the existing files under the `entrypoints/` directory have been discovered, the `entrypoints:found` hook can be used to add custom entrypoints.
:::info
The `entrypoints:found` hook is triggered before validation is carried out on the list of entrypoints. Thus, any custom entrypoints will still be checked for duplicate names and logged during debugging.
:::
```ts
import { defineWxtModule } from 'wxt/modules';
export default defineWxtModule({
setup(wxt) {
wxt.hook('entrypoints:found', (_, entrypointInfos) => {
// Add your new entrypoint
entrypointInfos.push({
name: 'my-custom-script',
inputPath: 'path/to/custom-script.js',
type: 'content-script',
});
});
},
});
```
#### Generate runtime module
Create a file in `.wxt`, add an alias to import it, and add auto-imports for exported variables.
+126 -36
View File
@@ -28,47 +28,137 @@ See https://wxt.dev/guide/essentials/config/browser-startup.html#persist-data
## My component library doesn't work in content scripts!
Component libraries place their CSS in the document's `<head>` by default. When using `createShadowRoot`, your UI is isolated from the document's styles because it's inside a ShadowRoot.
This is usually caused by one of two things (or both) when using `createShadowRootUi`:
To fix this, you need to tell your component library to insert it's CSS inside the shadow root. Here's the docs for a couple of popular libraries:
1. Styles are added outside the `ShadowRoot`
- React
- Ant Design: [`StyleProvider`](https://ant.design/docs/react/compatible-style#shadow-dom-usage)
- Mantine: [`MantineProvider#getRootElement` and `MantineProvider#cssVariablesSelector`](https://mantine.dev/theming/mantine-provider/)
:::details
Some component libraries manually add CSS to the page by adding a `<style>` or `<link>` element. They place this element in the document's `<head>` by default. This causes your styles to be placed outside the `ShadowRoot` and it's isolation blocks the styles from being applied to your UI.
> If your library isn't listed above, try searching it's docs/issues for "shadow root", "shadow dom", or "css container".
When a library does this, **you need to tell the library where to put its styles**. Here's the documentation for a few popular component libraries:
`createShadowRoot` provides it's own `<head>` element inside the shadow root, so that were you should tell the library to add the CSS. Here's an example with Ant Design:
- Ant Design: [`StyleProvider`](https://ant.design/docs/react/compatible-style#shadow-dom-usage)
- Mantine: [`MantineProvider#getRootElement` and `MantineProvider#cssVariablesSelector`](https://mantine.dev/theming/mantine-provider/)
```tsx
import { StyleProvider } from '@ant-design/cssinjs'; // [!code ++]
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
> If your library isn't listed above, try searching it's docs/issues for "shadow root", "shadow dom", or "css container". Not all libraries support shadow DOMs, you may have to open an issue to request this feature.
const ui = await createShadowRootUi(ctx, {
name: 'example-ui',
position: 'inline',
anchor: 'body',
onMount: (container) => { // [!code --]
onMount: (container, shadow) => { // [!code ++]
const cssContainer = shadow.querySelector("head")!; // [!code ++]
const root = ReactDOM.createRoot(container);
root.render(
<StyleProvider container={cssContainer}> // [!code ++]
<App />
</StyleProvider> // [!code ++]
);
return root;
},
onRemove: (root) => {
root?.unmount();
},
});
```
Here's an example of configuring Antd's styles:
Note that this doesn't effect all component libraries, just ones that inject CSS themselves rather than having you import their CSS. This approach is more prevailent in the React community, but not limited to it. That's why only React libraries are listed above. Vuetify, for example, works just fine because you import its CSS - WXT picks up on this and the CSS is added inside the shadow root automatically:
```tsx
import { StyleProvider } from '@ant-design/cssinjs';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
```ts
import 'vuetify/styles'; // <-- This line imports the CSS, just like importing a .css file
import { createVuetify } from 'vuetify';
```
const ui = await create`ShadowRoot`Ui(ctx, {
// ...
onMount: (container, shadow) => {
const cssContainer = shadow.querySelector('head')!;
const root = ReactDOM.createRoot(container);
root.render(
<StyleProvider container={cssContainer}>
<App />
</StyleProvider>,
);
return root;
},
});
```
:::
2. UI elements are added outside the `ShadowRoot`
::::::details
This is mostly caused by `Teleport` or `Portal` components that render an element somewhere else in the DOM, usually in the document's `<body>`. This is usually done for dialogs or popover components. This renders the element is outside the `ShadowRoot`, so styles are not applied to it.
To fix this, **you need to both provide a target to your app AND pass the target to the `Teleport`/`Portal`**.
First, store the reference to the `ShadowRoot`'s `<body>` element (not the document's `<body>`):
:::code-group
```ts [Vue]
import { createApp } from 'vue';
import App from './App.vue';
const ui = await create`ShadowRoot`Ui(ctx, {
// ...
onMount: (container, shadow) => {
const teleportTarget = shadow.querySelector('body')!;
const app = createApp(App)
.provide('TeleportTarget', teleportTarget)
.mount(container);
return app;
},
});
ui.mount();
```
```tsx [React]
// hooks/PortalTargetContext.ts
import { createContext } from 'react';
export const PortalTargetContext = createContext<HTMLElement>();
// entrypoints/example.content.ts
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import PortalTargetContext from '~/hooks/PortalTargetContext';
const ui = await create`ShadowRoot`Ui(ctx, {
// ...
onMount: (container, shadow) => {
const portalTarget = shadow.querySelector('body')!;
const root = ReactDOM.createRoot(container);
root.render(
<PortalTargetContext.Provider value={portalTarget}>
<App />
</PortalTargetContext.Provider>,
);
return root;
},
});
ui.mount();
```
:::
Then use the reference when teleporting/portaling part of your UI to a different place in the DOM:
:::code-group
```vue [Vue]
<script lang="ts" setup>
import { Teleport } from 'vue';
const teleportTarget = inject('TeleportTarget');
</script>
<template>
<div>
<Teleport :to="teleportTarget">
<dialog>My dialog</dialog>
</Teleport>
</div>
</template>
```
```tsx [React]
import { useContext } from 'react';
import { createPortal } from 'react-dom';
import PortalTargetContext from '~/hooks/PortalTargetContext';
const MyComponent = () => {
const portalTarget = useContext(PortalTargetContext);
return <div>{createPortal(<dialog>My dialog</dialog>, portalTarget)}</div>;
};
```
:::
::::::
Both issues have the same cause: the library puts something outside the `ShadowRoot`, and the `ShadowRoot`'s isolation prevents CSS from being applied to your UI.
Both issues have the same fix: tell the library to put elements inside the `ShadowRoot`, not outside it. See the details above for more information and example fixes for each problem.
+1
View File
@@ -354,6 +354,7 @@ You'll need to configure it the extension so it knows where your localization fi
# You can check available language ids here: https://code.visualstudio.com/docs/languages/identifiers
languageIds:
- typescript
- typescriptreact
# Look for t("...")
usageMatchRegex:
+17
View File
@@ -1,5 +1,22 @@
# Changelog
## v1.1.0
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.0.1...storage-v1.1.0)
### 🚀 Enhancements
- Add `storage.clear` ([#1368](https://github.com/wxt-dev/wxt/pull/1368))
### 📖 Documentation
- Update link ([654a54a](https://github.com/wxt-dev/wxt/commit/654a54a))
### ❤️ Contributors
- Chengxi ([@chengxilo](http://github.com/chengxilo))
- Aaron ([@aklinker1](http://github.com/aklinker1))
## v1.0.1
[compare changes](https://github.com/wxt-dev/wxt/compare/storage-v1.0.0...storage-v1.0.1)
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@wxt-dev/storage",
"description": "Web extension storage API provided by WXT, supports all browsers.",
"version": "1.0.1",
"version": "1.1.0",
"type": "module",
"repository": {
"type": "git",
@@ -489,6 +489,18 @@ describe('Storage Utils', () => {
});
});
describe('clear', () => {
it('should remove all items', async () => {
await fakeBrowser.storage[storageArea].set({
one: 1,
two: 2,
});
await storage.clear(storageArea);
expect(await fakeBrowser.storage[storageArea].get()).toEqual({});
});
});
describe('removeMeta', () => {
it('should remove all metadata', async () => {
await fakeBrowser.storage[storageArea].set({ count$: { v: 4 } });
+15 -1
View File
@@ -326,6 +326,10 @@ function createStorage(): WxtStorage {
}),
);
},
clear: async (base) => {
const driver = getDriver(base);
await driver.clear();
},
removeMeta: async (key, properties) => {
const { driver, driverKey } = resolveKey(key);
await removeMeta(driver, driverKey, properties);
@@ -534,6 +538,9 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
removeItems: async (keys) => {
await getStorageArea().remove(keys);
},
clear: async () => {
await getStorageArea().clear();
},
snapshot: async () => {
return await getStorageArea().get();
},
@@ -674,6 +681,12 @@ export interface WxtStorage {
| { item: WxtStorageItem<any, any>; options?: RemoveItemOptions }
>,
): Promise<void>;
/**
* Removes all items from the provided storage area.
*/
clear(base: StorageArea): Promise<void>;
/**
* Remove the entire metadata for a key, or specific properties by name.
*
@@ -712,7 +725,7 @@ export interface WxtStorage {
/**
* Define a storage item with a default value, type, or versioning.
*
* Read full docs: https://wxt.dev/guide/extension-apis/storage.html#defining-storage-items
* Read full docs: https://wxt.dev/storage.html#defining-storage-items
*/
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
key: StorageItemKey,
@@ -730,6 +743,7 @@ interface WxtStorageDriver {
setItems(values: Array<{ key: string; value: any }>): Promise<void>;
removeItem(key: string): Promise<void>;
removeItems(keys: string[]): Promise<void>;
clear(): Promise<void>;
snapshot(): Promise<Record<string, unknown>>;
restoreSnapshot(data: Record<string, unknown>): Promise<void>;
watch<T>(key: string, cb: WatchCallback<T | null>): Unwatch;
+3 -3
View File
@@ -38,10 +38,10 @@ The module can be configured via the `unocss` config:
export default defineConfig({
modules: ['@wxt-dev/unocss'],
unocss: {
// Will only apply unocss for popup/main.ts
entrypoints: ['popup/main.ts'],
// Exclude unocss from running for the background
excludeEntrypoints: ['background'],
},
});
```
Options have JSDocs available in your editor, or you can read them in the source code: [`UnoCSSOptions`](https://github.com/wxt-dev/wxt/blob/main/packages/auto-icons/src/index.ts).
Options have JSDocs available in your editor, or you can read them in the source code: [`UnoCSSOptions`](https://github.com/wxt-dev/wxt/blob/main/packages/unocss/src/index.ts).
+58
View File
@@ -1,5 +1,63 @@
# Changelog
## v0.19.25
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.24...wxt-v0.19.25)
### 🩹 Fixes
- Drop support for vite 6.0.9+ until websocket fix is finished ([8e2badc](https://github.com/wxt-dev/wxt/commit/8e2badc))
### ❤️ Contributors
- Aaron ([@aklinker1](http://github.com/aklinker1))
## v0.19.24
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.23...wxt-v0.19.24)
### 🩹 Fixes
- **init:** Remove "experimental" from bun package manager ([b150a52](https://github.com/wxt-dev/wxt/commit/b150a52))
- **web-ext:** Correctly pass `browserConsole` option to open devtools in Firefox ([#1308](https://github.com/wxt-dev/wxt/pull/1308))
- `autoMount` not working if anchor is already present ([#1350](https://github.com/wxt-dev/wxt/pull/1350))
### 🏡 Chore
- Fix typo in CLI docs ([028c601](https://github.com/wxt-dev/wxt/commit/028c601))
### ❤️ Contributors
- 1natsu ([@1natsu172](http://github.com/1natsu172))
- Paulo Pinto ([@psrpinto](https://github.com/psrpinto))
- Aaron ([@aklinker1](http://github.com/aklinker1))
## v0.19.23
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.22...wxt-v0.19.23)
### 🚀 Enhancements
- **hooks:** Move `entrypoints:resolved` before debug logs and add `entrypoints:found` ([#1292](https://github.com/wxt-dev/wxt/pull/1292))
### 🩹 Fixes
- Allow runtime registered content scripts to not have `matches` ([#1306](https://github.com/wxt-dev/wxt/pull/1306))
- Properly close readline instance on close ([#1278](https://github.com/wxt-dev/wxt/pull/1278))
### 📖 Documentation
- Sync READMEs ([27298b7](https://github.com/wxt-dev/wxt/commit/27298b7))
### 🏡 Chore
- Simplify keyboard shortcuts ([#1284](https://github.com/wxt-dev/wxt/pull/1284))
### ❤️ Contributors
- Aaron ([@aklinker1](http://github.com/aklinker1))
- Abhigyan Trips ([@abhigyantrips](http://github.com/abhigyantrips))
## v0.19.22
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.21...wxt-v0.19.22)
+6
View File
@@ -11,6 +11,7 @@ const hooks: WxtHooks = {
'build:done': vi.fn(),
'build:manifestGenerated': vi.fn(),
'build:publicAssets': vi.fn(),
'entrypoints:found': vi.fn(),
'entrypoints:resolved': vi.fn(),
'entrypoints:grouped': vi.fn(),
'vite:build:extendConfig': vi.fn(),
@@ -60,6 +61,7 @@ describe('Hooks', () => {
'build:done': false,
'build:publicAssets': false,
'build:manifestGenerated': false,
'entrypoints:found': true,
'entrypoints:grouped': false,
'entrypoints:resolved': true,
'vite:build:extendConfig': false,
@@ -91,6 +93,7 @@ describe('Hooks', () => {
'build:done': true,
'build:publicAssets': true,
'build:manifestGenerated': true,
'entrypoints:found': true,
'entrypoints:grouped': true,
'entrypoints:resolved': true,
'vite:build:extendConfig': 1,
@@ -122,6 +125,7 @@ describe('Hooks', () => {
'build:done': true,
'build:publicAssets': true,
'build:manifestGenerated': true,
'entrypoints:found': true,
'entrypoints:grouped': true,
'entrypoints:resolved': true,
'vite:build:extendConfig': 1,
@@ -153,6 +157,7 @@ describe('Hooks', () => {
'build:done': true,
'build:publicAssets': true,
'build:manifestGenerated': true,
'entrypoints:found': 2,
'entrypoints:grouped': true,
'entrypoints:resolved': 2,
'vite:build:extendConfig': 1,
@@ -191,6 +196,7 @@ describe('Hooks', () => {
'build:done': true,
'build:publicAssets': true,
'build:manifestGenerated': true,
'entrypoints:found': true,
'entrypoints:grouped': true,
'entrypoints:resolved': true,
'vite:build:extendConfig': 2,
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.19.22",
"version": "0.19.25",
"description": "Next gen framework for developing web extensions",
"repository": {
"type": "git",
@@ -123,7 +123,7 @@
"publish-browser-extension": "^2.2.2",
"scule": "^1.3.0",
"unimport": "^3.13.1",
"vite": "^5.0.0 || ^6.0.0",
"vite": "^5.0.0 || <=6.0.8",
"vite-node": "^2.1.4",
"web-ext-run": "^0.2.1",
"webextension-polyfill": "^0.12.0"
@@ -546,6 +546,25 @@ describe('Content Script UIs', () => {
] as const)(
'built-in UI type: $name',
({ name, createUiFunction, uiSelector }) => {
it('should mount if an anchor already exists at the initialization', async () => {
const onMount = vi.fn(appendTestApp);
ui = await createUiFunction(ctx, {
position: 'inline',
onMount,
anchor: `#parent > #${DYNAMIC_CHILD_ID}`,
page: name === 'iframe' ? '/page.html' : undefined,
name: 'test-component',
});
appendTestElement({ id: DYNAMIC_CHILD_ID });
ui.autoMount();
await runMicrotasks();
await expect
.poll(() => document.querySelector(uiSelector))
.not.toBeNull();
});
it('should mount when an anchor is dynamically added and unmount when an anchor is removed', async () => {
const onMount = vi.fn(appendTestApp);
const onRemove = vi.fn();
@@ -355,6 +355,11 @@ function autoMountUi(
async function observeElement(selector: string | null | undefined) {
let isAnchorExist = !!getAnchor(options);
// Mount if anchor exists at initialization.
if (isAnchorExist) {
uiCallbacks.mount();
}
while (!abortController.signal.aborted) {
try {
const changedAnchor = await waitElement(selector ?? 'body', {
@@ -372,6 +372,9 @@ export async function createViteBuilder(
},
},
watcher: viteServer.watcher,
on(event, cb) {
viteServer.httpServer?.on(event, cb);
},
};
return server;
+2
View File
@@ -171,6 +171,8 @@ async function createServerInternal(): Promise<WxtDevServer> {
await runner.openBrowser();
};
builderServer.on?.('close', () => keyboardShortcuts.stop());
return server;
}
+2 -5
View File
@@ -11,7 +11,7 @@ export async function initialize(options: {
template: string;
packageManager: string;
}) {
consola.info('Initalizing new project');
consola.info('Initializing new project');
const templates = await listTemplates();
const defaultTemplate = templates.find(
@@ -44,10 +44,7 @@ export async function initialize(options: {
{ title: pc.red('npm'), value: 'npm' },
{ title: pc.yellow('pnpm'), value: 'pnpm' },
{ title: pc.cyan('yarn'), value: 'yarn' },
{
title: `${pc.magenta('bun')}${pc.gray(' (experimental)')}`,
value: 'bun',
},
{ title: pc.magenta('bun'), value: 'bun' },
],
},
],
+1 -1
View File
@@ -35,7 +35,7 @@ export function createWebExtRunner(): ExtensionRunner {
const wxtUserConfig = wxt.config.runnerConfig.config;
const userConfig = {
console: wxtUserConfig?.openConsole,
browserConsole: wxtUserConfig?.openConsole,
devtools: wxtUserConfig?.openDevtools,
startUrl: wxtUserConfig?.startUrls,
keepProfileChanges: wxtUserConfig?.keepProfileChanges,
@@ -72,9 +72,10 @@ describe('Validation Utils', () => {
expect(actual).toEqual(expected);
});
it("should return an error when content scripts don't have a matches", () => {
it('should return an error when "registration: manifest" content scripts don\'t have matches', () => {
const entrypoint = fakeContentScriptEntrypoint({
options: {
registration: 'manifest',
// @ts-expect-error
matches: null,
},
@@ -83,7 +84,8 @@ describe('Validation Utils', () => {
errors: [
{
type: 'error',
message: '`matches` is required',
message:
'`matches` is required for manifest registered content scripts',
value: null,
entrypoint,
},
@@ -96,5 +98,24 @@ describe('Validation Utils', () => {
expect(actual).toEqual(expected);
});
it('should allow "registration: runtime" content scripts to not have matches', () => {
const entrypoint = fakeContentScriptEntrypoint({
options: {
registration: 'runtime',
// @ts-expect-error
matches: null,
},
});
const expected = {
errors: [],
errorCount: 0,
warningCount: 0,
};
const actual = validateEntrypoints([entrypoint]);
expect(actual).toEqual(expected);
});
});
});
@@ -3,6 +3,7 @@ import {
BackgroundEntrypoint,
ContentScriptEntrypoint,
Entrypoint,
EntrypointInfo,
GenericEntrypoint,
OptionsEntrypoint,
PopupEntrypoint,
@@ -67,6 +68,8 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
return results;
}, []);
await wxt.hooks.callHook('entrypoints:found', wxt, entrypointInfos);
// Validation
preventNoEntrypoints(entrypointInfos);
preventDuplicateEntrypointNames(entrypointInfos);
@@ -137,6 +140,8 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
skipped: isEntrypointSkipped(entry),
}));
await wxt.hooks.callHook('entrypoints:resolved', wxt, entrypoints);
wxt.logger.debug('All entrypoints:', entrypoints);
const skippedEntrypointNames = entrypoints
.filter((item) => item.skipped)
@@ -151,17 +156,10 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
].join('\n'),
);
}
await wxt.hooks.callHook('entrypoints:resolved', wxt, entrypoints);
return entrypoints;
}
interface EntrypointInfo {
name: string;
inputPath: string;
type: Entrypoint['type'];
}
/** Returns a map of input paths to the file's options. */
async function importEntrypoints(infos: EntrypointInfo[]) {
const resMap: Record<string, Record<string, any> | undefined> = {};
+5 -2
View File
@@ -30,10 +30,13 @@ function validateContentScriptEntrypoint(
definition: ContentScriptEntrypoint,
): ValidationResult[] {
const errors = validateBaseEntrypoint(definition);
if (definition.options.matches == null) {
if (
definition.options.registration !== 'runtime' &&
definition.options.matches == null
) {
errors.push({
type: 'error',
message: '`matches` is required',
message: '`matches` is required for manifest registered content scripts',
value: definition.options.matches,
entrypoint: definition,
});
+14
View File
@@ -789,6 +789,13 @@ export type Entrypoint =
| OptionsEntrypoint
| SidepanelEntrypoint;
export interface EntrypointInfo {
name: string;
/** Absolute path to the entrypoint file. */
inputPath: string;
type: Entrypoint['type'];
}
export type EntrypointGroup = Entrypoint | Entrypoint[];
export type OnContentScriptStopped = (cb: () => void) => void;
@@ -1091,6 +1098,7 @@ export interface WxtBuilderServer {
* Chokidar file watcher instance.
*/
watcher: FSWatcher;
on?(event: string, callback: () => void): void;
}
export interface ServerInfo {
@@ -1182,6 +1190,12 @@ export interface WxtHooks {
wxt: Wxt,
manifest: Manifest.WebExtensionManifest,
) => HookResult;
/**
* Called once the names and paths of all entrypoints have been resolved.
* @param wxt The configured WXT object
* @param infos List of entrypoints found in the project's `entrypoints` directory
*/
'entrypoints:found': (wxt: Wxt, infos: EntrypointInfo[]) => HookResult;
/**
* Called once all entrypoints have been loaded from the `entrypointsDir`.
* Use `wxt.builder.importEntrypoint` to load entrypoint options from the
+3 -3
View File
@@ -457,7 +457,7 @@ importers:
specifier: ^3.13.1
version: 3.13.1(rollup@4.24.0)(webpack-sources@3.2.3)
vite:
specifier: ^5.0.0 || ^6.0.0
specifier: ^5.0.0 || <=6.0.8
version: 5.4.11(@types/node@20.17.6)(sass@1.80.7)
vite-node:
specifier: ^2.1.4
@@ -7977,7 +7977,7 @@ snapshots:
is-reference@1.2.1:
dependencies:
'@types/estree': 1.0.5
'@types/estree': 1.0.6
is-reference@3.0.2:
dependencies:
@@ -8831,7 +8831,7 @@ snapshots:
dependencies:
nanoid: 3.3.7
picocolors: 1.1.1
source-map-js: 1.2.0
source-map-js: 1.2.1
postcss@8.4.47:
dependencies:
@@ -23,15 +23,6 @@ a:hover {
color: #535bf2;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;