Skip to content

第15章:Electron 进阶技巧

15.1 应用自动更新

15.1.1 autoUpdater 模块

Electron 提供了 autoUpdater 模块,用于实现应用的自动更新功能。

核心功能

  • 检查更新
  • 下载更新
  • 安装更新
  • 重启应用

15.1.2 实现步骤

  1. 安装依赖

    bash
    npm install electron-updater
  2. 配置 package.json

    json
    {
      "build": {
        "publish": {
          "provider": "github",
          "repo": "username/repository",
          "owner": "username",
          "private": false,
          "releaseType": "release"
        }
      }
    }
  3. 实现自动更新逻辑

    javascript
    const { app, autoUpdater, dialog } = require('electron')
    const { autoUpdater: electronUpdater } = require('electron-updater')
    
    // 配置自动更新
    function setupAutoUpdater() {
      // 设置更新服务器地址
      electronUpdater.setFeedURL({
        provider: 'github',
        owner: 'username',
        repo: 'repository',
        releaseType: 'release'
      })
    
      // 检查更新
      electronUpdater.checkForUpdates()
    
      // 监听更新事件
      electronUpdater.on('update-available', () => {
        console.log('有新版本可用')
      })
    
      electronUpdater.on('update-downloaded', () => {
        dialog.showMessageBox({
          type: 'info',
          title: '更新可用',
          message: '新版本已下载完成,是否立即重启应用?',
          buttons: ['是', '否']
        }).then((result) => {
          if (result.response === 0) {
            electronUpdater.quitAndInstall()
          }
        })
      })
    
      electronUpdater.on('error', (error) => {
        console.error('更新失败:', error)
      })
    }
    
    // 在应用就绪后设置自动更新
    app.whenReady().then(() => {
      setupAutoUpdater()
      // 创建窗口等其他逻辑
    })

15.2 自定义应用图标、启动页

15.2.1 应用图标

不同平台的图标要求

  • Windows.ico 格式,推荐尺寸 256x256
  • macOS.icns 格式,包含多种尺寸
  • Linux.png 格式,推荐尺寸 512x512

配置方法

json
{
  "build": {
    "mac": {
      "icon": "assets/icon.icns"
    },
    "win": {
      "icon": "assets/icon.ico"
    },
    "linux": {
      "icon": "assets/icon.png"
    }
  }
}

15.2.2 启动页

实现方法

  1. 创建启动页 HTML

    html
    <!-- splash.html -->
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>启动中</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          height: 100vh;
          background-color: #f0f0f0;
        }
        .logo {
          font-size: 24px;
          font-weight: bold;
          margin-bottom: 20px;
        }
        .progress {
          width: 200px;
          height: 10px;
          background-color: #e0e0e0;
          border-radius: 5px;
          overflow: hidden;
        }
        .progress-bar {
          width: 0;
          height: 100%;
          background-color: #4CAF50;
          transition: width 0.3s ease;
        }
      </style>
    </head>
    <body>
      <div class="logo">我的应用</div>
      <div class="progress">
        <div class="progress-bar" id="progress-bar"></div>
      </div>
      <script>
        // 模拟加载过程
        let progress = 0;
        const progressBar = document.getElementById('progress-bar');
        
        const interval = setInterval(() => {
          progress += 10;
          progressBar.style.width = progress + '%';
          
          if (progress >= 100) {
            clearInterval(interval);
            // 通知主进程加载完成
            require('electron').ipcRenderer.send('splash-loaded');
          }
        }, 200);
      </script>
    </body>
    </html>
  2. 在主进程中实现启动页

    javascript
    const { app, BrowserWindow, ipcMain } = require('electron')
    const path = require('path')
    
    let splashWindow
    let mainWindow
    
    function createSplashWindow() {
      splashWindow = new BrowserWindow({
        width: 400,
        height: 300,
        frame: false,
        transparent: true,
        alwaysOnTop: true,
        webPreferences: {
          nodeIntegration: true,
          contextIsolation: false
        }
      })
    
      splashWindow.loadFile('splash.html')
    }
    
    function createMainWindow() {
      mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
          nodeIntegration: true,
          contextIsolation: false
        },
        show: false // 先隐藏主窗口
      })
    
      mainWindow.loadFile('index.html')
    
      mainWindow.once('ready-to-show', () => {
        // 主窗口准备就绪后显示
        mainWindow.show()
        // 关闭启动页
        if (splashWindow) {
          splashWindow.close()
        }
      })
    }
    
    app.whenReady().then(() => {
      createSplashWindow()
      // 模拟加载时间
      setTimeout(createMainWindow, 2000)
    })
    
    // 监听启动页加载完成事件
    ipcMain.on('splash-loaded', () => {
      console.log('启动页加载完成')
    })

15.3 主进程与渲染进程权限控制

