From 97a4fb99a649e1605c0f44dca0dc901965a7a2df Mon Sep 17 00:00:00 2001 From: Jonathan Viney Date: Tue, 16 Sep 2025 21:11:51 +1200 Subject: [PATCH] feat: Add iifeName option. --- .../wxt/e2e/tests/output-structure.test.ts | 63 +++++++++++++++++++ packages/wxt/src/core/builders/vite/index.ts | 3 +- packages/wxt/src/core/resolve-config.ts | 2 + .../src/core/utils/testing/fake-objects.ts | 1 + packages/wxt/src/types.ts | 6 ++ 5 files changed, 73 insertions(+), 2 deletions(-) diff --git a/packages/wxt/e2e/tests/output-structure.test.ts b/packages/wxt/e2e/tests/output-structure.test.ts index 79d6e8487..36c8adf1e 100644 --- a/packages/wxt/e2e/tests/output-structure.test.ts +++ b/packages/wxt/e2e/tests/output-structure.test.ts @@ -383,6 +383,69 @@ describe('Output Directory Structure', () => { `); }); + it('should support a custom IIFE name', async () => { + const project = new TestProject(); + project.addFile( + 'entrypoints/background.js', + `export default defineBackground(() => {});`, + ); + + await project.build({ + iifeName: (entryPointName) => `custom_iife_name_${entryPointName}`, + vite: () => ({ + build: { + // Make output for snapshot readable + minify: false, + }, + }), + }); + + const contents = await project.serializeFile( + '.output/chrome-mv3/background.js', + ); + expect(contents).toMatchInlineSnapshot(` + ".output/chrome-mv3/background.js + ---------------------------------------- + var custom_iife_name_background = (function() { + "use strict"; + function defineBackground(arg) { + if (arg == null || typeof arg === "function") return { main: arg }; + return arg; + } + const definition = defineBackground(() => { + }); + function initPlugins() { + } + globalThis.browser?.runtime?.id ? globalThis.browser : globalThis.chrome; + function print(method, ...args) { + return; + } + const logger = { + debug: (...args) => print(console.debug, ...args), + log: (...args) => print(console.log, ...args), + warn: (...args) => print(console.warn, ...args), + error: (...args) => print(console.error, ...args) + }; + let result; + try { + initPlugins(); + result = definition.main(); + if (result instanceof Promise) { + console.warn( + "The background's main() function return a promise, but it must be synchronous" + ); + } + } catch (err) { + logger.error("The background crashed on startup!"); + throw err; + } + const result$1 = result; + return result$1; + })(); + " + `); + }); + it('should generate IIFE background script when type=undefined', async () => { const project = new TestProject(); project.addFile( diff --git a/packages/wxt/src/core/builders/vite/index.ts b/packages/wxt/src/core/builders/vite/index.ts index c798749da..30ef5e0cd 100644 --- a/packages/wxt/src/core/builders/vite/index.ts +++ b/packages/wxt/src/core/builders/vite/index.ts @@ -20,7 +20,6 @@ import { } from '../../utils/virtual-modules'; import { Hookable } from 'hookable'; import { toArray } from '../../utils/arrays'; -import { safeVarName } from '../../utils/strings'; import { ViteNodeServer } from 'vite-node/server'; import { ViteNodeRunner } from 'vite-node/client'; import { installSourcemapsSupport } from 'vite-node/source-map'; @@ -109,7 +108,7 @@ export async function createViteBuilder( const plugins: NonNullable = [ wxtPlugins.entrypointGroupGlobals(entrypoint), ]; - const iifeReturnValueName = safeVarName(entrypoint.name); + const iifeReturnValueName = wxtConfig.iifeName(entrypoint.name); if ( entrypoint.type === 'content-script-style' || diff --git a/packages/wxt/src/core/resolve-config.ts b/packages/wxt/src/core/resolve-config.ts index 001884f90..8ba675089 100644 --- a/packages/wxt/src/core/resolve-config.ts +++ b/packages/wxt/src/core/resolve-config.ts @@ -29,6 +29,7 @@ import { safeStringToNumber } from './utils/number'; import { loadEnv } from './utils/env'; import { getPort } from 'get-port-please'; import { fileURLToPath, pathToFileURL } from 'node:url'; +import { safeVarName } from './utils/strings'; /** * Given an inline config, discover the config file if necessary, merge the results, resolve any @@ -236,6 +237,7 @@ export async function resolveConfig( }, hooks: mergedConfig.hooks ?? {}, vite: mergedConfig.vite ?? (() => ({})), + iifeName: mergedConfig.iifeName ?? safeVarName, builtinModules, userModules, plugins: [], diff --git a/packages/wxt/src/core/utils/testing/fake-objects.ts b/packages/wxt/src/core/utils/testing/fake-objects.ts index 923113d3f..14aa4d59d 100644 --- a/packages/wxt/src/core/utils/testing/fake-objects.ts +++ b/packages/wxt/src/core/utils/testing/fake-objects.ts @@ -305,6 +305,7 @@ export const fakeResolvedConfig = fakeObjectCreator(() => { hooks: {}, vite: () => ({}), plugins: [], + iifeName: (entrypointName) => entrypointName, }; }); diff --git a/packages/wxt/src/types.ts b/packages/wxt/src/types.ts index b1ef253bd..55cda3a63 100644 --- a/packages/wxt/src/types.ts +++ b/packages/wxt/src/types.ts @@ -374,6 +374,11 @@ export interface InlineConfig { * "wxt-module-analytics"). */ modules?: string[]; + /** + * The name to use for each entrypoint IIFE global variable. + * If not set, a safe variable name will be generated from the entrypoint name. + */ + iifeName?: (entrypointName: string) => string; } // TODO: Extract to @wxt/vite-builder and use module augmentation to include the vite field @@ -1400,6 +1405,7 @@ export interface ResolvedConfig { * ["@wxt-dev/module-vue/plugin", "wxt-module-google-analytics/plugin"] */ plugins: string[]; + iifeName: (entryPointName: string) => string; } export interface FsCache {