Electron architecture

You can think of Electron as:

A multi-process application container with a built-in browser engine (Chromium) and a Node.js runtime,
Desktop applications that combine "web pages + system capabilities" by coordinating through inter-process communication (IPC).

Its core components are:

Role Description Analogy
Main Process (main process) Electron's brain, controlling the application lifecycle, creating windows, and calling system APIs OS Process Manager
Renderer Process The web page environment (HTML, CSS, JS) run by each window Browser Tab
Preload Script Runs before rendering, can bridge main process APIs for use by web pages Security "Middle Layer"
IPC (inter-process communication) Communication channel between the main process and renderer processes telephone line
BrowserWindow object The "window container" created by the main process, which loads web pages inside Browser window
App Module Controls the application lifecycle (startup, exit) Master control

Main Process

The main process is the "brain" of an Electron application; every Electron app has exactly one main process. It is responsible for:

  • Create and manage application windows(Renderer Process)
  • Handle application lifecycle(Startup, Exit, Foreground/Background Switching)
  • Interact with operating system native APIs
  • Manage system-level components such as menus and dialogs

Example

// main.js - main process example
const { app, BrowserWindow } = require('electron');

function createWindow() {
  // Create the browser window
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
    }
  });

  // Load the app's index.html
  mainWindow.loadFile('index.html');
}

// Called when Electron completes 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
  • Use HTML, CSS, and JavaScript to build interfaces
  • Each window is an independent process, and they do not affect each other
  • Communicates with the main process via 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 the renderer process
        document.getElementById('btn').addEventListener('click', () => {
alert('The button was clicked!');
        });
    </script>
</body>
</html>

Preload Scripts

The preload script is the "bridge" connecting the main process and the renderer process:

  • Runs before the webpage is loaded in the renderer process
  • Has access to Node.js APIs and the DOM
  • Safely expose APIs to the renderer process via contextBridge

Example

// preload.js - preload script example
const { contextBridge, ipcRenderer } = require('electron');

// Expose a safe API to the 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 communication core of Electron.

Since the main process and the renderer process are independent, data must be passed through IPC.

Communication Direction

Type Main process listening Sent by the renderer process
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 the 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 via the API exposed by the preload script
document.getElementById('btn').addEventListener('click', async () => {
  const result = await window.electronAPI.showDialog('Hello, Electron!');
  console.log('User clicked:', result.response);
});

Architecture Advantages & Features

Advantage Comparison

Features 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 Requires separate development for each platform
Development Efficiency High, leverages existing web ecosystem Lower, requires learning platform-specific technologies
Performance Relatively low, consumes more resources High, Native Performance
Installer size Large (includes Chromium) the smaller

Core Features

  1. Cross-Platform Consistency: The application looks and behaves consistently on all operating systems
  2. Leveraging the web ecosystem: Can directly use npm packages and Web frameworks
  3. Rapid Prototyping: Based on familiar Web technologies, short development cycle
  4. Rich APIs: Provides a complete API set for accessing system native features

Practical Application Examples

Let's create a simple file manager application to demonstrate the 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 File', 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 contents:</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[0];
                const fileResult = await window.fileAPI.readFile(filePath);
               
                if (fileResult.success) {
contentDiv.textContent = `File path: ${filePath}\n\n${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 the main process)

module name Function
app Control the application lifecycle (startup/exit)
Menu / MenuItem Creating the Application Menu Bar
Tray System Tray Icon
Notification System Notification
dialog Open File/Save Dialog
shell Call the system default program to open files or URLs
nativeImage Manipulate images (icons)
clipboard Manipulating Clipboard Content
powerMonitor Listen for system power events
screen Get screen information (multi-monitor support)

Security architecture and sandbox isolation

Since renderer processes can run web pages, to prevent malicious code from attacking the system, Electron implements the following security designs:

Policy Description
Disable NodeIntegration Defaultfalse, avoiding web pages from directly calling system APIs.
Enable ContextIsolation Make page scripts and Preload APIs run in an isolated context.
Preload + contextBridge Clearly control the secure APIs exposed to the frontend.
Content Security Policy (CSP) Restrict script sources to prevent XSS.
sandbox mode Sandboxing can be enabled to completely isolate the renderer process.
Validate remote URLs Untrusted remote content must undergo whitelist validation.

Execution Flow (From Startup to Rendering)

1、启动应用 (electron .)
     ↓
2、执行 main.js(主进程启动)
     ↓
3、app.whenReady() 触发,创建 BrowserWindow
     ↓
4、BrowserWindow 启动新的渲染进程(Chromium 实例)
     ↓
5、preload.js 先执行(在隔离上下文中)
     ↓
6、index.html 加载并显示(运行前端框架)
     ↓
7、渲染进程通过 IPC 调用主进程逻辑
     ↓
8、主进程处理请求、返回结果
     ↓
9、用户关闭窗口 → 主进程监听 → app.quit()

Main-subordinate multi-window model (multi-process parallel)

  • An Electron application always has onlyA main process;
  • But can haveMultiple renderer processes (each window is independent);
  • Communication between them must go through the main process relay or useipcMain.handlePerform asynchronous bridging.

Schematic:

主进程 (Main)
 ├── 窗口1 → 渲染进程A (index.html)
 ├── 窗口2 → 渲染进程B (settings.html)
 └── 窗口3 → 渲染进程C (dashboard.html)

Electron internal core components (underlying dependencies)

Tier Technology
UI rendering layer Chromium(Blink + V8)
System interface layer Node.js(libuv + C++ bindings)
Process management layer Electron Core (C++ + JavaScript)
Application logic layer Your JS/TS code (Main + Renderer)

Your JS code runs in the Node environment encapsulated by Electron, while the rendering interface runs in the Chromium WebView.

other extensions