I have Electron application with overlay over the loaded website in React and I get error:
Uncaught ReferenceError: exports is not defined
at index.js:2:23
this is on second line: Object.defineProperty(exports, "__esModule", { value: true });
even I don’t use any NodeJS modules inside React app.
This is my project structure:
/src
|--assets/
|--electron/
|--overlay/
|--shared/
|-.eslintrc.cjs
|-.gitignore
|-package.json
|-postcss.config.js
|-tailwind.config.js
|-tsconfig.electron.json
|-tsconfig.overlay.json
Basically assets contains images, electron contains backend code in typescript, overlay contains React code, shared contains types shared between backend and frontend.
This is how my build looks like in package.json:
"build": "bun run build:css && bun run build:overlay && bun run build:electron && cpx "src/assets/**/*" dist/assets",
"build:css": "bunx tailwindcss -i ./src/overlay/index.css -o ./dist/overlay/index.css --minify",
"build:electron": "tsc -p tsconfig.electron.json",
"build:overlay": "bun build src/overlay/index.tsx --config tsconfig.overlay.json --outdir=dist/overlay --format=esm --target=browser --external=electron --external=fs --external=path --external=os --splitting",
"start": "bun run build && electron dist/electron/main.js",
/src/overlay contains bunfig.toml, global.d.ts, index.css and index.tsx
bunfig.toml:
entrypoint = "index.tsx"
outdir = "../../dist/overlay"
target = "browser"
sourcemap = true
minify = false
format = "iife"
global.d.ts:
import { Config } from "../shared/types";
export {};
declare global {
interface Window {
electronAPI: {
onSidebarToggle: (callback: () => void) => void;
openExternal: (url: string) => void;
saveConfig: (config: Partial<Config>) => void;
onConfigLoaded: (callback: (config: Config) => void) => void;
};
}
}
index.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
index.tsx:
import "./index.css";
import { createRoot } from "react-dom/client";
const mount = document.createElement("div");
mount.id = "sidebar-root";
document.body.appendChild(mount);
const App = () => {
return <></>;
};
createRoot(mount).render(<App />);
/src/shared contains types.ts:
export const defaultConfig: Config = {
autofocus: false,
notify: true,
rpcEnabled: true,
informed: false,
accentColor: "",
};
export interface Config {
autofocus: boolean;
notify: boolean;
rpcEnabled: boolean;
informed: boolean;
accentColor: string;
}
If it is important this is /src/electron/preload.ts:
import { contextBridge, ipcRenderer, clipboard, shell } from "electron";
import { Config } from "../shared/types";
declare global {
interface Window {
toggleSidebar: () => void;
}
}
contextBridge.exposeInMainWorld("electronAPI", {
onSidebarToggle: (callback: () => void) => {
ipcRenderer.on("sidebar-toggle", (_event, ...args) => {
console.log("sidebar-toggle event received in preload");
callback();
});
},
saveConfig: (config: Partial<Config>) =>
ipcRenderer.send("save-config", config),
onConfigLoaded: (callback: (config: Config) => void) => {
console.log("Setting up config loaded listener");
ipcRenderer.on("config-loaded", (event, config) => callback(config));
},
copyToClipboard: (text: string) => clipboard.writeText(text),
openExternal: (url: string) => shell.openExternal(url),
checkForUpdates: () => ipcRenderer.invoke("check-for-updates"),
quitAndInstall: () => ipcRenderer.send("quit-and-install"),
updateAvailable: (
callback: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void
) => ipcRenderer.on("update-available", callback),
updateDownloaded: (
callback: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void
) => ipcRenderer.on("update-downloaded", callback),
downloadUpdate: () => ipcRenderer.invoke("download-update"),
});
I inject overlay into website like:
function injectOverlay(mainWindow: BrowserWindow) {
const { pathToFileURL } = require("url");
const overlayScriptPath = path.join(__dirname, "../overlay/index.js");
const overlayScriptUrl = pathToFileURL(overlayScriptPath).href;
mainWindow.webContents.executeJavaScript(`
const s = document.createElement("script");
s.type = "module";
s.src = "${overlayScriptUrl}";
document.body.appendChild(s);
`);
}
tsconfig.electron.json contains:
{
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"module": "Node16",
"target": "ESNext",
"moduleResolution": "node16",
"esModuleInterop": true,
"strict": true,
"jsx": "react-jsx",
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"exclude": ["node_modules"],
"include": [
"src/electron/**/*",
"src/overlay/**/*",
"postcss.config.js",
"tailwind.config.js"
],
"assets": ["src/assets/**/*"]
}
tsconfig.overlay.json contains:
{
"compilerOptions": {
"outDir": "dist/overlay",
"rootDir": "src/overlay",
"module": "ESNext",
"target": "ESNext",
"jsx": "react-jsx",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true
},
"include": ["src/overlay/**/*"]
}
Fun fact is that if I run:
bun build src/overlay/index.tsx
--config tsconfig.overlay.json
--outdir=dist/overlay
--format=esm
--target=browser
--external=electron --external=fs --external=path --external=os
--splitting
and then: electron dist/electron/main.js
I get no error, but when I run npm start, I get error, even build command is exactly same as in package.json.