From bf148dfd4e8470742143d29200996efcda3439a2 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Wed, 24 Dec 2025 15:10:10 +0100
Subject: [PATCH 01/64] fix: add types instead of `any` and improve readability
of analytics package
---
.../analytics/modules/analytics/client.ts | 108 +++++++++++-------
packages/analytics/modules/analytics/index.ts | 7 +-
.../analytics/providers/google-analytics-4.ts | 3 +-
.../modules/analytics/providers/umami.ts | 1 +
4 files changed, 71 insertions(+), 48 deletions(-)
diff --git a/packages/analytics/modules/analytics/client.ts b/packages/analytics/modules/analytics/client.ts
index 85bac1097..2cae7a06b 100644
--- a/packages/analytics/modules/analytics/client.ts
+++ b/packages/analytics/modules/analytics/client.ts
@@ -2,30 +2,62 @@ import { UAParser } from 'ua-parser-js';
import type {
Analytics,
AnalyticsConfig,
+ AnalyticsEventMetadata,
AnalyticsPageViewEvent,
+ AnalyticsProvider,
AnalyticsStorageItem,
AnalyticsTrackEvent,
BaseAnalyticsEvent,
- AnalyticsEventMetadata,
- AnalyticsProvider,
} from './types';
import { browser } from '@wxt-dev/browser';
const ANALYTICS_PORT = '@wxt-dev/analytics';
+type TAnalyticsMessage = {
+ [K in keyof Analytics]: {
+ fn: K;
+ args: Parameters;
+ };
+}[keyof Analytics];
+
+type TAnalyticsMethod =
+ | ((...args: Parameters) => void)
+ | undefined;
+
+type TMethodForwarder = (
+ fn: K,
+) => (...args: Parameters) => void;
+const INTERACTIVE_TAGS = new Set([
+ 'A',
+ 'BUTTON',
+ 'INPUT',
+ 'SELECT',
+ 'TEXTAREA',
+]);
+const INTERACTIVE_ROLES = new Set([
+ 'button',
+ 'link',
+ 'checkbox',
+ 'menuitem',
+ 'tab',
+ 'radio',
+]);
+
+// This is injected by the build process, it only seems unused
export function createAnalytics(config?: AnalyticsConfig): Analytics {
if (!browser?.runtime?.id)
throw Error(
'Cannot use WXT analytics in contexts without access to the browser.runtime APIs',
);
- if (config == null) {
+
+ if (config === null) {
console.warn(
"[@wxt-dev/analytics] Config not provided to createAnalytics. If you're using WXT, add the 'analytics' property to '/app.config.ts'.",
);
}
// TODO: This only works for standard WXT extensions, add a more generic
- // background script detector that works with non-WXT projects.
+ // Background script detector that works with non-WXT projects.
if (location.pathname === '/background.js')
return createBackgroundAnalytics(config);
@@ -41,12 +73,14 @@ function createBackgroundAnalytics(
// User properties storage
const userIdStorage =
config?.userId ?? defineStorageItem('wxt-analytics:user-id');
+
const userPropertiesStorage =
config?.userProperties ??
defineStorageItem>(
'wxt-analytics:user-properties',
{},
);
+
const enabled =
config?.enabled ??
defineStorageItem('local:wxt-analytics:enabled', false);
@@ -54,6 +88,7 @@ function createBackgroundAnalytics(
// Cached values
const platformInfo = browser.runtime.getPlatformInfo();
const userAgent = UAParser();
+
let userId = Promise.resolve(userIdStorage.getValue()).then(
(id) => id ?? globalThis.crypto.randomUUID(),
);
@@ -75,7 +110,7 @@ function createBackgroundAnalytics(
const getBaseEvent = async (
meta: AnalyticsEventMetadata,
): Promise => {
- const platform = await platformInfo;
+ const { arch, os } = await platformInfo;
return {
meta,
user: {
@@ -84,8 +119,8 @@ function createBackgroundAnalytics(
version: config?.version ?? manifest.version_name ?? manifest.version,
wxtMode: import.meta.env.MODE,
wxtBrowser: import.meta.env.BROWSER,
- arch: platform.arch,
- os: platform.os,
+ arch,
+ os,
browser: userAgent.browser.name,
browserVersion: userAgent.browser.version,
...(await userProperties),
@@ -110,7 +145,9 @@ function createBackgroundAnalytics(
]);
// Notify providers
const event = await getBaseEvent(meta);
+
if (config?.debug) console.debug('[@wxt-dev/analytics] identify', event);
+
if (await enabled.getValue()) {
await Promise.allSettled(
providers.map((provider) => provider.identify(event)),
@@ -134,7 +171,9 @@ function createBackgroundAnalytics(
title: meta?.title ?? globalThis.document?.title,
},
};
+
if (config?.debug) console.debug('[@wxt-dev/analytics] page', event);
+
if (await enabled.getValue()) {
await Promise.allSettled(
providers.map((provider) => provider.page(event)),
@@ -155,7 +194,9 @@ function createBackgroundAnalytics(
...baseEvent,
event: { name: eventName, properties: eventProperties },
};
+
if (config?.debug) console.debug('[@wxt-dev/analytics] track', event);
+
if (await enabled.getValue()) {
await Promise.allSettled(
providers.map((provider) => provider.track(event)),
@@ -181,9 +222,8 @@ function createBackgroundAnalytics(
// Listen for messages from the rest of the extension
browser.runtime.onConnect.addListener((port) => {
if (port.name === ANALYTICS_PORT) {
- port.onMessage.addListener(({ fn, args }) => {
- // @ts-expect-error: Untyped fn key
- void analytics[fn]?.(...args);
+ port.onMessage.addListener(({ fn, args }: TAnalyticsMessage) => {
+ void (analytics[fn] as TAnalyticsMethod)?.(...args);
});
}
});
@@ -201,17 +241,15 @@ function createFrontendAnalytics(): Analytics {
sessionId,
timestamp: Date.now(),
language: navigator.language,
- referrer: globalThis.document?.referrer || undefined,
- screen: globalThis.window
- ? `${globalThis.window.screen.width}x${globalThis.window.screen.height}`
- : undefined,
+ referrer: globalThis.document?.referrer,
+ screen: `${globalThis.window.screen.width}x${globalThis.window.screen.height}`,
url: location.href,
- title: document.title || undefined,
+ title: document.title,
});
- const methodForwarder =
- (fn: string) =>
- (...args: any[]) => {
+ const methodForwarder: TMethodForwarder =
+ (fn) =>
+ (...args) => {
port.postMessage({ fn, args: [...args, getFrontendMetadata()] });
};
@@ -222,20 +260,20 @@ function createFrontendAnalytics(): Analytics {
setEnabled: methodForwarder('setEnabled'),
autoTrack: (root) => {
const onClick = (event: Event) => {
- const element = event.target as any;
+ const element = event.target as HTMLElement | null;
if (
!element ||
(!INTERACTIVE_TAGS.has(element.tagName) &&
- !INTERACTIVE_ROLES.has(element.getAttribute('role')))
+ !INTERACTIVE_ROLES.has(element.getAttribute('role') ?? ''))
)
return;
void analytics.track('click', {
tagName: element.tagName?.toLowerCase(),
- id: element.id || undefined,
- className: element.className || undefined,
- textContent: element.textContent?.substring(0, 50) || undefined, // Limit text content length
- href: element.href,
+ id: element.id,
+ className: element.className,
+ textContent: element.textContent?.substring(0, 50), // Limit text content length
+ href: (element as HTMLAnchorElement).href,
});
};
root.addEventListener('click', onClick, { capture: true, passive: true });
@@ -249,32 +287,16 @@ function createFrontendAnalytics(): Analytics {
function defineStorageItem(
key: string,
- defaultValue?: NonNullable,
+ defaultValue?: T,
): AnalyticsStorageItem {
return {
getValue: async () =>
- (await browser.storage.local.get>(key))[key] ??
- defaultValue,
+ (((await browser.storage.local.get(key)) as Record)[key] ??
+ defaultValue) as T,
setValue: (newValue) => browser.storage.local.set({ [key]: newValue }),
};
}
-const INTERACTIVE_TAGS = new Set([
- 'A',
- 'BUTTON',
- 'INPUT',
- 'SELECT',
- 'TEXTAREA',
-]);
-const INTERACTIVE_ROLES = new Set([
- 'button',
- 'link',
- 'checkbox',
- 'menuitem',
- 'tab',
- 'radio',
-]);
-
export function defineAnalyticsProvider(
definition: (
/** The analytics object. */
diff --git a/packages/analytics/modules/analytics/index.ts b/packages/analytics/modules/analytics/index.ts
index f5ef4b00c..1e9d04ef4 100644
--- a/packages/analytics/modules/analytics/index.ts
+++ b/packages/analytics/modules/analytics/index.ts
@@ -25,6 +25,7 @@ export default defineWxtModule({
const clientModuleId = process.env.NPM
? '@wxt-dev/analytics'
: resolve(wxt.config.modulesDir, 'analytics/client');
+
const pluginModuleId = process.env.NPM
? '@wxt-dev/analytics/background-plugin'
: resolve(wxt.config.modulesDir, 'analytics/background-plugin');
@@ -44,10 +45,8 @@ export default defineWxtModule({
? clientModuleId
: relative(wxtAnalyticsFolder, clientModuleId)
}';`,
- `import { useAppConfig } from '#imports';`,
- ``,
- `export const analytics = createAnalytics(useAppConfig().analytics);`,
- ``,
+ `import { useAppConfig } from '#imports';\n`,
+ `export const analytics = createAnalytics(useAppConfig().analytics);\n`,
].join('\n');
addAlias(wxt, '#analytics', wxtAnalyticsIndex);
wxt.hook('prepare:types', async (_, entries) => {
diff --git a/packages/analytics/modules/analytics/providers/google-analytics-4.ts b/packages/analytics/modules/analytics/providers/google-analytics-4.ts
index 278be6794..00055cb6f 100644
--- a/packages/analytics/modules/analytics/providers/google-analytics-4.ts
+++ b/packages/analytics/modules/analytics/providers/google-analytics-4.ts
@@ -15,11 +15,12 @@ export const googleAnalytics4 =
data: BaseAnalyticsEvent,
eventName: string,
eventProperties: Record | undefined,
- ): Promise => {
+ ) => {
const url = new URL(
config?.debug ? '/debug/mp/collect' : '/mp/collect',
'https://www.google-analytics.com',
);
+
if (options.apiSecret)
url.searchParams.set('api_secret', options.apiSecret);
if (options.measurementId)
diff --git a/packages/analytics/modules/analytics/providers/umami.ts b/packages/analytics/modules/analytics/providers/umami.ts
index 99a4e4914..86b7e3f09 100644
--- a/packages/analytics/modules/analytics/providers/umami.ts
+++ b/packages/analytics/modules/analytics/providers/umami.ts
@@ -12,6 +12,7 @@ export const umami = defineAnalyticsProvider(
if (config.debug) {
console.debug('[@wxt-dev/analytics] Sending event to Umami:', payload);
}
+
return fetch(`${options.apiUrl}/send`, {
method: 'POST',
headers: {
From f7223d56811e5d28193034e0b91efc3ca5e259bf Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Wed, 24 Dec 2025 21:39:25 +0100
Subject: [PATCH 02/64] clean(analysis): make code more readable and fix null
check for mappedUserProperties
---
packages/analytics/modules/analytics/client.ts | 1 -
packages/analytics/modules/analytics/index.ts | 1 +
.../modules/analytics/providers/google-analytics-4.ts | 3 ++-
3 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/packages/analytics/modules/analytics/client.ts b/packages/analytics/modules/analytics/client.ts
index 2cae7a06b..99bd9cc2d 100644
--- a/packages/analytics/modules/analytics/client.ts
+++ b/packages/analytics/modules/analytics/client.ts
@@ -43,7 +43,6 @@ const INTERACTIVE_ROLES = new Set([
'radio',
]);
-// This is injected by the build process, it only seems unused
export function createAnalytics(config?: AnalyticsConfig): Analytics {
if (!browser?.runtime?.id)
throw Error(
diff --git a/packages/analytics/modules/analytics/index.ts b/packages/analytics/modules/analytics/index.ts
index 1e9d04ef4..3ebcf1e81 100644
--- a/packages/analytics/modules/analytics/index.ts
+++ b/packages/analytics/modules/analytics/index.ts
@@ -61,6 +61,7 @@ export default defineWxtModule({
const hasBackground = entrypoints.find(
(entry) => entry.type === 'background',
);
+
if (!hasBackground) {
entrypoints.push({
type: 'background',
diff --git a/packages/analytics/modules/analytics/providers/google-analytics-4.ts b/packages/analytics/modules/analytics/providers/google-analytics-4.ts
index 00055cb6f..76e6c0895 100644
--- a/packages/analytics/modules/analytics/providers/google-analytics-4.ts
+++ b/packages/analytics/modules/analytics/providers/google-analytics-4.ts
@@ -23,6 +23,7 @@ export const googleAnalytics4 =
if (options.apiSecret)
url.searchParams.set('api_secret', options.apiSecret);
+
if (options.measurementId)
url.searchParams.set('measurement_id', options.measurementId);
@@ -34,7 +35,7 @@ export const googleAnalytics4 =
const mappedUserProperties = Object.fromEntries(
Object.entries(userProperties).map(([name, value]) => [
name,
- value == null ? undefined : { value },
+ value === null ? undefined : { value },
]),
);
From 2a0492ead48b5b48bbb013e87681f97128a8fc4a Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Wed, 24 Dec 2025 21:42:15 +0100
Subject: [PATCH 03/64] clean(analysis): move new types for client.ts to
types.ts
---
packages/analytics/modules/analytics/client.ts | 17 +++--------------
packages/analytics/modules/analytics/types.ts | 15 +++++++++++++++
2 files changed, 18 insertions(+), 14 deletions(-)
diff --git a/packages/analytics/modules/analytics/client.ts b/packages/analytics/modules/analytics/client.ts
index 99bd9cc2d..46fa4c09b 100644
--- a/packages/analytics/modules/analytics/client.ts
+++ b/packages/analytics/modules/analytics/client.ts
@@ -8,24 +8,13 @@ import type {
AnalyticsStorageItem,
AnalyticsTrackEvent,
BaseAnalyticsEvent,
+ TAnalyticsMessage,
+ TAnalyticsMethod,
+ TMethodForwarder,
} from './types';
import { browser } from '@wxt-dev/browser';
const ANALYTICS_PORT = '@wxt-dev/analytics';
-type TAnalyticsMessage = {
- [K in keyof Analytics]: {
- fn: K;
- args: Parameters;
- };
-}[keyof Analytics];
-
-type TAnalyticsMethod =
- | ((...args: Parameters) => void)
- | undefined;
-
-type TMethodForwarder = (
- fn: K,
-) => (...args: Parameters) => void;
const INTERACTIVE_TAGS = new Set([
'A',
diff --git a/packages/analytics/modules/analytics/types.ts b/packages/analytics/modules/analytics/types.ts
index d14d59f81..c6f0c7a22 100644
--- a/packages/analytics/modules/analytics/types.ts
+++ b/packages/analytics/modules/analytics/types.ts
@@ -97,3 +97,18 @@ export interface AnalyticsTrackEvent extends BaseAnalyticsEvent {
properties?: Record;
};
}
+
+export type TAnalyticsMessage = {
+ [K in keyof Analytics]: {
+ fn: K;
+ args: Parameters;
+ };
+}[keyof Analytics];
+
+export type TAnalyticsMethod =
+ | ((...args: Parameters) => void)
+ | undefined;
+
+export type TMethodForwarder = (
+ fn: K,
+) => (...args: Parameters) => void;
From e159734614e8d9e0dd40dc9d25677a90141dcd7f Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Wed, 24 Dec 2025 21:43:03 +0100
Subject: [PATCH 04/64] clean(analysis): split comment of getBackgroundMeta a
little better
---
packages/analytics/modules/analytics/client.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/analytics/modules/analytics/client.ts b/packages/analytics/modules/analytics/client.ts
index 46fa4c09b..09be16027 100644
--- a/packages/analytics/modules/analytics/client.ts
+++ b/packages/analytics/modules/analytics/client.ts
@@ -85,8 +85,8 @@ function createBackgroundAnalytics(
const getBackgroundMeta = () => ({
timestamp: Date.now(),
- // Don't track sessions for the background, it can be running
- // indefinitely, and will inflate session duration stats.
+ // Don't track sessions for the background, it can be running indefinitely
+ // and will inflate session duration stats.
sessionId: undefined,
language: navigator.language,
referrer: undefined,
From ea303d53b902ab7ae50d976faa6064ccfa2f559a Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Wed, 24 Dec 2025 22:24:54 +0100
Subject: [PATCH 05/64] clean(auto-icons): add TODO suggestion and missing
await for ensureDir
---
packages/auto-icons/src/index.ts | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/packages/auto-icons/src/index.ts b/packages/auto-icons/src/index.ts
index a97aeede5..bd01adcb5 100644
--- a/packages/auto-icons/src/index.ts
+++ b/packages/auto-icons/src/index.ts
@@ -1,6 +1,6 @@
import 'wxt';
import { defineWxtModule } from 'wxt/modules';
-import { resolve, relative } from 'node:path';
+import { relative, resolve } from 'node:path';
import defu from 'defu';
import sharp from 'sharp';
import { ensureDir, exists } from 'fs-extra';
@@ -19,6 +19,9 @@ export default defineWxtModule({
},
);
+ // TODO: MAYBE DROP THIS COMPATIBILITY?
+ // TODO: It has a while, and for 1.0.0 we should be as much "up to date" as we can,
+ // TODO: because later it'll be harder to make breaking change?
// Backward compatibility for the deprecated option
if (options?.grayscaleOnDevelopment !== undefined) {
wxt.logger.warn(
@@ -37,7 +40,9 @@ export default defineWxtModule({
if (!parsedOptions.enabled)
return wxt.logger.warn(`\`[auto-icons]\` ${this.name} disabled`);
- if (!(await exists(resolvedPath))) {
+ // TODO: STH DOESN'T GOOD WITH FS-EXTRA, BECAUSE IT DOESN'T RECOGNIZE TYPES PROPERLY,
+ // TODO: SIMILAR ISSUE LIKE IN #2015 PR
+ if (!(await (exists as (path: string) => Promise)(resolvedPath))) {
return wxt.logger.warn(
`\`[auto-icons]\` Skipping icon generation, no base icon found at ${relative(process.cwd(), resolvedPath)}`,
);
@@ -91,7 +96,7 @@ export default defineWxtModule({
}
}
- ensureDir(resolve(outputFolder, 'icons'));
+ await ensureDir(resolve(outputFolder, 'icons'));
await resizedImage.toFile(resolve(outputFolder, `icons/${size}.png`));
output.publicAssets.push({
From ba6e87727a840b27b36d38d6bd9b536c27e9db17 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 11:48:53 +0100
Subject: [PATCH 06/64] fix(packages/browser): remove unnecessary argument of
transformFile function of generate.ts
---
packages/browser/scripts/generate.ts | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/packages/browser/scripts/generate.ts b/packages/browser/scripts/generate.ts
index ddbf1d816..b40849d29 100644
--- a/packages/browser/scripts/generate.ts
+++ b/packages/browser/scripts/generate.ts
@@ -5,12 +5,10 @@ import { dirname, join, resolve, sep } from 'node:path';
import { sep as posixSep } from 'node:path/posix';
// Fetch latest version
-
console.log('Getting latest version of \x1b[36m@types/chrome\x1b[0m');
await spawn('pnpm', ['i', '--ignore-scripts', '-D', '@types/chrome@latest']);
// Generate new package.json
-
console.log('Generating new \x1b[36mpackage.json\x1b[0m');
const pkgJsonPath = fileURLToPath(
@@ -31,7 +29,6 @@ await fs.writeJson(outPkgJsonPath, newPkgJson);
await spawn('pnpm', ['-w', 'prettier', '--write', outPkgJsonPath]);
// Generate declaration files
-
console.log('Generating declaration files');
const outDir = resolve('src/gen');
const declarationFileMapping = (
@@ -51,7 +48,7 @@ const declarationFileMapping = (
for (const { file, srcPath, destPath } of declarationFileMapping) {
const content = await fs.readFile(srcPath, 'utf8');
- const transformedContent = transformFile(file, content);
+ const transformedContent = transformFileContent(content);
const destDir = dirname(destPath);
await fs.mkdir(destDir, { recursive: true });
await fs.writeFile(destPath, transformedContent);
@@ -59,12 +56,10 @@ for (const { file, srcPath, destPath } of declarationFileMapping) {
}
// Done!
-
console.log('\x1b[32m✔\x1b[0m Done in ' + performance.now().toFixed(0) + ' ms');
// Transformations
-
-function transformFile(file: string, content: string): string {
+function transformFileContent(content: string) {
return (
// Add prefix
`/* DO NOT EDIT - generated by scripts/generate.ts */\n\n${content}\n`
From 7bdaf83ea614fed1c8d383d81d6b7061c5c7ebcf Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 12:09:33 +0100
Subject: [PATCH 07/64] fix(packages/browser): change tests type assertions
from deprecated to newer equivalent
---
packages/browser/src/__tests__/index.test.ts | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/packages/browser/src/__tests__/index.test.ts b/packages/browser/src/__tests__/index.test.ts
index 07b40f518..ad0193dfe 100644
--- a/packages/browser/src/__tests__/index.test.ts
+++ b/packages/browser/src/__tests__/index.test.ts
@@ -5,19 +5,19 @@ import { browser, type Browser } from '../index';
describe('browser', () => {
describe('types', () => {
it('should provide types via the Browser import', () => {
- expectTypeOf().toMatchTypeOf();
- expectTypeOf().toMatchTypeOf();
- expectTypeOf().toMatchTypeOf();
+ expectTypeOf().toEqualTypeOf();
+ expectTypeOf().toEqualTypeOf();
+ expectTypeOf().toEqualTypeOf();
});
it('should provide values via the browser import', () => {
- expectTypeOf(browser.runtime.id).toMatchTypeOf();
- expectTypeOf(
- browser.storage.local,
- ).toMatchTypeOf();
- expectTypeOf(
- browser.i18n.detectLanguage('Hello, world!'),
- ).resolves.toMatchTypeOf();
+ expectTypeOf().toEqualTypeOf();
+ expectTypeOf<
+ typeof browser.storage.local
+ >().toEqualTypeOf();
+ expectTypeOf<
+ typeof browser.i18n.detectLanguage
+ >().returns.resolves.toEqualTypeOf();
});
});
});
From dc2a35218e8c16f04ddf6ca852bb36f0fe8e182b Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 12:30:52 +0100
Subject: [PATCH 08/64] fix(packages/i18n): remove unnecessary staff from tests
and add question
---
packages/i18n/src/__tests__/types.test.ts | 4 +++-
packages/i18n/src/__tests__/utils.test.ts | 4 ++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/i18n/src/__tests__/types.test.ts b/packages/i18n/src/__tests__/types.test.ts
index 540732092..2df2ae516 100644
--- a/packages/i18n/src/__tests__/types.test.ts
+++ b/packages/i18n/src/__tests__/types.test.ts
@@ -4,6 +4,7 @@ import { browser } from '@wxt-dev/browser';
vi.mock('@wxt-dev/browser', async () => {
const { vi } = await import('vitest');
+
return {
browser: {
i18n: {
@@ -14,7 +15,7 @@ vi.mock('@wxt-dev/browser', async () => {
});
const getMessageMock = vi.mocked(browser.i18n.getMessage);
-const n: number = 1;
+const n = 1;
describe('I18n Types', () => {
beforeEach(() => {
@@ -47,6 +48,7 @@ describe('I18n Types', () => {
describe('t', () => {
it('should only allow passing valid combinations of arguments', () => {
i18n.t('simple');
+ // TODO: WHY THERE'S SO MUCH TS-EXPECT-ERRORS?
// @ts-expect-error
i18n.t('simple', []);
// @ts-expect-error
diff --git a/packages/i18n/src/__tests__/utils.test.ts b/packages/i18n/src/__tests__/utils.test.ts
index 7c44bc46f..d9fd38266 100644
--- a/packages/i18n/src/__tests__/utils.test.ts
+++ b/packages/i18n/src/__tests__/utils.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect } from 'vitest';
+import { describe, expect, it } from 'vitest';
import { ChromeMessage } from '../build';
import {
applyChromeMessagePlaceholders,
@@ -43,7 +43,7 @@ describe('Utils', () => {
});
describe('getSubstitutionCount', () => {
- it('should return the last substution present in the message', () => {
+ it('should return the last substitution present in the message', () => {
expect(getSubstitutionCount('I like $1, but I like $2 better')).toBe(2);
});
From 6dd1874a8cd4b35631faf55cf323c54d88a39bda Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 12:39:44 +0100
Subject: [PATCH 09/64] fix(packages/i18n): improve readability of code
---
packages/i18n/src/build.ts | 10 ++++++++--
packages/i18n/src/index.ts | 6 +++++-
packages/i18n/src/module.ts | 9 ++++++---
packages/i18n/src/utils.ts | 3 ++-
4 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/packages/i18n/src/build.ts b/packages/i18n/src/build.ts
index 65144939e..dd2d8b6b6 100644
--- a/packages/i18n/src/build.ts
+++ b/packages/i18n/src/build.ts
@@ -6,7 +6,7 @@
*/
import { mkdir, readFile, writeFile } from 'node:fs/promises';
-import { parseYAML, parseJSON5, parseTOML } from 'confbox';
+import { parseJSON5, parseTOML, parseYAML } from 'confbox';
import { dirname, extname } from 'node:path';
import { applyChromeMessagePlaceholders, getSubstitutionCount } from './utils';
@@ -118,6 +118,7 @@ export async function parseMessagesFile(
): Promise {
const text = await readFile(file, 'utf8');
const ext = extname(file).toLowerCase();
+
return parseMessagesText(text, EXT_FORMATS_MAP[ext] ?? 'JSON5');
}
@@ -173,13 +174,16 @@ function _parseMessagesObject(
`Messages file should not contain \`${object}\` (found at "${path.join('.')}")`,
);
}
+
if (Array.isArray(object))
return object.flatMap((item, i) =>
_parseMessagesObject(path.concat(String(i)), item, depth + 1),
);
+
if (isPluralMessage(object)) {
const message = Object.values(object).join('|');
const substitutions = getSubstitutionCount(message);
+
return [
{
type: 'plural',
@@ -189,9 +193,11 @@ function _parseMessagesObject(
},
];
}
+
if (depth === 1 && isChromeMessage(object)) {
const message = applyChromeMessagePlaceholders(object);
const substitutions = getSubstitutionCount(message);
+
return [
{
type: 'chrome',
@@ -227,7 +233,7 @@ function isChromeMessage(object: any): object is ChromeMessage {
export function generateTypeText(messages: ParsedMessage[]): string {
const renderMessageEntry = (message: ParsedMessage): string => {
- // Use . for deep keys at runtime and types
+ // Use '.' for deep keys at runtime and types
const key = message.key.join('.');
const features = [
diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts
index 559083b06..a736eb311 100644
--- a/packages/i18n/src/index.ts
+++ b/packages/i18n/src/index.ts
@@ -2,9 +2,9 @@
* @module @wxt-dev/i18n
*/
import {
- I18nStructure,
DefaultI18nStructure,
I18n,
+ I18nStructure,
Substitution,
} from './types';
import { browser } from '@wxt-dev/browser';
@@ -16,6 +16,7 @@ export function createI18n<
// Resolve args
let sub: Substitution[] | undefined;
let count: number | undefined;
+
args.forEach((arg, i) => {
if (arg == null) {
// ignore nullish args
@@ -37,6 +38,7 @@ export function createI18n<
// Load the localization
let message: string;
+
if (sub?.length) {
// Convert all substitutions to strings
const stringSubs = sub?.map((sub) => String(sub));
@@ -44,9 +46,11 @@ export function createI18n<
} else {
message = browser.i18n.getMessage(key.replaceAll('.', '_'));
}
+
if (!message) {
console.warn(`[i18n] Message not found: "${key}"`);
}
+
if (count == null) return message;
// Apply pluralization
diff --git a/packages/i18n/src/module.ts b/packages/i18n/src/module.ts
index 68c914894..624722ba3 100644
--- a/packages/i18n/src/module.ts
+++ b/packages/i18n/src/module.ts
@@ -14,8 +14,8 @@ import 'wxt';
import { addAlias, defineWxtModule } from 'wxt/modules';
import {
generateChromeMessagesText,
- parseMessagesFile,
generateTypeText,
+ parseMessagesFile,
SUPPORTED_LOCALES,
} from './build';
import glob from 'fast-glob';
@@ -70,6 +70,7 @@ export default defineWxtModule({
GeneratedPublicFile[]
> => {
const files = await getLocalizationFiles();
+
return await Promise.all(
files.map(async ({ file, locale }) => {
const messages = await parseMessagesFile(file);
@@ -86,13 +87,15 @@ export default defineWxtModule({
const defaultLocaleFile = files.find(
({ locale }) => locale === wxt.config.manifest.default_locale,
)!;
- if (defaultLocaleFile == null) {
+
+ if (defaultLocaleFile === null) {
throw Error(
`\`[i18n]\` Required localization file does not exist: \`/${wxt.config.manifest.default_locale}.{json|json5|yml|yaml|toml}\``,
);
}
const messages = await parseMessagesFile(defaultLocaleFile.file);
+
return {
path: typesPath,
text: generateTypeText(messages),
@@ -152,7 +155,7 @@ export { type GeneratedI18nStructure }
addAlias(wxt, '#i18n', sourcePath);
- // Generate separate declaration file containing types - this prevents
+ // Generate a separate declaration file containing types - this prevents
// firing the dev server's default file watcher when updating the types,
// which would cause a full rebuild and reload of the extension.
diff --git a/packages/i18n/src/utils.ts b/packages/i18n/src/utils.ts
index 6856c79ad..1762bf7be 100644
--- a/packages/i18n/src/utils.ts
+++ b/packages/i18n/src/utils.ts
@@ -1,7 +1,7 @@
import { ChromeMessage } from './build';
export function applyChromeMessagePlaceholders(message: ChromeMessage): string {
- if (message.placeholders == null) return message.message;
+ if (message.placeholders === null) return message.message;
return Object.entries(message.placeholders ?? {}).reduce(
(text, [name, value]) => {
@@ -28,6 +28,7 @@ export function standardizeLocale(locale: string): string {
const [is_match, prefix, suffix] =
locale.match(/^([a-z]{2})[-_]([a-z]{2,3})$/i) ?? [];
+
if (is_match) {
return `${prefix.toLowerCase()}_${suffix.toUpperCase()}`;
}
From bbd29eae55d86409c0cd7766e7d65e9aa9b3132c Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 13:22:45 +0100
Subject: [PATCH 10/64] fix(packages/runner): remove unnecessary code
---
packages/runner/src/__tests__/options.test.ts | 7 +++----
packages/runner/src/bidi.ts | 1 +
packages/runner/src/cdp.ts | 1 +
packages/runner/src/install.ts | 13 +++++--------
packages/wxt/src/core/define-web-ext-config.ts | 1 +
5 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/packages/runner/src/__tests__/options.test.ts b/packages/runner/src/__tests__/options.test.ts
index 23ec3e4ab..28a92c71a 100644
--- a/packages/runner/src/__tests__/options.test.ts
+++ b/packages/runner/src/__tests__/options.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect, vi, beforeAll } from 'vitest';
+import { beforeAll, describe, expect, it, vi } from 'vitest';
import { ResolvedRunOptions, resolveRunOptions } from '../options';
-import { resolve, join } from 'node:path';
-import { tmpdir, homedir } from 'node:os';
+import { join, resolve } from 'node:path';
+import { homedir, tmpdir } from 'node:os';
import { mkdir } from 'node:fs/promises';
vi.mock('node:os', async () => {
@@ -11,7 +11,6 @@ vi.mock('node:os', async () => {
return {
...os,
tmpdir: () => join(os.tmpdir(), 'tmpdir-mock'),
- homedir: () => join(os.tmpdir(), 'homedir-mock'),
};
});
diff --git a/packages/runner/src/bidi.ts b/packages/runner/src/bidi.ts
index d784d3552..d9b16a19d 100644
--- a/packages/runner/src/bidi.ts
+++ b/packages/runner/src/bidi.ts
@@ -23,6 +23,7 @@ export async function createBidiConnection(
send(method, params, timeout = 10e3) {
const id = ++requestId;
const command = { id, method, params };
+
debugBidi('Sending command:', command);
return new Promise((resolve, reject) => {
diff --git a/packages/runner/src/cdp.ts b/packages/runner/src/cdp.ts
index b96ac3ca8..92475ae84 100644
--- a/packages/runner/src/cdp.ts
+++ b/packages/runner/src/cdp.ts
@@ -21,6 +21,7 @@ export function createCdpConnection(
send(method, params, timeout = 10e3) {
const id = ++requestId;
const command = { id, method, params };
+
debugCdp('Sending command:', command);
return new Promise((resolve, reject) => {
diff --git a/packages/runner/src/install.ts b/packages/runner/src/install.ts
index 2338f22a0..7033f00f5 100644
--- a/packages/runner/src/install.ts
+++ b/packages/runner/src/install.ts
@@ -17,15 +17,12 @@ export async function installFirefox(
await bidi.send('session.new', { capabilities: {} });
// Install the extension
- return await bidi.send(
- 'webExtension.install',
- {
- extensionData: {
- type: 'path',
- path: extensionDir,
- },
+ return bidi.send('webExtension.install', {
+ extensionData: {
+ type: 'path',
+ path: extensionDir,
},
- );
+ });
}
export type BidiWebExtensionInstallResponse = {
diff --git a/packages/wxt/src/core/define-web-ext-config.ts b/packages/wxt/src/core/define-web-ext-config.ts
index 7826004fc..f12d0be97 100644
--- a/packages/wxt/src/core/define-web-ext-config.ts
+++ b/packages/wxt/src/core/define-web-ext-config.ts
@@ -1,6 +1,7 @@
import consola from 'consola';
import { WebExtConfig } from '../types';
+// TODO: MAYBE DROP IT, BEFORE 1.0.0?
/**
* @deprecated Use `defineWebExtConfig` instead. Same function, different name.
*/
From 64709e911375171f7b01c19bd211324743149dee Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 13:37:23 +0100
Subject: [PATCH 11/64] fix(packages/runner): make comparing to `null` more
strict and fix JDocs for getMetas() and setMetas()
---
packages/storage/src/index.ts | 23 ++++++++++++-----------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts
index b629ec4de..b743b1423 100644
--- a/packages/storage/src/index.ts
+++ b/packages/storage/src/index.ts
@@ -19,7 +19,7 @@ function createStorage(): WxtStorage {
};
const getDriver = (area: StorageArea) => {
const driver = drivers[area];
- if (driver == null) {
+ if (driver === null) {
const areaNames = Object.keys(drivers).join(', ');
throw Error(`Invalid area "${area}". Options: ${areaNames}`);
}
@@ -29,7 +29,7 @@ function createStorage(): WxtStorage {
const deliminatorIndex = key.indexOf(':');
const driverArea = key.substring(0, deliminatorIndex) as StorageArea;
const driverKey = key.substring(deliminatorIndex + 1);
- if (driverKey == null)
+ if (driverKey === null)
throw Error(
`Storage key should be in the form of "area:key", but received "${key}"`,
);
@@ -44,7 +44,7 @@ function createStorage(): WxtStorage {
const mergeMeta = (oldMeta: any, newMeta: any): any => {
const newFields = { ...oldMeta };
Object.entries(newMeta).forEach(([key, value]) => {
- if (value == null) delete newFields[key];
+ if (value === null) delete newFields[key];
else newFields[key] = value;
});
return newFields;
@@ -102,11 +102,11 @@ function createStorage(): WxtStorage {
properties: string | string[] | undefined,
) => {
const metaKey = getMetaKey(driverKey);
- if (properties == null) {
+ if (properties === null) {
await driver.removeItem(metaKey);
} else {
const newFields = getMetaValue(await driver.getItem(metaKey));
- [properties].flat().forEach((field) => delete newFields[field]);
+ [properties].flat().forEach((field) => delete newFields[field ?? '']);
await driver.setItem(metaKey, newFields);
}
};
@@ -368,7 +368,7 @@ function createStorage(): WxtStorage {
driverKey,
driverMetaKey,
]);
- if (value == null) return;
+ if (value === null) return;
const currentVersion = meta?.v ?? 1;
if (currentVersion > targetVersion) {
@@ -380,7 +380,7 @@ function createStorage(): WxtStorage {
return;
}
- if (debug === true) {
+ if (debug) {
console.debug(
`[@wxt-dev/storage] Running storage migration for ${key}: v${currentVersion} -> v${targetVersion}`,
);
@@ -395,7 +395,7 @@ function createStorage(): WxtStorage {
migratedValue =
(await migrations?.[migrateToVersion]?.(migratedValue)) ??
migratedValue;
- if (debug === true) {
+ if (debug) {
console.debug(
`[@wxt-dev/storage] Storage migration processed for version: v${migrateToVersion}`,
);
@@ -411,7 +411,7 @@ function createStorage(): WxtStorage {
{ key: driverMetaKey, value: { ...meta, v: targetVersion } },
]);
- if (debug === true) {
+ if (debug) {
console.debug(
`[@wxt-dev/storage] Storage migration completed for ${key} v${targetVersion}`,
{ migratedValue },
@@ -626,8 +626,8 @@ export interface WxtStorage {
/**
* Get the metadata of multiple storage items.
*
- * @param items List of keys or items to get the metadata of.
* @returns An array containing storage keys and their metadata.
+ * @param keys List of keys or items to get the metadata of.
*/
getMetas(
keys: Array>,
@@ -669,7 +669,7 @@ export interface WxtStorage {
/**
* Set the metadata of multiple storage items.
*
- * @param items List of storage keys or items and metadata to set for each.
+ * @param metas List of storage keys or items and metadata to set for each.
*/
setMetas(
metas: Array<
@@ -863,6 +863,7 @@ export interface SnapshotOptions {
}
export interface WxtStorageItemOptions {
+ // TODO: MAYBE REMOVE IT BEFORE 1.0 RELEASE?
/**
* @deprecated Renamed to `fallback`, use it instead.
*/
From 1d25b85fd10aa7d49c542594a07825d50a2383a2 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 13:39:52 +0100
Subject: [PATCH 12/64] fix(packages/runner): simplify return of
createStorage()
---
packages/storage/src/index.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts
index b743b1423..5d6394d88 100644
--- a/packages/storage/src/index.ts
+++ b/packages/storage/src/index.ts
@@ -118,7 +118,7 @@ function createStorage(): WxtStorage {
return driver.watch(driverKey, cb);
};
- const storage: WxtStorage = {
+ return {
getItem: async (key, opts) => {
const { driver, driverKey } = resolveKey(key);
return await getItem(driver, driverKey, opts);
@@ -491,7 +491,6 @@ function createStorage(): WxtStorage {
};
},
};
- return storage;
}
function createDriver(storageArea: StorageArea): WxtStorageDriver {
From dd4bc41da915a8c4b5cd794e1f32e44d1511f9d8 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 13:54:41 +0100
Subject: [PATCH 13/64] revert(packages/storage): back to check with `==`
instead of `===`
---
packages/storage/src/index.ts | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts
index 5d6394d88..b7f8a7e12 100644
--- a/packages/storage/src/index.ts
+++ b/packages/storage/src/index.ts
@@ -19,7 +19,7 @@ function createStorage(): WxtStorage {
};
const getDriver = (area: StorageArea) => {
const driver = drivers[area];
- if (driver === null) {
+ if (driver == null) {
const areaNames = Object.keys(drivers).join(', ');
throw Error(`Invalid area "${area}". Options: ${areaNames}`);
}
@@ -29,7 +29,7 @@ function createStorage(): WxtStorage {
const deliminatorIndex = key.indexOf(':');
const driverArea = key.substring(0, deliminatorIndex) as StorageArea;
const driverKey = key.substring(deliminatorIndex + 1);
- if (driverKey === null)
+ if (driverKey == null)
throw Error(
`Storage key should be in the form of "area:key", but received "${key}"`,
);
@@ -44,7 +44,7 @@ function createStorage(): WxtStorage {
const mergeMeta = (oldMeta: any, newMeta: any): any => {
const newFields = { ...oldMeta };
Object.entries(newMeta).forEach(([key, value]) => {
- if (value === null) delete newFields[key];
+ if (value == null) delete newFields[key];
else newFields[key] = value;
});
return newFields;
@@ -102,11 +102,11 @@ function createStorage(): WxtStorage {
properties: string | string[] | undefined,
) => {
const metaKey = getMetaKey(driverKey);
- if (properties === null) {
+ if (properties == null) {
await driver.removeItem(metaKey);
} else {
const newFields = getMetaValue(await driver.getItem(metaKey));
- [properties].flat().forEach((field) => delete newFields[field ?? '']);
+ [properties].flat().forEach((field) => delete newFields[field]);
await driver.setItem(metaKey, newFields);
}
};
@@ -368,7 +368,7 @@ function createStorage(): WxtStorage {
driverKey,
driverMetaKey,
]);
- if (value === null) return;
+ if (value == null) return;
const currentVersion = meta?.v ?? 1;
if (currentVersion > targetVersion) {
From 24590947cf35f5e4ca4d404c3cd5263324b5cbb4 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 14:35:01 +0100
Subject: [PATCH 14/64] fix(packages/storage): create types to avoid `any`
---
packages/storage/src/index.ts | 212 ++++++++++++++++++++++------------
1 file changed, 138 insertions(+), 74 deletions(-)
diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts
index b7f8a7e12..056167fde 100644
--- a/packages/storage/src/index.ts
+++ b/packages/storage/src/index.ts
@@ -41,7 +41,10 @@ function createStorage(): WxtStorage {
};
};
const getMetaKey = (key: string) => key + '$';
- const mergeMeta = (oldMeta: any, newMeta: any): any => {
+ const mergeMeta = (
+ oldMeta: Record,
+ newMeta: Record,
+ ): Record => {
const newFields = { ...oldMeta };
Object.entries(newMeta).forEach(([key, value]) => {
if (value == null) delete newFields[key];
@@ -49,41 +52,43 @@ function createStorage(): WxtStorage {
});
return newFields;
};
- const getValueOrFallback = (value: any, fallback: any) =>
+ const getValueOrFallback = (value: T | null | undefined, fallback: T) =>
value ?? fallback ?? null;
- const getMetaValue = (properties: any) =>
- typeof properties === 'object' && !Array.isArray(properties)
- ? properties
+ const getMetaValue = (properties: unknown): Record =>
+ typeof properties === 'object' &&
+ properties !== null &&
+ !Array.isArray(properties)
+ ? (properties as Record)
: {};
- const getItem = async (
+ const getItem = async (
driver: WxtStorageDriver,
driverKey: string,
- opts: GetItemOptions | undefined,
- ) => {
- const res = await driver.getItem(driverKey);
- return getValueOrFallback(res, opts?.fallback ?? opts?.defaultValue);
+ opts: GetItemOptions | undefined,
+ ): Promise => {
+ const res = await driver.getItem(driverKey);
+ return getValueOrFallback(res, (opts?.fallback ?? opts?.defaultValue) as T);
};
const getMeta = async (driver: WxtStorageDriver, driverKey: string) => {
const metaKey = getMetaKey(driverKey);
- const res = await driver.getItem(metaKey);
+ const res = await driver.getItem>(metaKey);
return getMetaValue(res);
};
- const setItem = async (
+ const setItem = async (
driver: WxtStorageDriver,
driverKey: string,
- value: any,
+ value: T,
) => {
await driver.setItem(driverKey, value ?? null);
};
const setMeta = async (
driver: WxtStorageDriver,
driverKey: string,
- properties: any | undefined,
+ properties: Record | undefined,
) => {
const metaKey = getMetaKey(driverKey);
const existingFields = getMetaValue(await driver.getItem(metaKey));
- await driver.setItem(metaKey, mergeMeta(existingFields, properties));
+ await driver.setItem(metaKey, mergeMeta(existingFields, properties ?? {}));
};
const removeItem = async (
driver: WxtStorageDriver,
@@ -110,12 +115,12 @@ function createStorage(): WxtStorage {
await driver.setItem(metaKey, newFields);
}
};
- const watch = (
+ const watch = (
driver: WxtStorageDriver,
driverKey: string,
- cb: WatchCallback,
+ cb: WatchCallback,
) => {
- return driver.watch(driverKey, cb);
+ return driver.watch(driverKey, cb);
};
return {
@@ -125,12 +130,15 @@ function createStorage(): WxtStorage {
},
getItems: async (keys) => {
const areaToKeyMap = new Map();
- const keyToOptsMap = new Map | undefined>();
+ const keyToOptsMap = new Map<
+ string,
+ GetItemOptions | undefined
+ >();
const orderedKeys: StorageItemKey[] = [];
keys.forEach((key) => {
let keyStr: StorageItemKey;
- let opts: GetItemOptions | undefined;
+ let opts: GetItemOptions | undefined;
if (typeof key === 'string') {
// key: string
keyStr = key;
@@ -150,7 +158,7 @@ function createStorage(): WxtStorage {
keyToOptsMap.set(keyStr, opts);
});
- const resultsMap = new Map();
+ const resultsMap = new Map();
await Promise.all(
Array.from(areaToKeyMap.entries()).map(async ([driverArea, keys]) => {
const driverResults = await drivers[driverArea].getItems(keys);
@@ -171,9 +179,9 @@ function createStorage(): WxtStorage {
value: resultsMap.get(key),
}));
},
- getMeta: async (key) => {
+ getMeta: async >(key: StorageItemKey) => {
const { driver, driverKey } = resolveKey(key);
- return await getMeta(driver, driverKey);
+ return (await getMeta(driver, driverKey)) as T;
},
getMetas: async (args) => {
const keys = args.map((arg) => {
@@ -194,14 +202,17 @@ function createStorage(): WxtStorage {
return map;
}, {});
- const resultsMap: Record = {};
+ const resultsMap: Record> = {};
await Promise.all(
Object.entries(areaToDriverMetaKeysMap).map(async ([area, keys]) => {
const areaRes = await browser.storage[area as StorageArea].get(
keys.map((key) => key.driverMetaKey),
);
keys.forEach((key) => {
- resultsMap[key.key] = areaRes[key.driverMetaKey] ?? {};
+ resultsMap[key.key] = (areaRes[key.driverMetaKey] ?? {}) as Record<
+ string,
+ unknown
+ >;
});
}),
);
@@ -217,14 +228,14 @@ function createStorage(): WxtStorage {
},
setItems: async (items) => {
const areaToKeyValueMap: Partial<
- Record>
+ Record>
> = {};
items.forEach((item) => {
const { driverArea, driverKey } = resolveKey(
'key' in item ? item.key : item.item.key,
);
areaToKeyValueMap[driverArea] ??= [];
- areaToKeyValueMap[driverArea].push({
+ areaToKeyValueMap[driverArea]!.push({
key: driverKey,
value: item.value,
});
@@ -238,20 +249,23 @@ function createStorage(): WxtStorage {
},
setMeta: async (key, properties) => {
const { driver, driverKey } = resolveKey(key);
- await setMeta(driver, driverKey, properties);
+ await setMeta(driver, driverKey, properties as Record);
},
setMetas: async (items) => {
const areaToMetaUpdatesMap: Partial<
- Record
+ Record<
+ StorageArea,
+ { key: string; properties: Record }[]
+ >
> = {};
items.forEach((item) => {
const { driverArea, driverKey } = resolveKey(
'key' in item ? item.key : item.item.key,
);
areaToMetaUpdatesMap[driverArea] ??= [];
- areaToMetaUpdatesMap[driverArea].push({
+ areaToMetaUpdatesMap[driverArea]!.push({
key: driverKey,
- properties: item.meta,
+ properties: item.meta as Record,
});
});
@@ -348,7 +362,13 @@ function createStorage(): WxtStorage {
driver.unwatch();
});
},
- defineItem: (key, opts?: WxtStorageItemOptions) => {
+ defineItem: <
+ TValue,
+ TMetadata extends Record = Record,
+ >(
+ key: StorageItemKey,
+ opts?: WxtStorageItemOptions,
+ ) => {
const { driver, driverKey } = resolveKey(key);
const {
@@ -364,13 +384,15 @@ function createStorage(): WxtStorage {
}
const migrate = async () => {
const driverMetaKey = getMetaKey(driverKey);
- const [{ value }, { value: meta }] = await driver.getItems([
+ const [itemRes, metaRes] = await driver.getItems([
driverKey,
driverMetaKey,
]);
+ const value = itemRes.value;
+ const meta = getMetaValue(metaRes.value);
if (value == null) return;
- const currentVersion = meta?.v ?? 1;
+ const currentVersion = (meta?.v as number | undefined) ?? 1;
if (currentVersion > targetVersion) {
throw Error(
`Version downgrade detected (v${currentVersion} -> v${targetVersion}) for "${key}"`,
@@ -408,7 +430,10 @@ function createStorage(): WxtStorage {
}
await driver.setItems([
{ key: driverKey, value: migratedValue },
- { key: driverMetaKey, value: { ...meta, v: targetVersion } },
+ {
+ key: driverMetaKey,
+ value: { ...meta, v: targetVersion },
+ },
]);
if (debug) {
@@ -417,7 +442,7 @@ function createStorage(): WxtStorage {
{ migratedValue },
);
}
- onMigrationComplete?.(migratedValue, targetVersion);
+ onMigrationComplete?.(migratedValue as TValue, targetVersion);
};
const migrationsDone =
opts?.migrations == null
@@ -435,12 +460,12 @@ function createStorage(): WxtStorage {
const getOrInitValue = () =>
initMutex.runExclusive(async () => {
- const value = await driver.getItem(driverKey);
+ const value = await driver.getItem(driverKey);
// Don't init value if it already exists or the init function isn't provided
if (value != null || opts?.init == null) return value;
const newValue = await opts.init();
- await driver.setItem(driverKey, newValue);
+ await driver.setItem(driverKey, newValue);
return newValue;
});
@@ -450,22 +475,25 @@ function createStorage(): WxtStorage {
return {
key,
get defaultValue() {
- return getFallback();
+ return getFallback() as TValue;
},
get fallback() {
- return getFallback();
+ return getFallback() as TValue;
},
getValue: async () => {
await migrationsDone;
if (opts?.init) {
- return await getOrInitValue();
+ return (await getOrInitValue()) as TValue;
} else {
- return await getItem(driver, driverKey, opts);
+ return (await getItem(driver, driverKey, opts)) as TValue;
}
},
getMeta: async () => {
await migrationsDone;
- return await getMeta(driver, driverKey);
+ return (await getMeta(
+ driver,
+ driverKey,
+ )) as NullablePartial;
},
setValue: async (value) => {
await migrationsDone;
@@ -473,7 +501,11 @@ function createStorage(): WxtStorage {
},
setMeta: async (properties) => {
await migrationsDone;
- return await setMeta(driver, driverKey, properties);
+ return await setMeta(
+ driver,
+ driverKey,
+ properties as Record,
+ );
},
removeValue: async (opts) => {
await migrationsDone;
@@ -483,9 +515,12 @@ function createStorage(): WxtStorage {
await migrationsDone;
return await removeMeta(driver, driverKey, properties);
},
- watch: (cb) =>
- watch(driver, driverKey, (newValue, oldValue) =>
- cb(newValue ?? getFallback(), oldValue ?? getFallback()),
+ watch: (cb: WatchCallback) =>
+ watch(driver, driverKey, (newValue, oldValue) =>
+ cb(
+ (newValue ?? getFallback()) as TValue,
+ (oldValue ?? getFallback()) as TValue,
+ ),
),
migrate,
};
@@ -517,9 +552,9 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
};
const watchListeners = new Set<(changes: StorageAreaChanges) => void>();
return {
- getItem: async (key) => {
- const res = await getStorageArea().get>(key);
- return res[key];
+ getItem: async (key: string) => {
+ const res = await getStorageArea().get>(key);
+ return (res[key] as T) ?? null;
},
getItems: async (keys) => {
const result = await getStorageArea().get(keys);
@@ -557,11 +592,11 @@ function createDriver(storageArea: StorageArea): WxtStorageDriver {
restoreSnapshot: async (data) => {
await getStorageArea().set(data);
},
- watch(key, cb) {
+ watch(key: string, cb: WatchCallback) {
const listener = (changes: StorageAreaChanges) => {
const change = changes[key] as {
- newValue?: any;
- oldValue?: any | null;
+ newValue?: T;
+ oldValue?: T | null;
} | null;
if (change == null) return;
if (dequal(change.newValue, change.oldValue)) return;
@@ -610,10 +645,10 @@ export interface WxtStorage {
getItems(
keys: Array<
| StorageItemKey
- | WxtStorageItem
- | { key: StorageItemKey; options?: GetItemOptions }
+ | WxtStorageItem>
+ | { key: StorageItemKey; options?: GetItemOptions }
>,
- ): Promise>;
+ ): Promise>;
/**
* Return an object containing metadata about the key. Object is stored at `key + "$"`. If value
* is not an object, it returns an empty object.
@@ -629,8 +664,10 @@ export interface WxtStorage {
* @param keys List of keys or items to get the metadata of.
*/
getMetas(
- keys: Array>,
- ): Promise>;
+ keys: Array<
+ StorageItemKey | WxtStorageItem>
+ >,
+ ): Promise }>>;
/**
* Set a value in storage. Setting a value to `null` or `undefined` is equivalent to calling
* `removeItem`.
@@ -650,8 +687,11 @@ export interface WxtStorage {
*/
setItems(
values: Array<
- | { key: StorageItemKey; value: any }
- | { item: WxtStorageItem; value: any }
+ | { key: StorageItemKey; value: unknown }
+ | {
+ item: WxtStorageItem>;
+ value: unknown;
+ }
>,
): Promise;
/**
@@ -672,8 +712,11 @@ export interface WxtStorage {
*/
setMetas(
metas: Array<
- | { key: StorageItemKey; meta: Record }
- | { item: WxtStorageItem; meta: Record }
+ | { key: StorageItemKey; meta: Record }
+ | {
+ item: WxtStorageItem>;
+ meta: Record;
+ }
>,
): Promise;
/**
@@ -689,9 +732,12 @@ export interface WxtStorage {
removeItems(
keys: Array<
| StorageItemKey
- | WxtStorageItem
+ | WxtStorageItem>
| { key: StorageItemKey; options?: RemoveItemOptions }
- | { item: WxtStorageItem; options?: RemoveItemOptions }
+ | {
+ item: WxtStorageItem>;
+ options?: RemoveItemOptions;
+ }
>,
): Promise;
@@ -725,7 +771,10 @@ export interface WxtStorage {
* Restores the results of `snapshot`. If new properties have been saved since the snapshot, they are
* not overridden. Only values existing in the snapshot are overridden.
*/
- restoreSnapshot(base: StorageArea, data: any): Promise;
+ restoreSnapshot(
+ base: StorageArea,
+ data: Record,
+ ): Promise;
/**
* Watch for changes to a specific key in storage.
*/
@@ -740,24 +789,39 @@ export interface WxtStorage {
*
* Read full docs: https://wxt.dev/storage.html#defining-storage-items
*/
- defineItem = {}>(
+ defineItem<
+ TValue,
+ TMetadata extends Record = Record,
+ >(
key: StorageItemKey,
): WxtStorageItem;
- defineItem = {}>(
+ defineItem<
+ TValue,
+ TMetadata extends Record = Record,
+ >(
key: StorageItemKey,
options: WxtStorageItemOptions & { fallback: TValue },
): WxtStorageItem;
- defineItem = {}>(
+ defineItem<
+ TValue,
+ TMetadata extends Record = Record,
+ >(
key: StorageItemKey,
options: WxtStorageItemOptions & { defaultValue: TValue },
): WxtStorageItem;
- defineItem = {}>(
+ defineItem<
+ TValue,
+ TMetadata extends Record = Record,
+ >(
key: StorageItemKey,
options: WxtStorageItemOptions & {
init: () => TValue | Promise;
},
): WxtStorageItem;
- defineItem = {}>(
+ defineItem<
+ TValue,
+ TMetadata extends Record = Record,
+ >(
key: StorageItemKey,
options: WxtStorageItemOptions,
): WxtStorageItem;
@@ -765,9 +829,9 @@ export interface WxtStorage {
interface WxtStorageDriver {
getItem(key: string): Promise;
- getItems(keys: string[]): Promise<{ key: string; value: any }[]>;
+ getItems(keys: string[]): Promise<{ key: string; value: unknown }[]>;
setItem(key: string, value: T | null): Promise;
- setItems(values: Array<{ key: string; value: any }>): Promise;
+ setItems(values: Array<{ key: string; value: unknown }>): Promise;
removeItem(key: string): Promise;
removeItems(keys: string[]): Promise;
clear(): Promise;
@@ -824,7 +888,7 @@ export interface WxtStorageItem<
/**
* If there are migrations defined on the storage item, migrate to the latest version.
*
- * **This function is ran automatically whenever the extension updates**, so you don't have to call it
+ * **This function is run automatically whenever the extension updates**, so you don't have to call it
* manually.
*/
migrate(): Promise;
@@ -886,7 +950,7 @@ export interface WxtStorageItemOptions {
/**
* A map of version numbers to the functions used to migrate the data to that version.
*/
- migrations?: Record any>;
+ migrations?: Record unknown>;
/**
* Print debug logs, such as migration process.
* @default false
From a03b9b4138f21b01b63ffad7b90cd2bf24502c79 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 14:42:01 +0100
Subject: [PATCH 15/64] fix(packages/runner): create types to avoid `any`
---
packages/runner/src/__tests__/options.test.ts | 2 +-
packages/runner/src/bidi.ts | 6 +++++-
packages/runner/src/cdp.ts | 6 +++++-
packages/runner/src/debug.ts | 5 +++--
packages/runner/src/web-socket.ts | 2 +-
5 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/packages/runner/src/__tests__/options.test.ts b/packages/runner/src/__tests__/options.test.ts
index 28a92c71a..8c4e87532 100644
--- a/packages/runner/src/__tests__/options.test.ts
+++ b/packages/runner/src/__tests__/options.test.ts
@@ -6,7 +6,7 @@ import { mkdir } from 'node:fs/promises';
vi.mock('node:os', async () => {
const { vi } = await import('vitest');
- const os: any = await vi.importActual('node:os');
+ const os = (await vi.importActual('node:os')) as typeof import('node:os');
const { join } = await import('node:path');
return {
...os,
diff --git a/packages/runner/src/bidi.ts b/packages/runner/src/bidi.ts
index d9b16a19d..468801e3b 100644
--- a/packages/runner/src/bidi.ts
+++ b/packages/runner/src/bidi.ts
@@ -4,7 +4,11 @@ import { debug } from './debug';
const debugBidi = debug.scoped('bidi');
export interface BidiConnection extends Disposable {
- send(method: string, params: any, timeout?: number): Promise;
+ send(
+ method: string,
+ params: Record,
+ timeout?: number,
+ ): Promise;
close(): void;
}
diff --git a/packages/runner/src/cdp.ts b/packages/runner/src/cdp.ts
index 92475ae84..ee2a074ae 100644
--- a/packages/runner/src/cdp.ts
+++ b/packages/runner/src/cdp.ts
@@ -5,7 +5,11 @@ import { debug } from './debug';
const debugCdp = debug.scoped('cdp');
export interface CDPConnection extends Disposable {
- send(method: string, params: any, timeout?: number): Promise;
+ send(
+ method: string,
+ params: Record,
+ timeout?: number,
+ ): Promise;
close(): void;
}
diff --git a/packages/runner/src/debug.ts b/packages/runner/src/debug.ts
index fbf91e395..ee308572a 100644
--- a/packages/runner/src/debug.ts
+++ b/packages/runner/src/debug.ts
@@ -1,10 +1,11 @@
export interface Debug {
- (...args: any[]): void;
scoped: (scope: string) => Debug;
+
+ (...args: unknown[]): void;
}
function createDebug(scopes: string[]): Debug {
- const debug = (...args: any[]) => {
+ const debug = (...args: unknown[]) => {
const scope = scopes.join(':');
if (
process.env.DEBUG === '1' ||
diff --git a/packages/runner/src/web-socket.ts b/packages/runner/src/web-socket.ts
index baf5bc1f9..be758d2ad 100644
--- a/packages/runner/src/web-socket.ts
+++ b/packages/runner/src/web-socket.ts
@@ -25,7 +25,7 @@ export function openWebSocket(url: string): Promise {
),
);
};
- const onError = (error: any) => {
+ const onError = (error: unknown) => {
cleanup();
reject(new Error('Error connecting to WebSocket', { cause: error }));
};
From 553628efdc8a20e705a5a00655ba898f16e5cb12 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 14:43:00 +0100
Subject: [PATCH 16/64] fix(packages/runner): add clearingTimeout on
createBidiConnection -> send(), for avoid unnecessary code run
---
packages/runner/src/bidi.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/runner/src/bidi.ts b/packages/runner/src/bidi.ts
index 468801e3b..f08f18774 100644
--- a/packages/runner/src/bidi.ts
+++ b/packages/runner/src/bidi.ts
@@ -36,7 +36,7 @@ export async function createBidiConnection(
webSocket.removeEventListener('error', onError);
};
- setTimeout(() => {
+ const timeoutId = setTimeout(() => {
cleanup();
reject(
new Error(
@@ -49,12 +49,14 @@ export async function createBidiConnection(
const data = JSON.parse(event.data);
if (data.id === id) {
debugBidi('Received response:', data);
+ clearTimeout(timeoutId);
cleanup();
if (data.type === 'success') resolve(data.result);
else reject(Error(data.message, { cause: data }));
}
};
- const onError = (error: any) => {
+ const onError = (error: unknown) => {
+ clearTimeout(timeoutId);
cleanup();
reject(new Error('Error sending request', { cause: error }));
};
From 604e895da8e6da13e76dde34188bd3ae2ea94776 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 15:26:01 +0100
Subject: [PATCH 17/64] fix(packages/runner): change `any` to more strict types
and by this change, avoids @ts-expect-error for tests
---
packages/i18n/src/__tests__/types.test.ts | 25 -------------------
packages/i18n/src/build.ts | 29 +++++++++++++----------
packages/i18n/src/index.ts | 4 ++--
packages/i18n/src/types.ts | 21 +++++++++-------
4 files changed, 30 insertions(+), 49 deletions(-)
diff --git a/packages/i18n/src/__tests__/types.test.ts b/packages/i18n/src/__tests__/types.test.ts
index 2df2ae516..160eac4d2 100644
--- a/packages/i18n/src/__tests__/types.test.ts
+++ b/packages/i18n/src/__tests__/types.test.ts
@@ -48,66 +48,41 @@ describe('I18n Types', () => {
describe('t', () => {
it('should only allow passing valid combinations of arguments', () => {
i18n.t('simple');
- // TODO: WHY THERE'S SO MUCH TS-EXPECT-ERRORS?
- // @ts-expect-error
i18n.t('simple', []);
- // @ts-expect-error
i18n.t('simple', ['one']);
- // @ts-expect-error
i18n.t('simple', n);
i18n.t('simpleSub1', ['one']);
- // @ts-expect-error
i18n.t('simpleSub1');
- // @ts-expect-error
i18n.t('simpleSub1', []);
- // @ts-expect-error
i18n.t('simpleSub1', ['one', 'two']);
- // @ts-expect-error
i18n.t('simpleSub1', n);
i18n.t('simpleSub2', ['one', 'two']);
- // @ts-expect-error
i18n.t('simpleSub2');
- // @ts-expect-error
i18n.t('simpleSub2', ['one']);
- // @ts-expect-error
i18n.t('simpleSub2', ['one', 'two', 'three']);
- // @ts-expect-error
i18n.t('simpleSub2', n);
i18n.t('plural', n);
- // @ts-expect-error
i18n.t('plural');
- // @ts-expect-error
i18n.t('plural', []);
- // @ts-expect-error
i18n.t('plural', ['one']);
- // @ts-expect-error
i18n.t('plural', n, ['sub']);
i18n.t('pluralSub1', n);
i18n.t('pluralSub1', n, undefined);
i18n.t('pluralSub1', n, ['one']);
- // @ts-expect-error
i18n.t('pluralSub1');
- // @ts-expect-error
i18n.t('pluralSub1', ['one']);
- // @ts-expect-error
i18n.t('pluralSub1', n, []);
- // @ts-expect-error
i18n.t('pluralSub1', n, ['one', 'two']);
i18n.t('pluralSub2', n, ['one', 'two']);
- // @ts-expect-error
i18n.t('pluralSub2');
- // @ts-expect-error
i18n.t('pluralSub2', ['one', 'two']);
- // @ts-expect-error
i18n.t('pluralSub2', n, ['one']);
- // @ts-expect-error
i18n.t('pluralSub2', n, ['one', 'two', 'three']);
- // @ts-expect-error
i18n.t('pluralSub2', n);
});
});
diff --git a/packages/i18n/src/build.ts b/packages/i18n/src/build.ts
index dd2d8b6b6..5bc62f0cd 100644
--- a/packages/i18n/src/build.ts
+++ b/packages/i18n/src/build.ts
@@ -94,7 +94,7 @@ const EXT_FORMATS_MAP: Record = {
'.toml': 'TOML',
};
-const PARSERS: Record any> = {
+const PARSERS: Record unknown> = {
YAML: parseYAML,
JSON5: parseJSON5,
TOML: parseTOML,
@@ -135,11 +135,11 @@ export function parseMessagesText(
/**
* Given the JS object form of a raw messages file, extract the messages.
*/
-export function parseMessagesObject(object: any): ParsedMessage[] {
+export function parseMessagesObject(object: unknown): ParsedMessage[] {
return _parseMessagesObject(
[],
{
- ...object,
+ ...(object as Record),
...PREDEFINED_MESSAGES,
},
0,
@@ -148,7 +148,7 @@ export function parseMessagesObject(object: any): ParsedMessage[] {
function _parseMessagesObject(
path: string[],
- object: any,
+ object: unknown,
depth: number,
): ParsedMessage[] {
switch (typeof object) {
@@ -169,7 +169,7 @@ function _parseMessagesObject(
];
}
case 'object':
- if ([null, undefined].includes(object)) {
+ if (object === null || object === undefined) {
throw new Error(
`Messages file should not contain \`${object}\` (found at "${path.join('.')}")`,
);
@@ -189,13 +189,13 @@ function _parseMessagesObject(
type: 'plural',
key: path,
substitutions,
- plurals: object,
+ plurals: object as Record,
},
];
}
- if (depth === 1 && isChromeMessage(object)) {
- const message = applyChromeMessagePlaceholders(object);
+ if (depth === 1 && isChromeMessage(object as object)) {
+ const message = applyChromeMessagePlaceholders(object as ChromeMessage);
const substitutions = getSubstitutionCount(message);
return [
@@ -203,25 +203,28 @@ function _parseMessagesObject(
type: 'chrome',
key: path,
substitutions,
- ...object,
+ ...(object as ChromeMessage),
},
];
}
- return Object.entries(object).flatMap(([key, value]) =>
- _parseMessagesObject(path.concat(key), value, depth + 1),
+ return Object.entries(object as Record).flatMap(
+ ([key, value]) =>
+ _parseMessagesObject(path.concat(key), value, depth + 1),
);
default:
throw Error(`"Could not parse object of type "${typeof object}"`);
}
}
-function isPluralMessage(object: any): object is Record {
+function isPluralMessage(
+ object: object,
+): object is Record {
return Object.keys(object).every(
(key) => key === 'n' || isFinite(Number(key)),
);
}
-function isChromeMessage(object: any): object is ChromeMessage {
+function isChromeMessage(object: object): object is ChromeMessage {
return Object.keys(object).every((key) =>
ALLOWED_CHROME_MESSAGE_KEYS.has(key),
);
diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts
index a736eb311..5abedca15 100644
--- a/packages/i18n/src/index.ts
+++ b/packages/i18n/src/index.ts
@@ -10,9 +10,9 @@ import {
import { browser } from '@wxt-dev/browser';
export function createI18n<
- T extends I18nStructure = DefaultI18nStructure,
+ T extends I18nStructure | DefaultI18nStructure = DefaultI18nStructure,
>(): I18n {
- const t = (key: string, ...args: any[]) => {
+ const t = (key: string, ...args: unknown[]) => {
// Resolve args
let sub: Substitution[] | undefined;
let count: number | undefined;
diff --git a/packages/i18n/src/types.ts b/packages/i18n/src/types.ts
index d742c873c..905c5fed9 100644
--- a/packages/i18n/src/types.ts
+++ b/packages/i18n/src/types.ts
@@ -7,13 +7,12 @@ export type I18nStructure = {
[K: string]: I18nFeatures;
};
-export type DefaultI18nStructure = {
- [K: string]: any;
-};
+export type DefaultI18nStructure = Record;
// prettier-ignore
export type SubstitutionTuple =
- T extends 1 ? [$1: Substitution]
+ T extends 0 ? []
+ : T extends 1 ? [$1: Substitution]
: T extends 2 ? [$1: Substitution, $2: Substitution]
: T extends 3 ? [$1: Substitution, $2: Substitution, $3: Substitution]
: T extends 4 ? [$1: Substitution, $2: Substitution, $3: Substitution, $4: Substitution]
@@ -22,7 +21,7 @@ export type SubstitutionTuple =
: T extends 7 ? [$1: Substitution, $2: Substitution, $3: Substitution, $4: Substitution, $5: Substitution, $6: Substitution, $7: Substitution]
: T extends 8 ? [$1: Substitution, $2: Substitution, $3: Substitution, $4: Substitution, $5: Substitution, $6: Substitution, $7: Substitution, $8: Substitution]
: T extends 9 ? [$1: Substitution, $2: Substitution, $3: Substitution, $4: Substitution, $5: Substitution, $6: Substitution, $7: Substitution, $8: Substitution, $9: Substitution]
- : never
+ : []
export type TFunction = {
// Non-plural, no substitutions
@@ -37,7 +36,7 @@ export type TFunction = {
key: K & { [P in keyof T]: T[P] extends { plural: false; substitutions: SubstitutionCount } ? P : never; }[keyof T],
substitutions: T[K] extends I18nFeatures
? SubstitutionTuple
- : never,
+ : [],
): string;
// Plural with 1 substitution
@@ -62,12 +61,16 @@ export type TFunction = {
n: number,
substitutions: T[K] extends I18nFeatures
? SubstitutionTuple
- : never,
+ : [],
): string;
};
-export interface I18n {
- t: TFunction;
+export interface I18n<
+ T extends I18nStructure | DefaultI18nStructure = DefaultI18nStructure,
+> {
+ t: T extends DefaultI18nStructure
+ ? (key: string, ...args: unknown[]) => string
+ : TFunction>;
}
export type Substitution = string | number;
From d417287ed91336f1295fec658410c862f3d53abc Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 15:39:54 +0100
Subject: [PATCH 18/64] fix(packages/wxt): add missing `lang` prop for
for couple e2e tests
---
packages/wxt/e2e/tests/analysis.test.ts | 20 ++++++-------
packages/wxt/e2e/tests/auto-imports.test.ts | 30 +++++++++----------
packages/wxt/e2e/tests/hooks.test.ts | 12 ++++----
packages/wxt/e2e/tests/modules.test.ts | 6 ++--
.../wxt/e2e/tests/output-structure.test.ts | 23 +++++++-------
.../wxt/e2e/tests/typescript-project.test.ts | 22 +++++++-------
packages/wxt/e2e/tests/user-config.test.ts | 2 +-
7 files changed, 59 insertions(+), 56 deletions(-)
diff --git a/packages/wxt/e2e/tests/analysis.test.ts b/packages/wxt/e2e/tests/analysis.test.ts
index 0ba662f9f..b14cfb344 100644
--- a/packages/wxt/e2e/tests/analysis.test.ts
+++ b/packages/wxt/e2e/tests/analysis.test.ts
@@ -17,8 +17,8 @@ describe('Analysis', () => {
it('should output a stats.html with no part files by default', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', '');
- project.addFile('entrypoints/options.html', '');
+ project.addFile('entrypoints/popup.html', '');
+ project.addFile('entrypoints/options.html', '');
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
@@ -38,8 +38,8 @@ describe('Analysis', () => {
it('should save part files when requested', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', '');
- project.addFile('entrypoints/options.html', '');
+ project.addFile('entrypoints/popup.html', '');
+ project.addFile('entrypoints/options.html', '');
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
@@ -59,8 +59,8 @@ describe('Analysis', () => {
it('should support customizing the stats output directory', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', '');
- project.addFile('entrypoints/options.html', '');
+ project.addFile('entrypoints/popup.html', '');
+ project.addFile('entrypoints/options.html', '');
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
@@ -78,8 +78,8 @@ describe('Analysis', () => {
it('should place artifacts next to the custom output file', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', '');
- project.addFile('entrypoints/options.html', '');
+ project.addFile('entrypoints/popup.html', '');
+ project.addFile('entrypoints/options.html', '');
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
@@ -100,8 +100,8 @@ describe('Analysis', () => {
it('should open the stats in the browser when requested', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', '');
- project.addFile('entrypoints/options.html', '');
+ project.addFile('entrypoints/popup.html', '');
+ project.addFile('entrypoints/options.html', '');
project.addFile(
'entrypoints/background.ts',
'export default defineBackground(() => {});',
diff --git a/packages/wxt/e2e/tests/auto-imports.test.ts b/packages/wxt/e2e/tests/auto-imports.test.ts
index 8746656ff..6b20e2721 100644
--- a/packages/wxt/e2e/tests/auto-imports.test.ts
+++ b/packages/wxt/e2e/tests/auto-imports.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect } from 'vitest';
+import { describe, expect, it } from 'vitest';
import { TestProject } from '../utils';
import spawn from 'nano-spawn';
@@ -6,7 +6,7 @@ describe('Auto Imports', () => {
describe('imports: { ... }', () => {
it('should generate a declaration file, imports.d.ts, for auto-imports', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', ``);
+ project.addFile('entrypoints/popup.html', ``);
await project.prepare();
@@ -70,7 +70,7 @@ describe('Auto Imports', () => {
it('should include auto-imports in the project', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', ``);
+ project.addFile('entrypoints/popup.html', ``);
await project.prepare();
@@ -91,7 +91,7 @@ describe('Auto Imports', () => {
it('should generate the #imports module', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', ``);
+ project.addFile('entrypoints/popup.html', ``);
// Project auto-imports should also be present
project.addFile(
'utils/time.ts',
@@ -138,7 +138,7 @@ describe('Auto Imports', () => {
project.setConfigFileConfig({
imports: false,
});
- project.addFile('entrypoints/popup.html', ``);
+ project.addFile('entrypoints/popup.html', ``);
await project.prepare();
@@ -150,8 +150,7 @@ describe('Auto Imports', () => {
project.setConfigFileConfig({
imports: false,
});
- project.addFile('entrypoints/popup.html', ``);
-
+ project.addFile('entrypoints/popup.html', ``);
await project.prepare();
expect(
@@ -176,7 +175,7 @@ describe('Auto Imports', () => {
project.setConfigFileConfig({
imports: false,
});
- project.addFile('entrypoints/popup.html', ``);
+ project.addFile('entrypoints/popup.html', ``);
// Project auto-imports should also be present
project.addFile(
'utils/time.ts',
@@ -219,7 +218,7 @@ describe('Auto Imports', () => {
describe('eslintrc', () => {
it('"enabled: true" should output a JSON config file compatible with ESlint 8', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', ``);
+ project.addFile('entrypoints/popup.html', ``);
await project.prepare({
imports: {
@@ -236,7 +235,7 @@ describe('Auto Imports', () => {
it('"enabled: 8" should output a JSON config file compatible with ESlint 8', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', ``);
+ project.addFile('entrypoints/popup.html', ``);
await project.prepare({
imports: {
@@ -253,7 +252,7 @@ describe('Auto Imports', () => {
it('"enabled: 9" should output a flat config file compatible with ESlint 9', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', ``);
+ project.addFile('entrypoints/popup.html', ``);
await project.prepare({
imports: {
@@ -270,7 +269,7 @@ describe('Auto Imports', () => {
it('"enabled: false" should NOT output an ESlint config file', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', ``);
+ project.addFile('entrypoints/popup.html', ``);
await project.prepare({
imports: {
@@ -290,7 +289,7 @@ describe('Auto Imports', () => {
it('should NOT output an ESlint config file by default', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', ``);
+ project.addFile('entrypoints/popup.html', ``);
await project.prepare();
@@ -304,7 +303,7 @@ describe('Auto Imports', () => {
it('should allow customizing the output', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', ``);
+ project.addFile('entrypoints/popup.html', ``);
await project.prepare({
imports: {
@@ -331,7 +330,8 @@ describe('Auto Imports', () => {
await project.prepare({
imports: { eslintrc: { enabled: version } },
});
- return await spawn('pnpm', ['eslint', 'entrypoints/background.js'], {
+
+ return spawn('pnpm', ['eslint', 'entrypoints/background.js'], {
cwd: project.root,
});
}
diff --git a/packages/wxt/e2e/tests/hooks.test.ts b/packages/wxt/e2e/tests/hooks.test.ts
index 9442c4088..ef063c1c9 100644
--- a/packages/wxt/e2e/tests/hooks.test.ts
+++ b/packages/wxt/e2e/tests/hooks.test.ts
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { TestProject } from '../utils';
-import { WxtHooks } from '../../src/types';
+import { WxtHooks } from '../../src';
const hooks: WxtHooks = {
ready: vi.fn(),
@@ -48,7 +48,7 @@ describe('Hooks', () => {
it('prepare should call hooks', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', '');
+ project.addFile('entrypoints/popup.html', '');
await project.prepare({ hooks });
@@ -80,7 +80,7 @@ describe('Hooks', () => {
it('build should call hooks', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', '');
+ project.addFile('entrypoints/popup.html', '');
await project.build({ hooks });
@@ -112,7 +112,7 @@ describe('Hooks', () => {
it('zip should call hooks', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', '');
+ project.addFile('entrypoints/popup.html', '');
await project.zip({ hooks });
@@ -144,7 +144,7 @@ describe('Hooks', () => {
it('zip -b firefox should call hooks', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', '');
+ project.addFile('entrypoints/popup.html', '');
await project.zip({ hooks, browser: 'firefox' });
@@ -176,7 +176,7 @@ describe('Hooks', () => {
it('server.start should call hooks', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', '');
+ project.addFile('entrypoints/popup.html', '');
const server = await project.startServer({
hooks,
diff --git a/packages/wxt/e2e/tests/modules.test.ts b/packages/wxt/e2e/tests/modules.test.ts
index dcbe6c90a..ece6a65ee 100644
--- a/packages/wxt/e2e/tests/modules.test.ts
+++ b/packages/wxt/e2e/tests/modules.test.ts
@@ -1,6 +1,6 @@
-import { describe, it, expect, vi } from 'vitest';
+import { describe, expect, it, vi } from 'vitest';
import { TestProject } from '../utils';
-import type { GenericEntrypoint, InlineConfig } from '../../src/types';
+import type { GenericEntrypoint, InlineConfig } from '../../src';
import { readFile } from 'fs-extra';
import { normalizePath } from '../../src/core/utils/paths';
@@ -201,7 +201,7 @@ describe('Module Helpers', () => {
project.addFile(
'entrypoints/popup/index.html',
`
-
+
`,
diff --git a/packages/wxt/e2e/tests/output-structure.test.ts b/packages/wxt/e2e/tests/output-structure.test.ts
index 0db84f041..76ff8b2a8 100644
--- a/packages/wxt/e2e/tests/output-structure.test.ts
+++ b/packages/wxt/e2e/tests/output-structure.test.ts
@@ -5,9 +5,12 @@ describe('Output Directory Structure', () => {
it('should not output hidden files and directories that start with "."', async () => {
const project = new TestProject();
project.addFile('entrypoints/.DS_Store');
- project.addFile('entrypoints/.hidden1/index.html', '');
- project.addFile('entrypoints/.hidden2.html', '');
- project.addFile('entrypoints/unlisted.html', '');
+ project.addFile(
+ 'entrypoints/.hidden1/index.html',
+ '',
+ );
+ project.addFile('entrypoints/.hidden2.html', '');
+ project.addFile('entrypoints/unlisted.html', '');
await project.build();
@@ -23,7 +26,7 @@ describe('Output Directory Structure', () => {
================================================================================
.output/chrome-mv3/unlisted.html
----------------------------------------
-
+
"
`);
});
@@ -113,7 +116,7 @@ describe('Output Directory Structure', () => {
it('should not include an entrypoint if the target browser is not in the list of included targets', async () => {
const project = new TestProject();
- project.addFile('entrypoints/options.html', '');
+ project.addFile('entrypoints/options.html', '');
project.addFile(
'entrypoints/background.ts',
`
@@ -133,7 +136,7 @@ describe('Output Directory Structure', () => {
it('should not include an entrypoint if the target browser is in the list of excluded targets', async () => {
const project = new TestProject();
- project.addFile('entrypoints/options.html', '');
+ project.addFile('entrypoints/options.html', '');
project.addFile(
'entrypoints/background.ts',
`
@@ -163,7 +166,7 @@ describe('Output Directory Structure', () => {
'entrypoints/background.ts',
`export default defineBackground(() => {});`,
);
- project.addFile('entrypoints/popup.html', '');
+ project.addFile('entrypoints/popup.html', '');
project.addFile(
'entrypoints/overlay.content.ts',
`export default defineContentScript({
@@ -293,7 +296,7 @@ describe('Output Directory Structure', () => {
it("should output to a custom directory when overriding 'outDir'", async () => {
const project = new TestProject();
- project.addFile('entrypoints/unlisted.html', '');
+ project.addFile('entrypoints/unlisted.html', '');
project.setConfigFileConfig({
outDir: 'dist',
});
@@ -324,7 +327,7 @@ describe('Output Directory Structure', () => {
);
project.addFile(
'entrypoints/popup/index.html',
- `
+ `
@@ -397,7 +400,7 @@ describe('Output Directory Structure', () => {
);
project.addFile(
'entrypoints/popup/index.html',
- `
+ `
diff --git a/packages/wxt/e2e/tests/typescript-project.test.ts b/packages/wxt/e2e/tests/typescript-project.test.ts
index 35d35a6aa..0327c38b5 100644
--- a/packages/wxt/e2e/tests/typescript-project.test.ts
+++ b/packages/wxt/e2e/tests/typescript-project.test.ts
@@ -4,7 +4,7 @@ import { TestProject } from '../utils';
describe('TypeScript Project', () => {
it('should generate defined constants correctly', async () => {
const project = new TestProject();
- project.addFile('entrypoints/unlisted.html', '');
+ project.addFile('entrypoints/unlisted.html', '');
await project.prepare();
@@ -33,9 +33,9 @@ describe('TypeScript Project', () => {
it('should augment the types for browser.runtime.getURL', async () => {
const project = new TestProject();
- project.addFile('entrypoints/popup.html', '');
- project.addFile('entrypoints/options.html', '');
- project.addFile('entrypoints/sandbox.html', '');
+ project.addFile('entrypoints/popup.html', '');
+ project.addFile('entrypoints/options.html', '');
+ project.addFile('entrypoints/sandbox.html', '');
await project.prepare();
@@ -65,7 +65,7 @@ describe('TypeScript Project', () => {
it('should augment the types for browser.i18n.getMessage', async () => {
const project = new TestProject();
- project.addFile('entrypoints/unlisted.html', '');
+ project.addFile('entrypoints/unlisted.html', '');
project.addFile(
'public/_locales/en/messages.json',
JSON.stringify({
@@ -227,7 +227,7 @@ describe('TypeScript Project', () => {
it('should reference all the required types in a single declaration file', async () => {
const project = new TestProject();
- project.addFile('entrypoints/unlisted.html', '');
+ project.addFile('entrypoints/unlisted.html', '');
await project.prepare();
@@ -248,7 +248,7 @@ describe('TypeScript Project', () => {
it('should generate a TSConfig file for the project', async () => {
const project = new TestProject();
- project.addFile('entrypoints/unlisted.html', '');
+ project.addFile('entrypoints/unlisted.html', '');
await project.prepare();
@@ -289,7 +289,7 @@ describe('TypeScript Project', () => {
it('should generate correct path aliases for a custom srcDir', async () => {
const project = new TestProject();
- project.addFile('src/entrypoints/unlisted.html', '');
+ project.addFile('src/entrypoints/unlisted.html', '');
project.setConfigFileConfig({
srcDir: 'src',
});
@@ -333,7 +333,7 @@ describe('TypeScript Project', () => {
it('should add additional path aliases listed in the alias config, preventing defaults from being overridden', async () => {
const project = new TestProject();
- project.addFile('src/entrypoints/unlisted.html', '');
+ project.addFile('src/entrypoints/unlisted.html', '');
project.setConfigFileConfig({
srcDir: 'src',
alias: {
@@ -383,7 +383,7 @@ describe('TypeScript Project', () => {
it('should start path aliases with "./" for paths inside the .wxt dir', async () => {
const project = new TestProject();
- project.addFile('src/entrypoints/unlisted.html', '');
+ project.addFile('src/entrypoints/unlisted.html', '');
project.setConfigFileConfig({
srcDir: 'src',
alias: {
@@ -399,7 +399,7 @@ describe('TypeScript Project', () => {
it('should set correct import.meta.env.BROWSER type based on targetBrowsers', async () => {
const project = new TestProject();
- project.addFile('entrypoints/unlisted.html', '');
+ project.addFile('entrypoints/unlisted.html', '');
project.setConfigFileConfig({
targetBrowsers: ['firefox', 'chrome'],
});
diff --git a/packages/wxt/e2e/tests/user-config.test.ts b/packages/wxt/e2e/tests/user-config.test.ts
index 7eecaae4a..b781d7ac0 100644
--- a/packages/wxt/e2e/tests/user-config.test.ts
+++ b/packages/wxt/e2e/tests/user-config.test.ts
@@ -60,7 +60,7 @@ describe('User Config', () => {
it('should merge inline and user config based manifests', async () => {
const project = new TestProject();
- project.addFile('entrypoints/unlisted.html', '');
+ project.addFile('entrypoints/unlisted.html', '');
project.addFile(
'wxt.config.ts',
`import { defineConfig } from 'wxt';
From a994bbe247ed4330ad962ef4d48f5d816d538948 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 16:22:54 +0100
Subject: [PATCH 19/64] clean(packages/wxt): make code more readable and add 2
questions
---
packages/wxt/e2e/tests/dev.test.ts | 1 +
packages/wxt/e2e/tests/hooks.test.ts | 4 +++-
packages/wxt/e2e/tests/modules.test.ts | 2 ++
packages/wxt/e2e/tests/output-structure.test.ts | 2 +-
packages/wxt/e2e/tests/remote-code.test.ts | 10 +++++-----
.../src/core/builders/vite/plugins/bundleAnalysis.ts | 1 +
6 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/packages/wxt/e2e/tests/dev.test.ts b/packages/wxt/e2e/tests/dev.test.ts
index c782bb94c..40539f855 100644
--- a/packages/wxt/e2e/tests/dev.test.ts
+++ b/packages/wxt/e2e/tests/dev.test.ts
@@ -10,6 +10,7 @@ describe('Dev Mode', () => {
);
const server = await project.startServer({
+ // TODO: MAYBE REMOVE IT BEFORE 1.0.0?
runner: {
disabled: true,
},
diff --git a/packages/wxt/e2e/tests/hooks.test.ts b/packages/wxt/e2e/tests/hooks.test.ts
index ef063c1c9..a5f068eb0 100644
--- a/packages/wxt/e2e/tests/hooks.test.ts
+++ b/packages/wxt/e2e/tests/hooks.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
import { TestProject } from '../utils';
import { WxtHooks } from '../../src';
@@ -34,6 +34,7 @@ function expectHooksToBeCalled(
const hookName = key as keyof WxtHooks;
const value = called[hookName];
const times = typeof value === 'number' ? value : value ? 1 : 0;
+
expect(
hooks[hookName],
`Expected "${hookName}" to be called ${times} time(s)`,
@@ -184,6 +185,7 @@ describe('Hooks', () => {
disabled: true,
},
});
+
expect(hooks['server:closed']).not.toBeCalled();
await server.stop();
diff --git a/packages/wxt/e2e/tests/modules.test.ts b/packages/wxt/e2e/tests/modules.test.ts
index ece6a65ee..d1d837846 100644
--- a/packages/wxt/e2e/tests/modules.test.ts
+++ b/packages/wxt/e2e/tests/modules.test.ts
@@ -255,12 +255,14 @@ describe('Module Helpers', () => {
customImport();
});`,
);
+
const utils = project.addFile(
'custom.ts',
`export function customImport() {
console.log("${expectedText}")
}`,
);
+
project.addFile(
'modules/test.ts',
`import { defineWxtModule } from 'wxt/modules';
diff --git a/packages/wxt/e2e/tests/output-structure.test.ts b/packages/wxt/e2e/tests/output-structure.test.ts
index 76ff8b2a8..94a76f401 100644
--- a/packages/wxt/e2e/tests/output-structure.test.ts
+++ b/packages/wxt/e2e/tests/output-structure.test.ts
@@ -411,7 +411,7 @@ describe('Output Directory Structure', () => {
await project.build({
vite: () => ({
build: {
- // Make output for snapshot readible
+ // Make output for snapshot readable
minify: false,
},
}),
diff --git a/packages/wxt/e2e/tests/remote-code.test.ts b/packages/wxt/e2e/tests/remote-code.test.ts
index c445cc564..6517e2da1 100644
--- a/packages/wxt/e2e/tests/remote-code.test.ts
+++ b/packages/wxt/e2e/tests/remote-code.test.ts
@@ -1,13 +1,13 @@
-import { describe, it, expect } from 'vitest';
+import { describe, expect, it } from 'vitest';
import { TestProject } from '../utils';
describe('Remote Code', () => {
it('should download "url:*" modules and include them in the final bundle', async () => {
- const url = 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js';
+ const URL = 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js';
const project = new TestProject();
project.addFile(
'entrypoints/popup.ts',
- `import "url:${url}"
+ `import "url:${URL}"
export default defineUnlistedScript(() => {})`,
);
@@ -18,9 +18,9 @@ describe('Remote Code', () => {
// Some text that will hopefully be in future versions of this script
'__lodash_placeholder__',
);
- expect(output).not.toContain(url);
+ expect(output).not.toContain(URL);
expect(
- await project.fileExists(`.wxt/cache/${encodeURIComponent(url)}`),
+ await project.fileExists(`.wxt/cache/${encodeURIComponent(URL)}`),
).toBe(true);
});
});
diff --git a/packages/wxt/src/core/builders/vite/plugins/bundleAnalysis.ts b/packages/wxt/src/core/builders/vite/plugins/bundleAnalysis.ts
index 744e3032a..266252e85 100644
--- a/packages/wxt/src/core/builders/vite/plugins/bundleAnalysis.ts
+++ b/packages/wxt/src/core/builders/vite/plugins/bundleAnalysis.ts
@@ -15,6 +15,7 @@ export function bundleAnalysis(config: ResolvedConfig): vite.Plugin {
}) as vite.Plugin;
}
+// TODO: MAYBE REMOVE IT BEFORE 1.0.0?
/**
* @deprecated FOR TESTING ONLY.
*/
From 754d36dcf5f41bbd6181f0afaf93e00ac54166bd Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 16:23:33 +0100
Subject: [PATCH 20/64] fix(packages/wxt): add `example` props for InlineConfig
for avoid ts-expect-error
---
packages/wxt/e2e/tests/modules.test.ts | 1 -
packages/wxt/src/types.ts | 4 ++++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/wxt/e2e/tests/modules.test.ts b/packages/wxt/e2e/tests/modules.test.ts
index d1d837846..51e137cda 100644
--- a/packages/wxt/e2e/tests/modules.test.ts
+++ b/packages/wxt/e2e/tests/modules.test.ts
@@ -33,7 +33,6 @@ describe('Module Helpers', () => {
);
await project.build({
- // @ts-expect-error: untyped field for testing
example: options,
});
diff --git a/packages/wxt/src/types.ts b/packages/wxt/src/types.ts
index 284af1d78..5ff354097 100644
--- a/packages/wxt/src/types.ts
+++ b/packages/wxt/src/types.ts
@@ -374,6 +374,10 @@ export interface InlineConfig {
* "wxt-module-analytics").
*/
modules?: string[];
+ /**
+ * Field only for testing purposes, don't use it for other cases
+ */
+ example?: { key: string };
}
// TODO: Extract to @wxt/vite-builder and use module augmentation to include the vite field
From 4ecae1dace0126dbc23907d582b28373fe11aaaf Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 16:28:10 +0100
Subject: [PATCH 21/64] clean(packages/wxt): remove unnecessary serializeWxtDir
---
packages/wxt/e2e/utils.ts | 8 --------
1 file changed, 8 deletions(-)
diff --git a/packages/wxt/e2e/utils.ts b/packages/wxt/e2e/utils.ts
index 8a44c9859..61ca3315f 100644
--- a/packages/wxt/e2e/utils.ts
+++ b/packages/wxt/e2e/utils.ts
@@ -138,14 +138,6 @@ export class TestProject {
return this.serializeDir('.output', ignoreContentsOfFilenames);
}
- /**
- * Read all the files from the test project's `.wxt` directory and combine them into a string
- * that can be used in a snapshot.
- */
- serializeWxtDir(): Promise {
- return this.serializeDir(resolve(this.root, '.wxt/types'));
- }
-
/**
* Deeply print the filename and contents of all files in a directory.
*
From db82216ac09d6fbad118958f75f74f3d808de69c Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 16:42:41 +0100
Subject: [PATCH 22/64] fix(packages/wxt): change deprecated exists with
pathExists
---
packages/wxt/e2e/utils.ts | 2 +-
packages/wxt/src/core/generate-wxt-dir.ts | 2 +-
packages/wxt/src/core/resolve-config.ts | 2 +-
packages/wxt/src/core/utils/fs.ts | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/wxt/e2e/utils.ts b/packages/wxt/e2e/utils.ts
index 61ca3315f..ff34c9a3e 100644
--- a/packages/wxt/e2e/utils.ts
+++ b/packages/wxt/e2e/utils.ts
@@ -178,7 +178,7 @@ export class TestProject {
}
fileExists(...path: string[]): Promise {
- return fs.exists(this.resolvePath(...path));
+ return fs.pathExists(this.resolvePath(...path));
}
async getOutputManifest(
diff --git a/packages/wxt/src/core/generate-wxt-dir.ts b/packages/wxt/src/core/generate-wxt-dir.ts
index ad7aeb2ef..921f1c130 100644
--- a/packages/wxt/src/core/generate-wxt-dir.ts
+++ b/packages/wxt/src/core/generate-wxt-dir.ts
@@ -136,7 +136,7 @@ declare module "wxt/browser" {
'messages.json',
);
let messages: Message[];
- if (await fs.exists(defaultLocalePath)) {
+ if (await fs.pathExists(defaultLocalePath)) {
const content = JSON.parse(await fs.readFile(defaultLocalePath, 'utf-8'));
messages = parseI18nMessages(content);
} else {
diff --git a/packages/wxt/src/core/resolve-config.ts b/packages/wxt/src/core/resolve-config.ts
index 5e14b4e8b..574d535e7 100644
--- a/packages/wxt/src/core/resolve-config.ts
+++ b/packages/wxt/src/core/resolve-config.ts
@@ -550,7 +550,7 @@ function resolveWxtModuleDir() {
}
async function isDirMissing(dir: string) {
- return !(await fs.exists(dir));
+ return !(await fs.pathExists(dir));
}
function logMissingDir(logger: Logger, name: string, expected: string) {
diff --git a/packages/wxt/src/core/utils/fs.ts b/packages/wxt/src/core/utils/fs.ts
index ff9cdfdc5..3d3f28878 100644
--- a/packages/wxt/src/core/utils/fs.ts
+++ b/packages/wxt/src/core/utils/fs.ts
@@ -28,7 +28,7 @@ export async function writeFileIfDifferent(
* `config.publicDir`.
*/
export async function getPublicFiles(): Promise {
- if (!(await fs.exists(wxt.config.publicDir))) return [];
+ if (!(await fs.pathExists(wxt.config.publicDir))) return [];
const files = await glob('**/*', { cwd: wxt.config.publicDir });
return files.map(unnormalizePath);
From 941b5475ea15add097c5dc01569e3da6f487df10 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Thu, 25 Dec 2025 16:46:37 +0100
Subject: [PATCH 23/64] fix(packages/wxt): change HtmlPublicPath from `type` to
`const` for fix `Unresolved variable or type HtmlPublicPath` error
---
packages/wxt/e2e/tests/typescript-project.test.ts | 2 +-
packages/wxt/src/core/generate-wxt-dir.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/wxt/e2e/tests/typescript-project.test.ts b/packages/wxt/e2e/tests/typescript-project.test.ts
index 0327c38b5..330a32b19 100644
--- a/packages/wxt/e2e/tests/typescript-project.test.ts
+++ b/packages/wxt/e2e/tests/typescript-project.test.ts
@@ -53,7 +53,7 @@ describe('TypeScript Project', () => {
| "/options.html"
| "/popup.html"
| "/sandbox.html"
- type HtmlPublicPath = Extract
+ const HtmlPublicPath = Extract
export interface WxtRuntime {
getURL(path: PublicPath): string;
getURL(path: \`\${HtmlPublicPath}\${string}\`): string;
diff --git a/packages/wxt/src/core/generate-wxt-dir.ts b/packages/wxt/src/core/generate-wxt-dir.ts
index 921f1c130..65c57e744 100644
--- a/packages/wxt/src/core/generate-wxt-dir.ts
+++ b/packages/wxt/src/core/generate-wxt-dir.ts
@@ -92,7 +92,7 @@ import "wxt/browser";
declare module "wxt/browser" {
export type PublicPath =
{{ union }}
- type HtmlPublicPath = Extract
+ const HtmlPublicPath = Extract
export interface WxtRuntime {
getURL(path: PublicPath): string;
getURL(path: \`\${HtmlPublicPath}\${string}\`): string;
From 8bd9f8ff628e294b4cc1d2c2be61754ce999bff3 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Fri, 26 Dec 2025 09:56:35 +0100
Subject: [PATCH 24/64] clean(packages/wxt): simplify ValidationError checking
on cli-utils.ts
---
packages/wxt/src/cli/cli-utils.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/wxt/src/cli/cli-utils.ts b/packages/wxt/src/cli/cli-utils.ts
index 2f525ae6f..3a772c434 100644
--- a/packages/wxt/src/cli/cli-utils.ts
+++ b/packages/wxt/src/cli/cli-utils.ts
@@ -19,8 +19,7 @@ export function wrapAction(
},
) {
return async (...args: any[]) => {
- // Enable consola's debug mode globally at the start of all commands when the `--debug` flag is
- // passed
+ // Enable consola's debug mode globally at the start of all commands when the `--debug` flag is passed
const isDebug = !!args.find((arg) => arg?.debug);
if (isDebug) {
consola.level = LogLevels.debug;
@@ -40,11 +39,11 @@ export function wrapAction(
consola.fail(
`Command failed after ${formatDuration(Date.now() - startTime)}`,
);
- if (err instanceof ValidationError) {
- // Don't log these errors, they've already been logged
- } else {
+
+ if (!(err instanceof ValidationError)) {
consola.error(err);
}
+
process.exit(1);
}
};
@@ -98,6 +97,7 @@ export function createAliasedCommand(
});
aliasCommandNames.add(aliasedCommand.name);
}
+
export function isAliasedCommand(command: Command | undefined): boolean {
return !!command && aliasCommandNames.has(command.name);
}
From 5285c2eaabd48820dbcea74bfdb9db604b315885 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Fri, 26 Dec 2025 10:03:12 +0100
Subject: [PATCH 25/64] fix(packages/wxt): add missing `lang` to `html` on
devHtmlPrerender.test.ts
---
.../builders/vite/plugins/__tests__/devHtmlPrerender.test.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/wxt/src/core/builders/vite/plugins/__tests__/devHtmlPrerender.test.ts b/packages/wxt/src/core/builders/vite/plugins/__tests__/devHtmlPrerender.test.ts
index db51f3c5b..e10c12e22 100644
--- a/packages/wxt/src/core/builders/vite/plugins/__tests__/devHtmlPrerender.test.ts
+++ b/packages/wxt/src/core/builders/vite/plugins/__tests__/devHtmlPrerender.test.ts
@@ -32,7 +32,7 @@ describe('Dev HTML Prerender Plugin', () => {
// URLs should not be changed
['https://example.com/style.css', 'https://example.com/style.css'],
])('should transform "%s" into "%s"', (input, expected) => {
- const { document } = parseHTML('');
+ const { document } = parseHTML('');
const root = '/some/root';
const config = fakeResolvedConfig({
root,
From 41b7998be2c66d70101e51e573a0622bc71275c5 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Fri, 26 Dec 2025 10:09:15 +0100
Subject: [PATCH 26/64] fix(packages/wxt): change `==` to `===` check, for real
`null`, to fix possibly undefined document.head of wxtPluginLoader.ts
---
.../core/builders/vite/plugins/wxtPluginLoader.ts | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/packages/wxt/src/core/builders/vite/plugins/wxtPluginLoader.ts b/packages/wxt/src/core/builders/vite/plugins/wxtPluginLoader.ts
index c6808bac5..3e335c106 100644
--- a/packages/wxt/src/core/builders/vite/plugins/wxtPluginLoader.ts
+++ b/packages/wxt/src/core/builders/vite/plugins/wxtPluginLoader.ts
@@ -35,11 +35,11 @@ export function wxtPluginLoader(config: ResolvedConfig): vite.Plugin {
if (id === resolvedVirtualHtmlModuleId) {
return `import { initPlugins } from '${virtualModuleId}';
-try {
- initPlugins();
-} catch (err) {
- console.error("[wxt] Failed to initialize plugins", err);
-}`;
+ try {
+ initPlugins();
+ } catch (err) {
+ console.error("[wxt] Failed to initialize plugins", err);
+ }`;
}
},
transformIndexHtml: {
@@ -59,10 +59,11 @@ try {
script.type = 'module';
script.src = src;
- if (document.head == null) {
+ if (document.head === null) {
const newHead = document.createElement('head');
document.documentElement.prepend(newHead);
}
+
document.head.prepend(script);
return document.toString();
},
From b1547beb096c9de0301c98ec837cafa703ed951f Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Fri, 26 Dec 2025 10:13:23 +0100
Subject: [PATCH 27/64] fix(packages/wxt): change deprecated `exists` to
`pathExists` of `fs-extra`
---
.../wxt/src/core/builders/vite/plugins/resolveAppConfig.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/wxt/src/core/builders/vite/plugins/resolveAppConfig.ts b/packages/wxt/src/core/builders/vite/plugins/resolveAppConfig.ts
index e2b4e670e..a76c2c3d9 100644
--- a/packages/wxt/src/core/builders/vite/plugins/resolveAppConfig.ts
+++ b/packages/wxt/src/core/builders/vite/plugins/resolveAppConfig.ts
@@ -1,4 +1,4 @@
-import { exists } from 'fs-extra';
+import { pathExists } from 'fs-extra';
import { resolve } from 'node:path';
import type * as vite from 'vite';
import { ResolvedConfig } from '../../../../types';
@@ -25,7 +25,7 @@ export function resolveAppConfig(config: ResolvedConfig): vite.Plugin {
async resolveId(id) {
if (id !== virtualModuleId) return;
- return (await exists(appConfigFile))
+ return (await pathExists(appConfigFile))
? appConfigFile
: resolvedVirtualModuleId;
},
From 2df5305c7046ac8c626fd838ed93ede2a2f571f0 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Fri, 26 Dec 2025 10:17:18 +0100
Subject: [PATCH 28/64] fix(packages/wxt): add missing await for
removeEmptyDirs of builders/vite/index.ts
---
packages/wxt/src/core/builders/vite/index.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/wxt/src/core/builders/vite/index.ts b/packages/wxt/src/core/builders/vite/index.ts
index c798749da..78c071faf 100644
--- a/packages/wxt/src/core/builders/vite/index.ts
+++ b/packages/wxt/src/core/builders/vite/index.ts
@@ -453,7 +453,7 @@ async function moveHtmlFiles(
);
// TODO: Optimize and only delete old path directories
- removeEmptyDirs(config.outDir);
+ await removeEmptyDirs(config.outDir);
return movedChunks;
}
@@ -463,9 +463,11 @@ async function moveHtmlFiles(
*/
export async function removeEmptyDirs(dir: string): Promise {
const files = await fs.readdir(dir);
+
for (const file of files) {
const filePath = join(dir, file);
const stats = await fs.stat(filePath);
+
if (stats.isDirectory()) {
await removeEmptyDirs(filePath);
}
From db0df10fd598b5a322bf85a86d4278756e9fceed Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Fri, 26 Dec 2025 10:17:51 +0100
Subject: [PATCH 29/64] clean(packages/wxt): improve readability of plugins
---
.../src/core/builders/vite/plugins/entrypointGroupGlobals.ts | 1 +
packages/wxt/src/core/builders/vite/plugins/extensionApiMock.ts | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/wxt/src/core/builders/vite/plugins/entrypointGroupGlobals.ts b/packages/wxt/src/core/builders/vite/plugins/entrypointGroupGlobals.ts
index a9e3e1aea..b203fe1ad 100644
--- a/packages/wxt/src/core/builders/vite/plugins/entrypointGroupGlobals.ts
+++ b/packages/wxt/src/core/builders/vite/plugins/entrypointGroupGlobals.ts
@@ -13,6 +13,7 @@ export function entrypointGroupGlobals(
config() {
const define: vite.InlineConfig['define'] = {};
let name = Array.isArray(entrypointGroup) ? 'html' : entrypointGroup.name;
+
for (const global of getEntrypointGlobals(name)) {
define[`import.meta.env.${global.name}`] = JSON.stringify(global.value);
}
diff --git a/packages/wxt/src/core/builders/vite/plugins/extensionApiMock.ts b/packages/wxt/src/core/builders/vite/plugins/extensionApiMock.ts
index ee46f4ef1..ef9cb0d7b 100644
--- a/packages/wxt/src/core/builders/vite/plugins/extensionApiMock.ts
+++ b/packages/wxt/src/core/builders/vite/plugins/extensionApiMock.ts
@@ -27,7 +27,7 @@ export function extensionApiMock(config: ResolvedConfig): vite.PluginOption {
],
},
ssr: {
- // Inline all WXT modules subdependencies can be mocked
+ // Inline all WXT modules sub dependencies can be mocked
noExternal: ['wxt'],
},
};
From d7fc6c2a3526198b543a5d02a03a40c8d9ab3591 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Fri, 26 Dec 2025 10:21:30 +0100
Subject: [PATCH 30/64] fix(packages/wxt): change deprecated `exists` to
`pathExists` on npm.test.ts
---
packages/wxt/src/core/package-managers/__tests__/npm.test.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/wxt/src/core/package-managers/__tests__/npm.test.ts b/packages/wxt/src/core/package-managers/__tests__/npm.test.ts
index de2e8e2ae..6117d946f 100644
--- a/packages/wxt/src/core/package-managers/__tests__/npm.test.ts
+++ b/packages/wxt/src/core/package-managers/__tests__/npm.test.ts
@@ -2,7 +2,7 @@ import { beforeAll, describe, expect, it } from 'vitest';
import path from 'node:path';
import { npm } from '../npm';
import spawn from 'nano-spawn';
-import { exists } from 'fs-extra';
+import { pathExists } from 'fs-extra';
describe('NPM Package Management Utils', () => {
describe('listDependencies', () => {
@@ -41,7 +41,7 @@ describe('NPM Package Management Utils', () => {
const actual = await npm.downloadDependency(id, downloadDir);
expect(actual).toEqual(expected);
- expect(await exists(actual)).toBe(true);
+ expect(await pathExists(actual)).toBe(true);
});
});
});
From c676a0cc71eb6dff76193494eb581d537de852db Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Fri, 26 Dec 2025 10:25:43 +0100
Subject: [PATCH 31/64] clean(packages/wxt): improve readability of code for
core/package-managers
---
packages/wxt/src/core/package-managers/npm.ts | 8 ++++++++
packages/wxt/src/core/package-managers/pnpm.ts | 2 ++
packages/wxt/src/core/package-managers/yarn.ts | 5 +++++
3 files changed, 15 insertions(+)
diff --git a/packages/wxt/src/core/package-managers/npm.ts b/packages/wxt/src/core/package-managers/npm.ts
index 7ad5386f8..1b1989205 100644
--- a/packages/wxt/src/core/package-managers/npm.ts
+++ b/packages/wxt/src/core/package-managers/npm.ts
@@ -12,13 +12,16 @@ export const npm: WxtPackageManagerImpl = {
cwd: downloadDir,
});
const packed: PackedDependency[] = JSON.parse(res.stdout);
+
return path.resolve(downloadDir, packed[0].filename);
},
async listDependencies(options) {
const args = ['ls', '--json'];
+
if (options?.all) {
args.push('--depth', 'Infinity');
}
+
const res = await spawn('npm', args, { cwd: options?.cwd });
const project: NpmListProject = JSON.parse(res.stdout);
@@ -30,12 +33,15 @@ export function flattenNpmListOutput(projects: NpmListProject[]): Dependency[] {
const queue: Record[] = projects.flatMap(
(project) => {
const acc: Record[] = [];
+
if (project.dependencies) acc.push(project.dependencies);
if (project.devDependencies) acc.push(project.devDependencies);
+
return acc;
},
);
const dependencies: Dependency[] = [];
+
while (queue.length > 0) {
Object.entries(queue.pop()!).forEach(([name, meta]) => {
dependencies.push({
@@ -46,11 +52,13 @@ export function flattenNpmListOutput(projects: NpmListProject[]): Dependency[] {
if (meta.devDependencies) queue.push(meta.devDependencies);
});
}
+
return dedupeDependencies(dependencies);
}
export function dedupeDependencies(dependencies: Dependency[]): Dependency[] {
const hashes = new Set();
+
return dependencies.filter((dep) => {
const hash = `${dep.name}@${dep.version}`;
if (hashes.has(hash)) {
diff --git a/packages/wxt/src/core/package-managers/pnpm.ts b/packages/wxt/src/core/package-managers/pnpm.ts
index 32d89b609..ed6a023ef 100644
--- a/packages/wxt/src/core/package-managers/pnpm.ts
+++ b/packages/wxt/src/core/package-managers/pnpm.ts
@@ -9,6 +9,7 @@ export const pnpm: WxtPackageManagerImpl = {
},
async listDependencies(options) {
const args = ['ls', '-r', '--json'];
+
if (options?.all) {
args.push('--depth', 'Infinity');
}
@@ -20,6 +21,7 @@ export const pnpm: WxtPackageManagerImpl = {
) {
args.push('--ignore-workspace');
}
+
const res = await spawn('pnpm', args, { cwd: options?.cwd });
const projects: NpmListProject[] = JSON.parse(res.stdout);
diff --git a/packages/wxt/src/core/package-managers/yarn.ts b/packages/wxt/src/core/package-managers/yarn.ts
index 8b5449329..b2d85101d 100644
--- a/packages/wxt/src/core/package-managers/yarn.ts
+++ b/packages/wxt/src/core/package-managers/yarn.ts
@@ -10,14 +10,17 @@ export const yarn: WxtPackageManagerImpl = {
},
async listDependencies(options) {
const args = ['list', '--json'];
+
if (options?.all) {
args.push('--depth', 'Infinity');
}
+
const res = await spawn('yarn', args, { cwd: options?.cwd });
const tree = res.stdout
.split('\n')
.map((line) => JSON.parse(line))
.find((line) => line.type === 'tree')?.data as JsonLineTree | undefined;
+
if (tree == null) throw Error("'yarn list --json' did not output a tree");
const queue = [...tree.trees];
@@ -26,10 +29,12 @@ export const yarn: WxtPackageManagerImpl = {
while (queue.length > 0) {
const { name: treeName, children } = queue.pop()!;
const match = /(@?\S+)@(\S+)$/.exec(treeName);
+
if (match) {
const [_, name, version] = match;
dependencies.push({ name, version });
}
+
if (children != null) {
queue.push(...children);
}
From 9fac3fc67289b43ed6b049f16ed408e765d44057 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak
Date: Fri, 26 Dec 2025 10:27:04 +0100
Subject: [PATCH 32/64] fix(packages/wxt): add question for core/runners
manual.ts
---
packages/wxt/src/core/runners/manual.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/wxt/src/core/runners/manual.ts b/packages/wxt/src/core/runners/manual.ts
index 8dfee1a55..fb5773096 100644
--- a/packages/wxt/src/core/runners/manual.ts
+++ b/packages/wxt/src/core/runners/manual.ts
@@ -16,6 +16,7 @@ export function createManualRunner(): ExtensionRunner {
);
},
async closeBrowser() {
+ // TODO: THIS ISN'T USED ANYWHERE, MAYBE REMOVE?
// noop
},
};
From 780ff175b60d8eea22a19e3cf65fcd5389bd1b07 Mon Sep 17 00:00:00 2001
From: PatrykKuniczak