使用electron-builder打包并自动更新

汪耀
2023-12-01

项目场景

一个已用Vue全家桶开发好的后台管理系统。应客户需求,需要限制电脑使用,但是不限制IP,用BS模式无法实现,故用Electron把该项目打包成CS模式的桌面应用。

Electron部分,使用electron-builder打包程序,使用electron-updater自动更新程序。使用Vue CLI Plugin Electron Builder和Vue Cli3集成。

Vue Cli3中集成Electron Builder

Vue CLI Plugin Electron Builder支持Electron中Vue的热加载,支持Electron部分的热加载,在vue.config.js中配置相关数据。

使用vue add electron-builder在vue cli3中安装electron-builder, 然后配置打包相关的东西,就可以使用yarn electron:serve开发,使用yarn electron:build打包。最好使用yarn安装相关依赖

如果安装electron是卡死,请使用淘宝镜像安装cnpm install electron -g

// 更多配置 https://www.electron.build/configuration/configuration
electronBuilder: {
  builderOptions: {
    appId: 'com.xxxxxx.example',
    productName: 'XXXXX',
    copyright: 'Copyright © 2018 XXXXXX',
    artifactName: 'XXXX Setup ${version}.${ext}', // 安装包名
    win: {
      icon: 'public/logo.png',
      target: {
        target: 'nsis', // 打包为nsis安装文件,
        arch: [
          'x64',
          'ia32'
        ] // 支持32、64位的Windows系统
      }
    },
    nsis: {
      oneClick: false, // 是否一键安装
      allowToChangeInstallationDirectory: true // 允许用户选择安装位置
    }
  }
}

这里会自动同步package.json中的name、version、author、description,如果上面配置中没有对这些进行配置,那么会获取package.json中的数据

自动更新

安装electron-updater

yarn add electron-updater

发布到GitHub (国内更新下载速度较慢)

  1. 在package.json添加脚本命令"electron:publish": "vue-cli-service electron:build --publish always"更多配置。这里主要是为了执行yarn electron:publish命令时,把最新的版本发布到GitHub的Release中
  2. 相关配置。在vue.config.js中使用下面的代码。如果没有使用Vue Cli3把下面代码转成json放置在package.json中的build下面。需要生成操作git仓库权限的Token
      publish: {
        provider: 'github',
        repo: 'xxxx', // git仓库
        owner: 'xxxx', // 拥有者
        token: 'xxxxxxxxxxxxxxx', // gitToken
        releaseType: 'release',
        publishAutoUpdate: true // 发布自动更新(需要配置GH_TOKEN)。 默认true
      }
  1. 在Electron的入口文件中background.js(普通Electron的是main.js)。添加监听自动更新代码。
const {autoUpdater} = require("electron-updater")
  
autoUpdater.autoDownload = false // 关闭自动更新
autoUpdater.autoInstallOnAppQuit = true // APP退出的时候自动安装
// 发送消息给渲染线程
function sendStatusToWindow(status, params) {
  win.webContents.send(status, params)
}
autoUpdater.on('checking-for-update', () => {
  sendStatusToWindow('Checking for update...')
})
autoUpdater.on('update-available', (info) => {
  // 可以更新版本
  sendStatusToWindow('autoUpdater-canUpdate', info)
})
// autoUpdater.on('update-not-available', (info) => {
//   // 不能够更新
// })
autoUpdater.on('error', (err) => {
  // 更新错误
  sendStatusToWindow('autoUpdater-error', err)
})
// 发起更新程序
ipcMain.on('autoUpdater-toDownload', () => {
  autoUpdater.downloadUpdate()
})
autoUpdater.on('download-progress', (progressObj) => {
  // 正在下载的下载进度
  sendStatusToWindow('autoUpdater-progress', progressObj)
})
autoUpdater.on('update-downloaded', (info) => {
  // 下载完成
  sendStatusToWindow('autoUpdater-downloaded')
})
// 退出程序
ipcMain.on('exit-app', () => {
  autoUpdater.quitAndInstall()
})

app.on('ready', async () => {
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    await installVueDevtools()
  }
  createWindow()

  // 每次运行APP检测更新。这里设置延时是为了避免还未开始渲染,更新检测就已经完成(网速超快,页面加载跟不上)。
  setTimeout(() => {
    // 检测是否有更新
    autoUpdater.checkForUpdates()
  }, 1500)
})
  1. 在渲染线程(Vue代码)中处理各种情形。
    template部分
  <el-dialog
     title="应用更新......"
     :visible="showUpdater"
     :close-on-click-modal="false"
     :close-on-press-escape="false"
     :show-close="false"
     width="620px"
     top="26vh"
     center>
     <template v-if="downloadProcess">
       <p>{{'当前:' + downloadProcess.transferred + '   /   共' + downloadProcess.total}}</p>
       <el-progress :text-inside="true" :stroke-width="18" :percentage="downloadProcess.percent"></el-progress>
       <p>正在下载({{downloadProcess.speed}})......</p>
     </template>
   </el-dialog>

