【译】Electron 自动更新的完整教程(Windows 和 OSX)

原文连接:Auto-updating apps for Windows and OSX using Electron: The complete guidehtml

2017.11.06 更新:electron-builder 提供了 electron-updater 模块,具体请查阅:《Quick and painless automatic updates in Electron》node

因为我以前也调研了 Electron 的自动更新方面的知识,因此我会在保留原文全部信息的前提下,加入了一些备注(如做者的一些错误信息和补充了我我的的一些认识)。git


经过 Electron,你可能只需一眨眼的时间就完成了一个不错的桌面应用,并分发到用户手中。当你以为本身能像一个侥幸的坏蛋同样轻松时,你可能会意识到你遗漏了一个重要的点:用户如何获取下一个版本呢?甚至该新版本新增了一些优秀的功能。固然,他们能删除后再从新安装该应用,但这难道不蹩脚吗?github

快速浏览 Electron 文档 时,你会注意到该文档中含有 auto-updater 模块,它仅仅是另外一个框架——Squirrel 的接口。Squirrel 会在背后检测(或你主动触发)是否有新版本、下载新版本,并在你启动或重启应用时自动更新应用。mongodb

但悲伤的是:实际实现起来并非文档上写的这么简单。由于自动更新在 OSX 和 Windows 上的工做方式并不相同(目前并不支持 Linux),而且这二者的文档是分散在多个库(repository)中。我已经花费了大量的时间把该功能实现了。因此我以为将我所学习到的知识总结成一篇教程是值得的,但愿它能节省你的时间。express

虽然这里所讲的一切应该均能在 Windows 和 OSX 上运行,但为了减小异议,我先声明我是在 Mac OSX 10.11 上执行的操做,除了为 Windows 系统构建安装包(在虚拟机上)。npm

如对该篇教程有任何改善或更新的建议,可在 twitter 联系我!编程

应用打包

在实现自动更新以前,有一个重要的步骤 —— 打包。我假设大多数人已经知道如何经过 electron-packager 实现该操做,但有两件事是时常被忽略的。json

{
  "name": "MyApp",
  "main": "app.js",
  "private": true,
  "productName": "MyApp",
  "version": "1.0.0",
  "author": "My Company Ltd",
  "description": "MyApp",
  "devDependencies": {
    "electron-installer-squirrel-windows": "^1.3.0",
    "electron-packager": "^5.1.1",
    "electron-prebuilt": "0.36.7"
  },
  "scripts": {
    "start": "NODE_ENV=development ./node_modules/.bin/electron .",
    "pack:osx": "./node_modules/.bin/electron-packager . $npm_package_productName --app-version=$npm_package_version --version=0.36.7 --out=builds --ignore='^/builds$' --platform=darwin --arch=x64 --sign='Developer ID Application: My Company Ltd (ABCDEFGH10)' --icon=icon.icns --overwrite",
    "pack:win": "./node_modules/.bin/electron-packager . $npm_package_productName --app-version=$npm_package_version --version=0.36.7 --out=builds --ignore='^/builds$' --platform=win32 --arch=ia32 --version-string.CompanyName='My Company Ltd' --version-string.LegalCopyright='Copyright (C) 2016 My Company Ltd' --version-string.FileDescription=$npm_package_productName --version-string.OriginalFilename='MyApp.exe' --version-string.InternalName=$npm_package_productName --version-string.ProductName=$npm_package_productName --version-string.ProductVersion=$npm_package_version --asar=true --icon=logo.ico --overwrite"
  }
}

package.jsonc#

注意 package.json 的额外字段 —— productNameauthordescription,虽然这几个字段并非打包必备的,但它们会在 Windows 的 Squirrel 安装包中使用到。

为应用执行代码签名(Code-signing)的这部操做并非自动更新的必备步骤(译者注:也许做者当时的 Electron 版本的自动更新模块没必要进行代码签名,但当前版本是必需要进行这部操做的,官方文档中写道:Your application must be signed for automatic updates on macOS. This is a requirement of Squirrel.Mac. ),但这是很是可取的操做。对于 OSX,你须要一个 Apple 的开发者认证,而后在 script 字段的 pack:osx 替换如下参数便可:

