用 Electron 打造 Win/Mac 應用,從「代碼」到可下載的「安裝包」,可能比你想得麻煩一點

首發於酷家樂前端博客,做者@摘星(segmentfault @StinsonZhao)javascript

咱們能從不少地方學習到怎麼起一個 Electron 項目,有些還會介紹怎麼打包或構建你的代碼,但距離「真正地發行一款 Electron 產品」這一目標,還有不少工做須要作...css

這是 Electron 系列文章的第二篇,這一篇文章將和你們分享我是怎麼去構建自動化的 Electron 開發構建工程的,說白了,就是怎麼把敲的代碼變成一個用戶能夠下載安裝的包,固然隨着以後應用複雜度的提高和技術再選型,工程體系可能隨時會重構或演進,但至少能夠給你們一些參考,歡迎留言交流。html

這是一篇很長的文章(手冊),寫得比較「唐僧」(知我者能夠說我寫得比較用心),至少會花你一天時間(沒開玩笑),適用場景是「用 Electron 打造 Windows 或 Mac 應用」,是的,你沒看錯,同時會講清楚兼容 Win 和 Mac 兩個系統的流程。文中說起的技術方案絕對不是最佳的(我保證),由於幾乎每隔幾天我都會發現某個環節能夠作得更好,但要明白要唱多大的戲,就先搭多大的臺,夠用就好,不要爲了搭臺耽誤演出時間前端

工程自動化,應該是全部開發者的一種基礎追求,當你搭建建好工程體系,之後你將專一於產品功能的開發,而不會花大量沒必要要的時間去手動構建。做爲前端,可能咱們已經熟悉了 web 應用的構建和部署,可是客戶端程序有其自己的特色,相比較 web 應用最大也是我認爲最根本的一點區別在於「你的應用是被用戶下載過去安裝在用戶本地再跑起來的」。java

這一區別對工程的影響在於,你不可能把你的代碼部署到「用戶的電腦」,你須要構建安裝包,你須要針對不一樣的用戶系統構建不一樣的安裝包,你須要讓你的應用被系統認爲是安全的... node

本文目的

本文須要作的是,把客戶端的打包構建發行這一流程作到像「把大象放進冰箱」同樣的簡單:打開命令行,敲一個 npm run xxx,喝一口咖啡,咪哩嘛哩哄,安裝包出現(一開始打造這個流程時,劇本多是「喝一口咖啡,啪,Error 了,又 Error 了」,take it easy,生活須要慢慢品味 —— 來自一位25歲的仙風道骨白鬍子程序員)。jquery

本文將分如下小節和你們分享「從本地的代碼到雲端可下載的安裝包」這一路的風景,你會有漫步月球般的感受(由於月球全是坑啊,還沒氧氣):linux

  • 第一節是關於目錄結構的討論,合適的目錄結構會是一個良好的開端
  • 第二節是以後幾個小節的概述,闡述了怎麼把這一整個過程分紅多個環節,每一個環節又大體要作什麼事
  • 第三到七節分別詳細描述了「配置」、「打包」、「代碼簽名」、「構建安裝包」、「發行安裝包」這幾個環節要作哪些事,有什麼講究
  • 第八節是簡述一些可進一步研究或優化的點
  • 附:這樣設計的 gulpfile 文件結構

下面一一展開進行闡述,再次強調,文中不少依賴的技術或包,你均可以嘗試替換成本身相中的,沒必要在乎是選「翠花」仍是「桂花」,多到處就知道了。git

1、目錄結構

如下目錄結構供參考,沒有很詳細地展開,由於每一個應用可能不一樣,最想表達的是這是一個「雙 package.json 結構」,你能夠看到根目錄下有一個package.jsonapp目錄下還有一個package.json程序員

/       // 項目根目錄
├── app/                // 應用源碼目錄,打包就針對app目錄進行打包
│   ├── assets/                     // 應用須要的圖片、icon等資源
│   ├── config/                     // 配置文件存放
│   ├── consts/                     // 應用運行須要的常量,如ipcChanel
│   ├── lib/                        // 引入的庫文件,如jquery
│   ├── plugins/                    // 應用運行須要的插件,如flash
│   ├── utils/                      // 經常使用的工具方法
│   ├── view/                       // 視圖,html、css和js
│   ├── app_config.js               // 整個app的配置,引入config文件夾下文件
│   ├── main.js                     // 應用入口
│   ├── package.json                // 內部的package,定義應用的版本、運行依賴等
│   └── yarn.lock                   
├── build_resource/     // 構建須要的一些工具、資源或者腳本的目錄
├── config/             // 環境配置文件目錄,會選擇一個寫入到app/config
├── deploy/             // 部署腳本,用戶部署文件到cdn或上傳文件到OSS
├── reserve/            // 保留目錄,存放一些文件用於寫入到app內
├── dist/               // 打包和構建的目標目錄
├── release/            // 發行的目標目錄
├── .gitignore
├── gulpfile.js         // gulp配置
├── package.json        // 外部package.json,用於定義開發依賴和腳本
└── yarn.lock

這是由於,咱們的應用在運行時須要一些第三方依賴,這些依賴咱們須要打包到應用內,也就是說/app/node_modules目錄內的內容是要被打包到應用內的,用戶使用的時候纔不會缺失「運行時依賴」,而若是咱們只有一個package.json,那麼全部的依賴都被下載和安裝到同一個node_modules文件夾下,咱們無法把咱們須要打包進去的依賴樹提取出來。因此這樣雙 package.json的結構最清晰明瞭和簡單易用,dependenciesdevDependencies有了明確劃分。

再大體解釋下其餘目錄的做用:

  • app目錄:是咱們應用的源碼目錄,咱們所說的打包針對的就是這個目錄,其餘目錄和文件不會被打包進去,而app目錄內的子目錄和文件就見仁見智了,在不一樣的複雜度下有不一樣的設置,這裏還有一些東西是須要從外面複製進來的,由於不一樣的平臺下你可能須要打包進去的東西是不一樣的。

    • config:配置文件目錄,可能由於你想打包的應用所處的階段(開發、內測、衆測、正式發行)和平臺(Windows、Mac),那麼可能須要不一樣的配置,好比一些資源的名稱和路徑等,這裏你能夠把不一樣狀況下都同樣的配置寫到一個配置文件,而根據狀況不同的配置文件是從外部腳本寫進來的,這就是爲何你會在app目錄外面看到一個config文件夾的緣由
    • plugins:是插件文件夾,你可能須要給本身的應用加一些插件,好比 flash,而一個 flash 插件有 40M 左右,Win(32bit)、Win(64bit) 和 Mac 須要的 flash 插件文件都是不同的,因此若是所有打包進你的應用,再用「if - else」去選顯然是不科學的,Mac 下的應用確定是用不到 Win 版本的插件的,因此這裏的文件也是從外面腳本寫進來的
    • view:是視圖文件夾,也能夠說是渲染進程對應的代碼文件夾
  • build_resource:構建資源或工具文件夾,這個文件夾下放打包到發行這一流程中須要用到的資源和工具,好比程序主圖標、構建安裝包的配置腳本(win)、代碼簽名工具等
  • deploy:存放部署腳本的文件夾,這裏的腳本負責把你的應用安裝包上傳到雲存儲(OSS),咱們會在 gulp 中的發行環節引入這裏寫的腳本進行自動上傳安裝包
  • distrelease:前者是打包和構建安裝包這兩步的 output 目錄,後者是最終咱們會上傳到雲端的安裝包目錄,構建和發行環節的差異咱們後面會講到

2、把整個流程拆分紅段

這個部分無法正向推導,我是從一個亂七八糟的 windows 開發流程開始的,而後修改爲一個合適的 windows 開發流程,再由於要兼容 Mac 的開發,再改爲如今這樣的流程設計的,因此我無法從一開始就說由於什麼因此要考慮什麼,而後慢慢構建出一個合適的工做流,這是上帝視角,這個偏實踐經驗的過程必定是實踐越多,感覺越多的。

因此我會先說個人作法,再說這麼作的好處,所用的工具是 gulp(若是不熟悉,能夠去 gulp 官網看一下,很容易上手),利用 gulp 的 task 串起整條流程,我把工程中的一個階段稱爲一個環節,是爲了和應用自己的階段(開發、內測、衆測或正式發行)作一個區分,否則都不知道說的階段是指啥

流程概覽

  • 配置環節:設定須要打包構建針對的系統、位數(Mac 版不考慮 32 bit)和這個版本所處的階段(開發、內存、衆測或正式發行)這些變量,而後把相關配置寫入配置文件模板,再導入 app 文件夾內相應位置,把其餘相應的文件也寫入 app 文件夾內相應位置,如此 app 文件夾就 Ready 了。
  • 打包環節:根據不一樣的平臺打出不一樣的可執行程序,這一步輸出的是可運行的程序
  • 代碼簽名環節:客戶端特殊的一步,你的應用須要被系統所信任,那就須要代碼簽名,獲取對應平臺下的代碼簽名 CA 而後進行應用簽名,這樣你的應用才能被系統信任
  • 構建安裝包環節:根據不一樣的系統利用不一樣的技術和依賴構建安裝包,Windows 下的 .exe 和 Mac下的 .dmg,而且對這兩個安裝包也須要代碼簽名,這一步後你的應用能夠被分發安裝啦
  • 發行環節:對構建的安裝包進行最後一步修飾,好比修改合適的文件名,而後上傳到雲存儲服務器,獲取到可下載的連接,如此,你的應用已經能夠經獲取到的 url 訪問進行下載安裝了

以上每一步,Mac 版和 Windows 版的開發都須要經歷,只是所用的方法不一樣,這樣作的好處,一是統一了 Mac 和 Win 下開發工做流的生命週期,二是簡單和直觀,每一環節目的是什麼,輸出是什麼很明確。

如此,我在package.json中的script就能夠這麼寫:

