diff --git a/e2e/tests/user-config.test.ts b/e2e/tests/user-config.test.ts index 409bce0f..c8275888 100644 --- a/e2e/tests/user-config.test.ts +++ b/e2e/tests/user-config.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from 'vitest'; import { TestProject } from '../utils'; +import { InlineConfig } from '~/types'; describe('User Config', () => { // Root directory is tested with all tests. @@ -85,4 +86,24 @@ describe('User Config', () => { {\\"manifest_version\\":3,\\"name\\":\\"E2E Extension\\",\\"description\\":\\"Example description\\",\\"version\\":\\"0.0.0\\",\\"example_customization\\":[\\"production\\",\\"chrome\\",\\"3\\",\\"build\\"]}" `); }); + + it('should exclude the polyfill when the experimental setting is set to false', async () => { + const buildBackground = async (config?: InlineConfig) => { + const background = `export default defineBackground(() => console.log(browser.runtime.id));`; + const projectWithPolyfill = new TestProject(); + projectWithPolyfill.addFile('entrypoints/background.ts', background); + await projectWithPolyfill.build(config); + return await projectWithPolyfill.serializeFile( + '.output/chrome-mv3/background.js', + ); + }; + + const withPolyfill = await buildBackground(); + const withoutPolyfill = await buildBackground({ + experimental: { + includeBrowserPolyfill: false, + }, + }); + expect(withoutPolyfill).not.toBe(withPolyfill); + }); }); diff --git a/src/core/utils/building/get-internal-config.ts b/src/core/utils/building/get-internal-config.ts index f704a5d7..f82d91de 100644 --- a/src/core/utils/building/get-internal-config.ts +++ b/src/core/utils/building/get-internal-config.ts @@ -123,6 +123,10 @@ export async function getInternalConfig( }, userConfigMetadata: userConfigMetadata ?? {}, alias, + experimental: { + includeBrowserPolyfill: + mergedConfig.experimental?.includeBrowserPolyfill ?? true, + }, }; finalConfig.vite = (env) => @@ -202,6 +206,10 @@ function mergeInlineConfig( ...userConfig.alias, ...inlineConfig.alias, }, + experimental: { + ...userConfig.experimental, + ...inlineConfig.experimental, + }, }; } @@ -267,5 +275,7 @@ async function resolveInternalViteConfig( } internalVite.plugins.push(plugins.globals(finalConfig)); + internalVite.plugins.push(plugins.excludeBrowserPolyfill(finalConfig)); + return internalVite; } diff --git a/src/core/utils/testing/fake-objects.ts b/src/core/utils/testing/fake-objects.ts index 4c0b4d20..1548a9f6 100644 --- a/src/core/utils/testing/fake-objects.ts +++ b/src/core/utils/testing/fake-objects.ts @@ -245,5 +245,8 @@ export const fakeInternalConfig = fakeObjectCreator(() => { transformManifest: () => {}, userConfigMetadata: {}, alias: {}, + experimental: { + includeBrowserPolyfill: true, + }, }; }); diff --git a/src/core/vite-plugins/excludeBrowserPolyfill.ts b/src/core/vite-plugins/excludeBrowserPolyfill.ts new file mode 100644 index 00000000..d6bf9a94 --- /dev/null +++ b/src/core/vite-plugins/excludeBrowserPolyfill.ts @@ -0,0 +1,33 @@ +import { InternalConfig } from '~/types'; +import * as vite from 'vite'; + +/** + * Apply the experimental config for disabling the polyfill. It works by aliasing the + * `webextension-polyfill` module to a virtual module and exporting the `chrome` global from the + * virtual module. + */ +export function excludeBrowserPolyfill(config: InternalConfig): vite.Plugin { + const virtualId = 'virtual:wxt-webextension-polyfill-disabled'; + + return { + name: 'wxt:exclude-browser-polyfill', + config() { + // Only apply the config if we're disabling the polyfill + if (config.experimental.includeBrowserPolyfill) return; + + return { + resolve: { + alias: { + 'webextension-polyfill': virtualId, + }, + }, + }; + }, + load(id) { + if (id === virtualId) { + // Use chrome instead of the polyfill when disabled. + return 'export default chrome'; + } + }, + }; +} diff --git a/src/core/vite-plugins/index.ts b/src/core/vite-plugins/index.ts index 24b486bd..da49d5f5 100644 --- a/src/core/vite-plugins/index.ts +++ b/src/core/vite-plugins/index.ts @@ -11,3 +11,4 @@ export * from './bundleAnalysis'; export * from './globals'; export * from './webextensionPolyfillAlias'; export * from './webextensionPolyfillInlineDeps'; +export * from './excludeBrowserPolyfill'; diff --git a/src/types/external.ts b/src/types/external.ts index 334b8c63..32494e45 100644 --- a/src/types/external.ts +++ b/src/types/external.ts @@ -206,6 +206,31 @@ export interface InlineConfig { * } */ alias?: Record; + /** + * Experimental settings - use with caution. + */ + experimental?: { + /** + * Whether to use [`webextension-polyfill`](https://www.npmjs.com/package/webextension-polyfill) + * when importing `browser` from `wxt/browser`. + * + * 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 + * } + * }) + */ + includeBrowserPolyfill?: boolean; + }; } export interface WxtInlineViteConfig diff --git a/src/types/internal.ts b/src/types/internal.ts index ecda6346..d5caaf07 100644 --- a/src/types/internal.ts +++ b/src/types/internal.ts @@ -53,6 +53,9 @@ export interface InternalConfig { * Import aliases to absolute paths. */ alias: Record; + experimental: { + includeBrowserPolyfill: boolean; + }; } export interface FsCache {