思惟導圖node
前言:python
Electron 從最初發布到如今已經維護很長一段時間了,可是去年纔開始慢慢升溫。筆者我的剛好也有一些 Electron 的實踐經驗,本來這篇總結是大約半年前開始起草的,可是因爲一些我的緣由拖到如今纔算是正式完稿,如今發出來供有意實踐 Electron 的開發者借鑑,避免一些本人在實踐中踩過的坑。此外,筆者的水平有限,不免出現錯誤,若是讀者在閱讀本文過程當中發現原則性的錯誤,請務必指出,以避免相關內容禍害他人。jquery
本文的實踐基於
Electron 1.x
。linux
問題: renderer 端無 jQuerywebpack
根據官方的新手教程,咱們很容易建立一個基本的 Electron 應用,咱們再也不累贅敘述,若是你只須要加載一個網頁,那麼只須要下面幾行簡單的代碼:git
12345678910複製代碼 |
const { BrowserWindow } = require('electron')let windowfunction createWindow() { window = BrowserWindow({ width: 1024, height 720 }) window.loadURL('https://changkun.us')}app.on('ready', createWindow)複製代碼 |
若是你真的嘗試加載筆者的博客頁面,會發現筆者博客的文字內容沒法加載,這是因爲 jQuery 找不到而產生的問題,爲此咱們並無必要調整 Web 端的內容,只須要簡單在 Electron 的 Renderer 端引入 jQuery 便可。github
解決方法就可使用 BrowserWindow 的 preload 參數,而後使用 preload 腳本爲 renderer 端引入 jQuery:web
123456複製代碼 |
// preload scriptdocument.addEventListener('DOMNodeInserted', (event) => { if (!!window && !(window.$)) { window.$ = window.jQuery = require('../utils/jquery.min') }})複製代碼 |
相關 issue: github.com/electron/el…shell
在 Electron 中,不管是應用程序的主菜單(macOS 頂部的菜單)、窗口菜單(Windows/Linux)的窗口菜單、Tray 菜單、仍是 Renderer 端的 Context 菜單等,凡是和菜單掛鉤的功能都是經過 Menu.buildFromTemplate 進行建立的,他們只是被掛載到了不一樣的實體上,好比被掛載到了頁面上的叫作 Context 菜單,被 setApplicationMenu
掛載的成爲了主菜單。json
Electron 的菜單其實限制也很是之大,其菜單在建立完成後便不能在運行時被直接修改,須要修改時,必須對整個菜單從新建立,從而達到動態菜單的目的。這也是很是不友好的。
另外一方面,一個菜單項被點擊後,只接受三個參數,也就是說 click 屬性的回調只接收: menuItem(被點擊的item)、focusedWindow(點擊按鈕時focus的窗口) 以及毫無用途的 event (提供鍵盤是否被按下的信息),這也就形成的諸多的不便。
例如,當咱們但願一個按鈕被點擊後,其餘的菜單項的 enbalbed 屬性設置爲 false,將無從下手。
爲了解決這一問題,這時候咱們能夠巧妙的使用 Electron 的 ipc 機制在 renderer 端建立應用菜單,在 renderer 端建立的菜單即可以在內部使用 ipcRenderer.send() 這個方法,讓 ipcMain 來處理其餘其餘菜單項的操做。
不只如此,甚至於可讓整個菜單的 click 邏輯都交付於 main 端進行管理,例以下面這樣的 menu template:
1234567891011121314151617181920212223242526複製代碼 |
{ type: 'separator', visible: (()=>{ if (process.platfrom == 'darwin') return true; else return false; })()},{ label: `Current Version: ${app.getVersion()}`, enabled: false},{ type: 'separator'},{ label: 'Logout', click: () => { ipcRenderer.send('application', 'logout'); }},{ type: 'separator'},{ label: 'Exit ${app.getAppName()}', accelerator: 'CmdOrCtrl+Q', click: () => { ipcRenderer.send('application', 'quit'); }}複製代碼 |
而在 main 端則能夠:
123456789複製代碼 |
ipcMain.on('application', (event, args) => { switch(args){ case: 'logout': ....; break case: 'quit': ...; break default: ... }})複製代碼 |
相關 issue: github.com/electron/el…
問題: Electron 的官方文檔其實就提供了網絡檢測的方法,思路是經過一個隱藏的後臺窗口,檢測網絡網絡狀態,經過網絡狀態產生變化後,經過 ipcRenderer 發送消息給 ipcMain,而後響應所需的操做,見這裏。
但事實上這個方法是基於 online
和 offline
事件的,換句話說這個方法只能檢測到當系統網絡鏈接被切斷物理鏈接後的狀態變化,沒法檢測網絡自己。
這個問題能夠參考下面的 issue,但其實現思路就是向蘋果的 hotspot detect 頁面發起請求,當未超時狀態下有 Success 返回時,便說明網絡狀態正常。在實際應用編寫過程當中,咱們也並不須要爲了這樣一個簡單的功能而引入框架,只需定時向服務器發起任意一個可以判斷網絡狀態的請求便可,與心跳鏈接異曲同工。
相關 issue: github.com/electron/el…
在前面建立基本應用中咱們已經談到了關於 preload 腳本用來引入 jQeury 的使用,事實上咱們能夠用 preload 作更多的事情。preload 腳本會在整個頁面開始加載以前被執行,因此若是咱們直接執行一些當整個 DOM 加載完成才能被執行的操做,是一定會失效的,所以這樣的兩個事件是很是有用的:DOMNodeInserted
、DOMContentLoaded
。
爲此,咱們能夠把 preload 腳本大體分爲三塊區域:
12345678910111213141516171819202122複製代碼 |
// ---------------------------------------------------// 在頁面加載以前須要執行的相關代碼// ...// ---------------------------------------------------// -------------------------------------------------------document.addEventListener('DOMNodeInserted', (event) => { // 頁面內容加載以前須要引入的一些代碼 // ...})// -------------------------------------------------------// -------------------------------------------------------document.addEventListener('DOMContentLoaded', (event) => { // 頁面內容加載以後須要引入的一些操做 // ...})// -------------------------------------------------------複製代碼 |
preload 腳本的做用很是大,有時候會有這樣的需求:當咱們加載一個網絡上的頁面時,咱們不能控制從網絡中讀取到的頁面內容,但 preload 提供了這樣的可能性,使得咱們可以向頁面 注入
一些代碼,知足一些神奇的需求,好比對網絡加載頁面增長 Context Menu。但也有使用時值得注意的地方:
Electron 的 main 進程、preload 腳本、renderer 進程、以及 document 對象分別有彼此的建立和執行順序。首先 main 進程會優先被建立毫無疑問,preload 會在 document
對象被建立以前優先加載(但可以使用 document
),而 renderer 進程會在 document
建立以後被建立,而他們三者又是併發建立的,以下圖所示。
那麼,若是咱們不當心在 preload 腳本中直接引入 ipcRenderer 發送一條消息給 ipcMain,那麼 ipcMain 可能不能收到這條早期消息。爲了保證咱們可以收到這條消息,最好的方式就是:
12345678910複製代碼 |
// preload.js// 不要再外面這麼幹// ipcRenderer.send(...)document.addEventListener('DOMContentLoaded', (event) => { // 頁面內容加載以後須要引入的一些操做 // ... // 正確的作法 ipcRenderer.send(...)})複製代碼 |
相關 issue: github.com/electron/el…
所謂的第三方 URL 檢查問題,是應用指針對提供第三方登陸接口時,頁面須要臨時性的跳轉到其餘第三方域名下進行後,會致使 Electron 窗口出現各類不可預計的後果的這個問題。當一個 Electron 窗口跳轉到第三方頁面後,因爲 Electron 本質上是一個瀏覽器,而第三方頁面一般包含各類其餘的指向性連接,例如一個 QQ 的第三方登陸頁面,點擊 QQ 的 Logo 會跳轉到 QQ 的主頁,這就致使了『穿幫』,讓用戶察覺這個應用是指一個簡單的瀏覽器,形成不夠優秀的用戶體驗。
這時,爲了解決這個問題,筆者實踐的一個解決作法是對每次要跳轉的 URL 和打開的窗口作 URL 檢查,主要依賴下面兩行代碼:
12複製代碼 |
win.webContents.on(‘will-navigate’, checker)win.webContents.on(‘new-window’, checker)複製代碼 |
在這兩行代碼中,不管是頁面即將跳轉,仍是打開一個新窗口, checker
都會對 URL 進行檢查,只有符合要求的URL纔會在窗口內被加載,不然將經過 shell.openExternal()
來調用。
讀者看到這裏可能會對 URL 檢查自己產生質疑,由於一個嚴格的 URL 檢查事實上是難以實現的。不過下面的例子應該可以給與讀者相關啓發,例如:
筆者在實現我的博客的客戶端時,須要考慮每篇博文下方的評論區登陸問題,如圖:
當用戶點擊登陸後,客戶端會跳轉到第三方登陸頁,下圖中的微博 logo、註冊、多說評論框都是可能致使用戶進行進一步跳轉的隱患:
對此,有這樣一個取巧的辦法避免這些有隱患的跳轉:
1234567891011121314複製代碼 |
function urlChecker(event, url) { event.preventDefault() // 第三方登陸在登陸回博客頁面時,一般會帶有 login-calback 的請求參數 if (url.match('login-callback')) // 可是在完成登陸後,會返回多說本身的主頁,而不是博客頁面自己, // 這是不能忍的,所以這裏要求 mainWindow 跳轉到 home 頁 mainWindow.loadURL(common.url.home) // 當匹配到要打開的 url 不屬於 changkun.us 域名下時,直接在外部打開 else if (url.match('changkun.us') == null) shell.openExternal(url) // 不然,加載此URL else mainWindow.loadURL(url)}mainWindow.webContents.on('will-navigate', urlChecker)mainWindow.webContents.on('new-window', urlChecker)複製代碼 |
下載也是一個常見的需求,好比,你正在基於 Electron 實現一個 Web 文本應用,用戶可能須要下載保存在服務器上的一個編輯好的文件,這時候當點擊 Web 界面中的下載時,Electron 並不須要專門針對這個下載行爲進行單獨的處理,Electron 會想瀏覽器那樣直接跳出一個保存的文件選擇器,讓用戶得到下一步的操做。當咱們真正須要處理一些特殊的下載操做時,一樣能夠用 electron 的 DownloadItem 來實現,但其接口設計着實有點讓筆者難以接受,這裏推薦能夠嘗試 electron-userland/electron-download
這個庫,雖然其本質也是 DownloadItem,但其接口相比之下友善許多,由於庫自己也並不複雜,也能夠在項目中自行實現這部分邏輯。
從 URL 啓動實際上是一個比較 tricky 的功能,這個功能在大約半年多之前的 Electron 1.4.x 再往前實際上是一個體驗至關差的功能,在那個時候 從 URL 啓動還只能在 macOS 上實現,還須要爲應用配置額外的 plist 文件 (使用 electron-builder 打包時,後面會具體討論這個工具和其餘工具),且 windows 和 linux 並不支持。後來 Windows 提供了支持,但 Linux 因爲須要 root 權限的問題,並不能很好的支持。
而如今實現這個功能在如今已經變得很是的容易了,只須要在 package.json 中配置 build.protocols
字段:
12345678910複製代碼 |
"build": { ..., "protocols": [ { "name": "changkun://", "schemes": [ "irc", "ircs" ] } ], ...}複製代碼 |
並在應用中添加一行代碼:
1複製代碼 |
app.setAsDefaultProtocolClient('changkun')複製代碼 |
即可以經過 changkun://anything
來啓動應用了,若是你還要讓應用來針對不一樣的 URL 處理不一樣的事件,一樣也是能作進一步定製的。
這個需求是筆者在下意識的建立多窗口應用時候發現的,雖然 Web 應用已經出現了 SPA 的發展趨勢。可是有時候多窗口仍是會有一些用處,例如一個用戶產生應用的主窗口,一個後臺隱藏的網絡監測窗口,以及一個負責打開其餘新頁面的窗口。筆者約半年多以前發現這個問題,至今因爲忙於他事也未認真尋找問題產生的根源,至今發現這個問題仍然違背解決,因而開出了 issue,有興趣的讀者能夠跟蹤研究一番。
注意: 此 bug 已被修復,請查看下面的 issue 得到更多細節。
相關 issue: github.com/electron/el…
在 UX 上其實大部分第三方框架已經作得足夠優秀,Electron反卻是個例外。拿加載網頁內容來講,當用戶點擊了某個選項以後,沒有反饋的交互會讓用戶沒有成功的執行剛纔的點擊操做,這時候頁面上的 loading 進度條是很是有必要的。因爲大部分的框架如 React, Polymer都具有這個組件,因爲其內容已經脫離 Electron 自己,限於篇幅這裏就只作簡單說起再也不作深刻討論。
Web 應用一般很難直接給用戶帶來像原生應用那樣的體驗的一個主要緣由就是,文本是可選可拖拽的。爲了維護上的可用性,若是一個 Web 應用會在瀏覽器端和 Electron 端同時分發,那麼能夠將這類體驗的 CSS 經過 preload 的方式注入到 Web 頁面,而不是直接在 Web 頁面進行實現,從而實現很好的邏輯分離。
軟件的往後更新一直都是產品往後迭代的殺手,一個須要被分發的桌面應用,在沒有肯定的更新機制以前,切忌發佈。
Electron 雖然自己自帶 audoUpdater 模塊,但做爲框架的使用者來講,筆者很難說它作得優秀,由於須要配置的內容相較於其餘功能來講略加繁瑣。所以這裏推薦使用 electron-updater
。下面的代碼至關於一個純粹的更新功能的封裝,使用成本很是簡單,只需根據 electron-builder wiki 的說明配置好 publisher 便可實現更新功能:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253複製代碼 |
// updater.jsconst { dialog } = require('electron')const { autoUpdater } = require('electron-updater')let updater// 禁用自動下載,給用戶選擇餘地autoUpdater.autoDownload = falseautoUpdater.on('error', (event, error) => { dialog.showErrorBox('Error: ', error)})autoUpdater.on('update-available', () => { dialog.showMessageBox({ type: 'info', title: 'Found Updates', message: 'Found updates, do you want update now?', buttons: ['Sure', 'No'] }, (buttonIndex) => { if (buttonIndex === 0) { autoUpdater.downloadUpdate() } else { updater.enabled = true updater = null } })})autoUpdater.on('update-not-available', () => { dialog.showMessageBox({ title: 'No Updates', message: 'Current version is up-to-date.' }) updater.enabled = true updater = null})// 下載完成時,提醒用戶autoUpdater.on('update-downloaded', () => { dialog.showMessageBox({ title: 'Install Updates', message: 'Updates downloaded, application will be quit for update...' }, () => { autoUpdater.quitAndInstall() })})// 將這個回調輸出給更新功能所在的菜單項的 click 回調function checkForUpdates (menuItem, focusedWindow, event) { updater = menuItem updater.enabled = false autoUpdater.checkForUpdates()}module.exports.checkForUpdates = checkForUpdates複製代碼 |
期初的 Electron 打包是一個比較惱人的問題,由於可用的工具其實很少,electron-packager
是一個很原始的打包工具,雖然具有打包的功能,可是其提供以開發 API 爲藍本的入口使得構建還須要額外編寫腳本進行,而它其實只具有將應用進行打包編譯的功能,這與最終發佈的 Installer 還有一步之遙,因此使用 electron-packager 是很是消耗開發成本的一件事情。好在有一個取而代之的工具 electron-builder
。它的好處在本文前面的部分也已經屢次說起,它不只擁有方便的配置 protocol 的功能、內置的 Auto Update、簡單的配置 package.json 便能完成整個打包工做,用戶體驗是至關優秀的。
代碼簽名對於發佈做爲正式商業產品應用來講是很是重要的。現在的 electron-builder 對於代碼簽名已經作得至關友好,對於 macOS 來講,它可以自動獲取系統中 Keychain 中的開發者證書,自動對代碼進行簽名,而 Windows 也能夠經過配置一個 .p12
證書來達到簽名的目的。
所以,到目前爲止,代碼的簽名成本已經很是低,只需購買好證書,基本上沒有什麼煩心的事情,惟一一個值得注意的事情是:若是要分發 macOS 上的應用,那麼構建平臺將只有 macOS 是被推薦的,由於它是惟一一個可以同時構建 macOS/Linux/Windows 三平臺應用的平臺。可是,若是使用 CSC_LINK 將會出現衝突,由於 CSC_LINK 已被用於 macOS 平臺的簽名,所以額外在 package.json 中配置 Windows 平臺的證書。
因爲證書最終不會被分發,能夠在簽名時使用一個移除密碼的證書;亦或者對兩個平臺的證書使用相同的密碼,方便最終的簽名和打包。
開發工具其實與 Electron 自己並不無關係,由於若是咱們編寫的應用是 SPA,那麼 Electron 並不能左右咱們的工具選擇。但做爲一篇完整的實踐總結,這裏簡單說起一些開發工具以做爲筆者的我的愛好進行推薦。
開發自己實際上是一件苦力活,咱們有時候確實是須要一個工具來幫助咱們提高代碼質量的,因此正如各位所熟悉的工具 ESLint,可能在任什麼時候候都是必不可少的。而對於最終整個應用的構建過程,筆者則更傾向於 webpack,雖然 gulp 也是不差。
像 Electron 這種涉及 UI 的應用,實施 UI Testing 實際上是一件不太順暢的事情。對於 main process 的核心邏輯相關的測試咱們大可以使用本身熟悉的測試框架編寫,可是對於 Electron 窗口和窗口之間的管理邏輯,直接下手是不太好作。好在有一個庫幫咱們解決了 UI 測試的問題,那就是 Spectron,它能夠與任何咱們喜歡的測試框架相配合,例如 mocha。Spectron 同時也是官方推薦的測試庫,起使用很是簡單,具體內容能夠參考下面的連接,這裏再也不贅述。
相關連接: electron.atom.io/spectron/
編輯器的選擇雖然是蘿蔔白菜的問題,可是筆者選擇的是 VSCode ,不得不說他從速度和功能上戰勝了 Atom,同時還有微軟在背後撐腰,Electron 的官方文檔也有一篇關於使用 VSCode 來分別配置調試 main process 和 renderer process 的過程,鑑於其內容比較簡單,這裏只說起此內容,讀者能夠進一步閱讀官方文檔,瞭解如何配置調試文件,這裏便也再也不贅述。
相關連接: electron.atom.io/docs/tutori…
架構是進化而來的。在項目之初過分的設計架構實際上是一件很是不合理的事情,筆者在實踐 Electron 之初就將 Electron 與業務邏輯、Web 頁面混合得稀爛,webContent.send()
和 ipcRenderer.send
翻來覆去的發,最終致使邏輯混成一團麻,難以維護。
Electron 的 IPC 機制是一把很是鋒利的雙刃劍,利用的當很是便於實現一些功能,也能維護得很好。前面的 renderer 端建立 Menu 就是一個典型的例子:在 Renderer端建立的 Menu,具有調用 ipcRenderer.send(message, args)
的能力,這樣全部的 MenuItem 的 click 功能都可以交給 ipcMain.on(message, args)
來實現。
最終,筆者實踐得出的一個便於維護的 Electron 應用架構應該以下圖所示:
固然也可能進化爲更好的架構, 若是你有更好的架構設計願意分享, 歡迎在下方留言
正如以前提到的,Electron 若是和業務邏輯與頁面代碼混成一坨,將致使往後的維護難度增大,一方面緣由在於 Electron 不斷在發展,也必將面臨接口升級的問題,另外一方面,咱們也很難保證 Electron 就能久經不衰,說不定明天就會有新的框架發佈,替代 Electron。若是代碼到處混雜着 Electron 的內容,整套應用所有重寫的成本實際上是很大的。一個比較簡單的作法就是對 Electron 再作一層自定義的封裝,將用到的 Electron 統一封裝到一個腳本以內。例如:
123456789101112131415161718192021222324252627282930複製代碼 |
// libmain.js'use strict'const electron = require('electron')const app = electron.appconst shell = electron.shellconst BrowserWindow = electron.BrowserWindowclass lib { static exit (cleanCookie) { if (cleanCookie) { BrowserWindow.getAllWindows().forEach((win) => { win.webContents.session.clearStorageData({storages: ['cookies']}, () => { console.log('Successfully eliminate cookies') }) }) } app.quit() app.exit(0) } static externalOpenURL (url) { shell.openExternal(url) }}// main.js...lib.exit(true)...lib.externalOpenURL(...)複製代碼 |
在上面的代碼中,咱們其實實現了應用的退出邏輯,在每次退出應用以前,都將瀏覽器的 cookie 清除,shell.openExternal
也是如此。這樣作的一個好處在於咱們在須要 Electron 時,只需統一的調用 libmain.js 便可,無需再任何須要 electron 的地方 require electron,減小 Electron 和應用之間的耦合度。值得一提的是,ipcMain 和 ipcRenderer 不能在同一個腳本中被引入,因此咱們好針對 renderer 進程額外再封裝另外一個腳本 librenderer.js
。
IPC 當然好,但消息發來發去實際上是很是繞邏輯的。例如一條消息首先從 ipcRenderer 發出,而後 ipcMain作出反應,而後經過 sender.send 發回,由另外一個 ipcRenderer 再作響應。這樣的邏輯方式是很是累人的。前面咱們對 Electron 進行解耦和的過程當中其實能夠看到,當對 Electron 解耦以後,消息自己從何處發出實際上是能夠被淡忘的,這樣便迴歸到了咱們常見的 MVC/MVVC 結構的實踐。Electron 自己的邏輯充當 Controller,而 Web 則充當 Model。在這個過程當中,因爲雙向綁定的存在,咱們竟可能減小IPC的雙邊通訊邏輯,讓 Render而 端單邊向 ipcMain 通訊,而後 Controller 來修改 Model 從而經過雙向綁定達到修改 View 的目的。
相關 pull request: github.com/electron-us…
若是你毅然決然的實現了前面提到的 protocol,即便應用代碼也成功的進行了簽名,仍然會有讓你心煩的事情,那就是(Windows平臺上)殺毒軟件的報毒。筆者估計這也是不少獨立開發者不肯意發行Windows版本應用的緣由。
這是一個很是噁心的事情,若是你應用的目標用戶是小白,那麼極可能會由於殺軟的誤報而不考慮安裝你的應用,這裏有一個叫作 virustotal 的網站可以幫你檢測你打包後的應用會被哪些殺毒軟件處理。
固然,報毒並不只僅是你使用了 protocol 這麼簡單,早期的 Electron 組件中包含了大量的容易被誤報的內容,但如今這一問題少了不少,對此咱們做爲 Electron 的用戶基本上沒有別的辦法,只能等待 Electron 的影響力繼續擴大,使殺毒軟件將其完全列入白名單。
相關 issue: github.com/atom/atom/i…
Electron 應用體積動輒一百多兆的體積從他發佈的第一天起到如今一直被人詬病,而其體積之因此如此之大的緣由在於 Electron 爲其跨平臺的支持,在內部打包了整個 V8 引擎,至關於一個功能完整的瀏覽器。
曾經 Slack 做爲仰仗 Electron 『大廠』, macOS 版本曾神奇的只有 20M 左右。但事實上那個時候的 Slack macOS 版本並不依賴 Electron,而是 MacGap(因此你能夠看到如今的 Slack 全面轉向 Electron 後也毫無疑問的成爲了百兆應用的大戶)。若是你打算使用 MapGap 來優化 Mac 版本的體積,請打住。簡單閱讀一下 MacGap 的文檔就會發現,MacGap 的發展速度甚至趕不上 Electron,不少需求在 MacGap 上甚至沒法實現,例如 preload 這樣的功能就不具有。
因此不管你的 Electron 應用多麼簡單,都至少擁有超過 120M 的體積。這是至關不友好的。對於這個問題,筆者實踐中找到了三種相對妥協,卻能很好的解決問題的方案:
咱們知道 node 程序實際上是將依賴庫整個下載到了 node_modules
中,這也就包括一些 example 和 docs 和 test,而在 electron 應用被打包的過程當中,這些依賴其實也是被耿直的打包進了應用之中。這也就無形之中增長了 Electron 應用的體積。
而使用 yarn clean
能夠清除這些內容,從而必定程度上減小應用的體積。
Electron 應用自己的 bundle 確實高達 120M,但其壓縮後的體積可以變得很小,尤爲是 Windows 平臺上的安裝程序甚至可以被壓縮到 33M 左右,而 macOS 和 Linux 的打包體積也將被壓縮爲 40M 左右。這實際上是一個至關可觀的體積了,若是配合下面提到的第三點方法,那麼幾乎向用戶隱瞞了應用自己體積巨大的事實。正如筆者在前面提到的,推薦使用 electron-builder。
關於這一點內容,咱們要從 Electron 打包應用的結構談起。以 macOS 爲例,Electron 應用最終被打包成了以下的結構:
123456789101112131415161718192021222324複製代碼 |
ElectronApp.app└── Contents ├── Frameworks │ ├── Changkun\ Blog\ Helper\ EH.app │ ├── Changkun\ Blog\ Helper\ NP.app │ ├── Changkun\ Blog\ Helper.app │ ├── Electron\ Framework.framework │ ├── Mantle.framework │ ├── ReactiveCocoa.framework │ └── Squirrel.framework ├── Info.plist ├── MacOS │ └── Changkun\ Blog ├── PkgInfo ├── Resources │ ├── Changkun\ Blog.icns │ ├── app-update.yml │ ├── app.asar │ ├── electron.asar │ ├── en.lproj │ ├── zh_CN.lproj │ └── zh_TW.lproj └── _CodeSignature └── CodeResources複製代碼 |
在這個結構中,咱們本身的核心代碼,實際上是被完整的打包進了 Contents/Frameworks/Resources/app.asar
中,其餘內容則都是 electron 自身的依賴。換句話說,咱們只要更新了 app.asar
這個文件,也就至關於更新了整個應用。咱們再來看看這個文件的大小:
1複製代碼 |
-rw-r--r-- 1 changkun admin 2.9M Mar 16 17:07 app.asar複製代碼 |
這將是一個很是可觀的下載量,配合第二點,用戶第一次下載了一個不超過 50M 的應用安裝程序,每次更新應用時,下載的內容也很是之少。固然,實現這一功能也並不複雜,咱們只須要和本身的服務器進行通訊,而後下載這個文件退出應用進行替換便可。
相關 issue: github.com/electron/el…
electron-builder
出現過一個 bug : 在 productName 字段中,若是其包含了一些 UTF-8 字符,那麼最終打包後的 dmg 包的應用 icon 的位置將出現錯誤。之因此以此爲例,是由於剛好這個問題筆者前幾日找時間跟蹤修復的。
這個問題出現的根源能夠追溯到 electron-builder
早期對 node-appdmg
的依賴。在 macOS 10.12 更新以前,用於打包 macOS 平臺的 dmg 依賴 node-appdmg
不存在任何問題,並且其自己的實現邏輯也是根據蘋果自身的文檔描述正確無誤完成的。可是莫名其妙的是在 macOS Sierra 上就是沒法顯示背景圖。後來才發現是蘋果系統自身的 bug,這個問題也在 10.12.3 以後的系統版本上被修復了。那麼從 10.12.0 到 10.12.3 這麼長的週期間隙中,electron-builder 的維護者也不能閒着,爲了擺脫對 node-appdmg
的依賴,electron-builder
的做者使用 perl 黑魔法解決了這個問題,從那之後 electron-builder
再也不依賴 node-appdmg
。
然而這也就埋下了隱患。在去年十二月份的一次 bug
維護中,electron-builder
爲了修復使其可以構建時自定義路徑而更新了這個 perl 腳本,並將其中關鍵的 UTF-8 解碼交給了 node 進行處理,在 packages/electron-builder/src/targets/dmg.ts
這個文件中很是『暴力』的讓 node 讀取 perl
腳本自己,而後將 $ENTRIES
進行替換,在運行時中執行整個 perl 腳本。但卻忽略了 perl 腳本自己與 python 2 同樣,若是不指定 utf-8 編碼,那麼執行將出錯。
所以修復這個 bug 的方法也很是簡單,只須要在 perl 腳本中增長一行代碼:
1複製代碼 |
use utf8;複製代碼 |
相關 issue:
Electron 是一個仍在發展的的框架(惋惜不能說它目前是高速發展的),甚至已經能夠在 Electron 的官網上看到已經開始有 2.0 的 breaking changes 計劃了。經過筆者我的的實踐,Electron 存在各式各樣的缺陷和問題,可是這些缺陷的背後其實有着高效的開發效率和的跨全平臺的優點撐腰。儘管咱們深知 native app 是『正道』,但『一套代碼隨處運行』的特色,在需求層出、高速迭代產品的今天,選擇 Electron 是毋庸置疑的。
若是你對筆者的部分實踐感興趣,可參考筆者使用 Electron 建立的博客桌面客戶端,能夠算做是一個很是輕量的 Electron 模板:
這個客戶端的代碼涵蓋了大部分的實踐內容(部分討論的內容出於各類各樣不可描述的緣由而沒有在代碼中體現,感興趣的讀者能夠根據本人的描述自行嘗試)。
而對於 Electron 自己,有幾個很是好的 repo 是值得深刻研究的: