跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScriptNode.js大前端

Electron 桌面应用开发实战指南

Electron 是基于 Chromium 和 Node.js 的跨平台桌面应用框架。文章详细讲解了 Electron 的核心架构、主进程与渲染进程通信机制、安全配置及性能优化方案。通过脚手架快速初始化项目,结合 preload 脚本实现 IPC 通信。实战部分演示了本地文本编辑器的开发流程,涵盖文件读写、全局快捷键及自动更新功能。同时提供了调试技巧、内存泄漏排查方法以及打包体积优化策略,帮助开发者高效构建生产级桌面应用。

未来可期发布于 2026/3/26更新于 2026/10/2474 浏览
Electron 桌面应用开发实战指南

Electron 桌面应用开发实战指南

引言

当前前端开发竞争激烈,掌握桌面端技能有助于提升竞争力。招聘市场中,许多岗位要求全栈能力,甚至涉及桌面端开发。Electron 允许使用 HTML、CSS、JavaScript 技术栈构建跨平台桌面应用,无需学习 C# 或 C++。

虽然跨平台能力强,但需注意不同操作系统的路径分隔符、系统通知 API 差异及功能支持情况。本文旨在解析 Electron 架构,提供项目搭建、安全配置及性能优化方案。

Electron 架构解析

核心架构:Chromium 与 Node.js

Electron 的核心由两部分组成:Chromium 负责渲染界面,Node.js 负责操作系统级别的操作。Chromium 运行 HTML、CSS、JavaScript,而 Node.js 允许在应用中直接调用 fs 模块读写文件、启动系统命令或访问数据库。

Electron 将这两个部分分为"主进程"(Main Process)和"渲染进程"(Renderer Process)。主进程运行 Node.js,负责创建窗口、管理生命周期及系统操作。渲染进程是 Chromium 实例,负责展示界面和响应用户交互。

两者通过 IPC(进程间通信)机制通信。渲染进程不能直接访问系统 API,需发送消息给主进程处理,这种设计增强了安全性。

// 主进程 main.js - 应用入口
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const fs = require('fs');

let mainWindow;

function createWindow() {
    mainWindow = new BrowserWindow({
        width: 1200,
        height: 800,
        webPreferences: {
            preload: path.join(__dirname, 'preload.js'),
            contextIsolation: true,
            enableRemoteModule: false,
            nodeIntegration: false
        }
    });

    mainWindow.loadFile('index.html');

    mainWindow.(,  {
        mainWindow = ;
    });
}

app.().(createWindow);

app.(,  {
     (process. !== ) {
        app.();
    }
});

