Compare commits

...

39 Commits

Author SHA1 Message Date
github-actions[bot] 3dd9bc7772 chore(release): @wxt-dev/i18n v0.2.4
📼 VHS / Create VHS (push) Waiting to run
2025-05-04 12:27:20 +00:00
Aaron c7335add19 chore: Change browser workspace dependency to ^ 2025-05-04 07:21:55 -05:00
Aaron c133498208 chore(deps): Update all dependencies (#1648) 2025-05-02 08:53:38 -05:00
Aaron 6a52bb22a3 fix: Use @wxt-dev/browser instead of @types/chrome (#1645) 2025-05-02 08:41:02 -05:00
Aaron 6f970efda5 chore: Upgrade @aklinker1/check to v2 (#1647) 2025-05-02 08:36:46 -05:00
Aaron c69350a0eb fix: Fix bad links to developer.chrome.com 2025-05-02 07:10:25 -05:00
Aaron 4add8820eb chore: Stop using PNPM catalog (#1644) 2025-05-02 06:26:12 -05:00
Aaron 033dd4811e chore(deps): Upgrade dev dependencies (#1642) 2025-05-02 05:52:06 -05:00
Aaron da5cd32502 fix: Improve CSS reset inside shadow roots 2025-05-02 05:14:42 -05:00
aklinker1 edf33fdec9 fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2025-05-02 01:53:07 +00:00
seaders 448cbf16c9 feat: Add @font-face to be processed by splitShadowRootCss (#1635) 2025-05-01 12:49:54 -05:00
Aaron e45b77b528 chore(deps): Upgrade vite and related dependencies 2025-05-01 12:25:56 -05:00
github-actions[bot] b61fd1716e chore(release): wxt v0.20.6
📼 VHS / Create VHS (push) Waiting to run
2025-04-30 16:22:57 +00:00
Aaron d584b5cc7e chore(deps): Upgrade web-ext-run to v0.2.3 (#1634) 2025-04-30 11:18:30 -05:00
Bang·_· faf9cedfbd docs: Added "[DesignPicker]" to the homepage (#1624) 2025-04-27 07:24:00 -05:00
Aaron 3a38ac9339 Fix changelog contributors 2025-04-26 18:07:17 -05:00
github-actions[bot] 7ea36a3cfc chore(release): wxt v0.20.5
📼 VHS / Create VHS (push) Waiting to run
2025-04-26 13:53:46 +00:00
Aaron 3577c0b47a fix: Don't use crypto.randUUID for shadow root UIs 2025-04-26 08:48:42 -05:00
Aaron d0bef42186 fix: Standardize locale codes and warn about unsupported ones (#1617) 2025-04-26 08:03:22 -05:00
github-actions[bot] d9f10c62ad chore(release): wxt v0.20.4
📼 VHS / Create VHS (push) Waiting to run
2025-04-25 03:03:08 +00:00
aklinker1 35d3c03e4b fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2025-04-25 01:52:10 +00:00
Yunsup Sim fdd38a1580 fix: Fix CORS error in Firefox (#1607) 2025-04-24 19:01:30 -05:00
ТΞNSΛI 3018801f06 feat: add {{packageVersion}} as template variable (#1604) 2025-04-24 12:06:50 -05:00
Anh71me 8e96bfef06 fix: fix typescript error on defineItem fallback (#1601) 2025-04-24 07:31:48 -05:00
ТΞNSΛI 31071bd11e feat: ignore elements with a vite-ignore or wxt-ignore attribute (#1603)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-04-24 00:34:49 -05:00
Yuan f5619f6be1 chore: Move the public folder outside of src for the default Svelte template (#1602) 2025-04-24 00:26:34 -05:00
Aaron adad1b5a2c chore: Fix formatting 2025-04-24 00:24:49 -05:00
Nishu a6c4e19a5d fix: adding missing "" to PublicPath and browser.runtime.getUrl (#1597) 2025-04-24 00:18:06 -05:00
Jack af6f74c344 docs: Added "[Always Light Mode]" to the homepage (#1608) 2025-04-24 00:05:37 -05:00
Aaron ce45cb8a89 docs: Add warning about --load-extension deprecation 2025-04-23 23:35:28 -05:00
aklinker1 14d4aa202d fix: Upgrade @wxt-dev/browser to latest @types/chrome version 2025-04-23 01:51:01 +00:00
Nishu 4c35798dba feat: Ignore popup/index.ts instead of erroring (#1520)
Co-authored-by: Aaron <aaronklinker1@gmail.com>
2025-04-20 21:17:05 -05:00
Aaron 656a9b365f docs(Content Script UI): Add additional details about when onRemove is called 2025-04-20 21:15:08 -05:00
Namu 67fa3db921 docs: Fix import in unit-testing.md (#1598) 2025-04-20 20:36:06 -05:00
github-actions[bot] f2d3061e97 chore(release): wxt v0.20.3
📼 VHS / Create VHS (push) Waiting to run
2025-04-19 17:17:39 +00:00
Aaron 760c34e416 feat: Automatically place document-level CSS outside shadow root (#1594) 2025-04-19 12:12:04 -05:00
Bang·_· 48398b315c docs: Added "[Blens - Time Tracker and AI Insight]" to the homepage (#1587) 2025-04-18 21:53:19 -05:00
techlism 45d0d9d7f1 docs: Fix entrypoints.md examples (#1586) 2025-04-18 21:11:04 -05:00
Aaron b0f4ac8221 fix: Fix double hashing of inline script keys
Additionally, update the vite plugin name to better align with purpose
2025-04-18 20:50:02 -05:00
65 changed files with 1777 additions and 1936 deletions
@@ -71,6 +71,9 @@ const chromeExtensionIds = [
'bmoggiinmnodjphdjnmpcnlleamkfedj', // AliasVault - Open-Source Password & (Email) Alias Manager
'hlnhhamckimoaiekbglafiebkfimhapb', // SnapThePrice: AI-Powered Real-time Lowest Price Finder
'gdjampjdgjmbifnhldgcnccdjkcoicmg', // radiofrance - news & broadcasts (French), music (international)
'jlnhphlghikichhgbnkepenehbmloenb', // Blens - Time Tracker and AI Insight
'njnammmpdodmfkodnfpammnpdcbhnlcm', // Always Light Mode - Setting website always in light mode
'lblmfclcfniabobmamfkdogcgdagbhhb', // DesignPicker - Color Picker & Font Detector
];
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
@@ -8,6 +8,16 @@ outline: deep
During development, WXT uses [`web-ext` by Mozilla](https://www.npmjs.com/package/web-ext) to automatically open a browser window with your extension installed.
:::danger
Chrome 137 removed support for the `--load-extension` CLI flag, which WXT relied on to open the browser with an extension installed. So this feature will not work for Chrome.
You have two options:
1. Install [Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (which still supports the `--load-extension` flag) and [point the `chrome` binary to it](#set-browser-binaries), or
2. [Disable this feature](#disable-opening-browser) and manually load your extension
:::
## Config Files
You can configure browser startup in 3 places:
+2 -2
View File
@@ -211,12 +211,12 @@ When defining your background entrypoint, keep in mind that WXT will import this
<!-- prettier-ignore -->
```ts
browser.action.onClick.addListener(() => { // [!code --]
browser.action.onClicked.addListener(() => { // [!code --]
// ... // [!code --]
}); // [!code --]
export default defineBackground(() => {
browser.action.onClick.addListener(() => { // [!code ++]
browser.action.onClicked.addListener(() => { // [!code ++]
// ... // [!code ++]
}); // [!code ++]
});
+1 -1
View File
@@ -85,7 +85,7 @@ import { injectScript, createShadowRootUi } from '#imports';
But Vitest sees this:
```ts
import { injectScript } from 'wxt/browser';
import { injectScript } from 'wxt/utils/inject-script';
import { createShadowRootUi } from 'wxt/utils/content-script-ui/shadow-root';
```
+28 -28
View File
@@ -4,7 +4,7 @@
"engines": {
"node": ">=18.20.3"
},
"packageManager": "pnpm@10.8.0",
"packageManager": "pnpm@10.10.0",
"scripts": {
"check": "check && pnpm -r --sequential run check",
"test": "pnpm -r --sequential run test run",
@@ -17,33 +17,33 @@
"docs:preview": "pnpm -s docs:gen && vitepress preview docs"
},
"devDependencies": {
"@aklinker1/buildc": "catalog:",
"@aklinker1/check": "catalog:",
"@commitlint/config-conventional": "catalog:",
"@commitlint/types": "catalog:",
"@types/fs-extra": "catalog:",
"@vitest/coverage-v8": "catalog:",
"changelogen": "catalog:",
"consola": "catalog:",
"fast-glob": "catalog:",
"feed": "catalog:",
"fs-extra": "catalog:",
"lint-staged": "catalog:",
"markdown-it-footnote": "catalog:",
"nano-spawn": "catalog:",
"prettier": "catalog:",
"simple-git-hooks": "catalog:",
"tsx": "catalog:",
"typedoc": "catalog:",
"typedoc-plugin-frontmatter": "catalog:",
"typedoc-plugin-markdown": "catalog:",
"typedoc-vitepress-theme": "catalog:",
"typescript": "catalog:",
"vitepress": "catalog:",
"vitepress-knowledge": "catalog:",
"vitepress-plugin-group-icons": "catalog:",
"vitest-mock-extended": "catalog:",
"vue": "catalog:",
"@aklinker1/buildc": "^1.1.4",
"@aklinker1/check": "2.0.0",
"@commitlint/config-conventional": "^19.8.0",
"@commitlint/types": "^19.8.0",
"@types/fs-extra": "^11.0.4",
"@vitest/coverage-v8": "^3.1.2",
"changelogen": "^0.6.1",
"consola": "^3.4.2",
"fast-glob": "^3.3.3",
"feed": "^4.2.2",
"fs-extra": "^11.3.0",
"lint-staged": "^15.5.1",
"markdown-it-footnote": "^4.0.0",
"nano-spawn": "^0.2.0",
"prettier": "^3.5.3",
"simple-git-hooks": "^2.13.0",
"tsx": "4.19.4",
"typedoc": "^0.25.4",
"typedoc-plugin-frontmatter": "^1.3.0",
"typedoc-plugin-markdown": "4.0.0-next.23",
"typedoc-vitepress-theme": "1.0.0-next.3",
"typescript": "^5.8.3",
"vitepress": "^1.6.3",
"vitepress-knowledge": "^0.4.1",
"vitepress-plugin-group-icons": "^1.5.2",
"vitest-mock-extended": "^3.1.0",
"vue": "^3.5.13",
"wxt": "workspace:*"
},
"simple-git-hooks": {
@@ -9,11 +9,12 @@ import type {
AnalyticsEventMetadata,
AnalyticsProvider,
} from './types';
import { browser } from '@wxt-dev/browser';
const ANALYTICS_PORT = '@wxt-dev/analytics';
export function createAnalytics(config?: AnalyticsConfig): Analytics {
if (typeof chrome === 'undefined' || !chrome?.runtime?.id)
if (!browser?.runtime?.id)
throw Error(
'Cannot use WXT analytics in contexts without access to the browser.runtime APIs',
);
@@ -51,13 +52,13 @@ function createBackgroundAnalytics(
defineStorageItem<boolean>('local:wxt-analytics:enabled', false);
// Cached values
const platformInfo = chrome.runtime.getPlatformInfo();
const platformInfo = browser.runtime.getPlatformInfo();
const userAgent = UAParser();
let userId = Promise.resolve(userIdStorage.getValue()).then(
(id) => id ?? globalThis.crypto.randomUUID(),
);
let userProperties = userPropertiesStorage.getValue();
const manifest = chrome.runtime.getManifest();
const manifest = browser.runtime.getManifest();
const getBackgroundMeta = () => ({
timestamp: Date.now(),
@@ -178,7 +179,7 @@ function createBackgroundAnalytics(
config?.providers?.map((provider) => provider(analytics, config)) ?? [];
// Listen for messages from the rest of the extension
chrome.runtime.onConnect.addListener((port) => {
browser.runtime.onConnect.addListener((port) => {
if (port.name === ANALYTICS_PORT) {
port.onMessage.addListener(({ fn, args }) => {
// @ts-expect-error: Untyped fn key
@@ -194,7 +195,7 @@ function createBackgroundAnalytics(
* Creates an analytics client for non-background contexts.
*/
function createFrontendAnalytics(): Analytics {
const port = chrome.runtime.connect({ name: ANALYTICS_PORT });
const port = browser.runtime.connect({ name: ANALYTICS_PORT });
const sessionId = Date.now();
const getFrontendMetadata = (): AnalyticsEventMetadata => ({
sessionId,
@@ -252,8 +253,8 @@ function defineStorageItem<T>(
): AnalyticsStorageItem<T> {
return {
getValue: async () =>
(await chrome.storage.local.get(key))[key] ?? defaultValue,
setValue: (newValue) => chrome.storage.local.set({ [key]: newValue }),
(await browser.storage.local.get(key))[key] ?? defaultValue,
setValue: (newValue) => browser.storage.local.set({ [key]: newValue }),
};
}
+7 -7
View File
@@ -51,15 +51,15 @@
"wxt": ">=0.20.0"
},
"devDependencies": {
"@aklinker1/check": "catalog:",
"@types/chrome": "catalog:",
"@types/ua-parser-js": "catalog:",
"publint": "catalog:",
"typescript": "catalog:",
"unbuild": "catalog:",
"@aklinker1/check": "2.0.0",
"@types/ua-parser-js": "^0.7.39",
"publint": "^0.3.12",
"typescript": "^5.8.3",
"unbuild": "^3.5.0",
"wxt": "workspace:*"
},
"dependencies": {
"ua-parser-js": "catalog:"
"@wxt-dev/browser": "workspace:^",
"ua-parser-js": "^1.0.40"
}
}
+1 -2
View File
@@ -3,8 +3,7 @@
"compilerOptions": {
"paths": {
"#analytics": ["./.wxt/analytics/index.ts"]
},
"types": ["chrome"]
}
},
"exclude": ["node_modules", "dist"]
}
+8 -8
View File
@@ -45,16 +45,16 @@
"wxt": ">=0.19.0"
},
"dependencies": {
"defu": "catalog:",
"fs-extra": "catalog:",
"sharp": "catalog:"
"defu": "^6.1.4",
"fs-extra": "^11.3.0",
"sharp": "^0.34.1"
},
"devDependencies": {
"@aklinker1/check": "catalog:",
"oxlint": "catalog:",
"publint": "catalog:",
"typescript": "catalog:",
"unbuild": "catalog:",
"@aklinker1/check": "2.0.0",
"oxlint": "^0.16.8",
"publint": "^0.3.12",
"typescript": "^5.8.3",
"unbuild": "^3.5.0",
"wxt": "workspace:*"
}
}
+7 -7
View File
@@ -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.0.315",
"version": "0.0.318",
"type": "module",
"main": "src/index.mjs",
"types": "src/index.d.ts",
@@ -23,12 +23,12 @@
"src"
],
"devDependencies": {
"@types/chrome": "0.0.315",
"fs-extra": "catalog:",
"nano-spawn": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
"@types/chrome": "0.0.318",
"fs-extra": "^11.3.0",
"nano-spawn": "^0.2.0",
"tsx": "4.19.4",
"typescript": "^5.8.3",
"vitest": "^3.1.2"
},
"dependencies": {
"@types/filesystem": "*",
+2
View File
@@ -76,5 +76,7 @@ function transformFile(file: string, content: string): string {
.replaceAll('declare namespace chrome', 'export namespace Browser')
// Update references to `chrome` namespace to `Browser`
.replaceAll('chrome.', 'Browser.')
// Fix links to developer.chrome.com
.replaceAll('developer.Browser.com', 'developer.chrome.com')
);
}
+127 -85
View File
@@ -7480,7 +7480,7 @@ export namespace Browser {
*/
export namespace permissions {
export interface Permissions {
/** The list of host permissions, including those specified in the `optional_permissions` or `permissions` keys in the manifest, and those associated with [Content Scripts](https://developer.Browser.com/docs/extensions/develop/concepts/content-scripts). */
/** The list of host permissions, including those specified in the `optional_permissions` or `permissions` keys in the manifest, and those associated with [Content Scripts](https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts). */
origins?: string[];
/** List of named permissions (does not include hosts or origins). */
permissions?: Browser.runtime.ManifestPermissions[];
@@ -8498,7 +8498,7 @@ export namespace Browser {
}
export interface ConnectionOptions {
/** Optional. Flag indicating whether or not the connection should be left open when the application is suspended (see Manage App Lifecycle: https://developer.Browser.com/apps/app_lifecycle).
/** Optional. Flag indicating whether or not the connection should be left open when the application is suspended (see Manage App Lifecycle: https://developer.chrome.com/apps/app_lifecycle).
* The default value is "false." When the application is loaded, any serial connections previously opened with persistent=true can be fetched with getConnections. */
persistent?: boolean | undefined;
/** Optional. An application-defined string to associate with the connection. */
@@ -8716,13 +8716,13 @@ export namespace Browser {
/** The ID of the extension/app. */
export var id: string;
/** https://developer.Browser.com/docs/extensions/reference/api/runtime#type-PlatformOs */
/** https://developer.chrome.com/docs/extensions/reference/api/runtime#type-PlatformOs */
export type PlatformOs = "mac" | "win" | "android" | "cros" | "linux" | "openbsd" | "fuchsia";
/** https://developer.Browser.com/docs/extensions/reference/api/runtime#type-PlatformArch */
/** https://developer.chrome.com/docs/extensions/reference/api/runtime#type-PlatformArch */
export type PlatformArch = "arm" | "arm64" | "x86-32" | "x86-64" | "mips" | "mips64";
/** https://developer.Browser.com/docs/extensions/reference/api/runtime#type-PlatformNaclArch */
/** https://developer.chrome.com/docs/extensions/reference/api/runtime#type-PlatformNaclArch */
export type PlatformNaclArch = "arm" | "x86-32" | "x86-64" | "mips" | "mips64";
/** https://developer.Browser.com/docs/extensions/reference/api/runtime#type-ContextType */
/** https://developer.chrome.com/docs/extensions/reference/api/runtime#type-ContextType */
export enum ContextType {
TAB = "TAB",
POPUP = "POPUP",
@@ -8731,7 +8731,7 @@ export namespace Browser {
SIDE_PANEL = "SIDE_PANEL",
DEVELOPER_TOOLS = "DEVELOPER_TOOLS",
}
/** https://developer.Browser.com/docs/extensions/reference/api/runtime#type-OnInstalledReason */
/** https://developer.chrome.com/docs/extensions/reference/api/runtime#type-OnInstalledReason */
export enum OnInstalledReason {
INSTALL = "install",
UPDATE = "update",
@@ -8958,7 +8958,7 @@ export namespace Browser {
default_popup?: string | undefined;
}
// Source: https://developer.Browser.com/docs/extensions/mv3/declare_permissions/
/** Source: https://developer.chrome.com/docs/extensions/reference/permissions-list */
export type ManifestPermissions =
| "accessibilityFeatures.modify"
| "accessibilityFeatures.read"
@@ -9015,11 +9015,11 @@ export namespace Browser {
| "privacy"
| "processes"
| "proxy"
| "readingList"
| "scripting"
| "search"
| "sessions"
| "sidePanel"
| "signedInDevices"
| "storage"
| "system.cpu"
| "system.display"
@@ -9042,6 +9042,23 @@ export namespace Browser {
| "webRequestBlocking"
| "webRequestAuthProvider";
/** Source : https://developer.chrome.com/docs/extensions/reference/api/permissions */
export type ManifestOptionalPermissions = Exclude<
ManifestPermissions,
| "debugger"
| "declarativeNetRequest"
| "devtools"
| "experimental"
| "fontSettings"
| "geolocation"
| "proxy"
| "tts"
| "ttsEngine"
| "unlimitedStorage"
| "wallpaper"
| "webAuthenticationProxy"
>;
export interface SearchProvider {
name?: string | undefined;
keyword?: string | undefined;
@@ -9261,8 +9278,8 @@ export namespace Browser {
}
| undefined;
content_security_policy?: string | undefined;
optional_permissions?: string[] | undefined;
permissions?: string[] | undefined;
optional_permissions?: ManifestOptionalPermissions[] | string[] | undefined;
permissions?: ManifestPermissions[] | string[] | undefined;
web_accessible_resources?: string[] | undefined;
}
@@ -9297,7 +9314,7 @@ export namespace Browser {
sandbox?: string;
};
host_permissions?: string[] | undefined;
optional_permissions?: ManifestPermissions[] | undefined;
optional_permissions?: ManifestOptionalPermissions[] | undefined;
optional_host_permissions?: string[] | undefined;
permissions?: ManifestPermissions[] | undefined;
web_accessible_resources?: Array<{ resources: string[]; matches: string[] }> | undefined;
@@ -12211,131 +12228,111 @@ export namespace Browser {
*/
export namespace tabGroups {
/** An ID that represents the absence of a group. */
export var TAB_GROUP_ID_NONE: -1;
export const TAB_GROUP_ID_NONE: -1;
export type ColorEnum = "grey" | "blue" | "red" | "yellow" | "green" | "pink" | "purple" | "cyan" | "orange";
/** The group's color. */
export enum Color {
BLUE = "blue",
CYAN = "cyan",
GREEN = "green",
GREY = "grey",
ORANGE = "orange",
PINK = "pink",
PURPLE = "purple",
RED = "red",
YELLOW = "yellow",
}
export interface TabGroup {
/** Whether the group is collapsed. A collapsed group is one whose tabs are hidden. */
collapsed: boolean;
/** The group's color. */
color: ColorEnum;
color: `${Color}`;
/** The ID of the group. Group IDs are unique within a browser session. */
id: number;
/** Optional. The title of the group. */
title?: string | undefined;
/** The title of the group. */
title?: string;
/** The ID of the window that contains the group. */
windowId: number;
}
export interface MoveProperties {
/** The position to move the group to. Use -1 to place the group at the end of the window. */
/** The position to move the group to. Use `-1` to place the group at the end of the window. */
index: number;
/** Optional. The window to move the group to. Defaults to the window the group is currently in. Note that groups can only be moved to and from windows with Browser.windows.WindowType type "normal". */
windowId?: number | undefined;
/** The window to move the group to. Defaults to the window the group is currently in. Note that groups can only be moved to and from windows with {@link windows.windowTypeEnum windows.windowType} type `"normal"`. */
windowId?: number;
}
export interface QueryInfo {
/** Optional. Whether the groups are collapsed. */
collapsed?: boolean | undefined;
/** Optional. The color of the groups. */
color?: ColorEnum | undefined;
/** Optional. Match group titles against a pattern. */
title?: string | undefined;
/** Optional. The ID of the window that contains the group. */
windowId?: number | undefined;
/** Whether the groups are collapsed. */
collapsed?: boolean;
/** The color of the groups. */
color?: `${Color}`;
/** Match group titles against a pattern. */
title?: string;
/** The ID of the parent window, or {@link windows.WINDOW_ID_CURRENT} for the current window. */
windowId?: number;
}
export interface UpdateProperties {
/** Optional. Whether the group should be collapsed. */
collapsed?: boolean | undefined;
/** Optional. The color of the group. */
color?: ColorEnum | undefined;
/** Optional. The title of the group. */
title?: string | undefined;
/** Whether the group should be collapsed. */
collapsed?: boolean;
/** The color of the group. */
color?: `${Color}`;
/** The title of the group. */
title?: string;
}
/**
* Retrieves details about the specified group.
* @param groupId The ID of the tab group.
* @param callback Called with the retrieved tab group.
*
* Can return its result via Promise since Chrome 90.
*/
export function get(groupId: number): Promise<TabGroup>;
export function get(groupId: number, callback: (group: TabGroup) => void): void;
/**
* Retrieves details about the specified group.
* @param groupId The ID of the tab group.
* @return The `get` method provides its result via callback or returned as a `Promise` (MV3 only).
*/
export function get(groupId: number): Promise<TabGroup>;
/**
* Moves the group and all its tabs within its window, or to a new window.
* @param groupId The ID of the group to move.
* @param moveProperties Information on how to move the group.
* @return The `move` method provides its result via callback or returned as a `Promise` (MV3 only).
*/
export function move(groupId: number, moveProperties: MoveProperties): Promise<TabGroup>;
/**
* Moves the group and all its tabs within its window, or to a new window.
* @param groupId The ID of the group to move.
* @param moveProperties Information on how to move the group.
* @param callback Optional.
*
* Can return its result via Promise since Chrome 90.
*/
export function move(groupId: number, moveProperties: MoveProperties): Promise<TabGroup | undefined>;
export function move(
groupId: number,
moveProperties: MoveProperties,
callback: (group: TabGroup) => void,
callback: (group?: TabGroup) => void,
): void;
/**
* Gets all groups that have the specified properties, or all groups if no properties are specified.
* @param queryInfo Object with search parameters.
* @param callback Called with retrieved tab groups.
*
* Can return its result via Promise since Chrome 90.
*/
export function query(queryInfo: QueryInfo): Promise<TabGroup[]>;
export function query(queryInfo: QueryInfo, callback: (result: TabGroup[]) => void): void;
/**
* Gets all groups that have the specified properties, or all groups if no properties are specified.
* @param queryInfo Object with search parameters.
* @return The `query` method provides its result via callback or returned as a `Promise` (MV3 only).
*/
export function query(queryInfo: QueryInfo): Promise<TabGroup[]>;
/**
* Modifies the properties of a group. Properties that are not specified in updateProperties are not modified.
* Modifies the properties of a group. Properties that are not specified in `updateProperties` are not modified.
* @param groupId The ID of the group to modify.
* @param updateProperties Information on how to update the group.
* @return The `update` method provides its result via callback or returned as a `Promise` (MV3 only).
*/
export function update(groupId: number, updateProperties: UpdateProperties): Promise<TabGroup>;
/**
* Modifies the properties of a group. Properties that are not specified in updateProperties are not modified.
* @param groupId The ID of the group to modify.
* @param updateProperties Information on how to update the group.
* @param callback Optional.
*
* Can return its result via Promise since Chrome 90.
*/
export function update(groupId: number, updateProperties: UpdateProperties): Promise<TabGroup | undefined>;
export function update(
groupId: number,
updateProperties: UpdateProperties,
callback: (group: TabGroup) => void,
callback: (group?: TabGroup) => void,
): void;
export interface TabGroupCreatedEvent extends Browser.events.Event<(group: TabGroup) => void> {}
export interface TabGroupMovedEvent extends Browser.events.Event<(group: TabGroup) => void> {}
export interface TabGroupRemovedEvent extends Browser.events.Event<(group: TabGroup) => void> {}
export interface TabGroupUpdated extends Browser.events.Event<(group: TabGroup) => void> {}
/** Fired when a group is created. */
export var onCreated: TabGroupCreatedEvent;
export const onCreated: events.Event<(group: TabGroup) => void>;
/** Fired when a group is moved within a window. Move events are still fired for the individual tabs within the group, as well as for the group itself. This event is not fired when a group is moved between windows; instead, it will be removed from one window and created in another. */
export var onMoved: TabGroupMovedEvent;
/** Fired when a group is closed, either directly by the user or automatically because it contained zero. */
export var onRemoved: TabGroupRemovedEvent;
export const onMoved: events.Event<(group: TabGroup) => void>;
/** Fired when a group is closed, either directly by the user or automatically because it contained zero tabs. */
export const onRemoved: events.Event<(group: TabGroup) => void>;
/** Fired when a group is updated. */
export var onUpdated: TabGroupUpdated;
export const onUpdated: events.Event<(group: TabGroup) => void>;
}
////////////////////
@@ -14845,6 +14842,18 @@ export namespace Browser {
*/
export type ExecutionWorld = "MAIN" | "USER_SCRIPT";
/** @since Chrome 135 */
export interface InjectionResult {
/** The document associated with the injection. */
documentId: string;
/** The error, if any. `error` and `result` are mutually exclusive. */
error?: string;
/** The frame associated with the injection. */
frameId: number;
/** The result of the script execution. */
result: any;
}
export interface WorldProperties {
/** Specifies the world csp. The default is the `ISOLATED` world csp. */
csp?: string;
@@ -14861,6 +14870,18 @@ export namespace Browser {
ids?: string[];
}
/** @since Chrome 135 */
export interface InjectionTarget {
/** Whether the script should inject into all frames within the tab. Defaults to false. This must not be true if `frameIds` is specified. */
allFrames?: boolean;
/** The IDs of specific documentIds to inject into. This must not be set if `frameIds` is set. */
documentIds?: string[];
/** The IDs of specific frames to inject into. */
frameIds?: number[];
/** The ID of the tab into which to inject. */
tabId: number;
}
export interface RegisteredUserScript {
/** If true, it will inject into all frames, even if the frame is not the top-most frame in the tab. Each frame is checked independently for URL requirements; it will not inject into child frames if the URL requirements are not met. Defaults to false, meaning that only the top frame is matched. */
allFrames?: boolean;
@@ -14887,6 +14908,20 @@ export namespace Browser {
worldId?: string;
}
/** @since Chrome 135 */
export interface UserScriptInjection {
/** Whether the injection should be triggered in the target as soon as possible. Note that this is not a guarantee that injection will occur prior to page load, as the page may have already loaded by the time the script reaches the target. */
injectImmediately?: boolean;
/** The list of ScriptSource objects defining sources of scripts to be injected into the target. */
js: ScriptSource[];
/** Details specifying the target into which to inject the script. */
target: InjectionTarget;
/** The JavaScript "world" to run the script in. The default is `USER_SCRIPT`. */
world?: ExecutionWorld;
/** Specifies the user script world ID to execute in. If omitted, the script will execute in the default user script world. Only valid if `world` is omitted or is `USER_SCRIPT`. Values with leading underscores (`_`) are reserved. */
worldId?: string;
}
/**
* Properties for a script source.
*/
@@ -14939,6 +14974,13 @@ export namespace Browser {
export function getWorldConfigurations(): Promise<WorldProperties[]>;
export function getWorldConfigurations(callback: (worlds: WorldProperties[]) => void): void;
/**
* Injects a script into a target context. By default, the script will be run at `document_idle`, or immediately if the page has already loaded. If the `injectImmediately` property is set, the script will inject without waiting, even if the page has not finished loading. If the script evaluates to a promise, the browser will wait for the promise to settle and return the resulting value.
* @since Chrome 135
*/
export function execute(injection: UserScriptInjection): Promise<InjectionResult[]>;
export function execute(injection: UserScriptInjection, callback: (result: InjectionResult[]) => void): void;
/**
* Registers one or more user scripts for this extension.
*
+5 -5
View File
@@ -24,10 +24,10 @@
],
"devDependencies": {
"@types/chrome": "{{chromeTypesVersion}}",
"fs-extra": "catalog:",
"nano-spawn": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
"fs-extra": "^11.3.0",
"nano-spawn": "^0.2.0",
"tsx": "4.19.4",
"typescript": "^5.8.3",
"vitest": "^3.1.2"
}
}
+31
View File
@@ -1,5 +1,36 @@
# Changelog
## v0.2.4
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.3...i18n-v0.2.4)
### 🩹 Fixes
- Standardize locale codes and warn about unsupported ones ([#1617](https://github.com/wxt-dev/wxt/pull/1617))
- Use `@wxt-dev/browser` instead of `@types/chrome` ([#1645](https://github.com/wxt-dev/wxt/pull/1645))
### 📖 Documentation
- Add react language ID to README ([#1347](https://github.com/wxt-dev/wxt/pull/1347))
- Fix public path reference ([bcb20874](https://github.com/wxt-dev/wxt/commit/bcb20874))
### 🏡 Chore
- **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164))
- **deps:** Bump dev and non-breaking major dependencies ([#1167](https://github.com/wxt-dev/wxt/pull/1167))
- Add funding links to `package.json` files ([#1446](https://github.com/wxt-dev/wxt/pull/1446))
- Use PNPM 10's new catelog feature ([#1493](https://github.com/wxt-dev/wxt/pull/1493))
- Move production dependencies to PNPM 10 catelog ([#1494](https://github.com/wxt-dev/wxt/pull/1494))
- Stop using PNPM catalog ([#1644](https://github.com/wxt-dev/wxt/pull/1644))
- Upgrade `@aklinker1/check` to v2 ([#1647](https://github.com/wxt-dev/wxt/pull/1647))
- Change browser workspace dependency to `^` ([c7335add](https://github.com/wxt-dev/wxt/commit/c7335add))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
- Okinea Dev ([@okineadev](https://github.com/okineadev))
- Redwoodlid ([@redwoodlid](https://github.com/redwoodlid))
## v0.2.3
[compare changes](https://github.com/wxt-dev/wxt/compare/i18n-v0.2.2...i18n-v0.2.3)
+13 -13
View File
@@ -1,7 +1,7 @@
{
"name": "@wxt-dev/i18n",
"description": "Type-safe wrapper around browser.i18n.getMessage with additional features",
"version": "0.2.3",
"version": "0.2.4",
"type": "module",
"repository": {
"type": "git",
@@ -26,9 +26,10 @@
"test": "buildc --deps-only -- vitest"
},
"dependencies": {
"chokidar": "catalog:",
"confbox": "catalog:",
"fast-glob": "catalog:"
"@wxt-dev/browser": "workspace:^",
"chokidar": "^4.0.3",
"confbox": "^0.1.8 || ^0.2.2",
"fast-glob": "^3.3.3"
},
"peerDependencies": {
"wxt": ">=0.19.7"
@@ -39,15 +40,14 @@
}
},
"devDependencies": {
"@aklinker1/check": "catalog:",
"@types/chrome": "catalog:",
"@types/node": "catalog:",
"oxlint": "catalog:",
"publint": "catalog:",
"typescript": "catalog:",
"unbuild": "catalog:",
"vitest": "catalog:",
"vitest-plugin-random-seed": "catalog:",
"@aklinker1/check": "2.0.0",
"@types/node": "^20.17.6",
"oxlint": "^0.16.8",
"publint": "^0.3.12",
"typescript": "^5.8.3",
"unbuild": "^3.5.0",
"vitest": "^3.1.2",
"vitest-plugin-random-seed": "^1.1.1",
"wxt": "workspace:*"
},
"main": "./dist/index.cjs",
+11 -6
View File
@@ -1,13 +1,18 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createI18n } from '../index';
import { browser } from '@wxt-dev/browser';
const getMessageMock = vi.fn();
vi.stubGlobal('chrome', {
i18n: {
getMessage: getMessageMock,
},
vi.mock('@wxt-dev/browser', async () => {
const { vi } = await import('vitest');
return {
browser: {
i18n: {
getMessage: vi.fn(),
},
},
};
});
const getMessageMock = vi.mocked(browser.i18n.getMessage);
describe('createI18n', () => {
beforeEach(() => {
+11 -6
View File
@@ -1,13 +1,18 @@
import { beforeEach, describe, it, vi } from 'vitest';
import { createI18n } from '..';
import { browser } from '@wxt-dev/browser';
const getMessageMock = vi.fn();
vi.stubGlobal('chrome', {
i18n: {
getMessage: getMessageMock,
},
vi.mock('@wxt-dev/browser', async () => {
const { vi } = await import('vitest');
return {
browser: {
i18n: {
getMessage: vi.fn(),
},
},
};
});
const getMessageMock = vi.mocked(browser.i18n.getMessage);
const n: number = 1;
+32 -2
View File
@@ -1,10 +1,14 @@
import { describe, it, expect } from 'vitest';
import { ChromeMessage } from '../build';
import { applyChromeMessagePlaceholders, getSubstitutionCount } from '../utils';
import {
applyChromeMessagePlaceholders,
getSubstitutionCount,
standardizeLocale,
} from '../utils';
describe('Utils', () => {
describe('applyChromeMessagePlaceholders', () => {
it('should return the combined stirng', () => {
it('should return the combined string', () => {
const input = {
message: 'Hello $username$, welcome to $appName$',
placeholders: {
@@ -60,4 +64,30 @@ describe('Utils', () => {
expect(getSubstitutionCount('Hello $10')).toBe(1);
});
});
describe('standardizeLocale', () => {
it('should convert two-letter locale codes to lowercase', () => {
expect(standardizeLocale('en')).toEqual('en');
expect(standardizeLocale('EN')).toEqual('en');
});
it('should convert locale code extensions to uppercase', () => {
expect(standardizeLocale('en_US')).toEqual('en_US');
expect(standardizeLocale('en_us')).toEqual('en_US');
expect(standardizeLocale('es_419')).toEqual('es_419');
});
it('should convert dashes to underscores', () => {
expect(standardizeLocale('en_US')).toEqual('en_US');
expect(standardizeLocale('en-US')).toEqual('en_US');
});
it('should return the input string as-is for unknown formats', () => {
expect(standardizeLocale('en_USSS')).toEqual('en_USSS');
expect(standardizeLocale('en-')).toEqual('en-');
expect(standardizeLocale('------')).toEqual('------');
expect(standardizeLocale('test')).toEqual('test');
expect(standardizeLocale('hello-world')).toEqual('hello-world');
});
});
});
+2
View File
@@ -10,6 +10,8 @@ import { parseYAML, parseJSON5, parseTOML } from 'confbox';
import { dirname, extname } from 'node:path';
import { applyChromeMessagePlaceholders, getSubstitutionCount } from './utils';
export { SUPPORTED_LOCALES } from './supported-locales';
//
// TYPES
//
+3 -2
View File
@@ -7,6 +7,7 @@ import {
I18n,
Substitution,
} from './types';
import { browser } from '@wxt-dev/browser';
export function createI18n<
T extends I18nStructure = DefaultI18nStructure,
@@ -39,9 +40,9 @@ export function createI18n<
if (sub?.length) {
// Convert all substitutions to strings
const stringSubs = sub?.map((sub) => String(sub));
message = chrome.i18n.getMessage(key.replaceAll('.', '_'), stringSubs);
message = browser.i18n.getMessage(key.replaceAll('.', '_'), stringSubs);
} else {
message = chrome.i18n.getMessage(key.replaceAll('.', '_'));
message = browser.i18n.getMessage(key.replaceAll('.', '_'));
}
if (!message) {
console.warn(`[i18n] Message not found: "${key}"`);
+18 -4
View File
@@ -16,12 +16,14 @@ import {
generateChromeMessagesText,
parseMessagesFile,
generateTypeText,
SUPPORTED_LOCALES,
} from './build';
import glob from 'fast-glob';
import { basename, extname, join, resolve } from 'node:path';
import { watch } from 'chokidar';
import { GeneratedPublicFile, WxtDirFileEntry } from 'wxt';
import { writeFile } from 'node:fs/promises';
import { standardizeLocale } from './utils';
export default defineWxtModule<I18nOptions>({
name: '@wxt-dev/i18n',
@@ -46,10 +48,22 @@ export default defineWxtModule<I18nOptions>({
cwd: localesDir,
absolute: true,
});
return files.map((file) => ({
file,
locale: basename(file).replace(extname(file), ''),
}));
const unsupportedLocales: string[] = [];
const res = files.map((file) => {
const rawLocale = basename(file).replace(extname(file), '');
const locale = standardizeLocale(rawLocale);
if (!SUPPORTED_LOCALES.has(locale)) unsupportedLocales.push(locale);
return { file, locale };
});
if (unsupportedLocales.length > 0)
wxt.logger.warn(
`Unsupported locales: [${unsupportedLocales.join(', ')}].\n\nWeb extensions only support a limited set of locales as described here: https://developer.chrome.com/docs/extensions/reference/api/i18n#locales`,
);
return res;
};
const generateOutputJsonFiles = async (): Promise<
+58
View File
@@ -0,0 +1,58 @@
/** From https://developer.chrome.com/docs/extensions/reference/api/i18n#locales */
export const SUPPORTED_LOCALES = new Set([
'ar',
'am',
'bg',
'bn',
'ca',
'cs',
'da',
'de',
'el',
'en',
'en_AU',
'en_GB',
'en_US',
'es',
'es_419',
'et',
'fa',
'fi',
'fil',
'fr',
'gu',
'he',
'hi',
'hr',
'hu',
'id',
'it',
'ja',
'kn',
'ko',
'lt',
'lv',
'ml',
'mr',
'ms',
'nl',
'no',
'pl',
'pt_BR',
'pt_PT',
'ro',
'ru',
'sk',
'sl',
'sr',
'sv',
'sw',
'ta',
'te',
'th',
'tr',
'uk',
'vi',
'zh_CN',
'zh_TW',
]);
+13
View File
@@ -21,3 +21,16 @@ export function getSubstitutionCount(message: string): number {
}
const MAX_SUBSTITUTIONS = 9;
/** Given a string, standardize it to the format `xx_YY`. */
export function standardizeLocale(locale: string): string {
if (locale.length === 2) return locale.toLowerCase();
const [is_match, prefix, suffix] =
locale.match(/^([a-z]{2})[-_]([a-z]{2,3})$/i) ?? [];
if (is_match) {
return `${prefix.toLowerCase()}_${suffix.toUpperCase()}`;
}
return locale;
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": ["chrome", "node"]
"types": ["node"]
},
"exclude": ["node_modules/**", "dist/**"]
}
+9 -9
View File
@@ -48,17 +48,17 @@
"wxt": ">=0.19.16"
},
"dependencies": {
"@vitejs/plugin-react": "catalog:"
"@vitejs/plugin-react": "^4.4.1"
},
"devDependencies": {
"@aklinker1/check": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"publint": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"typescript": "catalog:",
"unbuild": "catalog:",
"@aklinker1/check": "2.0.0",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3",
"publint": "^0.3.12",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"typescript": "^5.8.3",
"unbuild": "^3.5.0",
"wxt": "workspace:*"
}
}
+6 -6
View File
@@ -48,14 +48,14 @@
"wxt": ">=0.19.16"
},
"dependencies": {
"vite-plugin-solid": "catalog:"
"vite-plugin-solid": "^2.11.6"
},
"devDependencies": {
"@aklinker1/check": "catalog:",
"publint": "catalog:",
"solid-js": "catalog:",
"typescript": "catalog:",
"unbuild": "catalog:",
"@aklinker1/check": "2.0.0",
"publint": "^0.3.12",
"solid-js": "^1.9.6",
"typescript": "^5.8.3",
"unbuild": "^3.5.0",
"wxt": "workspace:*"
}
}
+5 -5
View File
@@ -47,13 +47,13 @@
"svelte": ">=5"
},
"dependencies": {
"@sveltejs/vite-plugin-svelte": "catalog:"
"@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0"
},
"devDependencies": {
"@aklinker1/check": "catalog:",
"publint": "catalog:",
"typescript": "catalog:",
"unbuild": "catalog:",
"@aklinker1/check": "2.0.0",
"publint": "^0.3.12",
"typescript": "^5.8.3",
"unbuild": "^3.5.0",
"wxt": "workspace:*"
}
}
+5 -5
View File
@@ -46,13 +46,13 @@
"wxt": ">=0.19.16"
},
"dependencies": {
"@vitejs/plugin-vue": "catalog:"
"@vitejs/plugin-vue": "^5.2.3"
},
"devDependencies": {
"@aklinker1/check": "catalog:",
"publint": "catalog:",
"typescript": "catalog:",
"unbuild": "catalog:",
"@aklinker1/check": "2.0.0",
"publint": "^0.3.12",
"typescript": "^5.8.3",
"unbuild": "^3.5.0",
"wxt": "workspace:*"
}
}
+10 -10
View File
@@ -30,18 +30,18 @@
"test": "buildc --deps-only -- vitest"
},
"dependencies": {
"async-mutex": "catalog:",
"dequal": "catalog:"
"@wxt-dev/browser": "workspace:^",
"async-mutex": "^0.5.0",
"dequal": "^2.0.3"
},
"devDependencies": {
"@aklinker1/check": "catalog:",
"@types/chrome": "catalog:",
"@webext-core/fake-browser": "catalog:",
"oxlint": "catalog:",
"publint": "catalog:",
"typescript": "catalog:",
"unbuild": "catalog:",
"vitest": "catalog:"
"@aklinker1/check": "2.0.0",
"@webext-core/fake-browser": "^1.3.2",
"oxlint": "^0.16.8",
"publint": "^0.3.12",
"typescript": "^5.8.3",
"unbuild": "^3.5.0",
"vitest": "^3.1.2"
},
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
+22 -3
View File
@@ -1,6 +1,7 @@
import { fakeBrowser } from '@webext-core/fake-browser';
import { describe, it, expect, beforeEach, vi, expectTypeOf } from 'vitest';
import { MigrationError, type WxtStorageItem, storage } from '../index';
import { browser } from '@wxt-dev/browser';
/**
* This works because fakeBrowser is synchronous, and is will finish any number of chained
@@ -222,7 +223,7 @@ describe('Storage Utils', () => {
describe('setMeta', () => {
it('should set metadata at key+$', async () => {
const existing = { v: 1 };
await chrome.storage[storageArea].set({ count$: existing });
await browser.storage[storageArea].set({ count$: existing });
const newValues = {
date: Date.now(),
};
@@ -238,7 +239,7 @@ describe('Storage Utils', () => {
'should remove any properties set to %s',
async (version) => {
const existing = { v: 1 };
await chrome.storage[storageArea].set({ count$: existing });
await browser.storage[storageArea].set({ count$: existing });
const expected = {};
await storage.setMeta(`${storageArea}:count`, { v: version });
@@ -1265,7 +1266,7 @@ describe('Storage Utils', () => {
await item.removeValue();
// Make sure it's actually blank before running the test
expect(await chrome.storage.local.get()).toEqual({});
expect(await browser.storage.local.get()).toEqual({});
init.mockClear();
const [value1, value2] = await Promise.all([
@@ -1281,6 +1282,19 @@ describe('Storage Utils', () => {
it('should define a nullable value when options are not passed', () => {
const item = storage.defineItem<number>(`local:test`);
expectTypeOf(item).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
const item2 = storage.defineItem<number>(`local:test`, {});
expectTypeOf(item2).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
const item3 = storage.defineItem<number>(`local:test`, {
fallback: undefined,
});
expectTypeOf(item3).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
const item4 = storage.defineItem<number>(`local:test`, {
defaultValue: undefined,
});
expectTypeOf(item4).toEqualTypeOf<WxtStorageItem<number | null, {}>>();
});
it('should define a non-null value when options are passed with a nullish default value', () => {
@@ -1288,6 +1302,11 @@ describe('Storage Utils', () => {
defaultValue: 123,
});
expectTypeOf(item).toEqualTypeOf<WxtStorageItem<number, {}>>();
const item2 = storage.defineItem(`local:test`, {
fallback: 123,
});
expectTypeOf(item2).toEqualTypeOf<WxtStorageItem<number, {}>>();
});
it('should define a nullable value when options are passed with null default value', () => {
+11 -10
View File
@@ -1,4 +1,3 @@
/// <reference types="chrome" />
/**
* Simplified storage APIs with support for versioned fields, snapshots, metadata, and item definitions.
*
@@ -7,13 +6,7 @@
*/
import { dequal } from 'dequal/lite';
import { Mutex } from 'async-mutex';
const browser: typeof chrome =
// @ts-expect-error
globalThis.browser?.runtime?.id == null
? globalThis.chrome
: // @ts-expect-error
globalThis.browser;
import { browser, type Browser } from '@wxt-dev/browser';
export const storage = createStorage();
@@ -750,8 +743,16 @@ export interface WxtStorage {
): WxtStorageItem<TValue | null, TMetadata>;
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
key: StorageItemKey,
options: WxtStorageItemOptions<TValue>,
options: WxtStorageItemOptions<TValue> & { fallback: TValue },
): WxtStorageItem<TValue, TMetadata>;
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
key: StorageItemKey,
options: WxtStorageItemOptions<TValue> & { defaultValue: TValue },
): WxtStorageItem<TValue, TMetadata>;
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
key: StorageItemKey,
options: WxtStorageItemOptions<TValue>,
): WxtStorageItem<TValue | null, TMetadata>;
}
interface WxtStorageDriver {
@@ -889,7 +890,7 @@ export interface WxtStorageItemOptions<T> {
}
export type StorageAreaChanges = {
[key: string]: chrome.storage.StorageChange;
[key: string]: Browser.storage.StorageChange;
};
/**
+1 -2
View File
@@ -1,8 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"verbatimModuleSyntax": true,
"types": ["chrome"]
"verbatimModuleSyntax": true
},
"exclude": ["node_modules/**", "dist/**"]
}
+8 -8
View File
@@ -40,16 +40,16 @@
"wxt": ">=0.19.0"
},
"devDependencies": {
"@aklinker1/check": "catalog:",
"oxlint": "catalog:",
"publint": "catalog:",
"typescript": "catalog:",
"unbuild": "catalog:",
"unocss": "catalog:",
"@aklinker1/check": "2.0.0",
"oxlint": "^0.16.8",
"publint": "^0.3.12",
"typescript": "^5.8.3",
"unbuild": "^3.5.0",
"unocss": "^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0",
"wxt": "workspace:*"
},
"dependencies": {
"defu": "catalog:",
"fast-glob": "catalog:"
"defu": "^6.1.4",
"fast-glob": "^3.3.3"
}
}
+6 -6
View File
@@ -45,12 +45,12 @@
"wxt": ">=0.20.0"
},
"devDependencies": {
"@aklinker1/check": "catalog:",
"@types/webextension-polyfill": "catalog:",
"publint": "catalog:",
"typescript": "catalog:",
"unbuild": "catalog:",
"webextension-polyfill": "catalog:",
"@aklinker1/check": "2.0.0",
"@types/webextension-polyfill": "^0.12.3",
"publint": "^0.3.12",
"typescript": "^5.8.3",
"unbuild": "^3.5.0",
"webextension-polyfill": "^0.12.0",
"wxt": "workspace:*"
}
}
+9 -9
View File
@@ -18,19 +18,19 @@
},
"dependencies": {
"@wxt-dev/i18n": "workspace:*",
"react": "catalog:",
"react-dom": "catalog:"
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"devDependencies": {
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3",
"@wxt-dev/auto-icons": "workspace:*",
"@wxt-dev/unocss": "workspace:*",
"sass": "catalog:",
"typescript": "catalog:",
"unocss": "catalog:",
"vitest": "catalog:",
"vitest-plugin-random-seed": "catalog:",
"sass": "^1.87.0",
"typescript": "^5.8.3",
"unocss": "^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0",
"vitest": "^3.1.2",
"vitest-plugin-random-seed": "^1.1.1",
"wxt": "workspace:*"
},
"buildc": {
+58
View File
@@ -1,5 +1,63 @@
# Changelog
## v0.20.6
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.5...wxt-v0.20.6)
## v0.20.5
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.4...wxt-v0.20.5)
### 🩹 Fixes
- Don't use crypto.randUUID for shadow root UIs ([3577c0b](https://github.com/wxt-dev/wxt/commit/3577c0b))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.20.4
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.3...wxt-v0.20.4)
### 🚀 Enhancements
- Ignore popup/index.ts instead of erroring ([#1520](https://github.com/wxt-dev/wxt/pull/1520))
- Ignore elements with a `vite-ignore` or `wxt-ignore` attribute ([#1603](https://github.com/wxt-dev/wxt/pull/1603))
- Add `{{packageVersion}}` as template variable ([#1604](https://github.com/wxt-dev/wxt/pull/1604))
### 🩹 Fixes
- Adding missing `""` to `PublicPath` and `browser.runtime.getUrl` ([#1597](https://github.com/wxt-dev/wxt/pull/1597))
- Fix CORS error in Firefox ([#1607](https://github.com/wxt-dev/wxt/pull/1607))
### 📖 Documentation
- **Content Script UI:** Add additional details about when `onRemove` is called ([656a9b3](https://github.com/wxt-dev/wxt/commit/656a9b3))
### ❤️ Contributors
- Yunsup Sim ([@SimYunSup](https://github.com/SimYunSup))
- ТΞNSΛI ([@Tensai75](https://github.com/Tensai75))
- Nishu ([@nishu-murmu](https://github.com/nishu-murmu))
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.20.3
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.2...wxt-v0.20.3)
### 🚀 Enhancements
- Automatically place document-level CSS outside shadow root ([#1594](https://github.com/wxt-dev/wxt/pull/1594))
### 🩹 Fixes
- Fix double hashing of inline script keys ([b0f4ac8](https://github.com/wxt-dev/wxt/commit/b0f4ac8))
### ❤️ Contributors
- Aaron ([@aklinker1](https://github.com/aklinker1))
## v0.20.2
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.20.1...wxt-v0.20.2)
@@ -48,6 +48,7 @@ describe('TypeScript Project', () => {
declare module "wxt/browser" {
export type PublicPath =
| ""
| "/"
| "/options.html"
| "/popup.html"
+65 -61
View File
@@ -1,7 +1,7 @@
{
"name": "wxt",
"type": "module",
"version": "0.20.2",
"version": "0.20.6",
"description": "⚡ Next-gen Web Extension Framework",
"license": "MIT",
"scripts": {
@@ -16,69 +16,69 @@
"prepack": "pnpm build"
},
"dependencies": {
"@1natsu/wait-element": "catalog:",
"@aklinker1/rollup-plugin-visualizer": "catalog:",
"@webext-core/fake-browser": "catalog:",
"@webext-core/isolated-element": "catalog:",
"@webext-core/match-patterns": "catalog:",
"@wxt-dev/browser": "workspace:*",
"@1natsu/wait-element": "^4.1.2",
"@aklinker1/rollup-plugin-visualizer": "5.12.0",
"@webext-core/fake-browser": "^1.3.2",
"@webext-core/isolated-element": "^1.1.2",
"@webext-core/match-patterns": "^1.0.3",
"@wxt-dev/browser": "workspace:^",
"@wxt-dev/storage": "workspace:^1.0.0",
"async-mutex": "catalog:",
"c12": "catalog:",
"cac": "catalog:",
"chokidar": "catalog:",
"ci-info": "catalog:",
"consola": "catalog:",
"defu": "catalog:",
"dotenv": "catalog:",
"dotenv-expand": "catalog:",
"esbuild": "catalog:",
"fast-glob": "catalog:",
"filesize": "catalog:",
"fs-extra": "catalog:",
"get-port-please": "catalog:",
"giget": "catalog:",
"hookable": "catalog:",
"import-meta-resolve": "catalog:",
"is-wsl": "catalog:",
"json5": "catalog:",
"jszip": "catalog:",
"linkedom": "catalog:",
"magicast": "catalog:",
"minimatch": "catalog:",
"nano-spawn": "catalog:",
"normalize-path": "catalog:",
"nypm": "catalog:",
"ohash": "catalog:",
"open": "catalog:",
"ora": "catalog:",
"perfect-debounce": "catalog:",
"picocolors": "catalog:",
"prompts": "catalog:",
"publish-browser-extension": "catalog:",
"scule": "catalog:",
"unimport": "catalog:",
"vite": "catalog:",
"vite-node": "catalog:",
"web-ext-run": "catalog:"
"async-mutex": "^0.5.0",
"c12": "^3.0.3",
"cac": "^6.7.14",
"chokidar": "^4.0.3",
"ci-info": "^4.2.0",
"consola": "^3.4.2",
"defu": "^6.1.4",
"dotenv": "^16.5.0",
"dotenv-expand": "^12.0.2",
"esbuild": "^0.25.0",
"fast-glob": "^3.3.3",
"filesize": "^10.1.6",
"fs-extra": "^11.3.0",
"get-port-please": "^3.1.2",
"giget": "^1.2.3 || ^2.0.0",
"hookable": "^5.5.3",
"import-meta-resolve": "^4.1.0",
"is-wsl": "^3.1.0",
"json5": "^2.2.3",
"jszip": "^3.10.1",
"linkedom": "^0.18.10",
"magicast": "^0.3.5",
"minimatch": "^10.0.1",
"nano-spawn": "^0.2.0",
"normalize-path": "^3.0.0",
"nypm": "^0.6.0",
"ohash": "^2.0.11",
"open": "^10.1.2",
"ora": "^8.2.0",
"perfect-debounce": "^1.0.0",
"picocolors": "^1.1.1",
"prompts": "^2.4.2",
"publish-browser-extension": "^2.3.0 || ^3.0.0",
"scule": "^1.3.0",
"unimport": "^3.13.1 || ^4.0.0 || ^5.0.0",
"vite": "^5.4.19 || ^6.3.4",
"vite-node": "^2.1.4 || ^3.1.2",
"web-ext-run": "^0.2.3"
},
"devDependencies": {
"@aklinker1/check": "catalog:",
"@faker-js/faker": "catalog:",
"@types/fs-extra": "catalog:",
"@types/lodash.merge": "catalog:",
"@types/node": "catalog:",
"@types/normalize-path": "catalog:",
"@types/prompts": "catalog:",
"extract-zip": "catalog:",
"happy-dom": "catalog:",
"lodash.merge": "catalog:",
"oxlint": "catalog:",
"publint": "catalog:",
"typescript": "catalog:",
"unbuild": "catalog:",
"vitest": "catalog:",
"vitest-plugin-random-seed": "catalog:"
"@aklinker1/check": "2.0.0",
"@faker-js/faker": "^9.7.0",
"@types/fs-extra": "^11.0.4",
"@types/lodash.merge": "^4.6.9",
"@types/node": "^20.17.6",
"@types/normalize-path": "^3.0.2",
"@types/prompts": "^2.4.9",
"extract-zip": "^2.0.1",
"happy-dom": "^17.4.6",
"lodash.merge": "^4.6.2",
"oxlint": "^0.16.8",
"publint": "^0.3.12",
"typescript": "^5.8.3",
"unbuild": "^3.5.0",
"vitest": "^3.1.2",
"vitest-plugin-random-seed": "^1.1.1"
},
"peerDependenciesMeta": {},
"repository": {
@@ -167,6 +167,10 @@
"types": "./dist/utils/match-patterns.d.ts",
"default": "./dist/utils/match-patterns.mjs"
},
"./utils/split-shadow-root-css": {
"types": "./dist/utils/split-shadow-root-css.d.ts",
"default": "./dist/utils/split-shadow-root-css.mjs"
},
"./utils/storage": {
"types": "./dist/utils/storage.d.ts",
"default": "./dist/utils/storage.mjs"
@@ -85,13 +85,13 @@ export function devHtmlPrerender(
inlineScripts.forEach((script) => {
// Save the text content for later
const textContent = script.textContent ?? '';
const textHash = hash(textContent);
inlineScriptContents[textHash] = textContent;
const key = hash(textContent);
inlineScriptContents[key] = textContent;
// Replace unsafe inline script
const virtualScript = document.createElement('script');
virtualScript.type = 'module';
virtualScript.src = `${server.origin}/@id/${virtualInlineScript}?${textHash}`;
virtualScript.src = `${server.origin}/@id/${virtualInlineScript}?${key}`;
script.replaceWith(virtualScript);
});
@@ -111,7 +111,7 @@ export function devHtmlPrerender(
},
},
{
name: 'wxt:virtualize-react-refresh',
name: 'wxt:virtualize-inline-scripts',
apply: 'serve',
resolveId(id) {
// Resolve inline scripts
@@ -127,9 +127,9 @@ export function devHtmlPrerender(
load(id) {
// Resolve virtualized inline scripts
if (id.startsWith(resolvedVirtualInlineScript)) {
// id="virtual:wxt-inline-script?<hash>"
const newHash = hash(id.substring(id.indexOf('?')));
return inlineScriptContents[newHash];
// id="virtual:wxt-inline-script?<key>"
const key = id.substring(id.indexOf('?') + 1);
return inlineScriptContents[key];
}
// Ignore chunks during HTML file pre-rendering
@@ -150,6 +150,13 @@ export function pointToDevServer(
attr: string,
) {
document.querySelectorAll(querySelector).forEach((element) => {
if (
element.hasAttribute('vite-ignore') ||
element.hasAttribute('wxt-ignore')
) {
element.removeAttribute('wxt-ignore');
return;
}
const src = element.getAttribute(attr);
if (!src || isUrl(src)) return;
+8 -6
View File
@@ -73,16 +73,18 @@ async function getPathsDeclarationEntry(
isHtmlEntrypoint(entry) ? '.html' : '.js',
),
)
.concat([''])
.concat(await getPublicFiles());
await wxt.hooks.callHook('prepare:publicPaths', wxt, paths);
const unions = paths
.map(normalizePath)
.sort()
.map((path) => ` | "/${path}"`)
.join('\n');
const unions = [
` | ""`,
` | "/"`,
...paths
.map(normalizePath)
.sort()
.map((path) => ` | "/${path}"`),
].join('\n');
const template = `// Generated by wxt
import "wxt/browser";
@@ -1503,7 +1503,7 @@ describe('Manifest Utils', () => {
expect(actual).toMatchObject({
content_security_policy:
"script-src 'self' http://localhost:3000; object-src 'self';",
permissions: ['http://localhost:3000/*', 'tabs'],
permissions: ['http://localhost/*', 'tabs'],
});
});
@@ -1535,7 +1535,7 @@ describe('Manifest Utils', () => {
sandbox:
"script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:3000; sandbox allow-scripts allow-forms allow-popups allow-modals; child-src 'self';",
},
host_permissions: ['http://localhost:3000/*'],
host_permissions: ['http://localhost/*'],
permissions: ['tabs', 'scripting'],
});
});
@@ -668,27 +668,46 @@ describe('findEntrypoints', () => {
expect(entrypoints[0]).toEqual(expected);
});
it('should not allow multiple entrypoints with the same name', async () => {
it('should ignore other index files in the same directory when index.html exists', async () => {
globMock.mockResolvedValueOnce([
'content/index.ts',
'popup/index.html',
'popup/index.ts',
'popup/index.css',
]);
const entrypoints = await findEntrypoints();
expect(entrypoints).toHaveLength(2);
expect(entrypoints[0]).toMatchObject({
type: 'content-script',
name: 'content',
});
expect(entrypoints[1]).toMatchObject({
type: 'popup',
name: 'popup',
});
});
it('should not allow a file entrypoint and directory entrypoint to have the same name', async () => {
globMock.mockResolvedValueOnce([
'options/index.html',
'options/index.jsx',
'popup.html',
'popup/index.html',
'popup/index.ts',
'ui.html',
'other.ts',
'other/index.ts',
]);
await expect(() => findEntrypoints()).rejects.toThrowError(
[
'Multiple entrypoints with the same name detected, only one entrypoint for each name is allowed.',
'',
'- options',
` - ${unnormalizePath('src/entrypoints/options/index.html')}`,
` - ${unnormalizePath('src/entrypoints/options/index.jsx')}`,
'- other',
` - ${unnormalizePath('src/entrypoints/other.ts')}`,
` - ${unnormalizePath('src/entrypoints/other/index.ts')}`,
'- popup',
` - ${unnormalizePath('src/entrypoints/popup.html')}`,
` - ${unnormalizePath('src/entrypoints/popup/index.html')}`,
` - ${unnormalizePath('src/entrypoints/popup/index.ts')}`,
].join('\n'),
);
});
@@ -53,20 +53,34 @@ export async function findEntrypoints(): Promise<Entrypoint[]> {
relativePaths.sort();
const pathGlobs = Object.keys(PATH_GLOB_TO_TYPE_MAP);
const entrypointInfos: EntrypointInfo[] = relativePaths.reduce<
EntrypointInfo[]
>((results, relativePath) => {
const inputPath = resolve(wxt.config.entrypointsDir, relativePath);
const name = getEntrypointName(wxt.config.entrypointsDir, inputPath);
const matchingGlob = pathGlobs.find((glob) =>
minimatch(relativePath, glob),
);
if (matchingGlob) {
const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
results.push({ name, inputPath, type });
}
return results;
}, []);
const entrypointInfos: EntrypointInfo[] = relativePaths
.reduce<EntrypointInfo[]>((results, relativePath) => {
const inputPath = resolve(wxt.config.entrypointsDir, relativePath);
const name = getEntrypointName(wxt.config.entrypointsDir, inputPath);
const matchingGlob = pathGlobs.find((glob) =>
minimatch(relativePath, glob),
);
if (matchingGlob) {
const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob];
results.push({ name, inputPath, type });
}
return results;
}, [])
.filter(({ name, inputPath }, _, entrypointInfos) => {
// Remove <name>/index.* if <name>/index.html exists
if (inputPath.endsWith('.html')) return true;
const isIndexFile = /index\..+$/.test(inputPath);
if (!isIndexFile) return true;
const hasIndexHtml = entrypointInfos.some(
(entry) =>
entry.name === name && entry.inputPath.endsWith('index.html'),
);
if (hasIndexHtml) return false;
return true;
});
await wxt.hooks.callHook('entrypoints:found', wxt, entrypointInfos);
+7 -1
View File
@@ -459,7 +459,13 @@ function discoverIcons(
}
function addDevModeCsp(manifest: Browser.runtime.Manifest): void {
const permission = `${wxt.server?.origin ?? ''}/*`;
let permissonUrl = wxt.server?.origin;
if (permissonUrl) {
const permissionUrlInstance = new URL(permissonUrl);
permissionUrlInstance.port = '';
permissonUrl = permissionUrlInstance.toString();
}
const permission = `${permissonUrl}*`;
const allowedCsp = wxt.server?.origin ?? 'http://localhost:*';
if (manifest.manifest_version === 3) {
+3 -3
View File
@@ -26,11 +26,10 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
wxt.logger.info('Zipping extension...');
const zipFiles: string[] = [];
const packageJson = await getPackageJson();
const projectName =
wxt.config.zip.name ??
safeFilename(
(await getPackageJson())?.name || path.basename(process.cwd()),
);
safeFilename(packageJson?.name || path.basename(process.cwd()));
const applyTemplate = (template: string): string =>
template
.replaceAll('{{name}}', projectName)
@@ -39,6 +38,7 @@ export async function zip(config?: InlineConfig): Promise<string[]> {
'{{version}}',
output.manifest.version_name ?? output.manifest.version,
)
.replaceAll('{{packageVersion}}', packageJson?.version)
.replaceAll('{{mode}}', wxt.config.mode)
.replaceAll('{{manifestVersion}}', `mv${wxt.config.manifestVersion}`);
+2
View File
@@ -147,6 +147,7 @@ export interface InlineConfig {
*
* - <span v-pre>`{{name}}`</span> - The project's name converted to kebab-case
* - <span v-pre>`{{version}}`</span> - The version_name or version from the manifest
* - <span v-pre>`{{packageVersion}}`</span> - The version from the package.json
* - <span v-pre>`{{browser}}`</span> - The target browser from the `--browser` CLI flag
* - <span v-pre>`{{mode}}`</span> - The current mode
* - <span v-pre>`{{manifestVersion}}`</span> - Either "2" or "3"
@@ -171,6 +172,7 @@ export interface InlineConfig {
*
* - <span v-pre>`{{name}}`</span> - The project's name converted to kebab-case
* - <span v-pre>`{{version}}`</span> - The version_name or version from the manifest
* - <span v-pre>`{{packageVersion}}`</span> - The version from the package.json
* - <span v-pre>`{{browser}}`</span> - The target browser from the `--browser` CLI flag
* - <span v-pre>`{{mode}}`</span> - The current mode
* - <span v-pre>`{{manifestVersion}}`</span> - Either "2" or "3"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@ import type { ContentScriptUi, ContentScriptUiOptions } from './types';
import { createIsolatedElement } from '@webext-core/isolated-element';
import { applyPosition, createMountFunctions, mountUi } from './shared';
import { logger } from '../internal/logger';
import { splitShadowRootCss } from '../split-shadow-root-css';
/**
* Create a content script UI inside a [`ShadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot).
@@ -17,10 +18,11 @@ export async function createShadowRootUi<TMounted>(
ctx: ContentScriptContext,
options: ShadowRootContentScriptUiOptions<TMounted>,
): Promise<ShadowRootContentScriptUi<TMounted>> {
const instanceId = Math.random().toString(36).substring(2, 15);
const css: string[] = [];
if (!options.inheritStyles) {
css.push(`/* WXT Shadow Root Reset */ body{all:initial;}`);
css.push(`/* WXT Shadow Root Reset */ :host{all:initial !important;}`);
}
if (options.css) {
css.push(options.css);
@@ -31,6 +33,9 @@ export async function createShadowRootUi<TMounted>(
css.push(entryCss.replaceAll(':root', ':host'));
}
// Some rules must be applied outside the shadow root, so split the CSS apart
const { shadowCss, documentCss } = splitShadowRootCss(css.join('\n').trim());
const {
isolatedElement: uiContainer,
parentElement: shadowHost,
@@ -38,7 +43,7 @@ export async function createShadowRootUi<TMounted>(
} = await createIsolatedElement({
name: options.name,
css: {
textContent: css.join('\n').trim(),
textContent: shadowCss,
},
mode: options.mode ?? 'open',
isolateEvents: options.isolateEvents,
@@ -51,6 +56,20 @@ export async function createShadowRootUi<TMounted>(
// Add shadow root element to DOM
mountUi(shadowHost, options);
applyPosition(shadowHost, shadow.querySelector('html'), options);
// Add document CSS
if (
documentCss &&
!document.querySelector(
`style[wxt-shadow-root-document-styles="${instanceId}"]`,
)
) {
const style = document.createElement('style');
style.textContent = documentCss;
style.setAttribute('wxt-shadow-root-document-styles', instanceId);
(document.head ?? document.body).append(style);
}
// Mount UI inside shadow root
mounted = options.onMount(uiContainer, shadow, shadowHost);
};
@@ -58,11 +77,20 @@ export async function createShadowRootUi<TMounted>(
const remove = () => {
// Cleanup mounted state
options.onRemove?.(mounted);
// Detach shadow root from DOM
shadowHost.remove();
// Remove document CSS
const documentStyle = document.querySelector(
`style[wxt-shadow-root-document-styles="${instanceId}"]`,
);
documentStyle?.remove();
// Remove children from uiContainer
while (uiContainer.lastChild)
uiContainer.removeChild(uiContainer.lastChild);
// Clear mounted value
mounted = undefined;
};
@@ -9,6 +9,9 @@ export type ContentScriptUiOptions<TMounted> = ContentScriptPositioningOptions &
/**
* Callback called before the UI is removed from the webpage. Use to cleanup your UI, like
* unmounting your Vue or React apps.
*
* Note that this callback is called only when `ui.remove` is called - that means it is
* not called automatically when the anchor is removed, unless you use `autoMount`.
*/
onRemove?: (mounted: TMounted | undefined) => void;
};
@@ -0,0 +1,25 @@
/**
* Given a CSS string that will be loaded into a shadow root, split it into two parts:
* - `documentCss`: CSS that needs to be applied to the document (like `@property`)
* - `shadowCss`: CSS that needs to be applied to the shadow root
* @param css
*/
export function splitShadowRootCss(css: string): {
documentCss: string;
shadowCss: string;
} {
let shadowCss = css;
let documentCss = '';
const rulesRegex = /(\s*@(property|font-face)[\s\S]*?{[\s\S]*?})/gm;
let match;
while ((match = rulesRegex.exec(css)) !== null) {
documentCss += match[1];
shadowCss = shadowCss.replace(match[1], '');
}
return {
documentCss: documentCss.trim(),
shadowCss: shadowCss.trim(),
};
}
+1
View File
@@ -14,6 +14,7 @@
"src/utils/define-unlisted-script.ts",
"src/utils/define-wxt-plugin.ts",
"src/utils/match-patterns.ts",
"src/utils/split-shadow-root-css.ts",
"src/utils/storage.ts",
"src/testing/index.ts",
"src/modules.ts"
+917 -1463
View File
File diff suppressed because it is too large Load Diff
-99
View File
@@ -8,102 +8,3 @@ onlyBuiltDependencies:
- sharp
- simple-git-hooks
- spawn-sync
catalog:
'@1natsu/wait-element': ^4.1.2
'@aklinker1/buildc': ^1.1.4
'@aklinker1/check': ^1.4.5
'@aklinker1/rollup-plugin-visualizer': 5.12.0
'@commitlint/config-conventional': ^19.8.0
'@commitlint/types': ^19.8.0
'@faker-js/faker': ^9.6.0
'@sveltejs/vite-plugin-svelte': ^4.0.0 || ^5.0.0
'@types/chrome': ^0.0.313
'@types/fs-extra': ^11.0.4
'@types/lodash.merge': ^4.6.9
'@types/node': ^20.17.6
'@types/normalize-path': ^3.0.2
'@types/prompts': ^2.4.9
'@types/react': ^19.1.0
'@types/react-dom': ^19.1.2
'@types/ua-parser-js': ^0.7.39
'@types/webextension-polyfill': ^0.12.3
'@vitejs/plugin-react': ^4.3.4
'@vitejs/plugin-vue': ^5.2.3
'@vitest/coverage-v8': ^3.1.1
'@webext-core/fake-browser': ^1.3.2
'@webext-core/isolated-element': ^1.1.2
'@webext-core/match-patterns': ^1.0.3
async-mutex: ^0.5.0
c12: ^3.0.3
cac: ^6.7.14
changelogen: ^0.6.1
chokidar: ^4.0.3
ci-info: ^4.2.0
confbox: ^0.1.8 || ^0.2.2
consola: ^3.4.2
defu: ^6.1.4
dequal: ^2.0.3
dotenv: ^16.4.7
dotenv-expand: ^12.0.1
esbuild: ^0.25.0
extract-zip: ^2.0.1
fast-glob: ^3.3.3
feed: ^4.2.2
filesize: ^10.1.6
fs-extra: ^11.3.0
get-port-please: ^3.1.2
giget: ^1.2.3 || ^2.0.0
happy-dom: ^17.4.4
hookable: ^5.5.3
import-meta-resolve: ^4.1.0
is-wsl: ^3.1.0
json5: ^2.2.3
jszip: ^3.10.1
linkedom: ^0.18.9
lint-staged: ^15.5.0
lodash.merge: ^4.6.2
magicast: ^0.3.5
markdown-it-footnote: ^4.0.0
minimatch: ^10.0.1
nano-spawn: ^0.2.0
normalize-path: ^3.0.0
nypm: ^0.6.0
ohash: ^2.0.11
open: ^10.1.0
ora: ^8.2.0
oxlint: ^0.16.5
perfect-debounce: ^1.0.0
picocolors: ^1.1.1
prettier: ^3.5.3
prompts: ^2.4.2
publint: ^0.3.10
publish-browser-extension: ^2.3.0 || ^3.0.0
react: ^19.1.0
react-dom: ^19.1.0
sass: ^1.86.3
scule: ^1.3.0
sharp: ^0.34.1
simple-git-hooks: ^2.12.1
solid-js: ^1.9.5
tsx: 4.19.3
typedoc: ^0.25.4
typedoc-plugin-frontmatter: ^1.3.0
typedoc-plugin-markdown: 4.0.0-next.23
typedoc-vitepress-theme: 1.0.0-next.3
typescript: ^5.8.3
ua-parser-js: ^1.0.40
unbuild: ^3.5.0
unimport: ^3.13.1 || ^4.0.0 || ^5.0.0
unocss: ^0.64.0 || ^0.65.0 || ^65.0.0 || ^66.0.0
vite: ^5.4.17 || ^6.2.5
vite-node: ^2.1.4 || ^3.0.0
vite-plugin-solid: ^2.11.6
vitepress: ^1.6.3
vitepress-knowledge: ^0.4.1
vitepress-plugin-group-icons: ^1.4.1
vitest: ^3.1.1
vitest-mock-extended: ^3.1.0
vitest-plugin-random-seed: ^1.1.1
vue: ^3.5.13
web-ext-run: ^0.2.2
webextension-polyfill: ^0.12.0
+3 -3
View File
@@ -19,10 +19,10 @@
"react-dom": "^19.1.0"
},
"devDependencies": {
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.2",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3",
"@wxt-dev/module-react": "^1.1.3",
"typescript": "^5.8.3",
"wxt": "^0.20.0"
"wxt": "^0.20.6"
}
}
+2 -2
View File
@@ -15,11 +15,11 @@
"postinstall": "wxt prepare"
},
"dependencies": {
"solid-js": "^1.9.5"
"solid-js": "^1.9.6"
},
"devDependencies": {
"@wxt-dev/module-solid": "^1.1.3",
"typescript": "^5.8.3",
"wxt": "^0.20.0"
"wxt": "^0.20.6"
}
}
+3 -3
View File
@@ -17,10 +17,10 @@
"devDependencies": {
"@tsconfig/svelte": "^5.0.4",
"@wxt-dev/module-svelte": "^2.0.3",
"svelte": "^5.25.9",
"svelte-check": "^4.1.5",
"svelte": "^5.28.2",
"svelte-check": "^4.1.6",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"wxt": "^0.20.0"
"wxt": "^0.20.6"
}
}

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Before

Width:  |  Height:  |  Size: 559 B

After

Width:  |  Height:  |  Size: 559 B

Before

Width:  |  Height:  |  Size: 916 B

After

Width:  |  Height:  |  Size: 916 B

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

+1 -1
View File
@@ -16,6 +16,6 @@
},
"devDependencies": {
"typescript": "^5.8.3",
"wxt": "^0.20.0"
"wxt": "^0.20.6"
}
}
+2 -2
View File
@@ -20,7 +20,7 @@
"devDependencies": {
"@wxt-dev/module-vue": "^1.0.2",
"typescript": "5.6.3",
"vue-tsc": "^2.2.8",
"wxt": "^0.20.0"
"vue-tsc": "^2.2.10",
"wxt": "^0.20.6"
}
}