Electron 在 DevTools 中的探索与实践

阅读数:7837 2019 年 6 月 1 日 08:00

Electron在DevTools中的探索与实践

引言

目前,主流的桌面应用开发方法有几种,一是使用纯 Native 技术栈进行开发,比如说 Windows 上使用 C++,Mac 上使用 Objective-C。这种方式能够实现最好的性能,但是开发成本比较高,周期也长,而且需要分别开发 Windows 和 Mac 版本,人员投入比较大。

二是基于 Qt 等 Native 框架进行开发,这种方案可以获得接近 Native 的性能体验,但是学习成本仍然较高,而且界面开发效率不高,没有办法满足快速迭代的需求。

第三种则是以 Electron 为代表的,允许我们使用 web 技术开发桌面应用的框架。这种方案背后是整个 web 技术体系,资源丰富,跨平台性好,开发效率高,甚至于我们可以使用一套代码逻辑同时开发桌面应用和 web 应用,特别适合企业用来开发一些偏业务型的桌面应用。而且,Electron 由 GitHub 维护,社区活跃度高,像我们熟悉的 VS Code,Skype 都是基于 Electron 构建的。

因此,如果不是要开发对性能要求很高的桌面应用,团队中 web 开发人员又相对充足,Electron 是一个比较合适的选择。

本文将介绍 Electron、开发过程中可能会遇到的问题和场景,以及 Electron 在 DevTools 中的实践,希望可以为想要开发 Electron 应用的小伙伴们提供一点参考或者思路。

一、初识 Electron

根据官方文档,基于 Electron,我们可以使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用。目前 Electron 支持的平台有 Mac, Windows 和 Linux。

1.1 Quick Start

先来看一下如何使用 Electron 快速构建一个桌面应用,目录结构如下图所示:

Electron在DevTools中的探索与实践

其中,index.html 就是我们平时开发的 web 页面,负责界面展示。main.js 则是整个 Electron 应用的入口文件,如下:

Electron在DevTools中的探索与实践

main.js 中首先引入了 app 和 BrowserWindow 模块,app 模块主要负责应用级别的事情,包括应用的生命周期。可以看到,Electron 初始化完成之后,会触发 app ready,这时就可以开始做自己的事情了,比如说在这里我们创建了一个窗口,然后加载了 index.html。如此,一个 Electron 应用就可以运行了,Easy Peasy。

1.2 Electron 工作机制

之所以可以使用 web 技术构建桌面应用,其实是因为 Electron 做了一个整合,它集成了 Chromium 和 Node.js,同时提供了一系列可以操作原生 GUI 的 API。

而能够做这个整合,首先得益于 Chromium 和 Node.js 都是基于 v8 引擎来执行 js 的,所以给了一种可能,他们是可以一起工作的。

但是有一个问题,Chromium 和 Node.js 的事件循环机制不同。我们知道,Node.js 是基于 libuv 的,Chromium 也有一套自己的事件循环方式,要让他们一起工作,就必须整合这两个事件循环机制。

Electron在DevTools中的探索与实践

如上图所示,Electron 采用了这样一种方式,它起了一个新的线程轮询 libuv 中的 backend fd,从而监听 Node.js 中的事件,一旦发现有新的事件发生,就会立即把它 post 到 Chromium 的事件循环中,唤醒主线程处理这个事件。

1.3 Electron 应用架构

Chromium 是多进程模式,每个 Tab 都是一个独立的进程在运行,从而确保了它的稳定性。Electron 延续了多进程的模式,每个窗口对应一个独立的渲染进程,里面运行的就是 web 页面。渲染进程统一由主进程管理,如下图所示。

Electron在DevTools中的探索与实践

1.4 通信

在 Electron 中,应用级别的活动以及原生 GUI 模块只能在主进程中运行,渲染进程则主要负责界面展示。这时候就需要解决主进程和渲染进程之间的通信问题。

IPC(Inter-Process Communication)

Electron 提供了两个模块,ipcMain 和 ipcRenderer,它们都是 Node.js EventEmitter 的实例,使用哪个取决于所在的进程。

主进程:

Electron在DevTools中的探索与实践

渲染进程:

Electron在DevTools中的探索与实践

remote 模块

remote 模块允许在渲染进程中直接调用主进程的模块和方法。从底层实现的角度,remote 其实是对 ipc 做了一层封装,它除了能帮我们避免繁琐的 ipc 消息传递,remote 和 ipc 还有一个本质的区别。

来看一个具体的例子,如下图所示,主进程的 global 上挂了一个 globalData 对象,现在想在渲染进程中获取这个对象中 test 属性的值。

主进程:

Electron在DevTools中的探索与实践

渲染进程:

Electron在DevTools中的探索与实践

