Electron

第一章:Electron 是什么

第一章:Electron 是什么

目录


引言:桌面应用的文艺复兴

在移动互联网如日中天的今天,桌面应用似乎已经”过时”了。但事实恰恰相反——越来越多的开发团队选择构建桌面客户端:VS Code、Slack、Discord、Notion、Figma Desktop、1Password……这些我们每天都在用的工具,背后都有一个共同的名字:Electron

为什么?因为桌面应用有 Web 应用无法替代的能力:

  • 系统级访问:文件系统、剪贴板、系统通知、全局快捷键
  • 离线可用:不依赖网络也能工作
  • 性能优势:本地计算、本地存储,延迟更低
  • 深度集成:系统托盘、开机自启、协议注册

而 Electron 让 Web 开发者能以极低的学习成本获得这些能力。


Electron 的诞生与历史

从 Atom Shell 到 Electron

2013 年,GitHub 的工程师赵成(Cheng Zhao,GitHub ID: zcbenz)在开发 Atom 编辑器时,需要一个能让 Web 技术驱动桌面应用的框架。当时已有 NW.js(原 node-webkit),但赵成对其架构不满意,决定从零开始。

时间线:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2013.04  Atom Shell 项目启动(GitHub 内部)
2014.05  Atom Shell 开源
2015.04  Atom Shell 更名为 Electron
2016.05  Electron v1.0 发布
2018.12  Electron 加入 OpenJS Foundation
2020.02  Electron v8.0(Chromium 80)
2021.10  Electron v15.0(新版本节奏:8 周一个大版本)
2023.05  Electron v25.0(Chromium 114)
2026.xx  Electron 持续跟进 Chromium 最新稳定版(建议以官方 release notes 为准)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

关键设计决策

Electron 在架构上与 NW.js 有本质区别。NW.js 将 Node.js 和 Chromium 的事件循环合并为一个,而 Electron 选择了分离主进程和渲染进程的设计。这个决策影响深远:

决策NW.js 的做法Electron 的做法为什么
入口文件HTML 页面JavaScript 脚本主进程不需要 GUI
Node 集成渲染进程直接用通过 preload 桥接安全性
事件循环合并 libuv 和 Chromium分开运行稳定性
上下文共享隔离安全性

核心架构:Chromium + Node.js 双引擎

Electron 的核心可以用一句话概括:把 Chromium 浏览器和 Node.js 运行时打包在一起,再加上一套操作系统 API 绑定

但这句话背后有很多值得深究的东西。

Chromium 引擎

Chromium 是 Google Chrome 浏览器的开源版本。Electron 使用的不是完整的 Chrome 浏览器,而是 Chromium 的内容模块(Content Module),它包含:

┌─────────────────────────────────────────────────────────┐
│                    Chromium Content Module                │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────────────────┐  │
│  │  Blink   │  │    V8    │  │    网络栈 (net)      │  │
│  │ 渲染引擎 │  │  JS引擎  │  │  HTTP/HTTPS/WebSocket│  │
│  └──────────┘  └──────────┘  └──────────────────────┘  │
│                                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────────────────┐  │
│  │  Skia    │  │  cc层    │  │    多媒体 (media)    │  │
│  │ 2D图形库 │  │ 合成器   │  │  音频/视频/WebRTC    │  │
│  └──────────┘  └──────────┘  └──────────────────────┘  │
│                                                          │
│  ┌──────────────────────────────────────────────────┐   │
│  │              IPC / Mojo 通信层                    │   │
│  └──────────────────────────────────────────────────┘   │
│                                                          │
└─────────────────────────────────────────────────────────┘

Blink 是渲染引擎,负责解析 HTML/CSS 并构建渲染树。它从 WebKit 分叉而来(2013 年),是 Chromium 最核心的组件。

V8 是 JavaScript 引擎,负责编译和执行 JS 代码。它同时服务于 Chromium 的渲染进程和 Electron 的 Node.js 运行时。

Skia 是 Google 的 2D 图形库,负责将渲染树绘制为像素。

Node.js 运行时

