electron

Electron 性能优化与生产调优

By AI-Writer 13 min read

前言

Electron 应用常被诟病「内存占用大、启动慢」,这主要是因为 Chromium 本身是一个资源消耗较大的引擎。但通过合理的优化策略,Electron 应用的性能可以达到接近原生应用的水平。本文将从启动速度、内存管理、渲染性能和打包体积四个维度,系统讲解 Electron 应用的性能优化技巧。

启动时间优化

应用启动流程分析

Electron 应用的启动分为几个阶段:

plaintext
应用启动 → 主进程初始化 → 窗口创建 → 页面加载 → 内容渲染
    ↓           ↓              ↓           ↓            ↓
  ~100ms     ~200ms        ~100ms     ~500ms-3s     ~200ms-1s

                                    最大的瓶颈点

延迟加载窗口内容

不要在应用启动时立即加载完整内容,使用骨架屏和懒加载:

typescript
// main.ts — 启动时先创建窗口,不立即加载内容
app.whenReady().then(() => {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    show: false,          // 不立即显示
    backgroundColor: '#ffffff',
  });

  // 窗口准备好显示再加载内容
  win.once('ready-to-show', () => {
    win.show();
  });

  // 延迟加载(非关键页面)
  setTimeout(() => {
    win.loadURL('https://app.example.com/main');
  }, 100);
});

内容加载策略

typescript
// renderer.ts — 骨架屏 + 懒加载
import { lazy, Suspense } from 'react';

// 懒加载路由组件
const SettingsPage = lazy(() => import('./pages/Settings'));
const DashboardPage = lazy(() => import('./pages/Dashboard'));

function App() {
  return (
    <Suspense fallback={<SkeletonLoading />}>
      <Routes>
        <Route path="/" element={<DashboardPage />} />
        <Route path="/settings" element={<SettingsPage />} />
      </Routes>
    </Suspense>
  );
}

Vite 预构建优化

typescript
// vite.renderer.config.mjs
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    // 分包策略:分离 vendor 和应用代码
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom', 'react-router-dom'],
          utils: ['lodash', 'dayjs'],
        },
      },
    },
    // 启用 gzip 压缩
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,    // 生产环境移除 console.log
        drop_debugger: true,
      },
    },
  },
});

内存泄漏排查

常见内存泄漏场景

Electron 应用中内存泄漏通常发生在以下场景:

  1. IPC 监听器未移除
  2. 全局事件监听器未清理
  3. 定时器(setInterval)未清除
  4. 大量数据未释放(如大数组、DOM 引用)

使用 Chrome DevTools 排查

typescript
// main.ts — 开启 DevTools 以便排查
if (process.env.NODE_ENV === 'development') {
  mainWindow.webContents.openDevTools();
}

排查步骤

  1. 打开 DevTools → Memory 面板
  2. 选择 Allocation instrumentation on timeline
  3. 执行可疑操作(打开窗口、切换页面)
  4. 点击 Record,观察内存曲线是否持续上升
  5. 使用 Heap Snapshot 对比操作前后的内存差异

常见泄漏代码与修复

typescript
// ❌ 泄漏:setInterval 未清除
useEffect(() => {
  const timer = setInterval(() => {
    fetchData();
  }, 5000);
  // 组件卸载时忘记清除
}, []);

// ✅ 修复:返回清理函数
useEffect(() => {
  const timer = setInterval(() => {
    fetchData();
  }, 5000);
  return () => clearInterval(timer);
}, []);
typescript
// ❌ 泄漏:IPC 监听器累积
useEffect(() => {
  ipcRenderer.on('data:update', handler);
  // 组件重新渲染时监听器被重复添加
}, []);

// ✅ 修复:使用 once 或确保移除
useEffect(() => {
  ipcRenderer.on('data:update', handler);
  return () => {
    ipcRenderer.removeListener('data:update', handler);
  };
}, []);

electron-log 内存监控

typescript
// main.ts — 使用 electron-log 监控内存
import log from 'electron-log';

function logMemoryUsage() {
  const used = process.memoryUsage();
  log.info('内存使用情况:', {
    heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)} MB`,
    heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)} MB`,
    rss: `${Math.round(used.rss / 1024 / 1024)} MB`,
    external: `${Math.round(used.external / 1024 / 1024)} MB`,
  });
}

// 每 30 秒记录一次内存使用
setInterval(logMemoryUsage, 30_000);

GPU 和渲染优化

禁用不必要的 GPU 加速

在某些机器上,GPU 加速反而导致性能下降:

bash
# 启动时添加参数禁用 GPU
electron . --disable-gpu
electron . --disable-software-rasterizer

# 完全禁用 GPU 加速
electron . --disable-gpu-compositing

或在代码中控制:

typescript
// main.ts
app.commandLine.appendSwitch('disable-gpu');
app.commandLine.appendSwitch('disable-software-rasterizer');

减少合成器负担

typescript
// main.ts — 控制窗口合成方式
const win = new BrowserWindow({
  webPreferences: {
    // 禁用背景纹理,减少 GPU 负担
    backgroundThrottling: true, // 窗口不可见时降低帧率
  },
});

内容优化

css
/* 避免触发重排/重绘的属性 */
.will-change {
  /* 在动画开始前声明将改变的属性 */
  will-change: transform, opacity;
}

