Electron autoUpdater实现Windows安装包自动更新的方法


Posted in Javascript onDecember 24, 2018

前言

Electron帮助我们突破浏览器的界限,通过Electron构建的桌面应用拥有各种浏览器应用梦寐以求的能力。

Electron提供的autoUpdater还可以帮助我们实现桌面应用的自动更新。

文件结构

首先,我们已经有了一个基于Electron做的应用,项目中有两个package.json。这样做的一个原因是将devDependencies和dependencies分开了,另外就是不需要在打包的时候再去指定哪些依赖不需要一起打到安装包里面去了(通过ignore参数)。

目录结构类似于这样:

myapp
  -node_modules
  -package.json
  -app
    -js
    -css
    -index.html
    -main.js
    -package.json

外面的package.json内容类似于:

{
 "name": "myapp",
 "main": "app/main.js",
 "scripts": {
  "start": "electron ."
 },
 "devDependencies": {
  "electron-prebuilt": "^1.2.7"
 }
}

里面的package.json的内容类似于:

{
  "name": "myapp",
  "version": "1.0",
  "main": "main.js",
  "description": "my app",
  "scripts": {
    "start": "electron ."
  },
  "dependencies": {}
}

注意里面的package.json中的name,version,description是必填的,接下来打包会用到。

electron-squirrel-startup

为了使最后的安装包能够实现自动更新,我们需要对现有的应用做一些改动,使它可以处理一些启动或者安装时的事件。
我们可以在main.js里面加入一些处理的代码或者方便起见,我们可以直接使用electron-squirrel-startup。

先安装:

npm install electron-squirrel-startup --save

因为需要在main.js里面用到,我们需要将其安装在app里面。

在main.js里面使用它,第一行加入如下代码即可:

if (require('electron-squirrel-startup')) return;

有兴趣的童鞋可以一起跟我去看看electron-squirrel-startup做了什么事情,急着打包的童鞋可以直接忽略这一段:

在myapp/app/node_modules/electron-squirrel-startup下面有一个index.js:

var path = require('path');
var spawn = require('child_process').spawn;
var debug = require('debug')('electron-squirrel-startup');
var app = require('electron').app;
  
var run = function(args, done) {
 var updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe');
 debug('Spawning `%s` with args `%s`', updateExe, args);
 spawn(updateExe, args, {
  detached: true
 }).on('close', done);
};
  
var check = function() {
 if (process.platform === 'win32') {
  var cmd = process.argv[1];
  debug('processing squirrel command `%s`', cmd);
  var target = path.basename(process.execPath);
  
  if (cmd === '--squirrel-install' || cmd === '--squirrel-updated') {
   run(['--createShortcut=' + target + ''], app.quit);
   return true;
  }
  if (cmd === '--squirrel-uninstall') {
   run(['--removeShortcut=' + target + ''], app.quit);
   return true;
  }
  if (cmd === '--squirrel-obsolete') {
   app.quit();
   return true;
  }
 }
 return false;
};
  
module.exports = check();

它的代码只有短短几十行,做的事情也很简单,注意返回值为true的那几行,基本上来说就是安装时,更新完成时,卸载时 main.js都会被调用,我们需要根据不同的情况做不同的事情,完成这些事情后不要启动应用(会出错),直接退出就好。

正常启动前我们需要去检测是否有新的安装包,之后下载新包,重新安装,重启应用,为了做到这一点,我们需要在main.js里面加入如下代码:

app.on('ready', () => {
  //安装后第一次启动不去检测更新,go做的事情就是启动我们的应用
  if (process.argv[1] == '--squirrel-firstrun') {
    go();
    return;
  }
  /* 设置自动更新的feedURL,本地测试可以设置为类似于http://127.0.0.1:8080/latest
   * 在latest文件夹下放着三个我们的安装文件(Setup.exe,RELEASES,myapp-1.0-full.nupkg),下面会讲到
   * /
  autoUpdater.setFeedURL(feedURL);
  autoUpdater.on('update-downloaded', function() {
    // 下载完成,更新前端显示
    autoUpdater.quitAndInstall();
  });
  try {
    // 不是安装应用的情况下启动下回出错,此时直接正常启动应用
    autoUpdater.checkForUpdates();
  } catch (ex) {
    go();
    return;
  }
    
  // createWindow是我们自己定义的方法,用来创建窗口,此处用来创建检测更新的窗口
  createWindow({
    name: 'updateWindow',
    url: 'check-for-updates.html',
    title: "checkForUpdates",
    icon: icon,
    frame: false,
    width: 1306,
    height: 750
  });
});

自动更新后台搭建

var express = require('express');
var app = express();
  
app.use(express.static('releases'));
  
var server = app.listen(8080, function() {

  var host = server.address().address
  var port = server.address().port
  
  console.log("应用实例,访问地址为 http://%s:%s", host, port);
});

文件结构如下:

autoupdate-backend
  -package.json
  -index.js
  -node_modules
  -releases
    -latest

此时latest文件夹里面还是空的,之后我们开始打包,将打包出来的三个文件放在此处即可。

electron-packager

在myapp下安装:

npm install electron-packager --save-dev
npm install electron-packager -g

两种安装方式对应两种使用方式,第一种在脚本中使用,第二种的命令行使用。

脚本中使用,小姐姐在这里借助了gulp,所以需要安装gulp:

npm install gulp --save-dev 
npm install gulp -g

新建GulpFile.js,定义一个task:

var gulp = require('gulp');

var platform = 'win32';
var arch = 'ia32';
var appPath = 'app';
var packageOutPath = 'production/package';
var iconPath = 'app/favicon.ico';

gulp.task('generate-package', () => {
  generatePackage();
});

function generatePackage(callback) {
  var packager = require('electron-packager')
  packager({
    dir: appPath,
    platform: platform,
    arch: arch,
    out: packageOutPath,
    icon: iconPath,
    /*桌面快捷方式名称以及开始菜单文件夹名称*/
    'version-string': {
      CompanyName: 'MyCompany Inc.',
      ProductName: 'myapp'
    }
  }, function (err) {
    if (err) {
      console.log(err);
    } else {
      callback && callback();
    }
  });
}

需要打包的时候,打开命令行:

gulp generate-package

这样做的好处是调用方便,当然我们也可以直接通过命令行调用electron-packager,前提是我们全局安装了它或者将其安装目录添加到了环境变量中:

更多参数一一加上即可。

贴上官方文档链接:

github链接:https://github.com/electron-userland/electron-packager

下面两个链接在上面的文档里面都能找到,但是个人感觉比较常用,还是贴出来:

参数使用:https://github.com/electron-userland/electron-packager/blob/master/usage.txt

脚本使用:https://github.com/electron-userland/electron-packager/blob/master/docs/api.md

打包

myapp下安装electron-winstaller:

npm install electron-winstaller --save-dev

还是在gulp里面添加一个task,连同package的代码一起贴上:

var gulp = require('gulp');

var platform = 'win32';
var arch = 'ia32';
var appPath = 'app';
var outName = 'myapp-win32-' + arch;
var packageOutPath = 'production/package';
var installerOutPath = 'production/installer';
var packagePath = `${packageOutPath}/${outName}`;
var installerPath = `${installerOutPath}/${outName}`;
var iconPath = 'app/favicon.ico';
var gifPath = 'loading.gif';

gulp.task('generate-package', () => {
  generatePackage();
});
gulp.task('generate-installer', () => {
  isDirExist(packagePath, (exist) => {
    if (exist) {
      generateInstaller();
    } else {
      generatePackage(() => {
        generateInstaller();
      });
    }
  });
});

function isDirExist(path, callback) {
  fs.readdir(path, (err) => {
    callback && callback(!err);
  });
}

function generatePackage(callback) {
  var packager = require('electron-packager')
  packager({
    dir: appPath,
    platform: platform,
    arch: arch,
    out: packageOutPath,
    icon: iconPath,
    /*桌面快捷方式名称以及开始菜单文件夹名称*/
    'version-string': {
      CompanyName: 'MyCompany Inc.',
      ProductName: 'myapp'
    }
  }, function (err) {
    if (err) {
      console.log(err);
    } else {
      callback && callback();
    }
  });
}

function generateInstaller() {
  var electronInstaller = require('electron-winstaller');
  electronInstaller.createWindowsInstaller({
    appDirectory: packagePath,
    outputDirectory: installerPath,
    loadingGif: gifPath,
    authors: 'ganyouyin',
    exe: 'myapp.exe',
    title: 'My APP',
    iconUrl: `${__dirname}/${iconPath}`,
    setupIcon: iconPath,
    setupExe: 'Setup.exe',
    noMsi: true
  }).then(() => console.log("It worked!"), (e) => console.log(`No dice: ${e.message}`));
}

之后执行任务:

gulp generate-installer

第一次会非常慢,但是执行完成后我们的安装包就出来了。

此时我们的文件结构是:

myapp
  -GulpFile.js
  -package.json
  -node_modules
  -app
  -production
    -package
      -myapp-win32-ia32
        - 各种文件,包含一个myapp.exe,双击可以直接运行
    -installer
      -myapp-win32-ia32
        -Setup.exe
        -RELEASES
        -myapp-1.0-full.nupkg

有了三个文件,将他们粘到之前的autoupdate-backend/releases/latest文件夹下面。