... ...
"start": "cross-env NODE_ENV=dev gulp dev",
"packDev": "cross-env NODE_ENV=dev gulp pack",
"buildDev": "cross-env NODE_ENV=dev gulp build",
"releaseDev": "cross-env NODE_ENV=dev gulp release",
... ...

固然這裏的NODE_ENV你也能夠寫成命令行參數(我只是習慣了用這個),利用這個參數去指定須要針對的應用階段,像以上這樣就配好了「dev」階段的相關腳本,能夠用npm run packDev -- --platform="xxxx" --arch="xxxx" --sign這樣形式的命令行去執行不一樣的 gulp 任務,後面的參數,是須要咱們在 gulpfile 文件中解析的,以上3個參數分別表示「系統平臺」、「系統位數」、「是否須要代碼簽名」,咱們能夠在 gulpfile 文件中給這些參數合適的默認值,使操做更人性化。

3、配置環節

目的:一是爲以後的環節初始化工做流參數,二是準備好應用文件夾內容(即要打包的目標文件夾 —— app)

作的事:解析命令行參數,初始化工做參數,填充配置文件,把配置文件和相關依賴文件導入到app文件夾內合適的地方

1. 初始化工做參數

所用工具:yargs

yargs 是一款優秀的命令行參數解析工具,咱們要初始化的工做參數包括如下 3 個:「系統平臺」、「系統位數」、「需不須要簽名」,你也能夠把應用的所處階段(開發、內測、衆測、正式)設計成參數。

// 如下 3 個變量在 gulpfile 內全局聲明

// 這裏的 detectPlatform() 須要本身寫,利用 node 的 os 模塊去檢測開發機環境從而給出
// 爲了理解上直觀一些,把 32 位的 win 寫成 win32,64 位的 win 寫成 win64
// node os.platform() 沒有 win64 的返回的,只有在返回 win32 基礎上,你再使用 os.arch() 去肯定是不是win64
// 可能的合法值:darwin、win6四、win32
platform = yargs.argv.platform || detectPlatform() || 'win32';

// 系統位數,若是是 Mac OS X,不考慮 32位
// 可能的合法值:x6四、ia32
arch = platform === 'darwin' ? 'x64' : (yargs.argv.arch || 'ia32');

// 布爾值,指定是否須要代碼簽名
needSign = yargs.argv.sign || process.env.NODE_ENV === 'prod' || platform === 'darwin';

看到上面的參數初始化,可能會有疑問,既然已經在platform中區分了 win32(32bit) 和 win64(64bit),並且darwin下不考慮 32bit(由於 OS X 10.6 以後就全是 64 位的),arch參數是否多餘?這是能夠認爲是多餘的,可是有的話更完整,並且若是你之後又想兼容 linux 了呢?

2. 填充並導入配置文件

所用工具:gulp API、gulp-replace、gulp-rename

首先我會在根目錄下的 config 文件夾下放幾個不一樣的配置文件模板,分別對應應用不一樣的階段的配置(好比dev.js、alpha.js、beta.js、prod.js),而後利用gulp-replace去替換掉裏面的一些佔位字符串(也就是填充模板),最後利用gulp-rename重命名爲好比env.js後,利用gulp.dest寫入文件到 app/config 目錄下,因而配置文件 Ready。

3. 二進制文件導入(以 flash 爲例)

所用工具:gulp API、del

以 flash 插件爲例,首先你要找到須要的插件文件,electron 官網所說的打開chrome://plugins已經無法用了,從 chrome 的某個版本開始,chrome://plugins Is Not Available。

因此用系統的搜索功能吧,記得先裝下 chrome 瀏覽器,Mac 搜索「PepperFlashPlayer.plugin」,Windows 搜索「pepflashplayer」,Windows下若是搜到多個,記得選擇和 chrome 目錄有關的那個「.dll」文件,此外 win32bit 和 win64bit 所用的 flash 也是不一樣的,Mac 下的「PepperFlashPlayer.plugin」本質是一個文件夾,整個文件夾都須要。全部的3個插件放進根目錄下 reserve 文件夾。

接下來須要作的就是,根據不一樣的平臺讀不一樣的 flash 插件( .dll 文件或 .plugin 文件夾)到 app/plugin 文件夾下。

這裏有一個須要注意的是,每次你構建時,若是 app/plugin 下的 flash 不是你要的,那麼你須要先刪除那個舊的,不然你的 app/plugin 文件夾下會躺着一個你不會用的 flash 插件,但會被打包進去,你的文件大小忽然多了 40M,我這裏用的刪除工具是 del。

通過配置環節,app文件夾已經準備就緒,因此以開發模式(不須要打包)運行應用也就沒啥大問題,能夠另寫一個「dev」的 gulp task,利用 node 的child_process模塊下的exec調用下electron app --debug就能夠運行應用了,沒啥能夠多說的,咱們繼續進入下一步 —— 打包。

4、打包環節

目的:產出一個可執行程序,簡單來講,就是能有一個應用,雙擊能運行起來

作的事:利用electron-packager打包,補充應用信息(only for win)