.composited-layer {
  /* 强制创建合成层(GPU 加速) */
  transform: translateZ(0);
}
typescript
// renderer.ts — 减少 DOM 操作
// ❌ 低效:每次循环都触发布局
for (let i = 0; i < 1000; i++) {
  container.appendChild(createElement(i));
}

// ✅ 高效:使用 DocumentFragment 批量插入
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  fragment.appendChild(createElement(i));
}
container.appendChild(fragment);

// ✅ 更优:使用虚拟列表(长列表场景)
import { FixedSizeList } from 'react-window';

打包体积优化

分析打包体积

bash
# 使用 electron-builder-analyze
pnpm add -D electron-builder-analytics

# 分析 dist 和 node_modules 体积
pnpm build && npx electron-builder-analalyze

asar 优化

json
// package.json
{
  "build": {
    "asar": true,
    "asarUnpack": [
      // 大型静态资源文件不需要打包到 asar 中
      "**/node_modules/lottie-web/**",
      "**/node_modules/@ffmpeg/**"
    ]
  }
}

使用 CDN 替代本地依赖

typescript
// 不再将大库打包进 asar
// 在 HTML 中通过 CDN 加载(需配合 CSP)

// index.html
<!-- 仅在需要的机器上加载,非首次启动 -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>

外部化大模块

javascript
// vite.main.config.mjs
export default defineConfig({
  build: {
    rollupOptions: {
      external: [
        'electron',
        'electron-log',
        'better-sqlite3',
        // 将大模块标记为 external,打包时引用 node_modules 中的版本
      ],
    },
  },
});

多进程架构

BrowserView 代替多窗口

BrowserView 将内容嵌入到主窗口中,相比多个 BrowserWindow 大幅节省资源:

typescript
// main.ts — 多 BrowserView 架构
const mainWindow = new BrowserWindow({ width: 1200, height: 800 });

// 主内容区域
const mainView = new BrowserView({ webPreferences: { nodeIntegration: false } });
mainWindow.setBrowserView(mainView);
mainView.setBounds({ x: 0, y: 50, width: 1200, height: 700 });
mainView.webContents.loadURL('https://app.example.com');

// 侧边栏区域
const sidebarView = new BrowserView();
mainWindow.setBrowserView(sidebarView);
sidebarView.setBounds({ x: 0, y: 50, width: 200, height: 700 });
sidebarView.webContents.loadURL('https://app.example.com/sidebar');

分离渲染进程

typescript
// main.ts — 显式创建独立的渲染进程
function createWindow() {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      // 启用独立渲染进程
      partition: 'persist:user-123',
      // 每个窗口使用独立渲染进程(增加内存但提高隔离性)
      contextIsolation: true,
    },
  });

  return win;
}

功耗优化

powerSaveBlocker 防止休眠

typescript
// main.ts — 保持屏幕常亮(如视频播放、演示场景)
import { powerSaveBlocker } from 'electron';

const id = powerSaveBlocker.start('prevent-display-sleep');
console.log('电源阻止器 ID:', id);

// 应用关闭或不再需要时停止
powerSaveBlocker.stop(id);

窗口不可见时降低资源占用

typescript
// main.ts — 窗口不可见时暂停任务
app.on('browser-window-blur', () => {
  // 用户切换到其他应用时暂停高频任务
  console.log('窗口失去焦点,降低资源占用');
});

app.on('browser-window-focus', () => {
  // 恢复任务
  console.log('窗口恢复焦点');
});

// macOS:窗口 mini 化时暂停
win.on('minimize', () => {
  // 暂停动画、心跳请求等
});

win.on('restore', () => {
  // 恢复操作
});

CI/CD 性能考量

yaml
# .github/workflows/performance-check.yml
name: Performance Check

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm

      - name: Build
        run: pnpm build

      - name: Check bundle size
        run: |
          du -sh release/mac/MyApp.app
          du -sh release/win/MyApp.exe

小结

  • 启动优化:延迟加载内容 + 骨架屏 + 懒加载路由
  • 内存泄漏:IPC 监听器清理、定时器清除、DevTools Memory 面板排查
  • GPU 控制:在必要机器上禁用 GPU 加速,减少合成器负担
  • 打包体积:asar 优化、外部化大模块、使用 CDN 替代本地包
  • 多进程架构:BrowserView 代替多窗口,分离独立渲染进程
  • 功耗优化powerSaveBlocker 防止休眠,窗口失焦时暂停高频任务

本系列文章到此结束。Electron 的学习路径从基础架构和开发环境开始,依次掌握了 IPC 通信、Preload 安全、窗口管理、原生能力集成、前端框架整合、打包分发、安全加固和性能优化八大主题,帮助你构建安全、高效、优质的桌面应用。

#electron #性能优化 #内存泄漏 #打包优化 #GPU

评论

A

Written by

AI-Writer

Related Articles

electron
#6

原生对话框与文件操作

使用 dialog.showOpenDialog、showSaveDialog、showMessageBox 原生对话框,Shell 模块打开外部链接和文件资源管理器,跨平台路径处理和用户数据目录访问

Read More
electron
#9

Electron 应用打包与分发

使用 electron-builder 配置多平台打包(Windows NSIS / macOS DMG / Linux AppImage),设置自动更新(electron-updater),配置代码签名(Authenticode / Apple Developer),搭建 GitHub Actions CI 流水线

Read More