desktop-app

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Desktop 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)

FrameworkTierRecommendationUse Case
TauriTier 2⭐ PrimaryLightweight (3MB), Rust security
ElectronTier 3SupportedMature 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)
框架层级推荐度使用场景
TauriTier 2⭐ 首选轻量(仅3MB)、Rust安全特性
ElectronTier 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集成选Electron

Electron Guide

Electron 指南

Project Creation

项目创建

bash
undefined
bash
undefined

Create 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
undefined
npm run dev
undefined

Folder 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.json
my-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.json

Core 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
undefined
bash
undefined

Prerequisite: Rust installation required

前置要求:需要安装Rust

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
undefined
npm run tauri dev
undefined

Folder 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.json
my-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.json

Command 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 menus
Web应用无法实现但桌面应用可实现的功能:
- 系统托盘图标
- 全局快捷键
- 原生通知
- 拖拽(访问文件路径)
- 完整剪贴板控制
- 原生菜单

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
undefined
yaml
undefined

electron-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

```bash
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

```bash

Execute build

执行构建

npm run build:mac npm run build:win npm run build:linux
undefined
npm run build:mac npm run build:win npm run build:linux
undefined

Auto-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
undefined
bash
undefined

Build 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

LibraryPurpose
electron-storeLocal settings/data storage
electron-updaterAuto-update
electron-logLogging
better-sqlite3SQLite database
用途
electron-store本地设置/数据存储
electron-updater自动更新
electron-log日志记录
better-sqlite3SQLite数据库

Tauri

Tauri

LibraryPurpose
tauri-plugin-storeSettings storage
tauri-plugin-sqlSQLite support
tauri-plugin-logLogging
tauri-plugin-updaterAuto-update

用途
tauri-plugin-store设置存储
tauri-plugin-sqlSQLite支持
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安装包
- 集成自动更新服务器"