1. 利用electron-packager打包

利用electron-packager打包,只須要針對不一樣系統平臺給出不一樣的配置,而後調用其 API 就能夠了。

// Mac
const options = {
    dir: './app',
    name: '應用名字',
    platform: 'darwin',
    arch: arch, // 這就是工做參數 arch
    overwrite: true,
    appVersion: 'Copyright(C) 2017 Qunhe',
    asar: {
        unpackDir: 'plugins' // plugins 內的文件咱們不但願打進 asar 格式包內
    },
    out: './dist',
    icon: './build_resource/logo.icns' // Mac 下 icon 格式是 .icns
};

// Win
const options = {
    dir: './app',
    platform: 'win32', // 無論是 32bit 仍是 64bit 的 win,這裏都是 win32
    arch: arch, // 這裏依靠 x64 或 ia32 去區分位數
    overwrite: true,
    asar: {
        unpackDir: 'plugins'
    },
    out: './dist',
    icon: './build_resource/logo.ico' // Win 下 icon 格式是 .ico
};

Mac 下各處(Dock、任務欄、進程名等地)展現的應用名字只要指定了name選項,就是到處同樣的,因此你能夠用 name 指定一箇中午名字,並且 Mac 下默認編碼都是 UTF-8,問題不大。

而對於 Windows,首先其中文默認編碼是 GBK 的,而因此若是指定中文名字可能會有奇怪的問題,因此 Windows 應用通常我不填name項,這樣它會去找你 app 目錄下的 package.json 文件中的productNamename字段值,這個字段通常設置是英文的,第二個不去設置中文的緣由是,Windows 下應用的展現名字是 exe 主程序的FileDescription配置項決定的,若是不去設置,那麼可能你的應用用任務管理器打開,顯示的進程是「Electron」,而不是你的應用名字。

關於應用的實際名字和展現名字,Win 和 Mac 下都有本身的一套,這裏不細展開。而基於目前的實踐,我給的建議是,Mac 下的開發,你能夠直接指定name爲一個你要的中文應用名,而對於 Win,你最好像下面那樣操做。

2. 補充應用信息(for win)

所用工具:rcedit

Command line tool to edit resources of exe file on Windows. 翻譯過來就是一個用於編輯 exe 文件信息的windows 命令行工具,固然它已經有了 node 版本,叫 node-rcedit,也就是說你能夠用 node 子進程的exec去執行,也能夠調用 node 版本的 API。

能夠這麼用:

execSync(`
.\\node_modules\\rcedit\\bin\\rcedit  // 調用rcedit
./dist/xxxxxx.exe  // 目標文件(剛打包出來的主程序)
--set-version-string "LegalCopyright" "Copyright(C) 2017 Health" // 版權信息
--set-version-string "CompanyName" "仙風道骨養生俱樂部"  // 公司名字
--set-version-string "ProductName" "養生" // 產品名字
--set-version-string "FileDescription" "養生寶典" // 這個很重要,由於這個就是你打開任務管理器看到的進程名字
`);

大部分信息,你能夠右鍵主程序(.exe)文件,「屬性 —— 詳細信息」中看到,這麼作還有一個考慮是,這樣你的應用看上去會更加規範。

這裏確定有人說,爲何不用electron-builder,由於我首先接觸到的是electron-packager,我以爲夠用(由於我有一臺 win 和一臺 mac,跨平臺打包,不存在的),第二,electron-packager完成打包的事就夠了,後面構建安裝包等過程可讓咱們有更多的選擇,符合本文的工做流設定,每一個環節作每一個環節該作的事就好,固然你也能夠選擇electron-builder,能達到目的就好。

5、代碼簽名環節

目的:使應用被系統所承認,能正常安裝

作的事:給應用進行代碼簽名

1. 爲何須要代碼簽名,沒有會怎樣

代碼簽名的目的就是爲了安全,你的應用一旦通過了代碼簽名,若是發行過程當中被篡改,你的用戶會看到系統給出的警告提示,而對於發行方而言,代碼簽名後,應用才能被系統承認,很大機率不會被殺毒軟件作掉,並且若是你要提交一些軟件市場,一些軟件市場要求應用須要有合法的代碼簽名。

而若是做爲鐵頭娃的你鐵定不簽名,這應用就不能跑了麼?不是的,仍是能夠跑的,只不過對你的用戶來講很不友好。

1.1 Windows 下有和沒有代碼簽名的差異

簽名對比

Windows 下代碼簽名的限制沒有 Mac 那麼嚴,你選擇「是」都是能夠安裝使用的,可是從你產品的用戶角度,有一個代碼簽名會更可靠,此外,這樣的沒有簽名的安裝包在一些軟件市場可能都提交不上去。

1.2 Mac 下有和沒有代碼簽名的差異

Mac 下有和沒有代碼簽名的差異就很大了,沒有合法的代碼簽名,你的 .dmg 安裝包根本無法打開。

