Electron Architecture | Rookie Tutorial
Think of Electron as:
A multi-process application container with a built-in browser engine (Chromium) and a Node.js runtime,
which works together through inter-process communication (IPC) to achieve "web + system capabilities" desktop applications.
Its core components are:
| Role | Description | Analogy |
|---|---|---|
| Main Process | The brain of Electron, controls application lifecycle, creates windows, calls system APIs | OS Manager |
| Renderer Process | The web environment (HTML, CSS, JS) running in each window | Browser Tab |
| Preload Script | Runs before rendering, can bridge main process APIs to the web page | Security "Middleware" |
| IPC | Communication channel between main process and renderer process | Telephone Line |
| BrowserWindow Object | The "window container" created by the main process, loads web pages inside | Browser Window |
| App Module | Controls application lifecycle (launch, quit) | Master Control |
Main Process
The main process is the "brain" of an Electron application. Each Electron application has one and only one main process. It is responsible for:
- Creating and managing application windows (renderer processes)
- Handling application lifecycle (launch, quit, foreground/background switching)
- Interacting with native OS APIs
- Managing system-level components like menus and dialogs
Example
// main.js - Main process example
const { app, BrowserWindow } = require('electron');
function createWindow() {
// Create browser window
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
// Load the application's index.html
mainWindow.loadFile('index.html');
}
// Called when Electron finishes initialization
app.whenReady().then(createWindow);
Renderer Process
The renderer process is responsible for displaying the user interface. Each Electron window is an independent renderer process:
- Runs in the Chromium browser environment
- Uses HTML, CSS, and JavaScript to build the interface
- Each window is an independent process, unaffected by others
- Communicates with the main process through IPC
Example
<!-- index.html - Renderer process example -->
<!DOCTYPE html>
<html>
<head>
<title>My Electron App</title>
</head>
<body>
<h1>Hello Electron!</h1>
<button id="btn">Click Me</button>
<script>
// JavaScript in renderer process
document.getElementById('btn').addEventListener('click', () => {
alert('Button clicked!');
});
</script>
</body>
</html>
Preload Scripts
Preload scripts are the "bridge" connecting the main process and renderer process:
- Runs before the renderer process loads the web page
- Has access to Node.js APIs and DOM
- Securely exposes APIs to the renderer process through contextBridge
Example
// preload.js - Preload script example
const { contextBridge, ipcRenderer } = require('electron');
// Expose secure API to renderer process
contextBridge.exposeInMainWorld('electronAPI', {
showDialog: (message) => ipcRenderer.invoke('show-dialog', message)
});
Inter-Process Communication (IPC)
IPC Communication Patterns
IPC (Inter-Process Communication) is the core of Electron's communication.
Since the main process and renderer process are independent, data must be passed through IPC.
Communication Directions
| Type | Main Process Listens | Renderer Process Sends |
|---|---|---|
| Renderer β Main | ipcMain.on(channel, handler) |
ipcRenderer.send(channel, data) |
| Main β Renderer | event.sender.send(channel, data) |
ipcRenderer.on(channel, callback) |
Basic Communication Example
Main Process Code:
Example
const { ipcMain, dialog } = require('electron');
// Listen for messages from renderer process
ipcMain.handle('show-dialog', async (event, message) => {
const result = await dialog.showMessageBox({
type: 'info',
message: message,
buttons: ['OK', 'Cancel']
});
return result;
});
Renderer Process Code:
Example
// Communicate through API exposed by preload script
document.getElementById('btn').addEventListener('click', async () => {
const result = await window.electronAPI.showDialog('Hello, Electron!');
console.log('User clicked:', result.response);
});
Architecture Advantages and Characteristics
Advantages Comparison
| Feature | Electron | Traditional Desktop Development |
|---|---|---|
| Development Technology | Web technologies (HTML/CSS/JS) | Native languages (C++/C#/Java) |
| Cross-platform Support | Develop once, run on multiple platforms | Need separate development for each platform |
| Development Efficiency | High, leverages existing web ecosystem | Lower, requires learning platform-specific technologies |
| Performance | Relatively lower, more resource consumption | High, native performance |
| Package Size | Larger (includes Chromium) | Smaller |
Core Characteristics
- Cross-platform Consistency: Applications look and behave the same across all operating systems
- Web Ecosystem Utilization: Can directly use npm packages and web frameworks
- Rapid Prototyping: Based on familiar web technologies, shorter development cycles
- Rich APIs: Provides a complete set of APIs for accessing native system functionality
Practical Application Example
Let's create a simple file manager application to demonstrate Electron architecture:
Main Process (main.js):
Example
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
const fs = require('fs').promises;
const path = require('path');
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000,
height: 700,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true
}
});
mainWindow.loadFile('index.html');
}
// Handle file reading
ipcMain.handle('read-file', async (event, filePath) => {
try {
const content = await fs.readFile(filePath, 'utf-8');
return { success: true, content };
} catch (error) {
return { success: false, error: error.message };
}
});
// Handle file selection
ipcMain.handle('select-file', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt', 'md', 'js', 'html', 'css'] }
]
});
return result;
});
app.whenReady().then(createWindow);
Preload Script (preload.js):
Example
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('fileAPI', {
readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
selectFile: () => ipcRenderer.invoke('select-file')
});
Renderer Process (index.html):
Example
<!DOCTYPE html>
<html>
<head>
<title>Simple File Manager</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.container { max-width: 800px; margin: 0 auto; }
button { padding: 10px 15px; margin: 5px; cursor: pointer; }
#content { border: 1px solid #ccc; padding: 15px; margin-top: 10px;
min-height: 300px; white-space: pre-wrap; }
</style>
</head>
<body>
<div class="container">
<h1>Simple File Manager</h1>
<button id="selectBtn">Select File</button>
<button id="clearBtn">Clear Content</button>
<div>
<h3>File Content:</h3>
<div id="content">Please select a file...</div>
</div>
</div>
<script>
const selectBtn = document.getElementById('selectBtn');
const clearBtn = document.getElementById('clearBtn');
const contentDiv = document.getElementById('content');
selectBtn.addEventListener('click', async () => {
const result = await window.fileAPI.selectFile();
if (!result.canceled && result.filePaths.length > 0) {
const filePath = result.filePaths;
const fileResult = await window.fileAPI.readFile(filePath);
if (fileResult.success) {
contentDiv.textContent = `File path: ${filePath}nn${fileResult.content}`;
} else {
contentDiv.textContent = `Failed to read file: ${fileResult.error}`;
}
}
});
clearBtn.addEventListener('click', () => {
contentDiv.textContent = 'Please select a file...';
});
</script>
</body>
</html>
Other Important Modules (Available in Main Process)
| Module Name | Function |
|---|---|
app |
Controls application lifecycle (launch/quit) |
Menu / MenuItem |
Creates application menu bar |
Tray |
System tray icon |
Notification |
System notifications |
dialog |
Open file/save dialogs |
shell |
Opens files or URLs with default system programs |
nativeImage |
Manipulates images (icons) |
clipboard |
Manipulates clipboard content |
powerMonitor |
Listens to system power events |
screen |
Gets screen information (multi-monitor support) |
Security Architecture and Sandbox Isolation
Since renderer processes can run web pages, Electron implements the following security designs to prevent malicious code from attacking the system:
| Strategy | Description |
|---|---|
| Disable NodeIntegration | Default false, prevents web pages from directly calling system APIs. |
| Enable ContextIsolation | Keeps page scripts and Preload API running in isolated contexts. |
| Preload + contextBridge | Explicitly controls the secure APIs exposed to the frontend. |
| Content Security Policy (CSP) | Restricts script sources, prevents XSS. |
| Sandbox Mode | Can enable sandbox to completely isolate renderer processes. |
| Validate Remote URLs | Untrusted remote content must be whitelist-validated. |
Runtime Flow (From Launch to Render)
- Launch application (electron .) β
- Execute main.js (main process starts) β
- app.whenReady() triggers, creates BrowserWindow β
- BrowserWindow starts new renderer process (Chromium instance) β
- preload.js executes first (in isolated context) β
- index.html loads and displays (frontend framework runs) β
- Renderer process calls main process logic through IPC β
- Main process handles request, returns result β
- User closes window β main process listens β app.quit()
Master-Slave Multi-Window Model (Multi-Process Parallelism)
- An Electron application always has only one main process;
- But can have multiple renderer processes (each window is independent);
- Communication between them must go through the main process or use
ipcMain.handlefor asynchronous bridging.
Illustration:
Main Process (Main)
βββ Window 1 β Renderer Process A (index.html)
βββ Window 2 β Renderer Process B (settings.html)
βββ Window 3 β Renderer Process C (dashboard.html)
Electron Internal Core Components (Underlying Dependencies)
| Layer | Technology |
|---|---|
| UI Rendering Layer | Chromium (Blink + V8) |
| System Interface Layer | Node.js (libuv + C++ bindings) |
| Process Management Layer | Electron Kernel (C++ + JavaScript) |
| Application Logic Layer | Your JS / TS code (Main + Renderer) |
Your JS code runs in the Node environment packaged by Electron, while the rendering interface runs in the Chromium WebView.
YouTip