Node.js 为 Electron 带来了:

  • 文件系统访问(fs 模块)
  • 操作系统信息(os 模块)
  • 子进程管理(child_process 模块)
  • 网络服务(net、http 模块)
  • npm 生态(数百万个包)
┌──────────────────────────────────────────────────┐
│                  Node.js Runtime                  │
├──────────────────────────────────────────────────┤
│                                                   │
│  ┌───────────┐  ┌───────────┐  ┌──────────────┐ │
│  │    V8     │  │   libuv   │  │  内置模块    │ │
│  │  JS 引擎  │  │ 事件循环  │  │ fs/net/os/.. │ │
│  └───────────┘  └───────────┘  └──────────────┘ │
│                                                   │
│  ┌──────────────────────────────────────────┐    │
│  │          N-API / node-addon-api          │    │
│  │        (原生模块绑定接口)               │    │
│  └──────────────────────────────────────────┘    │
│                                                   │
└──────────────────────────────────────────────────┘

双引擎如何协作

关键问题:Chromium 有自己的事件循环(基于 MessageLoop),Node.js 也有自己的事件循环(基于 libuv)。它们怎么共存?

Electron 的做法是将 libuv 集成到 Chromium 的消息循环中。具体来说:

  1. 主进程中,使用 Chromium 的消息循环作为主循环
  2. 通过 backend fd(libuv 的文件描述符)轮询 Node.js 事件
  3. 当有 Node.js 事件时,在 Chromium 的循环中执行回调
主进程事件循环:

  ┌──────────────────────────────────────────┐
  │        Chromium MessageLoop (主循环)      │
  │                                           │
  │  ┌─────────────┐    ┌─────────────────┐  │
  │  │  UI 事件    │    │  IPC 消息       │  │
  │  │  (窗口管理) │    │  (进程间通信)   │  │
  │  └──────┬──────┘    └───────┬─────────┘  │
  │         │                   │             │
  │         ▼                   ▼             │
  │  ┌──────────────────────────────────┐    │
  │  │         事件分发器               │    │
  │  └──────────────┬───────────────────┘    │
  │                 │                         │
  │                 ▼                         │
  │  ┌──────────────────────────────────┐    │
  │  │    libuv 事件(Node.js 回调)    │    │
  │  │    通过 backend fd 集成          │    │
  │  └──────────────────────────────────┘    │
  │                                           │
  └──────────────────────────────────────────┘

这种集成方式意味着你可以在同一个进程中同时使用 Chromium API 和 Node.js API,且它们不会互相阻塞。


V8 引擎的角色

V8 是 Google 开发的高性能 JavaScript 和 WebAssembly 引擎,用 C++ 编写。在 Electron 中,V8 扮演着”双重角色”:

V8 在 Chromium 侧

在渲染进程中,V8 负责执行网页中的 JavaScript 代码。它的工作包括:

  • 解析 JS 源码为 AST(抽象语法树)
  • 通过 Ignition 解释器执行字节码
  • 通过 TurboFan 编译器对热点代码进行 JIT 优化
  • 管理内存(垃圾回收 GC)

V8 在 Node.js 侧

在主进程中,同一个 V8 引擎也负责执行 Node.js 代码。但这里有一个关键区别:

渲染进程的 V8:
  - 运行在沙箱中
  - 只能访问 Web API(DOM、fetch、WebSocket 等)
  - 受 CSP(内容安全策略)限制

主进程的 V8:
  - 无沙箱限制
  - 可以访问所有 Node.js API
  - 可以访问操作系统 API
  - 拥有完全的系统权限

V8 上下文隔离

每个 Electron 窗口的渲染进程都有独立的 V8 上下文(Context)。这意味着:

  • 窗口 A 的全局变量不会影响窗口 B
  • preload 脚本运行在独立上下文中(当 contextIsolation: true 时)
  • 即使加载了恶意网页,也无法访问 Node.js API
┌─────────────────────────────────────────────────┐
│              V8 Isolate (一个进程)                │
│                                                  │
│  ┌──────────────┐  ┌──────────────────────────┐ │
│  │  Context A   │  │     Context B            │ │
│  │  (网页世界)  │  │  (preload 隔离世界)      │ │
│  │              │  │                           │ │
│  │  window      │  │  contextBridge            │ │
│  │  document    │  │  ipcRenderer (有限)       │ │
│  │  fetch       │  │                           │ │
│  └──────────────┘  └──────────────────────────┘ │
│                                                  │
│  两个 Context 之间通过 contextBridge 安全通信    │
└─────────────────────────────────────────────────┘