若是沒有代碼簽名,Mac 下的 .dmg 安裝包打開,首先會提示你「該應用來自身份不明的開發者,是否確認打開」,而後你點「確認」,再根據你的安全設定(系統偏好設置 —— 安全和隱私 —— 容許從如下位置的應用下的設置)去決定,而絕大部分的 Mac 用戶都是勾選「App Store 和 被認證的開發者」,因而就算你點了「打開」,直接會告訴你「打不開XXX,由於它來自身份不明的開發者」,這個時候只能去改變「系統偏好設置 —— 安全和隱私 —— 容許從如下位置的應用下的設置」才能打開。

典型的盜版軟件安裝方式啊,因此做爲一款要發行的產品,咱們必定是須要代碼簽名的。

2. Windows 下的代碼簽名

整體建議:我的的小項目就不用 Windows 代碼簽名了,由於很貴,2K+/年,並且 Windows 下代碼簽名沒有問題不是很是大(和 Mac 相比),公司的產品,那就必需要的。

2.1 購買微軟代碼簽名證書

能夠向權威的 CA 機構購買代碼簽名證書,這裏就我瞭解的作一個建議:建議向賽門鐵克購買簽名普通軟件(非驅動)的微軟代碼簽名證書,大概幾百刀一年。

背景說明:目前咱們用的是沃通的代碼簽名證書,賽門鐵克的只是諮詢過,沒用過。

就以上的建議作一個解釋,爲何我這麼建議:

  • 咱們須要代碼簽名,進一步,須要把 Windows 代碼簽名這一環節也作到自動化流程中,這是咱們的需求
  • 沃通的代碼簽名證書是封死在 U 盤裏,因此可認爲這是物理證書,更安全,但很不方便,不可能導出來進行簽名的
  • 瞭解到的,賽門鐵克頒發的若是是針對普通軟件(非驅動的),那麼是能夠給頒發文件格式的真·電子證書的
  • 意味着沃通的證書咱們要簽名,須要依靠一個物理U盤
  • 最坑爹的:沃通的代碼簽名時,要手輸密碼,若是一個 Windows 應用咱們選擇 SHA1 + SHA256 的簽名方式,那麼應用和安裝包,咱們須要輸4次密碼,氣到拉閘,他們官方說有本身的命令行,實際是命令行喚起他們的 GUI 圖形界面來簽名,還不是須要人工操做
  • 因此,顯然這和咱們的「自動化」目標相去甚遠,我建議普通的應用,沒有涉及到高度安全的,不要選擇購買封死在 U 盤中的 Windows 代碼簽名證書。

2.2 簽名

當你購買了證書後,就能夠利用signtool 命令行進行簽名了,命令怎麼寫,這些都在你購買證書的 CA 網站上找到或者 google 一下,這裏要說的就兩點:

  • Windows 代碼簽名咱們目前選擇 SHA1 簽名後再追加 SHA2(SHA256) 簽名,這樣的組合方式,安全和兼容性最好
  • 代碼簽名能夠在 gulpfile 文件中封裝成一個方法(參數是須要簽名的文件路徑),由於咱們會屢次調用

2.3 查看簽名信息

查看 Windows 代碼簽名信息很簡單,右鍵你簽名的文件,簽名後的文件,屬性打開會有一個「數字簽名」的 tab,點擊切換到「數字簽名」能夠看到代碼簽名信息。

3. Mac 下的代碼簽名

整體建議:Mac 下應用要代碼簽名,由於很方便,也不是很貴,我的開發者 99 USD 一年,若是公司有 Apple Develop Team,你能夠直接加入,關鍵是 Mac 下若是你不進行可供分發的代碼簽名,你的應用很難被他人安裝啊。

3.1 利用 Xcode 申請證書,各個證書間差異

證書是能夠在 Xcode 下申請的,Xcode —— Preference —— Account 下,選擇一個Team(以前要先加入),若是是獨立開發者,就選本身 Apple ID 的那個,點擊「Manage Certificates」,彈出的彈窗中左下角點加號,能夠選擇須要的證書。

我看到以後的第一反應是:尼瑪,哪些是我要的啊。下面簡單說明下(摘自Mac App 發佈的最後 1km):

  • Developer Certificate

    • Mac Development :這個只用來開發,Debug,不是正式發佈的版本
  • Production Certificate

    • Mac App Store

      • Mac App Distribution :這個用於 Xcode 本身把 .app 文件上傳到 Mac App Store
      • Mac Installer Distribution :這個沒用過,但能夠確定的,也是上傳 Mac App Store 用的
    • Developer ID

      • Developer ID Application:這個用於開發者使用開發者賬號簽名,導出一個線下發布版本的 .app 文件,脫離了蘋果的 Mac App Store。
      • Developer ID Installer:用於開發者打包,同時加上開發者賬號簽名,打包工具在下面介紹。

咱們主要須要的就是「Developer ID Application」這個類型的證書,「Mac Development」只是用於開發的,而前者能夠供分發,也就是簽名後,別人下載安裝,就是來自「被認證的開發者」的應用啦。

