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 相关文章推荐
JS子父窗口互相操作取值赋值的方法介绍
May 11 Javascript
jquery事件的ready()方法使用详解
Nov 11 Javascript
javascript设计简单的秒表计时器
Sep 05 Javascript
js模块加载方式浅析
Aug 12 Javascript
vue使用$emit时,父组件无法监听到子组件的事件实例
Feb 26 Javascript
jQuery实现table表格checkbox全选的方法分析
Jul 04 jQuery
jQuery实现根据身份证号获取生日、年龄、性别等信息的方法
Jan 09 jQuery
小程序实现列表多个批量倒计时
Jan 29 Javascript
vue项目中监听手机物理返回键的实现
Jan 18 Javascript
Node.js API详解之 assert模块用法实例分析
May 26 Javascript
json.stringify()与json.parse()的区别以及用处
Jan 25 Javascript
js基于canvas实现时钟组件
Feb 07 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--用万网的接口实现域名查询功能
2012/12/13 PHP
探讨fckeditor在Php中的配置详解
2013/06/08 PHP
thinkPHP5.0框架验证码调用及点击图片刷新简单实现方法
2018/09/07 PHP
比较简单的一个符合web标准的JS调用flash方法
2007/11/29 Javascript
js模拟弹出效果代码修正版
2008/08/07 Javascript
Extjs学习笔记之五 一个小细节renderTo和applyTo的区别
2010/01/07 Javascript
jQuery+PHP实现动态数字展示特效
2015/03/14 Javascript
整理AngularJS中的一些常用指令
2015/06/16 Javascript
浅析js中substring和substr的方法
2015/11/09 Javascript
Jquery和angularjs获取check框选中的值的方法汇总
2016/01/17 Javascript
简单实现js选项卡切换效果
2016/02/03 Javascript
轻松实现jquery选项卡切换效果
2016/10/10 Javascript
微信页面倒计时代码(解决safari不兼容date的问题)
2016/12/13 Javascript
Vue.js实现的表格增加删除demo示例
2018/05/22 Javascript
node.js部署之启动后台运行forever的方法
2018/05/23 Javascript
小程序显示弹窗时禁止下层的内容滚动实现方法
2019/03/20 Javascript
深入了解响应式React Native Echarts组件
2019/05/29 Javascript
使用Vue CLI创建typescript项目的方法
2019/08/09 Javascript
JavaScript实现字符串与HTML格式相互转换
2020/03/17 Javascript
[49:12]完美世界DOTA2联赛PWL S2 Magma vs GXR 第二场 11.29
2020/12/02 DOTA
python处理xml文件的方法小结
2017/05/02 Python
Python中的字符串切片(截取字符串)的详解
2019/05/15 Python
对django的User模型和四种扩展/重写方法小结
2019/08/17 Python
wxPython实现列表增删改查功能
2019/11/19 Python
PyTorch安装与基本使用详解
2020/08/31 Python
Python实现哲学家就餐问题实例代码
2020/11/09 Python
CSS3中线性颜色渐变的一些实现方法
2015/07/14 HTML / CSS
Html5移动端网页端适配(js+rem)
2021/02/03 HTML / CSS
如何用Lucene索引数据库
2016/02/23 面试题
企事业单位求职者的自我评价
2013/12/28 职场文书
大学毕业生自荐书怎么写?
2014/01/06 职场文书
维稳工作情况汇报
2014/10/27 职场文书
迟到检讨书
2015/01/26 职场文书
致青春观后感
2015/06/09 职场文书
培训后的感想
2015/08/07 职场文书
Pytorch数据读取之Dataset和DataLoader知识总结
2021/05/23 Python