多进程模型全景

Electron 继承了 Chromium 的多进程架构,这是理解 Electron 的最重要的概念

为什么需要多进程

想象一下单进程浏览器:一个标签页崩溃,整个浏览器都会挂掉。一个页面执行了死循环,所有页面都会卡住。这就是早期浏览器(IE6)的噩梦。

Chrome/Chromium 通过多进程架构解决了这个问题,Electron 继承了这一设计:

Electron 进程架构全景图:

┌──────────────────────────────────────────────────────────────┐
│                        操作系统                               │
│                                                               │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │                    主进程 (Main Process)                  │ │
│  │                    PID: 1000                              │ │
│  │                                                          │ │
│  │  ┌────────────┐ ┌──────────┐ ┌────────────────────────┐ │ │
│  │  │ app 生命   │ │ 窗口管理 │ │ 系统 API (菜单/托盘/  │ │ │
│  │  │ 周期管理   │ │ BW 创建  │ │ 通知/对话框/快捷键)   │ │ │
│  │  └────────────┘ └──────────┘ └────────────────────────┘ │ │
│  │                                                          │ │
│  │  ┌────────────────────────────────────────────────────┐  │ │
│  │  │            Node.js 完整运行时                       │  │ │
│  │  │  fs | child_process | net | crypto | ...           │  │ │
│  │  └────────────────────────────────────────────────────┘  │ │
│  └─────────┬──────────┬──────────────┬──────────────────────┘ │
│            │ IPC      │ IPC          │ IPC                     │
│            ▼          ▼              ▼                         │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐  │
│  │ 渲染进程 #1  │ │ 渲染进程 #2  │ │    GPU 进程          │  │
│  │ PID: 1001    │ │ PID: 1002    │ │    PID: 1003         │  │
│  │              │ │              │ │                       │  │
│  │ Blink+V8    │ │ Blink+V8    │ │ 图形合成、WebGL      │  │
│  │ HTML/CSS/JS │ │ HTML/CSS/JS │ │ 视频解码             │  │
│  │              │ │              │ │                       │  │
│  │ (preload:   │ │ (preload:   │ └───────────────────────┘  │
│  │  有限Node)  │ │  有限Node)  │                             │
│  └──────────────┘ └──────────────┘                            │
│                                                               │
│  还可能有:Utility 进程、网络服务进程、音频服务进程 ...       │
└──────────────────────────────────────────────────────────────┘

进程职责划分

进程类型数量职责Node.js 访问
主进程 (Main)1窗口管理、系统 API、应用生命周期✅ 完整
渲染进程 (Renderer)N页面渲染、用户界面交互⚠️ 仅通过 preload
GPU 进程1图形合成、WebGL、视频解码
实用进程 (Utility)N独立任务(如网络请求)✅ 可选

进程间通信(IPC)概览

进程之间不能直接共享内存,必须通过 IPC (Inter-Process Communication) 通信:

IPC 通信模式:

模式 1:渲染进程 → 主进程(请求/响应)
  Renderer ──invoke('channel', data)──→ Main
  Renderer ←──────── result ──────────← Main (handle)

模式 2:渲染进程 → 主进程(单向)
  Renderer ──send('channel', data)──→ Main (on)

模式 3:主进程 → 渲染进程(推送)
  Main ──webContents.send('channel', data)──→ Renderer (on)

详细的 IPC 机制将在第三章”进程模型深入”中展开。


Electron 的运行时结构

当你运行一个 Electron 应用时,实际上发生了什么?

启动流程:

  $ electron .


  ┌─────────────────────────────────┐
  │  1. 加载 Electron 二进制文件    │
  │     (包含 Chromium + Node.js)   │
  └──────────────┬──────────────────┘


  ┌─────────────────────────────────┐
  │  2. 读取 package.json           │
  │     找到 "main" 入口文件        │
  └──────────────┬──────────────────┘


  ┌─────────────────────────────────┐
  │  3. 启动主进程                   │
  │     执行 main.js                 │
  │     初始化 Node.js 运行时       │
  └──────────────┬──────────────────┘


  ┌─────────────────────────────────┐
  │  4. 创建 BrowserWindow          │
  │     ┌─启动渲染进程              │
  │     ├─加载 preload 脚本         │
  │     └─加载 HTML 页面            │
  └──────────────┬──────────────────┘


  ┌─────────────────────────────────┐
  │  5. 应用就绪,等待用户交互      │
  │     事件循环运行中...           │
  └─────────────────────────────────┘

Electron 二进制文件的组成

一个 Electron 应用的安装包(比如 VS Code)实际上包含:

MyApp.app/
├── Electron Framework/          # Chromium + Node.js 核心
│   ├── libchromiumcontent.dylib # Chromium 核心库 (~100MB)
│   ├── libnode.dylib            # Node.js 核心库
│   ├── libv8.dylib              # V8 引擎
│   ├── icudtl.dat               # 国际化数据
│   ├── resources/               # 资源文件
│   │   └── electron.asar        # Electron 内置模块
│   └── ...
├── Resources/
│   └── app.asar                 # 你的应用代码(打包后)
└── MacOS/
    └── MyApp                    # 可执行入口

这也解释了为什么 Electron 应用的体积通常在 80MB-200MB 之间——因为它包含了一整个 Chromium 浏览器。


与其他桌面框架对比

Electron vs NW.js

NW.js(原 node-webkit)是 Electron 的”前辈”,由 Roger Wang 在 Intel 创建。

              Electron                        NW.js
         ┌──────────────┐              ┌──────────────┐
         │  main.js     │              │  index.html  │
         │  (JS入口)    │              │  (HTML入口)  │
         └──────┬───────┘              └──────┬───────┘
                │                              │
         主进程和渲染进程                合并的执行环境
         严格分离                       Node+Chromium 融合
对比项ElectronNW.js
入口JS 文件HTML 文件
Node 集成主进程完整,渲染进程受限所有窗口直接使用
安全模型contextIsolation + sandbox较弱
社区规模⭐⭐⭐⭐⭐⭐⭐
大型产品VS Code, Slack, Discord微信开发者工具
构建工具electron-builder/forgenw-builder
Chrome 扩展支持部分完整
源码保护asar(可解包)V8 快照(较强)

总结:Electron 在安全性、社区支持和工具链方面远超 NW.js,是绝大多数场景的首选。

Electron vs Tauri

Tauri 是近年崛起的”Electron 杀手”,用 Rust 编写,使用系统 WebView。

  Electron 架构:                     Tauri 架构:
  ┌──────────────────┐              ┌──────────────────┐
  │  自带 Chromium   │              │  系统 WebView    │
  │  (~100MB)        │              │  (0MB, 系统自带) │
  ├──────────────────┤              ├──────────────────┤
  │  Node.js 后端    │              │  Rust 后端       │
  │  (JavaScript)    │              │  (编译为原生)    │
  ├──────────────────┤              ├──────────────────┤
  │  包体积: ~150MB  │              │  包体积: ~3-10MB │
  │  内存: ~150MB+   │              │  内存: ~30-80MB  │
  └──────────────────┘              └──────────────────┘
对比项ElectronTauri
后端语言JavaScript (Node.js)Rust
渲染引擎Chromium(自带)系统 WebView
包体积150-200MB3-10MB
内存占用150MB+30-80MB
渲染一致性⭐⭐⭐⭐⭐ (跨平台一致)⭐⭐⭐ (各平台 WebView 不同)
学习曲线低(纯 JS)中高(需要 Rust)
npm 生态完整可用前端可用,后端不可用
成熟度非常成熟快速成长中

选 Electron 当

  • 团队主力是 JS/TS 开发者
  • 需要跨平台渲染一致性
  • 依赖大量 npm 原生模块
  • 项目复杂,需要成熟生态支撑

选 Tauri 当

  • 对包体积和内存有严格要求
  • 团队有 Rust 能力
  • 应用逻辑简单,主要是前端展示
  • 不需要太多系统级 Node.js 模块

