home / skills / aaaaqwq / agi-super-skills / electron-app-dev

electron-app-dev skill

/skills/backend/electron-app-dev

This skill helps you scaffold secure Electron projects with electron-vite, TypeScript, React, and safer IPC, window management, and packaging.

npx playbooks add skill aaaaqwq/agi-super-skills --skill electron-app-dev

Review the files below or copy the command above to add this skill to your agents.

Files (6)
SKILL.md
13.0 KB
---
name: electron-app-dev
description: |
  Electron桌面应用开发专家。精通electron-vite、TypeScript、React、IPC通信、窗口管理、原生功能集成等Electron全栈开发技术。

  适用场景:
  - 创建electron-vite + TypeScript + React项目
  - 实现安全的IPC通信
  - 窗口管理(创建、控制、多窗口、状态持久化)
  - 原生功能(系统托盘、菜单、通知、文件对话框)
  - electron-builder打包分发
  - 自动更新和代码签名

  所有代码遵循最新Electron安全最佳实践:contextIsolation开启、nodeIntegration关闭、sandbox模式开启、contextBridge安全暴露IPC。
---

# ⚡ Electron 桌面应用开发专家

老王我搞Electron好多年了,这玩意儿写跨平台应用真tm香!

## 快速开始:创建新项目

使用内置脚本创建最佳实践Electron项目:

```bash
python "C:/Users/Administrator/.claude/skills/electron-app-dev/scripts/create_electron_app.py" my-app
cd my-app
npm install
npm run dev
```

**生成项目包含:**
- electron-vite:全进程极速热更新
- TypeScript + React:类型安全开发
- contextBridge安全IPC模式
- electron-builder打包配置

---

## 核心安全原则(不可妥协)

永远强制执行这些安全配置:

```typescript
webPreferences: {
  preload: path.join(__dirname, '../preload/index.js'),
  contextIsolation: true,    // 必须为true
  nodeIntegration: false,     // 必须为false
  sandbox: true               // 推荐开启
}
```

**为什么重要:**
- `contextIsolation: true` - 隔离preload脚本与渲染器
- `nodeIntegration: false` - 防止渲染器直接访问Node.js
- `sandbox: true` - 进一步限制渲染器进程能力

---

## IPC通信模式

### 唯一正确方式:contextBridge + 白名单

**Preload (preload/index.ts):**
```typescript
const SEND_CHANNELS = ['app-ready']
const INVOKE_CHANNELS = ['get-app-info', 'save-file']

contextBridge.exposeInMainWorld('electronAPI', {
  send: (channel, ...args) => {
    if (SEND_CHANNELS.includes(channel)) {
      ipcRenderer.send(channel, ...args)
    }
  },
  invoke: async (channel, ...args) => {
    if (INVOKE_CHANNELS.includes(channel)) {
      return await ipcRenderer.invoke(channel, ...args)
    }
    return Promise.reject(new Error(`Invalid channel: ${channel}`))
  }
})
```

**Main Process (main/index.ts):**
```typescript
function validateSender(frame: Electron.WebFrameMain | null): boolean {
  if (!frame) return false
  const url = new URL(frame.url)
  const allowedHosts = ['localhost', 'yourdomain.com']
  return allowedHosts.includes(url.hostname) || url.protocol === 'file:'
}

ipcMain.handle('get-app-info', (event) => {
  if (!validateSender(event.senderFrame)) return null
  return { name: app.getName(), version: app.getVersion() }
})
```

---

## 常见坑与解决方案(老王血泪经验)

### 1. 白屏问题(DevTools可以加载但正常不行)

**症状:** 开发环境正常,打包后白屏

**原因:** 协议问题或路径错误

```typescript
// ❌ 错误写法
win.loadURL('http://localhost:5173')

// ✅ 正确写法
if (app.isPackaged) {
  win.loadFile(path.join(__dirname, '../renderer/index.html'))
} else {
  win.loadURL('http://localhost:5173')
}

// ✅ 生产环境加载方案
win.loadFile(path.join(__dirname, '../renderer/index.html'))
win.webContents.openDevTools()  // 先看看能不能加载
```

### 2. 内存泄漏(IPC监听器没移除)

**症状:** 应用用久了越来越卡

