Skip to content

feat(experimental): First-class support for excluding webextension-polyfill #847

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jul 24, 2024
Merged
1 change: 1 addition & 0 deletions packages/wxt-demo/wxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default defineConfig({
},
experimental: {
entrypointImporter: 'vite-node',
extensionApi: 'chrome',
},
runner: {
startUrls: ['https://duckduckgo.com'],
Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/build.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default defineBuildConfig([
{
builder: 'mkdist',
input: 'src',
pattern: ['**/*', '!**/__tests__', '!**/*.md', '!virtual'],
pattern: ['**/*', '!**/__tests__', '!**/*.md', '!virtual', '!@types'],
declaration: true,
},
],
Expand Down
8 changes: 4 additions & 4 deletions packages/wxt/e2e/tests/modules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ describe('Module Helpers', () => {
await project.build({
experimental: {
// reduce build output when comparing test failures
includeBrowserPolyfill: false,
extensionApi: 'chrome',
},
});

Expand All @@ -216,7 +216,7 @@ describe('Module Helpers', () => {
await project.build({
experimental: {
// reduce build output when comparing test failures
includeBrowserPolyfill: false,
extensionApi: 'chrome',
},
});

Expand All @@ -239,7 +239,7 @@ describe('Module Helpers', () => {
await project.build({
experimental: {
// reduce build output when comparing test failures
includeBrowserPolyfill: false,
extensionApi: 'chrome',
},
});

Expand All @@ -257,7 +257,7 @@ describe('Module Helpers', () => {
await project.build({
experimental: {
// reduce build output when comparing test failures
includeBrowserPolyfill: false,
extensionApi: 'chrome',
},
});

Expand Down
4 changes: 2 additions & 2 deletions packages/wxt/e2e/tests/output-structure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@
await project.build({
experimental: {
// Simplify the build output for comparison
includeBrowserPolyfill: false,
extensionApi: 'chrome',
},
vite: () => ({
build: {
Expand All @@ -275,7 +275,7 @@
});

expect(await project.serializeFile('.output/chrome-mv3/background.js'))
.toMatchInlineSnapshot(`

Check failure on line 278 in packages/wxt/e2e/tests/output-structure.test.ts

View workflow job for this annotation

GitHub Actions / tests

e2e/tests/output-structure.test.ts > Output Directory Structure > should generate ESM background script when type=module

Error: Snapshot `Output Directory Structure > should generate ESM background script when type=module 1` mismatched - Expected + Received @@ -1,7 +1,8 @@ ".output/chrome-mv3/background.js ---------------------------------------- + var _a, _b; import { l as logHello, i as initPlugins } from "./chunks/_virtual_wxt-plugins-OjKtWpmY.js"; function defineBackground(arg) { if (typeof arg === "function") return { main: arg }; return arg; } @@ -9,11 +10,15 @@ type: "module", main() { logHello("background"); } }); - chrome; + // @ts-expect-error + ((_b = (_a = globalThis.browser) == null ? void 0 : _a.runtime) == null ? void 0 : _b.id) == null ? globalThis.chrome : ( + // @ts-expect-error + globalThis.browser + ); function print(method, ...args) { return; } const logger = { debug: (...args) => print(console.debug, ...args), ❯ e2e/tests/output-structure.test.ts:278:8

Check failure on line 278 in packages/wxt/e2e/tests/output-structure.test.ts

View workflow job for this annotation

GitHub Actions / windows-tests

e2e/tests/output-structure.test.ts > Output Directory Structure > should generate ESM background script when type=module

Error: Snapshot `Output Directory Structure > should generate ESM background script when type=module 1` mismatched - Expected + Received @@ -1,7 +1,8 @@ ".output/chrome-mv3/background.js ---------------------------------------- + var _a, _b; import { l as logHello, i as initPlugins } from "./chunks/_virtual_wxt-plugins-OjKtWpmY.js"; function defineBackground(arg) { if (typeof arg === "function") return { main: arg }; return arg; } @@ -9,11 +10,15 @@ type: "module", main() { logHello("background"); } }); - chrome; + // @ts-expect-error + ((_b = (_a = globalThis.browser) == null ? void 0 : _a.runtime) == null ? void 0 : _b.id) == null ? globalThis.chrome : ( + // @ts-expect-error + globalThis.browser + ); function print(method, ...args) { return; } const logger = { debug: (...args) => print(console.debug, ...args), ❯ e2e/tests/output-structure.test.ts:278:8
".output/chrome-mv3/background.js
----------------------------------------
import { l as logHello, i as initPlugins } from "./chunks/_virtual_wxt-plugins-OjKtWpmY.js";
Expand Down Expand Up @@ -345,7 +345,7 @@
await project.build({
experimental: {
// Simplify the build output for comparison
includeBrowserPolyfill: false,
extensionApi: 'chrome',
},
vite: () => ({
build: {
Expand All @@ -356,7 +356,7 @@
});

expect(await project.serializeFile('.output/chrome-mv3/background.js'))
.toMatchInlineSnapshot(`

Check failure on line 359 in packages/wxt/e2e/tests/output-structure.test.ts

View workflow job for this annotation

GitHub Actions / tests

e2e/tests/output-structure.test.ts > Output Directory Structure > should generate IIFE background script when type=undefined

Error: Snapshot `Output Directory Structure > should generate IIFE background script when type=undefined 1` mismatched - Expected + Received @@ -1,9 +1,10 @@ ".output/chrome-mv3/background.js ---------------------------------------- var _background = function() { "use strict"; + var _a, _b; function defineBackground(arg) { if (typeof arg === "function") return { main: arg }; return arg; } function logHello(name) { @@ -16,11 +17,15 @@ } }); _background; function initPlugins() { } - chrome; + // @ts-expect-error + ((_b = (_a = globalThis.browser) == null ? void 0 : _a.runtime) == null ? void 0 : _b.id) == null ? globalThis.chrome : ( + // @ts-expect-error + globalThis.browser + ); function print(method, ...args) { return; } const logger = { debug: (...args) => print(console.debug, ...args), ❯ e2e/tests/output-structure.test.ts:359:8

Check failure on line 359 in packages/wxt/e2e/tests/output-structure.test.ts

View workflow job for this annotation

GitHub Actions / windows-tests

e2e/tests/output-structure.test.ts > Output Directory Structure > should generate IIFE background script when type=undefined

Error: Snapshot `Output Directory Structure > should generate IIFE background script when type=undefined 1` mismatched - Expected + Received @@ -1,9 +1,10 @@ ".output/chrome-mv3/background.js ---------------------------------------- var _background = function() { "use strict"; + var _a, _b; function defineBackground(arg) { if (typeof arg === "function") return { main: arg }; return arg; } function logHello(name) { @@ -16,11 +17,15 @@ } }); _background; function initPlugins() { } - chrome; + // @ts-expect-error + ((_b = (_a = globalThis.browser) == null ? void 0 : _a.runtime) == null ? void 0 : _b.id) == null ? globalThis.chrome : ( + // @ts-expect-error + globalThis.browser + ); function print(method, ...args) { return; } const logger = { debug: (...args) => print(console.debug, ...args), ❯ e2e/tests/output-structure.test.ts:359:8
".output/chrome-mv3/background.js
----------------------------------------
var _background = function() {
Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/e2e/tests/user-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ describe('User Config', () => {
const withPolyfill = await buildBackground();
const withoutPolyfill = await buildBackground({
experimental: {
includeBrowserPolyfill: false,
extensionApi: 'chrome',
},
});
expect(withoutPolyfill).not.toBe(withPolyfill);
Expand Down
21 changes: 19 additions & 2 deletions packages/wxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,16 @@
"default": "./dist/sandbox/index.mjs"
},
"./browser": {
"types": "./dist/browser.d.ts",
"default": "./dist/browser.mjs"
"types": "./dist/browser/index.d.ts",
"default": "./dist/browser/index.mjs"
},
"./browser/chrome": {
"types": "./dist/browser/chrome.d.ts",
"import": "./dist/browser/chrome.mjs"
},
"./browser/webextension-polyfill": {
"types": "./dist/browser/webextension-polyfill.d.ts",
"import": "./dist/browser/webextension-polyfill.mjs"
},
"./testing": {
"types": "./dist/testing/index.d.ts",
Expand Down Expand Up @@ -121,6 +129,7 @@
"devDependencies": {
"@aklinker1/check": "^1.3.1",
"@faker-js/faker": "^8.4.1",
"@types/chrome": "^0.0.268",
"@types/fs-extra": "^11.0.4",
"@types/lodash.merge": "^4.6.9",
"@types/natural-compare": "^1.4.3",
Expand All @@ -138,5 +147,13 @@
"unbuild": "^2.0.0",
"vitest": "^2.0.3",
"vitest-plugin-random-seed": "^1.1.0"
},
"peerDependencies": {
"@types/chrome": "*"
},
"peerDependenciesMeta": {
"@types/chrome": {
"optional": true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,3 @@ declare module 'web-ext-run/util/logger' {
}
export const consoleStream: IConsoleStream;
}

declare module 'wxt/browser' {
// Overridden when types are generated per project
export type PublicPath = string;
}
2 changes: 1 addition & 1 deletion packages/wxt/src/__tests__/storage.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fakeBrowser } from '@webext-core/fake-browser';
import { describe, it, expect, beforeEach, vi, expectTypeOf } from 'vitest';
import { browser } from '../browser';
import { browser } from 'wxt/browser';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we're using mkdist now, we can import from wxt/browser and it will be resolved from the NPM module instead of using the local file, and this will be replaced with wxt/browser/chrome when using extensionApi: "chrome"

import { WxtStorageItem, storage } from '../storage';

/**
Expand Down
23 changes: 23 additions & 0 deletions packages/wxt/src/browser/chrome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference types="chrome" />

export type Browser = typeof chrome;

export type AugmentedBrowser = Browser & {
runtime: WxtRuntime;
i18n: WxtI18n;
};

export interface WxtRuntime {
// Overriden per-project
}

export interface WxtI18n {
// Overriden per-project
}

export const browser: AugmentedBrowser =
// @ts-expect-error
globalThis.browser?.runtime?.id == null
? globalThis.chrome
: // @ts-expect-error
globalThis.browser;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use browser instead of chrome if it exists. Both Firefox and Safari provide a browser global, so browser will be used by default for those two browsers.

For firefox MV2, browser supports the promise API whereas chrome doesn't (see comment), meaning this is necessary for Firefox.

For Safari, I'm not 100% sure if there's a difference, so I'll need feedback from people with safari extensions to see if it's the right move.

4 changes: 4 additions & 0 deletions packages/wxt/src/browser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* @module wxt/browser
*/
export * from './webextension-polyfill';
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
/**
* @module wxt/browser
*/
import originalBrowser, { Browser, Runtime, I18n } from 'webextension-polyfill';

export interface AugmentedBrowser extends Browser {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ContentScriptDefinition } from '../../types';
import { browser } from '../../browser';
import { browser } from 'wxt/browser';
import { logger } from '../../sandbox/utils/logger';
import { WxtLocationChangeEvent, getUniqueEventName } from './custom-events';
import { createLocationWatcher } from './location-watcher';
Expand Down
4 changes: 2 additions & 2 deletions packages/wxt/src/client/content-scripts/custom-events.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { browser } from '../../browser';
import { browser } from 'wxt/browser';

export class WxtLocationChangeEvent extends Event {
static EVENT_NAME = getUniqueEventName('wxt:locationchange');
Expand All @@ -22,5 +22,5 @@ export function getUniqueEventName(eventName: string): string {
? 'build'
: import.meta.env.ENTRYPOINT;

return `${browser.runtime.id}:${entrypointName}:${eventName}`;
return `${browser?.runtime?.id}:${entrypointName}:${eventName}`;
}
2 changes: 1 addition & 1 deletion packages/wxt/src/client/content-scripts/ui/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { browser } from '../../../browser';
import { browser } from 'wxt/browser';
import { ContentScriptContext } from '..';
import {
ContentScriptAnchoredOptions,
Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/src/core/builders/vite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export async function createViteBuilder(
wxtPlugins.tsconfigPaths(wxtConfig),
wxtPlugins.noopBackground(),
wxtPlugins.globals(wxtConfig),
wxtPlugins.excludeBrowserPolyfill(wxtConfig),
wxtPlugins.resolveExtensionApi(wxtConfig),
wxtPlugins.defineImportMeta(),
wxtPlugins.wxtPluginLoader(wxtConfig),
wxtPlugins.resolveAppConfig(wxtConfig),
Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion packages/wxt/src/core/builders/vite/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export * from './cssEntrypoints';
export * from './bundleAnalysis';
export * from './globals';
export * from './webextensionPolyfillMock';
export * from './excludeBrowserPolyfill';
export * from './resolveExtensionApi';
export * from './entrypointGroupGlobals';
export * from './defineImportMeta';
export * from './removeEntrypointMainFunction';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ResolvedConfig } from '../../../../types';
import type * as vite from 'vite';

/**
* Apply the experimental config for which extension API is used. This only
* effects the extension API included at RUNTIME - during development, types
* depend on the import.
*
* NOTE: this only works if we import `wxt/browser` instead of using the relative path.
Copy link
Collaborator Author

@aklinker1 aklinker1 Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Add eslint rule to enforce this.

*/
export function resolveExtensionApi(config: ResolvedConfig): vite.Plugin {
return {
name: 'wxt:resolve-extension-api',
config() {
// Only apply the config if we're disabling the polyfill
if (config.experimental.extensionApi === 'webextension-polyfill') return;

return {
resolve: {
alias: [
{ find: /^wxt\/browser$/, replacement: 'wxt/browser/chrome' },
],
},
};
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export function webextensionPolyfillMock(
config.wxtModuleDir,
'dist/virtual/mock-browser',
),
'wxt/browser': 'wxt/testing',
'wxt/browser/chrome': 'wxt/testing',
},
},
ssr: {
Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/src/core/utils/building/rebuild.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Manifest } from '../../../browser';
import type { Manifest } from 'wxt/browser';
import { BuildOutput, Entrypoint, EntrypointGroup } from '../../../types';
import { generateTypesDir } from './generate-wxt-dir';
import { buildEntrypoints } from './build-entrypoints';
Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/src/core/utils/building/resolve-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export async function resolveConfig(
userConfigMetadata: userConfigMetadata ?? {},
alias,
experimental: defu(mergedConfig.experimental, {
includeBrowserPolyfill: true,
extensionApi: 'webextension-polyfill' as const,
entrypointImporter: 'jiti' as const,
}),
dev: {
Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/src/core/utils/content-scripts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Manifest, Scripting } from '../../browser';
import type { Manifest, Scripting } from 'wxt/browser';
import { ContentScriptEntrypoint, ResolvedConfig } from '../../types';
import { getEntrypointBundlePath } from './entrypoints';

Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/src/core/utils/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Manifest } from '../../browser';
import type { Manifest } from 'wxt/browser';
import {
Entrypoint,
BackgroundEntrypoint,
Expand Down
4 changes: 2 additions & 2 deletions packages/wxt/src/core/utils/testing/fake-objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { resolve } from 'path';
import { faker } from '@faker-js/faker';
import merge from 'lodash.merge';
import { Commands, type Manifest } from '../../../browser';
import { Commands, type Manifest } from 'wxt/browser';
import {
FsCache,
ResolvedConfig,
Expand Down Expand Up @@ -297,7 +297,7 @@ export const fakeResolvedConfig = fakeObjectCreator<ResolvedConfig>(() => {
userConfigMetadata: {},
alias: {},
experimental: {
includeBrowserPolyfill: true,
extensionApi: 'webextension-polyfill',
entrypointImporter: 'jiti',
},
dev: {
Expand Down
2 changes: 1 addition & 1 deletion packages/wxt/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*
* @module wxt/storage
*/
import { Storage, browser } from './browser';
import { Storage, browser } from 'wxt/browser';
import { dequal } from 'dequal/lite';
import { logger } from './sandbox/utils/logger';
import { toArray } from './core/utils/arrays';
Expand Down
27 changes: 7 additions & 20 deletions packages/wxt/src/types/index.ts → packages/wxt/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type * as vite from 'vite';
import type { Manifest, Scripting } from '../browser';
import type { Manifest, Scripting } from 'wxt/browser';
import { UnimportOptions, Import } from 'unimport';
import { LogLevel } from 'consola';
import { ContentScriptContext } from '../client/content-scripts/content-script-context';
import type { ContentScriptContext } from './client/content-scripts/content-script-context';
import type { PluginVisualizerOptions } from '@aklinker1/rollup-plugin-visualizer';
import type { FSWatcher } from 'chokidar';
import { ResolvedConfig as C12ResolvedConfig } from 'c12';
Expand Down Expand Up @@ -297,25 +297,12 @@ export interface InlineConfig {
*/
experimental?: {
/**
* Whether to use [`webextension-polyfill`](https://www.npmjs.com/package/webextension-polyfill)
* when importing `browser` from `wxt/browser`.
* Which extension API to use. To switch to `"chrome"`, make sure to
* install the `@types/chrome` package.
*
* When set to `false`, WXT will export the chrome global instead of the polyfill from
* `wxt/browser`.
*
* You should use `browser` to access the web extension APIs.
*
* @experimental This option will remain experimental until Manifest V2 is dead.
*
* @default true
* @example
* export default defineConfig({
* experimental: {
* includeBrowserPolyfill: false
* }
* })
* @default "webextension-polyfill"
*/
includeBrowserPolyfill?: boolean;
extensionApi?: 'webextension-polyfill' | 'chrome';
/**
* Method used to import entrypoint files during the build process to extract their options.
*
Expand Down Expand Up @@ -1203,7 +1190,7 @@ export interface ResolvedConfig {
*/
alias: Record<string, string>;
experimental: {
includeBrowserPolyfill: boolean;
extensionApi: 'webextension-polyfill' | 'chrome';
entrypointImporter: 'jiti' | 'vite-runtime' | 'vite-node';
};
dev: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { fakeBrowser } from 'wxt/testing';

export const browser = fakeBrowser;
Loading
Loading