Electron深刻填坑之路

思惟导图node

前言:python

Electron 从最初发布到如今已经维护很长一段时间了,可是去年才开始慢慢升温。笔者我的刚好也有一些 Electron 的实践经验,本来这篇总结是大约半年前开始起草的,可是因为一些我的缘由拖到如今才算是正式完稿,如今发出来供有意实践 Electron 的开发者借鉴,避免一些本人在实践中踩过的坑。此外,笔者的水平有限,不免出现错误,若是读者在阅读本文过程当中发现原则性的错误,请务必指出,以避免相关内容祸害他人。jquery

本文的实践基于 Electron 1.xlinux

建立基本应用

问题: 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

一些常见需求

在 Renderer 端建立菜单

在 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,而后响应所需的操做,见这里

但事实上这个方法是基于 onlineoffline 事件的,换句话说这个方法只能检测到当系统网络链接被切断物理链接后的状态变化,没法检测网络自己。

这个问题能够参考下面的 issue,但其实现思路就是向苹果的 hotspot detect 页面发起请求,当未超时状态下有 Success 返回时,便说明网络状态正常。在实际应用编写过程当中,咱们也并不须要为了这样一个简单的功能而引入框架,只需定时向服务器发起任意一个可以判断网络状态的请求便可,与心跳链接异曲同工。

相关 issue: github.com/electron/el…

preload 脚本的执行阶段

在前面建立基本应用中咱们已经谈到了关于 preload 脚本用来引入 jQeury 的使用,事实上咱们能够用 preload 作更多的事情。preload 脚本会在整个页面开始加载以前被执行,因此若是咱们直接执行一些当整个 DOM 加载完成才能被执行的操做,是一定会失效的,所以这样的两个事件是很是有用的:DOMNodeInsertedDOMContentLoaded

为此,咱们能够把 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检查问题

所谓的第三方 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启动

从 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

交互的反馈

在 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 并不能左右咱们的工具选择。但做为一篇完整的实践总结,这里简单说起一些开发工具以做为笔者的我的爱好进行推荐。

Dev

开发自己实际上是一件苦力活,咱们有时候确实是须要一个工具来帮助咱们提高代码质量的,因此正如各位所熟悉的工具 ESLint,可能在任什么时候候都是必不可少的。而对于最终整个应用的构建过程,笔者则更倾向于 webpack,虽然 gulp 也是不差。

Test

像 Electron 这种涉及 UI 的应用,实施 UI Testing 实际上是一件不太顺畅的事情。对于 main process 的核心逻辑相关的测试咱们大可以使用本身熟悉的测试框架编写,可是对于 Electron 窗口和窗口之间的管理逻辑,直接下手是不太好作。好在有一个库帮咱们解决了 UI 测试的问题,那就是 Spectron,它能够与任何咱们喜欢的测试框架相配合,例如 mocha。Spectron 同时也是官方推荐的测试库,起使用很是简单,具体内容能够参考下面的连接,这里再也不赘述。

相关连接: electron.atom.io/spectron/

Debug

编辑器的选择虽然是萝卜白菜的问题,可是笔者选择的是 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 应用架构应该以下图所示:

固然也可能进化为更好的架构, 若是你有更好的架构设计愿意分享, 欢迎在下方留言

1. 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

2. IPC混合式 MVC/MVVC

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 的体积。这是至关不友好的。对于这个问题,笔者实践中找到了三种相对妥协,却能很好的解决问题的方案:

1. 使用 yarn clean

咱们知道 node 程序实际上是将依赖库整个下载到了 node_modules 中,这也就包括一些 example 和 docs 和 test,而在 electron 应用被打包的过程当中,这些依赖其实也是被耿直的打包进了应用之中。这也就无形之中增长了 Electron 应用的体积。

而使用 yarn clean 能够清除这些内容,从而必定程度上减小应用的体积。

2. 将应用程序打包后再分发

Electron 应用自己的 bundle 确实高达 120M,但其压缩后的体积可以变得很小,尤为是 Windows 平台上的安装程序甚至可以被压缩到 33M 左右,而 macOS 和 Linux 的打包体积也将被压缩为 40M 左右。这实际上是一个至关可观的体积了,若是配合下面提到的第三点方法,那么几乎向用户隐瞒了应用自己体积巨大的事实。正如笔者在前面提到的,推荐使用 electron-builder。

3. 定制应用的更新功能

关于这一点内容,咱们要从 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…

案例:一个 bug 的生与死

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:

  1. github.com/electron-us…
  2. github.com/LinusU/node…
  3. github.com/electron-us…

结论

Electron 是一个仍在发展的的框架(惋惜不能说它目前是高速发展的),甚至已经能够在 Electron 的官网上看到已经开始有 2.0 的 breaking changes 计划了。经过笔者我的的实践,Electron 存在各式各样的缺陷和问题,可是这些缺陷的背后其实有着高效的开发效率和的跨全平台的优点撑腰。尽管咱们深知 native app 是『正道』,但『一套代码随处运行』的特色,在需求层出、高速迭代产品的今天,选择 Electron 是毋庸置疑的。

进一步的参考

若是你对笔者的部分实践感兴趣,可参考笔者使用 Electron 建立的博客桌面客户端,能够算做是一个很是轻量的 Electron 模板:

这个客户端的代码涵盖了大部分的实践内容(部分讨论的内容出于各类各样不可描述的缘由而没有在代码中体现,感兴趣的读者能够根据本人的描述自行尝试)。

而对于 Electron 自己,有几个很是好的 repo 是值得深刻研究的:

  1. Electron: github.com/electron-us…
  2. Electron Userland: github.com/electron-us…
  3. Awesome Electron: github.com/sindresorhu…
相关文章
相关标签/搜索