From 92f7476c642e6ca8ae63bbd6e402d3abc4f484b0 Mon Sep 17 00:00:00 2001 From: wiiiii123 Date: Sat, 9 May 2026 10:51:22 +0700 Subject: [PATCH 1/2] fix(electron): force main bundle cjs output --- electron/mainCjsNormalize.test.mjs | 46 ++++++++++++++ scripts/normalize-electron-main-cjs.mjs | 82 +++++++++++++++++++++++++ vite.config.ts | 36 ++++++++++- 3 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 electron/mainCjsNormalize.test.mjs diff --git a/electron/mainCjsNormalize.test.mjs b/electron/mainCjsNormalize.test.mjs new file mode 100644 index 00000000..7b68badb --- /dev/null +++ b/electron/mainCjsNormalize.test.mjs @@ -0,0 +1,46 @@ +import { describe, expect, it } from "vitest"; +import { + findElectronMainCjsEsmSyntax, + normalizeElectronMainCjsSource, +} from "../scripts/normalize-electron-main-cjs.mjs"; + +describe("Electron main CJS normalizer", () => { + it("converts Rollup named export blocks to CommonJS assignments", () => { + const source = [ + 'const MAIN_DIST = "dist-electron";', + 'const RENDERER_DIST = "dist";', + "const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL;", + "export {", + " MAIN_DIST,", + " RENDERER_DIST,", + " VITE_DEV_SERVER_URL", + "};", + ].join("\n"); + + const result = normalizeElectronMainCjsSource(source); + + expect(result.changed).toBe(true); + expect(result.source).toContain("exports.MAIN_DIST = MAIN_DIST;"); + expect(result.source).toContain("exports.RENDERER_DIST = RENDERER_DIST;"); + expect(result.source).toContain("exports.VITE_DEV_SERVER_URL = VITE_DEV_SERVER_URL;"); + expect(findElectronMainCjsEsmSyntax(result.source)).toEqual([]); + }); + + it("converts single-line aliased named exports to CommonJS assignments", () => { + const source = "export { VITE_DEV_SERVER_URL as devServerUrl };"; + + const result = normalizeElectronMainCjsSource(source); + + expect(result.changed).toBe(true); + expect(result.source).toBe("exports.devServerUrl = VITE_DEV_SERVER_URL;"); + expect(findElectronMainCjsEsmSyntax(result.source)).toEqual([]); + }); + + it("reports unsupported ESM export syntax that cannot be normalized safely", () => { + const source = "export default MAIN_DIST;"; + + expect(findElectronMainCjsEsmSyntax(source)).toEqual([ + { line: 1, text: "export default MAIN_DIST;" }, + ]); + }); +}); diff --git a/scripts/normalize-electron-main-cjs.mjs b/scripts/normalize-electron-main-cjs.mjs index 3d3b186c..a62171ee 100644 --- a/scripts/normalize-electron-main-cjs.mjs +++ b/scripts/normalize-electron-main-cjs.mjs @@ -49,6 +49,49 @@ function convertImportLine(line) { return null; } +function convertNamedExports(namedSpec, indent = "") { + const statements = []; + for (const rawSpecifier of namedSpec.split(",")) { + const specifier = rawSpecifier.trim(); + if (!specifier) { + continue; + } + + const aliasMatch = specifier.match( + /^([A-Za-z_$][A-Za-z0-9_$]*)\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)$/, + ); + const localName = aliasMatch ? aliasMatch[1] : specifier; + const exportName = aliasMatch ? aliasMatch[2] : specifier; + if ( + !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(localName) || + !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(exportName) + ) { + return null; + } + + statements.push(`${indent}exports.${exportName} = ${localName};`); + } + + return statements; +} + +function convertExportLine(line) { + const singleLineMatch = line.match( + /^([ \t]*)export\s*\{\s*([^}]*)\s*\}\s*;?[ \t]*$/, + ); + if (singleLineMatch) { + const [, indent, namedSpec] = singleLineMatch; + return convertNamedExports(namedSpec, indent); + } + + const blockStartMatch = line.match(/^([ \t]*)export\s*\{\s*$/); + if (blockStartMatch) { + return { indent: blockStartMatch[1], specifiers: [] }; + } + + return null; +} + function updateLexicalState(line, state) { let mode = state.mode; let escaped = false; @@ -296,8 +339,29 @@ export function normalizeElectronMainCjsSource(source) { const lines = source.split(/\r?\n/); const normalizedLines = []; let state = { mode: null }; + let exportBlock = null; for (const line of lines) { + if (exportBlock) { + if (/^[ \t]*\}\s*;?[ \t]*$/.test(line)) { + const statements = convertNamedExports( + exportBlock.specifiers.join(","), + exportBlock.indent, + ); + if (statements === null) { + normalizedLines.push(`export {\n${exportBlock.specifiers.join("\n")}\n};`); + } else { + normalizedLines.push(...statements); + changed = true; + } + exportBlock = null; + continue; + } + + exportBlock.specifiers.push(line.trim().replace(/,$/, "")); + continue; + } + if (state.mode === null) { const converted = convertImportLine(line); if (converted !== null) { @@ -305,6 +369,17 @@ export function normalizeElectronMainCjsSource(source) { changed = true; continue; } + + const convertedExport = convertExportLine(line); + if (convertedExport !== null) { + if (Array.isArray(convertedExport)) { + normalizedLines.push(...convertedExport); + changed = true; + } else { + exportBlock = convertedExport; + } + continue; + } } const normalized = replaceImportMetaUrlInCode(line, state); @@ -348,6 +423,13 @@ export function findElectronMainCjsEsmSyntax(source) { }); continue; } + if (/^[ \t]*export\b/.test(line)) { + matches.push({ + line: index + 1, + text: line.trim(), + }); + continue; + } } state = updateLexicalState(line, state); diff --git a/vite.config.ts b/vite.config.ts index 64614cdb..3dd36633 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,6 +4,36 @@ import react from "@vitejs/plugin-react"; import { defineConfig, type Plugin } from "vite"; import electron from "vite-plugin-electron/simple"; +function electronMainCjsOutputPlugin(): Plugin { + return { + name: "recordly-electron-main-cjs-output", + enforce: "post", + config(config) { + // Vite mergeConfig concatenates lib.formats with the plugin's ESM default. + config.build ??= {}; + const build = config.build; + const lib = build.lib; + if (lib && typeof lib === "object") { + lib.formats = ["cjs"]; + lib.fileName = (_format, entryName) => `${entryName}.cjs`; + } + + build.rollupOptions ??= {}; + const rollupOptions = build.rollupOptions; + const cjsOutput = { + format: "cjs" as const, + inlineDynamicImports: true, + entryFileNames: "[name].cjs", + chunkFileNames: "[name]-[hash].cjs", + }; + + rollupOptions.output = Array.isArray(rollupOptions.output) + ? rollupOptions.output.map((output) => ({ ...output, ...cjsOutput })) + : { ...(rollupOptions.output ?? {}), ...cjsOutput }; + }, + }; +} + function electronMainCjsGuardPlugin(): Plugin { return { name: "recordly-electron-main-cjs-guard", @@ -37,17 +67,19 @@ export default defineConfig({ lib: { entry: "electron/main.ts", formats: ["cjs"], + fileName: (_format, entryName) => `${entryName}.cjs`, }, rollupOptions: { external: ["ffmpeg-static", "uiohook-napi"], output: { format: "cjs", + inlineDynamicImports: true, entryFileNames: "[name].cjs", - chunkFileNames: "[name].cjs", + chunkFileNames: "[name]-[hash].cjs", }, }, }, - plugins: [electronMainCjsGuardPlugin()], + plugins: [electronMainCjsOutputPlugin(), electronMainCjsGuardPlugin()], }, }, preload: { From 513c576c2708281877549c4c663cf5d25cf90c37 Mon Sep 17 00:00:00 2001 From: wiiiii123 Date: Sat, 9 May 2026 11:23:01 +0700 Subject: [PATCH 2/2] fix(electron): preserve unsupported export blocks --- electron/mainCjsNormalize.test.mjs | 24 ++++++++++++++++++++++++ scripts/normalize-electron-main-cjs.mjs | 5 +++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/electron/mainCjsNormalize.test.mjs b/electron/mainCjsNormalize.test.mjs index 7b68badb..9ccf91da 100644 --- a/electron/mainCjsNormalize.test.mjs +++ b/electron/mainCjsNormalize.test.mjs @@ -43,4 +43,28 @@ describe("Electron main CJS normalizer", () => { { line: 1, text: "export default MAIN_DIST;" }, ]); }); + + it("preserves an unsupported export block exactly while normalizing other syntax", () => { + const source = [ + 'import fs from "node:fs";', + "export {", + ' MAIN_DIST as "main-dist",', + "};", + ].join("\n"); + + const result = normalizeElectronMainCjsSource(source); + + expect(result.changed).toBe(true); + expect(result.source).toBe( + [ + 'const fs = require("node:fs");', + "export {", + ' MAIN_DIST as "main-dist",', + "};", + ].join("\n"), + ); + expect(findElectronMainCjsEsmSyntax(result.source)).toEqual([ + { line: 2, text: "export {" }, + ]); + }); }); diff --git a/scripts/normalize-electron-main-cjs.mjs b/scripts/normalize-electron-main-cjs.mjs index a62171ee..fcbca31f 100644 --- a/scripts/normalize-electron-main-cjs.mjs +++ b/scripts/normalize-electron-main-cjs.mjs @@ -86,7 +86,7 @@ function convertExportLine(line) { const blockStartMatch = line.match(/^([ \t]*)export\s*\{\s*$/); if (blockStartMatch) { - return { indent: blockStartMatch[1], specifiers: [] }; + return { indent: blockStartMatch[1], specifiers: [], rawLines: [line] }; } return null; @@ -349,7 +349,7 @@ export function normalizeElectronMainCjsSource(source) { exportBlock.indent, ); if (statements === null) { - normalizedLines.push(`export {\n${exportBlock.specifiers.join("\n")}\n};`); + normalizedLines.push(...exportBlock.rawLines, line); } else { normalizedLines.push(...statements); changed = true; @@ -359,6 +359,7 @@ export function normalizeElectronMainCjsSource(source) { } exportBlock.specifiers.push(line.trim().replace(/,$/, "")); + exportBlock.rawLines.push(line); continue; }