15.3.1 安全最佳实践

主进程权限

  • 可以访问所有 Electron API
  • 可以访问 Node.js API
  • 可以访问文件系统

渲染进程权限

  • 默认情况下,不应该直接访问 Node.js API
  • 应该通过 IPC 与主进程通信,由主进程执行敏感操作

15.3.2 上下文隔离

启用上下文隔离

javascript
const win = new BrowserWindow({
  webPreferences: {
    contextIsolation: true,
    preload: path.join(__dirname, 'preload.js')
  }
})

创建预加载脚本

javascript
// preload.js
const { contextBridge, ipcRenderer } = require('electron')

// 向渲染进程暴露安全的 API
contextBridge.exposeInMainWorld('electronAPI', {
  // 暴露方法
  sendMessage: (message) => ipcRenderer.send('message', message),
  onMessage: (callback) => ipcRenderer.on('message', (event, ...args) => callback(...args)),
  // 暴露属性
  appVersion: process.version
})

在渲染进程中使用

javascript
// 渲染进程中
window.electronAPI.sendMessage('Hello from renderer')

window.electronAPI.onMessage((message) => {
  console.log('收到消息:', message)
})

console.log('应用版本:', window.electronAPI.appVersion)

15.4 第三方插件使用

15.4.1 常用插件

15.4.1.1 electron-store

功能:持久化存储应用配置和用户数据

安装

bash
npm install electron-store

使用

javascript
const Store = require('electron-store')

const store = new Store({
  name: 'app-config',
  defaults: {
    theme: 'light',
    language: 'zh-CN'
  }
})

// 存储数据
store.set('user', { name: 'John', email: 'john@example.com' })

// 获取数据
const user = store.get('user')

// 删除数据
store.delete('user')

15.4.1.2 electron-log

功能:日志管理

安装

bash
npm install electron-log

使用

javascript
const log = require('electron-log')

// 配置日志
log.transports.file.level = 'info'
log.transports.console.level = 'debug'

// 使用日志
log.info('应用启动')
log.warn('警告信息')
log.error('错误信息')
log.debug('调试信息')

15.4.1.3 electron-updater

功能:应用自动更新

安装

bash
npm install electron-updater

使用

javascript
const { autoUpdater } = require('electron-updater')

// 检查更新
autoUpdater.checkForUpdatesAndNotify()

// 监听更新事件
autoUpdater.on('update-available', () => {
  console.log('有新版本可用')
})

autoUpdater.on('update-downloaded', () => {
  console.log('更新已下载完成')
  // 提示用户重启应用
})

15.5 Electron 版本升级与兼容性处理

15.5.1 版本升级注意事项

主要版本升级

  • 可能包含 breaking changes
  • 需要更新依赖
  • 需要测试应用功能

次要版本升级

  • 通常包含新功能和 bug 修复
  • 兼容性较好

补丁版本升级

  • 主要包含 bug 修复
  • 兼容性最好

15.5.2 兼容性处理

处理方法

  1. 使用特性检测

    javascript
    const { app } = require('electron')
    
    if (app.isPackaged) {
      // 生产环境逻辑
    } else {
      // 开发环境逻辑
    }
  2. 版本检测

    javascript
    const { app } = require('electron')
    const electronVersion = process.versions.electron
    
    if (parseInt(electronVersion.split('.')[0]) >= 18) {
      // 新版本逻辑
    } else {
      // 旧版本逻辑
    }
  3. 使用 polyfill

    • 对于已废弃的 API,提供替代实现
    • 对于新 API,提供降级方案

15.6 实操案例:高级 Electron 应用

15.6.1 场景描述

创建一个具有以下功能的 Electron 应用:

  • 自动更新
  • 自定义启动页
  • 安全的 IPC 通信
  • 持久化存储
  • 日志管理