测试

  • 启动我们的自动更新后台;
  • 将myapp/app下的package.json里面的version改为1.1,再次打包;
  • 将之前的autoupdate-backend中的latest文件夹重命名为1.0;
  • 新建文件夹latest,将新打包产生的三个文件粘进去;
  • 双击1.0里面的Setup.exe安装应用;
  • 关闭应用,双击桌面上的快捷方式myapp.exe再次打开应用;

不出意外此时就会去进行自动更新的操作,结束后自动重启,再次打开时已经是1.1的应用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
自适应图片大小的弹出窗口
Jul 27 Javascript
javascript 清除输入框中的数据
Apr 13 Javascript
给js文件传参数(详解)
Jul 13 Javascript
不依赖Flash和任何JS库实现文本复制与剪切附源码下载
Oct 09 Javascript
jQuery easyui的validatebox校验规则扩展及easyui校验框validatebox用法
Jan 18 Javascript
javascript实现简单的on事件绑定
Aug 23 Javascript
轻松学习Javascript闭包
Mar 01 Javascript
js 显示日期时间的实例(时间过一秒加1)
Oct 25 Javascript
微信小程序 功能函数小结(手机号验证*、密码验证*、获取验证码*)
Dec 08 Javascript
React BootStrap用户体验框架快速上手
Mar 06 Javascript
vue如何通过id从列表页跳转到对应的详情页
May 01 Javascript
vuejs实现标签选项卡动态更改css样式的方法
May 31 Javascript
Windows下支持自动更新的Electron应用脚手架的方法
Dec 24 #Javascript
如何使用electron-builder及electron-updater给项目配置自动更新
Dec 24 #Javascript
Vue.js组件高级特性实例详解
Dec 24 #Javascript
JavaScript模板引擎原理与用法详解
Dec 24 #Javascript
jQuery实现的简单日历组件定义与用法示例
Dec 24 #jQuery
原生js实现Flappy Bird小游戏
Dec 24 #Javascript
node错误处理与日志记录的实现
Dec 24 #Javascript
You might like
用php的ob_start来生成静态页面的方法分析
2011/03/09 PHP
用PHP实现弹出消息提示框的两种方法
2013/12/17 PHP
Yii2中SqlDataProvider用法示例
2016/09/22 PHP
thinkphp5.1框架中容器(Container)和门面(Facade)的实现方法分析
2019/08/05 PHP
IE FF OPERA都可用的弹出层实现代码
2009/09/29 Javascript
jQuery通过改变input的type属性实现密码显示隐藏切换功能
2017/02/08 Javascript
把JavaScript代码改成ES6语法不完全指南(分享)
2017/09/10 Javascript
javascript实现数字配对游戏的实例讲解
2017/12/14 Javascript
利用weixin-java-miniapp生成小程序码并直接返回图片文件流的方法
2019/03/29 Javascript
TypeScript中的方法重载详解
2019/04/12 Javascript
vue中使用props传值的方法
2019/05/08 Javascript
vue表单中遍历表单操作按钮的显示隐藏示例
2019/10/30 Javascript
[15:09]DOTA2国际邀请赛采访专栏:Loda
2013/08/06 DOTA
Django中URL视图函数的一些高级概念介绍
2015/07/20 Python
Python随机生成数据后插入到PostgreSQL
2016/07/28 Python
基于Python pip用国内镜像下载的方法
2018/06/12 Python
Python实现全排列的打印
2018/08/18 Python
Python实现线性判别分析(LDA)的MATLAB方式
2019/12/09 Python
如何使用repr调试python程序
2020/02/28 Python
Python学习笔记之装饰器
2020/08/06 Python
Python: glob匹配文件的操作
2020/12/11 Python
matplotlib部件之矩形选区(RectangleSelector)的实现
2021/02/01 Python
非常漂亮的CSS3百叶窗焦点图动画
2016/02/24 HTML / CSS
纯css实现照片墙3D效果的示例代码
2017/11/13 HTML / CSS
韩国江南富人区高端时尚百货商场:Galleria(格乐丽雅)
2018/03/27 全球购物
Carrs Silver官网:英国著名的银器品牌
2020/08/29 全球购物
简述synchronized和java.util.concurrent.locks.Lock的异同
2014/12/08 面试题
C#公司笔试题
2014/03/28 面试题
一套中级Java程序员笔试题
2015/01/14 面试题
会计专业应届生自荐信
2014/02/07 职场文书
社区消防工作实施方案
2014/03/21 职场文书
企业安全生产目标责任书
2014/07/23 职场文书
2014年政风行风工作总结
2014/11/22 职场文书
地道战观后感2000字
2015/06/04 职场文书
MySQL中一条update语句是如何执行的
2022/03/16 MySQL
SQL Server中的逻辑函数介绍
2022/05/25 SQL Server