From b70e3474506f6bb479386cba5394569a00d5f7d3 Mon Sep 17 00:00:00 2001 From: fnlCtrl Date: Sun, 13 Aug 2023 20:05:21 -0700 Subject: [PATCH 1/4] fix: use string manipulation instead of regex to inject esbuild helpers --- packages/vite/src/node/plugins/esbuild.ts | 38 +++++++++++++---------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/vite/src/node/plugins/esbuild.ts b/packages/vite/src/node/plugins/esbuild.ts index d2bf848437a915..27435527faa07d 100644 --- a/packages/vite/src/node/plugins/esbuild.ts +++ b/packages/vite/src/node/plugins/esbuild.ts @@ -28,10 +28,8 @@ import { searchForWorkspaceRoot } from '../server/searchRoot' const debug = createDebugger('vite:esbuild') -const INJECT_HELPERS_IIFE_RE = - /^(.*?)((?:const|var)\s+\S+\s*=\s*function\s*\([^)]*\)\s*\{\s*"use strict";)/s -const INJECT_HELPERS_UMD_RE = - /^(.*?)(\(function\([^)]*\)\s*\{.+?amd.+?function\([^)]*\)\s*\{\s*"use strict";)/s +// IIFE content looks like `var MyLib = function() {`. Spaces are removed when minified +const IIFE_BEGIN_RE = /(const|var)(.*)=\s*function\(\)\s*\{\s*"use strict";/ const validExtensionRE = /\.\w+$/ const jsxExtensionsRE = /\.(?:j|t)sx\b/ @@ -333,22 +331,30 @@ export const buildEsbuildPlugin = (config: ResolvedConfig): 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 use a regex to inject the helpers inside the wrappers. + // 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 injectHelpers = - opts.format === 'umd' - ? INJECT_HELPERS_UMD_RE - : opts.format === 'iife' - ? INJECT_HELPERS_IIFE_RE - : undefined - if (injectHelpers) { - res.code = res.code.replace( - injectHelpers, - (_, helpers, header) => header + helpers, - ) + const esbuildCode = res.code + const contentIndex = + opts.format === 'iife' + ? esbuildCode.match(IIFE_BEGIN_RE)?.index || 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) } } + return res }, } From 1f0a7f6e3f42068371254e14264a235bb7da6e8e Mon Sep 17 00:00:00 2001 From: fnlCtrl Date: Sun, 13 Aug 2023 20:29:19 -0700 Subject: [PATCH 2/4] add test --- playground/lib/src/main.js | 3 +++ playground/lib/vite.config.js | 1 + 2 files changed, 4 insertions(+) diff --git a/playground/lib/src/main.js b/playground/lib/src/main.js index 59c8e897cb0789..4440b67c3c16f7 100644 --- a/playground/lib/src/main.js +++ b/playground/lib/src/main.js @@ -10,3 +10,6 @@ export default function myLib(sel) { // make sure umd helper has been moved to the right position console.log(`amd function(){ "use strict"; }`) } + +// For triggering unhandled global esbuild helpers in previous regex-based implementation for injection +myLib()?.foo diff --git a/playground/lib/vite.config.js b/playground/lib/vite.config.js index 6b4395624dc27a..84612ba1f65306 100644 --- a/playground/lib/vite.config.js +++ b/playground/lib/vite.config.js @@ -7,6 +7,7 @@ export default defineConfig({ supported: { // Force esbuild inject helpers to test regex 'object-rest-spread': false, + 'optional-chain': false, }, }, build: { From b38fff6fa46270e7cf76530070e66e63c6cc8e65 Mon Sep 17 00:00:00 2001 From: fnlCtrl Date: Sun, 13 Aug 2023 21:42:38 -0700 Subject: [PATCH 3/4] fix tests --- packages/vite/src/node/plugins/esbuild.ts | 3 ++- playground/lib/__tests__/lib.spec.ts | 2 +- playground/lib/src/main.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/vite/src/node/plugins/esbuild.ts b/packages/vite/src/node/plugins/esbuild.ts index 27435527faa07d..591e2883453fe3 100644 --- a/packages/vite/src/node/plugins/esbuild.ts +++ b/packages/vite/src/node/plugins/esbuild.ts @@ -29,7 +29,8 @@ import { searchForWorkspaceRoot } from '../server/searchRoot' const debug = createDebugger('vite:esbuild') // IIFE content looks like `var MyLib = function() {`. Spaces are removed when minified -const IIFE_BEGIN_RE = /(const|var)(.*)=\s*function\(\)\s*\{\s*"use strict";/ +const IIFE_BEGIN_RE = + /(const|var)\s+\S+\s*=\s*function\(\)\s*\{.*"use strict";/s const validExtensionRE = /\.\w+$/ const jsxExtensionsRE = /\.(?:j|t)sx\b/ diff --git a/playground/lib/__tests__/lib.spec.ts b/playground/lib/__tests__/lib.spec.ts index b203535e0154da..b2e6ec1d33a86e 100644 --- a/playground/lib/__tests__/lib.spec.ts +++ b/playground/lib/__tests__/lib.spec.ts @@ -33,7 +33,7 @@ describe.runIf(isBuild)('build', () => { 'dist/nominify/my-lib-custom-filename.iife.js', ) // esbuild helpers are injected inside of the IIFE wrapper - expect(code).toMatch(/^var MyLib=function\(\)\{"use strict";/) + expect(code).toMatch(/^var MyLib=function\(\)\{(.*?)"use strict";/) expect(noMinifyCode).toMatch( /^var MyLib\s*=\s*function\(\)\s*\{.*?"use strict";/s, ) diff --git a/playground/lib/src/main.js b/playground/lib/src/main.js index 4440b67c3c16f7..8be8ec37e635ee 100644 --- a/playground/lib/src/main.js +++ b/playground/lib/src/main.js @@ -12,4 +12,4 @@ export default function myLib(sel) { } // For triggering unhandled global esbuild helpers in previous regex-based implementation for injection -myLib()?.foo +;(function () {})()?.foo From dc58a74ae368c387326efa95427dfb0381ccd774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=A0=20/=20green?= Date: Fri, 25 Aug 2023 12:00:12 +0900 Subject: [PATCH 4/4] chore: add comment about esbuild's bug --- playground/lib/__tests__/lib.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/playground/lib/__tests__/lib.spec.ts b/playground/lib/__tests__/lib.spec.ts index b2e6ec1d33a86e..8232a2fd100649 100644 --- a/playground/lib/__tests__/lib.spec.ts +++ b/playground/lib/__tests__/lib.spec.ts @@ -33,7 +33,9 @@ describe.runIf(isBuild)('build', () => { 'dist/nominify/my-lib-custom-filename.iife.js', ) // esbuild helpers are injected inside of the IIFE wrapper - expect(code).toMatch(/^var MyLib=function\(\)\{(.*?)"use strict";/) + // esbuild has a bug that injects some statements before `"use strict"`: https://github.com/evanw/esbuild/issues/3322 + // remove the `.*?` part once it's fixed + expect(code).toMatch(/^var MyLib=function\(\)\{.*?"use strict";/) expect(noMinifyCode).toMatch( /^var MyLib\s*=\s*function\(\)\s*\{.*?"use strict";/s, )