Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f14aa1c71 | |||
| f771e6a122 | |||
| a0a23947b6 | |||
| 7b6f1dc88b | |||
| 16f9e1705f | |||
| 40e28b702d | |||
| 75f0b843f8 | |||
| 8793385325 | |||
| 4f86143853 | |||
| 498f74b21b | |||
| 300798eb11 | |||
| d482fe6832 | |||
| 955bb20914 | |||
| 22339d261b | |||
| 5d89a40c6d | |||
| c1fe602c8b | |||
| bea5e0da1e | |||
| 7907e56a31 | |||
| 4e43e6a315 | |||
| 78ef08390e | |||
| b5235ed4f0 | |||
| 868a567ebd | |||
| 0797041643 | |||
| 60b22be29d | |||
| 1a817e1367 | |||
| 2be3044f6a | |||
| 59f6f4b926 | |||
| 3d2d36954e | |||
| e44cb3950f | |||
| 06682a6edc | |||
| e6e513a34b | |||
| 13dd3742a8 | |||
| c27278ecfb | |||
| 2f68c6e344 | |||
| a7976d2e63 | |||
| c15167ca86 | |||
| e75e534882 | |||
| b21ec34a65 | |||
| e45b1fb8df | |||
| 0b3c3ab39d | |||
| 8b530bc1c9 | |||
| a2212f7a89 | |||
| e613b54d94 | |||
| d9c140114a | |||
| 9b59f38c0d | |||
| 2a8ec0d786 | |||
| e29fad16ea | |||
| 50d223a30a | |||
| 1dbdf4ea9e | |||
| 09b5a19577 | |||
| b5ea72e214 | |||
| 57e3748d79 | |||
| 155626fbed | |||
| e802aa211f | |||
| 54fe8c6687 | |||
| 5da6e4ff75 | |||
| 4090bb100e | |||
| 54ed99d91d | |||
| 3db3eac62a | |||
| 0315882fa4 | |||
| 9757e2634f | |||
| 42b55f66fb | |||
| e64058bc94 | |||
| 386503cce0 | |||
| 6224ff49f6 | |||
| 6578be00ce | |||
| 2f325d898f |
@@ -7,3 +7,6 @@
|
||||
# Creator of specific wxt modules
|
||||
/packages/auto-icons/ @Timeraa
|
||||
/packages/unocss/ @Timeraa
|
||||
|
||||
# Other Directories
|
||||
/docs @aklinker1 @PatrykKuniczak
|
||||
|
||||
+12
-7
@@ -1,15 +1,20 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
- package-ecosystem: bun
|
||||
directory: /
|
||||
schedule:
|
||||
interval: 'monthly'
|
||||
interval: 'daily'
|
||||
groups:
|
||||
production-updates:
|
||||
applies-to: version-updates
|
||||
dependency-type: production
|
||||
update-types: [minor, patch]
|
||||
dev-updates:
|
||||
applies-to: version-updates
|
||||
dependency-type: development
|
||||
update-types: [minor, patch]
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'monthly'
|
||||
interval: 'daily'
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
- name: Gather Info
|
||||
id: check
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
- name: Sync Author
|
||||
if: steps.check.outputs.has_assignees == 'false'
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.addAssignees({
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
console.log('Assigned PR author: ${{ steps.check.outputs.author }}');
|
||||
|
||||
- name: Sync Labels
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const newLabels = ${{ steps.check.outputs.labels }};
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
run: bun run buildc all
|
||||
|
||||
- name: Publish
|
||||
run: bun pkg-pr-new publish --compact --bun './packages/*'
|
||||
run: bunx pkg-pr-new publish --compact --bun './packages/*'
|
||||
|
||||
- name: DEBUG
|
||||
if: always()
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
github.event.pull_request.user.login != 'PatrykKuniczak' &&
|
||||
github.event.pull_request.user.login != 'dependabot[bot]' &&
|
||||
github.event.pull_request.user.login != 'copilot-swe-agent[bot]'
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
comment: Thanks for helping make WXT better!
|
||||
with:
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: https://${{ secrets.DOCKER_REGISTRY_HOSTNAME }}
|
||||
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
|
||||
@@ -44,4 +44,4 @@ jobs:
|
||||
run: docker push ${{ secrets.DOCKER_REGISTRY_HOSTNAME }}/wxt/docs:${{ github.event.inputs.tag || 'latest' }}
|
||||
|
||||
- name: Deploy
|
||||
run: curl -X POST -i ${{ secrets.UPDATE_DOCS_WEBHOOK }}
|
||||
run: 'curl -X POST -i -H "X-API-Key: ${{ secrets.DOCS_WEBHOOK_API_KEY }}" https://${{ secrets.DOCS_WEBHOOK_DOMAIN }}/api/webhook/stacks/${{ secrets.DOCS_WEBHOOK_STACK_ID }}'
|
||||
|
||||
@@ -74,9 +74,9 @@ jobs:
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
uses: pnpm/action-setup@91ab88e2619ed1f46221f0ba42d1492c02baf788 # v6.0.6
|
||||
with:
|
||||
version: latest
|
||||
version: 10.33.0
|
||||
|
||||
- name: Run Tests
|
||||
if: ${{ ! matrix.coverage }}
|
||||
|
||||
@@ -21,10 +21,10 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
||||
uses: pnpm/action-setup@91ab88e2619ed1f46221f0ba42d1492c02baf788 # v6.0.6
|
||||
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
cache: pnpm
|
||||
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
bun 1.3.12
|
||||
bun 1.3.13
|
||||
nodejs 24.14.1
|
||||
|
||||
@@ -11,6 +11,7 @@ ignorePaths:
|
||||
- patches/**
|
||||
- '**/*.snap'
|
||||
- docs/assets/extension-showcase.yml
|
||||
- packages/*/stats.html
|
||||
words:
|
||||
- Aabid
|
||||
- aabidk
|
||||
@@ -21,6 +22,7 @@ words:
|
||||
- buildc
|
||||
- bunx
|
||||
- cachable
|
||||
- cacjs
|
||||
- charmbracelet
|
||||
- chromedriver
|
||||
- Cira
|
||||
@@ -78,6 +80,7 @@ words:
|
||||
- Noto
|
||||
- ntvs
|
||||
- nypm
|
||||
- obug
|
||||
- ohash
|
||||
- oklab
|
||||
- oxlint
|
||||
|
||||
@@ -3,8 +3,9 @@ import { computed } from 'vue';
|
||||
// @ts-expect-error: Vitepress data-loader magic, this import is correct
|
||||
import { data } from '../loaders/blog.data';
|
||||
import BlogPostPreview from './BlogPostPreview.vue';
|
||||
import { Post } from '../utils/types';
|
||||
|
||||
const posts = computed(() =>
|
||||
const posts = computed<Post[]>(() =>
|
||||
data
|
||||
.map((post) => ({
|
||||
...post,
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
import useBlogDate from '../composables/useBlogDate';
|
||||
import { useData } from 'vitepress';
|
||||
import { Content, useData } from 'vitepress';
|
||||
import { PostFrontmatter } from '../utils/types';
|
||||
|
||||
const { frontmatter } = useData();
|
||||
const { frontmatter } = useData<PostFrontmatter>();
|
||||
const date = useBlogDate(() => frontmatter.value.date);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vp-doc">
|
||||
<main class="container-content">
|
||||
<h1 v-html="$frontmatter.title" />
|
||||
<h1 v-html="frontmatter.title" />
|
||||
<p class="meta-row">
|
||||
<a
|
||||
class="author"
|
||||
v-for="author of $frontmatter.authors"
|
||||
v-for="author of frontmatter.authors"
|
||||
:key="author.github"
|
||||
:href="`https://github.com/${author.github}`"
|
||||
class="author"
|
||||
>
|
||||
<img :src="`https://github.com/${author.github}.png?size=96`" />
|
||||
<span>{{ author.name }}</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
patterns: Array<[intput: string, output: string]>;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, computed, toRaw, Ref } from 'vue';
|
||||
import { computed, onMounted, ref, Ref, toRaw } from 'vue';
|
||||
import ExampleSearchFilterByItem from './ExampleSearchFilterByItem.vue';
|
||||
import ExampleSearchResult from './ExampleSearchResult.vue';
|
||||
import { ExamplesMetadata, KeySelectedObject } from '../utils/types';
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
tag?: string;
|
||||
}>();
|
||||
|
||||
@@ -48,7 +48,7 @@ function doesExampleMatchSelected(
|
||||
|
||||
const filteredExamples = computed(() => {
|
||||
const text = searchText.value.toLowerCase();
|
||||
return exampleMetadata.value.examples.filter((example) => {
|
||||
return exampleMetadata.value?.examples.filter((example) => {
|
||||
const matchesText = example.searchText.toLowerCase().includes(text);
|
||||
const matchesApis = doesExampleMatchSelected(example.apis, requiredApis);
|
||||
const matchesPermissions = doesExampleMatchSelected(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { computed, toRaw } from 'vue';
|
||||
import { KeySelectedObject } from '../utils/types';
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
label: string;
|
||||
items?: string[];
|
||||
}>();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { Example } from '../utils/types';
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
example: Example;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Feed } from 'feed';
|
||||
// @ts-expect-error; It isn't TypeScript lib
|
||||
import footnote from 'markdown-it-footnote';
|
||||
import fs, { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
@@ -73,8 +74,11 @@ export default defineConfig({
|
||||
description,
|
||||
vite: {
|
||||
clearScreen: false,
|
||||
//TODO: REMOVE THIS @TS-EXPECT-ERROR AFTER BUMP VITEPRESS TO V2.0
|
||||
plugins: [
|
||||
// @ts-expect-error: Vite version mismatch between this project and the plugin
|
||||
llmstxt(),
|
||||
// @ts-expect-error: Vite version mismatch between this project and the plugin
|
||||
groupIconVitePlugin({
|
||||
customIcon: {
|
||||
'wxt.config.ts': localIconLoader(
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { createContentLoader } from 'vitepress';
|
||||
import { type ContentData, createContentLoader } from 'vitepress';
|
||||
import { PostFrontmatter } from '../utils/types';
|
||||
|
||||
export default createContentLoader('blog/*.md');
|
||||
declare const data: Array<ContentData & { frontmatter: PostFrontmatter }>;
|
||||
export { data };
|
||||
|
||||
export default createContentLoader<
|
||||
Array<ContentData & { frontmatter: PostFrontmatter }>
|
||||
>('blog/*.md');
|
||||
|
||||
@@ -56,8 +56,3 @@ async function getPublishExtensionHelp(command: string): Promise<string> {
|
||||
);
|
||||
return res.replace(/\$ publish-extension/g, '$ wxt submit');
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
name: string;
|
||||
docs: string;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ body {
|
||||
background-image: url('/assets/art002e009298.jpg');
|
||||
opacity: 50%;
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
height: 110vh;
|
||||
background-size: cover;
|
||||
background-position: 90%;
|
||||
@@ -82,7 +82,7 @@ body {
|
||||
.dark .VPHome::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
height: 10vh;
|
||||
top: 100vh;
|
||||
background: linear-gradient(to bottom, transparent, var(--vp-c-bg));
|
||||
|
||||
@@ -6,10 +6,11 @@ import ExampleSearch from '../components/ExampleSearch.vue';
|
||||
import BlogLayout from '../components/BlogLayout.vue';
|
||||
import './custom.css';
|
||||
import 'virtual:group-icons.css';
|
||||
import type { EnhanceAppContext } from 'vitepress/client';
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
enhanceApp(ctx) {
|
||||
enhanceApp(ctx: EnhanceAppContext) {
|
||||
ctx.app
|
||||
.component('Icon', Icon)
|
||||
.component('EntrypointPatterns', EntrypointPatterns)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HeadConfig } from 'vitepress/types/shared';
|
||||
import type { HeadConfig } from 'vitepress';
|
||||
|
||||
export function meta(
|
||||
property: string,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { DefaultTheme } from 'vitepress';
|
||||
|
||||
type SidebarItem = DefaultTheme.SidebarItem;
|
||||
type NavItem = DefaultTheme.NavItem;
|
||||
type NavItemWithLink = DefaultTheme.NavItemWithLink;
|
||||
type NavItemWithChildren = DefaultTheme.NavItemWithChildren;
|
||||
type NavItemChildren = DefaultTheme.NavItemChildren;
|
||||
|
||||
@@ -19,7 +17,7 @@ export function navItem(text: string, arg2?: unknown): any {
|
||||
}
|
||||
|
||||
export function menuRoot(items: SidebarItem[]) {
|
||||
return items.map((item, index) => {
|
||||
return items.map((item) => {
|
||||
// item.collapsed = false; // uncomment to expand all level-0 items
|
||||
return item;
|
||||
});
|
||||
@@ -60,12 +58,6 @@ export function menuGroup(
|
||||
throw Error('Unknown overload');
|
||||
}
|
||||
|
||||
export function menuItems(items: SidebarItem[]) {
|
||||
return {
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
export function menuItem(
|
||||
text: string,
|
||||
link: string,
|
||||
@@ -97,9 +89,7 @@ export function prepareTypedocSidebar(items: SidebarItem[]) {
|
||||
}
|
||||
};
|
||||
|
||||
// process
|
||||
prepareItems(filtered);
|
||||
|
||||
// return
|
||||
return filtered;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ContentData } from 'vitepress';
|
||||
|
||||
export interface Example {
|
||||
name: string;
|
||||
description?: string;
|
||||
@@ -16,3 +18,15 @@ export type ExamplesMetadata = {
|
||||
};
|
||||
|
||||
export type KeySelectedObject = Record<string, boolean | undefined>;
|
||||
|
||||
export interface PostFrontmatter {
|
||||
title: string;
|
||||
description?: string;
|
||||
date: Date;
|
||||
authors: { name: string; github: string }[];
|
||||
}
|
||||
|
||||
export interface Post
|
||||
extends Omit<ContentData, 'frontmatter'>, Omit<PostFrontmatter, 'date'> {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
@@ -436,3 +436,25 @@
|
||||
|
||||
- # TF2 Trader - TF2 & Steam Trading Extension - https://github.com/offish/tf2-trader
|
||||
chromeId: gmicpekfpbikhibodgokfpghadkclhoe
|
||||
|
||||
- # Mimik
|
||||
chromeId: jmfohdaflahliammccpiadmkcibohgha
|
||||
firefoxSlug: mimik
|
||||
|
||||
- # Modrinth Extras - https://github.com/creeperkatze/modrinth-extras
|
||||
chromeId: ajmkilipadfpaefpcjfgnkejalmhdlcj
|
||||
firefoxSlug: modrinth-extras
|
||||
|
||||
- # Pi-hole In One - https://github.com/creeperkatze/pihole-in-one
|
||||
chromeId: gaaobidjebianpcngcfpkniaocibidhe
|
||||
firefoxSlug: pihole-in-one
|
||||
|
||||
- # QIE Wallet – Non-Custodial Multi-Chain Crypto Wallet
|
||||
chromeId: oljchdcgmibnjbbopolafbjncfhdacjb
|
||||
|
||||
- # Headstart – Supercharge your browsing efficiency https://useheadstart.app/
|
||||
chromeId: fhhldbfombjdnjnpeojjohloohmbanda
|
||||
|
||||
- # Redmine Time Tracking
|
||||
chromeId: ldcanhhkffokndenejhafhlkapflgcjg
|
||||
firefoxSlug: redmine-time-tracking
|
||||
|
||||
@@ -200,6 +200,23 @@ export default defineConfig({
|
||||
});
|
||||
```
|
||||
|
||||
:::warning
|
||||
|
||||
Different browsers support different permissions. You are responsible for passing only the permissions required for each browser:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
manifest: ({ browser }) => ({
|
||||
permissions:
|
||||
browser === 'chrome'
|
||||
? ['storage', 'favicon', 'declarativeNetRequest']
|
||||
: ['storage', 'webRequest'],
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Host Permissions
|
||||
|
||||
> [Chrome docs](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions#host-permissions)
|
||||
|
||||
@@ -463,8 +463,8 @@ When you define a Newtab entrypoint, WXT will automatically update the manifest
|
||||
<meta
|
||||
name="manifest.default_icon"
|
||||
content="{
|
||||
16: '/icon-16.png',
|
||||
24: '/icon-24.png',
|
||||
'16': '/icon-16.png',
|
||||
'24': '/icon-24.png',
|
||||
...
|
||||
}"
|
||||
/>
|
||||
@@ -554,8 +554,8 @@ Firefox does not support sandboxed pages.
|
||||
<meta
|
||||
name="manifest.default_icon"
|
||||
content="{
|
||||
16: '/icon-16.png',
|
||||
24: '/icon-24.png',
|
||||
'16': '/icon-16.png',
|
||||
'24': '/icon-24.png',
|
||||
...
|
||||
}"
|
||||
/>
|
||||
|
||||
@@ -206,13 +206,13 @@ WXT uses the command `npm pack <package-name>` to download the package. That mea
|
||||
|
||||
WXT does not currently support automated publishing for Safari. Safari extensions require a native MacOS or iOS app wrapper, which WXT does not create yet. For now, if you want to publish to Safari, follow this guide:
|
||||
|
||||
- [Converting a web extension for Safari](https://developer.apple.com/documentation/safariservices/safari_web_extensions/converting_a_web_extension_for_safari) - "Convert your existing extension to a Safari web extension using Xcode’s command-line tool."
|
||||
- [Packaging a web extension for Safari](https://developer.apple.com/documentation/safariservices/packaging-a-web-extension-for-safari) - "Package your existing extension as a Safari web extension using Xcode’s command-line tool."
|
||||
|
||||
When running the `safari-web-extension-converter` CLI tool, pass the `.output/safari-mv2` or `.output/safari-mv3` directory, not your source code directory.
|
||||
When running the `safari-web-extension-packager` CLI tool, pass the `.output/safari-mv2` or `.output/safari-mv3` directory, not your source code directory.
|
||||
|
||||
```sh
|
||||
pnpm wxt build -b safari
|
||||
xcrun safari-web-extension-converter .output/safari-mv2
|
||||
xcrun safari-web-extension-packager .output/safari-mv2
|
||||
```
|
||||
|
||||
### Edge Addons
|
||||
|
||||
@@ -4,33 +4,21 @@ outline: deep
|
||||
|
||||
# WXT Modules
|
||||
|
||||
WXT provides a "module system" that let's you run code at different steps in the build process to modify it.
|
||||
WXT provides a "module system" that lets you run code at different steps in the build process to modify it.
|
||||
|
||||
[[toc]]
|
||||
|
||||
## Adding a Module
|
||||
## Installing a Module
|
||||
|
||||
There are two ways to add a module to your project:
|
||||
To use a published module from NPM, install the package and add it to your config:
|
||||
|
||||
1. **NPM**: install an NPM package, like [`@wxt-dev/auto-icons`](https://www.npmjs.com/package/@wxt-dev/auto-icons) and add it to your config:
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/auto-icons'],
|
||||
});
|
||||
```
|
||||
|
||||
```ts [wxt.config.ts]
|
||||
export default defineConfig({
|
||||
modules: ['@wxt-dev/auto-icons'],
|
||||
});
|
||||
```
|
||||
|
||||
> Searching for ["wxt module"](https://www.npmjs.com/search?q=wxt%20module) on NPM is a good way to find published WXT modules.
|
||||
|
||||
2. **Local**: add a file to your project's `modules/` directory:
|
||||
|
||||
```plaintext
|
||||
<rootDir>/
|
||||
modules/
|
||||
my-module.ts
|
||||
```
|
||||
|
||||
> To learn more about writing your own modules, read the [Writing Modules](/guide/essentials/wxt-modules) docs.
|
||||
> Searching for ["wxt module"](https://www.npmjs.com/search?q=wxt%20module) on NPM is a good way to find published WXT modules.
|
||||
|
||||
## Module Options
|
||||
|
||||
@@ -49,7 +37,7 @@ Modules are loaded in the same order as hooks are executed. Refer to the [Hooks
|
||||
|
||||
## Writing Modules
|
||||
|
||||
Here's what a basic WXT module looks like:
|
||||
If you need custom build logic for your project, you can write your own local module. Here's what a basic WXT module looks like:
|
||||
|
||||
```ts
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
@@ -61,6 +49,14 @@ export default defineWxtModule({
|
||||
});
|
||||
```
|
||||
|
||||
To add it to your project, place the file in the `modules/` directory at the root of your project. Any module file in this directory is **automatically discovered and loaded** — no additional configuration is needed:
|
||||
|
||||
```plaintext
|
||||
<rootDir>/
|
||||
modules/
|
||||
my-module.ts ← loaded automatically
|
||||
```
|
||||
|
||||
Each module's setup function is executed after the `wxt.config.ts` file is loaded. The `wxt` object provides everything you need to write a module:
|
||||
|
||||
- Use `wxt.hook(...)` to hook into the build's lifecycle and make changes
|
||||
@@ -141,6 +137,35 @@ console.log(config.myModule);
|
||||
|
||||
This is very useful when [generating runtime code](#generate-runtime-module).
|
||||
|
||||
#### Logging
|
||||
|
||||
There are two "correct" ways to add console logs to your modules:
|
||||
|
||||
1. Use `wxt.logger` for info, warnings, and errors
|
||||
2. Install [`obug`](https://www.npmjs.com/package/obug) for debug messages
|
||||
|
||||
```ts
|
||||
import { defineWxtModule } from 'wxt/modules';
|
||||
import { createDebug } from 'obug';
|
||||
|
||||
const debug = createDebug('my-module');
|
||||
|
||||
export default defineWxtModule({
|
||||
setup(wxt) {
|
||||
wxt.logger.info('Module loaded');
|
||||
debug('Debug details');
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
`wxt.logger` is great for formatted, pretty messages that match the rest of WXT's logs.
|
||||
|
||||
`obug` makes it easy for devs to enable and filter debug logs when necessary:
|
||||
|
||||
```sh
|
||||
DEBUG=my-module wxt dev
|
||||
```
|
||||
|
||||
#### Add custom entrypoint options
|
||||
|
||||
Modules can add custom options to entrypoints by augmenting the entrypoint options types. This allows you to add custom configuration that can be accessed during the build process.
|
||||
|
||||
+28
-13
@@ -17,48 +17,63 @@
|
||||
"docs:build": "bun run --silent docs:gen && bun run --bun vitepress build docs",
|
||||
"docs:preview": "bun run --silent docs:gen && bun run --bun vitepress preview docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"catalog": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@aklinker1/check": "^2.4.0",
|
||||
"@types/bun": "^1.3.5",
|
||||
"@types/node": "^20.17.6",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@webext-core/fake-browser": "^1.3.4",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"oxlint": "^1.63.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^6.0.3",
|
||||
"unocss": "^66.6.8",
|
||||
"vitest": "^4.1.5",
|
||||
"vitest-plugin-random-seed": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@commitlint/config-conventional": "^20.4.3",
|
||||
"@commitlint/types": "^20.4.3",
|
||||
"@types/bun": "^1.3.5",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@vitest/coverage-v8": "^4.1.5",
|
||||
"changelogen": "^0.6.2",
|
||||
"consola": "^3.4.2",
|
||||
"cspell": "^9.7.0",
|
||||
"feed": "^5.2.0",
|
||||
"markdown-it-footnote": "^4.0.0",
|
||||
"markdownlint-cli": "^0.48.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"nano-staged": "^0.8.0",
|
||||
"nano-spawn": "catalog:",
|
||||
"nano-staged": "^1.0.2",
|
||||
"p-map": "^7.0.4",
|
||||
"pkg-pr-new": "^0.0.66",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-jsdoc": "^1.8.0",
|
||||
"semver": "^7.7.4",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"tsdown": "^0.21.0",
|
||||
"tsdown": "catalog:",
|
||||
"tsx": "4.21.0",
|
||||
"typedoc": "^0.25.4",
|
||||
"typedoc-plugin-frontmatter": "^1.3.1",
|
||||
"typedoc-plugin-markdown": "4.0.0-next.23",
|
||||
"typedoc-vitepress-theme": "1.0.0-next.3",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript": "catalog:",
|
||||
"vitepress": "^1.6.4",
|
||||
"vitepress-knowledge": "^0.4.1",
|
||||
"vitepress-plugin-group-icons": "^1.7.3",
|
||||
"vitepress-plugin-llms": "^1.12.0",
|
||||
"vitest-mock-extended": "^3.1.0",
|
||||
"vue": "^3.5.29",
|
||||
"vitepress-plugin-llms": "^1.12.2",
|
||||
"vitest-mock-extended": "^4.0.0",
|
||||
"vue": "^3.5.34",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"markdown-it-footnote@4.0.0": "patches/markdown-it-footnote.patch",
|
||||
"simple-git-hooks@2.13.1": "patches/simple-git-hooks@2.13.1.patch",
|
||||
"pkg-pr-new@0.0.66": "patches/pkg-pr-new@0.0.66.patch"
|
||||
"simple-git-hooks@2.13.1": "patches/simple-git-hooks@2.13.1.patch"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "./node_modules/.bin/nano-staged"
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
"wxt": ">=0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@@ -52,13 +52,12 @@
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wxt-dev/browser",
|
||||
"description": "Provides a cross-browser API for using extension APIs and types based on @types/chrome",
|
||||
"version": "0.1.40",
|
||||
"version": "0.1.42",
|
||||
"type": "module",
|
||||
"main": "src/index.mjs",
|
||||
"types": "src/index.d.ts",
|
||||
@@ -25,11 +25,11 @@
|
||||
"src"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "0.1.40",
|
||||
"@types/node": "^20.0.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
"@types/chrome": "0.1.42",
|
||||
"@types/node": "catalog:",
|
||||
"nano-spawn": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/filesystem": "*",
|
||||
|
||||
@@ -58,6 +58,8 @@ for (const { file, srcPath, destPath } of declarationFileMapping) {
|
||||
console.log(` ${styleText('dim', '-')} ${styleText('cyan', file)}`);
|
||||
}
|
||||
|
||||
await Bun.$`bun install --ignore-scripts`;
|
||||
|
||||
// Done!
|
||||
|
||||
console.log(
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
/* DO NOT EDIT - generated by scripts/generate.ts */
|
||||
|
||||
/* eslint-disable @definitelytyped/strict-export-declare-modifiers */
|
||||
/* eslint-disable @typescript-eslint/no-wrapper-object-types */
|
||||
export namespace Browser {
|
||||
////////////////////
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
import { Entry, Log } from "har-format";
|
||||
|
||||
declare global {
|
||||
export type HARFormatEntry = Entry;
|
||||
export type HARFormatLog = Log;
|
||||
type HARFormatEntry = Entry;
|
||||
type HARFormatLog = Log;
|
||||
}
|
||||
|
||||
|
||||
+1682
-1681
File diff suppressed because it is too large
Load Diff
@@ -26,9 +26,9 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "{{chromeTypesVersion}}",
|
||||
"@types/node": "^20.0.0",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
"@types/node": "catalog:",
|
||||
"nano-spawn": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,14 +42,14 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@types/node": "^20.17.6",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18",
|
||||
"vitest-plugin-random-seed": "^1.1.2",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vitest-plugin-random-seed": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"main": "./dist/index.cjs",
|
||||
|
||||
@@ -35,14 +35,16 @@ describe('I18n Types', () => {
|
||||
});
|
||||
|
||||
describe('With type-safety', () => {
|
||||
const i18n = createI18n<{
|
||||
type MyStructure = {
|
||||
simple: { plural: false; substitutions: 0 };
|
||||
simpleSub1: { plural: false; substitutions: 1 };
|
||||
simpleSub2: { plural: false; substitutions: 2 };
|
||||
plural: { plural: true; substitutions: 0 };
|
||||
pluralSub1: { plural: true; substitutions: 1 };
|
||||
pluralSub2: { plural: true; substitutions: 2 };
|
||||
}>();
|
||||
};
|
||||
|
||||
const i18n = createI18n<MyStructure>();
|
||||
|
||||
describe('t', () => {
|
||||
it('should only allow passing valid combinations of arguments', () => {
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
/** @module @wxt-dev/i18n */
|
||||
import {
|
||||
I18nStructure,
|
||||
DefaultI18nStructure,
|
||||
I18n,
|
||||
Substitution,
|
||||
} from './types';
|
||||
import { I18nStructure, I18n, Substitution, UntypedI18n } from './types';
|
||||
import { browser } from '@wxt-dev/browser';
|
||||
|
||||
export function createI18n<
|
||||
T extends I18nStructure = DefaultI18nStructure,
|
||||
>(): I18n<T> {
|
||||
const t = (key: string, ...args: any[]) => {
|
||||
export function createI18n(): UntypedI18n;
|
||||
export function createI18n<T extends I18nStructure>(): I18n<T>;
|
||||
export function createI18n(): UntypedI18n {
|
||||
const t: UntypedI18n['t'] = (key: string, ...args: unknown[]) => {
|
||||
// Resolve args
|
||||
let sub: Substitution[] | undefined;
|
||||
let count: number | undefined;
|
||||
@@ -64,5 +59,5 @@ export function createI18n<
|
||||
}
|
||||
};
|
||||
|
||||
return { t } as I18n<T>;
|
||||
return { t };
|
||||
}
|
||||
|
||||
@@ -3,13 +3,22 @@ export interface I18nFeatures {
|
||||
substitutions: SubstitutionCount;
|
||||
}
|
||||
|
||||
export type I18nStructure = {
|
||||
[K: string]: I18nFeatures;
|
||||
export interface UntypedI18n {
|
||||
t: UntypedTFunction;
|
||||
}
|
||||
|
||||
export type UntypedTFunction = {
|
||||
(key: string): string;
|
||||
(key: string, substitutions: Substitution[]): string;
|
||||
(key: string, n: number): string;
|
||||
(key: string, n: number, substitutions: Substitution[]): string;
|
||||
};
|
||||
|
||||
export type DefaultI18nStructure = {
|
||||
[K: string]: any;
|
||||
};
|
||||
export type I18nStructure = Record<string, I18nFeatures>;
|
||||
|
||||
export interface I18n<T extends I18nStructure> {
|
||||
t: TFunction<T>;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export type SubstitutionTuple<T extends SubstitutionCount> =
|
||||
@@ -66,10 +75,6 @@ export type TFunction<T extends I18nStructure> = {
|
||||
): string;
|
||||
};
|
||||
|
||||
export interface I18n<T extends DefaultI18nStructure> {
|
||||
t: TFunction<T>;
|
||||
}
|
||||
|
||||
export type Substitution = string | number;
|
||||
|
||||
type SubstitutionCount = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
"@wxt-dev/browser": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -55,13 +55,14 @@
|
||||
"@vitejs/plugin-react": "^4.4.1 || ^5.0.0 || ^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"publint": "^0.3.18",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"typescript": "^5.9.3",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vite": "^7.0.0",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -54,11 +54,11 @@
|
||||
"vite-plugin-solid": "^2.11.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"publint": "^0.3.18",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"solid-js": "^1.9.11",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +53,10 @@
|
||||
"@sveltejs/vite-plugin-svelte": ">=4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,10 +52,10 @@
|
||||
"@vitejs/plugin-vue": "^5.2.3 || ^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
"prepack": "bun run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
},
|
||||
"main": "dist/index.mjs",
|
||||
"types": "dist/index.d.mts",
|
||||
@@ -46,5 +46,8 @@
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
],
|
||||
"dependencies": {
|
||||
"obug": "^2.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('Options', () => {
|
||||
describe('extensionDir', () => {
|
||||
it('should default to the current working directory', async () => {
|
||||
const actual = await resolveRunOptions({});
|
||||
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
extensionDir: process.cwd(),
|
||||
});
|
||||
@@ -34,17 +35,27 @@ describe('Options', () => {
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: './path/to/extension',
|
||||
});
|
||||
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
extensionDir: resolve(process.cwd(), './path/to/extension'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should use absolute paths as-is', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: '/abs/path/to/extension2',
|
||||
});
|
||||
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
extensionDir: resolve('/abs/path/to/extension2'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('target', () => {
|
||||
it('should be "chrome" by default', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: 'path/to/extension',
|
||||
});
|
||||
const actual = await resolveRunOptions({});
|
||||
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
target: 'chrome',
|
||||
});
|
||||
@@ -52,12 +63,12 @@ describe('Options', () => {
|
||||
|
||||
it('should be what is passed in', async () => {
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: 'path/to/extension',
|
||||
target: 'custom',
|
||||
browserBinaries: {
|
||||
custom: '/path/to/custom/browser',
|
||||
},
|
||||
});
|
||||
|
||||
expect(actual).toMatchObject<Partial<ResolvedRunOptions>>({
|
||||
target: 'custom',
|
||||
});
|
||||
@@ -65,9 +76,9 @@ describe('Options', () => {
|
||||
|
||||
it('should throw an error if the target binary could not be found', async () => {
|
||||
const actual = resolveRunOptions({
|
||||
extensionDir: 'path/to/extension',
|
||||
target: 'custom',
|
||||
});
|
||||
|
||||
await expect(actual).rejects.toThrow('Could not find "custom" binary.');
|
||||
});
|
||||
});
|
||||
@@ -83,7 +94,6 @@ describe('Options', () => {
|
||||
? 'C:\\path\\to\\custom\\browser.exe'
|
||||
: path;
|
||||
const actual = await resolveRunOptions({
|
||||
extensionDir: 'path/to/extension',
|
||||
target: 'custom',
|
||||
browserBinaries: {
|
||||
custom: path,
|
||||
@@ -98,9 +108,11 @@ describe('Options', () => {
|
||||
describe('chromiumArgs', () => {
|
||||
it('should log a warning when --user-data-dir is passed in', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn');
|
||||
|
||||
await resolveRunOptions({
|
||||
chromiumArgs: ['--user-data-dir=some/custom/path'],
|
||||
});
|
||||
|
||||
expect(warnSpy).toBeCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
@@ -111,9 +123,11 @@ describe('Options', () => {
|
||||
|
||||
it('should log a warning when --remote-debugging-port is passed in', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn');
|
||||
|
||||
await resolveRunOptions({
|
||||
chromiumArgs: ['--remote-debugging-port=9222'],
|
||||
});
|
||||
|
||||
expect(warnSpy).toBeCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
@@ -126,6 +140,7 @@ describe('Options', () => {
|
||||
const actual = await resolveRunOptions({
|
||||
chromiumArgs: ['--window-size=1920,1080'],
|
||||
});
|
||||
|
||||
expect(actual.chromiumArgs).toEqual([
|
||||
// Defaults
|
||||
'--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculateNativeWinOcclusion,InterestFeedContentSuggestions,CertificateTransparencyComponentUpdater,AutofillServerCommunication,PrivacySandboxSettings4',
|
||||
@@ -161,9 +176,11 @@ describe('Options', () => {
|
||||
describe('firefoxArgs', () => {
|
||||
it('should log a warning when --profile is passed in', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn');
|
||||
|
||||
await resolveRunOptions({
|
||||
firefoxArgs: ['--profile=some/custom/path'],
|
||||
});
|
||||
|
||||
expect(warnSpy).toBeCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Custom Firefox --profile argument ignored'),
|
||||
@@ -172,9 +189,11 @@ describe('Options', () => {
|
||||
|
||||
it('should log a warning when --remote-debugging-port is passed in', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn');
|
||||
|
||||
await resolveRunOptions({
|
||||
firefoxArgs: ['--remote-debugging-port=9222'],
|
||||
});
|
||||
|
||||
expect(warnSpy).toBeCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
@@ -187,6 +206,7 @@ describe('Options', () => {
|
||||
const actual = await resolveRunOptions({
|
||||
firefoxArgs: ['--window-size=1920,1080'],
|
||||
});
|
||||
|
||||
expect(actual.firefoxArgs).toEqual([
|
||||
// Defaults
|
||||
'--new-instance',
|
||||
|
||||
+10
-10
@@ -1,7 +1,7 @@
|
||||
import { openWebSocket } from './web-socket';
|
||||
import { debug } from './debug';
|
||||
import { runnerDebug } from './debug';
|
||||
|
||||
const debugBidi = debug.scoped('bidi');
|
||||
const debug = runnerDebug.extend('bidi');
|
||||
|
||||
export interface BidiConnection extends Disposable {
|
||||
send<T>(method: string, params: any, timeout?: number): Promise<T>;
|
||||
@@ -12,10 +12,10 @@ export async function createBidiConnection(
|
||||
baseUrl: string,
|
||||
): Promise<BidiConnection> {
|
||||
const url = new URL('/session', baseUrl);
|
||||
debugBidi('Connecting to BiDi server @', url.href);
|
||||
debug('Connecting to BiDi server @', url.href);
|
||||
|
||||
const webSocket = await openWebSocket(url.href);
|
||||
debugBidi('Connected');
|
||||
debug('Connected');
|
||||
|
||||
let requestId = 0;
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function createBidiConnection(
|
||||
send(method, params, timeout = 10e3) {
|
||||
const id = ++requestId;
|
||||
const command = { id, method, params };
|
||||
debugBidi('Sending command:', command);
|
||||
debug('Sending command:', command);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const cleanup = () => {
|
||||
@@ -43,7 +43,7 @@ export async function createBidiConnection(
|
||||
const onMessage = (event: MessageEvent) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.id === id) {
|
||||
debugBidi('Received response:', data);
|
||||
debug('Received response:', data);
|
||||
cleanup();
|
||||
if (data.type === 'success') resolve(data.result);
|
||||
else reject(Error(data.message, { cause: data }));
|
||||
@@ -62,14 +62,14 @@ export async function createBidiConnection(
|
||||
},
|
||||
|
||||
close() {
|
||||
debugBidi('Closing connection...');
|
||||
debug('Closing connection...');
|
||||
webSocket.close();
|
||||
debugBidi('Closed connection');
|
||||
debug('Closed connection');
|
||||
},
|
||||
[Symbol.dispose]() {
|
||||
debugBidi('Disposing connection...');
|
||||
debug('Disposing connection...');
|
||||
webSocket.close();
|
||||
debugBidi('Disposed connection');
|
||||
debug('Disposed connection');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -135,11 +135,12 @@ export const KNOWN_BROWSER_PATHS: Record<
|
||||
*/
|
||||
export const FALLBACK_TARGETS: Partial<Record<KnownTarget, KnownTarget[]>> = {
|
||||
chrome: [
|
||||
'arc',
|
||||
'chromium',
|
||||
'chrome-canary',
|
||||
'chrome-beta',
|
||||
'chrome-dev',
|
||||
'brave',
|
||||
'arc',
|
||||
'dia',
|
||||
'edge',
|
||||
'edge-canary',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { ChildProcess } from 'node:child_process';
|
||||
import type { Readable, Writable } from 'node:stream';
|
||||
import { debug } from './debug';
|
||||
import { runnerDebug } from './debug';
|
||||
|
||||
const debugCdp = debug.scoped('cdp');
|
||||
const debug = runnerDebug.extend('cdp');
|
||||
|
||||
export interface CDPConnection extends Disposable {
|
||||
send<T>(method: string, params: any, timeout?: number): Promise<T>;
|
||||
@@ -21,7 +21,7 @@ export function createCdpConnection(
|
||||
send(method, params, timeout = 10e3) {
|
||||
const id = ++requestId;
|
||||
const command = { id, method, params };
|
||||
debugCdp('Sending command:', command);
|
||||
debug('Sending command:', command);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
@@ -35,7 +35,7 @@ export function createCdpConnection(
|
||||
|
||||
if (res.id !== id) return;
|
||||
|
||||
debugCdp('Received response:', res);
|
||||
debug('Received response:', res);
|
||||
clearTimeout(timer);
|
||||
outputStream.removeListener('data', onData);
|
||||
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
export interface Debug {
|
||||
(...args: any[]): void;
|
||||
scoped: (scope: string) => Debug;
|
||||
}
|
||||
import { createDebug } from 'obug';
|
||||
|
||||
function createDebug(scopes: string[]): Debug {
|
||||
const debug = (...args: any[]) => {
|
||||
const scope = scopes.join(':');
|
||||
if (
|
||||
process.env.DEBUG === '1' ||
|
||||
process.env.DEBUG === 'true' ||
|
||||
scope.startsWith(process.env.DEBUG ?? '@NOT')
|
||||
) {
|
||||
const params = scope ? [`\x1b[31m${scope}\x1b[0m`, ...args] : args;
|
||||
console.log(...params);
|
||||
}
|
||||
};
|
||||
|
||||
debug.scoped = (scope: string) => createDebug([...scopes, scope]);
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
||||
export const debug = createDebug(['@wxt-dev/runner']);
|
||||
export const runnerDebug = createDebug('wxt:runner', { color: 31 });
|
||||
|
||||
@@ -6,11 +6,11 @@ import {
|
||||
} from './browser-paths';
|
||||
import { resolve, join } from 'node:path';
|
||||
import { homedir, tmpdir } from 'node:os';
|
||||
import { debug } from './debug';
|
||||
import { runnerDebug } from './debug';
|
||||
import { mkdtemp, open } from 'node:fs/promises';
|
||||
import { styleText } from 'node:util';
|
||||
|
||||
const debugOptions = debug.scoped('options');
|
||||
const debug = runnerDebug.extend('options');
|
||||
|
||||
export type UnknownTarget = string & {};
|
||||
export type Target = KnownTarget | UnknownTarget;
|
||||
@@ -79,7 +79,7 @@ export type ResolvedRunOptions = {
|
||||
export async function resolveRunOptions(
|
||||
options: RunOptions | undefined,
|
||||
): Promise<ResolvedRunOptions> {
|
||||
debugOptions('User options:', options);
|
||||
debug('User options:', options);
|
||||
|
||||
const target = options?.target || 'chrome';
|
||||
|
||||
@@ -126,7 +126,7 @@ export async function resolveRunOptions(
|
||||
firefoxRemoteDebuggingPort,
|
||||
target,
|
||||
};
|
||||
debugOptions('Resolved options:', resolved);
|
||||
debug('Resolved options:', resolved);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { debug } from './debug';
|
||||
import { runnerDebug } from './debug';
|
||||
import {
|
||||
resolveRunOptions,
|
||||
type ResolvedRunOptions,
|
||||
@@ -8,8 +8,8 @@ import { spawn } from 'node:child_process';
|
||||
import { installChromium, installFirefox } from './install';
|
||||
import { promiseWithResolvers } from './promises';
|
||||
|
||||
const debugFirefox = debug.scoped('firefox');
|
||||
const debugChrome = debug.scoped('chrome');
|
||||
const debugFirefox = runnerDebug.extend('firefox');
|
||||
const debugChrome = runnerDebug.extend('chrome');
|
||||
|
||||
export interface Runner {
|
||||
stop(): void;
|
||||
@@ -44,7 +44,7 @@ async function runFirefox(options: ResolvedRunOptions): Promise<Runner> {
|
||||
shell: true,
|
||||
},
|
||||
);
|
||||
const debugFirefoxStderr = debugFirefox.scoped('stderr');
|
||||
const debugFirefoxStderr = debugFirefox.extend('stderr');
|
||||
browserProcess.stderr.on('data', (data: string) => {
|
||||
const message = data.toString().trim();
|
||||
debugFirefoxStderr(message);
|
||||
@@ -54,7 +54,7 @@ async function runFirefox(options: ResolvedRunOptions): Promise<Runner> {
|
||||
urlRes.resolve(message.slice(28));
|
||||
}
|
||||
});
|
||||
const debugFirefoxStdout = debugFirefox.scoped('stdout');
|
||||
const debugFirefoxStdout = debugFirefox.extend('stdout');
|
||||
browserProcess.stdout.on('data', (data: string) => {
|
||||
const message = data.toString().trim();
|
||||
debugFirefoxStdout(message);
|
||||
@@ -85,7 +85,7 @@ async function runChromium(options: ResolvedRunOptions): Promise<Runner> {
|
||||
opened.reject(Error('Timed out after 10s waiting for browser to open.'));
|
||||
}, 10e3);
|
||||
|
||||
const debugChromeStderr = debugChrome.scoped('stderr');
|
||||
const debugChromeStderr = debugChrome.extend('stderr');
|
||||
browserProcess.stderr!.on('data', (data: string) => {
|
||||
const message = data.toString().trim();
|
||||
debugChromeStderr(message);
|
||||
@@ -96,7 +96,7 @@ async function runChromium(options: ResolvedRunOptions): Promise<Runner> {
|
||||
opened.resolve();
|
||||
}
|
||||
});
|
||||
const debugChromeStdout = debugChrome.scoped('stdout');
|
||||
const debugChromeStdout = debugChrome.extend('stdout');
|
||||
browserProcess.stdout!.on('data', (data: string) => {
|
||||
const message = data.toString().trim();
|
||||
debugChromeStdout(message);
|
||||
|
||||
@@ -37,13 +37,13 @@
|
||||
"dequal": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@webext-core/fake-browser": "^1.3.4",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@webext-core/fake-browser": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
},
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts",
|
||||
|
||||
@@ -43,12 +43,12 @@
|
||||
"wxt": ">=0.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"unocss": "^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"unocss": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -52,8 +52,7 @@ export interface UnoCSSOptions<Theme extends object = object> {
|
||||
* it from specific builds.
|
||||
*
|
||||
* @example
|
||||
* {undefined} ('popup',
|
||||
* 'options');
|
||||
* ['popup', 'options'];
|
||||
*
|
||||
* @default [ ]
|
||||
*/
|
||||
|
||||
@@ -48,11 +48,11 @@
|
||||
"wxt": ">=0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@types/webextension-polyfill": "^0.12.5",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"webextension-polyfill": "^0.12.0",
|
||||
"wxt": "workspace:*"
|
||||
}
|
||||
|
||||
@@ -25,17 +25,17 @@
|
||||
"react-dom": "^19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@aklinker1/check": "^2.2.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@aklinker1/check": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"@wxt-dev/auto-icons": "workspace:*",
|
||||
"@wxt-dev/unocss": "workspace:*",
|
||||
"sass": "^1.97.3",
|
||||
"typescript": "^5.9.3",
|
||||
"unocss": "^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0",
|
||||
"vitest": "^4.0.18",
|
||||
"vitest-plugin-random-seed": "^1.1.2",
|
||||
"typescript": "catalog:",
|
||||
"unocss": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vitest-plugin-random-seed": "catalog:",
|
||||
"wxt": "workspace:*"
|
||||
},
|
||||
"buildc": {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-expect-error: URL imports not typed
|
||||
import 'url:https://code.jquery.com/jquery-3.7.1.slim.min.js';
|
||||
|
||||
console.log(browser.runtime.id);
|
||||
|
||||
@@ -1,5 +1,108 @@
|
||||
# Changelog
|
||||
|
||||
## v0.20.26
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.25...wxt-v0.20.26)
|
||||
|
||||
### 🚀 Enhancements
|
||||
|
||||
- Add `default_state` option to popup actions ([#2010](https://github.com/wxt-dev/wxt/pull/2010))
|
||||
- Add content script `noScriptStartedPostMessage` option ([#2265](https://github.com/wxt-dev/wxt/pull/2265))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Use `manifestVersion` from CLI during manifest generation ([#2306](https://github.com/wxt-dev/wxt/pull/2306))
|
||||
- Modify command to support variadic positional args ([#2317](https://github.com/wxt-dev/wxt/pull/2317))
|
||||
- Bump `publish-browser-extension` to `v4.0.5`, to resolve Chrome Web Store submission issue ([#2331](https://github.com/wxt-dev/wxt/pull/2331))
|
||||
- Avoid errors when files are removed during build ([#2343](https://github.com/wxt-dev/wxt/pull/2343))
|
||||
- **manifest:** Exclude `open_in_tab` from `options_ui` for Safari ([#2311](https://github.com/wxt-dev/wxt/pull/2311))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **wxt-modules:** Add logging examples/best practices ([57e3748d](https://github.com/wxt-dev/wxt/commit/57e3748d))
|
||||
- Correct entrypoints icon example code ([#2302](https://github.com/wxt-dev/wxt/pull/2302))
|
||||
- Add types for components ([#2273](https://github.com/wxt-dev/wxt/pull/2273))
|
||||
- Fix JSDoc example formatting ([2a8ec0d7](https://github.com/wxt-dev/wxt/commit/2a8ec0d7))
|
||||
- Remove unused code ([#2275](https://github.com/wxt-dev/wxt/pull/2275))
|
||||
- Fix horizontal scrollbar displaying on landing page ([#2329](https://github.com/wxt-dev/wxt/pull/2329))
|
||||
- Add Mimik extension to the list of extensions ([#2319](https://github.com/wxt-dev/wxt/pull/2319))
|
||||
- Added modrinth extras and pi-hole in one to showcase ([#2337](https://github.com/wxt-dev/wxt/pull/2337))
|
||||
- Added "QIE Wallet" to showcase ([#2341](https://github.com/wxt-dev/wxt/pull/2341))
|
||||
- Update Safari publishing instructions to match Apple docs ([#2314](https://github.com/wxt-dev/wxt/pull/2314))
|
||||
- Add "Redmine Time Tracking" to extension showcase ([#2312](https://github.com/wxt-dev/wxt/pull/2312))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- More JSDoc fixes ([9b59f38c](https://github.com/wxt-dev/wxt/commit/9b59f38c))
|
||||
- Move `createFileReloader` into it's own file ([#2307](https://github.com/wxt-dev/wxt/pull/2307))
|
||||
- Remove ts-expect-error that are no longer needed ([#2344](https://github.com/wxt-dev/wxt/pull/2344))
|
||||
- **deps-dev:** Bump typescript from 5.9.3 to 6.0.3 ([#2325](https://github.com/wxt-dev/wxt/pull/2325))
|
||||
- **deps-dev:** Bump oxlint from 1.59.0 to 1.63.0 ([#2356](https://github.com/wxt-dev/wxt/pull/2356))
|
||||
- Use `catalog:` for dev dependencies ([#2357](https://github.com/wxt-dev/wxt/pull/2357))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Jonathan Viney ([@jviney](https://github.com/jviney))
|
||||
- Rxliuli ([@rxliuli](https://github.com/rxliuli))
|
||||
- CrawlerCode ([@CrawlerCode](https://github.com/CrawlerCode))
|
||||
- Thomas Howlett ([@howlettt](https://github.com/howlettt))
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
- Brian Zhou ([@aiktb](https://github.com/aiktb))
|
||||
- Soaib Haque ([@SoaibHaque](https://github.com/SoaibHaque))
|
||||
- Arthur <creeperkatze.dev@gmail.com>
|
||||
- Luis Ortiz ([@KiryuuLight](https://github.com/KiryuuLight))
|
||||
- Patryk Kuniczak ([@PatrykKuniczak](https://github.com/PatrykKuniczak))
|
||||
- Laplace-anon ([@laplace-anon](https://github.com/laplace-anon))
|
||||
- Kairi ([@kairi003](https://github.com/kairi003))
|
||||
|
||||
## v0.20.25
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.24...wxt-v0.20.25)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Port change in dev server on reload ([#2283](https://github.com/wxt-dev/wxt/pull/2283))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Update `permissions` section with per-browser warning ([#2284](https://github.com/wxt-dev/wxt/pull/2284))
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- Increase PNPM test timeout ([3db3eac6](https://github.com/wxt-dev/wxt/commit/3db3eac6))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v0.20.24
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.23...wxt-v0.20.24)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Correct `actionKey` for Firefox in MV3 ([#2274](https://github.com/wxt-dev/wxt/pull/2274))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- John Wong <john_wong@live.com>
|
||||
|
||||
## v0.20.23
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.22...wxt-v0.20.23)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- Use correct color name ([6224ff49](https://github.com/wxt-dev/wxt/commit/6224ff49))
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- Re-structure the WXT Modules documentation ([#2260](https://github.com/wxt-dev/wxt/pull/2260))
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
- Aaron ([@aklinker1](https://github.com/aklinker1))
|
||||
|
||||
## v0.20.22
|
||||
|
||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.21...wxt-v0.20.22)
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import { createServer as createNetServer } from 'node:net';
|
||||
|
||||
/** Starts a TCP server on the given port and returns a cleanup function. */
|
||||
function occupyPort(port: number): Promise<() => Promise<void>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const srv = createNetServer();
|
||||
srv.listen(port, 'localhost', () => {
|
||||
resolve(() => new Promise<void>((res) => srv.close(() => res())));
|
||||
});
|
||||
srv.on('error', reject);
|
||||
});
|
||||
}
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { TestProject, occupyPort } from '../utils';
|
||||
|
||||
describe('Dev Mode', () => {
|
||||
it('should not change ports when restarting the server', async () => {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { registerWxt, wxt } from '../../src/core/wxt';
|
||||
import { TestProject, occupyPort } from '../utils';
|
||||
|
||||
describe('WXT Global', () => {
|
||||
describe('reloadConfig', () => {
|
||||
it('should not change dev server ports when reloading config, even if the port is in-use', async () => {
|
||||
const project = new TestProject();
|
||||
|
||||
await registerWxt('serve', { root: project.root });
|
||||
|
||||
const firstPort = wxt.config.dev.server!.port;
|
||||
expect(firstPort).toBeDefined();
|
||||
|
||||
const cleanup = await occupyPort(firstPort);
|
||||
try {
|
||||
await wxt.reloadConfig();
|
||||
const secondPort = wxt.config.dev.server?.port;
|
||||
expect(secondPort).toBe(firstPort);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import merge from 'lodash.merge';
|
||||
import spawn, { Subprocess } from 'nano-spawn';
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import { createServer as createNetServer } from 'node:net';
|
||||
import { dirname, relative, resolve } from 'path';
|
||||
import { glob } from 'tinyglobby';
|
||||
import {
|
||||
@@ -204,3 +205,14 @@ export class TestProject {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Starts a TCP server on the given port and returns a cleanup function. */
|
||||
export function occupyPort(port: number): Promise<() => Promise<void>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const srv = createNetServer();
|
||||
srv.listen(port, 'localhost', () => {
|
||||
resolve(() => new Promise<void>((res) => srv.close(() => res())));
|
||||
});
|
||||
srv.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
// oxlint-disable-next-line eslint(no-shadow-restricted-names)
|
||||
// oxlint-disable-next-line no-shadow-restricted-names
|
||||
declare namespace globalThis {
|
||||
var __ENTRYPOINT__: string;
|
||||
}
|
||||
|
||||
+11
-12
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wxt",
|
||||
"type": "module",
|
||||
"version": "0.20.22",
|
||||
"version": "0.20.26",
|
||||
"description": "⚡ Next-gen Web Extension Framework",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -51,7 +51,7 @@
|
||||
"perfect-debounce": "^2.1.0",
|
||||
"picomatch": "^4.0.3",
|
||||
"prompts": "^2.4.2",
|
||||
"publish-browser-extension": "^2.3.0 || ^3.0.2 || ^4.0.4",
|
||||
"publish-browser-extension": "^2.3.0 || ^3.0.2 || ^4.0.5",
|
||||
"scule": "^1.3.0",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"unimport": "^3.13.1 || ^4.0.0 || ^5.0.0 || ^6.0.0",
|
||||
@@ -68,12 +68,11 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aklinker1/buildc": "^1.1.7",
|
||||
"@aklinker1/buildc": "catalog:",
|
||||
"@faker-js/faker": "^10.3.0",
|
||||
"@types/bun": "^1.3.5",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/lodash.merge": "^4.6.9",
|
||||
"@types/node": "^20.17.6",
|
||||
"@types/node": "catalog:",
|
||||
"@types/normalize-path": "^3.0.2",
|
||||
"@types/picomatch": "^4.0.2",
|
||||
"@types/prompts": "^2.4.9",
|
||||
@@ -81,12 +80,12 @@
|
||||
"extract-zip": "^2.0.1",
|
||||
"happy-dom": "^20.8.3",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"oxlint": "^1.51.0",
|
||||
"publint": "^0.3.18",
|
||||
"tsdown": "^0.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18",
|
||||
"vitest-plugin-random-seed": "^1.1.2"
|
||||
"oxlint": "catalog:",
|
||||
"publint": "catalog:",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vitest-plugin-random-seed": "catalog:"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -82,8 +82,12 @@ export function createAliasedCommand(
|
||||
bin: string,
|
||||
docsUrl: string,
|
||||
) {
|
||||
// Declare a variadic positional arg so cac forwards subcommands like `wxt
|
||||
// submit init` instead of rejecting them as unused args. `.allowUnknownOptions`
|
||||
// only relaxes flag checks, not positional args. Required since cac@7, which
|
||||
// throws on unused positional args (see cacjs/cac#135).
|
||||
const aliasedCommand = base
|
||||
.command(name, `Alias for ${alias} (${docsUrl})`)
|
||||
.command(`${name} [...args]`, `Alias for ${alias} (${docsUrl})`)
|
||||
.allowUnknownOptions()
|
||||
.action(async () => {
|
||||
try {
|
||||
|
||||
@@ -1,35 +1,15 @@
|
||||
import { debounce } from 'perfect-debounce';
|
||||
import chokidar from 'chokidar';
|
||||
import {
|
||||
BuildStepOutput,
|
||||
EntrypointGroup,
|
||||
InlineConfig,
|
||||
ServerInfo,
|
||||
WxtDevServer,
|
||||
} from '../types';
|
||||
import { getEntrypointBundlePath, isHtmlEntrypoint } from './utils/entrypoints';
|
||||
import {
|
||||
getContentScriptCssFiles,
|
||||
getContentScriptsCssMap,
|
||||
} from './utils/manifest';
|
||||
import {
|
||||
internalBuild,
|
||||
detectDevChanges,
|
||||
rebuild,
|
||||
findEntrypoints,
|
||||
} from './utils/building';
|
||||
import { InlineConfig, ServerInfo, WxtDevServer } from '../types';
|
||||
import { internalBuild } from './utils/building';
|
||||
import { createExtensionRunner } from './runners';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import { relative } from 'node:path';
|
||||
import { deinitWxtModules, initWxtModules, registerWxt, wxt } from './wxt';
|
||||
import { unnormalizePath } from './utils/paths';
|
||||
import {
|
||||
getContentScriptJs,
|
||||
mapWxtOptionsToRegisteredContentScript,
|
||||
} from './utils/content-scripts';
|
||||
import { createKeyboardShortcuts } from './keyboard-shortcuts';
|
||||
import { isBabelSyntaxError, logBabelSyntaxError } from './utils/syntax-errors';
|
||||
import { styleText } from 'node:util';
|
||||
import {
|
||||
createFileReloader,
|
||||
reloadContentScripts,
|
||||
} from './utils/create-file-reloader';
|
||||
|
||||
/**
|
||||
* Creates a dev server and pre-builds all the files that need to exist before
|
||||
@@ -199,155 +179,6 @@ async function createServerInternal(): Promise<WxtDevServer> {
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function responsible for reloading different parts of the extension
|
||||
* when a file changes.
|
||||
*/
|
||||
function createFileReloader(server: WxtDevServer) {
|
||||
const fileChangedMutex = new Mutex();
|
||||
const changeQueue: Array<[string, string]> = [];
|
||||
|
||||
const cb = async (event: string, path: string) => {
|
||||
changeQueue.push([event, path]);
|
||||
|
||||
const reloading = fileChangedMutex.runExclusive(async () => {
|
||||
if (server.currentOutput == null) return;
|
||||
|
||||
const fileChanges = changeQueue
|
||||
.splice(0, changeQueue.length)
|
||||
.map(([_, file]) => file);
|
||||
if (fileChanges.length === 0) return;
|
||||
|
||||
await wxt.reloadConfig();
|
||||
|
||||
const changes = detectDevChanges(fileChanges, server.currentOutput);
|
||||
if (changes.type === 'no-change') return;
|
||||
|
||||
if (changes.type === 'full-restart') {
|
||||
wxt.logger.info('Config changed, restarting server...');
|
||||
server.restart();
|
||||
return;
|
||||
}
|
||||
|
||||
if (changes.type === 'browser-restart') {
|
||||
wxt.logger.info('Runner config changed, restarting browser...');
|
||||
server.restartBrowser();
|
||||
return;
|
||||
}
|
||||
|
||||
// Log the entrypoints that were effected
|
||||
wxt.logger.info(
|
||||
`Changed: ${Array.from(new Set(fileChanges))
|
||||
.map((file) => styleText('dim', relative(wxt.config.root, file)))
|
||||
.join(', ')}`,
|
||||
);
|
||||
|
||||
// Rebuild entrypoints on change
|
||||
const allEntrypoints = await findEntrypoints();
|
||||
try {
|
||||
const { output: newOutput } = await rebuild(
|
||||
allEntrypoints,
|
||||
// TODO: this excludes new entrypoints, so they're not built until the dev command is restarted
|
||||
changes.rebuildGroups,
|
||||
changes.cachedOutput,
|
||||
);
|
||||
server.currentOutput = newOutput;
|
||||
|
||||
// Perform reloads
|
||||
switch (changes.type) {
|
||||
case 'extension-reload':
|
||||
server.reloadExtension();
|
||||
wxt.logger.success(`Reloaded extension`);
|
||||
break;
|
||||
case 'html-reload':
|
||||
const { reloadedNames } = reloadHtmlPages(
|
||||
changes.rebuildGroups,
|
||||
server,
|
||||
);
|
||||
wxt.logger.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
|
||||
break;
|
||||
case 'content-script-reload':
|
||||
reloadContentScripts(changes.changedSteps, server);
|
||||
|
||||
const rebuiltNames = changes.rebuildGroups
|
||||
.flat()
|
||||
.map((entry) => entry.name);
|
||||
wxt.logger.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Catch build errors instead of crashing. Don't log error either, builder should have already logged it
|
||||
}
|
||||
});
|
||||
|
||||
await reloading.catch((error) => {
|
||||
if (!isBabelSyntaxError(error)) {
|
||||
throw error;
|
||||
}
|
||||
// Log syntax errors without crashing the server.
|
||||
logBabelSyntaxError(error);
|
||||
});
|
||||
};
|
||||
|
||||
return debounce(cb, wxt.config.dev.server!.watchDebounce, {
|
||||
leading: true,
|
||||
trailing: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* From the server, tell the client to reload content scripts from the provided
|
||||
* build step outputs.
|
||||
*/
|
||||
function reloadContentScripts(steps: BuildStepOutput[], server: WxtDevServer) {
|
||||
if (wxt.config.manifestVersion === 3) {
|
||||
steps.forEach((step) => {
|
||||
if (server.currentOutput == null) return;
|
||||
|
||||
const entry = step.entrypoints;
|
||||
if (Array.isArray(entry) || entry.type !== 'content-script') return;
|
||||
|
||||
const js = getContentScriptJs(wxt.config, entry);
|
||||
const cssMap = getContentScriptsCssMap(server.currentOutput, [entry]);
|
||||
const css = getContentScriptCssFiles([entry], cssMap);
|
||||
|
||||
server.reloadContentScript({
|
||||
registration: entry.options.registration,
|
||||
contentScript: mapWxtOptionsToRegisteredContentScript(
|
||||
entry.options,
|
||||
js,
|
||||
css,
|
||||
),
|
||||
});
|
||||
});
|
||||
} else {
|
||||
server.reloadExtension();
|
||||
}
|
||||
}
|
||||
|
||||
function reloadHtmlPages(
|
||||
groups: EntrypointGroup[],
|
||||
server: WxtDevServer,
|
||||
): { reloadedNames: string[] } {
|
||||
// groups might contain other files like background/content scripts, and we only care about the HTMl pages
|
||||
const htmlEntries = groups.flat().filter(isHtmlEntrypoint);
|
||||
|
||||
htmlEntries.forEach((entry) => {
|
||||
const path = getEntrypointBundlePath(entry, wxt.config.outDir, '.html');
|
||||
server.reloadPage(path);
|
||||
});
|
||||
|
||||
return {
|
||||
reloadedNames: htmlEntries.map((entry) => entry.name),
|
||||
};
|
||||
}
|
||||
|
||||
function getFilenameList(names: string[]): string {
|
||||
return names
|
||||
.map((name) => styleText('cyan', name))
|
||||
.join(styleText('dim', ', '));
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the current build output, return a list of files that are:
|
||||
*
|
||||
|
||||
@@ -6,13 +6,13 @@ import { pnpm } from '../pnpm';
|
||||
process.env.WXT_PNPM_IGNORE_WORKSPACE = 'true';
|
||||
|
||||
describe('PNPM Package Management Utils', () => {
|
||||
describe('listDependencies', { timeout: 30e3 }, () => {
|
||||
describe('listDependencies', () => {
|
||||
const cwd = path.resolve(__dirname, 'fixtures/simple-pnpm-project');
|
||||
|
||||
beforeAll(async () => {
|
||||
// PNPM needs the modules installed, or 'pnpm ls' will return a blank list.
|
||||
await spawn('pnpm', ['install'], { cwd });
|
||||
});
|
||||
}, 30e3);
|
||||
|
||||
it('should list direct dependencies', async () => {
|
||||
const actual = await pnpm.listDependencies({ cwd });
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
ContentScriptEntrypoint,
|
||||
Entrypoint,
|
||||
OutputAsset,
|
||||
TargetManifestVersion,
|
||||
} from '../../../types';
|
||||
import { wxt } from '../../wxt';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
@@ -34,6 +35,7 @@ describe('Manifest Utils', () => {
|
||||
describe('generateManifest', () => {
|
||||
describe('popup', () => {
|
||||
type ActionType = 'browser_action' | 'page_action';
|
||||
type Mv3ActionType = 'action' | 'page_action';
|
||||
const popupEntrypoint = (type?: ActionType) =>
|
||||
fakePopupEntrypoint({
|
||||
options: {
|
||||
@@ -107,29 +109,39 @@ describe('Manifest Utils', () => {
|
||||
},
|
||||
);
|
||||
|
||||
it('should allow converting action to page_action for Firefox MV3', async () => {
|
||||
const popup = popupEntrypoint('page_action');
|
||||
const buildOutput = fakeBuildOutput();
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
outDir,
|
||||
browser: 'firefox',
|
||||
},
|
||||
});
|
||||
const expected = {
|
||||
default_icon: popup.options.defaultIcon,
|
||||
default_title: popup.options.defaultTitle,
|
||||
default_popup: 'popup.html',
|
||||
};
|
||||
it.each<{
|
||||
inputType: ActionType | undefined;
|
||||
expectedType: Mv3ActionType;
|
||||
}>([
|
||||
{ inputType: undefined, expectedType: 'action' },
|
||||
{ inputType: 'browser_action', expectedType: 'action' },
|
||||
{ inputType: 'page_action', expectedType: 'page_action' },
|
||||
])(
|
||||
'should use the correct action for Firefox in mv3: %j',
|
||||
async ({ inputType, expectedType }) => {
|
||||
const popup = popupEntrypoint(inputType);
|
||||
const buildOutput = fakeBuildOutput();
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
outDir,
|
||||
browser: 'firefox',
|
||||
},
|
||||
});
|
||||
const expected = {
|
||||
default_icon: popup.options.defaultIcon,
|
||||
default_title: popup.options.defaultTitle,
|
||||
default_popup: 'popup.html',
|
||||
};
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[popup],
|
||||
buildOutput,
|
||||
);
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[popup],
|
||||
buildOutput,
|
||||
);
|
||||
|
||||
expect(actual.page_action).toEqual(expected);
|
||||
});
|
||||
expect(actual[expectedType]).toEqual(expected);
|
||||
},
|
||||
);
|
||||
|
||||
it('should include default_area for Firefox in mv3', async () => {
|
||||
const popup = fakePopupEntrypoint({
|
||||
@@ -187,6 +199,74 @@ describe('Manifest Utils', () => {
|
||||
expect((actual.action as any).theme_icons).toEqual(themeIcons);
|
||||
});
|
||||
|
||||
describe('default_state', () => {
|
||||
it.each<{
|
||||
browser: string;
|
||||
manifestVersion: TargetManifestVersion;
|
||||
type: 'browser_action' | 'action' | 'page_action';
|
||||
shouldExist: boolean;
|
||||
}>([
|
||||
{
|
||||
browser: 'chrome',
|
||||
manifestVersion: 2,
|
||||
type: 'browser_action',
|
||||
shouldExist: false,
|
||||
},
|
||||
{
|
||||
browser: 'chrome',
|
||||
manifestVersion: 3,
|
||||
type: 'action',
|
||||
shouldExist: true,
|
||||
},
|
||||
{
|
||||
browser: 'firefox',
|
||||
manifestVersion: 2,
|
||||
type: 'browser_action',
|
||||
shouldExist: false,
|
||||
},
|
||||
{
|
||||
browser: 'firefox',
|
||||
manifestVersion: 3,
|
||||
type: 'action',
|
||||
shouldExist: true,
|
||||
},
|
||||
])(
|
||||
'should configure default_state: $defaultState based on the $browser and mv$manifestVersion',
|
||||
async ({ browser, manifestVersion, type, shouldExist }) => {
|
||||
const popup = fakePopupEntrypoint({
|
||||
options: {
|
||||
// @ts-expect-error: Force this to be undefined when null
|
||||
actionType: manifestVersion === 3 ? null : type,
|
||||
defaultState: 'enabled',
|
||||
},
|
||||
outputDir: outDir,
|
||||
skipped: false,
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
setFakeWxt({
|
||||
config: {
|
||||
browser,
|
||||
manifestVersion,
|
||||
outDir,
|
||||
},
|
||||
});
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[popup],
|
||||
buildOutput,
|
||||
);
|
||||
|
||||
if (shouldExist) {
|
||||
expect(actual[type]).toMatchObject({
|
||||
default_state: 'enabled',
|
||||
});
|
||||
} else {
|
||||
expect(actual[type].default_state).toBeUndefined();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should include default_area for Firefox in mv2', async () => {
|
||||
const popup = fakePopupEntrypoint({
|
||||
options: {
|
||||
@@ -365,6 +445,28 @@ describe('Manifest Utils', () => {
|
||||
|
||||
expect(actual.options_ui).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should exclude open_in_tab for safari', async () => {
|
||||
setFakeWxt({
|
||||
config: {
|
||||
manifestVersion: 3,
|
||||
browser: 'safari',
|
||||
outDir,
|
||||
},
|
||||
});
|
||||
const buildOutput = fakeBuildOutput();
|
||||
|
||||
const { manifest: actual } = await generateManifest(
|
||||
[options],
|
||||
buildOutput,
|
||||
);
|
||||
|
||||
expect(actual.options_ui).toEqual({
|
||||
chrome_style: true,
|
||||
page: 'options.html',
|
||||
});
|
||||
expect(actual.options_ui?.open_in_tab).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('background', () => {
|
||||
|
||||
@@ -31,7 +31,6 @@ export function hashContentScriptOptions(
|
||||
match_about_blank: false,
|
||||
run_at: 'document_idle',
|
||||
all_frames: false,
|
||||
// @ts-expect-error: Untyped
|
||||
match_origin_as_fallback: false,
|
||||
world: 'ISOLATED',
|
||||
...simplifiedOptions,
|
||||
@@ -63,7 +62,6 @@ export function mapWxtOptionsToContentScript(
|
||||
run_at: options.runAt,
|
||||
css,
|
||||
js,
|
||||
// @ts-expect-error: Untyped
|
||||
match_origin_as_fallback: options.matchOriginAsFallback,
|
||||
world: options.world,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import { debounce } from 'perfect-debounce';
|
||||
import { Mutex } from 'async-mutex';
|
||||
import { relative } from 'node:path';
|
||||
import { BuildStepOutput, EntrypointGroup, WxtDevServer } from '../../types';
|
||||
import { wxt } from '../wxt';
|
||||
import { detectDevChanges, findEntrypoints, rebuild } from './building';
|
||||
import { getEntrypointBundlePath, isHtmlEntrypoint } from './entrypoints';
|
||||
import { getContentScriptCssFiles, getContentScriptsCssMap } from './manifest';
|
||||
import {
|
||||
getContentScriptJs,
|
||||
mapWxtOptionsToRegisteredContentScript,
|
||||
} from './content-scripts';
|
||||
import { isBabelSyntaxError, logBabelSyntaxError } from './syntax-errors';
|
||||
import { styleText } from 'node:util';
|
||||
|
||||
/**
|
||||
* Returns a function responsible for reloading different parts of the extension
|
||||
* when a file changes.
|
||||
*/
|
||||
export function createFileReloader(server: WxtDevServer) {
|
||||
const fileChangedMutex = new Mutex();
|
||||
const changeQueue: Array<[string, string]> = [];
|
||||
|
||||
const cb = async (event: string, path: string) => {
|
||||
changeQueue.push([event, path]);
|
||||
|
||||
const reloading = fileChangedMutex.runExclusive(async () => {
|
||||
if (server.currentOutput == null) return;
|
||||
|
||||
const fileChanges = changeQueue
|
||||
.splice(0, changeQueue.length)
|
||||
.map(([_, file]) => file);
|
||||
if (fileChanges.length === 0) return;
|
||||
|
||||
await wxt.reloadConfig();
|
||||
|
||||
const changes = detectDevChanges(fileChanges, server.currentOutput);
|
||||
if (changes.type === 'no-change') return;
|
||||
|
||||
if (changes.type === 'full-restart') {
|
||||
wxt.logger.info('Config changed, restarting server...');
|
||||
server.restart();
|
||||
return;
|
||||
}
|
||||
|
||||
if (changes.type === 'browser-restart') {
|
||||
wxt.logger.info('Runner config changed, restarting browser...');
|
||||
server.restartBrowser();
|
||||
return;
|
||||
}
|
||||
|
||||
// Log the entrypoints that were effected
|
||||
wxt.logger.info(
|
||||
`Changed: ${Array.from(new Set(fileChanges))
|
||||
.map((file) => styleText('dim', relative(wxt.config.root, file)))
|
||||
.join(', ')}`,
|
||||
);
|
||||
|
||||
// Rebuild entrypoints on change
|
||||
const allEntrypoints = await findEntrypoints();
|
||||
try {
|
||||
const { output: newOutput } = await rebuild(
|
||||
allEntrypoints,
|
||||
// TODO: this excludes new entrypoints, so they're not built until the dev command is restarted
|
||||
changes.rebuildGroups,
|
||||
changes.cachedOutput,
|
||||
);
|
||||
server.currentOutput = newOutput;
|
||||
|
||||
// Perform reloads
|
||||
switch (changes.type) {
|
||||
case 'extension-reload':
|
||||
server.reloadExtension();
|
||||
wxt.logger.success(`Reloaded extension`);
|
||||
break;
|
||||
case 'html-reload':
|
||||
const { reloadedNames } = reloadHtmlPages(
|
||||
changes.rebuildGroups,
|
||||
server,
|
||||
);
|
||||
wxt.logger.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
|
||||
break;
|
||||
case 'content-script-reload':
|
||||
reloadContentScripts(changes.changedSteps, server);
|
||||
|
||||
const rebuiltNames = changes.rebuildGroups
|
||||
.flat()
|
||||
.map((entry) => entry.name);
|
||||
wxt.logger.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Catch build errors instead of crashing. Don't log error either, builder should have already logged it
|
||||
}
|
||||
});
|
||||
|
||||
await reloading.catch((error) => {
|
||||
if (!isBabelSyntaxError(error)) {
|
||||
throw error;
|
||||
}
|
||||
// Log syntax errors without crashing the server.
|
||||
logBabelSyntaxError(error);
|
||||
});
|
||||
};
|
||||
|
||||
return debounce(cb, wxt.config.dev.server!.watchDebounce, {
|
||||
leading: true,
|
||||
trailing: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* From the server, tell the client to reload content scripts from the provided
|
||||
* build step outputs.
|
||||
*/
|
||||
export function reloadContentScripts(
|
||||
steps: BuildStepOutput[],
|
||||
server: WxtDevServer,
|
||||
) {
|
||||
if (wxt.config.manifestVersion === 3) {
|
||||
steps.forEach((step) => {
|
||||
if (server.currentOutput == null) return;
|
||||
|
||||
const entry = step.entrypoints;
|
||||
if (Array.isArray(entry) || entry.type !== 'content-script') return;
|
||||
|
||||
const js = getContentScriptJs(wxt.config, entry);
|
||||
const cssMap = getContentScriptsCssMap(server.currentOutput, [entry]);
|
||||
const css = getContentScriptCssFiles([entry], cssMap);
|
||||
|
||||
server.reloadContentScript({
|
||||
registration: entry.options.registration,
|
||||
contentScript: mapWxtOptionsToRegisteredContentScript(
|
||||
entry.options,
|
||||
js,
|
||||
css,
|
||||
),
|
||||
});
|
||||
});
|
||||
} else {
|
||||
server.reloadExtension();
|
||||
}
|
||||
}
|
||||
|
||||
function reloadHtmlPages(
|
||||
groups: EntrypointGroup[],
|
||||
server: WxtDevServer,
|
||||
): { reloadedNames: string[] } {
|
||||
// groups might contain other files like background/content scripts, and we only care about the HTMl pages
|
||||
const htmlEntries = groups.flat().filter(isHtmlEntrypoint);
|
||||
|
||||
htmlEntries.forEach((entry) => {
|
||||
const path = getEntrypointBundlePath(entry, wxt.config.outDir, '.html');
|
||||
server.reloadPage(path);
|
||||
});
|
||||
|
||||
return {
|
||||
reloadedNames: htmlEntries.map((entry) => entry.name),
|
||||
};
|
||||
}
|
||||
|
||||
function getFilenameList(names: string[]): string {
|
||||
return names
|
||||
.map((name) => styleText('cyan', name))
|
||||
.join(styleText('dim', ', '));
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { filesize } from 'filesize';
|
||||
import { printTable } from './printTable';
|
||||
import { styleText } from 'node:util';
|
||||
import { TextStyle } from '../../../utils/text-style';
|
||||
import { wxt } from '../../wxt';
|
||||
|
||||
export async function printFileList(
|
||||
log: (message: string) => void,
|
||||
@@ -21,9 +22,16 @@ export async function printFileList(
|
||||
];
|
||||
const prefix = i === files.length - 1 ? ' └─' : ' ├─';
|
||||
const chunkColor = getChunkColor(file);
|
||||
const stats = await lstat(file);
|
||||
totalSize += stats.size;
|
||||
const size = String(filesize(stats.size));
|
||||
|
||||
let size = '';
|
||||
try {
|
||||
const stats = await lstat(file);
|
||||
totalSize += stats.size;
|
||||
size = String(filesize(stats.size));
|
||||
} catch (ex) {
|
||||
wxt.logger.warn(`Could not get stats of '${file}' error: ${ex}`);
|
||||
}
|
||||
|
||||
return [
|
||||
`${styleText('gray', prefix)} ${styleText('dim', parts[0])}${styleText(chunkColor, parts[1])}`,
|
||||
styleText('dim', size),
|
||||
|
||||
@@ -4,6 +4,6 @@ import { styleText } from 'node:util';
|
||||
|
||||
export function printHeader() {
|
||||
consola.log(
|
||||
`\n${styleText('gray', 'WXT')} ${styleText(['bold', 'grey'], version)}`,
|
||||
`\n${styleText('gray', 'WXT')} ${styleText(['bold', 'gray'], version)}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -294,6 +294,9 @@ function addEntrypoints(
|
||||
options.default_icon = popup.options.defaultIcon;
|
||||
if (popup.options.defaultTitle)
|
||||
options.default_title = popup.options.defaultTitle;
|
||||
if (popup.options.defaultState && wxt.config.manifestVersion === 3)
|
||||
// @ts-expect-error: Not typed by @wxt-dev/browser, but supported by Chrome
|
||||
options.default_state = popup.options.defaultState;
|
||||
if (popup.options.browserStyle)
|
||||
// @ts-expect-error: Not typed by @wxt-dev/browser, but supported by Firefox
|
||||
options.browser_style = popup.options.browserStyle;
|
||||
@@ -305,10 +308,11 @@ function addEntrypoints(
|
||||
options.theme_icons = popup.options.themeIcons;
|
||||
|
||||
const actionKey =
|
||||
manifest.manifest_version === 2
|
||||
wxt.config.manifestVersion === 2
|
||||
? (popup.options.actionType ?? 'browser_action')
|
||||
: wxt.config.browser === 'firefox'
|
||||
? (popup.options.actionType ?? 'action')
|
||||
: wxt.config.browser === 'firefox' &&
|
||||
popup.options.actionType === 'page_action'
|
||||
? 'page_action'
|
||||
: 'action';
|
||||
|
||||
manifest[actionKey] = {
|
||||
@@ -329,7 +333,9 @@ function addEntrypoints(
|
||||
if (options) {
|
||||
const page = getEntrypointBundlePath(options, wxt.config.outDir, '.html');
|
||||
manifest.options_ui = {
|
||||
open_in_tab: options.options.openInTab ?? false,
|
||||
...(wxt.config.browser !== 'safari' && {
|
||||
open_in_tab: options.options.openInTab ?? false,
|
||||
}),
|
||||
// @ts-expect-error: Not typed by @wxt-dev/browser, but supported by Firefox
|
||||
browser_style:
|
||||
wxt.config.browser === 'firefox'
|
||||
@@ -489,7 +495,7 @@ function addDevModeCsp(manifest: Browser.runtime.Manifest): void {
|
||||
const permission = `${permissionUrl}*`;
|
||||
const allowedCsp = wxt.server?.origin ?? 'http://localhost:*';
|
||||
|
||||
if (manifest.manifest_version === 3) {
|
||||
if (wxt.config.manifestVersion === 3) {
|
||||
addHostPermission(manifest, permission);
|
||||
} else {
|
||||
addPermission(manifest, permission);
|
||||
@@ -498,7 +504,7 @@ function addDevModeCsp(manifest: Browser.runtime.Manifest): void {
|
||||
const extensionPagesCsp = new ContentSecurityPolicy(
|
||||
// @ts-expect-error: extension_pages exists, we convert MV2 CSPs to this earlier in the process
|
||||
manifest.content_security_policy?.extension_pages ??
|
||||
(manifest.manifest_version === 3
|
||||
(wxt.config.manifestVersion === 3
|
||||
? DEFAULT_MV3_EXTENSION_PAGES_CSP
|
||||
: DEFAULT_MV2_CSP),
|
||||
);
|
||||
|
||||
@@ -8,10 +8,8 @@ import picomatch, { PicomatchOptions } from 'picomatch';
|
||||
* matches any of the negative patterns, it will return `false`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* picomatchMultiple('a.json', ['*.json', '!b.json']); // => true
|
||||
* picomatchMultiple('b.json', ['*.json', '!b.json']); // => false
|
||||
* ```;
|
||||
*/
|
||||
export function picomatchMultiple(
|
||||
search: string,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { InlineConfig, Wxt, WxtCommand, WxtHooks, WxtModule } from '../types';
|
||||
import { resolveConfig } from './resolve-config';
|
||||
import { createHooks } from 'hookable';
|
||||
import { createWxtPackageManager } from './package-managers';
|
||||
import { createViteBuilder } from './builders/vite';
|
||||
import { builtinModules } from '../builtin-modules';
|
||||
import { relative } from 'path';
|
||||
import { builtinModules } from '../builtin-modules';
|
||||
import { InlineConfig, Wxt, WxtCommand, WxtHooks, WxtModule } from '../types';
|
||||
import { createViteBuilder } from './builders/vite';
|
||||
import { createWxtPackageManager } from './package-managers';
|
||||
import { resolveConfig } from './resolve-config';
|
||||
|
||||
/**
|
||||
* Global variable set once `createWxt` is called once. Since this variable is
|
||||
@@ -47,6 +47,7 @@ export async function registerWxt(
|
||||
inlineConfig.dev ??= {};
|
||||
inlineConfig.dev.server ??= {};
|
||||
inlineConfig.dev.server.port = wxt.config.dev.server.port;
|
||||
inlineConfig.dev.server.strictPort = true;
|
||||
}
|
||||
|
||||
wxt.config = await resolveConfig(inlineConfig, command);
|
||||
|
||||
@@ -68,7 +68,7 @@ export interface InlineConfig {
|
||||
* 'serve')
|
||||
*
|
||||
* @example
|
||||
* {{browser}} -mv{{manifestVersion}}
|
||||
* '{{browser}}-mv{{manifestVersion}}';
|
||||
*
|
||||
* @default <span v-pre>`"{{browser}}-mv{{manifestVersion}}{{modeSuffix}}"`</span>
|
||||
*/
|
||||
@@ -147,13 +147,11 @@ export interface InlineConfig {
|
||||
* Suppress specific warnings during the build process.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* export default defineConfig({
|
||||
* suppressWarnings: {
|
||||
* firefoxDataCollection: true,
|
||||
* },
|
||||
* })
|
||||
* ```;
|
||||
* });
|
||||
*/
|
||||
suppressWarnings?: {
|
||||
/**
|
||||
@@ -288,10 +286,10 @@ export interface InlineConfig {
|
||||
*
|
||||
* @example
|
||||
* // Correct:
|
||||
* ['@scope/package-name', 'package-name'][
|
||||
* // Incorrect, don't include versions!!!
|
||||
* ('@scope/package-name@1.1.3', 'package-name@^2')
|
||||
* ];
|
||||
* ['@scope/package-name', 'package-name'];
|
||||
*
|
||||
* // Incorrect, don't include versions!!!
|
||||
* ['@scope/package-name@1.1.3', 'package-name@^2'];
|
||||
*
|
||||
* @default [ ]
|
||||
*/
|
||||
@@ -713,6 +711,22 @@ export interface BaseContentScriptEntrypointOptions extends BaseScriptEntrypoint
|
||||
* @default 'manifest'
|
||||
*/
|
||||
registration?: PerBrowserOption<'manifest' | 'runtime'>;
|
||||
/**
|
||||
* Do not send the `wxt:content-script-started` message via
|
||||
* `window.postMessage`.
|
||||
*
|
||||
* This has been replaced with custom events. The `postMessage` call is kept
|
||||
* for backwards compatibility. For some websites the `postMessage` call is
|
||||
* undesirable, such as those with poorly written message event listeners.
|
||||
*
|
||||
* Setting this to `true` opts into the behavior that will become the default
|
||||
* in a future version of WXT, where the `postMessage` call is removed
|
||||
* entirely.
|
||||
*
|
||||
* See https://github.com/wxt-dev/wxt/pull/1938 and
|
||||
* https://github.com/wxt-dev/wxt/pull/2035 for a detailed discussion.
|
||||
*/
|
||||
noScriptStartedPostMessage?: boolean;
|
||||
}
|
||||
|
||||
export interface MainWorldContentScriptEntrypointOptions extends BaseContentScriptEntrypointOptions {
|
||||
@@ -760,6 +774,12 @@ export interface PopupEntrypointOptions extends BaseEntrypointOptions {
|
||||
mv2Key?: PerBrowserOption<'browser_action' | 'page_action'>;
|
||||
defaultIcon?: Record<string, string>;
|
||||
defaultTitle?: PerBrowserOption<string>;
|
||||
/**
|
||||
* Chrome only. Controls the initial enabled/disabled state of the action.
|
||||
*
|
||||
* @see https://developer.chrome.com/docs/extensions/reference/api/action#enabled_state
|
||||
*/
|
||||
defaultState?: PerBrowserOption<'enabled' | 'disabled'>;
|
||||
browserStyle?: PerBrowserOption<boolean>;
|
||||
/**
|
||||
* Firefox only. Defines the part of the browser in which the button is
|
||||
|
||||
@@ -84,6 +84,22 @@ describe('Content Script Context', () => {
|
||||
expect(ctx.isValid).toBe(true);
|
||||
});
|
||||
|
||||
describe('noScriptStartedPostMessage', () => {
|
||||
it('should send window.postMessage by default', async () => {
|
||||
const postMessageSpy = vi.spyOn(window, 'postMessage');
|
||||
new ContentScriptContext('test');
|
||||
expect(postMessageSpy).toHaveBeenCalledOnce();
|
||||
postMessageSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should not send window.postMessage when noScriptStartedPostMessage is true', async () => {
|
||||
const postMessageSpy = vi.spyOn(window, 'postMessage');
|
||||
new ContentScriptContext('test', { noScriptStartedPostMessage: true });
|
||||
expect(postMessageSpy).not.toHaveBeenCalled();
|
||||
postMessageSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('addEventListener', () => {
|
||||
const context = new ContentScriptContext('test');
|
||||
it('should infer types correctly for the window target', () => {
|
||||
|
||||
@@ -258,16 +258,18 @@ export class ContentScriptContext implements AbortController {
|
||||
}),
|
||||
);
|
||||
|
||||
// Send message using `window.postMessage` for backwards compatibility to invalidate old versions before WXT changed to `document.dispatchEvent`
|
||||
// TODO: Remove this once WXT version using `document.dispatchEvent` has been released for a while
|
||||
window.postMessage(
|
||||
{
|
||||
type: ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE,
|
||||
contentScriptName: this.contentScriptName,
|
||||
messageId: this.id,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
if (!this.options?.noScriptStartedPostMessage) {
|
||||
// Send message using `window.postMessage` for backwards compatibility to invalidate old versions before WXT changed to `document.dispatchEvent`
|
||||
// TODO: Remove this once WXT version using `document.dispatchEvent` has been released for a while
|
||||
window.postMessage(
|
||||
{
|
||||
type: ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE,
|
||||
contentScriptName: this.contentScriptName,
|
||||
messageId: this.id,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
verifyScriptStartedEvent(event: CustomEvent) {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
Bun outputs packed tarballs relative to the workspace root when using `--filename`. So we need to use an absolute patht make sure the file is output where pkg-pr-new expects it to be - in the package's directory.
|
||||
|
||||
`p3` is equal to: '/home/runner/work/wxt/wxt/packages/analytics/'
|
||||
|
||||
This has been fixed in <https://github.com/stackblitz-labs/pkg.pr.new/pull/483>, but is not released as of `0.66.0`.
|
||||
|
||||
Once v0.67.0 is released, we can revert back to using bunx, delete this patch, and remove the local dependency in `<root>/package.json`.
|
||||
@@ -1,47 +0,0 @@
|
||||
diff --git a/node_modules/pkg-pr-new/.bun-tag-c719f450f9f5410d b/.bun-tag-c719f450f9f5410d
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index e2626a009efe1ee3cfa6c96fe712341d3501ba1c..20755fe90b3c8807b28e63e9ebcfb244aea07be9 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -3743,7 +3743,7 @@ async function resolveTarball(pm, p3, pJson) {
|
||||
if (pm === "yarn") {
|
||||
cmd += ` --filename ${filename}`;
|
||||
} else if (pm === "bun") {
|
||||
- cmd = `bun pm pack --quiet --filename ${filename}`;
|
||||
+ cmd = `bun pm pack --quiet --filename ${p3}${filename}`;
|
||||
}
|
||||
const { stdout: stdout2 } = await ezSpawn.async(cmd, {
|
||||
stdio: "overlapped",
|
||||
@@ -3753,18 +3753,18 @@ async function resolveTarball(pm, p3, pJson) {
|
||||
if (pm !== "yarn" && pm !== "bun") {
|
||||
filename = lines[lines.length - 1].trim();
|
||||
}
|
||||
- if (pm === "bun") {
|
||||
- const tgzFiles = fsSync.readdirSync(p3).filter((file) => file.endsWith(".tgz"));
|
||||
- const bunFilename = stdout2.trim();
|
||||
- if (bunFilename) {
|
||||
- filename = bunFilename;
|
||||
- }
|
||||
- console.warn(`[bun pack] stdout:
|
||||
-${stdout2}`);
|
||||
- console.warn(
|
||||
- `[bun pack] expected filename: ${filename}; tgz files: ${tgzFiles.join(", ") || "(none)"}`
|
||||
- );
|
||||
- }
|
||||
+// if (pm === "bun") {
|
||||
+// const tgzFiles = fsSync.readdirSync(p3).filter((file) => file.endsWith(".tgz"));
|
||||
+// const bunFilename = stdout2.trim();
|
||||
+// if (bunFilename) {
|
||||
+// filename = bunFilename;
|
||||
+// }
|
||||
+// console.warn(`[bun pack] stdout:
|
||||
+// ${stdout2}`);
|
||||
+// console.warn(
|
||||
+// `[bun pack] expected filename: ${filename}; tgz files: ${tgzFiles.join(", ") || "(none)"}`
|
||||
+// );
|
||||
+// }
|
||||
const shasum = createHash("sha1").update(await fs.readFile(path.resolve(p3, filename))).digest("hex");
|
||||
return { filename, shasum };
|
||||
}
|
||||
Vendored
+1
-1
@@ -23,6 +23,6 @@
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@wxt-dev/module-react": "^1.1.5",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "^0.20.22"
|
||||
"wxt": "^0.20.26"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -20,6 +20,6 @@
|
||||
"devDependencies": {
|
||||
"@wxt-dev/module-solid": "^1.1.4",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "^0.20.22"
|
||||
"wxt": "^0.20.26"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -21,6 +21,6 @@
|
||||
"svelte-check": "^4.4.4",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "^0.20.22"
|
||||
"wxt": "^0.20.26"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -16,6 +16,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"wxt": "^0.20.22"
|
||||
"wxt": "^0.20.26"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -21,6 +21,6 @@
|
||||
"@wxt-dev/module-vue": "^1.0.3",
|
||||
"typescript": "^5.9.3",
|
||||
"vue-tsc": "^3.2.5",
|
||||
"wxt": "^0.20.22"
|
||||
"wxt": "^0.20.26"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
/* Type Checking */
|
||||
"strict": true,
|
||||
"lib": ["DOM", "WebWorker", "ESNext"],
|
||||
"types": ["bun"],
|
||||
|
||||
/* Completeness */
|
||||
"skipLibCheck": true
|
||||
|
||||
Reference in New Issue
Block a user