Desktop Application Development with Electron
Electron is a framework for creating cross-platform desktop applications based on Chromium and Node.js. VS Code, Slack, Figma, GitHub Desktop — all written on Electron. Main advantage: one codebase, three platforms. Main disadvantage: distribution size from 80 MB.
Architecture: Main and Renderer Processes
Electron separates code into two isolated processes:
Main process — Node.js environment with access to the file system, OS, native APIs. Manages windows, system tray, native menus.
Renderer process — Chromium, renders web content. No direct Node.js access (unless nodeIntegration: true is enabled, which is unsafe). Communication with main — only through IPC.
main.js (Node.js)
├── BrowserWindow
│ └── renderer (Chromium + preload script)
├── Tray
├── Menu
└── IPC handlers
Minimal Project Structure
my-app/
├── package.json
├── main/
│ ├── index.js # main process entry point
│ ├── preload.js # bridge between main and renderer
│ └── ipc-handlers.js # IPC handlers
├── renderer/ # React/Vue/plain HTML
│ ├── index.html
│ └── src/
└── resources/
└── icon.png
{
"main": "main/index.js",
"scripts": {
"start": "electron .",
"build": "electron-builder"
},
"devDependencies": {
"electron": "^31.0.0",
"electron-builder": "^24.0.0"
}
}
Main Process: Creating a Window
// main/index.js
const { app, BrowserWindow } = require('electron');
const path = require('path');
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
titleBarStyle: 'hiddenInset', // macOS — custom title bar
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, // required for security
nodeIntegration: false, // disable for security
sandbox: true
}
});
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:3000');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
}
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: Secure Bridge
// main/preload.js
const { contextBridge, ipcRenderer } = require('electron');
// Export only what's needed to renderer
contextBridge.exposeInMainWorld('electronAPI', {
// File system
openFile: () => ipcRenderer.invoke('dialog:openFile'),
saveFile: (content) => ipcRenderer.invoke('fs:saveFile', content),
// System functions
getVersion: () => ipcRenderer.invoke('app:getVersion'),
minimize: () => ipcRenderer.send('window:minimize'),
maximize: () => ipcRenderer.send('window:maximize'),
close: () => ipcRenderer.send('window:close'),
// Subscribe to main events
onUpdateAvailable: (callback) => {
const listener = (_, info) => callback(info);
ipcRenderer.on('update:available', listener);
return () => ipcRenderer.removeListener('update:available', listener);
}
});
// main/ipc-handlers.js
const { ipcMain, app, dialog, BrowserWindow } = require('electron');
const fs = require('fs/promises');
ipcMain.handle('dialog:openFile', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog({
filters: [
{ name: 'Text Files', extensions: ['txt', 'md'] },
{ name: 'All Files', extensions: ['*'] }
]
});
if (canceled) return null;
const content = await fs.readFile(filePaths[0], 'utf-8');
return { path: filePaths[0], content };
});
ipcMain.handle('app:getVersion', () => app.getVersion());
ipcMain.on('window:minimize', (event) => {
BrowserWindow.fromWebContents(event.sender)?.minimize();
});
Integration with React (Vite + Electron)
Most convenient stack — electron-vite:
npm create @quick-start/electron my-app
# Select React + TypeScript
electron.vite.config.ts configuration out of the box configures three entry points: main, preload, renderer.
For existing Vite project:
npm install electron-vite --save-dev
Packaging and Distribution
electron-builder — standard for building installers:
# electron-builder.yml
appId: com.yourcompany.myapp
productName: My Application
directories:
output: release/
files:
- main/**
- renderer/dist/**
- '!**/*.map'
mac:
category: public.app-category.productivity
icon: resources/icon.icns
target:
- dmg
- zip
win:
icon: resources/icon.ico
target:
- nsis
- portable
linux:
icon: resources/icons/
target:
- AppImage
- deb
- rpm
# Build for all platforms
npm run build -- --mac --win --linux
# Only for current platform
npm run build
Performance: Reducing Bundle Size
Electron includes entire Chromium — 80-150 MB minimum. Can be reduced by:
-
electron-builderwithcompression: maximum - Excluding unused
node_modulesviafilesconfiguration - ASAR archiving (enabled by default)
For applications where size is critical, consider Tauri (~5 MB) as an alternative.
Typical Development Timeline
Basic app with UI + File I/O + Auto-update: 1-2 weeks. Full-featured application with native menus, tray, notifications, updates, and builds for all platforms: 3-6 weeks depending on functionality.







