--- name: electron-desktop description: Desktop application development with Electron for Windows, macOS, and Linux. Use when building cross-platform desktop apps, implementing native OS features, or packaging web apps for desktop. --- # Electron Desktop Development Build cross-platform desktop applications using web technologies. ## Platforms Supported | Platform | Architecture | Notes | | -------- | -------------------------- | ------------------ | | Windows | x64, arm64, ia32 | Windows 10+ | | macOS | x64, arm64 (Apple Silicon) | macOS 10.15+ | | Linux | x64, arm64, armv7l | Most distributions | --- ## Project Structure ``` my-app/ ├── src/ │ ├── main/ │ │ ├── main.ts # Main process │ │ ├── preload.ts # Preload scripts │ │ └── ipc.ts # IPC handlers │ ├── renderer/ │ │ ├── index.html │ │ ├── App.tsx │ │ └── components/ │ └── shared/ │ └── types.ts ├── resources/ │ ├── icon.icns # macOS │ ├── icon.ico # Windows │ └── icon.png # Linux ├── electron-builder.yml ├── package.json └── forge.config.ts ``` --- ## Main Process ### Entry Point ```typescript // src/main/main.ts import { app, BrowserWindow, ipcMain } from "electron"; import path from "path"; let mainWindow: BrowserWindow | null = null; function createWindow() { mainWindow = new BrowserWindow({ width: 1200, height: 800, minWidth: 800, minHeight: 600, webPreferences: { preload: path.join(__dirname, "preload.js"), contextIsolation: true, nodeIntegration: false, sandbox: true, }, titleBarStyle: "hiddenInset", // macOS frame: process.platform === "darwin", // Windows/Linux custom frame show: false, // Show when ready }); // Load the app if (process.env.NODE_ENV === "development") { mainWindow.loadURL("http://localhost:5173"); mainWindow.webContents.openDevTools(); } else { mainWindow.loadFile(path.join(__dirname, "../renderer/index.html")); } // Show when ready to prevent flash mainWindow.once("ready-to-show", () => { mainWindow?.show(); }); mainWindow.on("closed", () => { mainWindow = null; }); } app.whenReady().then(createWindow); app.on("window-all-closed", () => { if (process.platform !== "darwin") { app.quit(); } }); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); ``` ### Preload Script (Security Bridge) ```typescript // src/main/preload.ts import { contextBridge, ipcRenderer } from "electron"; // Expose safe APIs to renderer contextBridge.exposeInMainWorld("electronAPI", { // File operations openFile: () => ipcRenderer.invoke("dialog:openFile"), saveFile: (content: string) => ipcRenderer.invoke("dialog:saveFile", content), // App info getVersion: () => ipcRenderer.invoke("app:getVersion"), // Window controls minimize: () => ipcRenderer.send("window:minimize"), maximize: () => ipcRenderer.send("window:maximize"), close: () => ipcRenderer.send("window:close"), // Two-way communication onUpdateAvailable: (callback: () => void) => { ipcRenderer.on("update:available", callback); return () => ipcRenderer.removeListener("update:available", callback); }, }); // Type definitions for renderer declare global { interface Window { electronAPI: { openFile: () => Promise; saveFile: (content: string) => Promise; getVersion: () => Promise; minimize: () => void; maximize: () => void; close: () => void; onUpdateAvailable: (callback: () => void) => () => void; }; } } ``` ### IPC Handlers ```typescript // src/main/ipc.ts import { ipcMain, dialog, BrowserWindow } from "electron"; import fs from "fs/promises"; export function setupIPC() { // File dialogs ipcMain.handle("dialog:openFile", async () => { const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ["openFile"], filters: [ { name: "Text Files", extensions: ["txt", "md"] }, { name: "All Files", extensions: ["*"] }, ], }); if (canceled || filePaths.length === 0) return null; return fs.readFile(filePaths[0], "utf-8"); }); ipcMain.handle("dialog:saveFile", async (_, content: string) => { const { canceled, filePath } = await dialog.showSaveDialog({ filters: [{ name: "Text Files", extensions: ["txt"] }], }); if (canceled || !filePath) return false; await fs.writeFile(filePath, content); return true; }); // Window controls ipcMain.on("window:minimize", (event) => { BrowserWindow.fromWebContents(event.sender)?.minimize(); }); ipcMain.on("window:maximize", (event) => { const win = BrowserWindow.fromWebContents(event.sender); if (win?.isMaximized()) { win.unmaximize(); } else { win?.maximize(); } }); ipcMain.on("window:close", (event) => { BrowserWindow.fromWebContents(event.sender)?.close(); }); } ``` --- ## Renderer Process (React) ### Using Exposed APIs ```tsx // src/renderer/App.tsx import { useState, useEffect } from "react"; function App() { const [version, setVersion] = useState(""); const [content, setContent] = useState(""); useEffect(() => { window.electronAPI.getVersion().then(setVersion); const unsubscribe = window.electronAPI.onUpdateAvailable(() => { alert("Update available!"); }); return unsubscribe; }, []); const handleOpen = async () => { const fileContent = await window.electronAPI.openFile(); if (fileContent) { setContent(fileContent); } }; const handleSave = async () => { await window.electronAPI.saveFile(content); }; return (
My App v{version}