15.6.2 实现步骤

  1. 创建项目结构

    advanced-app/
    ├── main.js
    ├── preload.js
    ├── splash.html
    ├── index.html
    ├── package.json
    └── assets/
        ├── icon.ico
        ├── icon.icns
        └── icon.png
  2. 修改 package.json

    json
    {
      "name": "advanced-electron-app",
      "version": "1.0.0",
      "description": "高级 Electron 应用示例",
      "main": "main.js",
      "scripts": {
        "start": "electron .",
        "build": "electron-builder"
      },
      "devDependencies": {
        "electron": "^18.0.0",
        "electron-builder": "^22.14.5"
      },
      "dependencies": {
        "electron-store": "^8.0.1",
        "electron-log": "^4.4.8",
        "electron-updater": "^4.6.5"
      },
      "build": {
        "appId": "com.example.advanced-app",
        "productName": "高级 Electron 应用",
        "copyright": "Copyright © 2023 Example",
        "directories": {
          "output": "dist"
        },
        "files": [
          "main.js",
          "preload.js",
          "splash.html",
          "index.html",
          "package.json",
          "assets/**/*"
        ],
        "mac": {
          "category": "public.app-category.utilities",
          "icon": "assets/icon.icns"
        },
        "win": {
          "target": [
            "nsis",
            "portable"
          ],
          "icon": "assets/icon.ico"
        },
        "linux": {
          "target": [
            "deb",
            "rpm",
            "AppImage"
          ],
          "icon": "assets/icon.png"
        },
        "publish": {
          "provider": "github",
          "repo": "username/repository",
          "owner": "username",
          "private": false,
          "releaseType": "release"
        }
      }
    }
  3. 修改 main.js

    javascript
    const { app, BrowserWindow, ipcMain, dialog } = require('electron')
    const { autoUpdater } = require('electron-updater')
    const Store = require('electron-store')
    const log = require('electron-log')
    const path = require('path')
    
    // 配置日志
    log.transports.file.level = 'info'
    log.transports.console.level = 'debug'
    
    // 创建存储实例
    const store = new Store({
      name: 'app-config',
      defaults: {
        theme: 'light',
        language: 'zh-CN',
        lastWindowState: {
          width: 800,
          height: 600
        }
      }
    })
    
    let splashWindow
    let mainWindow
    
    // 设置自动更新
    function setupAutoUpdater() {
      autoUpdater.logger = log
      autoUpdater.checkForUpdatesAndNotify()
    
      autoUpdater.on('update-available', () => {
        log.info('有新版本可用')
      })
    
      autoUpdater.on('update-downloaded', () => {
        dialog.showMessageBox({
          type: 'info',
          title: '更新可用',
          message: '新版本已下载完成,是否立即重启应用?',
          buttons: ['是', '否']
        }).then((result) => {
          if (result.response === 0) {
            autoUpdater.quitAndInstall()
          }
        })
      })
    
      autoUpdater.on('error', (error) => {
        log.error('更新失败:', error)
      })
    }
    
    // 创建启动页
    function createSplashWindow() {
      splashWindow = new BrowserWindow({
        width: 400,
        height: 300,
        frame: false,
        transparent: true,
        alwaysOnTop: true,
        webPreferences: {
          nodeIntegration: true,
          contextIsolation: false
        }
      })
    
      splashWindow.loadFile('splash.html')
    }
    
    // 创建主窗口
    function createMainWindow() {
      const lastWindowState = store.get('lastWindowState')
    
      mainWindow = new BrowserWindow({
        width: lastWindowState.width,
        height: lastWindowState.height,
        webPreferences: {
          nodeIntegration: false,
          contextIsolation: true,
          preload: path.join(__dirname, 'preload.js')
        },
        show: false
      })
    
      mainWindow.loadFile('index.html')
    
      mainWindow.once('ready-to-show', () => {
        mainWindow.show()
        if (splashWindow) {
          splashWindow.close()
        }
      })
    
      mainWindow.on('close', () => {
        // 保存窗口状态
        store.set('lastWindowState', {
          width: mainWindow.getBounds().width,
          height: mainWindow.getBounds().height
        })
      })
    
      mainWindow.on('closed', () => {
        mainWindow = null
      })
    }
    
    // IPC 事件处理
    ipcMain.handle('get-settings', () => {
      return store.store
    })
    
    ipcMain.handle('update-settings', (event, settings) => {
      store.set(settings)
      return '设置已更新'
    })
    
    ipcMain.handle('get-app-version', () => {
      return app.getVersion()
    })
    
    // 全局错误捕获
    process.on('uncaughtException', (error) => {
      log.error('未捕获异常:', error)
    })
    
    process.on('unhandledRejection', (reason) => {
      log.error('未处理的 Promise 拒绝:', reason)
    })
    
    app.whenReady().then(() => {
      log.info('应用启动')
      createSplashWindow()
      setTimeout(() => {
        createMainWindow()
        setupAutoUpdater()
      }, 2000)
    })
    
    app.on('window-all-closed', function () {
      if (process.platform !== 'darwin') app.quit()
    })
    
    app.on('activate', function () {
      if (BrowserWindow.getAllWindows().length === 0) {
        createSplashWindow()
        setTimeout(() => {
          createMainWindow()
        }, 2000)
      }
    })
  4. 修改 preload.js

    javascript
    const { contextBridge, ipcRenderer } = require('electron')
    
    contextBridge.exposeInMainWorld('electronAPI', {
      // 设置相关
      getSettings: () => ipcRenderer.invoke('get-settings'),
      updateSettings: (settings) => ipcRenderer.invoke('update-settings', settings),
      // 应用相关
      getAppVersion: () => ipcRenderer.invoke('get-app-version'),
      // 消息传递
      sendMessage: (message) => ipcRenderer.send('message', message),
      onMessage: (callback) => ipcRenderer.on('message', (event, ...args) => callback(...args))
    })
  5. 修改 splash.html

    html
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>启动中</title>
      <style>
        body {
          margin: 0;
          padding: 0;
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          height: 100vh;
          background-color: #f0f0f0;
        }
        .logo {
          font-size: 24px;
          font-weight: bold;
          margin-bottom: 20px;
        }
        .progress {
          width: 200px;
          height: 10px;
          background-color: #e0e0e0;
          border-radius: 5px;
          overflow: hidden;
        }
        .progress-bar {
          width: 0;
          height: 100%;
          background-color: #4CAF50;
          transition: width 0.3s ease;
        }
      </style>
    </head>
    <body>
      <div class="logo">高级 Electron 应用</div>
      <div class="progress">
        <div class="progress-bar" id="progress-bar"></div>
      </div>
      <script>
        let progress = 0;
        const progressBar = document.getElementById('progress-bar');
        
        const interval = setInterval(() => {
          progress += 10;
          progressBar.style.width = progress + '%';
          
          if (progress >= 100) {
            clearInterval(interval);
          }
        }, 200);
      </script>
    </body>
    </html>
  6. 修改 index.html

    html
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>高级 Electron 应用</title>
      <style>
        body {
          font-family: Arial, sans-serif;
          margin: 20px;
          padding: 0;
          background-color: #f0f0f0;
        }
        .container {
          max-width: 700px;
          margin: 0 auto;
          background-color: white;
          padding: 20px;
          border-radius: 8px;
          box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        h1 {
          color: #333;
          text-align: center;
        }
        .info-section {
          margin: 20px 0;
          padding: 15px;
          background-color: #f9f9f9;
          border-radius: 4px;
        }
        .setting-item {
          margin: 10px 0;
        }
        label {
          display: block;
          margin-bottom: 5px;
          font-weight: bold;
        }
        select {
          width: 100%;
          padding: 8px;
          border: 1px solid #ddd;
          border-radius: 4px;
        }
        .version {
          text-align: center;
          margin-top: 30px;
          font-size: 14px;
          color: #999;
        }
      </style>
    </head>
    <body>
      <div class="container">
        <h1>高级 Electron 应用</h1>
        
        <div class="info-section">
          <h3>应用信息</h3>
          <div id="app-version"></div>
        </div>
        
        <div class="info-section">
          <h3>设置</h3>
          <div class="setting-item">
            <label for="theme">主题</label>
            <select id="theme">
              <option value="light">浅色</option>
              <option value="dark">深色</option>
            </select>
          </div>
          <div class="setting-item">
            <label for="language">语言</label>
            <select id="language">
              <option value="zh-CN">中文</option>
              <option value="en-US">英文</option>
            </select>
          </div>
        </div>
        
        <div class="version">版本:<span id="version"></span></div>
      </div>
      
      <script>
        const themeSelect = document.getElementById('theme')
        const languageSelect = document.getElementById('language')
        const versionElement = document.getElementById('version')
        const appVersionElement = document.getElementById('app-version')
    
        // 加载设置
        async function loadSettings() {
          const settings = await window.electronAPI.getSettings()
          themeSelect.value = settings.theme
          languageSelect.value = settings.language
        }
    
        // 保存设置
        async function saveSettings() {
          const settings = {
            theme: themeSelect.value,
            language: languageSelect.value
          }
          await window.electronAPI.updateSettings(settings)
        }
    
        // 加载应用版本
        async function loadAppVersion() {
          const version = await window.electronAPI.getAppVersion()
          versionElement.textContent = version
          appVersionElement.textContent = `应用版本:${version}`
        }
    
        // 事件监听
        themeSelect.addEventListener('change', saveSettings)
        languageSelect.addEventListener('change', saveSettings)
    
        // 初始化
        loadSettings()
        loadAppVersion()
      </script>
    </body>
    </html>
  7. 运行应用

    bash
    npm install
    npm start

15.7 小结

通过本章的学习,你已经掌握了 Electron 开发的进阶技巧:

  • 应用自动更新:使用 autoUpdater 模块实现应用的自动更新功能
  • 自定义应用图标和启动页:为应用添加专业的图标和启动页
  • 主进程与渲染进程权限控制:使用上下文隔离和预加载脚本提高应用安全性
  • 第三方插件使用:使用 electron-store、electron-log 等插件增强应用功能
  • 版本升级与兼容性处理:处理不同版本的 Electron 兼容性问题

这些进阶技巧将帮助你开发更加专业、安全和功能丰富的 Electron 应用。在接下来的章节中,我们将学习 Electron 的高频面试题和拓展学习方向。

© 2026 编程马·菜鸟教程 版权所有