若是是在一個 Team 中,不是我的獨立開發者,那麼這個「Developer ID Application」證書的申請你是沒有權限的,就算大家 Team 的 Agent 設置你爲 admin(管理員),你仍是沒有權限的,由於一個「Developer ID Application」只有一個 Team 的 agent(owner) 才能申請,你須要作的是利用你 Mac 上的鑰匙串工具(具體怎麼作,google 下就能夠了),生成「CertificateSigningRequest」(簡稱 CSR),而後發給你的 team agent,讓他幫你生成證書,發回給你,你再安裝到本身機子上,搞定。

你能夠在終端調用security find-identity -p codesigning -v來看一下你可用的代碼簽名證書,其中那個Developer ID Application開頭的就是咱們要的。

3.2 簽名

所用工具:electron-osx-sign

Mac 下的簽名簡直是紅紅火火開開心心嘿嘿哈哈啊,你能夠從electron-osx-sign 指導這裏得到徹底的指導,你在這個頁面右邊能夠根據你的項目進行填寫,頁面最後會根據你的配置,給你一段你均可以直接複製的簽名代碼,完美。

並且簽名還能集成到打包階段,不過我建議仍是拿出來好,比較清真。

3.3 查看簽名信息

Mac 下查看文件簽名信息,你能夠終端運行codesign --display --verbose=4 "文件路徑"

6、構建安裝包環節

目的:使你的應用能夠被安裝(若是沒有這一步,你能怎麼辦,壓縮整個應用文件夾,而後分發這個壓縮包,呃,你能接受也能夠啊)

作的事:把經歷了打包和簽名環節後的應用程序文件夾(Mac 下的.app其實也是文件夾)打成一個安裝包文件

爲何要構建安裝包,這有不少的緣由,可能你也會想到不少,其中值得強調的兩點,一是構建安裝包會直接便利於應用的自動更新,具體咱們下一篇文章裏再說,二是 Win 下安裝包的體積相比原先的文件夾,體積明顯小不少,在硬盤容積很大的時代,下載體積纔是最影響用戶體驗的,而安裝後的體積不是最須要考慮的體積。

安裝包這個事和代碼簽名相似,兩個不一樣的系統(Win 和 Mac)實現徹底不一樣,Windows 下咱們習慣.exe.msi這樣的安裝包格式,習慣點下一步到完成或一鍵安裝,而 Mac 下除了 Store 下載安裝的,咱們習慣的.dmg格式的,掛載後打開,將裏面的應用拖入到Application文件夾就完成了安裝。

這裏咱們實現的就是經典的 Windows exe 安裝和 Mac dmg 安裝,相比較而言,Windows 下的繁瑣得多得多。

1. Windows 下利用 inno setup 進行安裝包構建

1.1 爲何用這個 inno setup

最終說服我使用 inno setup 來構建應用安裝包的理由是,VS Code 也是這麼作的。由於按照程序這個領域離一個小前端已經很遙遠了,對於跨度大的未知東西,通常都會作充足的調研,最後發現 VS Code 也是這麼作的,好,幹!

而使用了一段時間後,我能夠說幾點不後悔的理由(固然我沒使用過其餘的安裝包構建工具,因此僅一些偏見):

  • inno setup 應該是 windows 下構建安裝程序的老牌工具了,你能夠去進他們的官網,一股「老牌可靠」的風格撲面而來,可靠
  • 它有 GUI 和 命令行工具,有 unicode 版本(意味着徹底支持中文),gulp 有別人寫好的現成的插件(對於中文應用須要修改)
  • 基本使用的話,學習成本不大,基本去找一些案例配置文件去學一下就能夠了
  • 進階使用,須要寫 pascal 腳本,可是功能是真的強大
  • 還有一點我感覺很好的是,這個工具的支持很好,stackoverflow 上有足夠的問答資源,若是仍是沒有你滿意的,官網有一個看上去很很很簡陋的論壇,可是頗有用啊,我問過 2 個問題,睡一覺起來都有迴應了

1.2 怎麼學習 inno setup

先能夠本身去搜一下 inno setup,進入官網逛一逛,下載安裝一下(記得安裝 unicode 版本,即括號裏有 u 的版本),瀏覽後有幾個基本認知須要具有:

  • inno setup 是徹底根據配置文件(.iss)來構建安裝程序的,你用 GUI 其實也是去編寫 .iss 文件,而後利用這個配置構建的
  • inno setup 能夠用 pascal 腳本控制安裝嚮導的行爲,這是進階的使用方式,足夠你安裝本身的設想優化安裝程序了
  • inno setup 構建出來的安裝包運行時能夠添加參數,使安裝有不一樣的表現,好比徹底靜默的後臺安裝(Amazing,這裏的參數對於自動更新頗有用)