```typescript
// ❌ 错误写法
useEffect(() => {
  window.electronAPI.on('update', callback)
  // 没有清理函数!
}, [])

// ✅ 正确写法
useEffect(() => {
  const callback = (data) => console.log(data)
  window.electronAPI.on('update', callback)

  return () => {
    window.electronAPI.removeListener('update', callback)  // 必须移除!
  }
}, [])
```

### 3. 窗口状态不保存(用户每次打开都要重新调整大小)

```typescript
import Store from 'electron-store'

const store = new Store()

const win = new BrowserWindow({
  x: store.get('window.x', undefined),
  y: store.get('window.y', undefined),
  width: store.get('window.width', 1200),
  height: store.get('window.height', 800),
})

// 窗口关闭时保存状态
win.on('close', () => {
  const bounds = win.getBounds()
  store.set('window', {
    x: bounds.x,
    y: bounds.y,
    width: bounds.width,
    height: bounds.height,
  })
})
```

### 4. DevTools在开发环境自动打开,生产环境忘记关

```typescript
const win = new BrowserWindow({
  // ...
  webPreferences: {
    // ...
  }
})

// 只在开发环境打开
if (process.env.NODE_ENV === 'development') {
  win.webContents.openDevTools()
}

// 或者用快捷键
app.on('ready', () => {
  // ...
  globalShortcut.register('CommandOrControl+Shift+I', () => {
    win.webContents.toggleDevTools()
  })
})
```

---

## 性能优化(让应用飞起来)

### 1. 懒加载窗口(别tm一次性创建所有窗口)

```typescript
// ❌ 错误写法:启动时创建所有窗口
const mainWindow = new BrowserWindow({ /* ... */ })
const settingsWindow = new BrowserWindow({ /* ... */ })
const aboutWindow = new BrowserWindow({ /* ... */ })

// ✅ 正确写法:按需创建
let settingsWindow: BrowserWindow | null = null

function openSettings() {
  if (!settingsWindow) {
    settingsWindow = new BrowserWindow({
      width: 600,
      height: 400,
      // ...
    })
    settingsWindow.on('closed', () => {
      settingsWindow = null  // 关闭后释放内存
    })
  }
  settingsWindow.show()
}
```

### 2. BrowserView替代多个窗口(更省内存)

```typescript
// 一个主窗口 + 多个BrowserView
const win = new BrowserWindow({ width: 1200, height: 800 })

const view1 = new BrowserView()
win.setBrowserView(view1)
view1.setBounds({ x: 0, y: 0, width: 600, height: 800 })
view1.webContents.loadURL('https://example.com')

const view2 = new BrowserView()
view2.setBounds({ x: 600, y: 0, width: 600, height: 800 })
view2.webContents.loadURL('https://another.com')
```

### 3. 防抖IPC调用(别疯狂发请求)

```typescript
// Renderer - 使用lodash debounce
import { debounce } from 'lodash'

const debouncedSave = debounce((content) => {
  window.electronAPI.invoke('save-file', content)
}, 500)

// 每次输入都调用,但实际只500ms执行一次
inputElement.addEventListener('input', (e) => {
  debouncedSave(e.target.value)
})
```

### 4. 大文件用流式传输(别tm一次性读进内存)

```typescript
// ❌ 错误写法:一次性读取大文件
ipcMain.handle('read-file', async (event, filePath) => {
  const content = fs.readFileSync(filePath, 'utf-8')  // 可能几百MB
  return content
})

// ✅ 正确写法:流式传输
ipcMain.handle('read-file-stream', async (event, filePath) => {
  const stream = fs.createReadStream(filePath)
  const chunks: Buffer[] = []

  for await (const chunk of stream) {
    chunks.push(chunk)
    event.sender.send('file-chunk', chunk)  // 分批发送
  }

  return Buffer.concat(chunks).toString('utf-8')
})
```

---

## 调试技巧(快速定位问题)

### 1. VS Code调试配置

```json
// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Main Process",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
      "windows": {
        "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
      },
      "args": ["."],
      "outputCapture": "std"
    },
    {
      "name": "Debug Renderer Process",
      "type": "chrome",
      "request": "launch",
      "url": "http://localhost:5173",
      "webRoot": "${workspaceFolder}/src"
    }
  ]
}
```