ipcMain.(,  (event, filePath) => {
     {
         content =  fs..(filePath, );
         { : , content };
    }  (error) {
         { : , : error. };
    }
});
on
'closed'
() =>
null
whenReady
then
on
'window-all-closed'
() =>
if
platform
'darwin'
quit
handle
'read-file'
async
try
const
await
promises
readFile
'utf-8'
return
success
true
catch
return
success
false
error
message
// preload.js - 预加载脚本
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
    readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
    writeFile: (filePath, content) => ipcRenderer.invoke('write-file', filePath, content),
    platform: process.platform,
    versions: {
        node: process.versions.node,
        electron: process.versions.electron,
        chrome: process.versions.chrome
    }
});
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>我的第一个 Electron 应用</title>
    <style>
        body { font-family: sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
        .container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; }
        textarea { width: 100%; height: 300px; padding: 10px; border: 1px solid #ddd; }
        button { background: #0066cc; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Electron 文件编辑器</h1>
        <p>当前平台:<span id="platform"></span></p>
        <textarea id="editor" placeholder="在这里输入内容..."></textarea>
        <div>
            <button onclick="openFile()">打开文件</button>
            <button onclick="saveFile()">保存文件</button>
        </div>
    </div>
    <script>
        document.getElementById('platform').textContent = window.electronAPI.platform;
        const editor = document.getElementById('editor');
        let currentFilePath = null;

        async function openFile() {
            const result = await window.electronAPI.readFile('test.txt');
            if (result.success) {
                editor.value = result.content;
                currentFilePath = 'test.txt';
            }
        }

        async function saveFile() {
            const result = await window.electronAPI.writeFile(currentFilePath || 'test.txt', editor.value);
            console.log(result);
        }
    </script>
</body>
</html>

其他跨平台方案对比

Electron 并非唯一选择。Tauri 使用 Rust 后端,打包体积小且内存占用低,适合对体积敏感的场景,但学习曲线较陡。NW.js 是早期方案,Node.js 与 Chromium 同上下文,存在安全隐患且社区活跃度较低。Flutter Desktop 和 React Native Windows 则需要学习 Dart 或原生开发,不属于纯前端技术栈范畴。

大厂如 VS Code、Discord、Slack 选用 Electron 主要基于开发效率和人才储备。一套代码可发布至 Windows、macOS、Linux,虽细节需适配,但避免了维护多套代码库的成本。

项目搭建与配置

使用脚手架初始化

推荐使用 electron-forge 或 electron-vite 快速搭建项目。

npm install -g @electron-forge/cli
electron-forge init my-app
cd my-app
npm start

项目结构如下:

my-app/
├── src/           # 源代码目录
│   ├── index.html
│   ├── index.js
│   ├── preload.js
│   └── renderer.js
├── forge.config.js
├── package.json
└── assets/        # 静态资源

主进程配置详解

实际项目中需考虑窗口状态记忆、系统托盘等功能。

const { app, BrowserWindow, Tray, Menu, ipcMain } = require('electron');
const Store = require('electron-store');

let mainWindow;
let tray;
const store = new Store();

function createWindow() {
    const windowState = store.get('windowState', { width: 1200, height: 800 });
    mainWindow = new BrowserWindow({
        width: windowState.width,
        height: windowState.height,
        minWidth: 800,
        minHeight: 600,
        show: false,
        webPreferences: {
            preload: path.join(__dirname, 'preload.js'),
            contextIsolation: true,
            nodeIntegration: false,
            allowEval: false,
            webSecurity: true
        }
    });

    mainWindow.loadFile('src/index.html');

    mainWindow.once('ready-to-show', () => {
        mainWindow.show();
    });

    mainWindow.on('close', (event) => {
        if (store.get('minimizeToTray', true)) {
            event.preventDefault();
            mainWindow.hide();
            return;
        }
        const bounds = mainWindow.getBounds();
        store.set('windowState', { width: bounds.width, height: bounds.height });
    });
}

function createTray() {
    const iconPath = process.platform === 'win32' ? 'assets/tray.ico' : 'assets/trayTemplate.png';
    tray = new Tray(iconPath);
    const contextMenu = Menu.buildFromTemplate([
        { label: '显示应用', click: () => mainWindow.show() },
        { label: '退出', click: () => app.quit() }
    ]);
    tray.setToolTip('我的 Electron 应用');
    tray.setContextMenu(contextMenu);
}

app.whenReady().then(() => {
    createWindow();
    createTray();
});

预加载脚本与安全隔离

preload 脚本应作为安全层和适配层,实施白名单校验和权限控制。

const { contextBridge, ipcRenderer, shell } = require('electron');
const ALLOWED_PATHS = [app.getPath('userData'), app.getPath('documents')];

function isPathAllowed(filePath) {
    const normalizedPath = path.normalize(filePath);
    return ALLOWED_PATHS.some(allowedPath => normalizedPath.startsWith(allowedPath));
}

contextBridge.exposeInMainWorld('electronAPI', {
    file: {
        read: async (filePath) => {
            if (!isPathAllowed(filePath)) throw new Error('Access denied');
            return ipcRenderer.invoke('read-file', filePath);
        },
        write: async (filePath, content) => {
            if (!isPathAllowed(filePath)) throw new Error('Access denied');
            if (Buffer.byteLength(content, 'utf-8') > 10 * 1024 * 1024) throw new Error('File too large');
            return ipcRenderer.invoke('write-file', filePath, content);
        },
        openDialog: () => ipcRenderer.invoke('show-open-dialog'),
        saveDialog: (content, defaultPath) => ipcRenderer.invoke('show-save-dialog', content, defaultPath)
    },
    system: {
        platform: process.platform,
        getPath: (name) => ipcRenderer.invoke('get-path', name)
    },
    window: {
        minimize: () => ipcRenderer.send('window-minimize'),
        close: () => ipcRenderer.send('window-close')
    },
    openExternal: (url) => {
        if (!/^https?:\/\//.test(url)) return;
        shell.openExternal(url);
    }
});

常见问题与优化

打包体积优化

Electron 包含 Chromium 和 Node.js 运行时,导致包体积较大。可通过配置忽略开发依赖、压缩资源来优化。

module.exports = {
    packagerConfig: {
        ignore: [/^\/src\/assets\/raw\//, /^\/tests?\//, /^\/\.git\//],
        compression: 'maximum',
        prune: true
    },
    makers: [{
        name: '@electron-forge/maker-squirrel',
        config: { setupIcon: 'assets/icon.ico' }
    }]
};

内存管理

Chromium 本身占用较多内存。可使用 process.memoryUsage() 监控主进程,Chrome DevTools Memory 面板分析渲染进程。

setInterval(() => {
    const usage = process.memoryUsage();
    console.log('主进程内存:', {
        rss: `${(usage.rss / 1024 / 1024).toFixed(2)} MB`,
        heapUsed: `${(usage.heapUsed / 1024 / 1024).toFixed(2)} MB`
    });
}, 30000);

自动更新机制

需配置静态文件服务器、签名证书及集成 electron-updater。

const { autoUpdater } = require('electron-updater');
const log = require('electron-log');
autoUpdater.logger = log;

function checkForUpdates() {
    if (process.env.NODE_ENV === 'development') return;
    autoUpdater.checkForUpdatesAndNotify();
}

autoUpdater.on('update-downloaded', () => {
    // 提示用户重启应用
});

app.whenReady().then(() => {
    setTimeout(checkForUpdates, 5000);
});

安全最佳实践

  1. 开启上下文隔离:contextIsolation: true。
  2. 禁用 Node 集成:nodeIntegration: false。
  3. 内容安全策略:设置 CSP 头限制资源加载。
  4. 校验输入:防止路径遍历和协议劫持。
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline';">

实战案例:本地文本编辑器

实现一个极简本地记事本,支持打字、保存、全局快捷键隐藏。

<!-- src/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>本地记事本</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50 h-screen flex flex-col">
    <div class="h-10 bg-white border-b flex items-center px-4">
        <div class="flex space-x-2">
            <div class="w-3 h-3 rounded-full bg-red-400"></div>
            <div class="w-3 h-3 rounded-full bg-yellow-400"></div>
            <div class="w-3 h-3 rounded-full bg-green-400"></div>
        </div>
        <div class="flex-1 text-center text-sm">摸鱼记事本</div>
    </div>
    <div class="flex-1 relative">
        <textarea id="editor" class="w-full h-full p-6 resize-none bg-white" placeholder="在此输入..."></textarea>
    </div>
    <script>
        const editor = document.getElementById('editor');
        let currentFilePath = null;

        async function saveFile() {
            const result = await window.electronAPI.file.saveDialog(editor.value, currentFilePath);
            if (result.success) {
                currentFilePath = result.path;
            }
        }

        document.addEventListener('keydown', (e) => {
            const ctrlKey = e.metaKey || e.ctrlKey;
            if (ctrlKey && e.key.toLowerCase() === 's') {
                e.preventDefault();
                saveFile();
            }
        });
    </script>
</body>
</html>

主进程需注册全局快捷键:

const { globalShortcut } = require('electron');
globalShortcut.register('CommandOrControl+Shift+H', () => {
    if (mainWindow.isVisible()) {
        mainWindow.hide();
    } else {
        mainWindow.show();
    }
});

调试与故障排查

主进程日志

使用 electron-log 记录崩溃信息。

const log = require('electron-log');
log.transports.file.resolvePath = () => path.join(app.getPath('userData'), 'logs/main.log');
Object.assign(console, log.functions);

process.on('uncaughtException', (error) => {
    log.error('未捕获的异常:', error);
});

渲染进程崩溃

监听 render-process-gone 事件。

mainWindow.webContents.on('render-process-gone', (event, details) => {
    console.error('渲染进程崩溃:', details.reason);
    dialog.showErrorBox('页面崩溃', '应用页面发生错误,即将重启');
    app.relaunch();
    app.quit();
});

版本对齐

锁定 package.json 中的 Electron 版本,避免 API 变动问题。原生模块需使用 electron-rebuild 重新编译。

npx electron-rebuild

进阶开发技巧

逻辑分离

计算密集型任务应放在主进程或 Worker 线程,避免阻塞 UI。

const { Worker } = require('worker_threads');
ipcMain.handle('heavy-task', async (event, data) => {
    return new Promise((resolve, reject) => {
        const worker = new Worker('./src/worker.js');
        worker.postMessage(data);
        worker.on('message', resolve);
        worker.on('error', reject);
    });
});

多窗口管理

统一管理窗口状态和生命周期。

class WindowManager {
    constructor() { this.windows = new Map(); }
    create(name, options) {
        if (this.windows.has(name)) return this.windows.get(name);
        const win = new BrowserWindow(options);
        this.windows.set(name, win);
        return win;
    }
}

资源压缩

启用 ASAR 打包,压缩静态资源。

packagerConfig: {
    asar: true,
    asarUnpack: ['node_modules/sharp/**']
}

总结

Electron 适合开发内部工具、数据录入系统等场景,开发效率高且跨平台。但对于视频剪辑、大型游戏等性能敏感型应用,建议采用原生方案。技术选型应避免过度设计,根据实际需求选择合适的工具。

目录

  1. Electron 桌面应用开发实战指南
  2. 引言
  3. Electron 架构解析
  4. 核心架构:Chromium 与 Node.js
  5. 其他跨平台方案对比
  6. 项目搭建与配置
  7. 使用脚手架初始化
  8. 主进程配置详解
  9. 预加载脚本与安全隔离
  10. 常见问题与优化
  11. 打包体积优化
  12. 内存管理
  13. 自动更新机制
  14. 安全最佳实践
  15. 实战案例:本地文本编辑器
  16. 调试与故障排查
  17. 主进程日志
  18. 渲染进程崩溃
  19. 版本对齐
  20. 进阶开发技巧
  21. 逻辑分离
  22. 多窗口管理
  23. 资源压缩
  24. 总结

更多推荐文章

查看全部
  • ComfyUI ControlNet 预处理器功能介绍与安装指南
  • Anaconda 安装与环境配置详解
  • Electron 一小时快速上手:前端桌面应用开发教程
  • Python Web 自动化测试实战:常用函数全解析与场景化应用指南
  • 基于Canvas和Web Audio API的交互式烟花动画网页游戏
  • SeaTunnel 2.3.11 与 Web 1.0.3 Docker 部署:Kafka 同步 Hive/ES 实战
  • C++微服务架构下的好友管理子服务设计与实现
  • 本周 GitHub 爆火!10 个开源神器,彻底改变你的 AI 开发效率
  • 本地搭建带知识库的 AI 助手(Ollama + Open WebUI)
  • SpringBoot 整合 Neo4j 图数据库项目实战详解
  • AI 大模型技术入门、进阶与职业发展路径详解
  • 2025 强网杯 Web 部分题目解题思路
  • ShellCrash Web面板部署与自定义配置指南
  • 优秀程序员的 7 个关键习惯
  • 相干伊辛机在医疗与医疗 AI 领域的应用前景
  • 无人机远程路径规划技术:A*算法与GPS定位实现
  • 1Panel 部署 Open WebUI 镜像加速:从 ghcr.io 到国内镜像源
  • 昇腾 NPU 部署 Llama 大模型全流程实战与性能测试
  • FPGA 高速接口实战:LitePCIe 开源核深度解析
  • VS Code 远程连接服务器后 GitHub Copilot 无法使用

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online