文档样式:

Electron 桌面框架使用说明


一、什么是 Electron?

Electron = Chromium + Node.js + 原生 API

  • HTML/CSS/JavaScript 构建跨平台桌面应用
  • 一套代码打包出 Windows + macOS + Linux 三个平台
  • 代表产品:VS Code、Slack、Discord、Figma、Notion、钉钉

二、核心架构

┌─────────────────────────────────────┐
│           Main Process              │ ← Node.js 环境
│   ┌─────────────┐   ┌───────────┐  │
│   │  BrowserWindow │   │ 系统菜单  │  │
│   │  创建/管理窗口  │   │ 托盘/通知 │  │
│   └──────┬──────┘   └───────────┘  │
└──────────┼──────────────────────────┘
           │ IPC (进程间通信)
┌──────────┼──────────────────────────┐
│   Renderer Process        │          │ ← Chromium 环境
│   ┌──────┴──────┐                   │
│   │  index.html  │  ← 你的 UI       │
│   │  renderer.js │  ← 你的逻辑       │
│   └─────────────┘                   │
└─────────────────────────────────────┘
  • 主进程 (Main):Node.js 环境,管理系统窗口、菜单、原生操作
  • 渲染进程 (Renderer):Chromium 环境,跑网页 UI
  • IPC:两者通过 ipcMain / ipcRenderer 通信

三、快速开始

1. 创建项目

mkdir my-app && cd my-app
npm init -y
npm install electron --save-dev

2. 入口文件 main.js

const { app, BrowserWindow } = require('electron')

function createWindow() {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      nodeIntegration: true,          // 渲染进程可访问 Node.js
      contextIsolation: false,        // 关闭隔离(开发方便)
      preload: __dirname + '/preload.js'
    }
  })
  win.loadFile('index.html')
}

app.whenReady().then(createWindow)

// macOS 点击 Dock 图标重新创建窗口
app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

// 所有窗口关闭时退出(macOS 除外)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

3. 页面 index.html

<!DOCTYPE html>
<html>
<body>
  <h1>Hello Electron!</h1>
  <button id="btn">版本信息</button>
  <script src="renderer.js"></script>
</body>
</html>

4. 渲染进程 renderer.js

const { ipcRenderer } = require('electron')

document.getElementById('btn').onclick = async () => {
  const version = await ipcRenderer.invoke('get-app-version')
  alert(`版本: ${version}`)
}

5. 主进程处理 IPC

const { ipcMain } = require('electron')

ipcMain.handle('get-app-version', () => {
  return app.getVersion()
})

6. 启动

// package.json
{
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  }
}
npm start

四、核心 API 速查

窗口管理

const win = new BrowserWindow({ ... })

win.loadURL('https://example.com')   // 加载 URL
win.loadFile('index.html')            // 加载本地文件
win.setTitle('新标题')                // 改标题
win.minimize() / maximize() / close()
win.webContents.openDevTools()        // 打开开发者工具

// 无边框窗口
new BrowserWindow({
  frame: false,
  transparent: true,     // 透明背景
  titleBarStyle: 'hidden'
})

系统功能

const { clipboard, nativeImage, shell, dialog, Notification } = require('electron')

clipboard.writeText('文字')           // 剪贴板
shell.openExternal('https://...')     // 打开外部链接
shell.openPath('/path/to/file')      // 打开文件/文件夹

dialog.showOpenDialog({              // 文件选择对话框
  properties: ['openFile', 'multiSelections']
})

dialog.showSaveDialog({              // 保存对话框
  defaultPath: 'report.pdf'
})

new Notification({                   // 系统通知
  title: '提示',
  body: '下载完成'
}).show()

系统托盘

const { Tray, Menu } = require('electron')

const tray = new Tray('icon.png')
const contextMenu = Menu.buildFromTemplate([
  { label: '显示窗口', click: () => win.show() },
  { label: '退出', click: () => app.quit() }
])
tray.setContextMenu(contextMenu)
tray.setToolTip('我的应用')

五、进程间通信(IPC)

方式一:invoke / handle(推荐)

