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

项目场景

一个已用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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 更多配置 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

    1
    2
    3
    4
    5
    6
    7
    8
    publish: {
    provider: 'github',
    repo: 'xxxx', // git仓库
    owner: 'xxxx', // 拥有者
    token: 'xxxxxxxxxxxxxxx', // gitToken
    releaseType: 'release',
    publishAutoUpdate: true // 发布自动更新(需要配置GH_TOKEN)。 默认true
    }
  3. 在Electron的入口文件中background.js(普通Electron的是main.js)。添加监听自动更新代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    const {autoUpdater} = require("electron-updater")

    // 发送消息给渲染线程
    function sendStatusToWindow(status, params) {
    win.webContents.send(status, params)
    }
    autoUpdater.autoDownload = false // 关闭自动更新
    autoUpdater.autoInstallOnAppQuit = true // APP退出的时候自动安装
    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)
    })
    autoUpdater.on('download-progress', (progressObj) => {
    // 正在下载的下载进度
    sendStatusToWindow('autoUpdater-progress', progressObj)
    })
    autoUpdater.on('update-downloaded', (info) => {
    // 下载完成
    sendStatusToWindow('autoUpdater-downloaded')
    })

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

    // 每次运行APP检测更新。这里设置延时是为了避免还未开始渲染,更新检测就已经完成(网速超快,页面加载跟不上)。
    setTimeout(() => {
    // 检测是否有更新
    autoUpdater.checkForUpdates()
    }, 1500)
    })
  4. 在渲染线程(Vue代码)中处理各种情形。
    template部分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <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部分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    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的配置
    1
    2
    3
    4
    publish: {
    provider: 'generic',
    url: 'http://xxx.xxxx.com/app/' // 七牛云存储服务器的下载链接。下面必须要lasted.yml文件和需要更新的exe文件
    }

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

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

记录一些坑

  1. 在electron项目路径中不能有中文、空格等非ASCII字符串。否则会报错

    1
    2
    3
    4
    5
    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\遵义宏荣源地产\iwiteks_admin_for_hry\node_modules\app-builder-lib\templates\nsis\include\StdUtils.nsh"
    Error in script "<stdin>" on line 1 -- aborting creation process
  2. 结合Vue CLi3使用,然后以非Electron模式(yarn serve)运行。页面空白,报错

    1
    Uncaught TypeError: fs.existsSync is not a function

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

坚持原创技术分享,您的支持将鼓励我继续创作!