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 相关文章推荐
JQUERY THICKBOX弹出层插件
Aug 30 Javascript
实现png图片和png背景透明(支持多浏览器)的方法
Sep 08 Javascript
动态的绑定事件addEventListener方法的使用
Jan 24 Javascript
jquery-syntax动态语法着色示例代码
May 14 Javascript
最短的IE判断var ie=!-[1,]分析
May 28 Javascript
快速掌握jQuery插件WebUploader文件上传
Nov 07 Javascript
AngularJS 异步解决实现方法
Jun 12 Javascript
微信小程序 新建登录页并实现tabBar隐藏
Jun 13 Javascript
jQuery实现常见的隐藏与展示列表效果示例
Jun 04 jQuery
如何给element添加一个抽屉组件的方法步骤
Jul 14 Javascript
JS实现移动端在线签协议功能
Aug 22 Javascript
swiper4实现移动端导航切换
Oct 16 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
利用discuz实现PHP大文件上传应用实例代码
2008/11/14 PHP
php站内搜索并高亮显示关键字的实现代码
2011/12/29 PHP
JS 页面自动加载函数(兼容多浏览器)
2009/05/18 Javascript
JSQL SQLProxy 的 php 版本代码
2010/05/05 Javascript
解决js正则匹配换行问题实现代码
2012/12/10 Javascript
node.js中的emitter.on方法使用说明
2014/12/10 Javascript
javascript框架设计之类工厂
2015/06/23 Javascript
jQuery中的基本选择器用法学习教程
2016/04/14 Javascript
详解JavaScript RegExp对象
2017/02/04 Javascript
你真的了解BOM中的history对象吗
2017/02/13 Javascript
JavaScript和JQuery获取DIV值的方法示例
2017/03/07 Javascript
微信小程序模版渲染详解
2018/01/26 Javascript
vue iview组件表格 render函数的使用方法详解
2018/03/15 Javascript
详解vue的diff算法原理
2018/05/20 Javascript
JS逻辑运算符短路操作实例分析
2018/07/09 Javascript
Vue.js子组件向父组件通信的方法实例代码详解
2018/12/10 Javascript
js实现随机数小游戏
2019/06/28 Javascript
解决nuxt页面中mounted、created、watch执行两遍的问题
2020/11/05 Javascript
[01:00]DOTA2 store: Collection of Artisan's Wonders
2015/08/12 DOTA
python中根据字符串调用函数的实现方法
2016/06/12 Python
python3 shelve模块的详解
2017/07/08 Python
Python数据结构之双向链表的定义与使用方法示例
2018/01/16 Python
为什么str(float)在Python 3中比Python 2返回更多的数字
2018/10/16 Python
Python实现判断一个整数是否为回文数算法示例
2019/03/02 Python
人工神经网络算法知识点总结
2019/06/11 Python
kafka监控获取指定topic的消息总量示例
2019/12/23 Python
matplotlib更改窗口图标的方法示例
2021/02/03 Python
小学生寒假家长评语
2014/04/16 职场文书
高中英语演讲稿范文
2014/04/24 职场文书
大学生党员个人剖析材料
2014/10/08 职场文书
2014年城管工作总结
2014/11/20 职场文书
乡镇法制宣传日活动总结
2015/05/05 职场文书
交通事故代理词范文
2015/05/23 职场文书
长征观后感
2015/06/09 职场文书
css布局巧妙技巧之css三角示例的运用
2022/03/16 HTML / CSS
Nginx配置根据url参数重定向
2022/04/11 Servers