fix(electron): force main bundle cjs output

This commit is contained in:
wiiiii123
2026-05-09 10:51:22 +07:00
parent 1b81c2dc40
commit 92f7476c64
3 changed files with 162 additions and 2 deletions
+46
View File
@@ -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;" },
]);
});
});
+82
View File
@@ -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);
+34 -2
View File
@@ -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: {