desktop-app
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDesktop App Development Expertise
桌面应用开发专业指南
Overview
概述
A guide for developing desktop apps using web technologies (HTML, CSS, JavaScript).
Support Windows, macOS, and Linux simultaneously with a single codebase.
本指南介绍如何使用Web技术(HTML、CSS、JavaScript)开发桌面应用。
通过单一代码库同时支持Windows、macOS和Linux系统。
Framework Selection Guide
框架选择指南
Framework Selection by Tier (v1.3.0)
按层级选择框架(v1.3.0)
| Framework | Tier | Recommendation | Use Case |
|---|---|---|---|
| Tauri | Tier 2 | ⭐ Primary | Lightweight (3MB), Rust security |
| Electron | Tier 3 | Supported | Mature ecosystem, VS Code-like apps |
AI-Native Recommendation: Tauri
- 35% YoY growth
- 20-40MB memory vs Electron's 200-400MB
- Mobile support (iOS/Android) via Tauri 2.0
- Rust backend = memory safety
Ecosystem Recommendation: Electron
- Mature tooling
- Node.js integration
- Proven at scale (VS Code, Slack)
| 框架 | 层级 | 推荐度 | 使用场景 |
|---|---|---|---|
| Tauri | Tier 2 | ⭐ 首选 | 轻量(仅3MB)、Rust安全特性 |
| Electron | Tier 3 | 支持 | 成熟生态、类VS Code应用 |
原生AI推荐:Tauri
- 年增长率35%
- 内存占用20-40MB,对比Electron的200-400MB
- 通过Tauri 2.0支持移动端(iOS/Android)
- Rust后端保障内存安全
生态系统推荐:Electron
- 成熟工具链
- 支持Node.js集成
- 经大规模验证(如VS Code、Slack)
Level-wise Recommendations
按开发阶段推荐
Starter → Tauri (v2) [Tier 2]
- Simpler setup than Electron
- Smaller output bundles (~3MB vs ~150MB)
Dynamic → Tauri + auto-update [Tier 2]
- Includes server integration, auto-update
- Lower memory footprint
Enterprise → Tauri [Tier 2] or Electron [Tier 3]
- Tauri for performance and security
- Electron for complex Node.js integration入门级 → Tauri (v2) [Tier 2]
- 比Electron设置更简单
- 输出包体积更小(约3MB vs 约150MB)
动态功能级 → Tauri + 自动更新 [Tier 2]
- 包含服务端集成、自动更新功能
- 内存占用更低
企业级 → Tauri [Tier 2] 或 Electron [Tier 3]
- 追求性能与安全选Tauri
- 需要复杂Node.js集成选ElectronElectron Guide
Electron 指南
Project Creation
项目创建
bash
undefinedbash
undefinedCreate with electron-vite (recommended)
推荐使用electron-vite创建
npm create @electron-vite/create my-electron-app
cd my-electron-app
npm create @electron-vite/create my-electron-app
cd my-electron-app
Install dependencies
安装依赖
npm install
npm install
Start development server
启动开发服务器
npm run dev
undefinednpm run dev
undefinedFolder Structure
文件夹结构
my-electron-app/
├── src/
│ ├── main/ # Main process (Node.js)
│ │ └── index.ts # App entry point, window management
│ ├── preload/ # Preload script
│ │ └── index.ts # Renderer↔Main bridge
│ └── renderer/ # Renderer process (Web)
│ ├── src/ # React/Vue code
│ └── index.html # HTML entry point
├── resources/ # App icons, assets
├── electron.vite.config.ts # Build configuration
├── electron-builder.yml # Deployment configuration
└── package.jsonmy-electron-app/
├── src/
│ ├── main/ # 主进程(Node.js)
│ │ └── index.ts # 应用入口、窗口管理
│ ├── preload/ # 预加载脚本
│ │ └── index.ts # 渲染进程↔主进程的桥接层
│ └── renderer/ # 渲染进程(Chromium)
│ ├── src/ # React/Vue代码
│ └── index.html # HTML入口
├── resources/ # 应用图标、资源文件
├── electron.vite.config.ts # 构建配置
├── electron-builder.yml # 部署配置
└── package.jsonCore Concept: Process Separation
核心概念:进程分离
┌─────────────────────────────────────────────────────┐
│ Electron App │
├─────────────────────────────────────────────────────┤
│ Main Process (Node.js) │
│ - System API access (files, network, etc.) │
│ - Window creation/management │
│ - Menu, tray management │
├─────────────────────────────────────────────────────┤
│ Preload Script (Bridge) │
│ - Safe main↔renderer communication │
│ - Expose only specific APIs │
├─────────────────────────────────────────────────────┤
│ Renderer Process (Chromium) │
│ - Web UI (React, Vue, etc.) │
│ - DOM access │
│ - No direct Node.js API access (security) │
└─────────────────────────────────────────────────────┘┌─────────────────────────────────────────────────────┐
│ Electron 应用 │
├─────────────────────────────────────────────────────┤
│ 主进程(Node.js) │
│ - 访问系统API(文件、网络等) │
│ - 创建/管理窗口 │
│ - 菜单、托盘管理 │
├─────────────────────────────────────────────────────┤
│ 预加载脚本(桥接层) │
│ - 安全的主进程↔渲染进程通信 │
│ - 仅暴露特定API │
├─────────────────────────────────────────────────────┤
│ 渲染进程(Chromium) │
│ - Web UI(React、Vue等) │
│ - 访问DOM │
│ - 无法直接访问Node.js API(安全考虑) │
└─────────────────────────────────────────────────────┘Main Process
主进程示例
typescript
// src/main/index.ts
import { app, BrowserWindow, ipcMain } from 'electron';
import { join } from 'path';
let mainWindow: BrowserWindow | null = null;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
},
});
// Dev mode: Load Vite server
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:5173');
mainWindow.webContents.openDevTools();
} else {
// Production: Load built files
mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
}
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// IPC: Handle requests from renderer
ipcMain.handle('read-file', async (event, filePath) => {
const fs = await import('fs/promises');
return fs.readFile(filePath, 'utf-8');
});typescript
// src/main/index.ts
import { app, BrowserWindow, ipcMain } from 'electron';
import { join } from 'path';
let mainWindow: BrowserWindow | null = null;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
},
});
// 开发模式:加载Vite服务器
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:5173');
mainWindow.webContents.openDevTools();
} else {
// 生产环境:加载构建后的文件
mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
}
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// IPC:处理渲染进程的请求
ipcMain.handle('read-file', async (event, filePath) => {
const fs = await import('fs/promises');
return fs.readFile(filePath, 'utf-8');
});Preload Script
预加载脚本示例
typescript
// src/preload/index.ts
import { contextBridge, ipcRenderer } from 'electron';
// APIs to safely expose to renderer
contextBridge.exposeInMainWorld('electronAPI', {
// Read file
readFile: (filePath: string) => ipcRenderer.invoke('read-file', filePath),
// Save file dialog
saveFile: (content: string) => ipcRenderer.invoke('save-file', content),
// App version
getVersion: () => process.env.npm_package_version,
// Platform
platform: process.platform,
});
// Type definitions (for use in renderer)
declare global {
interface Window {
electronAPI: {
readFile: (path: string) => Promise<string>;
saveFile: (content: string) => Promise<void>;
getVersion: () => string;
platform: NodeJS.Platform;
};
}
}typescript
// src/preload/index.ts
import { contextBridge, ipcRenderer } from 'electron';
// 安全暴露给渲染进程的API
contextBridge.exposeInMainWorld('electronAPI', {
// 读取文件
readFile: (filePath: string) => ipcRenderer.invoke('read-file', filePath),
// 保存文件对话框
saveFile: (content: string) => ipcRenderer.invoke('save-file', content),
// 应用版本
getVersion: () => process.env.npm_package_version,
// 平台信息
platform: process.platform,
});
// 类型定义(供渲染进程使用)
declare global {
interface Window {
electronAPI: {
readFile: (path: string) => Promise<string>;
saveFile: (content: string) => Promise<void>;
getVersion: () => string;
platform: NodeJS.Platform;
};
}
}Renderer Process
渲染进程示例
typescript
// src/renderer/src/App.tsx
import { useState } from 'react';
function App() {
const [content, setContent] = useState('');
const handleOpenFile = async () => {
const result = await window.electronAPI.readFile('/path/to/file.txt');
setContent(result);
};
return (
<div className="app">
<h1>My Electron App</h1>
<p>Platform: {window.electronAPI.platform}</p>
<button onClick={handleOpenFile}>Open File</button>
<pre>{content}</pre>
</div>
);
}
export default App;typescript
// src/renderer/src/App.tsx
import { useState } from 'react';
function App() {
const [content, setContent] = useState('');
const handleOpenFile = async () => {
const result = await window.electronAPI.readFile('/path/to/file.txt');
setContent(result);
};
return (
<div className="app">
<h1>我的Electron应用</h1>
<p>平台:{window.electronAPI.platform}</p>
<button onClick={handleOpenFile}>打开文件</button>
<pre>{content}</pre>
</div>
);
}
export default App;Creating Menus
创建菜单
typescript
// src/main/menu.ts
import { Menu, app, shell } from 'electron';
const template: Electron.MenuItemConstructorOptions[] = [
{
label: 'File',
submenu: [
{ label: 'New File', accelerator: 'CmdOrCtrl+N', click: () => {} },
{ label: 'Open', accelerator: 'CmdOrCtrl+O', click: () => {} },
{ type: 'separator' },
{ label: 'Quit', role: 'quit' },
],
},
{
label: 'Edit',
submenu: [
{ label: 'Undo', role: 'undo' },
{ label: 'Redo', role: 'redo' },
{ type: 'separator' },
{ label: 'Cut', role: 'cut' },
{ label: 'Copy', role: 'copy' },
{ label: 'Paste', role: 'paste' },
],
},
{
label: 'Help',
submenu: [
{
label: 'Documentation',
click: () => shell.openExternal('https://docs.example.com'),
},
],
},
];
// Add macOS app menu
if (process.platform === 'darwin') {
template.unshift({
label: app.getName(),
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' },
],
});
}
export const menu = Menu.buildFromTemplate(template);typescript
// src/main/menu.ts
import { Menu, app, shell } from 'electron';
const template: Electron.MenuItemConstructorOptions[] = [
{
label: '文件',
submenu: [
{ label: '新建文件', accelerator: 'CmdOrCtrl+N', click: () => {} },
{ label: '打开', accelerator: 'CmdOrCtrl+O', click: () => {} },
{ type: 'separator' },
{ label: '退出', role: 'quit' },
],
},
{
label: '编辑',
submenu: [
{ label: '撤销', role: 'undo' },
{ label: '重做', role: 'redo' },
{ type: 'separator' },
{ label: '剪切', role: 'cut' },
{ label: '复制', role: 'copy' },
{ label: '粘贴', role: 'paste' },
],
},
{
label: '帮助',
submenu: [
{
label: '文档',
click: () => shell.openExternal('https://docs.example.com'),
},
],
},
];
// 添加macOS应用菜单
if (process.platform === 'darwin') {
template.unshift({
label: app.getName(),
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' },
],
});
}
export const menu = Menu.buildFromTemplate(template);System Tray
系统托盘
typescript
// src/main/tray.ts
import { Tray, Menu, nativeImage } from 'electron';
import { join } from 'path';
let tray: Tray | null = null;
export function createTray() {
const icon = nativeImage.createFromPath(join(__dirname, '../../resources/icon.png'));
tray = new Tray(icon.resize({ width: 16, height: 16 }));
const contextMenu = Menu.buildFromTemplate([
{ label: 'Open', click: () => {} },
{ type: 'separator' },
{ label: 'Quit', role: 'quit' },
]);
tray.setToolTip('My App');
tray.setContextMenu(contextMenu);
}typescript
// src/main/tray.ts
import { Tray, Menu, nativeImage } from 'electron';
import { join } from 'path';
let tray: Tray | null = null;
export function createTray() {
const icon = nativeImage.createFromPath(join(__dirname, '../../resources/icon.png'));
tray = new Tray(icon.resize({ width: 16, height: 16 }));
const contextMenu = Menu.buildFromTemplate([
{ label: '打开', click: () => {} },
{ type: 'separator' },
{ label: '退出', role: 'quit' },
]);
tray.setToolTip('我的应用');
tray.setContextMenu(contextMenu);
}Tauri Guide
Tauri 指南
Project Creation
项目创建
bash
undefinedbash
undefinedPrerequisite: Rust installation required
前置要求:需要安装Rust
Install from https://rustup.rs
Create Tauri project
创建Tauri项目
npm create tauri-app my-tauri-app
cd my-tauri-app
npm create tauri-app my-tauri-app
cd my-tauri-app
Install dependencies
安装依赖
npm install
npm install
Start development server
启动开发服务器
npm run tauri dev
undefinednpm run tauri dev
undefinedFolder Structure
文件夹结构
my-tauri-app/
├── src/ # Frontend (React, Vue, etc.)
│ ├── App.tsx
│ └── main.tsx
├── src-tauri/ # Tauri backend (Rust)
│ ├── src/
│ │ ├── main.rs # Main entry point
│ │ └── lib.rs # Command definitions
│ ├── tauri.conf.json # Tauri configuration
│ └── Cargo.toml # Rust dependencies
├── public/
└── package.jsonmy-tauri-app/
├── src/ # 前端(React、Vue等)
│ ├── App.tsx
│ └── main.tsx
├── src-tauri/ # Tauri后端(Rust)
│ ├── src/
│ │ ├── main.rs # 主入口
│ │ └── lib.rs # 命令定义
│ ├── tauri.conf.json # Tauri配置
│ └── Cargo.toml # Rust依赖
├── public/
└── package.jsonCommand Definition (Rust)
命令定义(Rust)
rust
// src-tauri/src/lib.rs
use tauri::command;
#[command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
#[command]
async fn read_file(path: &str) -> Result<String, String> {
std::fs::read_to_string(path)
.map_err(|e| e.to_string())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, read_file])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}rust
// src-tauri/src/lib.rs
use tauri::command;
#[command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
#[command]
async fn read_file(path: &str) -> Result<String, String> {
std::fs::read_to_string(path)
.map_err(|e| e.to_string())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, read_file])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}Calling from Frontend
前端调用示例
typescript
// src/App.tsx
import { invoke } from '@tauri-apps/api/core';
function App() {
const [greeting, setGreeting] = useState('');
const handleGreet = async () => {
const result = await invoke('greet', { name: 'World' });
setGreeting(result as string);
};
const handleReadFile = async () => {
try {
const content = await invoke('read_file', { path: '/path/to/file.txt' });
console.log(content);
} catch (error) {
console.error(error);
}
};
return (
<div>
<button onClick={handleGreet}>Greet</button>
<p>{greeting}</p>
</div>
);
}typescript
// src/App.tsx
import { invoke } from '@tauri-apps/api/core';
import { useState } from 'react';
function App() {
const [greeting, setGreeting] = useState('');
const handleGreet = async () => {
const result = await invoke('greet', { name: 'World' });
setGreeting(result as string);
};
const handleReadFile = async () => {
try {
const content = await invoke('read_file', { path: '/path/to/file.txt' });
console.log(content);
} catch (error) {
console.error(error);
}
};
return (
<div>
<button onClick={handleGreet}>问候</button>
<p>{greeting}</p>
</div>
);
}Web vs Desktop Differences
Web与桌面应用的差异
File System Access
文件系统访问
typescript
// Web: Not possible (user must select directly)
// Desktop: Free access
// Electron
const fs = require('fs');
fs.writeFileSync('/path/to/file.txt', 'content');
// Tauri
await invoke('write_file', { path: '/path/to/file.txt', content: 'content' });typescript
// Web:无法直接访问(用户必须手动选择)
// 桌面应用:可自由访问
// Electron
const fs = require('fs');
fs.writeFileSync('/path/to/file.txt', 'content');
// Tauri
await invoke('write_file', { path: '/path/to/file.txt', content: 'content' });System Integration
系统集成
Things impossible on web but possible on desktop:
- System tray icon
- Global shortcuts
- Native notifications
- Drag and drop (file path access)
- Full clipboard control
- Native menusWeb应用无法实现但桌面应用可实现的功能:
- 系统托盘图标
- 全局快捷键
- 原生通知
- 拖拽(访问文件路径)
- 完整剪贴板控制
- 原生菜单Offline Support
离线支持
Web: Requires Service Worker, limited
Desktop: Works offline by default
⚠️ Server integration features must handle offline!Web:需要Service Worker,支持有限
桌面应用:默认支持离线运行
⚠️ 服务端集成功能需处理离线场景!Build & Deployment
构建与部署
Electron Build
Electron 构建
yaml
undefinedyaml
undefinedelectron-builder.yml
electron-builder.yml
appId: com.example.myapp
productName: My App
directories:
buildResources: resources
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.*'
mac:
artifactName: ${name}-${version}-${arch}.${ext}
target:
- dmg
- zip icon: resources/icon.icns win: artifactName: ${name}-${version}-${arch}.${ext} target:
- nsis icon: resources/icon.ico linux: target:
- AppImage
- deb
```bashappId: com.example.myapp
productName: My App
directories:
buildResources: resources
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.*'
mac:
artifactName: ${name}-${version}-${arch}.${ext}
target:
- dmg
- zip icon: resources/icon.icns win: artifactName: ${name}-${version}-${arch}.${ext} target:
- nsis icon: resources/icon.ico linux: target:
- AppImage
- deb
```bashExecute build
执行构建
npm run build:mac
npm run build:win
npm run build:linux
undefinednpm run build:mac
npm run build:win
npm run build:linux
undefinedAuto-update
自动更新
typescript
// src/main/updater.ts
import { autoUpdater } from 'electron-updater';
autoUpdater.checkForUpdatesAndNotify();
autoUpdater.on('update-available', () => {
// Notify update available
});
autoUpdater.on('update-downloaded', () => {
// Restart to apply update
autoUpdater.quitAndInstall();
});typescript
// src/main/updater.ts
import { autoUpdater } from 'electron-updater';
autoUpdater.checkForUpdatesAndNotify();
autoUpdater.on('update-available', () => {
// 通知用户有更新可用
});
autoUpdater.on('update-downloaded', () => {
// 重启应用以安装更新
autoUpdater.quitAndInstall();
});Tauri Build
Tauri 构建
bash
undefinedbash
undefinedBuild for current platform
为当前平台构建
npm run tauri build
npm run tauri build
Output locations
输出位置
macOS: src-tauri/target/release/bundle/dmg/
macOS: src-tauri/target/release/bundle/dmg/
Windows: src-tauri/target/release/bundle/msi/
Windows: src-tauri/target/release/bundle/msi/
Linux: src-tauri/target/release/bundle/appimage/
Linux: src-tauri/target/release/bundle/appimage/
---
---Desktop PDCA Checklist
桌面应用PDCA检查清单
Phase 1: Schema
阶段1:架构设计
□ Decide local data storage method (SQLite, JSON file, etc.)
□ Decide if cloud sync is needed□ 确定本地数据存储方式(SQLite、JSON文件等)
□ 确定是否需要云同步Phase 3: Mockup
阶段3:原型设计
□ Consider platform-specific UI guidelines (macOS, Windows)
□ Plan keyboard shortcuts
□ Design menu structure□ 考虑平台特定UI指南(macOS、Windows)
□ 规划键盘快捷键
□ 设计菜单结构Phase 6: UI
阶段6:UI开发
□ Support dark/light mode
□ Handle window resizing
□ Handle platform-specific UI differences (window control positions, etc.)□ 支持深色/浅色模式
□ 处理窗口缩放
□ 适配平台特定UI差异(如窗口控件位置等)Phase 7: Security
阶段7:安全
□ Don't expose Node.js APIs directly (use contextBridge)
□ Security handling when loading external URLs
□ Encrypt sensitive data storage□ 不要直接暴露Node.js API(使用contextBridge)
□ 加载外部URL时的安全处理
□ 加密敏感数据存储Phase 9: Deployment
阶段9:部署
□ Code signing (macOS Notarization, Windows Signing)
□ Set up auto-update
□ App Store submission (if needed)□ 代码签名(macOS公证、Windows签名)
□ 配置自动更新
□ 提交应用商店(如需)Useful Libraries
实用库
Electron
Electron
| Library | Purpose |
|---|---|
| electron-store | Local settings/data storage |
| electron-updater | Auto-update |
| electron-log | Logging |
| better-sqlite3 | SQLite database |
| 库 | 用途 |
|---|---|
| electron-store | 本地设置/数据存储 |
| electron-updater | 自动更新 |
| electron-log | 日志记录 |
| better-sqlite3 | SQLite数据库 |
Tauri
Tauri
| Library | Purpose |
|---|---|
| tauri-plugin-store | Settings storage |
| tauri-plugin-sql | SQLite support |
| tauri-plugin-log | Logging |
| tauri-plugin-updater | Auto-update |
| 库 | 用途 |
|---|---|
| tauri-plugin-store | 设置存储 |
| tauri-plugin-sql | SQLite支持 |
| tauri-plugin-log | 日志记录 |
| tauri-plugin-updater | 自动更新 |
Requesting from Claude
向Claude发起请求示例
Project Creation
项目创建
"Set up a [app description] app project with Electron + React.
- Use electron-vite
- Support system tray
- Set up auto-update""使用Electron + React搭建一个[应用描述]的项目。
- 使用electron-vite
- 支持系统托盘
- 配置自动更新"Feature Implementation
功能实现
"Implement file open/save functionality.
- Use native file dialogs
- Save recent file list
- Support drag and drop""实现文件打开/保存功能。
- 使用原生文件对话框
- 保存最近文件列表
- 支持拖拽"Build Configuration
构建配置
"Create electron-builder configuration.
- macOS: DMG + notarization
- Windows: NSIS installer
- Auto-update server integration""创建electron-builder配置。
- macOS:DMG + 公证
- Windows:NSIS安装包
- 集成自动更新服务器"