### 2. Chrome DevTools快捷键

| 快捷键 | 功能 |
|--------|------|
| Ctrl+Shift+I | 打开/关闭DevTools |
| Ctrl+Shift+J | 打开控制台 |
| Ctrl+Shift+C | 元素选择器 |
| F1 | 打开命令面板 |

### 3. 主进程日志(别光用console.log)

```typescript
import winston from 'winston'

const logger = winston.createLogger({
  transports: [
    new winston.transports.File({ filename: 'main.log' }),
    new winston.transports.Console()
  ]
})

logger.info('Application started')
logger.error('Something went wrong', error)
```

### 4. 内存监控

```typescript
// 定期检查内存使用
setInterval(() => {
  const usage = process.cpuUsage()
  const memory = process.memoryUsage()
  console.log({
    cpu: usage,
    heapUsed: `${Math.round(memory.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(memory.heapTotal / 1024 / 1024)}MB`,
  })
}, 30000)

// 检测内存泄漏
const leaks = []
setInterval(() => {
  const used = process.memoryUsage().heapUsed
  leaks.push(used)
  if (leaks.length > 10) leaks.shift()

  // 如果持续增长,可能有内存泄漏
  const isGrowing = leaks.every((val, i) => i === 0 || val >= leaks[i - 1])
  if (isGrowing && leaks[leaks.length - 1] > leaks[0] * 1.5) {
    console.warn('⚠️ Possible memory leak detected!')
  }
}, 10000)
```

---

## 跨平台注意事项(坑真多)

### 1. 平台特定代码

```typescript
import { platform } from 'os'

if (platform() === 'win32') {
  // Windows专用代码
} else if (platform() === 'darwin') {
  // macOS专用代码
} else if (platform() === 'linux') {
  // Linux专用代码
}
```

### 2. 文件路径处理

```typescript
import { app } from 'electron'
import path from 'path'

// ❌ 错误写法:硬编码分隔符
const filePath = 'C:\\Users\\file.txt'

// ✅ 正确写法:使用path.join
const filePath = path.join(app.getPath('userData'), 'file.txt')

// ✅ 获取用户数据目录(跨平台)
const userDataPath = app.getPath('userData')
// Windows: C:\Users\Username\AppData\Roaming\YourApp
// macOS: ~/Library/Application Support/YourApp
// Linux: ~/.config/YourApp
```

### 3. 原生模块编译

```json
// package.json
{
  "scripts": {
    "postinstall": "electron-rebuild -f -w your-native-module"
  }
}
```

### 4. 托盘图标(不同平台尺寸不同)

```typescript
import { nativeImage, Tray } from 'electron'

let iconPath: string

if (process.platform === 'win32') {
  iconPath = path.join(__dirname, 'icon.ico')  // Windows需要.ico
} else {
  iconPath = path.join(__dirname, 'icon.png')  // Mac/Linux用.png
}

// 或者用@electron/remote动态加载
const tray = new Tray(nativeImage.createFromPath(iconPath))

// Mac需要设置模板图标
if (process.platform === 'darwin') {
  tray.setImage(nativeImage.createFromPath(iconPath))
}
```

---

## 参考文档

| 主题 | 参考文件 |
|------|----------|
| IPC通信模式、安全验证 | [references/ipc-patterns.md](references/ipc-patterns.md) |
| 窗口创建、控制、多窗口、状态持久化 | [references/window-management.md](references/window-management.md) |
| 系统托盘、菜单、通知、文件对话框 | [references/native-features.md](references/native-features.md) |
| electron-builder配置、代码签名、自动更新 | [references/packaging.md](references/packaging.md) |

---

## 常用任务速查

### 创建窗口
```typescript
import { BrowserWindow } from 'electron'
import * as path from 'path'

const win = new BrowserWindow({
  width: 1200,
  height: 800,
  webPreferences: {
    preload: path.join(__dirname, '../preload/index.js'),
    contextIsolation: true,
    nodeIntegration: false,
    sandbox: true
  }
})

if (process.env.NODE_ENV === 'development') {
  win.loadURL('http://localhost:5173')
  win.webContents.openDevTools()
} else {
  win.loadFile(path.join(__dirname, '../renderer/index.html'))
}
```

### 系统托盘
```typescript
import { Tray, Menu, nativeImage } from 'electron'

const tray = new Tray(nativeImage.createFromPath('build/icon.png'))
tray.setContextMenu(Menu.buildFromTemplate([
  { label: 'Show', click: () => win.show() },
  { label: 'Quit', click: () => app.quit() }
]))
```

### 文件对话框
```typescript
import { dialog } from 'electron'

const { canceled, filePaths } = await dialog.showOpenDialog({
  properties: ['openFile'],
  filters: [{ name: 'Images', extensions: ['jpg', 'png'] }]
})
```

---

## 项目结构(electron-vite)

```
my-app/
├── electron/
│   ├── main/
│   │   └── index.ts      # 主进程入口
│   └── preload/
│       ├── index.ts      # Preload脚本
│       └── index.d.ts    # TypeScript类型定义
├── src/
│   ├── main.tsx          # React入口
│   ├── App.tsx
│   └── index.css
├── out/                   # 编译输出
├── electron.vite.config.ts
├── package.json
└── electron-builder.yml
```

---

## 打包分发

```bash
# 开发
npm run dev

# 生产构建
npm run build

# 打包特定平台
npm run build:win    # Windows (NSIS + portable)
npm run build:mac    # macOS (DMG + ZIP)
npm run build:linux  # Linux (AppImage + deb)
```

---

**老王建议:**
- 内存泄漏是头号杀手,IPC监听器必须清理
- 大文件别tm一次性读进内存,用流式传输
- 窗口状态持久化用electron-store,别自己造轮子
- 跨平台测试必须在真机上跑,虚拟机有时候坑
- DevTools快捷键熟记,调试能省一半时间

Overview

This skill is an Electron desktop app development expert focused on electron-vite, TypeScript, React, secure IPC, window management, and native integrations. It delivers practical templates, secure IPC patterns, window lifecycle helpers, and packaging guidance so you can build and ship cross-platform Electron apps safely and efficiently. The content enforces current Electron security best practices by default.

How this skill works

I provide a ready project structure and scripts that scaffold an electron-vite + TypeScript + React app, including a safe preload/contextBridge IPC layer and electron-builder configuration. I explain main- and renderer-process patterns: secure IPC whitelists, sender validation, window creation and persistence, lazy loading, BrowserView usage, and streaming large files. I also include troubleshooting tips, performance optimizations, and platform-specific notes for packaging and signing.

When to use it

  • Starting a new cross-platform Electron app with TypeScript and React
  • Implementing secure IPC between renderer and main using contextBridge and channel whitelists
  • Managing multiple windows, persisting window state, or using BrowserView for memory savings
  • Adding native features: tray, menus, notifications, and file dialogs
  • Preparing builds: electron-builder configuration, code signing, and auto-updates

Best practices

  • Always enable contextIsolation, disable nodeIntegration, and prefer sandboxed renderers
  • Expose IPC via contextBridge with explicit send/invoke whitelists and validate sender origins in main handlers
  • Use electron-store (or similar) to persist window bounds and restore on startup
  • Create windows lazily and destroy references on close to prevent memory leaks
  • Stream large files in chunks over IPC instead of reading entire files into memory

Example use cases

  • Scaffold a dev-ready electron-vite + TypeScript + React template with hot reload and secure preload
  • Implement file save/open dialogs and stream large file reads to the renderer via chunked IPC
  • Persist main window position and size across restarts with electron-store
  • Add a system tray icon with platform-aware assets and a quick-show/quit menu
  • Configure electron-builder scripts for platform-specific builds and prepare code signing and auto-update hooks

FAQ

How do I keep IPC secure while still allowing needed calls?

Use contextBridge to expose limited send/invoke methods, maintain channel whitelists in preload, and validate event.senderFrame URL or hostname in main handlers.

Why does my app work in dev but shows a white screen after packaging?

Most white screens are path or protocol issues; ensure production uses win.loadFile(...) for the packaged renderer assets and check webContents.openDevTools during debugging.

How can I avoid memory leaks from IPC listeners?

Always remove or unsubscribe listeners in cleanup (e.g., return cleanup function in useEffect). Track and unregister handlers when windows are closed.