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 相关文章推荐
Javascript对象属性方法汇总
Nov 21 Javascript
jQuery动画效果实现图片无缝连续滚动
Jan 12 Javascript
Bootstrap布局组件应用实例讲解
Feb 17 Javascript
JavaScript开发Chrome浏览器扩展程序UI的教程
May 16 Javascript
jQuery stop()用法实例详解
Jul 28 Javascript
基于jQuery ligerUI实现分页样式
Sep 18 Javascript
详解JS构造函数中this和return
Sep 16 Javascript
angularJs 表格添加删除修改查询方法
Feb 27 Javascript
vue项目中api接口管理总结
Apr 20 Javascript
Vue脚手架的简单使用实例
Jul 10 Javascript
Vue路由history模式解决404问题的几种方法
Sep 29 Javascript
如何在vue中使用jointjs过程解析
May 29 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制作静态网站的模板框架(一)
2006/10/09 PHP
PHP中配置IIS7实现基本身份验证的方法
2015/09/24 PHP
在PHP中使用FastCGI解析漏洞及修复方案
2015/11/10 PHP
php+ajax无刷新上传图片实例代码
2015/11/17 PHP
laravel通过a标签从视图向控制器实现传值
2019/10/15 PHP
用javascript实现读取txt文档的脚本
2007/07/20 Javascript
关于hashchangebroker和statehashable的补充文档
2011/08/08 Javascript
js和jquery对dom节点的操作(创建/追加)
2013/04/21 Javascript
解析JavaScript中的不可见数据类型
2013/12/02 Javascript
几句话带你理解JS中的this、闭包、原型链
2016/09/26 Javascript
微信小程序 教程之wxapp视图容器 scroll-view
2016/10/19 Javascript
BootStrap 弹出层代码
2017/02/09 Javascript
用户管理的设计_jquery的ajax实现二级联动效果
2017/07/13 jQuery
详解使用nvm管理多版本node的方法
2017/08/30 Javascript
layui 表格操作列按钮动态显示的实现方法
2019/09/06 Javascript
微信小程序上传帖子的实例代码(含有文字图片的微信验证)
2020/07/11 Javascript
简单了解Python中的几种函数
2017/11/03 Python
python机器学习之KNN分类算法
2018/08/29 Python
Python 创建新文件时避免覆盖已有的同名文件的解决方法
2018/11/16 Python
Django管理员账号和密码忘记的完美解决方法
2018/12/06 Python
对python过滤器和lambda函数的用法详解
2019/01/21 Python
Django实现学生管理系统
2019/02/26 Python
详解用python写网络爬虫-爬取新浪微博评论
2019/05/10 Python
Python解决pip install时出现的Could not fetch URL问题
2019/08/01 Python
Pycharm及python安装详细步骤及PyCharm配置整理(推荐)
2020/07/31 Python
解决Python paramiko 模块远程执行ssh 命令 nohup 不生效的问题
2020/07/14 Python
CSS3中使用RGBA设置透明度的示例
2015/08/04 HTML / CSS
C#如何判断当前用户是否输入某个域
2015/12/07 面试题
销售员自我评价怎么写
2013/09/19 职场文书
医药专业推荐信
2013/11/15 职场文书
自荐信怎么写呢?
2013/12/09 职场文书
安全标语口号
2014/06/09 职场文书
老乡聚会通知
2015/04/23 职场文书
2015民办小学年度工作总结
2015/05/26 职场文书
2015年教师党员个人总结
2015/11/24 职场文书
十大最强格斗系宝可梦,超梦X仅排第十,第二最重格斗礼仪
2022/03/18 日漫