diff --git a/packages/vite/src/node/__tests__/plugins/esbuild.spec.ts b/packages/vite/src/node/__tests__/plugins/esbuild.spec.ts index 14e953d24be786..7f5e90db5f68fd 100644 --- a/packages/vite/src/node/__tests__/plugins/esbuild.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/esbuild.spec.ts @@ -2,6 +2,7 @@ import path from 'node:path' import { describe, expect, test } from 'vitest' import type { ResolvedConfig, UserConfig } from '../../config' import { + injectEsbuildHelpers, resolveEsbuildTranspileOptions, transformWithEsbuild, } from '../../plugins/esbuild' @@ -397,6 +398,33 @@ describe('transformWithEsbuild', () => { }) }) +describe('injectEsbuildHelpers', () => { + test('injects helpers in IIFE format', () => { + const esbuildCode = + 'var $=function(){};var MyLib=function(){"use strict";return 42;}' + const result = injectEsbuildHelpers(esbuildCode, 'iife') + expect(result).toBe( + 'var MyLib=function(){"use strict";var $=function(){};return 42;}', + ) + }) + + test('injects helpers in UMD format', () => { + const esbuildCode = + 'var $=function(){};(function(global){"use strict";return 42;})' + const result = injectEsbuildHelpers(esbuildCode, 'umd') + expect(result).toBe( + '(function(global){"use strict";var $=function(){};return 42;})', + ) + }) + + test('handles helpers with special characters', () => { + const esbuildCode = + 'var $$=function(){};var MyLib=function(){"use strict";return 42;}' + const result = injectEsbuildHelpers(esbuildCode, 'iife') + expect(result).toContain('"use strict";var $$=function(){};') + }) +}) + /** * Helper for `resolveEsbuildTranspileOptions` to created resolved config with types. * Note: The function only uses `build.target`, `build.minify` and `esbuild` options. diff --git a/packages/vite/src/node/plugins/esbuild.ts b/packages/vite/src/node/plugins/esbuild.ts index 09d8ecc147da7b..d48d5a738d6230 100644 --- a/packages/vite/src/node/plugins/esbuild.ts +++ b/packages/vite/src/node/plugins/esbuild.ts @@ -318,6 +318,37 @@ const rollupToEsbuildFormatMap: Record< iife: undefined, } +// #7188, esbuild adds helpers out of the UMD and IIFE wrappers, and the +// names are minified potentially causing collision with other globals. +// We inject the helpers inside the wrappers. +// e.g. turn: +// (function(){ /*actual content/* })() +// into: +// (function(){ /*actual content/* })() +// Not using regex because it's too hard to rule out performance issues like #8738 #8099 #10900 #14065 +// Instead, using plain string index manipulation (indexOf, slice) which is simple and performant +// We don't need to create a MagicString here because both the helpers and +// the headers don't modify the sourcemap +export const injectEsbuildHelpers = ( + esbuildCode: string, + format: string, +): string => { + const contentIndex = + format === 'iife' + ? Math.max(esbuildCode.search(IIFE_BEGIN_RE), 0) + : format === 'umd' + ? esbuildCode.indexOf(`(function(`) // same for minified or not + : 0 + + if (contentIndex > 0) { + const esbuildHelpers = esbuildCode.slice(0, contentIndex) + return esbuildCode + .slice(contentIndex) + .replace('"use strict";', (m: string) => m + esbuildHelpers) + } + return esbuildCode +} + export const buildEsbuildPlugin = (): Plugin => { return { name: 'vite:esbuild-transpile', @@ -346,30 +377,7 @@ export const buildEsbuildPlugin = (): Plugin => { ) if (config.build.lib) { - // #7188, esbuild adds helpers out of the UMD and IIFE wrappers, and the - // names are minified potentially causing collision with other globals. - // We inject the helpers inside the wrappers. - // e.g. turn: - // (function(){ /*actual content/* })() - // into: - // (function(){ /*actual content/* })() - // Not using regex because it's too hard to rule out performance issues like #8738 #8099 #10900 #14065 - // Instead, using plain string index manipulation (indexOf, slice) which is simple and performant - // We don't need to create a MagicString here because both the helpers and - // the headers don't modify the sourcemap - const esbuildCode = res.code - const contentIndex = - opts.format === 'iife' - ? Math.max(esbuildCode.search(IIFE_BEGIN_RE), 0) - : opts.format === 'umd' - ? esbuildCode.indexOf(`(function(`) // same for minified or not - : 0 - if (contentIndex > 0) { - const esbuildHelpers = esbuildCode.slice(0, contentIndex) - res.code = esbuildCode - .slice(contentIndex) - .replace(`"use strict";`, `"use strict";` + esbuildHelpers) - } + res.code = injectEsbuildHelpers(res.code, opts.format) } return res