有了上面的幾點認知,能夠給出「學習和使用 inno setup 路徑」的建議:

  1. 下載安裝後,找幾篇 inno setup GUI 使用教程,嘗試構建一個安裝包(要能夠安裝的)
  2. 找一些 inno setup 配置文件的案例,對於 inno setup 配置方式有一個印象,分多個[section],每一個[section]有不少配置項,每一個配置項可能有多個字段
  3. 能夠把 inno setup 官方文檔 瀏覽一遍,跳過「pascal scripting」部分
  4. 到這裏,你應該能看得懂他人的 .iss 文件裏除了 [code] 這個 section 外的配置了
  5. 把安裝嚮導的語言換成中文(先要導入中文語言包,再改配置,具體作法也有一些文章說到了,很少說,這一步對於你以後步驟也是有用的)
  6. 能夠嘗試正式結合到你的 gulp 工做流了

1.3 怎麼結合到 gulp 工做流中

所用工具:修改後的 gulp-inno

若是按照以前的步驟花了個把小時大概學習了下 inno setup 的話,那麼到這裏你應該能夠嘗試把 inno setup 構建安裝包作到你的 gulp 工做流中了,若是還不熟悉 inno setup 配置文件,不要緊,你能夠從仿照開始,不要慫,就是幹,都到這一步了,誰慫誰尷尬。

配置文件的詳解不是這裏的重點,因此再也不展開,把 inno setup 整合進腳本中,由於它自己提供命令行工具,勤快和好學的你能夠根據官方或其餘渠道的指導本身封裝一個 node 模塊,而我就比較懶了,搜到一個已有的 gulp 插件 —— 「gulp-inno」,高興地一匹。

然而,事情總不會那麼順利,該吃的shi躲不掉,該經歷的坑繞不過,這才叫「歷shi」。我利用「gulp-inno」根據其指導怎麼都不能正確編譯,大概提示是有不合法的字符的意思。

明白了,絕壁是「gulp-inno」裏包的 inno setup 不是 unicode 版本,因此一旦有中文等字符,就出錯了,我看到這個包裏的 inno 文件夾徹底就是和個人 inno setup 文件夾沒差嘛,因而我把我本地安裝的 inno setup 文件夾裏內容複製替換到 gulp-inno 的 inno 的文件夾內,問題解決。

由於我以前導入過中文語言包,因此我複製過去的時候,中文語言包也複製過去了,能夠愉快地配置安裝嚮導界面爲中文了。

一旦修改好「gulp-inno」包(替換成 unicode 版本 & 加入簡體中文語言包),就能夠怎麼操做:

// 1. 準備 iss 文件:填充你的 iss 配置文件模板,並輸出到 dist 目錄下

const appInfo = require('./app/package.json'); // 全部和應用相關的信息從 package.json 讀取
const bom = require('gulp-bom'); // 這是爲了解析中文的
const outputName = 
`${appInfo.name}-${platform}-${appInfo.version}-${process.env.NODE_ENV}`;
const outputIssName = 
`${appInfo.name}-${platform}-${process.env.NODE_ENV}.iss`

gulp
    .src(`./build_resource/installer_win_config_${platform}.iss`)
    .pipe(bom())
    .pipe(replace('${version}', appInfo.version))
    .pipe(replace('${appExe}', `${appInfo.name}.exe`))
    .pipe(replace('${sourcePath}', `${appInfo.name}-${platform}`))
    .pipe(replace('${outputName}',outputName))
    .pipe(rename(outputIssName))
    .pipe(gulp.dest('./dist'))
    .on('end', () => {
        // .iss file is ready
    })

// 2. 交給 inno setup

const inno = require('my-gulp-inno'); // 修改後的 gulp-inno

gulp
    .src(`./dist/${outputIssName}`)
    .pipe(inno())
    .on('end', () => {
        // you have an installer now
    });

1.4 將來能夠作什麼

當時還有一個看中 inno setup 的理由是,它可讓咱們定製咱們的安裝嚮導步驟和外觀,也就是說你可讓你的應用也像其餘一些優秀的產品同樣,在安裝的時候能夠定製酷炫的外觀,能夠優化安裝流程,支持一鍵安裝,inno setup 仍是能夠玩出一些花樣的,enjoy。

1.5 對安裝包也進行代碼簽名

一樣的,安裝包也須要代碼簽名,利用以前封裝的簽名方法進行簽名就好了。

2. Mac 下的構建 dmg 安裝包

所用工具:appdmg

相比於 windows 的安裝包構建,Mac 下的構建安裝包又是美滋滋啊,你看我下面小標題都沒有就知道了。

// 由於 appdmg 在 windows 下不能下載安裝的,因此放在外部 package.json 的 optionalDependencies 下
// 在 gulp 腳本中須要作 try...catch 處理,不然當你回到 windows 下使用這份 gulp 時會出報錯
let appdmg;
try {
    appdmg = require('appdmg');
} catch (err) {
    appdmg = null;
}

