Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba3d8838e3 | |||
| e9b6f5f97c | |||
| 5b6c82d38f | |||
| 5b41b70abe | |||
| 93f624610f | |||
| 73bdd49471 | |||
| 635f3f0898 | |||
| a53ee6d590 | |||
| 665b9194cf | |||
| 4690418c94 | |||
| 20eea2a7d1 | |||
| ac7b6760f2 | |||
| a593cfbbb6 | |||
| a205f2a0a8 | |||
| 8e2badc92b | |||
| 7c18d9c0b8 | |||
| 55a1f39450 | |||
| d708eca78e | |||
| 5d7e2516b6 | |||
| fb6a29872b | |||
| 187206062e | |||
| 2615e29e52 | |||
| 8ca176d37e | |||
| cb2bf43f30 | |||
| 1719d17cff | |||
| 32afb8d97c | |||
| 72b8a29d76 | |||
| af0a32d478 | |||
| 060e85f90e | |||
| 654a54ac79 | |||
| ef24358942 | |||
| e5e67e39bd | |||
| 228304bef9 | |||
| 78f97f32a2 | |||
| 459662a6c1 | |||
| b150a52471 | |||
| 028c601515 | |||
| af382ef065 | |||
| 91ef7f2ab5 | |||
| 0bf7279c8f | |||
| 62525d33c6 | |||
| 88ff40e301 | |||
| 1a72a0cdc6 | |||
| 2b07b4bc12 | |||
| 06c09e7c87 | |||
| 30b96ac87c | |||
| 2e9b5cffe8 | |||
| 3e1641a6ec | |||
| d64ca52bb0 | |||
| 8c645fa1ce | |||
| 79c9c89923 | |||
| 11d93fe724 | |||
| 039c561a80 | |||
| 64a704fc72 | |||
| 4fbf137d15 | |||
| ff065ab88b | |||
| 7fdf3fb968 | |||
| 7ebd451409 | |||
| a7a48a8a75 | |||
| 27298b7f22 | |||
| 565ce93a9a | |||
| 0bc6088b6a | |||
| 2fb99e8fbb | |||
| cd2050c534 | |||
| 5b557d0224 | |||
| 0137d79ba0 | |||
| e05fbf4fb5 | |||
| 72e6ca5063 | |||
| af9e842fe0 | |||
| 230962686f | |||
| 413cd9354f | |||
| 64a42de31f | |||
| f7f03e2669 | |||
| 1dc7c34bfd | |||
| 0fd5c3846c | |||
| e42e92f9df | |||
| e45c91f941 | |||
| 8f71d80f94 | |||
| 78e896e5fb | |||
| a643852387 | |||
| e221252325 | |||
| 88f126e658 | |||
| 17af05acc8 | |||
| fd436a0d63 | |||
| 0d796b63c2 |
+2
-14
@@ -1,15 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository#about-funding-files
|
||||
|
||||
github: [wxt-dev] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
github: wxt-dev
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
### Overview
|
||||
|
||||
<!-- Describe your changes and why you made them -->
|
||||
|
||||
### Manual Testing
|
||||
|
||||
<!-- Describe how to test your changes to make sure the PR works as intended -->
|
||||
|
||||
### Related Issue
|
||||
|
||||
<!-- If this PR is related to an issue, please link it here -->
|
||||
|
||||
This PR closes #<issue_number>
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
aaronklinker1@gmail.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -145,3 +145,24 @@ pnpm dlx taze -r
|
||||
```
|
||||
|
||||
Configuration is in [`taze.config.ts`](./taze.config.ts).
|
||||
|
||||
## Install Unreleased Versions
|
||||
|
||||
This repo uses https://pkg.pr.new to publish versions of all it's packages for almost every commit. You can install them via:
|
||||
|
||||
```sh
|
||||
npm i https://pkg.pr.new/[package-name]@[ref]
|
||||
```
|
||||
|
||||
Or use one of the shorthands:
|
||||
|
||||
```sh
|
||||
# Install the latest build of `wxt` from a PR:
|
||||
npm i https://pkg.pr.new/wxt@1283
|
||||
|
||||
# Install the latest build of `@wxt-dev/module-react` on the `main` branch
|
||||
npm i https://pkg.pr.new/@wxt-dev/module-react@main
|
||||
|
||||
# Install `@wxt-dev/storage` from a specific commit:
|
||||
npm i https://pkg.pr.new/@wxt-dev/module-react@426f907
|
||||
```
|
||||
|
||||
@@ -75,6 +75,12 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
|
||||
- 📏 Bundle analysis
|
||||
- ⬇️ Download and bundle remote URL imports
|
||||
|
||||
## Sponsors
|
||||
|
||||
WXT is a [MIT-licensed](https://github.com/wxt-dev/wxt/blob/main/LICENSE) open source project with its ongoing development made possible entirely by the support of these awesome backers. If you'd like to join them, please consider [sponsoring WXT's development](https://github.com/sponsors/wxt-dev).
|
||||
|
||||
<a href="https://github.com/sponsors/wxt-dev"><img alt="WXT Sponsors" src="https://raw.githubusercontent.com/wxt-dev/static/refs/heads/main/sponsorkit/sponsors.svg"></a>
|
||||
|
||||
## Contributors
|
||||
|
||||
<!-- automd:contributors author="aklinker1" license="MIT" github="wxt-dev/wxt" -->
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# Security Policy
|
||||
|
||||
While WXT is in prerelease, only the latest version will recieve security updates. The latest version is:
|
||||
|
||||
<img alt="npm version" src="https://img.shields.io/npm/v/wxt?labelColor=black&color=%234fa048">
|
||||
|
||||
<!--
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
|
||||
Tell them where to go, how often they can expect to get an update on a
|
||||
reported vulnerability, what to expect if the vulnerability is accepted or
|
||||
declined, etc.
|
||||
-->
|
||||
@@ -4,11 +4,8 @@ import useListExtensionDetails, {
|
||||
ChromeExtension,
|
||||
} from '../composables/useListExtensionDetails';
|
||||
|
||||
// Add extension IDs here. Order doesn't matter, will be sorted by weekly active users
|
||||
// During the transition from chrome.google.com/webstore to
|
||||
// chromewebstore.google.com, queue.wxt.dev might return null for your
|
||||
// extension. If it does, use "<slug>/<id>" instead of just the ID. The slug
|
||||
// can be retrieved from the URL of the item on chromewebstore.google.com
|
||||
// Add extension IDs here. Order doesn't matter, will be sorted by a combination of weekly active users and rating.
|
||||
// Change the commit message or PR title to: "docs: Added "[extension name]" to the homepage"
|
||||
const chromeExtensionIds = [
|
||||
'ocfdgncpifmegplaglcnglhioflaimkd', // GitHub: Better Line Counts
|
||||
'mgmdkjcljneegjfajchedjpdhbadklcf', // Anime Skip Player
|
||||
@@ -16,7 +13,7 @@ const chromeExtensionIds = [
|
||||
'elfaihghhjjoknimpccccmkioofjjfkf', // StayFree - Website Blocker & Web Analytics
|
||||
'okifoaikfmpfcamplcfjkpdnhfodpkil', // Doozy: Ai Made Easy
|
||||
'lknmjhcajhfbbglglccadlfdjbaiifig', // tl;dv - Record, Transcribe & ChatGPT for Google Meet
|
||||
'youtube中文配音/oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
'oglffgiaiekgeicdgkdlnlkhliajdlja', // Youtube中文配音
|
||||
'agjnjboanicjcpenljmaaigopkgdnihi', // PreMiD
|
||||
'aiakblgmlabokilgljkglggnpflljdgp', // Markdown Sticky Notes
|
||||
'nomnkbngkijpffepcgbbofhcnafpkiep', // DocVersionRedirector
|
||||
@@ -59,6 +56,14 @@ const chromeExtensionIds = [
|
||||
'macmkmchfoclhpbncclinhjflmdkaoom', // Wandpen - Instantly improve your writing with AI
|
||||
'lhmgechokhmdekdpgkkemoeecelcaonm', // YouTube Hider - Remove Comments By Keywords, Usernames & Tools
|
||||
'imgheieooppmahcgniieddodaliodeeg', // QA Compass - Record standardized bug reports easily
|
||||
'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);
|
||||
|
||||
@@ -62,6 +62,16 @@ export default defineConfig({
|
||||
alt: 'WXT logo',
|
||||
},
|
||||
|
||||
footer: {
|
||||
message: [
|
||||
'<a class="light-netlify" href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-bg.svg" alt="Deploys by Netlify" style="display: inline;" /></a>',
|
||||
'<a class="dark-netlify" href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-accent.svg" alt="Deploys by Netlify" style="display: inline;" /></a>',
|
||||
'Released under the <a href="https://github.com/wxt-dev/wxt/blob/main/LICENSE">MIT License</a>.',
|
||||
].join('<br/>'),
|
||||
copyright:
|
||||
'Copyright © 2023-present <a href="https://github.com/aklinker1">Aaron Klinker</a>',
|
||||
},
|
||||
|
||||
editLink: {
|
||||
pattern: 'https://github.com/wxt-dev/wxt/edit/main/docs/:path',
|
||||
},
|
||||
|
||||
@@ -1,72 +1,28 @@
|
||||
/* Colors */
|
||||
:root {
|
||||
--wxt-c-green: #53bc4a;
|
||||
--wxt-c-green-1: #0b8a00;
|
||||
--wxt-c-green-2: #096600;
|
||||
--wxt-c-green-3: #096600;
|
||||
--wxt-c-green-soft: rgba(11, 138, 0, 0.14);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--wxt-c-green-1: #67d45e;
|
||||
--wxt-c-green-2: #4fa048;
|
||||
--wxt-c-green-3: #447e3f;
|
||||
--wxt-c-green-2: #329929;
|
||||
--wxt-c-green-3: #21651b;
|
||||
--wxt-c-green-soft: rgba(103, 212, 94, 0.14);
|
||||
}
|
||||
|
||||
/* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css */
|
||||
|
||||
:root {
|
||||
--vp-c-brand: var(--wxt-c-green);
|
||||
--vp-c-brand-1: var(--wxt-c-green-1);
|
||||
--vp-c-brand-2: var(--wxt-c-green-2);
|
||||
--vp-c-brand-3: var(--wxt-c-green-3);
|
||||
|
||||
--vp-button-brand-bg: var(--wxt-c-green);
|
||||
--vp-button-brand-hover-bg: var(--wxt-c-green-2);
|
||||
--vp-button-brand-active-bg: var(--wxt-c-green-3);
|
||||
|
||||
--vp-code-link-color: var(--wxt-c-green);
|
||||
|
||||
/* --vp-c-text-1: var(--wxt-c-green-1); */
|
||||
|
||||
--vp-button-brand-text: var(--vp-c-black);
|
||||
--vp-button-brand-hover-text: var(--vp-c-black);
|
||||
--vp-button-brand-active-text: var(--vp-c-black);
|
||||
|
||||
--vp-custom-block-tip-border: var(--wxt-c-green);
|
||||
--vp-custom-block-tip-text: var(--wxt-c-green);
|
||||
|
||||
/* --vp-code-block-bg: #222422;
|
||||
--vp-code-copy-code-bg: #313431;
|
||||
--vp-code-copy-code-hover-bg: #3c403c; */
|
||||
|
||||
--vp-custom-block-tip-bg: var(--vp-code-block-bg);
|
||||
--vp-custom-block-info-bg: var(--vp-code-block-bg);
|
||||
|
||||
--vp-code-color: #476582;
|
||||
--vp-c-brand-soft: var(--wxt-c-green-soft);
|
||||
}
|
||||
|
||||
.vp-doc a {
|
||||
color: var(--wxt-c-green);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-c-bg: #131413;
|
||||
|
||||
--vp-c-bg-soft: #1a1b1a;
|
||||
--vp-c-bg-soft-up: #1f201f;
|
||||
--vp-c-bg-soft-down: #262926;
|
||||
--vp-c-bg-soft-mute: #242424;
|
||||
|
||||
--vp-c-bg-alt: #171817;
|
||||
|
||||
--vp-c-default: #313136;
|
||||
--vp-c-default-1: #3a3a3c;
|
||||
--vp-c-default-2: #505053;
|
||||
--vp-c-default-3: #2c2c30;
|
||||
--vp-c-default-soft: #252529;
|
||||
|
||||
--vp-code-block-bg: #191a19;
|
||||
--vp-code-copy-code-bg: #212321;
|
||||
--vp-code-copy-code-hover-bg: #292d29;
|
||||
|
||||
--vp-custom-block-info-bg: #191a19;
|
||||
|
||||
--vp-code-color: #c9def1;
|
||||
}
|
||||
/* Customize Individual Components */
|
||||
|
||||
.vp-doc .no-vertical-dividers th,
|
||||
.vp-doc .no-vertical-dividers td {
|
||||
@@ -97,3 +53,16 @@ body {
|
||||
vertical-align: middle;
|
||||
background-color: var(--vp-c-default-2);
|
||||
}
|
||||
|
||||
.light-netlify {
|
||||
display: inline;
|
||||
}
|
||||
.dark .light-netlify {
|
||||
display: none;
|
||||
}
|
||||
.dark-netlify {
|
||||
display: none;
|
||||
}
|
||||
.dark .dark-netlify {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 132 KiB |
@@ -77,6 +77,84 @@ export default defineContentScript({
|
||||
|
||||
## WASM
|
||||
|
||||
:::warning 🚧 Under construction
|
||||
These docs will be coming soon!
|
||||
:::
|
||||
How a `.wasm` file is loaded varies greatly between packages, but most follow a basic setup: Use a JS API to load and execute the `.wasm` file.
|
||||
|
||||
For an extension, that means two things:
|
||||
|
||||
1. The `.wasm` file needs to be present in output folder so it can be loaded.
|
||||
2. You must import the JS API to load and initialize the `.wasm` file, usually provided by the NPM package.
|
||||
|
||||
For an example, let's say you have a content script needs to parse TS code into AST. We'll use [`@oxc-parser/wasm`](https://www.npmjs.com/package/@oxc-parser/wasm) to do it!
|
||||
|
||||
First, we need to copy the `.wasm` file to the output directory. We'll do it with a [WXT module](/guide/essentials/wxt-modules):
|
||||
|
||||
```ts
|
||||
// modules/oxc-parser-wasm.ts
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
export default defineWxtModule((wxt) => {
|
||||
wxt.hook('build:publicAssets', (_, assets) => {
|
||||
assets.push({
|
||||
absoluteSrc: resolve(
|
||||
'node_modules/@oxc-parser/wasm/web/oxc_parser_wasm_bg.wasm',
|
||||
),
|
||||
relativeDest: 'oxc_parser_wasm_bg.wasm',
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Run `wxt build`, and you should see the WASM file copied into your `.output/chrome-mv3` folder!
|
||||
|
||||
Next, since this is in a content script and we'll be fetching the WASM file over the network to load it, we need to add the file to the `web_accessible_resources`:
|
||||
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
manifest: {
|
||||
web_accessible_resources: [
|
||||
{
|
||||
// We'll use this matches in the cotent script as well
|
||||
matches: ['*://*.github.com/*'],
|
||||
// Use the same path as `relativeDest` from the WXT module
|
||||
resources: ['/oxc_parser_wasm_bg.wasm'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
And finally, we need to load and initialize the `.wasm` file inside the content script to use it:
|
||||
|
||||
```ts
|
||||
// entrypoints/content.ts
|
||||
import initWasm, { parseSync } from '@oxc-parser/wasm';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: '*://*.github.com/*',
|
||||
async main(ctx) {
|
||||
if (!location.pathname.endsWith('.ts')) return;
|
||||
|
||||
// Get text from GitHub
|
||||
const code = document.getElementById(
|
||||
'read-only-cursor-text-area',
|
||||
)?.textContent;
|
||||
if (!code) return;
|
||||
const sourceFilename = document.getElementById('file-name-id')?.textContent;
|
||||
if (!sourceFilename) return;
|
||||
|
||||
// Load the WASM file:
|
||||
await initWasm({
|
||||
module_or_path: browser.runtime.getURL('/oxc_parser_wasm_bg.wasm'),
|
||||
});
|
||||
|
||||
// Once loaded, we can use `parseSync`!
|
||||
const ast = parseSync(code, { sourceFilename });
|
||||
console.log(ast);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This code is taken directly from `@oxc-parser/wasm` docs with one exception: We manually pass in a file path. In a standard NodeJS or web project, the default path works just fine so you don't have to pass anything in. However, extensions are different. You should always explicitly pass in the full URL to the WASM file in your output directory, which is what `browser.runtime.getURL` returns.
|
||||
|
||||
Run your extension, and you should see OXC parse the TS file!
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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.
|
||||
@@ -576,6 +592,39 @@ For MV3, `injectScript` is synchronous and the injected script will be evaluated
|
||||
However for MV2, `injectScript` has to `fetch` the script's text content and create an inline `<script>` block. This means for MV2, your script is injected asynchronously and it will not be evaluated at the same time as your content script's `run_at`.
|
||||
:::
|
||||
|
||||
## Mounting UI to dynamic element
|
||||
|
||||
In many cases, you may need to mount a UI to a DOM element that does not exist at the time the web page is initially loaded. To handle this, use the `autoMount` API to automatically mount the UI when the target element appears dynamically and unmount it when the element disappears. In WXT, the `anchor` option is used to target the element, enabling automatic mounting and unmounting based on its appearance and removal.
|
||||
|
||||
```ts
|
||||
export default defineContentScript({
|
||||
matches: ['<all_urls>'],
|
||||
|
||||
main(ctx) {
|
||||
const ui = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
// It observes the anchor
|
||||
anchor: '#your-target-dynamic-element',
|
||||
onMount: (container) => {
|
||||
// Append children to the container
|
||||
const app = document.createElement('p');
|
||||
app.textContent = '...';
|
||||
container.append(app);
|
||||
},
|
||||
});
|
||||
|
||||
// Call autoMount to observe anchor element for add/remove.
|
||||
ui.autoMount();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
:::tip
|
||||
When the `ui.remove` is called, `autoMount` also stops.
|
||||
:::
|
||||
|
||||
See the [API Reference](/api/reference/wxt/client/interfaces/ContentScriptUi.html#automount) for the complete list of options.
|
||||
|
||||
## Dealing with SPAs
|
||||
|
||||
It is difficult to write content scripts for SPAs (single page applications) and websites using HTML5 history mode for navigation because content scripts are only ran on full page reloads. SPAs and websites that take advantage of HTML5 history mode **_do not perform a full reload when changing paths_**, and thus your content script isn't going to be ran when you expect it to be.
|
||||
|
||||
@@ -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', ...]" />
|
||||
|
||||
@@ -20,7 +20,7 @@ In your background script, set `type: "module"`:
|
||||
|
||||
```ts
|
||||
export default defineBackground({
|
||||
type: 'module', // !code ++
|
||||
type: 'module', // [!code ++]
|
||||
main() {
|
||||
// ...
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -129,7 +129,7 @@ wxt zip
|
||||
|
||||
Firefox requires you to upload a ZIP of your source code. This allows them to rebuild your extension and review the code in a readable way. More details can be found in [Firefox's docs](https://extensionworkshop.com/documentation/publish/source-code-submission/).
|
||||
|
||||
When running `wxt zip -b firefox`, WXT will zip both your extension and sources. Certain files (such as config files, hidden files, and tests) are automatically excluded from your sources. However, it's important to manually check the ZIP to ensure it only contains the files necessary to rebuild your extension.
|
||||
When running `wxt zip -b firefox`, WXT will zip both your extension and sources. Certain files (such as config files, hidden files, tests, and excluded entrypoints) are automatically excluded from your sources. However, it's important to manually check the ZIP to ensure it only contains the files necessary to rebuild your extension.
|
||||
|
||||
To customize which files are zipped, add the `zip` option to your config 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
@@ -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.
|
||||
|
||||
@@ -109,27 +109,6 @@ You have two options:
|
||||
```
|
||||
2. Rename your `modules` directory to something else.
|
||||
|
||||
## v0.18.0 → v0.18.5
|
||||
|
||||
> When this version was released, it was not considered a breaking change... but it should have been.
|
||||
|
||||
### New `modules/` Directory
|
||||
|
||||
WXT now recognizes the `modules/` directory as a folder containing [WXT modules](/guide/essentials/wxt-modules).
|
||||
|
||||
If you already have `<srcDir>/modules` or `<srcDir>/Modules` directory, `wxt prepare` and other commands will fail.
|
||||
|
||||
You have two options:
|
||||
|
||||
1. [Recommended] Keep your files where they are and tell WXT to look in a different folder:
|
||||
```ts
|
||||
// wxt.config.ts
|
||||
export default defineConfig({
|
||||
modulesDir: 'wxt-modules', // defaults to "modules"
|
||||
});
|
||||
```
|
||||
2. Rename your `modules` directory to something else.
|
||||
|
||||
## v0.17.0 → v0.18.0
|
||||
|
||||
### Automatic MV3 `host_permissions` to MV2 `permissions`
|
||||
|
||||
@@ -73,6 +73,12 @@ features:
|
||||
linkText: Read docs
|
||||
---
|
||||
|
||||
## Sponsors
|
||||
|
||||
WXT is a [MIT-licensed](https://github.com/wxt-dev/wxt/blob/main/LICENSE) open source project with its ongoing development made possible entirely by the support of these awesome backers. If you'd like to join them, please consider [sponsoring WXT's development](https://github.com/sponsors/wxt-dev).
|
||||
|
||||
<a href="https://github.com/sponsors/wxt-dev"><img alt="WXT Sponsors" src="https://raw.githubusercontent.com/wxt-dev/static/refs/heads/main/sponsorkit/sponsors-wide.svg"></a>
|
||||
|
||||
## Put <span style="color: var(--vp-c-brand-1)">Developer Experience</span> First
|
||||
|
||||
WXT simplifies the web extension development process by providing tools for zipping and publishing, the best-in-class dev mode, an opinionated project structure, and more. Iterate faster, develop features not build scripts, and use everything the JS ecosystem has to offer.
|
||||
|
||||
@@ -20,7 +20,7 @@ Set Framerate 50
|
||||
# Terminal theme with WXT brand colors (which was taken from the website)
|
||||
# Based on the standard charmbracelet/vhs theme:
|
||||
# https://github.com/charmbracelet/vhs/blob/88e634f4a10bbe305b6aea9a12b4d8dc3dd7f31c/style.go#L7-L28
|
||||
Set Theme {"background": "#1a1b1a", "foreground": "#dddddd", "black": "#282a2e", "brightBlack": "#4d4d4d", "red": "#D74E6F", "brightRed": "#FE5F86", "green": "#67d45e", "brightGreen": "#67d45e", "yellow": "#D3E561", "brightYellow": "#EBFF71", "blue": "#8056FF", "brightBlue": "#9B79FF", "magenta": "#ED61D7", "brightMagenta": "#FF7AEA", "cyan": "#04D7D7", "brightCyan": "#00FEFE", "white": "#bfbfbf", "brightWhite": "#e6e6e6", "indigo": "#5B56E0"}
|
||||
Set Theme {"background": "#161618", "foreground": "#dddddd", "black": "#282a2e", "brightBlack": "#4d4d4d", "red": "#D74E6F", "brightRed": "#FE5F86", "green": "#67d45e", "brightGreen": "#67d45e", "yellow": "#D3E561", "brightYellow": "#EBFF71", "blue": "#8056FF", "brightBlue": "#9B79FF", "magenta": "#ED61D7", "brightMagenta": "#FF7AEA", "cyan": "#04D7D7", "brightCyan": "#00FEFE", "white": "#bfbfbf", "brightWhite": "#e6e6e6", "indigo": "#5B56E0"}
|
||||
Set FontSize 32
|
||||
|
||||
# Terminal settings
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## v1.1.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.2...module-react-v1.1.3)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Use react 19 in `@wxt-dev/module-react` and `templates/react` ([#1285](https://github.com/wxt-dev/wxt/pull/1285))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v1.1.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.1...module-react-v1.1.2)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"version": "1.1.2",
|
||||
"version": "1.1.3",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -47,15 +47,15 @@
|
||||
"wxt": ">=0.19.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "^4.3.3"
|
||||
"@vitejs/plugin-react": "^4.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"publint": "^0.2.12",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"typescript": "^5.6.3",
|
||||
"unbuild": "^2.0.0",
|
||||
"wxt": "workspace:*"
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## v2.0.3
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.2...module-svelte-v2.0.3)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Upgrade `vite-plugin-svelte` to support Vite 6 ([#1375](https://github.com/wxt-dev/wxt/pull/1375))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Eli ([@lishaduck](http://github.com/lishaduck))
|
||||
|
||||
## v2.0.2
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.1...module-svelte-v2.0.2)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Svelte production errors and warnings on resolve conditions ([#1283](https://github.com/wxt-dev/wxt/pull/1283))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v2.0.1
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-svelte-v2.0.0...module-svelte-v2.0.1)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"email": "aaronklinker1+wxt@gmail.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.3",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
@@ -46,7 +46,7 @@
|
||||
"svelte": ">=5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0"
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/check": "^1.4.5",
|
||||
|
||||
@@ -22,7 +22,7 @@ export default defineWxtModule<SvelteModuleOptions>({
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
conditions: mode === 'development' ? ['browser'] : [],
|
||||
conditions: ['browser', mode],
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -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,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 } });
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@wxt-dev/i18n": "workspace:*",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chrome": "^0.0.280",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@wxt-dev/auto-icons": "workspace:*",
|
||||
"@wxt-dev/unocss": "workspace:*",
|
||||
"sass": "^1.80.7",
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'uno.css';
|
||||
import './style.css';
|
||||
|
||||
export default defineContentScript({
|
||||
matches: ['https://*.duckduckgo.com/*'],
|
||||
cssInjectionMode: 'manifest',
|
||||
|
||||
async main(ctx) {
|
||||
const dynamicUI = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
append: 'after',
|
||||
anchor: 'form[role=search]',
|
||||
onMount: (container) => {
|
||||
const app = document.createElement('div');
|
||||
container.id = 'automount-anchor';
|
||||
app.classList.add('m-4', 'text-center', 'text-red-500');
|
||||
app.textContent = i18n.t('prompt_for_name');
|
||||
container.append(app);
|
||||
return { container, app };
|
||||
},
|
||||
onRemove() {
|
||||
console.log('dynamicUI removed');
|
||||
},
|
||||
});
|
||||
|
||||
const autoMountUi = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
append: 'after',
|
||||
anchor: '#automount-anchor',
|
||||
onMount: (container) => {
|
||||
const app = document.createElement('div');
|
||||
app.id = 'automount-ui';
|
||||
app.classList.add('m-0', 'text-center', 'text-blue-500');
|
||||
app.textContent = `Hello, I'm automount UI.`;
|
||||
container.append(app);
|
||||
},
|
||||
onRemove() {
|
||||
console.log('autoMountUi removed');
|
||||
},
|
||||
});
|
||||
|
||||
autoMountUi.autoMount({
|
||||
onStop: () => {
|
||||
console.log('Auto mount stopped.');
|
||||
},
|
||||
});
|
||||
|
||||
const stopAutoMountButton = createIntegratedUi(ctx, {
|
||||
position: 'inline',
|
||||
append: 'last',
|
||||
anchor: 'form[role=search]',
|
||||
onMount: (container) => {
|
||||
const app = document.createElement('button');
|
||||
container.classList.add('flex', 'flex-justify-center');
|
||||
app.classList.add('mt-4', 'p-2');
|
||||
app.textContent = 'Stop auto-mount';
|
||||
app.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
autoMountUi.remove();
|
||||
};
|
||||
container.append(app);
|
||||
return { container, app };
|
||||
},
|
||||
});
|
||||
|
||||
stopAutoMountButton.mount();
|
||||
|
||||
setInterval(() => {
|
||||
if (dynamicUI.mounted) {
|
||||
dynamicUI.remove();
|
||||
} else {
|
||||
dynamicUI.mount();
|
||||
}
|
||||
}, 2000);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
}
|
||||
@@ -1,5 +1,136 @@
|
||||
# Changelog
|
||||
|
||||
## v0.19.26
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.25...wxt-v0.19.26)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- **context:** Deduplicate `wxt:content-script-started` ([#1364](https://github.com/wxt-dev/wxt/pull/1364))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Deniz Uğur ([@DenizUgur](http://github.com/DenizUgur))
|
||||
|
||||
## 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)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Exclude entire `import.meta.env` object from content script output ([#1267](https://github.com/wxt-dev/wxt/pull/1267))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v0.19.21
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.20...wxt-v0.19.21)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Exclude skipped entrypoints from manifest ([#1265](https://github.com/wxt-dev/wxt/pull/1265))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v0.19.20
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.19...wxt-v0.19.20)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- `autoMount` content script UIs ([#1210](https://github.com/wxt-dev/wxt/pull/1210))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Only show reopen browser shortcut when it can be done ([#1263](https://github.com/wxt-dev/wxt/pull/1263))
|
||||
- Import entrypoint improvements ([#1207](https://github.com/wxt-dev/wxt/pull/1207))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
- 1natsu ([@1natsu172](http://github.com/1natsu172))
|
||||
|
||||
## v0.19.19
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.18...wxt-v0.19.19)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Keyboard shortcut to reopen the browser without restarting the dev command ([#1211](https://github.com/wxt-dev/wxt/pull/1211))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Prevent changing dev server port when reloading config ([#1241](https://github.com/wxt-dev/wxt/pull/1241))
|
||||
- Ensure content scripts are registered immediately in dev mode ([#1253](https://github.com/wxt-dev/wxt/pull/1253))
|
||||
- Exclude skipped entrypoints from Firefox sources zip ([#1238](https://github.com/wxt-dev/wxt/pull/1238))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Nishu ([@nishu-murmu](http://github.com/nishu-murmu))
|
||||
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||
|
||||
## v0.19.18
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.17...wxt-v0.19.18)
|
||||
|
||||
@@ -76,6 +76,12 @@ Or see the [installation guide](https://wxt.dev/guide/installation.html) to get
|
||||
- 📏 Bundle analysis
|
||||
- ⬇️ Download and bundle remote URL imports
|
||||
|
||||
## Sponsors
|
||||
|
||||
WXT is a [MIT-licensed](https://github.com/wxt-dev/wxt/blob/main/LICENSE) open source project with its ongoing development made possible entirely by the support of these awesome backers. If you'd like to join them, please consider [sponsoring WXT's development](https://github.com/sponsors/wxt-dev).
|
||||
|
||||
<a href="https://github.com/sponsors/wxt-dev"><img alt="WXT Sponsors" src="https://raw.githubusercontent.com/wxt-dev/static/refs/heads/main/sponsorkit/sponsors.svg"></a>
|
||||
|
||||
## Contributors
|
||||
|
||||
<!-- automd:contributors author="aklinker1" license="MIT" github="wxt-dev/wxt" -->
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
|
||||
describe('Dev Mode', () => {
|
||||
it('should not change ports when restarting the server', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile(
|
||||
'entrypoints/background.ts',
|
||||
'export default defineBackground(() => {})',
|
||||
);
|
||||
|
||||
const server = await project.startServer({
|
||||
runner: {
|
||||
disabled: true,
|
||||
},
|
||||
});
|
||||
const initialPort = server.port;
|
||||
await server.restart();
|
||||
const finalPort = server.port;
|
||||
await server.stop();
|
||||
|
||||
expect(finalPort).toBe(initialPort);
|
||||
});
|
||||
});
|
||||
@@ -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,8 +157,9 @@ describe('Hooks', () => {
|
||||
'build:done': true,
|
||||
'build:publicAssets': true,
|
||||
'build:manifestGenerated': true,
|
||||
'entrypoints:found': 2,
|
||||
'entrypoints:grouped': true,
|
||||
'entrypoints:resolved': true,
|
||||
'entrypoints:resolved': 2,
|
||||
'vite:build:extendConfig': 1,
|
||||
'vite:devServer:extendConfig': false,
|
||||
'zip:start': true,
|
||||
@@ -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,
|
||||
|
||||
@@ -181,6 +181,42 @@ describe('Zipping', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should exclude skipped entrypoints from respective browser sources zip', async () => {
|
||||
const project = new TestProject({
|
||||
name: 'test',
|
||||
version: '1.0.0',
|
||||
});
|
||||
project.addFile(
|
||||
'entrypoints/not-firefox.content.ts',
|
||||
`export default defineContentScript({
|
||||
matches: ['*://*/*'],
|
||||
exclude: ['firefox'],
|
||||
main() {},
|
||||
});`,
|
||||
);
|
||||
project.addFile(
|
||||
'entrypoints/all.content.ts',
|
||||
`export default defineContentScript({
|
||||
matches: ['*://*/*'],
|
||||
main(ctx) {},
|
||||
});
|
||||
`,
|
||||
);
|
||||
const unzipDir = project.resolvePath('.output/test-1.0.0-sources');
|
||||
const sourcesZip = project.resolvePath('.output/test-1.0.0-sources.zip');
|
||||
|
||||
await project.zip({
|
||||
browser: 'firefox',
|
||||
});
|
||||
await extract(sourcesZip, { dir: unzipDir });
|
||||
expect(
|
||||
await project.fileExists(unzipDir, 'entrypoints/not-firefox.content.ts'),
|
||||
).toBe(false);
|
||||
expect(
|
||||
await project.fileExists(unzipDir, 'entrypoints/all.content.ts'),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it.each(['firefox', 'opera'])(
|
||||
'should create sources zip for "%s" browser when sourcesZip is undefined',
|
||||
async (browser) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.19.18",
|
||||
"version": "0.19.26",
|
||||
"description": "Next gen framework for developing web extensions",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -81,6 +81,7 @@
|
||||
"prepack": "pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@1natsu/wait-element": "^4.1.2",
|
||||
"@aklinker1/rollup-plugin-visualizer": "5.12.0",
|
||||
"@types/chrome": "^0.0.280",
|
||||
"@types/webextension-polyfill": "^0.12.1",
|
||||
@@ -122,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"
|
||||
|
||||
@@ -42,6 +42,7 @@ export class ContentScriptContext implements AbortController {
|
||||
private isTopFrame = window.self === window.top;
|
||||
private abortController: AbortController;
|
||||
private locationWatcher = createLocationWatcher(this);
|
||||
private receivedMessageIds = new Set<string>();
|
||||
|
||||
constructor(
|
||||
private readonly contentScriptName: string,
|
||||
@@ -233,19 +234,28 @@ export class ContentScriptContext implements AbortController {
|
||||
{
|
||||
type: ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE,
|
||||
contentScriptName: this.contentScriptName,
|
||||
messageId: Math.random().toString(36).slice(2),
|
||||
},
|
||||
'*',
|
||||
);
|
||||
}
|
||||
|
||||
verifyScriptStartedEvent(event: MessageEvent) {
|
||||
const isScriptStartedEvent =
|
||||
event.data?.type === ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE;
|
||||
const isSameContentScript =
|
||||
event.data?.contentScriptName === this.contentScriptName;
|
||||
const isNotDuplicate = !this.receivedMessageIds.has(event.data?.messageId);
|
||||
return isScriptStartedEvent && isSameContentScript && isNotDuplicate;
|
||||
}
|
||||
|
||||
listenForNewerScripts(options?: { ignoreFirstEvent?: boolean }) {
|
||||
let isFirst = true;
|
||||
|
||||
const cb = (event: MessageEvent) => {
|
||||
if (
|
||||
event.data?.type === ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE &&
|
||||
event.data?.contentScriptName === this.contentScriptName
|
||||
) {
|
||||
if (this.verifyScriptStartedEvent(event)) {
|
||||
this.receivedMessageIds.add(event.data.messageId);
|
||||
|
||||
const wasFirst = isFirst;
|
||||
isFirst = false;
|
||||
if (wasFirst && options?.ignoreFirstEvent) return;
|
||||
|
||||
@@ -15,12 +15,5 @@ export class WxtLocationChangeEvent extends Event {
|
||||
* Returns an event name unique to the extension and content script that's running.
|
||||
*/
|
||||
export function getUniqueEventName(eventName: string): string {
|
||||
// During the build process, import.meta.env is not defined when importing
|
||||
// entrypoints to get their metadata.
|
||||
const entrypointName =
|
||||
typeof import.meta.env === 'undefined'
|
||||
? 'build'
|
||||
: import.meta.env.ENTRYPOINT;
|
||||
|
||||
return `${browser?.runtime?.id}:${entrypointName}:${eventName}`;
|
||||
return `${browser?.runtime?.id}:${import.meta.env.ENTRYPOINT}:${eventName}`;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,43 @@
|
||||
/** @vitest-environment happy-dom */
|
||||
import { describe, it, beforeEach, vi, expect } from 'vitest';
|
||||
import { createIntegratedUi, createIframeUi, createShadowRootUi } from '..';
|
||||
import { describe, it, beforeEach, afterEach, vi, expect } from 'vitest';
|
||||
import {
|
||||
createIntegratedUi,
|
||||
createIframeUi,
|
||||
createShadowRootUi,
|
||||
ContentScriptUi,
|
||||
} from '..';
|
||||
import { ContentScriptContext } from '../../content-script-context';
|
||||
|
||||
/**
|
||||
* Util for floating promise.
|
||||
*/
|
||||
async function runMicrotasks() {
|
||||
return await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
|
||||
function appendTestApp(container: HTMLElement) {
|
||||
container.innerHTML = '<app>Hello world</app>';
|
||||
}
|
||||
|
||||
function appendTestElement({
|
||||
tagName = 'div',
|
||||
id,
|
||||
}: {
|
||||
tagName?: string;
|
||||
id: string;
|
||||
}) {
|
||||
const parent = document.querySelector('#parent');
|
||||
if (!parent) {
|
||||
throw Error(
|
||||
'Parent element not found. Please check the testing environment DOM',
|
||||
);
|
||||
}
|
||||
const element = document.createElement(tagName);
|
||||
element.id = id;
|
||||
parent.append(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
const fetch = vi.fn();
|
||||
|
||||
describe('Content Script UIs', () => {
|
||||
@@ -480,4 +511,242 @@ describe('Content Script UIs', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Need call runMicrotasks after floating-promise and append/remove dom
|
||||
*/
|
||||
describe('auto mount', () => {
|
||||
const DYNAMIC_CHILD_ID = 'dynamic-child';
|
||||
let ui: ContentScriptUi<any>;
|
||||
beforeEach(async () => {
|
||||
ui?.remove();
|
||||
await runMicrotasks();
|
||||
});
|
||||
afterEach(async () => {
|
||||
ui?.remove();
|
||||
await runMicrotasks();
|
||||
});
|
||||
|
||||
describe.each([
|
||||
{
|
||||
name: 'integrated',
|
||||
createUiFunction: createIntegratedUi,
|
||||
uiSelector: 'div[data-wxt-integrated]',
|
||||
},
|
||||
{
|
||||
name: 'iframe',
|
||||
createUiFunction: createIframeUi,
|
||||
uiSelector: 'div[data-wxt-iframe]',
|
||||
},
|
||||
{
|
||||
name: 'shadow-root',
|
||||
createUiFunction: createShadowRootUi,
|
||||
uiSelector: 'test-component[data-wxt-shadow-root]',
|
||||
},
|
||||
] 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();
|
||||
ui = await createUiFunction(ctx, {
|
||||
position: 'inline',
|
||||
onMount,
|
||||
onRemove,
|
||||
anchor: `#parent > #${DYNAMIC_CHILD_ID}`,
|
||||
page: name === 'iframe' ? '/page.html' : undefined,
|
||||
name: 'test-component',
|
||||
});
|
||||
let dynamicEl;
|
||||
ui.autoMount();
|
||||
await runMicrotasks();
|
||||
|
||||
for (let index = 0; index < 3; index++) {
|
||||
await expect
|
||||
.poll(() => document.querySelector(uiSelector))
|
||||
.toBeNull();
|
||||
|
||||
dynamicEl = appendTestElement({ id: DYNAMIC_CHILD_ID });
|
||||
await runMicrotasks();
|
||||
await expect
|
||||
.poll(() => document.querySelector(uiSelector))
|
||||
.not.toBeNull();
|
||||
|
||||
dynamicEl.remove();
|
||||
await runMicrotasks();
|
||||
}
|
||||
|
||||
expect(onMount).toHaveBeenCalledTimes(3);
|
||||
expect(onRemove).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
describe('options', () => {
|
||||
it('should auto-mount only once mount and remove when the `once` option is true', async () => {
|
||||
const onMount = vi.fn(appendTestApp);
|
||||
const onRemove = vi.fn();
|
||||
ui = await createUiFunction(ctx, {
|
||||
position: 'inline',
|
||||
onMount,
|
||||
onRemove,
|
||||
anchor: `#parent > #${DYNAMIC_CHILD_ID}`,
|
||||
page: name === 'iframe' ? '/page.html' : undefined,
|
||||
name: 'test-component',
|
||||
});
|
||||
let dynamicEl;
|
||||
ui.autoMount({ once: true });
|
||||
await runMicrotasks();
|
||||
await expect
|
||||
.poll(() => document.querySelector(uiSelector))
|
||||
.toBeNull();
|
||||
|
||||
dynamicEl = appendTestElement({ id: DYNAMIC_CHILD_ID });
|
||||
await runMicrotasks();
|
||||
await expect
|
||||
.poll(() => document.querySelector(uiSelector))
|
||||
.not.toBeNull();
|
||||
|
||||
dynamicEl.remove();
|
||||
await runMicrotasks();
|
||||
expect(onMount).toHaveBeenCalledTimes(1);
|
||||
expect(onRemove).toHaveBeenCalledTimes(1);
|
||||
|
||||
// re-append after once cycle
|
||||
dynamicEl = appendTestElement({ id: DYNAMIC_CHILD_ID });
|
||||
await runMicrotasks();
|
||||
|
||||
// expect stop automount
|
||||
await expect
|
||||
.poll(() => document.querySelector(uiSelector))
|
||||
.toBeNull();
|
||||
expect(onMount).toHaveBeenCalledTimes(1);
|
||||
expect(onRemove).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid anchors', () => {
|
||||
it('should throw when anchor is set as type Element', async () => {
|
||||
ui = await createUiFunction(ctx, {
|
||||
position: 'inline',
|
||||
onMount: appendTestApp,
|
||||
anchor: document.documentElement,
|
||||
page: name === 'iframe' ? '/page.html' : undefined,
|
||||
name: 'test-component',
|
||||
});
|
||||
expect(() => ui.autoMount()).toThrowError(
|
||||
'autoMount and Element anchor option cannot be combined. Avoid passing `Element` directly or `() => Element` to the anchor.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw when anchor is set as type `() => Element`', async () => {
|
||||
ui = await createUiFunction(ctx, {
|
||||
position: 'inline',
|
||||
onMount: appendTestApp,
|
||||
anchor: () => document.documentElement,
|
||||
page: name === 'iframe' ? '/page.html' : undefined,
|
||||
name: 'test-component',
|
||||
});
|
||||
expect(() => ui.autoMount()).toThrowError(
|
||||
'autoMount and Element anchor option cannot be combined. Avoid passing `Element` directly or `() => Element` to the anchor.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('StopAutoMount', () => {
|
||||
it('should stop auto-mounting and remove ui when `ui.remove` is called', async () => {
|
||||
const onMount = vi.fn(appendTestApp);
|
||||
const onRemove = vi.fn();
|
||||
ui = await createUiFunction(ctx, {
|
||||
position: 'inline',
|
||||
onMount,
|
||||
onRemove,
|
||||
anchor: `#parent > #${DYNAMIC_CHILD_ID}`,
|
||||
page: name === 'iframe' ? '/page.html' : undefined,
|
||||
name: 'test-component',
|
||||
});
|
||||
let dynamicEl;
|
||||
ui.autoMount();
|
||||
await runMicrotasks();
|
||||
|
||||
dynamicEl = appendTestElement({ id: DYNAMIC_CHILD_ID });
|
||||
await runMicrotasks();
|
||||
await expect
|
||||
.poll(() => document.querySelector(uiSelector))
|
||||
.not.toBeNull();
|
||||
|
||||
dynamicEl.remove();
|
||||
await runMicrotasks();
|
||||
expect(onMount).toHaveBeenCalledTimes(1);
|
||||
expect(onRemove).toHaveBeenCalledTimes(1);
|
||||
|
||||
ui.remove();
|
||||
|
||||
dynamicEl = appendTestElement({ id: DYNAMIC_CHILD_ID });
|
||||
dynamicEl.remove();
|
||||
await runMicrotasks();
|
||||
expect(onMount).toHaveBeenCalledTimes(1);
|
||||
expect(onRemove).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should call internal StopAutoMount when `ui.remove` is called', async () => {
|
||||
const onMount = vi.fn(appendTestApp);
|
||||
const onRemove = vi.fn();
|
||||
const onStop = vi.fn();
|
||||
ui = await createUiFunction(ctx, {
|
||||
position: 'inline',
|
||||
onMount,
|
||||
onRemove,
|
||||
anchor: `#parent > #${DYNAMIC_CHILD_ID}`,
|
||||
page: name === 'iframe' ? '/page.html' : undefined,
|
||||
name: 'test-component',
|
||||
});
|
||||
ui.autoMount({ onStop });
|
||||
ui.remove();
|
||||
expect(onStop).toHaveBeenCalledTimes(1);
|
||||
expect(onRemove).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should allow calling automount again after internal StopAutoMount is called', 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',
|
||||
});
|
||||
const onStop = vi.fn();
|
||||
ui.autoMount({ onStop });
|
||||
ui.autoMount({ onStop });
|
||||
|
||||
ui.remove();
|
||||
expect(onStop).toBeCalledTimes(1);
|
||||
|
||||
ui.autoMount({ onStop });
|
||||
|
||||
ui.remove();
|
||||
expect(onStop).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import { browser } from 'wxt/browser';
|
||||
import { waitElement } from '@1natsu/wait-element';
|
||||
import {
|
||||
isExist as mountDetector,
|
||||
isNotExist as removeDetector,
|
||||
} from '@1natsu/wait-element/detectors';
|
||||
import { ContentScriptContext } from '..';
|
||||
import {
|
||||
AutoMount,
|
||||
AutoMountOptions,
|
||||
BaseMountFunctions,
|
||||
ContentScriptAnchoredOptions,
|
||||
ContentScriptPositioningOptions,
|
||||
ContentScriptUiOptions,
|
||||
IframeContentScriptUi,
|
||||
IframeContentScriptUiOptions,
|
||||
IntegratedContentScriptUi,
|
||||
IntegratedContentScriptUiOptions,
|
||||
MountFunctions,
|
||||
ShadowRootContentScriptUi,
|
||||
ShadowRootContentScriptUiOptions,
|
||||
} from './types';
|
||||
@@ -39,6 +49,14 @@ export function createIntegratedUi<TMounted>(
|
||||
mounted = undefined;
|
||||
};
|
||||
|
||||
const mountFunctions = createMountFunctions(
|
||||
{
|
||||
mount,
|
||||
remove,
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
ctx.onInvalidated(remove);
|
||||
|
||||
return {
|
||||
@@ -46,8 +64,7 @@ export function createIntegratedUi<TMounted>(
|
||||
return mounted;
|
||||
},
|
||||
wrapper,
|
||||
mount,
|
||||
remove,
|
||||
...mountFunctions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,6 +96,14 @@ export function createIframeUi<TMounted>(
|
||||
mounted = undefined;
|
||||
};
|
||||
|
||||
const mountFunctions = createMountFunctions(
|
||||
{
|
||||
mount,
|
||||
remove,
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
ctx.onInvalidated(remove);
|
||||
|
||||
return {
|
||||
@@ -87,8 +112,7 @@ export function createIframeUi<TMounted>(
|
||||
},
|
||||
iframe,
|
||||
wrapper,
|
||||
mount,
|
||||
remove,
|
||||
...mountFunctions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -137,7 +161,7 @@ export async function createShadowRootUi<TMounted>(
|
||||
const remove = () => {
|
||||
// Cleanup mounted state
|
||||
options.onRemove?.(mounted);
|
||||
// Detatch shadow root from DOM
|
||||
// Detach shadow root from DOM
|
||||
shadowHost.remove();
|
||||
// Remove children from uiContainer
|
||||
while (uiContainer.lastChild)
|
||||
@@ -146,14 +170,21 @@ export async function createShadowRootUi<TMounted>(
|
||||
mounted = undefined;
|
||||
};
|
||||
|
||||
const mountFunctions = createMountFunctions(
|
||||
{
|
||||
mount,
|
||||
remove,
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
ctx.onInvalidated(remove);
|
||||
|
||||
return {
|
||||
shadow,
|
||||
shadowHost,
|
||||
uiContainer,
|
||||
mount,
|
||||
remove,
|
||||
...mountFunctions,
|
||||
get mounted() {
|
||||
return mounted;
|
||||
},
|
||||
@@ -256,6 +287,112 @@ function mountUi(
|
||||
}
|
||||
}
|
||||
|
||||
function createMountFunctions<TMounted>(
|
||||
baseFunctions: BaseMountFunctions,
|
||||
options: ContentScriptUiOptions<TMounted>,
|
||||
): MountFunctions {
|
||||
let autoMountInstance: AutoMount | undefined = undefined;
|
||||
|
||||
const stopAutoMount = () => {
|
||||
autoMountInstance?.stopAutoMount();
|
||||
autoMountInstance = undefined;
|
||||
};
|
||||
|
||||
const mount = () => {
|
||||
baseFunctions.mount();
|
||||
};
|
||||
|
||||
const unmount = baseFunctions.remove;
|
||||
|
||||
const remove = () => {
|
||||
stopAutoMount();
|
||||
baseFunctions.remove();
|
||||
};
|
||||
|
||||
const autoMount = (autoMountOptions?: AutoMountOptions) => {
|
||||
if (autoMountInstance) {
|
||||
logger.warn('autoMount is already set.');
|
||||
}
|
||||
autoMountInstance = autoMountUi(
|
||||
{ mount, unmount, stopAutoMount },
|
||||
{
|
||||
...options,
|
||||
...autoMountOptions,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
mount,
|
||||
remove,
|
||||
autoMount,
|
||||
};
|
||||
}
|
||||
|
||||
function autoMountUi(
|
||||
uiCallbacks: {
|
||||
mount: () => void;
|
||||
unmount: () => void;
|
||||
stopAutoMount: () => void;
|
||||
},
|
||||
options: ContentScriptAnchoredOptions & AutoMountOptions,
|
||||
): AutoMount {
|
||||
const abortController = new AbortController();
|
||||
const EXPLICIT_STOP_REASON = 'explicit_stop_auto_mount';
|
||||
const _stopAutoMount = () => {
|
||||
abortController.abort(EXPLICIT_STOP_REASON);
|
||||
options.onStop?.();
|
||||
};
|
||||
|
||||
let resolvedAnchor =
|
||||
typeof options.anchor === 'function' ? options.anchor() : options.anchor;
|
||||
if (resolvedAnchor instanceof Element) {
|
||||
throw Error(
|
||||
'autoMount and Element anchor option cannot be combined. Avoid passing `Element` directly or `() => Element` to the anchor.',
|
||||
);
|
||||
}
|
||||
|
||||
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', {
|
||||
customMatcher: () => getAnchor(options) ?? null,
|
||||
detector: isAnchorExist ? removeDetector : mountDetector,
|
||||
signal: abortController.signal,
|
||||
});
|
||||
isAnchorExist = !!changedAnchor;
|
||||
if (isAnchorExist) {
|
||||
uiCallbacks.mount();
|
||||
} else {
|
||||
uiCallbacks.unmount();
|
||||
if (options.once) {
|
||||
uiCallbacks.stopAutoMount();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
abortController.signal.aborted &&
|
||||
abortController.signal.reason === EXPLICIT_STOP_REASON
|
||||
) {
|
||||
break;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
observeElement(resolvedAnchor);
|
||||
|
||||
return { stopAutoMount: _stopAutoMount };
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the CSS for the current entrypoint.
|
||||
*/
|
||||
|
||||
@@ -36,18 +36,7 @@ export interface ShadowRootContentScriptUi<TMounted>
|
||||
shadow: ShadowRoot;
|
||||
}
|
||||
|
||||
export interface ContentScriptUi<TMounted> {
|
||||
/**
|
||||
* Function that mounts or remounts the UI on the page.
|
||||
*/
|
||||
mount: () => void;
|
||||
/**
|
||||
* Function that removes the UI from the webpage.
|
||||
*/
|
||||
remove: () => void;
|
||||
/**>
|
||||
* Custom data returned from the `options.mount` function.
|
||||
*/
|
||||
export interface ContentScriptUi<TMounted> extends MountFunctions {
|
||||
mounted: TMounted | undefined;
|
||||
}
|
||||
|
||||
@@ -216,3 +205,40 @@ export interface ContentScriptAnchoredOptions {
|
||||
*/
|
||||
append?: ContentScriptAppendMode | ((anchor: Element, ui: Element) => void);
|
||||
}
|
||||
|
||||
export interface BaseMountFunctions {
|
||||
/**
|
||||
* Function that mounts or remounts the UI on the page.
|
||||
*/
|
||||
mount: () => void;
|
||||
|
||||
/**
|
||||
* Function that removes the UI from the webpage.
|
||||
*/
|
||||
remove: () => void;
|
||||
}
|
||||
|
||||
export interface MountFunctions extends BaseMountFunctions {
|
||||
/**
|
||||
* Call `ui.autoMount()` to automatically mount and remove the UI as the anchor is dynamically added/removed by the webpage.
|
||||
*/
|
||||
autoMount: (options?: AutoMountOptions) => void;
|
||||
}
|
||||
|
||||
export type AutoMountOptions = {
|
||||
/**
|
||||
* When true, only mount and unmount a UI once.
|
||||
*/
|
||||
once?: boolean;
|
||||
/**
|
||||
* The callback triggered when `StopAutoMount` is called.
|
||||
*/
|
||||
onStop?: () => void;
|
||||
};
|
||||
export type StopAutoMount = () => void;
|
||||
export interface AutoMount {
|
||||
/**
|
||||
* Stop watching the anchor element for changes, but keep the UI mounted.
|
||||
*/
|
||||
stopAutoMount: StopAutoMount;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import { importEntrypointFile } from '../../utils/building';
|
||||
import { ViteNodeServer } from 'vite-node/server';
|
||||
import { ViteNodeRunner } from 'vite-node/client';
|
||||
import { installSourcemapsSupport } from 'vite-node/source-map';
|
||||
import { createExtensionEnvironment } from '../../utils/environments';
|
||||
import { relative } from 'node:path';
|
||||
|
||||
export async function createViteBuilder(
|
||||
wxtConfig: ResolvedConfig,
|
||||
@@ -220,55 +222,104 @@ export async function createViteBuilder(
|
||||
};
|
||||
};
|
||||
|
||||
const createViteNodeImporter = async (paths: string[]) => {
|
||||
const baseConfig = await getBaseConfig({
|
||||
excludeAnalysisPlugin: true,
|
||||
});
|
||||
// Disable dep optimization, as recommended by vite-node's README
|
||||
baseConfig.optimizeDeps ??= {};
|
||||
baseConfig.optimizeDeps.noDiscovery = true;
|
||||
baseConfig.optimizeDeps.include = [];
|
||||
const envConfig: vite.InlineConfig = {
|
||||
plugins: paths.map((path) =>
|
||||
wxtPlugins.removeEntrypointMainFunction(wxtConfig, path),
|
||||
),
|
||||
};
|
||||
const config = vite.mergeConfig(baseConfig, envConfig);
|
||||
const server = await vite.createServer(config);
|
||||
await server.pluginContainer.buildStart({});
|
||||
const node = new ViteNodeServer(
|
||||
// @ts-ignore: Some weird type error...
|
||||
server,
|
||||
);
|
||||
installSourcemapsSupport({
|
||||
getSourceMap: (source) => node.getSourceMap(source),
|
||||
});
|
||||
const runner = new ViteNodeRunner({
|
||||
root: server.config.root,
|
||||
base: server.config.base,
|
||||
// when having the server and runner in a different context,
|
||||
// you will need to handle the communication between them
|
||||
// and pass to this function
|
||||
fetchModule(id) {
|
||||
return node.fetchModule(id);
|
||||
},
|
||||
resolveId(id, importer) {
|
||||
return node.resolveId(id, importer);
|
||||
},
|
||||
});
|
||||
return { runner, server };
|
||||
};
|
||||
|
||||
const requireDefaultExport = (path: string, mod: any) => {
|
||||
const relativePath = relative(wxtConfig.root, path);
|
||||
if (mod?.default == null) {
|
||||
const defineFn = relativePath.includes('.content')
|
||||
? 'defineContentScript'
|
||||
: relativePath.includes('background')
|
||||
? 'defineBackground'
|
||||
: 'defineUnlistedScript';
|
||||
|
||||
throw Error(
|
||||
`${relativePath}: Default export not found, did you forget to call "export default ${defineFn}(...)"?`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
name: 'Vite',
|
||||
version: vite.version,
|
||||
async importEntrypoint(path) {
|
||||
const env = createExtensionEnvironment();
|
||||
switch (wxtConfig.entrypointLoader) {
|
||||
default:
|
||||
case 'jiti': {
|
||||
return await importEntrypointFile(path);
|
||||
return await env.run(() => importEntrypointFile(path));
|
||||
}
|
||||
case 'vite-node': {
|
||||
const baseConfig = await getBaseConfig({
|
||||
excludeAnalysisPlugin: true,
|
||||
});
|
||||
// Disable dep optimization, as recommended by vite-node's README
|
||||
baseConfig.optimizeDeps ??= {};
|
||||
baseConfig.optimizeDeps.noDiscovery = true;
|
||||
baseConfig.optimizeDeps.include = [];
|
||||
const envConfig: vite.InlineConfig = {
|
||||
plugins: [wxtPlugins.removeEntrypointMainFunction(wxtConfig, path)],
|
||||
};
|
||||
const config = vite.mergeConfig(baseConfig, envConfig);
|
||||
const server = await vite.createServer(config);
|
||||
await server.pluginContainer.buildStart({});
|
||||
const node = new ViteNodeServer(
|
||||
// @ts-ignore: Some weird type error...
|
||||
server,
|
||||
);
|
||||
installSourcemapsSupport({
|
||||
getSourceMap: (source) => node.getSourceMap(source),
|
||||
});
|
||||
const runner = new ViteNodeRunner({
|
||||
root: server.config.root,
|
||||
base: server.config.base,
|
||||
// when having the server and runner in a different context,
|
||||
// you will need to handle the communication between them
|
||||
// and pass to this function
|
||||
fetchModule(id) {
|
||||
return node.fetchModule(id);
|
||||
},
|
||||
resolveId(id, importer) {
|
||||
return node.resolveId(id, importer);
|
||||
},
|
||||
});
|
||||
const res = await runner.executeFile(path);
|
||||
const { runner, server } = await createViteNodeImporter([path]);
|
||||
const res = await env.run(() => runner.executeFile(path));
|
||||
await server.close();
|
||||
requireDefaultExport(path, res);
|
||||
return res.default;
|
||||
}
|
||||
}
|
||||
},
|
||||
async importEntrypoints(paths) {
|
||||
const env = createExtensionEnvironment();
|
||||
switch (wxtConfig.entrypointLoader) {
|
||||
default:
|
||||
case 'jiti': {
|
||||
return await env.run(() =>
|
||||
Promise.all(paths.map(importEntrypointFile)),
|
||||
);
|
||||
}
|
||||
case 'vite-node': {
|
||||
const { runner, server } = await createViteNodeImporter(paths);
|
||||
const res = await env.run(() =>
|
||||
Promise.all(
|
||||
paths.map(async (path) => {
|
||||
const mod = await runner.executeFile(path);
|
||||
requireDefaultExport(path, mod);
|
||||
return mod.default;
|
||||
}),
|
||||
),
|
||||
);
|
||||
await server.close();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
},
|
||||
async build(group) {
|
||||
let entryConfig;
|
||||
if (Array.isArray(group)) entryConfig = getMultiPageConfig(group);
|
||||
@@ -321,6 +372,9 @@ export async function createViteBuilder(
|
||||
},
|
||||
},
|
||||
watcher: viteServer.watcher,
|
||||
on(event, cb) {
|
||||
viteServer.httpServer?.on(event, cb);
|
||||
},
|
||||
};
|
||||
|
||||
return server;
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
getContentScriptJs,
|
||||
mapWxtOptionsToRegisteredContentScript,
|
||||
} from './utils/content-scripts';
|
||||
import { createKeyboardShortcuts } from './keyboard-shortcuts';
|
||||
|
||||
/**
|
||||
* Creates a dev server and pre-builds all the files that need to exist before loading the extension.
|
||||
@@ -96,21 +97,30 @@ async function createServerInternal(): Promise<WxtDevServer> {
|
||||
wxt.logger.success(`Started dev server @ ${server.origin}`);
|
||||
await wxt.hooks.callHook('server:started', wxt, server);
|
||||
|
||||
await buildAndOpenBrowser();
|
||||
|
||||
// Register content scripts for the first time after the background starts up since they're not
|
||||
// listed in the manifest
|
||||
// Register content scripts for the first time after the background starts
|
||||
// up since they're not listed in the manifest.
|
||||
// Add listener before opening the browser to guarantee it is present when
|
||||
// the extension sends back the initialization message.
|
||||
server.ws.on('wxt:background-initialized', () => {
|
||||
if (server.currentOutput == null) return;
|
||||
reloadContentScripts(server.currentOutput.steps, server);
|
||||
});
|
||||
|
||||
await buildAndOpenBrowser();
|
||||
|
||||
// Listen for file changes and reload different parts of the extension accordingly
|
||||
const reloadOnChange = createFileReloader(server);
|
||||
server.watcher.on('all', reloadOnChange);
|
||||
keyboardShortcuts.start();
|
||||
keyboardShortcuts.printHelp({
|
||||
canReopenBrowser:
|
||||
!wxt.config.runnerConfig.config.disabled && !!runner.canOpen?.(),
|
||||
});
|
||||
},
|
||||
|
||||
async stop() {
|
||||
wasStopped = true;
|
||||
keyboardShortcuts.stop();
|
||||
await runner.closeBrowser();
|
||||
await builderServer.close();
|
||||
await wxt.hooks.callHook('server:closed', wxt, server);
|
||||
@@ -136,11 +146,14 @@ async function createServerInternal(): Promise<WxtDevServer> {
|
||||
},
|
||||
async restartBrowser() {
|
||||
await runner.closeBrowser();
|
||||
keyboardShortcuts.stop();
|
||||
await wxt.reloadConfig();
|
||||
runner = await createExtensionRunner();
|
||||
await runner.openBrowser();
|
||||
keyboardShortcuts.start();
|
||||
},
|
||||
};
|
||||
const keyboardShortcuts = createKeyboardShortcuts(server);
|
||||
|
||||
const buildAndOpenBrowser = async () => {
|
||||
// Build after starting the dev server so it can be used to transform HTML files
|
||||
@@ -158,6 +171,8 @@ async function createServerInternal(): Promise<WxtDevServer> {
|
||||
await runner.openBrowser();
|
||||
};
|
||||
|
||||
builderServer.on?.('close', () => keyboardShortcuts.stop());
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
@@ -230,6 +245,7 @@ function createFileReloader(server: WxtDevServer) {
|
||||
break;
|
||||
case 'content-script-reload':
|
||||
reloadContentScripts(changes.changedSteps, server);
|
||||
|
||||
const rebuiltNames = changes.rebuildGroups
|
||||
.flat()
|
||||
.map((entry) => entry.name);
|
||||
|
||||
@@ -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' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import readline from 'node:readline';
|
||||
import { WxtDevServer } from '../types';
|
||||
import { wxt } from './wxt';
|
||||
import pc from 'picocolors';
|
||||
|
||||
export interface KeyboardShortcutWatcher {
|
||||
start(): void;
|
||||
stop(): void;
|
||||
printHelp(flags: { canReopenBrowser: boolean }): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that creates a keyboard shortcut handler for the extension.
|
||||
*/
|
||||
export function createKeyboardShortcuts(
|
||||
server: WxtDevServer,
|
||||
): KeyboardShortcutWatcher {
|
||||
let rl: readline.Interface | undefined;
|
||||
|
||||
const handleInput = (line: string) => {
|
||||
// Only handle our specific command
|
||||
if (line.trim() === 'o') {
|
||||
server.restartBrowser();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
start() {
|
||||
if (rl) return;
|
||||
|
||||
rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
terminal: false, // Don't intercept ctrl+C, ctrl+Z, etc
|
||||
});
|
||||
|
||||
rl.on('line', handleInput);
|
||||
},
|
||||
|
||||
stop() {
|
||||
rl?.close();
|
||||
rl = undefined;
|
||||
},
|
||||
|
||||
printHelp(flags) {
|
||||
if (flags.canReopenBrowser) {
|
||||
wxt.logger.info(
|
||||
`${pc.dim('Press')} ${pc.bold('o + enter')} ${pc.dim('to reopen the browser')}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -11,6 +11,9 @@ export function createWebExtRunner(): ExtensionRunner {
|
||||
let runner: WebExtRunInstance | undefined;
|
||||
|
||||
return {
|
||||
canOpen() {
|
||||
return true;
|
||||
},
|
||||
async openBrowser() {
|
||||
const startTime = Date.now();
|
||||
|
||||
@@ -32,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,
|
||||
|
||||
@@ -44,6 +44,7 @@ describe('Manifest Utils', () => {
|
||||
defaultTitle: 'Default Iitle',
|
||||
},
|
||||
outputDir: outDir,
|
||||
skipped: false,
|
||||
});
|
||||
|
||||
it('should include an action for mv3', async () => {
|
||||
@@ -209,6 +210,7 @@ describe('Manifest Utils', () => {
|
||||
chromeStyle: true,
|
||||
browserStyle: true,
|
||||
},
|
||||
skipped: false,
|
||||
});
|
||||
|
||||
it('should include a options_ui and chrome_style for chrome', async () => {
|
||||
@@ -265,6 +267,7 @@ describe('Manifest Utils', () => {
|
||||
persistent: true,
|
||||
type: 'module',
|
||||
},
|
||||
skipped: false,
|
||||
});
|
||||
|
||||
describe('MV3', () => {
|
||||
@@ -368,7 +371,7 @@ describe('Manifest Utils', () => {
|
||||
|
||||
describe('icons', () => {
|
||||
it('should auto-discover icons with the correct name', async () => {
|
||||
const entrypoints = fakeArray(fakeEntrypoint);
|
||||
const entrypoints = fakeArray(() => fakeEntrypoint({ skipped: false }));
|
||||
const buildOutput = fakeBuildOutput({
|
||||
publicAssets: [
|
||||
{ type: 'asset', fileName: 'icon-16.png' },
|
||||
@@ -396,7 +399,7 @@ describe('Manifest Utils', () => {
|
||||
});
|
||||
|
||||
it('should return undefined when no icons are found', async () => {
|
||||
const entrypoints = fakeArray(fakeEntrypoint);
|
||||
const entrypoints = fakeArray(() => fakeEntrypoint({ skipped: false }));
|
||||
const buildOutput = fakeBuildOutput({
|
||||
publicAssets: [
|
||||
{ type: 'asset', fileName: 'logo.png' },
|
||||
@@ -413,7 +416,7 @@ describe('Manifest Utils', () => {
|
||||
});
|
||||
|
||||
it('should allow icons to be overwritten from the wxt.config.ts file', async () => {
|
||||
const entrypoints = fakeArray(fakeEntrypoint);
|
||||
const entrypoints = fakeArray(() => fakeEntrypoint({ skipped: false }));
|
||||
const buildOutput = fakeBuildOutput({
|
||||
publicAssets: [
|
||||
{ type: 'asset', fileName: 'icon-16.png' },
|
||||
@@ -837,6 +840,7 @@ describe('Manifest Utils', () => {
|
||||
options: {
|
||||
registration: 'runtime',
|
||||
},
|
||||
skipped: false,
|
||||
});
|
||||
|
||||
const entrypoints = [cs];
|
||||
@@ -902,6 +906,7 @@ describe('Manifest Utils', () => {
|
||||
async (browser) => {
|
||||
const sidepanel = fakeSidepanelEntrypoint({
|
||||
outputDir: outDir,
|
||||
skipped: false,
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
|
||||
@@ -934,6 +939,7 @@ describe('Manifest Utils', () => {
|
||||
async (browser) => {
|
||||
const sidepanel = fakeSidepanelEntrypoint({
|
||||
outputDir: outDir,
|
||||
skipped: false,
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
|
||||
@@ -1617,6 +1623,29 @@ describe('Manifest Utils', () => {
|
||||
expect(actual.content_security_policy).toEqual(expectedCsp);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not add skipped entrypoints to manifest', async () => {
|
||||
const popup = fakePopupEntrypoint({ skipped: true });
|
||||
const content = fakeContentScriptEntrypoint({ skipped: true });
|
||||
const sidePanel = fakeSidepanelEntrypoint({ skipped: true });
|
||||
const buildOutput = fakeBuildOutput();
|
||||
|
||||
setFakeWxt({
|
||||
config: {
|
||||
command: 'build',
|
||||
manifestVersion: 3,
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest } = await generateManifest(
|
||||
[popup, content, sidePanel],
|
||||
buildOutput,
|
||||
);
|
||||
|
||||
expect(manifest.action).toBeUndefined();
|
||||
expect(manifest.sidebar_action).toBeUndefined();
|
||||
expect(manifest.content_scripts).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('stripPathFromMatchPattern', () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,11 +36,12 @@ describe('findEntrypoints', () => {
|
||||
outDir: resolve('.output'),
|
||||
command: 'build',
|
||||
});
|
||||
let importEntrypointMock: Mock;
|
||||
let importEntrypointsMock: Mock<typeof wxt.builder.importEntrypoints>;
|
||||
|
||||
beforeEach(() => {
|
||||
setFakeWxt({ config });
|
||||
importEntrypointMock = vi.mocked(wxt.builder.importEntrypoint);
|
||||
importEntrypointsMock = vi.mocked(wxt.builder.importEntrypoints);
|
||||
importEntrypointsMock.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
it.each<[string, string, PopupEntrypoint]>([
|
||||
@@ -210,13 +211,13 @@ describe('findEntrypoints', () => {
|
||||
matches: ['<all_urls>'],
|
||||
};
|
||||
globMock.mockResolvedValueOnce([path]);
|
||||
importEntrypointMock.mockResolvedValue(options);
|
||||
importEntrypointsMock.mockResolvedValue([options]);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toHaveLength(1);
|
||||
expect(entrypoints[0]).toEqual({ ...expected, options });
|
||||
expect(importEntrypointMock).toBeCalledWith(expected.inputPath);
|
||||
expect(importEntrypointsMock).toBeCalledWith([expected.inputPath]);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -244,17 +245,17 @@ describe('findEntrypoints', () => {
|
||||
])(
|
||||
'should find and load background entrypoint config from %s',
|
||||
async (path, expected) => {
|
||||
const options: BackgroundEntrypointOptions = {
|
||||
const options = {
|
||||
type: 'module',
|
||||
};
|
||||
} satisfies BackgroundEntrypointOptions;
|
||||
globMock.mockResolvedValueOnce([path]);
|
||||
importEntrypointMock.mockResolvedValue(options);
|
||||
importEntrypointsMock.mockResolvedValue([options]);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toHaveLength(1);
|
||||
expect(entrypoints[0]).toEqual({ ...expected, options });
|
||||
expect(importEntrypointMock).toBeCalledWith(expected.inputPath);
|
||||
expect(importEntrypointsMock).toBeCalledWith([expected.inputPath]);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -339,11 +340,11 @@ describe('findEntrypoints', () => {
|
||||
},
|
||||
builder: wxt.builder,
|
||||
});
|
||||
const options: BackgroundEntrypointOptions = {
|
||||
const options = {
|
||||
type: 'module',
|
||||
};
|
||||
} satisfies BackgroundEntrypointOptions;
|
||||
globMock.mockResolvedValueOnce(['background.ts']);
|
||||
importEntrypointMock.mockResolvedValue(options);
|
||||
importEntrypointsMock.mockResolvedValue([options]);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
@@ -357,11 +358,11 @@ describe('findEntrypoints', () => {
|
||||
},
|
||||
builder: wxt.builder,
|
||||
});
|
||||
const options: BackgroundEntrypointOptions = {
|
||||
const options = {
|
||||
type: 'module',
|
||||
};
|
||||
} satisfies BackgroundEntrypointOptions;
|
||||
globMock.mockResolvedValueOnce(['background.ts']);
|
||||
importEntrypointMock.mockResolvedValue(options);
|
||||
importEntrypointsMock.mockResolvedValue([options]);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
@@ -410,15 +411,15 @@ describe('findEntrypoints', () => {
|
||||
outputDir: config.outDir,
|
||||
skipped: false,
|
||||
};
|
||||
const options: BaseEntrypointOptions = {};
|
||||
const options = {} satisfies BaseEntrypointOptions;
|
||||
globMock.mockResolvedValueOnce([path]);
|
||||
importEntrypointMock.mockResolvedValue(options);
|
||||
importEntrypointsMock.mockResolvedValue([options]);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
expect(entrypoints).toHaveLength(1);
|
||||
expect(entrypoints[0]).toEqual({ ...expected, options });
|
||||
expect(importEntrypointMock).toBeCalledWith(expected.inputPath);
|
||||
expect(importEntrypointsMock).toBeCalledWith([expected.inputPath]);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -703,9 +704,9 @@ describe('findEntrypoints', () => {
|
||||
describe('include option', () => {
|
||||
it("should mark the background as skipped when include doesn't contain the target browser", async () => {
|
||||
globMock.mockResolvedValueOnce(['background.ts']);
|
||||
importEntrypointMock.mockResolvedValue({
|
||||
include: ['not' + config.browser],
|
||||
});
|
||||
importEntrypointsMock.mockResolvedValue([
|
||||
{ include: ['not' + config.browser] },
|
||||
]);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
@@ -719,9 +720,9 @@ describe('findEntrypoints', () => {
|
||||
|
||||
it("should mark content scripts as skipped when include doesn't contain the target browser", async () => {
|
||||
globMock.mockResolvedValueOnce(['example.content.ts']);
|
||||
importEntrypointMock.mockResolvedValue({
|
||||
include: ['not' + config.browser],
|
||||
});
|
||||
importEntrypointsMock.mockResolvedValue([
|
||||
{ include: ['not' + config.browser] },
|
||||
]);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
@@ -803,9 +804,7 @@ describe('findEntrypoints', () => {
|
||||
describe('exclude option', () => {
|
||||
it('should mark the background as skipped when exclude contains the target browser', async () => {
|
||||
globMock.mockResolvedValueOnce(['background.ts']);
|
||||
importEntrypointMock.mockResolvedValue({
|
||||
exclude: [config.browser],
|
||||
});
|
||||
importEntrypointsMock.mockResolvedValue([{ exclude: [config.browser] }]);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
@@ -819,9 +818,7 @@ describe('findEntrypoints', () => {
|
||||
|
||||
it('should mark content scripts as skipped when exclude contains the target browser', async () => {
|
||||
globMock.mockResolvedValueOnce(['example.content.ts']);
|
||||
importEntrypointMock.mockResolvedValue({
|
||||
exclude: [config.browser],
|
||||
});
|
||||
importEntrypointsMock.mockResolvedValue([{ exclude: [config.browser] }]);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
@@ -914,7 +911,7 @@ describe('findEntrypoints', () => {
|
||||
builder: wxt.builder,
|
||||
});
|
||||
|
||||
importEntrypointMock.mockResolvedValue({});
|
||||
importEntrypointsMock.mockResolvedValue([{}]);
|
||||
|
||||
const entrypoints = await findEntrypoints();
|
||||
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import { relative, resolve } from 'path';
|
||||
import {
|
||||
BackgroundEntrypoint,
|
||||
BackgroundDefinition,
|
||||
BaseEntrypointOptions,
|
||||
ContentScriptDefinition,
|
||||
ContentScriptEntrypoint,
|
||||
Entrypoint,
|
||||
EntrypointInfo,
|
||||
GenericEntrypoint,
|
||||
OptionsEntrypoint,
|
||||
PopupEntrypoint,
|
||||
UnlistedScriptDefinition,
|
||||
PopupEntrypointOptions,
|
||||
OptionsEntrypointOptions,
|
||||
SidepanelEntrypoint,
|
||||
SidepanelEntrypointOptions,
|
||||
MainWorldContentScriptEntrypointOptions,
|
||||
IsolatedWorldContentScriptEntrypointOptions,
|
||||
} from '../../../types';
|
||||
import fs from 'fs-extra';
|
||||
import { minimatch } from 'minimatch';
|
||||
@@ -22,13 +18,15 @@ import JSON5 from 'json5';
|
||||
import glob from 'fast-glob';
|
||||
import {
|
||||
getEntrypointName,
|
||||
isHtmlEntrypoint,
|
||||
isJsEntrypoint,
|
||||
resolvePerBrowserOptions,
|
||||
} from '../../utils/entrypoints';
|
||||
import { VIRTUAL_NOOP_BACKGROUND_MODULE_ID } from '../../utils/constants';
|
||||
import { CSS_EXTENSIONS_PATTERN } from '../../utils/paths';
|
||||
import pc from 'picocolors';
|
||||
import { wxt } from '../../wxt';
|
||||
import { createExtensionEnvironment } from '../environments';
|
||||
import { camelCase } from 'scule';
|
||||
|
||||
/**
|
||||
* Return entrypoints and their configuration by looking through the project's files.
|
||||
@@ -70,65 +68,69 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
|
||||
return results;
|
||||
}, []);
|
||||
|
||||
await wxt.hooks.callHook('entrypoints:found', wxt, entrypointInfos);
|
||||
|
||||
// Validation
|
||||
preventNoEntrypoints(entrypointInfos);
|
||||
preventDuplicateEntrypointNames(entrypointInfos);
|
||||
|
||||
// Import entrypoints to get their config
|
||||
let hasBackground = false;
|
||||
const env = createExtensionEnvironment();
|
||||
const entrypointsWithoutSkipped: Entrypoint[] = await env.run(() =>
|
||||
Promise.all(
|
||||
entrypointInfos.map(async (info): Promise<Entrypoint> => {
|
||||
const { type } = info;
|
||||
switch (type) {
|
||||
case 'popup':
|
||||
return await getPopupEntrypoint(info);
|
||||
case 'sidepanel':
|
||||
return await getSidepanelEntrypoint(info);
|
||||
case 'options':
|
||||
return await getOptionsEntrypoint(info);
|
||||
case 'background':
|
||||
hasBackground = true;
|
||||
return await getBackgroundEntrypoint(info);
|
||||
case 'content-script':
|
||||
return await getContentScriptEntrypoint(info);
|
||||
case 'unlisted-page':
|
||||
return await getUnlistedPageEntrypoint(info);
|
||||
case 'unlisted-script':
|
||||
return await getUnlistedScriptEntrypoint(info);
|
||||
case 'content-script-style':
|
||||
return {
|
||||
...info,
|
||||
type,
|
||||
outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
|
||||
options: {
|
||||
include: undefined,
|
||||
exclude: undefined,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {
|
||||
...info,
|
||||
type,
|
||||
outputDir: wxt.config.outDir,
|
||||
options: {
|
||||
include: undefined,
|
||||
exclude: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
}),
|
||||
),
|
||||
const entrypointOptions = await importEntrypoints(entrypointInfos);
|
||||
const entrypointsWithoutSkipped: Entrypoint[] = await Promise.all(
|
||||
entrypointInfos.map(async (info): Promise<Entrypoint> => {
|
||||
const { type } = info;
|
||||
const options = entrypointOptions[info.inputPath] ?? {};
|
||||
switch (type) {
|
||||
case 'popup':
|
||||
return await getPopupEntrypoint(info, options);
|
||||
case 'sidepanel':
|
||||
return await getSidepanelEntrypoint(info, options);
|
||||
case 'options':
|
||||
return await getOptionsEntrypoint(info, options);
|
||||
case 'background':
|
||||
hasBackground = true;
|
||||
return await getBackgroundEntrypoint(info, options);
|
||||
case 'content-script':
|
||||
return await getContentScriptEntrypoint(info, options);
|
||||
case 'unlisted-page':
|
||||
return await getUnlistedPageEntrypoint(info, options);
|
||||
case 'unlisted-script':
|
||||
return await getUnlistedScriptEntrypoint(info, options);
|
||||
case 'content-script-style':
|
||||
return {
|
||||
...info,
|
||||
type,
|
||||
outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
|
||||
options: {
|
||||
include: options.include,
|
||||
exclude: options.exclude,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {
|
||||
...info,
|
||||
type,
|
||||
outputDir: wxt.config.outDir,
|
||||
options: {
|
||||
include: options.include,
|
||||
exclude: options.exclude,
|
||||
},
|
||||
};
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if (wxt.config.command === 'serve' && !hasBackground) {
|
||||
entrypointsWithoutSkipped.push(
|
||||
await getBackgroundEntrypoint({
|
||||
inputPath: VIRTUAL_NOOP_BACKGROUND_MODULE_ID,
|
||||
name: 'background',
|
||||
type: 'background',
|
||||
}),
|
||||
await getBackgroundEntrypoint(
|
||||
{
|
||||
inputPath: VIRTUAL_NOOP_BACKGROUND_MODULE_ID,
|
||||
name: 'background',
|
||||
type: 'background',
|
||||
},
|
||||
{},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,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)
|
||||
@@ -152,15 +156,63 @@ 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> = {};
|
||||
|
||||
const htmlInfos = infos.filter((info) => isHtmlEntrypoint(info));
|
||||
const jsInfos = infos.filter((info) => isJsEntrypoint(info));
|
||||
|
||||
await Promise.all([
|
||||
// HTML
|
||||
...htmlInfos.map(async (info) => {
|
||||
const res = await importHtmlEntrypoint(info);
|
||||
resMap[info.inputPath] = res;
|
||||
}),
|
||||
// JS
|
||||
(async () => {
|
||||
const res = await wxt.builder.importEntrypoints(
|
||||
jsInfos.map((info) => info.inputPath),
|
||||
);
|
||||
res.forEach((res, i) => {
|
||||
resMap[jsInfos[i].inputPath] = res;
|
||||
});
|
||||
})(),
|
||||
// CSS - never has options
|
||||
]);
|
||||
|
||||
return resMap;
|
||||
}
|
||||
|
||||
/** Extract `manifest.` options from meta tags, converting snake_case keys to camelCase */
|
||||
async function importHtmlEntrypoint(
|
||||
info: EntrypointInfo,
|
||||
): Promise<Record<string, any>> {
|
||||
const content = await fs.readFile(info.inputPath, 'utf-8');
|
||||
const { document } = parseHTML(content);
|
||||
|
||||
const metaTags = document.querySelectorAll('meta');
|
||||
const res: Record<string, any> = {
|
||||
title: document.querySelector('title')?.textContent || undefined,
|
||||
};
|
||||
|
||||
metaTags.forEach((tag) => {
|
||||
const name = tag.name;
|
||||
if (!name.startsWith('manifest.')) return;
|
||||
|
||||
const key = camelCase(name.slice(9));
|
||||
try {
|
||||
res[key] = JSON5.parse(tag.content);
|
||||
} catch {
|
||||
res[key] = tag.content;
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function preventDuplicateEntrypointNames(files: EntrypointInfo[]) {
|
||||
@@ -200,32 +252,26 @@ function preventNoEntrypoints(files: EntrypointInfo[]) {
|
||||
|
||||
async function getPopupEntrypoint(
|
||||
info: EntrypointInfo,
|
||||
options: Record<string, any>,
|
||||
): Promise<PopupEntrypoint> {
|
||||
const options = await getHtmlEntrypointOptions<PopupEntrypointOptions>(
|
||||
info,
|
||||
const stictOptions: PopupEntrypoint['options'] = resolvePerBrowserOptions(
|
||||
{
|
||||
browserStyle: 'browser_style',
|
||||
exclude: 'exclude',
|
||||
include: 'include',
|
||||
defaultIcon: 'default_icon',
|
||||
defaultTitle: 'default_title',
|
||||
mv2Key: 'type',
|
||||
},
|
||||
{
|
||||
defaultTitle: (document) =>
|
||||
document.querySelector('title')?.textContent || undefined,
|
||||
},
|
||||
{
|
||||
defaultTitle: (content) => content,
|
||||
mv2Key: (content) =>
|
||||
content === 'page_action' ? 'page_action' : 'browser_action',
|
||||
browserStyle: options.browserStyle,
|
||||
exclude: options.exclude,
|
||||
include: options.include,
|
||||
defaultIcon: options.defaultIcon,
|
||||
defaultTitle: options.title,
|
||||
mv2Key: options.type,
|
||||
},
|
||||
wxt.config.browser,
|
||||
);
|
||||
if (stictOptions.mv2Key && stictOptions.mv2Key !== 'page_action')
|
||||
stictOptions.mv2Key = 'browser_action';
|
||||
|
||||
return {
|
||||
type: 'popup',
|
||||
name: 'popup',
|
||||
options: resolvePerBrowserOptions(options, wxt.config.browser),
|
||||
options: stictOptions,
|
||||
inputPath: info.inputPath,
|
||||
outputDir: wxt.config.outDir,
|
||||
};
|
||||
@@ -233,21 +279,21 @@ async function getPopupEntrypoint(
|
||||
|
||||
async function getOptionsEntrypoint(
|
||||
info: EntrypointInfo,
|
||||
options: Record<string, any>,
|
||||
): Promise<OptionsEntrypoint> {
|
||||
const options = await getHtmlEntrypointOptions<OptionsEntrypointOptions>(
|
||||
info,
|
||||
{
|
||||
browserStyle: 'browser_style',
|
||||
chromeStyle: 'chrome_style',
|
||||
exclude: 'exclude',
|
||||
include: 'include',
|
||||
openInTab: 'open_in_tab',
|
||||
},
|
||||
);
|
||||
return {
|
||||
type: 'options',
|
||||
name: 'options',
|
||||
options: resolvePerBrowserOptions(options, wxt.config.browser),
|
||||
options: resolvePerBrowserOptions(
|
||||
{
|
||||
browserStyle: options.browserStyle,
|
||||
chromeStyle: options.chromeStyle,
|
||||
exclude: options.exclude,
|
||||
include: options.include,
|
||||
openInTab: options.openInTab,
|
||||
},
|
||||
wxt.config.browser,
|
||||
),
|
||||
inputPath: info.inputPath,
|
||||
outputDir: wxt.config.outDir,
|
||||
};
|
||||
@@ -255,61 +301,56 @@ async function getOptionsEntrypoint(
|
||||
|
||||
async function getUnlistedPageEntrypoint(
|
||||
info: EntrypointInfo,
|
||||
options: Record<string, any>,
|
||||
): Promise<GenericEntrypoint> {
|
||||
const options = await getHtmlEntrypointOptions<BaseEntrypointOptions>(info, {
|
||||
exclude: 'exclude',
|
||||
include: 'include',
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'unlisted-page',
|
||||
name: info.name,
|
||||
inputPath: info.inputPath,
|
||||
outputDir: wxt.config.outDir,
|
||||
options,
|
||||
options: {
|
||||
include: options.include,
|
||||
exclude: options.exclude,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function getUnlistedScriptEntrypoint({
|
||||
inputPath,
|
||||
name,
|
||||
}: EntrypointInfo): Promise<GenericEntrypoint> {
|
||||
const defaultExport =
|
||||
await wxt.builder.importEntrypoint<UnlistedScriptDefinition>(inputPath);
|
||||
if (defaultExport == null) {
|
||||
throw Error(
|
||||
`${name}: Default export not found, did you forget to call "export default defineUnlistedScript(...)"?`,
|
||||
);
|
||||
}
|
||||
const { main: _, ...options } = defaultExport;
|
||||
async function getUnlistedScriptEntrypoint(
|
||||
{ inputPath, name }: EntrypointInfo,
|
||||
options: Record<string, any>,
|
||||
): Promise<GenericEntrypoint> {
|
||||
return {
|
||||
type: 'unlisted-script',
|
||||
name,
|
||||
inputPath,
|
||||
outputDir: wxt.config.outDir,
|
||||
options: resolvePerBrowserOptions(options, wxt.config.browser),
|
||||
options: resolvePerBrowserOptions(
|
||||
{
|
||||
include: options.include,
|
||||
exclude: options.exclude,
|
||||
},
|
||||
wxt.config.browser,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
async function getBackgroundEntrypoint({
|
||||
inputPath,
|
||||
name,
|
||||
}: EntrypointInfo): Promise<BackgroundEntrypoint> {
|
||||
let options: Omit<BackgroundDefinition, 'main'> = {};
|
||||
if (inputPath !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) {
|
||||
const defaultExport =
|
||||
await wxt.builder.importEntrypoint<BackgroundDefinition>(inputPath);
|
||||
if (defaultExport == null) {
|
||||
throw Error(
|
||||
`${name}: Default export not found, did you forget to call "export default defineBackground(...)"?`,
|
||||
);
|
||||
}
|
||||
const { main: _, ...moduleOptions } = defaultExport;
|
||||
options = moduleOptions;
|
||||
}
|
||||
async function getBackgroundEntrypoint(
|
||||
{ inputPath, name }: EntrypointInfo,
|
||||
options: Record<string, any>,
|
||||
): Promise<BackgroundEntrypoint> {
|
||||
const strictOptions: BackgroundEntrypoint['options'] =
|
||||
resolvePerBrowserOptions(
|
||||
{
|
||||
include: options.include,
|
||||
exclude: options.exclude,
|
||||
persistent: options.persistent,
|
||||
type: options.type,
|
||||
},
|
||||
wxt.config.browser,
|
||||
);
|
||||
|
||||
if (wxt.config.manifestVersion !== 3) {
|
||||
delete options.type;
|
||||
delete strictOptions.type;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -317,112 +358,51 @@ async function getBackgroundEntrypoint({
|
||||
name,
|
||||
inputPath,
|
||||
outputDir: wxt.config.outDir,
|
||||
options: resolvePerBrowserOptions(options, wxt.config.browser),
|
||||
options: strictOptions,
|
||||
};
|
||||
}
|
||||
|
||||
async function getContentScriptEntrypoint({
|
||||
inputPath,
|
||||
name,
|
||||
}: EntrypointInfo): Promise<ContentScriptEntrypoint> {
|
||||
const defaultExport =
|
||||
await wxt.builder.importEntrypoint<ContentScriptDefinition>(inputPath);
|
||||
if (defaultExport == null) {
|
||||
throw Error(
|
||||
`${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`,
|
||||
);
|
||||
}
|
||||
|
||||
const { main: _, ...options } = defaultExport;
|
||||
if (options == null) {
|
||||
throw Error(
|
||||
`${name}: Default export not found, did you forget to call "export default defineContentScript(...)"?`,
|
||||
);
|
||||
}
|
||||
async function getContentScriptEntrypoint(
|
||||
{ inputPath, name }: EntrypointInfo,
|
||||
options: Record<string, any>,
|
||||
): Promise<ContentScriptEntrypoint> {
|
||||
return {
|
||||
type: 'content-script',
|
||||
name,
|
||||
inputPath,
|
||||
outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR),
|
||||
options: resolvePerBrowserOptions(options, wxt.config.browser),
|
||||
options: resolvePerBrowserOptions(
|
||||
options as
|
||||
| MainWorldContentScriptEntrypointOptions
|
||||
| IsolatedWorldContentScriptEntrypointOptions,
|
||||
wxt.config.browser,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
async function getSidepanelEntrypoint(
|
||||
info: EntrypointInfo,
|
||||
options: Record<string, any>,
|
||||
): Promise<SidepanelEntrypoint> {
|
||||
const options = await getHtmlEntrypointOptions<SidepanelEntrypointOptions>(
|
||||
info,
|
||||
{
|
||||
browserStyle: 'browser_style',
|
||||
exclude: 'exclude',
|
||||
include: 'include',
|
||||
defaultIcon: 'default_icon',
|
||||
defaultTitle: 'default_title',
|
||||
openAtInstall: 'open_at_install',
|
||||
},
|
||||
{
|
||||
defaultTitle: (document) =>
|
||||
document.querySelector('title')?.textContent || undefined,
|
||||
},
|
||||
{
|
||||
defaultTitle: (content) => content,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
type: 'sidepanel',
|
||||
name: info.name,
|
||||
options: resolvePerBrowserOptions(options, wxt.config.browser),
|
||||
options: resolvePerBrowserOptions(
|
||||
{
|
||||
browserStyle: options.browserStyle,
|
||||
exclude: options.exclude,
|
||||
include: options.include,
|
||||
defaultIcon: options.defaultIcon,
|
||||
defaultTitle: options.title,
|
||||
openAtInstall: options.openAtInstall,
|
||||
},
|
||||
wxt.config.browser,
|
||||
),
|
||||
inputPath: info.inputPath,
|
||||
outputDir: wxt.config.outDir,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the HTML tags to extract options from them.
|
||||
*/
|
||||
async function getHtmlEntrypointOptions<T extends BaseEntrypointOptions>(
|
||||
info: EntrypointInfo,
|
||||
keyMap: Record<keyof T, string>,
|
||||
queries?: Partial<{
|
||||
[key in keyof T]: (
|
||||
document: Document,
|
||||
manifestKey: string,
|
||||
) => string | undefined;
|
||||
}>,
|
||||
parsers?: Partial<{ [key in keyof T]: (content: string) => T[key] }>,
|
||||
): Promise<T> {
|
||||
const content = await fs.readFile(info.inputPath, 'utf-8');
|
||||
const { document } = parseHTML(content);
|
||||
|
||||
const options = {} as T;
|
||||
|
||||
const defaultQuery = (manifestKey: string) =>
|
||||
document
|
||||
.querySelector(`meta[name='manifest.${manifestKey}']`)
|
||||
?.getAttribute('content');
|
||||
|
||||
Object.entries(keyMap).forEach(([_key, manifestKey]) => {
|
||||
const key = _key as keyof T;
|
||||
const content = queries?.[key]
|
||||
? queries[key]!(document, manifestKey)
|
||||
: defaultQuery(manifestKey);
|
||||
if (content) {
|
||||
try {
|
||||
options[key] = (parsers?.[key] ?? JSON5.parse)(content);
|
||||
} catch (err) {
|
||||
wxt.logger.fatal(
|
||||
`Failed to parse meta tag content. Usually this means you have invalid JSON5 content (content=${content})`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function isEntrypointSkipped(entry: Omit<Entrypoint, 'skipped'>): boolean {
|
||||
if (wxt.config.filterEntrypoints != null) {
|
||||
return !wxt.config.filterEntrypoints.has(entry.name);
|
||||
|
||||
@@ -112,6 +112,9 @@ function getEsbuildOptions(opts: JitiTransformOptions): TransformOptions {
|
||||
return {
|
||||
format: 'cjs',
|
||||
loader: isJsx ? 'tsx' : 'ts',
|
||||
define: {
|
||||
'import.meta.env.ENTRYPOINT': '"build"',
|
||||
},
|
||||
...(isJsx
|
||||
? {
|
||||
// `h` and `Fragment` are undefined, but that's OK because JSX is never evaluated while
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
ResolvedPerBrowserOptions,
|
||||
TargetBrowser,
|
||||
} from '../../types';
|
||||
import path, { relative, resolve } from 'node:path';
|
||||
import path, { relative, resolve, extname } from 'node:path';
|
||||
import { normalizePath } from './paths';
|
||||
|
||||
export function getEntrypointName(
|
||||
@@ -76,6 +76,21 @@ export function resolvePerBrowserOptions<
|
||||
*
|
||||
* Naively just checking the file extension of the input path.
|
||||
*/
|
||||
export function isHtmlEntrypoint(entrypoint: Entrypoint): boolean {
|
||||
return entrypoint.inputPath.endsWith('.html');
|
||||
export function isHtmlEntrypoint(
|
||||
entrypoint: Pick<Entrypoint, 'inputPath'>,
|
||||
): boolean {
|
||||
const ext = extname(entrypoint.inputPath);
|
||||
return ['.html'].includes(ext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the entrypoint is a JS entrypoint.
|
||||
*
|
||||
* Naively just checking the file extension of the input path.
|
||||
*/
|
||||
export function isJsEntrypoint(
|
||||
entrypoint: Pick<Entrypoint, 'inputPath'>,
|
||||
): boolean {
|
||||
const ext = extname(entrypoint.inputPath);
|
||||
return ['.js', '.jsx', '.ts', '.tsx'].includes(ext);
|
||||
}
|
||||
|
||||
@@ -47,9 +47,11 @@ export async function writeManifest(
|
||||
* Generates the manifest based on the config and entrypoints.
|
||||
*/
|
||||
export async function generateManifest(
|
||||
entrypoints: Entrypoint[],
|
||||
allEntrypoints: Entrypoint[],
|
||||
buildOutput: Omit<BuildOutput, 'manifest'>,
|
||||
): Promise<{ manifest: Manifest.WebExtensionManifest; warnings: any[][] }> {
|
||||
const entrypoints = allEntrypoints.filter((entry) => !entry.skipped);
|
||||
|
||||
const warnings: any[][] = [];
|
||||
const pkg = await getPackageJson();
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
UserManifest,
|
||||
Wxt,
|
||||
SidepanelEntrypoint,
|
||||
BaseEntrypoint,
|
||||
} from '../../../types';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { vi } from 'vitest';
|
||||
@@ -50,7 +51,7 @@ export function fakeDir(root = process.cwd()): string {
|
||||
return resolve(root, faker.string.alphanumeric());
|
||||
}
|
||||
|
||||
export const fakeEntrypoint = () =>
|
||||
export const fakeEntrypoint = (options?: DeepPartial<BaseEntrypoint>) =>
|
||||
faker.helpers.arrayElement([
|
||||
fakePopupEntrypoint,
|
||||
fakeGenericEntrypoint,
|
||||
@@ -58,7 +59,7 @@ export const fakeEntrypoint = () =>
|
||||
fakeBackgroundEntrypoint,
|
||||
fakeContentScriptEntrypoint,
|
||||
fakeUnlistedScriptEntrypoint,
|
||||
])();
|
||||
])(options);
|
||||
|
||||
export const fakeContentScriptEntrypoint =
|
||||
fakeObjectCreator<ContentScriptEntrypoint>(() => ({
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -36,6 +36,14 @@ export async function registerWxt(
|
||||
return config.logger;
|
||||
},
|
||||
async reloadConfig() {
|
||||
// Prevent changing the server port when resolving config multiple times
|
||||
// get-port-please doesn't always return the same port if it was recently closed.
|
||||
if (wxt.config.dev.server?.port) {
|
||||
inlineConfig.dev ??= {};
|
||||
inlineConfig.dev.server ??= {};
|
||||
inlineConfig.dev.server.port = wxt.config.dev.server.port;
|
||||
}
|
||||
|
||||
wxt.config = await resolveConfig(inlineConfig, command);
|
||||
await wxt.hooks.callHook('config:resolved', wxt);
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getPackageJson } from './utils/package';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { formatDuration } from './utils/time';
|
||||
import { printFileList } from './utils/log/printFileList';
|
||||
import { internalBuild } from './utils/building';
|
||||
import { findEntrypoints, internalBuild } from './utils/building';
|
||||
import { registerWxt, wxt } from './wxt';
|
||||
import JSZip from 'jszip';
|
||||
import glob from 'fast-glob';
|
||||
@@ -55,6 +55,14 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
await wxt.hooks.callHook('zip:extension:done', wxt, outZipPath);
|
||||
|
||||
if (wxt.config.zip.zipSources) {
|
||||
const entrypoints = await findEntrypoints();
|
||||
const skippedEntrypoints = entrypoints.filter((entry) => entry.skipped);
|
||||
const excludeSources = [
|
||||
...wxt.config.zip.excludeSources,
|
||||
...skippedEntrypoints.map((entry) =>
|
||||
path.relative(wxt.config.zip.sourcesRoot, entry.inputPath),
|
||||
),
|
||||
].map((paths) => paths.replaceAll('\\', '/'));
|
||||
await wxt.hooks.callHook('zip:sources:start', wxt);
|
||||
const { overrides, files: downloadedPackages } =
|
||||
await downloadPrivatePackages();
|
||||
@@ -65,7 +73,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
|
||||
);
|
||||
await zipDir(wxt.config.zip.sourcesRoot, sourcesZipPath, {
|
||||
include: wxt.config.zip.includeSources,
|
||||
exclude: wxt.config.zip.excludeSources,
|
||||
exclude: excludeSources,
|
||||
transform(absolutePath, zipPath, content) {
|
||||
if (zipPath.endsWith('package.json')) {
|
||||
return addOverridesToPackageJson(absolutePath, content, overrides);
|
||||
|
||||
@@ -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;
|
||||
@@ -1035,9 +1042,13 @@ export interface WxtBuilder {
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* Import the entrypoint file, returning the default export containing the options.
|
||||
* Import a JS entrypoint file, returning the default export containing the options.
|
||||
*/
|
||||
importEntrypoint<T>(path: string): Promise<T>;
|
||||
/**
|
||||
* Import a list of JS entrypoint files, returning their options.
|
||||
*/
|
||||
importEntrypoints(paths: string[]): Promise<Record<string, unknown>[]>;
|
||||
/**
|
||||
* Build a single entrypoint group. This is effectively one of the multiple "steps" during the
|
||||
* build process.
|
||||
@@ -1087,6 +1098,7 @@ export interface WxtBuilderServer {
|
||||
* Chokidar file watcher instance.
|
||||
*/
|
||||
watcher: FSWatcher;
|
||||
on?(event: string, callback: () => void): void;
|
||||
}
|
||||
|
||||
export interface ServerInfo {
|
||||
@@ -1178,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
|
||||
@@ -1411,6 +1429,8 @@ export interface FsCache {
|
||||
export interface ExtensionRunner {
|
||||
openBrowser(): Promise<void>;
|
||||
closeBrowser(): Promise<void>;
|
||||
/** Whether or not this runner actually opens the browser. */
|
||||
canOpen?(): boolean;
|
||||
}
|
||||
|
||||
export type EslintGlobalsPropValue =
|
||||
|
||||
Generated
+224
-100
@@ -160,27 +160,27 @@ importers:
|
||||
packages/module-react:
|
||||
dependencies:
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(vite@5.4.11(@types/node@20.17.6)(sass@1.80.7))
|
||||
specifier: ^4.3.4
|
||||
version: 4.3.4(vite@5.4.11(@types/node@20.17.6)(sass@1.80.7))
|
||||
devDependencies:
|
||||
'@aklinker1/check':
|
||||
specifier: ^1.4.5
|
||||
version: 1.4.5(typescript@5.6.3)
|
||||
'@types/react':
|
||||
specifier: ^18.3.12
|
||||
version: 18.3.12
|
||||
specifier: ^19.0.1
|
||||
version: 19.0.1
|
||||
'@types/react-dom':
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
specifier: ^19.0.2
|
||||
version: 19.0.2(@types/react@19.0.1)
|
||||
publint:
|
||||
specifier: ^0.2.12
|
||||
version: 0.2.12
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
specifier: ^19.0.0
|
||||
version: 19.0.0
|
||||
react-dom:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1(react@18.3.1)
|
||||
specifier: ^19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
typescript:
|
||||
specifier: ^5.6.3
|
||||
version: 5.6.3
|
||||
@@ -219,7 +219,7 @@ importers:
|
||||
packages/module-svelte:
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^4.0.0
|
||||
specifier: ^4.0.0 || ^5.0.0
|
||||
version: 4.0.0(svelte@5.1.6)(vite@5.4.11(@types/node@20.17.6)(sass@1.80.7))
|
||||
svelte:
|
||||
specifier: '>=5'
|
||||
@@ -330,6 +330,9 @@ importers:
|
||||
|
||||
packages/wxt:
|
||||
dependencies:
|
||||
'@1natsu/wait-element':
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2
|
||||
'@aklinker1/rollup-plugin-visualizer':
|
||||
specifier: 5.12.0
|
||||
version: 5.12.0(rollup@4.24.0)
|
||||
@@ -454,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
|
||||
@@ -524,21 +527,21 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../i18n
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
specifier: ^19.0.0
|
||||
version: 19.0.0
|
||||
react-dom:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1(react@18.3.1)
|
||||
specifier: ^19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
devDependencies:
|
||||
'@types/chrome':
|
||||
specifier: ^0.0.280
|
||||
version: 0.0.280
|
||||
'@types/react':
|
||||
specifier: ^18.3.12
|
||||
version: 18.3.12
|
||||
specifier: ^19.0.1
|
||||
version: 19.0.1
|
||||
'@types/react-dom':
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
specifier: ^19.0.2
|
||||
version: 19.0.2(@types/react@19.0.1)
|
||||
'@wxt-dev/auto-icons':
|
||||
specifier: workspace:*
|
||||
version: link:../auto-icons
|
||||
@@ -566,6 +569,9 @@ importers:
|
||||
|
||||
packages:
|
||||
|
||||
'@1natsu/wait-element@4.1.2':
|
||||
resolution: {integrity: sha512-qWxSJD+Q5b8bKOvESFifvfZ92DuMsY+03SBNjTO34ipJLP6mZ9yK4bQz/vlh48aEQXoJfaZBqUwKL5BdI5iiWw==}
|
||||
|
||||
'@aklinker1/buildc@1.1.4':
|
||||
resolution: {integrity: sha512-3SQ2zVEsX+UWg/mHfp9TqU9OPI8abRGTOadChQB0Aqb4dtIy311pBddndVPuuD4uQfIhKbvPxkzZ0rwvlw52Rw==}
|
||||
hasBin: true
|
||||
@@ -669,20 +675,24 @@ packages:
|
||||
resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/code-frame@7.26.2':
|
||||
resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/compat-data@7.24.7':
|
||||
resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/compat-data@7.25.7':
|
||||
resolution: {integrity: sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==}
|
||||
'@babel/compat-data@7.26.3':
|
||||
resolution: {integrity: sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/core@7.24.7':
|
||||
resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/core@7.25.7':
|
||||
resolution: {integrity: sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==}
|
||||
'@babel/core@7.26.0':
|
||||
resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/generator@7.24.7':
|
||||
@@ -693,12 +703,16 @@ packages:
|
||||
resolution: {integrity: sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/generator@7.26.3':
|
||||
resolution: {integrity: sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-compilation-targets@7.24.7':
|
||||
resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-compilation-targets@7.25.7':
|
||||
resolution: {integrity: sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==}
|
||||
'@babel/helper-compilation-targets@7.25.9':
|
||||
resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-environment-visitor@7.24.7':
|
||||
@@ -721,8 +735,8 @@ packages:
|
||||
resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-module-imports@7.25.7':
|
||||
resolution: {integrity: sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==}
|
||||
'@babel/helper-module-imports@7.25.9':
|
||||
resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-module-transforms@7.24.7':
|
||||
@@ -731,8 +745,8 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
|
||||
'@babel/helper-module-transforms@7.25.7':
|
||||
resolution: {integrity: sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==}
|
||||
'@babel/helper-module-transforms@7.26.0':
|
||||
resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
@@ -741,12 +755,12 @@ packages:
|
||||
resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-simple-access@7.24.7':
|
||||
resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==}
|
||||
'@babel/helper-plugin-utils@7.25.9':
|
||||
resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-simple-access@7.25.7':
|
||||
resolution: {integrity: sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==}
|
||||
'@babel/helper-simple-access@7.24.7':
|
||||
resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-split-export-declaration@7.24.7':
|
||||
@@ -765,6 +779,10 @@ packages:
|
||||
resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-string-parser@7.25.9':
|
||||
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.24.7':
|
||||
resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -773,20 +791,24 @@ packages:
|
||||
resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.25.9':
|
||||
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-option@7.24.7':
|
||||
resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-option@7.25.7':
|
||||
resolution: {integrity: sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==}
|
||||
'@babel/helper-validator-option@7.25.9':
|
||||
resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helpers@7.24.7':
|
||||
resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helpers@7.25.7':
|
||||
resolution: {integrity: sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==}
|
||||
'@babel/helpers@7.26.0':
|
||||
resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/highlight@7.24.7':
|
||||
@@ -812,20 +834,25 @@ packages:
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/parser@7.26.3':
|
||||
resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/plugin-syntax-jsx@7.24.7':
|
||||
resolution: {integrity: sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx-self@7.24.7':
|
||||
resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==}
|
||||
'@babel/plugin-transform-react-jsx-self@7.25.9':
|
||||
resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx-source@7.24.7':
|
||||
resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==}
|
||||
'@babel/plugin-transform-react-jsx-source@7.25.9':
|
||||
resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
@@ -846,6 +873,10 @@ packages:
|
||||
resolution: {integrity: sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.25.9':
|
||||
resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/traverse@7.24.7':
|
||||
resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -854,6 +885,10 @@ packages:
|
||||
resolution: {integrity: sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/traverse@7.26.4':
|
||||
resolution: {integrity: sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.24.7':
|
||||
resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -866,6 +901,10 @@ packages:
|
||||
resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.26.3':
|
||||
resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@bcoe/v8-coverage@0.2.3':
|
||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||
|
||||
@@ -1946,12 +1985,17 @@ packages:
|
||||
'@types/prop-types@15.7.9':
|
||||
resolution: {integrity: sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==}
|
||||
|
||||
'@types/react-dom@18.3.1':
|
||||
resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==}
|
||||
'@types/react-dom@19.0.2':
|
||||
resolution: {integrity: sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==}
|
||||
peerDependencies:
|
||||
'@types/react': ^19.0.0
|
||||
|
||||
'@types/react@18.3.12':
|
||||
resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==}
|
||||
|
||||
'@types/react@19.0.1':
|
||||
resolution: {integrity: sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==}
|
||||
|
||||
'@types/resolve@1.20.2':
|
||||
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
|
||||
|
||||
@@ -2050,11 +2094,11 @@ packages:
|
||||
peerDependencies:
|
||||
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0
|
||||
|
||||
'@vitejs/plugin-react@4.3.3':
|
||||
resolution: {integrity: sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==}
|
||||
'@vitejs/plugin-react@4.3.4':
|
||||
resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
vite: ^4.2.0 || ^5.0.0
|
||||
vite: ^4.2.0 || ^5.0.0 || ^6.0.0
|
||||
|
||||
'@vitejs/plugin-vue@5.2.0':
|
||||
resolution: {integrity: sha512-7n7KdUEtx/7Yl7I/WVAMZ1bEb0eVvXF3ummWTeLcs/9gvo9pJhuLdouSXGjdZ/MKD1acf1I272+X0RMua4/R3g==}
|
||||
@@ -3546,6 +3590,9 @@ packages:
|
||||
make-error@1.3.6:
|
||||
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
||||
|
||||
many-keys-map@2.0.1:
|
||||
resolution: {integrity: sha512-DHnZAD4phTbZ+qnJdjoNEVU1NecYoSdbOOoVmTDH46AuxDkEVh3MxTVpXq10GtcTC6mndN9dkv1rNfpjRcLnOw==}
|
||||
|
||||
mark.js@8.11.1:
|
||||
resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==}
|
||||
|
||||
@@ -4213,6 +4260,11 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
|
||||
react-dom@19.0.0:
|
||||
resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==}
|
||||
peerDependencies:
|
||||
react: ^19.0.0
|
||||
|
||||
react-refresh@0.14.2:
|
||||
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -4221,6 +4273,10 @@ packages:
|
||||
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
react@19.0.0:
|
||||
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
||||
|
||||
@@ -4341,6 +4397,9 @@ packages:
|
||||
scheduler@0.23.2:
|
||||
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
||||
|
||||
scheduler@0.25.0:
|
||||
resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==}
|
||||
|
||||
scule@1.3.0:
|
||||
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
||||
|
||||
@@ -5113,6 +5172,11 @@ packages:
|
||||
|
||||
snapshots:
|
||||
|
||||
'@1natsu/wait-element@4.1.2':
|
||||
dependencies:
|
||||
defu: 6.1.4
|
||||
many-keys-map: 2.0.1
|
||||
|
||||
'@aklinker1/buildc@1.1.4':
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
@@ -5255,9 +5319,15 @@ snapshots:
|
||||
'@babel/highlight': 7.25.7
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@babel/code-frame@7.26.2':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
js-tokens: 4.0.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@babel/compat-data@7.24.7': {}
|
||||
|
||||
'@babel/compat-data@7.25.7': {}
|
||||
'@babel/compat-data@7.26.3': {}
|
||||
|
||||
'@babel/core@7.24.7':
|
||||
dependencies:
|
||||
@@ -5279,18 +5349,18 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/core@7.25.7':
|
||||
'@babel/core@7.26.0':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@babel/code-frame': 7.25.7
|
||||
'@babel/generator': 7.25.7
|
||||
'@babel/helper-compilation-targets': 7.25.7
|
||||
'@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.7)
|
||||
'@babel/helpers': 7.25.7
|
||||
'@babel/parser': 7.25.7
|
||||
'@babel/template': 7.25.7
|
||||
'@babel/traverse': 7.25.7
|
||||
'@babel/types': 7.25.7
|
||||
'@babel/code-frame': 7.26.2
|
||||
'@babel/generator': 7.26.3
|
||||
'@babel/helper-compilation-targets': 7.25.9
|
||||
'@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0)
|
||||
'@babel/helpers': 7.26.0
|
||||
'@babel/parser': 7.26.3
|
||||
'@babel/template': 7.25.9
|
||||
'@babel/traverse': 7.26.4
|
||||
'@babel/types': 7.26.3
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.3.7
|
||||
gensync: 1.0.0-beta.2
|
||||
@@ -5313,6 +5383,14 @@ snapshots:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jsesc: 3.0.2
|
||||
|
||||
'@babel/generator@7.26.3':
|
||||
dependencies:
|
||||
'@babel/parser': 7.26.3
|
||||
'@babel/types': 7.26.3
|
||||
'@jridgewell/gen-mapping': 0.3.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jsesc: 3.0.2
|
||||
|
||||
'@babel/helper-compilation-targets@7.24.7':
|
||||
dependencies:
|
||||
'@babel/compat-data': 7.24.7
|
||||
@@ -5321,17 +5399,17 @@ snapshots:
|
||||
lru-cache: 5.1.1
|
||||
semver: 6.3.1
|
||||
|
||||
'@babel/helper-compilation-targets@7.25.7':
|
||||
'@babel/helper-compilation-targets@7.25.9':
|
||||
dependencies:
|
||||
'@babel/compat-data': 7.25.7
|
||||
'@babel/helper-validator-option': 7.25.7
|
||||
'@babel/compat-data': 7.26.3
|
||||
'@babel/helper-validator-option': 7.25.9
|
||||
browserslist: 4.24.0
|
||||
lru-cache: 5.1.1
|
||||
semver: 6.3.1
|
||||
|
||||
'@babel/helper-environment-visitor@7.24.7':
|
||||
dependencies:
|
||||
'@babel/types': 7.25.6
|
||||
'@babel/types': 7.25.7
|
||||
|
||||
'@babel/helper-function-name@7.24.7':
|
||||
dependencies:
|
||||
@@ -5353,10 +5431,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/helper-module-imports@7.25.7':
|
||||
'@babel/helper-module-imports@7.25.9':
|
||||
dependencies:
|
||||
'@babel/traverse': 7.25.7
|
||||
'@babel/types': 7.25.7
|
||||
'@babel/traverse': 7.26.4
|
||||
'@babel/types': 7.26.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -5371,26 +5449,20 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/helper-module-transforms@7.25.7(@babel/core@7.25.7)':
|
||||
'@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)':
|
||||
dependencies:
|
||||
'@babel/core': 7.25.7
|
||||
'@babel/helper-module-imports': 7.25.7
|
||||
'@babel/helper-simple-access': 7.25.7
|
||||
'@babel/helper-validator-identifier': 7.25.7
|
||||
'@babel/traverse': 7.25.7
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/helper-module-imports': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
'@babel/traverse': 7.26.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/helper-plugin-utils@7.24.7': {}
|
||||
|
||||
'@babel/helper-simple-access@7.24.7':
|
||||
dependencies:
|
||||
'@babel/traverse': 7.24.7
|
||||
'@babel/types': 7.25.6
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
'@babel/helper-plugin-utils@7.25.9': {}
|
||||
|
||||
'@babel/helper-simple-access@7.25.7':
|
||||
'@babel/helper-simple-access@7.24.7':
|
||||
dependencies:
|
||||
'@babel/traverse': 7.25.7
|
||||
'@babel/types': 7.25.7
|
||||
@@ -5399,7 +5471,7 @@ snapshots:
|
||||
|
||||
'@babel/helper-split-export-declaration@7.24.7':
|
||||
dependencies:
|
||||
'@babel/types': 7.25.6
|
||||
'@babel/types': 7.25.7
|
||||
|
||||
'@babel/helper-string-parser@7.24.7': {}
|
||||
|
||||
@@ -5407,23 +5479,27 @@ snapshots:
|
||||
|
||||
'@babel/helper-string-parser@7.25.7': {}
|
||||
|
||||
'@babel/helper-string-parser@7.25.9': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.24.7': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.25.7': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.25.9': {}
|
||||
|
||||
'@babel/helper-validator-option@7.24.7': {}
|
||||
|
||||
'@babel/helper-validator-option@7.25.7': {}
|
||||
'@babel/helper-validator-option@7.25.9': {}
|
||||
|
||||
'@babel/helpers@7.24.7':
|
||||
dependencies:
|
||||
'@babel/template': 7.24.7
|
||||
'@babel/types': 7.25.6
|
||||
|
||||
'@babel/helpers@7.25.7':
|
||||
'@babel/helpers@7.26.0':
|
||||
dependencies:
|
||||
'@babel/template': 7.25.7
|
||||
'@babel/types': 7.25.7
|
||||
'@babel/template': 7.25.9
|
||||
'@babel/types': 7.26.3
|
||||
|
||||
'@babel/highlight@7.24.7':
|
||||
dependencies:
|
||||
@@ -5451,20 +5527,24 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.25.7
|
||||
|
||||
'@babel/parser@7.26.3':
|
||||
dependencies:
|
||||
'@babel/types': 7.26.3
|
||||
|
||||
'@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.24.7)':
|
||||
dependencies:
|
||||
'@babel/core': 7.24.7
|
||||
'@babel/helper-plugin-utils': 7.24.7
|
||||
|
||||
'@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.7)':
|
||||
'@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)':
|
||||
dependencies:
|
||||
'@babel/core': 7.25.7
|
||||
'@babel/helper-plugin-utils': 7.24.7
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/helper-plugin-utils': 7.25.9
|
||||
|
||||
'@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.7)':
|
||||
'@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)':
|
||||
dependencies:
|
||||
'@babel/core': 7.25.7
|
||||
'@babel/helper-plugin-utils': 7.24.7
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/helper-plugin-utils': 7.25.9
|
||||
|
||||
'@babel/runtime@7.24.7':
|
||||
dependencies:
|
||||
@@ -5484,6 +5564,12 @@ snapshots:
|
||||
'@babel/parser': 7.25.7
|
||||
'@babel/types': 7.25.7
|
||||
|
||||
'@babel/template@7.25.9':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
'@babel/parser': 7.26.3
|
||||
'@babel/types': 7.26.3
|
||||
|
||||
'@babel/traverse@7.24.7':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.24.7
|
||||
@@ -5511,6 +5597,18 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/traverse@7.26.4':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
'@babel/generator': 7.26.3
|
||||
'@babel/parser': 7.26.3
|
||||
'@babel/template': 7.25.9
|
||||
'@babel/types': 7.26.3
|
||||
debug: 4.3.7
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/types@7.24.7':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.24.7
|
||||
@@ -5529,6 +5627,11 @@ snapshots:
|
||||
'@babel/helper-validator-identifier': 7.25.7
|
||||
to-fast-properties: 2.0.0
|
||||
|
||||
'@babel/types@7.26.3':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
|
||||
'@bcoe/v8-coverage@0.2.3': {}
|
||||
|
||||
'@devicefarmer/adbkit-logcat@2.1.3': {}
|
||||
@@ -6320,16 +6423,22 @@ snapshots:
|
||||
'@types/node': 20.17.6
|
||||
kleur: 3.0.3
|
||||
|
||||
'@types/prop-types@15.7.9': {}
|
||||
'@types/prop-types@15.7.9':
|
||||
optional: true
|
||||
|
||||
'@types/react-dom@18.3.1':
|
||||
'@types/react-dom@19.0.2(@types/react@19.0.1)':
|
||||
dependencies:
|
||||
'@types/react': 18.3.12
|
||||
'@types/react': 19.0.1
|
||||
|
||||
'@types/react@18.3.12':
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.9
|
||||
csstype: 3.1.3
|
||||
optional: true
|
||||
|
||||
'@types/react@19.0.1':
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
|
||||
'@types/resolve@1.20.2': {}
|
||||
|
||||
@@ -6497,11 +6606,11 @@ snapshots:
|
||||
- supports-color
|
||||
- vue
|
||||
|
||||
'@vitejs/plugin-react@4.3.3(vite@5.4.11(@types/node@20.17.6)(sass@1.80.7))':
|
||||
'@vitejs/plugin-react@4.3.4(vite@5.4.11(@types/node@20.17.6)(sass@1.80.7))':
|
||||
dependencies:
|
||||
'@babel/core': 7.25.7
|
||||
'@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.7)
|
||||
'@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.7)
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0)
|
||||
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0)
|
||||
'@types/babel__core': 7.20.5
|
||||
react-refresh: 0.14.2
|
||||
vite: 5.4.11(@types/node@20.17.6)(sass@1.80.7)
|
||||
@@ -6913,7 +7022,7 @@ snapshots:
|
||||
|
||||
caniuse-api@3.0.0:
|
||||
dependencies:
|
||||
browserslist: 4.23.1
|
||||
browserslist: 4.24.0
|
||||
caniuse-lite: 1.0.30001633
|
||||
lodash.memoize: 4.1.2
|
||||
lodash.uniq: 4.5.0
|
||||
@@ -7868,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:
|
||||
@@ -8086,6 +8195,7 @@ snapshots:
|
||||
loose-envify@1.4.0:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
optional: true
|
||||
|
||||
loupe@3.1.1:
|
||||
dependencies:
|
||||
@@ -8127,6 +8237,8 @@ snapshots:
|
||||
|
||||
make-error@1.3.6: {}
|
||||
|
||||
many-keys-map@2.0.1: {}
|
||||
|
||||
mark.js@8.11.1: {}
|
||||
|
||||
markdown-it-footnote@4.0.0(patch_hash=ue34jdgdx43siqdj557feoepzq): {}
|
||||
@@ -8719,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:
|
||||
@@ -8810,12 +8922,21 @@ snapshots:
|
||||
loose-envify: 1.4.0
|
||||
react: 18.3.1
|
||||
scheduler: 0.23.2
|
||||
optional: true
|
||||
|
||||
react-dom@19.0.0(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
scheduler: 0.25.0
|
||||
|
||||
react-refresh@0.14.2: {}
|
||||
|
||||
react@18.3.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
optional: true
|
||||
|
||||
react@19.0.0: {}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
dependencies:
|
||||
@@ -8954,6 +9075,9 @@ snapshots:
|
||||
scheduler@0.23.2:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
optional: true
|
||||
|
||||
scheduler@0.25.0: {}
|
||||
|
||||
scule@1.3.0: {}
|
||||
|
||||
@@ -9170,7 +9294,7 @@ snapshots:
|
||||
|
||||
stylehacks@7.0.2(postcss@8.4.39):
|
||||
dependencies:
|
||||
browserslist: 4.23.1
|
||||
browserslist: 4.24.0
|
||||
postcss: 8.4.39
|
||||
postcss-selector-parser: 6.1.0
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
"postinstall": "wxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chrome": "^0.0.280",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@wxt-dev/module-react": "^1.1.1",
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"@wxt-dev/module-react": "^1.1.2",
|
||||
"typescript": "^5.6.3",
|
||||
"wxt": "^0.19.13"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user