mcpField
D
desktop-app

desktop-app

github.com/ww-w-ai开发606231 次安装4 周前更新
代码仓库

关于 desktop-app

面向桌面应用开发的技能指南,介绍使用 Web 技术构建应用,并涵盖 Tauri 与 Electron。内容涉及框架选择、实现、系统集成和 Windows、macOS、Linux 的打包。

主要功能

  1. 01Tauri 与 Electron 框架指南
  2. 02项目配置与进程结构
  3. 03原生系统集成模式
  4. 04构建、部署与更新
  5. 05安全和发布检查清单

适用场景

  1. 01为桌面项目选择框架
  2. 02实现文件访问、菜单或托盘功能
  3. 03准备多个桌面平台的构建版本

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ww-w-ai/bkit-claude-code --skill desktop-app

安装后在 Claude Code 中输入/desktop-app确认已加载。

详细介绍

桌面应用开发专业知识

概览

使用 Web 技术(HTML、CSS、JavaScript)开发桌面应用的指南。 使用单一代码库同时支持 Windows、macOS 和 Linux。


框架选择指南

按级别选择框架(v1.3.0)
框架级别建议使用场景
Tauri级别 2⭐ 首选轻量(3MB),Rust 安全性
Electron级别 3支持生态成熟,适用于类似 VS Code 的应用

AI 原生建议:Tauri

  • 年增长率为 35%
  • 内存占用为 20-40MB,而 Electron 为 200-400MB
  • 通过 Tauri 2.0 支持移动平台(iOS/Android)
  • Rust 后端 = 内存安全

生态系统建议:Electron

  • 工具链成熟
  • 与 Node.js 集成
  • 经大规模应用验证(VS Code、Slack)
分级建议
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

Electron 指南

创建项目
# Create with electron-vite (recommended)
npm create @electron-vite/create my-electron-app
cd my-electron-app

# Install dependencies
npm install

# Start development server
npm run dev
文件夹结构
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
核心概念:进程分离
┌─────────────────────────────────────────────────────┐
│                    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)          │
└─────────────────────────────────────────────────────┘
主进程
// 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');
});
预加载脚本
// 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;
    };
  }
}
渲染进程
// 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;
创建菜单
// 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);
系统托盘
// 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);
}

Tauri 指南

创建项目
# Prerequisite: Rust installation required
# Install from https://rustup.rs

# Create Tauri project
npm create tauri-app my-tauri-app
cd my-tauri-app

# Install dependencies
npm install

# Start development server
npm run tauri dev
文件夹结构
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
命令定义(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");
}
从前端调用
// 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>
  );
}

Web 与桌面应用的区别

文件系统访问
// 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' });
系统集成
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: Requires Service Worker, limited
Desktop: Works offline by default

⚠️ Server integration features must handle offline!

构建与部署

Electron 构建
# 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
# Execute build
npm run build:mac
npm run build:win
npm run build:linux
自动更新
// 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();
});
Tauri 构建
# Build for current platform
npm run tauri build

# Output locations
# macOS: src-tauri/target/release/bundle/dmg/
# Windows: src-tauri/target/release/bundle/msi/
# Linux: src-tauri/target/release/bundle/appimage/

桌面应用 PDCA 检查清单

阶段 1:架构
□ Decide local data storage method (SQLite, JSON file, etc.)
□ Decide if cloud sync is needed
阶段 3:原型图
□ Consider platform-specific UI guidelines (macOS, Windows)
□ Plan keyboard shortcuts
□ Design menu structure
阶段 6:UI
□ Support dark/light mode
□ Handle window resizing
□ Handle platform-specific UI differences (window control positions, etc.)
阶段 7:安全
□ Don't expose Node.js APIs directly (use contextBridge)
□ Security handling when loading external URLs
□ Encrypt sensitive data storage
阶段 9:部署
□ Code signing (macOS Notarization, Windows Signing)
□ Set up auto-update
□ App Store submission (if needed)

实用库

Electron
库用途
electron-store本地设置/数据存储
electron-updater自动更新
electron-log日志记录
better-sqlite3SQLite 数据库
Tauri
库用途
tauri-plugin-store设置存储
tauri-plugin-sqlSQLite 支持
tauri-plugin-log日志记录
tauri-plugin-updater自动更新

向 Claude 提出请求

创建项目
"Set up a [app description] app project with Electron + React.
- Use electron-vite
- Support system tray
- Set up auto-update"
功能实现
"Implement file open/save functionality.
- Use native file dialogs
- Save recent file list
- Support drag and drop"
构建配置
"Create electron-builder configuration.
- macOS: DMG + notarization
- Windows: NSIS installer
- Auto-update server integration"

评论

支持 Markdown