
Electron = Chromium + Node.js + 原生 API
┌─────────────────────────────────────┐
│ Main Process │ ← Node.js 环境
│ ┌─────────────┐ ┌───────────┐ │
│ │ BrowserWindow │ │ 系统菜单 │ │
│ │ 创建/管理窗口 │ │ 托盘/通知 │ │
│ └──────┬──────┘ └───────────┘ │
└──────────┼──────────────────────────┘
│ IPC (进程间通信)
┌──────────┼──────────────────────────┐
│ Renderer Process │ │ ← Chromium 环境
│ ┌──────┴──────┐ │
│ │ index.html │ ← 你的 UI │
│ │ renderer.js │ ← 你的逻辑 │
│ └─────────────┘ │
└─────────────────────────────────────┘
ipcMain / ipcRenderer 通信mkdir my-app && cd my-app
npm init -y
npm install electron --save-dev
main.jsconst { 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()
})
index.html<!DOCTYPE html>
<html>
<body>
<h1>Hello Electron!</h1>
<button id="btn">版本信息</button>
<script src="renderer.js"></script>
</body>
</html>
renderer.jsconst { ipcRenderer } = require('electron')
document.getElementById('btn').onclick = async () => {
const version = await ipcRenderer.invoke('get-app-version')
alert(`版本: ${version}`)
}
const { ipcMain } = require('electron')
ipcMain.handle('get-app-version', () => {
return app.getVersion()
})
// package.json
{
"main": "main.js",
"scripts": {
"start": "electron ."
}
}
npm start
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('我的应用')
// 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' })
// 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.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' })
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
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 就是最好的学习范本。