来看一下 remote 是怎么取值的。如下图所示,首先,渲染进程通过 ipc 给主进程发消息说需要 global 上的 globalData 对象,主进程收到消息后,取到相应的对象,处理之后就把 globalData 相关的信息传回到渲染进程。渲染进程拿到对象之后,直接重写了它的 get 和 set 方法。因此,这时候再获取 test 值的时候,渲染进程会再次发送消息到主进程来获取。

Electron在DevTools中的探索与实践

基于这样的机制,可以看出,虽然是在两个进程中,但是完全可以把 remote 取回的对象当作是对主进程中这个对象的引用,因为我们获取到的值总是和主进程中的一致,而使用 ipc 通信,其实是对数据进行了序列化和反序列化,渲染进程拿到的对象和主进程已经没什么关系了。

二、探索 Electron

到这里,大家对 Electron 已经有一个基本的印象了。下面来看 Electron 开发过程中可能会遇到的几个问题和场景。

2.1 启动时间优化

Electron 应用创建窗口之后,由于需要初始化窗口,加载 html,js 以及各种依赖,会出现一个短暂的白屏。除了传统的,比如说延迟 js 加载等 web 性能优化的方法,在 Electron 中还可以使用一种方式,就是在 close 窗口之前缓存 index 页面,下次再打开窗口的时候直接加载缓存好的页面,这样就会提前页面渲染的时间,缩短白屏时间。

但是,优化之后也还是会有白屏出现,对于这段时间可以用一个比较 tricky 的方法,就是让窗口监听 ready-to-show 事件,等到页面完成首次绘制后,再显示窗口。这样,虽然延迟了窗口显示时间,总归不会有白屏出现了。

2.2 CPU 密集型任务处理

对于 cpu 密集型或者 long-running 的 task,我们肯定不希望它们阻塞主进程或者影响渲染进程页面的渲染,这时候就需要在其他进程中执行这些任务。通常有三种方式:

  • 使用 child_process 模块,spawn 或者 fork 一个子进程;
  • WebWorker;
  • Backgroundprocess。在 Electron 应用中,我们可以创建一个隐藏的 Browser Window 作为 background process,这种方法的优势就在于它本身就是一个渲染进程,所以可以使用 Electron 和 Node.js 提供的所有 api。

2.3 数据持久化存储

为了使应用在 offline 的情况下也可以正常运行,对于桌面应用,我们会将一些数据存储到本地,常见方式有:

  • localStorage。对于渲染进程中的数据,可以存到 localStorage 中。需要注意的是主进程是无法获取的。
  • 嵌入式数据库。我们也可以直接打包一个嵌入式数据库到应用中,比如说 SQLite,nedb,这种方式比较适合大规模数据的存储以及增删改查。
  • 对于简易的配置或者用户数据,可以使用 electron-config 等模块,将数据以 JSON 格式保存到文件中。

2.4 安全性考虑

在 Electron 应用中,web 页面是可以直接调用 Node.js api 的,这样就可以做很多事情,比如说操作文件系统,但同时也会带来安全隐患,建议大家渲染进程中禁用 NodeJS 集成。

如果需要在页面中使用 node 或者 electron 的 api,可以通过提前加载一个 preload.js 作为 bridge,这个 js 会在所有页面 js 运行前被执行。我们可以在里面做很多事情,比如说把需要的 node 方法放到 global 或者 window 中,这样页面中就没办法直接使用 node 模块,但是又可以使用需要的某些功能,如下图所示。

Electron在DevTools中的探索与实践

除此之外,还要注意,使用安全的协议,比如说 https 加载外部资源。在 Electron 应用中,可以通过监听新窗口创建和页面跳转事件,判断是否是安全跳转,加以限制。亦可以通过设置 CSP,对指定 URL 的访问进行约束。

2.5 应用体积优化

对于 Electron 应用打包,首先会使用 webpack 分别对主进程和渲染进程代码进行处理优化,和 web 应用一样。有点区别的地方是配置中主进程的 target 是 electron-main, 渲染进程的 target 是 electron-renderer。除此之外,还要对 node 做一些配置,我们是不需要 webpack 来 polyfill 或者 mocknode 的全局变量和模块的,所以设为 false。

之后,在基于 electron-builder 将应用 build 成不同平台的安装包,需要注意的是,对于 package.json,尽可能地把可以打包到 bundle 的依赖模块,从 dependencies 移到 devDependencies,因为所有 dependencies 中的模块都会被打到安装包中,会严重增大安装包体积。

三、Electron 在 NFES DevTools 中的实践

最后,分享一下 Electron 在 NFES DevTools 中的应用。

NFES 是携程新推出的一整套无线前端解决方案,适用于 pc,h5,hybrid 多个场景,同时支持服务端渲染和单页模式。

NFES DevTools 作为辅助开发平台,能够为开发人员提供稳定的,不受本地 Node.js 版本、全局模块等外部依赖干扰的开发环境,以及 NFES 项目相关的构建,调试,发布,性能监控等功能,从而帮助开发人员更好的开发和维护 NFES 应用。

