skills/electron-app-dev/SKILL.md
Electron桌面应用开发专家。精通electron-vite、TypeScript、React、IPC通信、窗口管理、原生功能集成等Electron全栈开发技术。 适用场景: - 创建electron-vite + TypeScript + React项目 - 实现安全的IPC通信 - 窗口管理(创建、控制、多窗口、状态持久化) - 原生功能(系统托盘、菜单、通知、文件对话框) - electron-builder打包分发 - 自动更新和代码签名 所有代码遵循最新Electron安全最佳实践:contextIsolation开启、nodeIntegration关闭、sandbox模式开启、contextBridge安全暴露IPC。
npx skillsauth add aaaaqwq/agi-super-team electron-app-devInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
老王我搞Electron好多年了,这玩意儿写跨平台应用真tm香!
使用内置脚本创建最佳实践Electron项目:
python "C:/Users/Administrator/.claude/skills/electron-app-dev/scripts/create_electron_app.py" my-app
cd my-app
npm install
npm run dev
生成项目包含:
永远强制执行这些安全配置:
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
contextIsolation: true, // 必须为true
nodeIntegration: false, // 必须为false
sandbox: true // 推荐开启
}
为什么重要:
contextIsolation: true - 隔离preload脚本与渲染器nodeIntegration: false - 防止渲染器直接访问Node.jssandbox: true - 进一步限制渲染器进程能力Preload (preload/index.ts):
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):
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() }
})
症状: 开发环境正常,打包后白屏
原因: 协议问题或路径错误
// ❌ 错误写法
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() // 先看看能不能加载
症状: 应用用久了越来越卡
// ❌ 错误写法
useEffect(() => {
window.electronAPI.on('update', callback)
// 没有清理函数!
}, [])
// ✅ 正确写法
useEffect(() => {
const callback = (data) => console.log(data)
window.electronAPI.on('update', callback)
return () => {
window.electronAPI.removeListener('update', callback) // 必须移除!
}
}, [])
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,
})
})
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()
})
})
// ❌ 错误写法:启动时创建所有窗口
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()
}
// 一个主窗口 + 多个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')
// 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)
})
// ❌ 错误写法:一次性读取大文件
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')
})
// .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"
}
]
}
| 快捷键 | 功能 | |--------|------| | Ctrl+Shift+I | 打开/关闭DevTools | | Ctrl+Shift+J | 打开控制台 | | Ctrl+Shift+C | 元素选择器 | | F1 | 打开命令面板 |
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)
// 定期检查内存使用
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)
import { platform } from 'os'
if (platform() === 'win32') {
// Windows专用代码
} else if (platform() === 'darwin') {
// macOS专用代码
} else if (platform() === 'linux') {
// Linux专用代码
}
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
// package.json
{
"scripts": {
"postinstall": "electron-rebuild -f -w your-native-module"
}
}
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/window-management.md | | 系统托盘、菜单、通知、文件对话框 | references/native-features.md | | electron-builder配置、代码签名、自动更新 | references/packaging.md |
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'))
}
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() }
]))
import { dialog } from 'electron'
const { canceled, filePaths } = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: 'Images', extensions: ['jpg', 'png'] }]
})
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
# 开发
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)
老王建议:
development
Technology-agnostic prompt generator that creates customizable AI prompts for scanning codebases and identifying high-quality code exemplars. Supports multiple programming languages (.NET, Java, JavaScript, TypeScript, React, Angular, Python) with configurable analysis depth, categorization methods, and documentation formats to establish coding standards and maintain consistency across development teams.
tools
Expert-level browser automation, debugging, and performance analysis using Chrome DevTools MCP. Use for interacting with web pages, capturing screenshots, analyzing network traffic, and profiling performance.
data-ai
Prompt for creating detailed feature implementation plans, following Epoch monorepo structure.
tools
Interactive prompt refinement workflow: interrogates scope, deliverables, constraints; copies final markdown to clipboard; never writes code. Requires the Joyride extension.