diff --git a/src/plugins/esmShim.ts b/src/plugins/esmShim.ts index 3829846..f97dfce 100644 --- a/src/plugins/esmShim.ts +++ b/src/plugins/esmShim.ts @@ -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 } diff --git a/src/plugins/swc.ts b/src/plugins/swc.ts index 369f874..05499f4 100644 --- a/src/plugins/swc.ts +++ b/src/plugins/swc.ts @@ -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 {