const dmg = appdmg({
    // 打出的目標 dmg
    target: `dist/balabala.dmg`,
    // 基準目錄,如下的資源都基於這個目錄
    basepath: __dirname,
    // 具體的選項
    specification: {
        // dmg 打開後的窗口名字
        // 注意不要給中文,給中文會致使下面的 background 無效,不明白, github 上也有人提了這個 issue
        title: `myapp`,
        // dmg 掛載後的圖標,出如今桌面上
        icon: "xxx.icns",
        // 背景圖,若是同時存在 bg.png 和 bg@2x.png,appdmg 會根據用戶屏幕本身找合適的圖
        background: "bg.png",
        // 裏面全部icon的尺寸
        'icon-size': 96,
        // 窗口設置
        window: {
            size: {
                width: 550,
                height: 320
            }
        },
        // 裏面的內容,x 是指這個 icon 中心距離窗口最左邊的距離,y 是指這個 icon 中心距離窗口頂部的距離
        // 這裏能夠指定一個name項,不要給中文,會致使圖標異常
        contents: [
            { "x": 400, "y": 128, "type": "link", "path": "/Applications" },
            { "x": 150, "y": 128, "type": "file", "path": "你的應用.app" }
        ],
        // 對 dmg 進行代碼簽名
        'code-sign': {
            'signing-identity': '你的代碼簽名證書'
        }
    }
});
dmg.on('finish', function () {
    // you have a dmg now
});
dmg.on('error', function (err) {
    // error
});

其他的配置和因此配置影響的內容能夠參加 appdmg githug 主頁,而後就是本身試試看了。

7、發行環節

目的:使應用能夠被下載(上一步只是能被安裝,但並不能被下載)

作的事:重命名應用安裝包供發行,上傳應用安裝包到雲存儲服務器供下載

這一步根據每一個人使用的雲存儲方式不一樣而須要利用賣方提供的 API 編寫合適的腳本去上傳你的安裝包,所以具體的腳本不作展開,只是有幾點最佳實踐能夠參考:

  • 上傳前,把你的安裝包文件重命名成符合必定規範的,多是「應用名-版本-階段-系統-尾數」,多是「應用名-版本-系統-構建號」,多是...這個就本身定,但必定要有一個合適的命名,這樣一看到名字就知道這個是啥,不會弄錯
  • 你的 OSS 服務器上要針對應用安裝包的不一樣階段創建不一樣的文件夾,一方面能夠方面管理,另外一方面也便於作權限管理

當你上傳了你的安裝包後,也就意味着這個安裝包有了一個下載連接,你能夠分發這個連接供用戶下載啦,至此終於走完了「代碼」到可下載「安裝包」的過程,鼓掌。

8、路漫漫

這一路走來看上去已經頗有成就感,但實際上還有許多事能夠作得更好,不過工程化的東西,邏輯清晰、流程自動化、能知足需求就能夠了,而搭好工程,咱們須要開始專一於 Electron 應用的功能開發了,纔剛剛要邁上紅地毯,路還有很長,下期見。

附:gulp 文件和腳本看上去會是怎樣的

對以前的工做流作一個小結(若是遇到有一些舊文件覆蓋不了,能夠本身加一個清理環節或方法,去清理舊文件)

/* gulpfile.js START */
// 此處省略一堆須要引入的依賴

// 工做參數
let platform = 'win32';
let arch = 'ia32';
let needSign = false;

// 配置環節
gulp.task('env', (cb) => {
    // ...
});

// 開發調試
gulp.task('dev', ['env'], (cb) => {
    exec('electron app --debug', (err) => {
        if (err) return cb(err);
        cb();
    });
});

// 打包環節
gulp.task('pack',['env'], (cb) => {
    if (platform === 'darwin') {
        // ...
    } else {
        // ...
    }
});

// 簽名環節
gulp.task('sign-pack', ['pack'], (cb) => {
    if (needSign) {
        if (platform === 'win32' || platform === 'win64') {
            // ...
        } else if (platform === 'darwin') {
            // ...
        }
    } else {
        cb();
    }
});

// 構建環節
gulp.task('build', ['sign-pack'], (cb) => {
    if (platform === 'darwin') {
        // ...
    } else {
        // ...
    }
});

// 發行環節
gulp.task('release', ['build'], (cb) => {
    // ...
});

const codeSignForWin = (filePath) => {...};

const codeSignForMac = (filePath) => {...};

const detectPlatform = () => {...};
/* gulpfile.js END */


// package.json 中配腳本
 "scripts": {
    "yarnall": "yarn && (cd app && yarn)",
    "start": "cross-env NODE_ENV=dev gulp dev",
    "packDev": "cross-env NODE_ENV=dev gulp pack",
    "packAlpha": "cross-env NODE_ENV=alpha gulp pack",
    "packProd": "cross-env NODE_ENV=prod gulp pack",
    "buildDev": "cross-env NODE_ENV=dev gulp build",
    "buildAlpha": "cross-env NODE_ENV=alpha gulp build",
    "buildProd": "cross-env NODE_ENV=prod gulp build",
    "releaseDev": "cross-env NODE_ENV=dev gulp release",
    "releaseAlpha": "cross-env NODE_ENV=alpha gulp release",
    "releaseProd": "cross-env NODE_ENV=prod gulp release"
  }
// 可選命令行參數:
//      sign: 是否簽名
//      platform: 系統平臺
//      arch: 系統位數
相關文章
相關標籤/搜索