electron-app-dev
ElectronとVite、TypeScript、Reactを使い、セキュアなIPC通信やウィンドウ管理、OSの機能連携などを備えたデスクトップアプリを開発・パッケージ化し、自動更新まで実現するSkill。
📜 元の英語説明(参考)
Electron桌面应用开发专家。精通electron-vite、TypeScript、React、IPC通信、窗口管理、原生功能集成等Electron全栈开发技术。 适用场景: - 创建electron-vite + TypeScript + React项目 - 实现安全的IPC通信 - 窗口管理(创建、控制、多窗口、状态持久化) - 原生功能(系统托盘、菜单、通知、文件对话框) - electron-builder打包分发 - 自动更新和代码签名 所有代码遵循最新Electron安全最佳实践:contextIsolation开启、nodeIntegration关闭、sandbox模式开启、contextBridge安全暴露IPC。
🇯🇵 日本人クリエイター向け解説
ElectronとVite、TypeScript、Reactを使い、セキュアなIPC通信やウィンドウ管理、OSの機能連携などを備えたデスクトップアプリを開発・パッケージ化し、自動更新まで実現するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o electron-app-dev.zip https://jpskill.com/download/8147.zip && unzip -o electron-app-dev.zip && rm electron-app-dev.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/8147.zip -OutFile "$d\electron-app-dev.zip"; Expand-Archive "$d\electron-app-dev.zip" -DestinationPath $d -Force; ri "$d\electron-app-dev.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
electron-app-dev.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
electron-app-devフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
⚡ Electron デスクトップアプリケーション開発エキスパート
老王です。Electronを長年やってきましたが、これはクロスプラットフォームアプリケーションを書くのに本当に最高です!
快速開始:新しいプロジェクトの作成
組み込みスクリプトを使用して、ベストプラクティスな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
生成されるプロジェクトには以下が含まれます。
- electron-vite:全プロセスで超高速ホットリロード
- TypeScript + React:型安全な開発
- contextBridgeによる安全なIPCモード
- electron-builderによるパッケージング設定
核心安全原則(妥協は許されない)
常にこれらのセキュリティ設定を強制的に適用してください。
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
contextIsolation: true, // 必須
nodeIntegration: false, // 必須
sandbox: true // 推奨
}
なぜ重要なのか:
contextIsolation: true- preloadスクリプトとレンダラーを分離nodeIntegration: false- レンダラーがNode.jsに直接アクセスするのを防止sandbox: true- レンダラープロセスの能力をさらに制限
IPC通信モード
唯一の正しい方法:contextBridge + ホワイトリスト
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() }
})
常见坑与解决方案(老王血泪经验)
1. 白屏問題(DevToolsはロードできるが正常に動作しない)
症状: 開発環境では正常だが、パッケージング後に白屏になる
原因: プロトコルの問題またはパスの間違い
// ❌ 間違った書き方
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リスナーが削除されていない)
症状: アプリケーションを長く使用するとどんどん重くなる
// ❌ 間違った書き方
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. ウィンドウの状態が保存されない(ユーザーが毎回サイズを調整する必要がある)
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が開発環境で自動的に開き、本番環境で閉じ忘れる
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. ウィンドウの遅延ロード(一度にすべてのウィンドウを作成しない)
// ❌ 間違った書き方:起動時にすべてのウィンドウを作成
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で複数のウィンドウを代替(より省メモリ)
// 1つのメインウィンドウ + 複数の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呼び出しのデバウンス(リクエストを大量に送信しない)
// Renderer - lodash debounceを使用
import { debounce } from 'lodash'
const debouncedSave = debounce((content) => {
window.electronAPI.invoke('save-file', content)
}, 500)
// 入力するたびに呼び出すが、実際には500msに1回だけ実行
inputElement.addEventListener('input', (e) => {
debouncedSave(e.target.value)
})
4. 大きなファイルはストリームで転送(一度にメモリに読み込まない)
// ❌ 間違った書き方:大きなファイルを一度に読み込む
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デバッグ設定
// .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",
(原文がここで途切れています) 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
⚡ Electron 桌面应用开发专家
老王我搞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
生成项目包含:
- electron-vite:全进程极速热更新
- TypeScript + React:类型安全开发
- contextBridge安全IPC模式
- electron-builder打包配置
核心安全原则(不可妥协)
永远强制执行这些安全配置:
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
contextIsolation: true, // 必须为true
nodeIntegration: false, // 必须为false
sandbox: true // 推荐开启
}
为什么重要:
contextIsolation: true- 隔离preload脚本与渲染器nodeIntegration: false- 防止渲染器直接访问Node.jssandbox: true- 进一步限制渲染器进程能力
IPC通信模式
唯一正确方式:contextBridge + 白名单
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() }
})
常见坑与解决方案(老王血泪经验)
1. 白屏问题(DevTools可以加载但正常不行)
症状: 开发环境正常,打包后白屏
原因: 协议问题或路径错误
// ❌ 错误写法
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监听器没移除)
症状: 应用用久了越来越卡
// ❌ 错误写法
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. 窗口状态不保存(用户每次打开都要重新调整大小)
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在开发环境自动打开,生产环境忘记关
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一次性创建所有窗口)
// ❌ 错误写法:启动时创建所有窗口
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替代多个窗口(更省内存)
// 一个主窗口 + 多个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调用(别疯狂发请求)
// 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一次性读进内存)
// ❌ 错误写法:一次性读取大文件
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调试配置
// .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)
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. 内存监控
// 定期检查内存使用
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. 平台特定代码
import { platform } from 'os'
if (platform() === 'win32') {
// Windows专用代码
} else if (platform() === 'darwin') {
// macOS专用代码
} else if (platform() === 'linux') {
// Linux专用代码
}
2. 文件路径处理
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. 原生模块编译
// package.json
{
"scripts": {
"postinstall": "electron-rebuild -f -w your-native-module"
}
}
4. 托盘图标(不同平台尺寸不同)
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'] }]
})
项目结构(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
打包分发
# 开发
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快捷键熟记,调试能省一半时间