fix(esm-shim): ignore import-like text inside strings/comments

Mask string, template and comment spans before locating static imports
or CJS syntax, so the shim is not injected mid-string into large Vite 8
/ Rolldown bundles. Also disable oxc when SWC owns transform (Vite 8).

close #906
close #916

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
edenbuilds 2026-08-05 16:14:00 +05:30
parent 31965d2972
commit e9a9478fb4
2 changed files with 101 additions and 3 deletions

View File

@ -37,14 +37,108 @@ interface StaticImport {
end: number
}
/**
* Replace string/template contents and comments with spaces of the same length.
* Indices stay aligned with the original source so import-end offsets still apply
* when injecting the CJS shim.
*
* Without this, `import ... from '...'` text embedded in string literals (common
* in large app bundles that ship schema/SQL/source snippets) is treated as a real
* static import. Injecting the shim after that false match mid-string corrupts
* the chunk and can cause bundlers (e.g. Rolldown / Vite 8) to emit an empty entry
* see https://github.com/alex8088/electron-vite/issues/906
*/
export function maskNonCode(code: string): string {
let out = ''
let i = 0
const n = code.length
const spaces = (len: number): string => ' '.repeat(len)
while (i < n) {
const c = code[i]!
const c2 = code[i + 1]
// line comment
if (c === '/' && c2 === '/') {
let j = i + 2
while (j < n && code[j] !== '\n' && code[j] !== '\r') j++
out += spaces(j - i)
i = j
continue
}
// block comment
if (c === '/' && c2 === '*') {
let j = i + 2
while (j + 1 < n && !(code[j] === '*' && code[j + 1] === '/')) j++
j = Math.min(j + 2, n)
out += spaces(j - i)
i = j
continue
}
// single- and double-quoted strings
if (c === '"' || c === "'") {
const q = c
let j = i + 1
while (j < n) {
if (code[j] === '\\') {
j += 2
continue
}
if (code[j] === q) {
j++
break
}
j++
}
out += spaces(j - i)
i = j
continue
}
// template literals (mask whole span, including ${...} expressions —
// rare real top-level imports inside interpolations, and safer than
// re-matching import-like SQL/source embedded in templates)
if (c === '`') {
let j = i + 1
while (j < n) {
if (code[j] === '\\') {
j += 2
continue
}
if (code[j] === '`') {
j++
break
}
j++
}
out += spaces(j - i)
i = j
continue
}
out += c
i++
}
return out
}
function findStaticImports(code: string): StaticImport[] {
const searchable = maskNonCode(code)
const matches: StaticImport[] = []
for (const match of code.matchAll(ESMStaticImportRe)) {
for (const match of searchable.matchAll(ESMStaticImportRe)) {
matches.push({ end: (match.index || 0) + match[0].length })
}
return matches
}
function hasCjsSyntax(code: string): boolean {
// Ignore CJS markers that only appear inside strings/comments
return CJSyntaxRe.test(maskNonCode(code))
}
export default function esmShimPlugin(): Plugin {
const CJSShim = supportImportMetaPaths() ? CJSShim_node_20_11 : CJSShim_normal
@ -54,7 +148,7 @@ export default function esmShimPlugin(): Plugin {
enforce: 'post',
renderChunk(code, _chunk, { format, sourcemap }): { code: string; map?: SourceMapInput } | null {
if (format === 'es') {
if (code.includes(CJSShim) || !CJSyntaxRe.test(code)) {
if (code.includes(CJSShim) || !hasCjsSyntax(code)) {
return null
}

View File

@ -83,8 +83,12 @@ export function swcPlugin(options: SwcOptions = {}): Plugin {
return {
name: 'vite:swc',
config(): UserConfig {
// Vite 8 moved default transform to Oxc: only disabling esbuild is ignored
// and emits a warning. SWC replaces both paths when this plugin is active.
// See https://github.com/alex8088/electron-vite/issues/916
return {
esbuild: false
esbuild: false,
oxc: false
}
},
async configResolved(resolvedConfig): Promise<void> {