Electron vs Flutter Desktop

Flutter Desktop 使用 Dart 语言和自绘引擎(Skia),完全不依赖 WebView。

对比项ElectronFlutter Desktop
UI 技术HTML/CSS/JSDart + 自绘 Widget
渲染方式Web 页面自绘(Skia)
性能中等较高
生态npm(海量)pub.dev(快速增长)
桌面成熟度非常成熟尚在发展
代码复用Web ↔ DesktopMobile ↔ Desktop

总结:如果已有 Flutter 移动应用且需要桌面版,Flutter Desktop 是好选择。否则 Electron 的 Web 生态优势无可比拟。


Electron 适用场景分析

✅ 非常适合

  1. 效率工具 — VS Code、Notion、Obsidian

    • 需要复杂的 UI 交互
    • 需要文件系统深度访问
    • 需要离线工作能力
  2. 通讯应用 — Slack、Discord、Teams

    • Web 技术处理聊天 UI 非常高效
    • 需要系统通知、托盘图标
    • 需要全局快捷键
  3. 创作工具 — Figma Desktop、Postman

    • 复杂界面用 Web 技术构建更快
    • 需要本地文件处理能力
  4. 企业内部工具

    • 开发速度快,前端团队即可搞定
    • 不需要极致性能
    • 快速迭代需求强烈

⚠️ 需要权衡

  1. 音视频应用 — 可行但需优化
  2. 大数据量处理 — 计算密集部分应放到 Worker 或原生模块

❌ 不太适合

  1. 游戏 — 性能不够
  2. 系统级工具(杀毒软件、驱动管理)— 需要更底层的权限
  3. 对安装包大小极度敏感的应用 — 150MB 起步

一个判断框架

你需要桌面应用吗?

  ├── YES
  │     │
  │     ├── 团队技术栈?
  │     │     │
  │     │     ├── Web (JS/TS) → Electron ✓  或  Tauri
  │     │     ├── Dart/Flutter → Flutter Desktop
  │     │     ├── C++/C# → Qt / .NET MAUI
  │     │     └── Rust → Tauri ✓
  │     │
  │     ├── 包体积重要吗?
  │     │     │
  │     │     ├── 不重要 → Electron ✓
  │     │     └── 非常重要 → Tauri
  │     │
  │     └── 需要跨平台渲染一致吗?
  │           │
  │           ├── YES → Electron ✓ (自带 Chromium)
  │           └── NO  → Tauri (用系统 WebView)

  └── NO → 用 Web 应用 / PWA

深入理解

Electron 的版本与 Chromium 的关系

Electron 的版本号与其内置的 Chromium 版本紧密关联。每个 Electron 大版本都会升级到最新稳定版 Chromium:

Electron 版本    Chromium 版本    Node.js 版本
──────────────  ─────────────   ─────────────
v22.x           Chromium 108    Node 16.x
v24.x           Chromium 112    Node 18.x
v26.x           Chromium 116    Node 18.x
v28.x           Chromium 120    Node 18.x
v30.x           Chromium 124    Node 20.x
v32.x           Chromium 128    Node 20.x
v34+            (请以官方发布说明为准)

这意味着:

  • Electron 应用自动获得最新的 Web API 支持
  • CSS 新特性(如 Container Queries、:has() 选择器)随 Chromium 升级而可用
  • JavaScript 新特性(如 top-level await)同步可用

为什么 Electron 应用”吃内存”

这是最常见的批评。让我们分析原因:

一个简单 Electron 应用的内存构成:

  主进程:
    V8 堆:           ~20MB
    Node.js 运行时:  ~15MB
    Chromium 基础:    ~30MB
                      ──────
    小计:             ~65MB

  每个渲染进程:
    V8 堆:           ~20MB
    Blink 渲染引擎:  ~25MB
    页面内容:         ~10-50MB(取决于页面复杂度)
                      ──────
    小计:             ~55-95MB

  GPU 进程:           ~50-100MB

  总计 (1个窗口):     ~170-260MB

对比原生应用(如用 Swift/Cocoa 写的 macOS 应用)可能只用 30-50MB。这就是 “Electron 税”——你用开发效率换取了运行时开销。