// main.js
ipcMain.handle('save-file', async (event, data) => {
  const fs = require('fs')
  fs.writeFileSync('data.json', JSON.stringify(data))
  return { success: true }
})

// renderer.js
const result = await ipcRenderer.invoke('save-file', { key: 'value' })

方式二:send / on(双向)

// main.js
ipcMain.on('msg', (event, arg) => {
  event.reply('msg-reply', '收到: ' + arg)
})

// renderer.js
ipcRenderer.send('msg', '你好')
ipcRenderer.on('msg-reply', (event, arg) => {
  console.log(arg)
})

主进程 → 渲染进程(推送)

// main.js
win.webContents.send('update', { progress: 80 })

// renderer.js
ipcRenderer.on('update', (event, data) => {
  console.log(data.progress)
})

六、安全实践

✅ 生产环境推荐配置

webPreferences: {
  nodeIntegration: false,       // 关闭 Node 集成
  contextIsolation: true,       // 开启上下文隔离
  preload: path.join(__dirname, 'preload.js'),  // 用 preload 暴露接口
  sandbox: true                 // 沙箱模式
}

Preload 脚本模式

// preload.js - 安全的桥接层
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  saveFile: (data) => ipcRenderer.invoke('save-file', data),
  getVersion: () => ipcRenderer.invoke('get-version'),
  onUpdate: (callback) => ipcRenderer.on('update', callback)
})
// renderer.js - 只能通过暴露的 API
window.electronAPI.saveFile({ name: 'test' })

七、打包发布

使用 electron-builder(推荐)

npm install electron-builder --save-dev
// package.json
{
  "build": {
    "appId": "com.example.myapp",
    "productName": "我的应用",
    "directories": { "output": "release" },
    "mac": { "target": ["dmg", "zip"] },
    "win": { "target": ["nsis", "portable"] },
    "linux": { "target": ["AppImage", "deb"] },
    "nsis": {
      "oneClick": false,
      "allowToChangeInstallationDirectory": true
    }
  },
  "scripts": {
    "build:mac": "electron-builder --mac",
    "build:win": "electron-builder --win",
    "build:linux": "electron-builder --linux"
  }
}
npm run build:mac       # 打包 macOS .dmg
npm run build:win       # 打包 Windows .exe

使用 electron-forge(官方推荐)

npm install @electron-forge/cli --save-dev
npx electron-forge import    # 自动配置
npm run make                 # 打包所有平台

八、常用开发工具

工具 用途
Electron DevTools win.webContents.openDevTools()
electron-reload 热重载(npm install electron-reload
React/Vue DevTools 安装 Chrome 扩展到 Electron
Spectron 集成测试
electron-log 日志记录
electron-store 持久化配置(本地 JSON 存储)

热重载开发

// main.js 开发环境
if (process.env.NODE_ENV === 'development') {
  require('electron-reload')(__dirname, {
    electron: path.join(__dirname, 'node_modules', '.bin', 'electron')
  })
}

九、错误排查

常见问题:

1. 白屏?
   → 检查 loadFile 路径是否正确
   → 检查 DeveTools 控制台有无报错

2. nodeIntegration 失效?
   → 检查 contextIsolation 是否设为 true
   → 用 preload 脚本暴露接口

3. 打包后文件找不到?
   → 用 __dirname 或 app.getAppPath() 获取正确路径
   → __static 在打包后可能失效

4. macOS 窗口不退出?
   → 按上面示例处理 window-all-closed 事件

十、项目结构推荐

my-app/
├── main/                   # 主进程代码
│   ├── main.js
│   ├── menu.js             # 菜单配置
│   └── ipc.js              # IPC 处理
├── renderer/               # 渲染进程(前端)
│   ├── index.html
│   ├── renderer.js
│   └── styles.css
├── preload/                # 预加载脚本
│   └── preload.js
├── assets/                 # 静态资源
│   ├── icons/
│   └── images/
├── package.json
└── electron-builder.yml    # 打包配置

一句话总结: Electron 让前端工程师用 Web 技术写桌面应用,核心是理解主进程/渲染进程的分离,以及通过 IPC 安全地通信。VS Code 就是最好的学习范本。