我和electron有个误会(三)

164 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天


背景介绍

公司接到需求,需要使用开发一个桌面程序用于客户自助机使用,因为没有人会搞桌面端开发,身为啥都搞的前端程序员,身披战袍出战


书接上回,经过一天的调试,终于完成了这个新奇需求的开发,当我又准备去找产品炫耀的时候,产品又黑着脸,"我们现在更新每次要客服重新下载我们的安装包,客户觉得麻烦,我希望可以做热更新,😱好家伙,来源不断的需求,热更新是吧,难不倒我的,经我缜密的调研,发现一个库electron-updater,这个库需要配合electron的打包一起使用,那我们就从打包配置入手误会(一)打包配置


打包配置修改

    ...,
    "publish": {
        // 开发者
      "provider": "generic",
        // 对应包的地址
      "url": "xxx",
      "channel": "latest"
    },
    ...

我们在package.json文件中修改打包配置,添加最重要的publish属性,该属性最重要的字段是url字段,该字段是electron检测更新包的地址,添加好对应属性后,开始我们的更新方法编写


如何检测更新

    npm install electron-updater

先下载需要使用的工具库,然后新增一个update.js,你们爱叫啥就建啥

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

let mainWindow = null

function sendUpdateMessage (text) {
  mainWindow.webContents.send('message', text)
}

module.exports = function updateHandle(window, updateURL) {
  mainWindow = window
  const message = {
    error: '检查更新出错',
    checking: '正在检查更新…',
    updateAva: '正在更新',
    updateNotAva: '已经是最新版本',
    downloadProgress: '正在下载...'
  }
  // 设置是否自动下载,默认是true,当点击检测到新版本时,会自动下载安装包,所以设置为false
  autoUpdater.autoDownload = false
  // 设置地址
  autoUpdater.setFeedURL(updateURL)
  // 报错反馈
  autoUpdater.on('error', function (e) {
    sendUpdateMessage({ cmd: 'error', message: message.error, e })
  })
  // 检查更新反馈
  autoUpdater.on('checking-for-update', function (info) {
    sendUpdateMessage({ cmd: 'checking-for-update', message: message.checking, info })
  })
  autoUpdater.on('update-available', function (info) {
    sendUpdateMessage({ cmd: 'update-available', message: message.updateAva, info })
  })
  autoUpdater.on('update-not-available', function (info) {
    sendUpdateMessage({ cmd: 'update-not-available', message: message.updateNotAva, info: info })
  })
  // 更新下载进度事件
  autoUpdater.on('download-progress', function (progressObj) {
    sendUpdateMessage({ cmd: 'downloadProgress', message: message.downloadProgress, progressObj })
  })
  autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
    autoUpdater.quitAndInstall()
    mainWindow.destroy()
  })
  // 主线程检查更新事件
  ipcMain.on('checkForUpdate', () => {
    sendUpdateMessage({ cmd: '执行自动更新检查', message: '执行自动更新检查' })
    autoUpdater.checkForUpdates()
  })
  // 主线程开始更新事件
  ipcMain.on('downloadUpdate', () => {
    log.warn('执行下载')
    autoUpdater.downloadUpdate()
  })

这个文件是封装了一个更新事件的回调,并通过接受主线程的消息来进行更新,这样可以有效的控制程序的更新而不是自动更新,然后主线程引入文件方法

   const upgradeFn = require('./update')
   
   app.whenReady().then(() => {
       ...,
       // 第一个参数传入 electron主体应用对象
       // 第二个参数就是包对应的服务器地址和packag.json里面一致
       upgradeFn(win, xxx)
       ...
  });

做完上述准备后,我们就可以进行调试,首先进行首次打包,打包ing>>>>>>,叮!,打包完成,会输入以下文件

image.png

因为公司的东西,需要马赛克,生成一个对应的安装包和一个latest.yml文件,latest.yml是重中之重,electron之后会通过该文件进行对比检测是否有更新程序


启动一个测试服务器

作为一个前端程序员,应该要会自己启服务器,只有拥有充分的后端知识,才可以和别人battle🐕,这里我直接上代码了


const express = require('express')
const app = express()
const cors = require('cors')
const path = require('path')
app.use('/static', express.static(path.join(__dirname, 'public')))
app.use(cors())
app.listen(8001)
console.log('server start at 8001')

app.get('/', (req, res) => {
  res.send('welcome JueJin')
})

启动一个服务器,把刚刚打包生成的两个文件放在服务器public文件下,服务器目录结构

image.png


更改electron版本,再次打包

现在我们需要做的就是,改一点代码,然后再重新生成一份安装包,不过这里需要注意,我的项目electron版本是和package.json文件里的版本是对应的,需要修改一下,两个不同版本的文件才有对比的意义,打包中>>>>>>,叮~完成。

然后我们打开打包完成后的程序,通过页面通知主线程的checkForUpdate的命令,electron就会显示检测的结果了,这里我忘了截图,就不截了🐕


我踩过的坑,你们继续踩,electron的通信问题

electron中,主线程和渲染线程之间的通信是通过自带的ipcMain方法进行通信,具体使用方法可以看文档,其实就是简单的发布订阅,不过这里有个巨坑,就是渲染线程想要调用ipcMain方法时,需要用electron作为桥梁传递该对象给到渲染进程(vue页面),这里就需要使用electron的预加载功能

新建preload.js文件


const { contextBridge, ipcRenderer } =  require('electron/renderer')

// 注册ipc
contextBridge.exposeInMainWorld('electron', {
  ipcRenderer
})

上述代码,通过electron自带的contextBridge方法,传递对象,然后在主线程注册preload.js,这里很简单就不复制代码了,但是当你使用contextBridge传递对象的时候,在vue页面接受到的ipcRenderer对象之后监听方法,缺没有发布方法,所以无法通过页面去通知electron进行任何操作,包括更新。一杯茶一包烟,一个bug修一天,就我花了整整一天去研究这个bug的时候,在github上给我找到了解决方法

image.png

大佬的意思简单就是说,如果直接通过ipcRenderer对象渲染进程就可以控制主线程的话,真的太不安全拉,所以我们取消了它发布的权限,如果你们还是想这么做,可以通过桥梁,给渲染进程传递对应的工厂函数哔哩吧啦一大堆,不过还是找到解决该bug的方法

// 注册ipc
contextBridge.exposeInMainWorld('electron', {
  ipcRenderer: { 
    ...ipcRenderer, 
    onMessage: (handler) => ipcRenderer.on('message', (event, ...args) => handler(...args))
  }
})

将注册方法改成这样,vue页面可以通过ipcRenderer对象的onMessage事件进行发布操作,完美完美,这样子就可以通过页面通知electron进行更新拉


结尾

当我向产品再次炫耀我做完的功能后,产品这次直接五体投地,世界终于恢复平静的摸鱼时间,暂时没有新的需求了

关于作者

一个工作三年,摆烂躺平的前端攻城狮~~~🦁

往期链接

我和electron有个误会(二)

我和electron有个误会