--sign='Developer ID Application: My Company Ltd (ABCDEFGH10)'

在 OSX 中,你能够经过 Keychain Access > My Certificates 查看(应用程序 -> 钥匙串 > 个人证书,若是有的话)。

我并无在 Windows 上执行代码签名这项操做,但你能够看看该主题相关的优秀教程。

对于 Windows,推荐为 electron-packager 传递 version-string 的全部可选参数,如 company name、product name 等。由于一旦咱们生成 Windows 的 Squirrel 安装包,该应用就能在 Windows 的『开始』菜单显示正确的元信息(metadata),而不是 Atom 的默认信息。

Atom Shell is now called Electron。

因此,让咱们开始吧!

OSX

在 OSX 中,自动更新是经过 Squirrel.Mac 处理的,它是内置于 Electron 中。这意味着你只需打包你的应用,而后照常运行就好!

恩,其实不彻底是。

Squirrel.Mac 的工做方式是经过访问一个你所提供的 API 『路径』(endpoint),判断是否有新版本。若是没有新版本,那么该路径应该返回 HTTP 204。若是有新版本,则它会期待接收一个 HTTP 200、且是 JSON 格式 的响应,其中包含一个 能获取 .zip 文件的 url

PS:『路径』又称"终点"(endpoint),表示API的具体网址。

{
  "url": "http://mysite.com/path/to/zip/MyApp.zip"
}

在获得该 url 后,Squirrel 会构造一个 application/zip 的请求去访问该 url,下载相应文件,而后触发最终事件(下载完成)让你知道更新包即将安装。对于你来讲,全部事情的处理都是自动化的。

若是你不十分肯定服务器程序应该长什么样,可看看下面的一个超级小型的 Node.js/Express 服务,假定它的目录结构以下:

└── releases
 ├── darwin
 │ ├── 1.0.0
 │ ├── 1.0.2
 │ └── 1.0.3
 └── win32
{
  "name": "squirrel-version-checker",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "PORT=80 node app.js",
    "dev": "./node_modules/.bin/nodemon app.js"
  },
  "dependencies": {
    "express": "^4.9.8",
    "morgan": "^1.3.2"
  },
  "devDependencies": {
    "nodemon": "^1.8.1"
  }
}

基于 Node 的更新服务 package.json

'use strict';
const fs = require('fs');
const express = require('express');
const path = require('path');
const app = express();

app.use(require('morgan')('dev'));

app.use('/updates/releases', express.static(path.join(__dirname, 'releases')));

app.get('/updates/latest', (req, res) => {
  const latest = getLatestRelease();
  const clientVersion = req.query.v;

  if (clientVersion === latest) {
    res.status(204).end();
  } else {
    res.json({
      url: `${getBaseUrl()}/releases/darwin/${latest}/MyApp.zip`
    });
  }
});

let getLatestRelease = () => {
  const dir = `${__dirname}/releases/darwin`;

  const versionsDesc = fs.readdirSync(dir).filter((file) => {
    const filePath = path.join(dir, file);
    return fs.statSync(filePath).isDirectory();
  }).reverse();

  return versionsDesc[0];
}

let getBaseUrl = () => {
  if (process.env.NODE_ENV === 'development') {
    return 'http://localhost:3000';
  } else {
    return 'http://download.mydomain.com'
  }
}

app.listen(process.env.PORT, () => {
  console.log(`Express server listening on port ${process.env.PORT}`);
});

一个简单地、用于测试 Squirrel.Mac 自动更新的 Express 服务器

这将会从本地的文件系统进行分发文件,但这不是理想的处理方式。个人建议是:将这些文件放置在 Amazon S3。

Amazon S3:Amazon Simple Storage Service

而后你能够在开发环境下,经过 Electron 访问该路径:

http://localhost:3000/updates/latest?v=1.0.1

?v=1.0.1 是你当前应用的版本。

如今你已经拥有了服务器程序和路径了,那么在应用中处理更新操做就十分简单了。

在 Electron 的主进程文件中,引入 auto-updater 模块,而后获取当前系统和应用的版本:

const autoUpdater = require('auto-updater');
const appVersion = require('./package.json').version;
const os = require('os').platform();

而后配置路径,该路径会因系统(Windows 和 Mac)不一样而有所差别(至于缘由,会在 Windows 章节看到):

var updateFeed = 'http://localhost:3000/updates/latest';

if (process.env.NODE_ENV !== 'development') {
  updateFeed = os === 'darwin' ?
    'https://mysite.com/updates/latest' :
    'http://download.mysite.com/releases/win32';
}

autoUpdater.setFeedURL(updateFeed + '?v=' + appVersion);

告诉 Electron 到哪里检测新版本

autoUpdater 模块提供了一些事件,你可经过渲染进程触发它们(译者注:经过 IPC 通信模块),想获取更多信息,可查阅 auto-Updater 文档页面 。相关交互的实现决定取决于你如何处理这些事件(如发生错误等),并通知用户。但你最后一步应该作的是:

autoUpdater.quitAndInstall();

将上述语句放在主进程文件后,应用会以新本版的形式重启。赞!

Windows

如你想象的那样,在 Windows 上实现自动更新是经过 Squirrel.Windows。但它的处理方式与 OSX 彻底不一样。

与 Squirrel.Mac 不一样的点在于:Squirrel.Windows 并不须要一个用于检测新版本的 API 路径,它须要的是一个文件服务器,因此你能够简单地将文件拖拽到 Amazon S3 bucket 上。另外,该 Squirrel 更新器并不内置于 Electron,它是一个第三方依赖。这意味着你须要为你所打包的 Windows 应用生成一个安装器,这样它才会包含 Squirrel 更新器。

Amazon S3 bucket:S3 的数据存储结构很是简单,就是一个扁平化的两层结构:一层是存储桶(Bucket,又称存储段),另外一层是存储对象(Object,又称数据元)。具体信息可查看 《亚马逊S3服务介绍》

好消息是:Windows 的安装包和更新器的运行过程顺滑的。由于当你启动 Setup.exe 时,你会发现安装和启动该应用是迅速的。没有无聊的安装向导和一直按“下一步”、最后按“完成”的步骤,否则与大多数 Windows 安装器一模一样。固然,它也能生成 delta packages,这让你在执行更新时,没必要下载整个应用,这真的是一流啊。

译者注:我经过 electron-builder 生成的 Windows 安装包与咱们常见的软件安装界面不太同样,他没有安装向导和点击“下一步”,只有一个安装时的 gif 动画(默认的 gif 动画以下图),所以也就没有让用户选择安装路径等权利。也许做者习惯了 Mac 的安装方式(即下面第二幅图),因此会以为 Windows 的安装包比较繁琐。

Windows 安装时默认的 gif 动画
Windows 安装时 默认显示的 gif 动画

Mac 常见的安装模式
Mac 常见的安装模式,将“左侧的应用图标”拖拽到“右侧的 Applications”便可

若是你想为 Windows 应用生成常见的、须要点击“下一步”的(即用户可自定义的)安装包,能够经过 NSIS 程序,具体可看这篇教程《[教學]只要10分鐘學會使用 NSIS 包裝您的桌面軟體–安裝程式打包。彻底免費。》。固然,前提仍是经过 electron-packager 打包程序。

NSIS(Nullsoft Scriptable Install System)是一个开源的 Windows 系统下安装程序制做程序。它提供了安装、卸载、系统设置、文件解压缩等功能。这如其名字所指出的那样,NSIS 是经过它的脚本语言来描述安装程序的行为和逻辑的。NSIS 的脚本语言和一般的编程语言有相似的结构和语法,但它是为安装程序这类应用所设计的。

