【譯】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 的性能優化》。有興趣的同窗可點擊查閱哦。

相關文章
相關標籤/搜索