Electron在DevTools中的探索与实践

先从整体应用架构的角度看一下 NFES DevTools。如上图所示,NFES DevTools 是基于 Electron 构建的。

主进程主要做了这几件事情,首先是 app 级别的活动,包括生命周期,自动更新;然后提供了一系列的 remote service,比如说窗口管理,应用级别数据管理,cookie 管理,让渲染进程可以直接调用。其次就是 Native GUI 相关的活动,像创建原生菜单,托盘图标,通知提醒等。最后,在窗口创建之前,我们在主进程中本地起了一个 node server,用来跑 web 应用。

对于渲染进程,主要是基于 React,Redux 写的,Web Worker 用于处理复杂计算,避免阻塞页面渲染。除此之外,我们还启了一个 background 进程,用来执行比如说文件监控这样的活动。

对于功能模块的实现,主要看下调试功能。对于调试,NFES DevTools 既提供开发态代码的调试,也支持生产态代码的调试。

3.1 开发态调试

开发过程中的调试主要包括以下几点:

  • 埋点数据查看
  • 性能面板,帮助开发人员在开发时期发现页面可能存在的性能问题。比如说,NFES 支持服务端渲染,所以服务端会传一些数据到客户端,但这样会增大 doc 的体积,影响页面性能。所以,我们会做一个监控,看这些数据是否真的在 render 时被使用了,如果没有我们会提醒开发人员做优化。
  • web 和 Node.js 代码调试

Electron在DevTools中的探索与实践

对于 web 和 Node.js 代码调试功能的实现,由于 Electron 自身提供的调试 webview 的 api 功能比较弱,不能满足需求,所以我们决定直接使用 Chromium 提供的能力。

相信大家应该都用过 chrome 开发者工具,其实它本质上就是一个 web app,通过 Chromium 提供的远程调试协议,开发者工具就可以和 chromium 基于 WebSocket 进行通信,如上图左边所示。

调试功能也是基于这个协议实现的,但是如果让调试界面直接和 Chromium 连接会有两个问题,首先是我们没办法完全控制调试过程,不能主动向 Chromium 发送指令;其次是,Chromium 提供的 WebSocket server 只允许一个 client 跟它连接,多个 client 就会出现链接已经被占用的情况。

为了解决这两个问题,我们在调试界面和 Chromium 之间做了一层拦截,如上图右边所示,首先起了一个 WebSocketserver,让它充当 Chromium 跟调试界面连接;然后通过接口获取到真实的 WebSocket 调试地址,起了一个 client 让它跟 Chromium 链接。通过这种方式,就可以拦截掉所有调试界面和 Chromium 之间的 WebSocket 通信,之后做一个转发就可以了。

3.2 生产态调试

生产态调试功能是为了帮助开发人员更方便地调试线上代码。通常,调试线上代码会选择使用 fiddler,将线上文件代理到本地来进行调试,如下图所示。NFES DevTools 也支持这种方式,但是这里想跟大家聊一下另一种调试方法。

Electron在DevTools中的探索与实践

如下图所示,开发人员在发布 NFES 应用的时候可以选择同步生成一个生产态调试环境,这个调试环境和发布到线上的是一致的,但是多了 sourcemap。

当开发人员需要调试线上的代码的时候,可以开启代理功能,开发人员设置好浏览器代理后,我们会拦截浏览器中的 http/https 请求,把其中与 NFES 应用相关的请求代理到生产态调试环境中,对请求头,响应头,返回值作出相关处理后再返回给客户端,这样开发人员就可以方便的使用 sourcemap 调试线上代码。

Electron在DevTools中的探索与实践

代理功能的实现是在 background 进程中,我们基于 Node.js 搭建了代理服务器,并将拦截到的请求数据存储在 nedb 数据库中,因为请求量可能比较大,并且需要根据请求状态的变化对数据进行更新。

数据库插入或者更新数据之后会通知 WebSocket 服务器,实时发送数据到渲染进程,然后在 Web Worker 中计算好需要展示 / 更新的节点信息之后,重新渲染 http 请求列表。

四、结语

本文简单介绍了 Electron,由于它整合了 Chromium 和 Node.js,所以基于 Electron,可以使用 web 技术开发跨平台的桌面应用。我们也了解了 Electron 的工作机制,以及在开发过程中可能会遇到的白屏,多进程,数据持久化,安全性等问题 / 场景。

另外也分享了 Electron 在 NFES DevTools 中的实践,包括对 Electron,Chromium,Node 能力的应用,希望可以为想要开发 Electron 应用的小伙伴们有一点启发。

作者简介

隋丰蔚,携程无线平台研发部前端工程师,现负责开发者工具 NFES Developer Tools 的设计与研发。

本文转载自公众号携程技术中心(ID:ctriptech)

原文链接

https://mp.weixin.qq.com/s/XSn8jYlE85bcIa_NmUgqrA

评论

发布