js部分

  export default {
    name: 'App',
    data() {
      return {
        showUpdater: false,
        downloadProcess: null
      }
    },
    created() {
      // 仅在Electron模式下(为了让非Electron后能够正常运行,添加的判断)
      if (process.env.IS_ELECTRON) {
        const { ipcRenderer } = require('electron')
        // 发现新版本
        ipcRenderer.once('autoUpdater-canUpdate', (event, info) => {
          this.$confirm(`发现有新版本【v${info.version}】,是否更新?`, '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            ipcRenderer.send('autoUpdater-toDownload')
          })
        })
        // 下载进度
        ipcRenderer.on('autoUpdater-progress', (event, process) => {
          if (process.transferred >= 1024 * 1024) {
            process.transferred = (process.transferred / 1024 / 1024).toFixed(2) + 'M'
          } else {
            process.transferred = (process.transferred / 1024).toFixed(2) + 'K'
          }
          if (process.total >= 1024 * 1024) {
            process.total = (process.total / 1024 / 1024).toFixed(2) + 'M'
          } else {
            process.total = (process.total / 1024).toFixed(2) + 'K'
          }
          if (process.bytesPerSecond >= 1024 * 1024) {
            process.speed = (process.bytesPerSecond / 1024 / 1024).toFixed(2) + 'M/s'
          } else if (process.bytesPerSecond >= 1024) {
            process.speed = (process.bytesPerSecond / 1024).toFixed(2) + 'K/s'
          } else {
            process.speed = process.bytesPerSecond + 'B/s'
          }
          process.percent = process.percent.toFixed(2)
          this.downloadProcess = process
          this.showUpdater = true
        })
        // 下载更新失败
        ipcRenderer.once('autoUpdater-error', (event) => {
          this.$message.error('更新失败!')
          this.showUpdater = false
        })
        // 下载完成
        ipcRenderer.once('autoUpdater-downloaded', () => {
          this.$confirm(`更新完成,是否关闭应用程序安装新版本?`, '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            ipcRenderer.send('exit-app')
          })
        })
      }
    },

发布到私有/云存储服务器

发布到GitHub虽然实现自动推送等功能,但是在国内更新下载的速度太慢。所以推荐后端搭建文件存储服务器,或者使用七牛云等云存储服务器。这里以七牛云示例。

  1. 因为electron-builder并没有支持搭建的服务器、或者一般云存储服务器的自动推送,所以这个"electron:publish": "vue-cli-service electron:build --publish always"脚本命令也就意义不大了。和使用yarn electron:build是一样的效果
  2. publish的配置
   publish: {
      provider: 'generic',
      url: 'http://xxx.xxxx.com/app/' // 七牛云存储服务器的下载链接。下面必须要lasted.yml文件和需要更新的exe文件
    }

然后后面的操作就和发布到GitHub一样的。需要注意的是,有的electron-builder版本不能正确获取到publish的配置,所以需要在electron的入口文件手动设置feedURL。

  // 在开启更新监听事件之前设置
  autoUpdater.setFeedURL({
    provider: 'generic',
    url: 'http://xxxx.xxxx.com/app/' // 一定要保证该地址下面包含lasted.yml文件和需要更新的exe文件
  })

记录一些坑

  1. 在electron项目路径中不能有中文、空格等非ASCII字符串。否则会报错
  Error: C:\Users\Vincent\AppData\Local\electron-builder\Cache\nsis\nsis-3.0.3.2\Bin\makensis.exe exited with code 1

  Error output:
  !include: could not find: "E:\WorkFile\xxx\iwiteks_xxxx\node_modules\app-builder-lib\templates\nsis\include\StdUtils.nsh"
  Error in script "<stdin>" on line 1 -- aborting creation process
  1. 结合Vue CLi3使用,然后以非Electron模式(yarn serve)运行。页面空白,报错
    Uncaught TypeError: fs.existsSync is not a function

解决方案: 渲染线程中的Electron相关代码添加process.env.IS_ELECTRON判断。

Demo

https://github.com/lytian/electron-builder-demo

 类似资料: