Electron Isn't a Shell Browser: The Dual-Process, IPC, and Native API Architecture That Runs VS Code and Figma
Prerequisite Basics: Three Core Concepts for Understanding Electron
Before diving into Electron, let's lay the groundwork with three core concepts to build your foundational understanding:
The Natural Boundary of Web Technology: The Browser Sandbox Regular HTML/CSS/JS code can only run within the browser's sandbox environment — for security, the browser strictly limits web pages' access to operating system resources. For example, a web page cannot directly read or write local files, invoke global shortcuts, or modify system configurations. This is both the security advantage of web applications and the core limitation that prevents them from replacing desktop applications.
Two Technological Cornerstones: Chromium and Node.js
- Chromium: The open-source browser kernel project by Google, which mainstream browsers like Chrome and Edge are based on. It is responsible for parsing HTML, rendering pages, and executing JS scripts, providing full Web standard API support. Its core capability is "rendering the interface."
- Node.js: A server-side JS runtime based on the Chrome V8 engine, allowing JS code to run directly on the operating system outside the browser. It has built-in system-level capabilities like file system, network, and process management. Its core capability is "calling system resources." These two were originally completely independent runtime environments, each with its own role, with no intersection.
The Core Pain Point of Traditional Desktop Development Before Electron, developing a Windows application required C#/WPF, a macOS application required Objective-C/Swift, and a Linux application required C++/GTK. A single product needed to maintain three sets of technology stacks and codebases, leading to high development costs, slow iteration speeds, and extreme unfriendliness to frontend developers.
Electron's core value is to resolve the above contradictions — integrating Chromium and Node.js into a single runtime system, allowing a cross-platform desktop application with full system capabilities to be developed using a single web technology stack.
I. Core Definition and Overall Architecture of Electron
1. Definition
Electron is an open-source, cross-platform desktop application development framework created by GitHub. It allows developers to build desktop applications that run on Windows, macOS, and Linux using only web technologies like HTML, CSS, and JavaScript. Simply put, Electron is like packaging a customized browser kernel and a Node.js runtime into desktop software, giving web pages all the system capabilities of a desktop application.
2. Underlying Principles
Electron uses a three-layer core architecture, where the three layers work together to complete the entire chain from page rendering to system calls:
- Chromium Kernel Layer: Responsible for page rendering, DOM parsing, and JS execution, providing full Web standard APIs. It is the foundation of the UI interaction layer.
- Node.js Runtime Layer: Provides a server-side JS runtime environment with built-in general system capabilities like file system, network, and process management. It is the fundamental carrier for native capabilities.
- Native API Adaptation Layer: Encapsulates the native interfaces of the three major operating systems (Win32/Cocoa/GTK) via C++, exposing a unified JS call entry point upwards and automatically smoothing out differences between different systems.
The three have clear divisions of labor: Chromium manages interface rendering and interaction, Node.js manages general system calls, and the native adaptation layer manages desktop-specific capabilities and cross-platform compatibility.
3. Concrete Example
A minimal Electron application requires only 3 core files:
package.json: Project configuration file, specifying the application entry point and dependencies.main.js: Main process entry code, responsible for creating the application window.index.html: The frontend page loaded by the window.
Core code example for the main process entry:
// Import Electron main process modules
const { app, BrowserWindow } = require('electron')
// Create the main window after the application is initialized
app.whenReady().then(() => {
const mainWindow = new BrowserWindow({ width: 800, height: 600 })
mainWindow.loadFile('index.html') // Load the frontend page, starting the renderer process
})
After running the above code, an 800×600 desktop window will pop up, rendering the custom HTML page inside — this is a most basic Electron application.
4. Application Scenarios
Electron's core value is lowering the barrier to cross-platform desktop development. Typical applicable scenarios include:
- Having a mature web product that needs a quick desktop implementation, allowing reuse of over 90% of the frontend code.
- Teams primarily using a web technology stack who don't want to invest in learning native development languages like C++/Swift.
- Tool-type or internal enterprise desktop applications that require rapid iteration and frequent feature updates.
- Products with high requirements for cross-platform experience consistency, hoping for completely unified interaction logic across three systems.
II. The Dual-Process Runtime Model
1. Definition
The dual-process model is the most core runtime mechanism of Electron. It divides an application into two types of processes with completely isolated responsibilities:
- Main Process: Each Electron application has one and only one. It is the application's entry point and master control center, responsible for managing the application lifecycle, creating windows, and calling system native APIs.
- Renderer Process: Each time an Electron window is opened, an independent renderer process is started. It is responsible for rendering web pages, handling UI interactions, and running frontend business code.
2. Underlying Principles
The dual-process design is not original to Electron but is inherited from Chromium's multi-process security architecture: to prevent a single page crash from causing a total application crash and to prevent malicious web pages from stealing system resources, the browser places each tab in an independent sandboxed process. Electron follows this design philosophy:
- The main process has full system privileges and can call all Node.js and native APIs, but it is not responsible for UI rendering.
- Renderer processes run in a sandbox by default and can only access Web APIs. They cannot directly touch system resources. Even if a page crashes, it does not affect the overall application.
The core differences between the two can be intuitively compared in the following table:
| Comparison Dimension | Main Process | Renderer Process |
|---|---|---|
| Quantity | Globally unique | 1 per window, supports multiple instances |
| Core Responsibility | Application lifecycle, window management, native capability calls | Page rendering, UI interaction, frontend business logic |
| Runtime Environment | Node.js + Electron Native API | Chromium Sandbox Environment |
| System Privileges | Full system privileges | No system privileges by default, requires indirect calls |
| Crash Impact | Causes the entire application to crash | Only the current window is abnormal, does not affect the whole |
3. Concrete Example
Take Discord, a chat application built on Electron, as an example:
- In the system task manager, you will see multiple Discord processes: 1 main process responsible for background message push, tray icon, and system notifications; each chat window and settings page corresponds to an independent renderer process.
- UI updates like typing in the chat box and sending emojis are all handled by the renderer process; when a new message arrives, the main process calls the system notification interface to pop up an alert, then passes the message data to the renderer process to display on the interface.
- If a chat page crashes due to a bug, only that window will go white. The entire application will not exit, and the chat functionality in other windows will not be affected.
4. Application Scenarios
The design goal of the dual-process model is to ensure stability and security. Core application scenarios include:
- Multi-window applications: Each window runs independently, preventing a single window exception from dragging down the entire application.
- Applications integrating third-party scripts/plugins: Isolating third-party code through sandboxing to prevent security risks from spreading.
- Tool-type applications with high availability requirements: Ensuring overall stability and reducing the impact scope of single-module crashes.
III. Inter-Process Communication (IPC) Mechanism
1. Definition
IPC stands for Inter-Process Communication. It is the core mechanism in Electron for passing data and calling methods between the main process and renderer processes. Because the two processes have isolated memory and privileges, they cannot directly call each other's functions and must complete data interaction through an IPC channel.
Electron provides two core IPC modules:
ipcMain: Runs in the main process, used to listen for messages sent from renderer processes, handle requests, and return results.ipcRenderer: Runs on the renderer process side (must be securely exposed via a preload script), used to send messages to the main process and listen for replies from the main process.
Two key terms are added here:
- Preload Script: A special script that runs before the renderer process sandbox. It can access Node.js APIs and securely expose methods to the frontend page through specified interfaces. It is the recommended communication bridge in modern Electron.
- Context Isolation: A security mechanism enabled by default in Electron. It ensures that the preload script and the frontend page script run in independent JS contexts, preventing the page from maliciously tampering with the preload interface and directly gaining Node.js privileges.
2. Underlying Principles
The underlying layer of IPC is based on Chromium's inter-process communication pipeline, using an event-driven, asynchronous communication model:
- The sender sends a request to the other process by specifying an event name with attached parameter data.
- The receiver pre-listens for the corresponding event name and executes the corresponding logic upon receiving the request.
- The receiver can return the processing result to the sender via a callback or Promise.
Based on the communication pattern, it is divided into two common methods:
- One-way Communication: The renderer process sends a message, the main process listens, no result needs to be returned (
send/onpattern). - Two-way Communication: The renderer process initiates a request, the main process processes it and returns a result (
invoke/handlepattern, implemented based on Promises).
3. Concrete Example
Take the "save file" function of a desktop notepad as an example. The complete communication flow is as follows:
- In the preload script, securely expose the save file interface to the renderer process:
// preload.js (Preload Script)
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
saveFile: (content) => ipcRenderer.invoke('save-file', content)
})
- In the main process, listen for the save request and call the file system to write the file:
// main.js (Main Process)
const { ipcMain } = require('electron')
const fs = require('fs')
ipcMain.handle('save-file', async (event, content) => {
fs.writeFileSync('note.txt', content)
return 'Save successful'
})
- The frontend page clicks a button and calls the exposed interface to trigger the save operation:
// Renderer process frontend code
document.querySelector('#saveBtn').addEventListener('click', async () => {
const content = document.querySelector('#editor').value
const result = await window.electronAPI.saveFile(content)
alert(result)
})
Throughout the entire process, the frontend page cannot directly call the fs module. It can only initiate requests through a predefined whitelist interface, and the main process completes the system operation, all under security control.
4. Application Scenarios
IPC is the "neural network" of an Electron application. All cross-process interactions rely on it. Typical scenarios include:
- Frontend UI triggering system operations: file read/write, system notifications, window maximize/minimize.
- Main process pushing events to pages: system theme switching, global shortcut triggers, background message push.
- Data synchronization between multiple windows: data sharing between different windows via the main process as a relay.
IV. Native Capability Invocation and Cross-Platform Compatibility
1. Definition
Electron has a built-in, complete native API system. Developers do not need to write native code like C++/Objective-C; they can call the operating system's native functions using only JS, and it automatically adapts to the three major platforms of Windows, macOS, and Linux, eliminating the need for separate development for different systems.
2. Underlying Principles
Electron's native capabilities are implemented in three layers, balancing generality and extensibility:
- Node.js General Native Capabilities: General system capabilities like file system, network, and process management are provided by Node.js's built-in modules and can be called directly by the main process.
- Electron Desktop-Specific APIs: Desktop-specific capabilities like window management, system notifications, tray icons, and context menus are officially packaged by Electron, covering over 90% of common desktop needs. The underlying layer interfaces with different systems' native APIs via C++, exposing a unified JS API upwards and automatically handling platform differences.
- Native Extension Modules: For special customization needs not built into Electron, it supports accessing Node.js native C++ extensions, which can call any system native interface.
3. Concrete Example
Take the system notification function as an example. Full-platform native adaptation can be achieved with just a few lines of code:
// Main process code
const { Notification } = require('electron')
new Notification({
title: 'Task Complete',
body: 'The file has been successfully exported to the desktop'
}).show()
On Windows, this code will call the system notification center to pop up a notification; on macOS, it will call the system notification bar. The style perfectly matches the native design of each system, and the developer does not need to do any platform adaptation. Besides this, common desktop features like tray icons, file drag-and-drop, system theme adaptation, and auto-start on boot all have corresponding single-line APIs.
4. Application Scenarios
Native capabilities are the core hallmark that distinguishes Electron from a "webpage shortcut." Typical application scenarios include:
- System tray resident applications: Chat tools, download tools that minimize to the background and run continuously.
- Fitting into the desktop interaction experience: Global shortcuts, file associations, system context menu extensions.
- System-level hardware invocation: Camera, microphone, Bluetooth, reading system hardware information.
- System experience adaptation: Following the system's dark/light mode, adapting to high-DPI screens.
Common Misconceptions
- Misconception: Electron is just a "shell browser" with no technical depth. In reality, Electron's underlying layer involves complex technical fields like Chromium customization, process scheduling, security sandboxing, cross-platform native adaptation, and performance optimization. An Electron application with a good user experience needs to solve a large number of engineering problems such as memory optimization, startup speed, white screen issues, security hardening, and native interaction adaptation. It is by no means a simple shell.
- Misconception: Electron applications are always laggy and have high memory usage. High memory usage is an inherent characteristic of the Chromium kernel, but it is not unoptimizable. Unoptimized Electron applications do tend to have high memory usage, but after reasonable optimization (reducing the number of renderer processes, frontend lazy loading, memory leak management), an application can run smoothly with memory controlled within a reasonable range. Lag issues mostly stem from unreasonable frontend code, not Electron itself.
- Misconception: Renderer processes can directly use all Node.js APIs. Early versions of Electron allowed renderer processes to directly enable Node.js integration, but for security reasons, Electron 12+ versions enable context isolation and sandbox mode by default. Renderer processes cannot directly access Node.js APIs. Directly disabling security configurations brings serious risks; if an XSS vulnerability appears on a frontend page, an attacker could directly gain full system privileges.
- Misconception: Electron can only make simple tools and cannot support complex applications. In reality, large, complex applications like VS Code (code editor), Figma Desktop (design tool), Discord (instant messaging), and Slack (enterprise collaboration) are all built on Electron. They carry complex business logic, high-performance rendering, and massive user bases, fully capable of supporting heavy-duty desktop application scenarios.
Actionable Practical Advice
Getting Started
- Use the official scaffolding
electron-forgeto initialize projects. It has built-in complete workflows for development, packaging, and publishing, avoiding pitfalls from manual environment configuration. - Prioritize learning the official documentation and be cautious about referencing outdated third-party tutorials. Electron versions iterate quickly, and APIs change frequently.
Security Standards (Must Follow)
- Always enable
contextIsolation: trueandsandbox: true. Do not disable context isolation and sandboxing. - All interactions between the renderer process and the main process must go through a preload script +
contextBridgeto expose a whitelist of interfaces. Do not directly expose the fullipcRendererto the page. - When loading remote pages, a Content Security Policy (CSP) must be enabled, and
nodeIntegrationmust be disabled to prevent XSS vulnerabilities from escalating into system privilege leaks.
Performance Optimization
- Reduce the number of unnecessary renderer processes. Reuse windows for same-origin pages as much as possible to avoid memory spikes from multiple windows.
- Put heavy computational logic into the main process or a separate utility process. Do not block the renderer process and cause UI lag.
- Perform routine performance optimization on frontend code: virtual lists, lazy loading, timely cleanup of event listeners to avoid memory leaks.
- Optimize startup speed: load modules on demand, reduce main process initialization logic, and use V8 snapshot technology.
Packaging and Publishing
- Use
electron-builderorelectron-forgefor production packaging. They support multi-platform packaging, code signing, and automatic updates. - Adapt installation packages for different platforms: generate exe/msi for Windows, dmg for macOS, and deb/rpm for Linux.
- Configure incremental automatic updates to avoid users downloading the full installation package for every update.