Electron 的安全边界

安全边界图:

  互联网                 Electron 应用
  ┌─────┐    ┌─────────────────────────────────────────┐
  │     │    │                                          │
  │ 恶意│    │  渲染进程 (沙箱)                         │
  │ 网页│───→│  ┌───────────────────────────────┐      │
  │     │    │  │  只能用 Web API               │      │
  │     │    │  │  不能访问文件系统             │      │
  │     │    │  │  不能执行系统命令             │      │
  │     │    │  └──────────────┬────────────────┘      │
  └─────┘    │                 │ contextBridge          │
             │                 │ (白名单 API)           │
             │                 ▼                        │
             │  ┌───────────────────────────────┐      │
             │  │  主进程(完全权限)           │      │
             │  │  ● 文件系统 ● 子进程          │      │
             │  │  ● 网络 ● 系统 API            │      │
             │  └───────────────────────────────┘      │
             └─────────────────────────────────────────┘

  安全的关键:渲染进程到主进程的桥只暴露白名单 API

常见问题

Q1: Electron 应用能做到原生应用的体验吗?

可以接近,但有差距。VS Code 是最好的例子——大多数人甚至不知道它是 Electron 应用。关键在于:

  • 避免不必要的 DOM 操作
  • 使用虚拟列表处理大量数据
  • 善用 Web Worker 处理计算密集任务
  • 利用 CSS GPU 加速(transform、opacity)

Q2: 为什么不直接用 Chrome + PWA?

PWA (Progressive Web App) 确实能做很多事,但缺少:

  • 文件系统完全访问权限
  • 系统托盘
  • 全局快捷键
  • 原生菜单
  • 自动更新控制
  • 自定义协议(deeplink)

Q3: Electron 应用安全吗?

如果正确配置,是安全的。关键设置:

  • nodeIntegration: false(默认)
  • contextIsolation: true(默认)
  • sandbox: true
  • 配置 CSP 头
  • 不要加载不受信任的远程内容

Q4: Electron 的未来会怎样?

Electron 仍然是最成熟的跨平台桌面框架。但竞争在加剧:

  • Tauri 在包体积和性能方面挑战 Electron
  • WebView2(微软)让 Windows 应用可以用系统 WebView
  • PWA 能力持续增强

短期内(3-5 年),Electron 的生态优势仍然不可替代。


实践建议

1. 入门路径

推荐学习路线:

  Web 基础 (HTML/CSS/JS)


  Node.js 基础 (模块、事件、文件系统)


  Electron 快速上手 (本教程 Part 1)


  进程模型与 IPC (第三章重点)


  安全最佳实践 (第六章)


  打包与分发 (第八章)


  实战项目

2. 开发环境准备

# 确保 Node.js 版本 >= 18
node --version

# 推荐使用 nvm 管理 Node.js 版本
nvm install 20
nvm use 20

# 安装推荐工具
# 不建议全局安装 electron,本地项目 devDependency 更安全可复现
npm install -g @electron-forge/cli  # 脚手架工具

3. 资源推荐

4. 避免常见新手错误

  1. ❌ 不要在渲染进程直接使用 require('fs')
  2. ❌ 不要设置 nodeIntegration: true
  3. ❌ 不要忽略 CSP 配置
  4. ❌ 不要把敏感数据存在 localStorage
  5. ✅ 始终使用 contextBridge 暴露 API
  6. ✅ 始终验证 IPC 消息的来源和参数
  7. ✅ 使用 TypeScript 获得更好的开发体验

本章小结

Electron 通过将 Chromium 和 Node.js 结合,让 Web 开发者能够构建功能丰富的桌面应用。它的核心架构特点是:

  1. 双引擎:Chromium 负责渲染,Node.js 提供系统能力
  2. 多进程:主进程管控全局,渲染进程各自独立
  3. IPC 通信:进程间通过消息传递协作
  4. 安全模型:沙箱 + 上下文隔离 + 白名单 API

下一章,我们将动手创建第一个 Electron 应用,从 npm init 开始。


下一篇02 - 从零搭建你的第一个 Electron 应用