坏消息是(至少对于 Mac 用户):我不能在 OSX 上正确地生成安装包,因此我建议你下载一个 Windows 虚拟机(如 VirtualBoxparallels),并安装 Node.js。

译者注:我经过 electron-builder,可在 MacOS 中直接(即不经过虚拟机)生成 Windows 安装包(即Setup.exe)。具体可 查看这里

假设你已经配置好并设置了正确的更新源,那么在上述 OSX 章节的代码基础上,还须要处理一些 Squirrel.Windows 事件,这些事件与 OSX 上的不一样。你能够查看该 案例。然而,这里提供一个更简单的方式,仅需安装 electron-squirrel-startup npm 模块:

npm install electron-squirrel-startup --save-dev

而后在 Electron 的主进程文件顶部添加如下一行语句:

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

Squirrel.Windows 事件应该被尽早处理,显然,这是要走的路。

最后,为了生成安装包,咱们会使用 Atom 的 grunt-electron-installer。为何它是一个 grunt 插件,而不是一个简单的命令行工具——我不知道,但它就是解决方法。

更新:Electron 团队开发了一个独立的安装器打包工具——electron-winstaller,它拥有与 grunt task 一样的 API

将 Electron-packager 生成的 win32 文件夹打包压缩(zip),而后将其复制到虚拟机上。在该文件夹外(译者注:在解压后),你须要配置 grunt task,该 task 会生成安装包,所以你应该首先安装全部依赖:

npm install -g grunt-cli
npm install grunt grunt-electron-installer --save-dev

假设 Windows 编译后的包放置在一个称为 MyApp-win32-ia32 的文件夹下。下面展现 Gruntfile 的样子:

module.exports = function(grunt) {
  grunt.initConfig({
    'create-windows-installer': {
      ia32: {
        appDirectory: './MyApp-win32-ia32',
        outputDirectory: './dist',
        name: 'MyApp',
        description: 'MyApp',
        authors: 'My Company Ltd',
        exe: 'MyApp.exe'
      }
    }
  });

  grunt.loadNpmTasks('grunt-electron-installer');
};

须要注意的是:若是你想为你的文件和安装包进行代码签名(code-sign)操做,你也须要为该 task 配置提供全部参数。

运行该 grunt task 后,会在 ./dist 目录下产生一堆文件:

grunt create-windows-installer

你预期看到的与下面相似:

└── dist
 ├── MyApp.1.0.0.nupkg
 ├── MyApp-1.0.0-full.nupkg
 ├── RELEASES
 ├── Setup.exe

在下一次发布时,该安装器也会自动生成一个 delta packages。

如今进行最简单的一步 —— 拖拽这些文件到 S3 bucket 进行上传。而后 url 指向该文件夹(包含 RELEASESnupkg 文件)。当应用运行在 Windows 系统上时,它会将该 url 设置到 updateFeed 参数上(由于咱们在先前的 OSX 章节处已实现)。

注意:目前有一个与安装器的 node-rcedit 模块相关的问题,该模块会在你尝试去修改 .exe 文件的一些元信息和替换默认图标(icon)时抛出错误。你能够在 这里查看该 issue。所以,目前若是你想为安装器文件修改 icon 或为其赋予实际数据,你可能不得不手动地经过 ResHacker 进行修改。

结束语

但愿这篇文章能做为一个好的起点,能帮助和服务每个正在为 Electron 应用实现自动更新的朋友们。若是你发现任何我遗漏的点,或有任何改善的建议,欢迎在 twitter 告诉我!另外,请记住 Electron 是一个快速发展的框架,因此要确保你阅读的是你当前版本的文档。Electron 的 API 也是更新十分频繁的。


另外,凹凸实验室基于 Electron 和 Vue 开发了一个 Excel 数据清洗工具 XCEL,并根据此项目总结出了一篇博文《XCel 项目总结 - Electron 与 Vue 的性能优化》。有兴趣的同窗可点击查阅哦。

相关文章
相关标签/搜索