前言
之前要更新自己做的electron
软件,只能到github
重新下载release
包安装,太麻烦了。所以就决定添加一个自动更新
的功能,正好来试一下electron
如何实现自动更新。
因为我的项目是通过electron-vite
脚手架搭建的,所以自带了electron-updater
和electron-builder
,只要配置一下,写一下更新逻辑就可以了实现了。
先来看看效果,下载过程就跳过了。

后面有源码仓库地址
正文
自动更新原理: 检查安装的版本号和远程的版本号是否一致。打包后build
文件夹里会有一个latest.yml
文件,里面有一个version
字段。

流程
- 安装
electron-updater
和electron-builder
- 配置更新地址
为了在开发环境中好测试功能,编写以下代码,不然会出现Skip checkForUpdates because application is not packed and dev update config is not forced
js
//update.ts
if (!app.isPackaged) {
Object.defineProperty(app, 'isPackaged', {
get: () => true
})
autoUpdater.forceDevUpdateConfig = true
autoUpdater.updateConfigPath = path.join(__dirname, '../../dev-app-update.yml')
}
在dev-app-update.yml
配置,为了开发环境使用
yml
provider: generic
url: http://www.hewkq.top/auto-updates
updaterCacheDirName: screenshot-translation-updater
在根目录下electron-builder.yml
中配置,为了生产环境使用。
yml
publish:
provider: generic
url: http://www.hewkq.top/auto-updates
- 主进程更新逻辑
ts
//update.ts
import { app, ipcMain, BrowserWindow } from 'electron'
import { autoUpdater } from 'electron-updater'
import path from 'path'
import { SendEnum } from '../type/ipc-constants'
import { UpdateProgress } from '../type/update'
import { showNotification } from './utils/notification'
import { NoticeType } from '../type/notice'
export const registerAutoUpdate = (mainWindow: BrowserWindow) => {
/* 开发环境 */
if (!app.isPackaged) {
Object.defineProperty(app, 'isPackaged', {
get: () => true
})
autoUpdater.forceDevUpdateConfig = true
autoUpdater.updateConfigPath = path.join(__dirname, '../../dev-app-update.yml')
}
/* 不允许自动下载更新 */
autoUpdater.autoDownload = false
/* 允许降级更新(应付回滚的情况) */
autoUpdater.allowDowngrade = true
/* 开始检查更新 */
autoUpdater.on('checking-for-update', () => {
console.log('start checking for update')
})
/* 发现更新 */
autoUpdater.on('update-available', (info) => {
console.log('find update version', info)
})
/* 没有更新 */
autoUpdater.on('update-not-available', (info) => {
console.log('no need to update', info.version)
})
/* 更新下载进度 */
autoUpdater.on('download-progress', (progressInfo) => {
console.log('update progress', progressInfo)
mainWindow.webContents.send(SendEnum.DOWNLOAD_PROGRESS, progressInfo)
})
/* 更新下载完成 */
autoUpdater.on('update-downloaded', () => {
console.log('update downloaded')
showNotification('更新下载完成,请等待重启', NoticeType.SUCCESS)
autoUpdater.quitAndInstall()
})
/* 更新失败 */
autoUpdater.on('error', (errorMessage) => {
console.log('update error', errorMessage.message)
showNotification(errorMessage.message, NoticeType.ERROR)
mainWindow.webContents.send(SendEnum.DOWNLOAD_FAIL)
})
/** 获取应用版本 */
ipcMain.handle(SendEnum.GET_APP_VERSION, (event) => {
return app.getVersion()
})
/* 检查更新 */
ipcMain.on(SendEnum.CHECK_UPDATE, (event) => {
autoUpdater.checkForUpdates().then((result) => {
event.reply(SendEnum.CHECK_UPDATE_RESULT, result)
event.reply(SendEnum.CHECK_UPDATE_COMPLETE, result)
})
})
/* 下载更新 */
ipcMain.on(SendEnum.DOWNLOAD_UPDATE, (event) => {
autoUpdater.downloadUpdate()
})
}
将registerAutoUpdat
引入主进程入口文件main.ts
中。
- 渲染进程代码
tsx
//APP.tsx
useEffect(() => {
window.electron.ipcRenderer.send(SendEnum.CHECK_UPDATE)
return () => {
window.electron.ipcRenderer.removeAllListeners(SendEnum.CHECK_UPDATE_RESULT)
}
}, [])
tsx
//UpdateDialog.tsx
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from '@renderer/components/ui/dialog'
import { Button } from '@renderer/components/ui/button'
import { useCallback, useEffect, useState } from 'react'
import { SendEnum } from '../../../type/ipc-constants'
import { UpdateProgress } from '../../../type/update'
import { Progress } from '@renderer/components/ui/progress'
import { X } from 'lucide-react'
import { toast } from 'sonner'
export default function UpdateDialog() {
const [isOpen, setIsOpen] = useState(false)
const [updateData, setUpdateData] = useState<UpdateProgress | null>(null)
const [isUpdating, setIsUpdating] = useState(false)
/* 立即下载 */
const onDownload = () => {
setIsUpdating(true)
window.electron.ipcRenderer.send(SendEnum.DOWNLOAD_UPDATE)
}
/* 下次再说 */
const onNextTime = () => {
setIsOpen(false)
}
const onClose = useCallback(() => {
setIsOpen(false)
}, [])
useEffect(() => {
// 监听检查更新的结果
window.electron.ipcRenderer.on(SendEnum.CHECK_UPDATE_RESULT, (_event, result) => {
const { isUpdateAvailable, versionInfo } = result
if (isUpdateAvailable) {
setUpdateData(versionInfo)
setIsOpen(true)
}
})
// 监听更新失败
window.electron.ipcRenderer.on(SendEnum.DOWNLOAD_FAIL, (_event) => {
setIsUpdating(false)
setIsOpen(false)
})
return () => {
window.electron.ipcRenderer.removeAllListeners(SendEnum.CHECK_UPDATE_RESULT)
}
}, [])
useEffect(() => {
if (!isUpdating) {
return
}
// 监听下载进度
window.electron.ipcRenderer.on(SendEnum.DOWNLOAD_PROGRESS, (_event, progressInfo) => {
setUpdateData(progressInfo)
if (progressInfo.percent === 100) {
setIsUpdating(false)
setIsOpen(false)
}
})
return () => {
window.electron.ipcRenderer.removeAllListeners(SendEnum.DOWNLOAD_PROGRESS)
}
}, [isUpdating])
return (
<Dialog open={isOpen}>
<DialogContent className="sm:max-w-md bg-slate-50" showCloseButton={false}>
<DialogHeader>
<DialogTitle className="flex items-center justify-between text-lg font-semibold text-slate-800">
更新提示
<Button variant="ghost" onClick={onClose} disabled={isUpdating}>
<X className="w-4 h-4" />
</Button>
</DialogTitle>
<DialogDescription asChild>
<div>
<p className="pt-4 text-base text-slate-700">
{isUpdating ? '正在下载更新,请稍后...' : '发现新版本,是否需要立即下载?'}
</p>
{isUpdating && (
<div className="flex items-center mt-2 ">
<Progress value={updateData?.percent} />
<span className="text-sm text-slate-700 ml-2">
{Math.floor(updateData?.percent || 0)}%
</span>
</div>
)}
</div>
</DialogDescription>
</DialogHeader>
<DialogFooter className="gap-2">
<Button variant="outline" onClick={onNextTime} disabled={isUpdating}>
下次再说
</Button>
<Button onClick={onDownload} disabled={isUpdating}>
{isUpdating ? '正在下载' : '立即下载'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
- 配置服务器
nginx
没有配置服务器nginx
的话,将无法访问到文件,也就无法自动更新
conf
server {
# 监听 HTTPS 端口
listen 80;
server_name hewkq.top;
# Electron 自动更新的目录
location /auto-updates {
# 指定存放更新文件的本地目录
root /var/www;
# 确保 Nginx 能够正确处理 `latest.yml` 和其他文件
try_files $uri $uri/ =404;
}
}
打包更新
自动更新的实现基本就是这样,需要发版
的时候,只需要更改package.json
里面的版本号,然后打包。将打包后的xxx.exe
、xxx.blockmap
和latest.yml
放到服务器中electron
访问的更新目录中就行了。

有一个点需要注意,为了让 electron-updater
的增量更新
功能正常工作,服务器上不能删除 旧版本的 .blockmap
文件。因为electron-updater
需要同时访问两个版本的.blockmap
文件。 它将这两个文件进行对比,才能计算出增量更新
所需的数据块。
当然你删除了.blockmap
文件也没有事,electron-updater
会自动回退到完整更新
去下载.exe
文件。

结语
本人使用Windows
系统,其他系统是否需要别的配置还是调整不清楚。
源码地址:github.com/lzt-T/scree...
谢谢观看!!