阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

Electron 在 DevTools 中的探索与实践

  • 2019-06-01
  • 本文字数:5191 字

    阅读完需:约 17 分钟

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 快速构建一个桌面应用,目录结构如下图所示:



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



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 采用了这样一种方式,它起了一个新的线程轮询 libuv 中的 backend fd,从而监听 Node.js 中的事件,一旦发现有新的事件发生,就会立即把它 post 到 Chromium 的事件循环中,唤醒主线程处理这个事件。

1.3 Electron 应用架构

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


1.4 通信

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


IPC(Inter-Process Communication)


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


主进程:



渲染进程:



remote 模块


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


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


主进程:



渲染进程:



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



基于这样的机制,可以看出,虽然是在两个进程中,但是完全可以把 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 模块,但是又可以使用需要的某些功能,如下图所示。



除此之外,还要注意,使用安全的协议,比如说 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 应用。



先从整体应用架构的角度看一下 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 代码调试



对于 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 也支持这种方式,但是这里想跟大家聊一下另一种调试方法。



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


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



代理功能的实现是在 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


2019-06-01 08:0010666

评论

发布
暂无评论
发现更多内容

开发者有话说 | 一个普通人的前端职业成长之路

范文杰

个人成长

用户组角色绑定(原生element-plus-ui表格)

青柚1943

Element Plus Vue3 Typescript

数字化转型新抓手:一看就懂的《企业应用现代化行动指南》(附下载)

York

容器 微服务 云原生 应用现代化

SSM整合(功能模块的开发)

十八岁讨厌编程

Java ssm 后端开发 9月月更

面试突击85:为什么事务@Transactional会失效?

王磊

Java 面试

概述构建应用智能运维系统的核心能力

阿泽🧸

智能运维 9月月更

40 岁程序员会有哪些肺腑之言?这篇文章告诉你

宇宙之一粟

学习 程序员 读书感悟 9月月更

C++学习---cstdio的源码学习分析04-创建临时文件函数tmpfile

桑榆

c++ 源码阅读 9月月更

阿里云PolarDB-X 荣获“2022 OSCAR 尖峰开源项目及开源社区”奖

阿里云数据库开源

阿里云 分布式数据库 开源数据库 PolarDB-X 可信开源

ESP32-C3入门教程 基础篇(四、I2C总线 — 与SHT21温湿度传感器通讯)

矜辰所致

I2C I2C协议 ESP32-C3 9月月更

4 分钟优化 Fetch 函数写法~

掘金安东尼

前端 9月月更

开发者有话说|情分 or 本分

卷卷龙

个人成长 职场 PUA

2022-09-20:以下go语言代码输出什么?A:8 8;B:8 16;C:16 16;D:16 8。 package main import ( “unsafe“ “fmt“ )

福大大架构师每日一题

golang 福大大 选择题

通过爬虫爬取一些图片

吉师职业混子

9月月更

RAID(独立冗余磁盘阵列)

阿柠xn

Linux 运维 操作系统 raid 9月月更

【云原生 | 从零开始学Kubernetes】一、kubernetes到底是个啥

泡泡

云计算 云原生 k8s 9月月更

开发者有话说|时间过得真快,我也是一个“奔三”的人了

武师叔

个人成长

【数据结构】五分钟带你了解及自定义有向图

迷彩

数据结构 算法 无向图 9月月更 有向图

跟着卷卷龙一起学Camera--CCM

卷卷龙

ISP 9月月更

[SSM]SSM整合①(整合配置)

十八岁讨厌编程

Java 后端开发 9月月更

SSM整合(接口测试)

十八岁讨厌编程

Java SSM框架 后端开发 9月月更

ESP32-C3入门教程 基础篇(三、UART模块 — 与Enocean无线模块串口通信)

矜辰所致

ESP32-C3 9月月更 UART

监控系统的阶段建设

穿过生命散发芬芳

监控系统 9月月更

kube-prometheus 监控系统使用与总结

CTO技术共享

数据平台发展史-从数据仓库数据湖到数据湖仓

明哥的IT随笔

hadoop spark 数据仓库 数据湖 湖仓一体

干货分享|使用 Istio 实现灰度发布

北京好雨科技有限公司

istio #Kubernetes#

Ubuntu服务器上部署Kubernetes集群

CTO技术共享

springboot搭建基于minio的高性能存储

CTO技术共享

Web3.0杂谈-#001(47/100)

hackstoic

Web3.0

《简单记个笔记》之表单标签加CSS选择器

吉师职业混子

9月月更

EMQ走进亚马逊云科技:携手云端,共筑「面向未来」的IoT基础设施底座

EMQ映云科技

物联网 IoT 解决方案 亚马逊云科技 9月月更

Electron在DevTools中的探索与实践_语言 & 开发_隋丰蔚_InfoQ精选文章