天天都要写次日的 todoList。有一天在写的时候忽然想到,为了让本身清楚知道本身须要作啥、作了多少、还剩多少没作,想写一个电脑端程序,在技术选型的时候就选了 Electron。javascript
本篇文章的目的不是讲解 API 如何使用,想知道这些能够直接看官方文档。本文目的旨在讲明如何技术如何选择、如何快速上手、如何调试、Electron 底层原理、工程体系方面的总结。css
1、浅谈 GUI 系统
浏览器是如何将布局数据计算为像素数据的,你能实现出原理相似的渲染器吗? 浏览器在各个平台上的文字排版渲染结果是否一致,你能解释缘由吗? 你所负责的前端应用,其渲染性能还有多大的提高空间,你能量化地证实吗? 你能设计实现出相似 RN 和小程序那样的 Hybrid 方案吗? 你能本身控制 GPU 渲染管线,实现渲染的硬件加速吗?
GUI 起源:从 1979 年乔布斯造访施乐 PARC 算起html
GUI 架构:过程化绘制(drawLine、drawRect)-> 面向对象抽象时代 -> 界面与样式分离时代 -> MVC、MVVM 时代 -> 声明式、组件式时代(Vue、React、RN、Weex、Flutter)前端
咱们能够看到不变的是:随着计算机科学技术的发展,为了实现某个效果,一流程序员或者组织不断研发各类技术框架,来提升开发效率和效果。Electron 就是这条历史长河中诞生的 PC 端技术框架之一。vue
2、 技术选型
3天时间写了个 PC 端应用程序。先看看结果吧java
为何要选 Electron 做为 pc 端开发方案?node
史前时代,以 MFC 为表明的技术栈,开发效率较低,维护成本高。 后来使用 QT 技术,特色是使用 DirectUI + 面向对象 + XML 定义 UI,适用于小型软件、性能要求、包大小、UI 复杂度叫高的需求。 再到后来,以 QT Quick 为表明的技术,特色是框架自己提供子控件,基于子控件组合来建立新的控件。相似于 ActionScript 的脚本化界面逻辑代码。 新时代主要是以 Electron 和 Cef 为 表明。特色是界面开发以 Web 技术为主,部分逻辑须要 Native 代码实现。你们都熟悉的 VS Code 就是使用 Electron 开发的。适用于 UI 变化较多、体积限制不大、开发效率高的场景。git
拿 C 系列写应用程序的体验很差,累到奔溃。再加上有 Hybrid、React Native、iOS、Vue、React 等开发经验,Electron 是不二选择。程序员
3、 Quick start
执行下面命令快速体验 Hello world,也是官方给的一个 Demo。github
git clone https://github.com/Electron/Electron-quick-start cd Electron-quick-start npm install && npm start
简单介绍下 Demo 工程,工程目录以下所示
在终端执行 npm start
执行的是 package.json 中的 scripts
节点下的 start 命令,也就是 Electron .
,.
表明执行 main.js 中的逻辑。
// Modules to control application life and create native browser window const {app, BrowserWindow} = require('Electron') const path = require('path') function createWindow () { // Create the browser window. const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') } }) // and load the index.html of the app. mainWindow.loadFile('index.html') // Open the DevTools. mainWindow.webContents.openDevTools() } // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.whenReady().then(createWindow) // Quit when all windows are closed. app.on('window-all-closed', function () { // On macOS it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== 'darwin') app.quit() }) app.on('activate', function () { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) createWindow() }) // In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here.
写过 Vue、React、Native 的人看代码很容易。好比应用程序的生命周期钩子函数对开发者很重要,也是一个标准的作法,根据需求在钩子函数里面作相应的视图建立、初始化、销毁对象等等。好比 Electron 中的 activate
、window-all-closed
等。
app 对象在 whenReady
的时候执行 createWindow
方法。内部建立了一个 BrowserWindow
对象,指定了大小和功能设置。
-
webPreferences:Object (可选) - 网页功能的设置。
- preload: String (可选) - 在页面运行其余脚本以前预先加载指定的脚本。不管页面是否集成 Node, 此脚本均可以访问全部 Node API 脚本路径为文件的绝对路径。 当 node
integration
关闭时, 预加载的脚本将从全局范围从新引入 node 的全局引用标志。
- preload: String (可选) - 在页面运行其余脚本以前预先加载指定的脚本。不管页面是否集成 Node, 此脚本均可以访问全部 Node API 脚本路径为文件的绝对路径。 当 node
mainWindow.loadFile('index.html')
加载了同级目录下的 index.html 文件。也能够加载服务器资源(部署好的网页),好比 win.loadURL('https://github.com/FantasticLBP')
接下去看看 preload.js
// All of the Node.js APIs are available in the preload process. // It has the same sandbox as a Chrome extension. window.addEventListener('DOMContentLoaded', () => { const replaceText = (selector, text) => { const element = document.getElementById(selector) if (element) element.innerText = text } console.table(process) console.info(process.versions) for (const type of ['chrome', 'node', 'Electron']) { replaceText(`${type}-version`, process.versions[type]) } })
在页面运行其余脚本以前预先加载指定的脚本,不管页面是否集成 Node, 此脚本均可以访问全部 Node API 脚本路径为文件的绝对路径。Demo 中的逻辑很简单,就是读取 process.versions
对象中的 node、chrome、Electron 的版本信息并展现出来。
index.html
中的内容就是主页面显示的内容。通常不会直接写 html、css、js,都会根据技术背景选择前端框架,好比 Vue、React 等,或者模版引擎 ejs 等。
4、 实现原理
Electron 分为渲染进程和主进程。和 Native 中的概念不同的是 Electron 中主进程只有一个,渲染进程(也就是 UI 进程) 有多个。主进程在后台运行,每次打开一个界面,会新开一个新的渲染进程。
- 渲染进程: 用户看到的 web 界面就是由渲染进程绘制出来的,包括 html、css、js。
- 主进程:Electron 运行 package.json 中的 main.js 脚本的进程被称为主进程。在主进程中运行的脚本经过建立 web 页面来展现用户界面。一个 Electron 应用程序老是只有一个主进程。
1. Chromium 架构
浏览器分为单进程和多进程架构。下面先讲讲 Chrome 为表明的浏览器过去和将来。
1.1 单进程浏览器
单进程浏览器指的是浏览器的全部功能模块都是运行在同一个进程里的,这些模块包括网络、插件、Javascript 运行环境、渲染引擎和页面等。如此复杂的功能都在一个进程内运行,因此致使浏览器出现不稳定、不安全、不流畅等问题。
早在2007年以前,市面上的浏览器都是单进程架构。
-
问题1: 不稳定
早期浏览器须要借助插件来实现相似 Web 视频、Web 游戏等各类“强大”的功能。但插件每每是最容易出现问题的模块。由于运行在浏览器进程中,因此一个插件的意外奔溃到致使整个浏览器到的奔溃。
除了插件以外,渲染引擎模块也是不稳定的。一般一些复杂的 Javascript 代码就有可能致使渲染引擎模块的奔溃。和插件同样,渲染引擎的奔溃会致使整个浏览器奔溃。
-
问题2: 不流畅
从单进程浏览器架构图看出,全部页面的渲染模块、Javascript 执行环境、插件都是在一个线程中执行的。这意味着同一时刻只有一个模块能够执行。
function execUtilCrash() { while (true) { console.log("Stay hungry, stay foolish."); } } execUtilCrash();
在单进程浏览器架构下,该代码在执行的时候会独占线程,致使其余运行在该线程中的模块没机会执行,浏览器中的全部页面都运行在该线程中,因此页面都没机会去执行任务,表现为整个浏览器失去响应,也就是卡顿。
脚本、插件 会让单进程浏览器变卡顿外,页面的内存泄露也会致使浏览器卡顿。一般浏览器内核是很是复杂的,运行一个复杂的页面再关闭页面,会存在内存不能彻底回收的状况,这样致使的问题是随着使用时间的变长,内存泄漏问题越严重,内存占用越高,可用内存愈来愈少,浏览器会变得愈来愈慢。
-
问题3: 不安全
通常浏览器插件都是用 C/C++ 编写的,经过插件就能够获取到较多的操做系统资源,当你在页面上运行一个插件的时候,也就意味着这个插件能“彻底”控制你的电脑。若是是恶意插件,那它就能够作一些窃取帐号密码等,引起安全问题
1.2 早期多进程架构浏览器
上图2008年 Chrome 发布时的进程架构图。能够看出 Chrome 的页面是运行在单独的渲染进程中,同时页面的插件也是运行在单独的插件进行中的,进程之间经过 IPC 进行通讯。
解决了不稳定问题。因为进程之间是彼此隔离的,因此当一个页面或者插件奔溃时,受影响的仅仅是当前的页面或者插件进程,并不会影响到浏览器和其余的页面。也就是说解决了早期浏览器某个页面或者插件奔溃致使整个浏览器的奔溃,从而解决了不稳定问题。
解决了不流畅问题。 一样,Javascript 进行也是运行在渲染进程中的,因此即便当前 Javascript 阻塞了渲染进程,影响到的也只是当前的渲染页面,并不会影响到浏览器和其余页面或者插件进程(其余的页面的脚本是运行在本身的渲染进程中的)。
对于内存泄漏的解决办法更加简单。当关闭某个页面的时候,整个渲染进程就会被关闭,因此该进程所占用的内存都会被系统回收,因而轻松解决了浏览器页面的内存泄漏问题。
解决了安全问题。采用多进程架构可使用安全沙箱技术。沙箱能够当作是操做系统给浏览器一个小黑盒,黑盒内部能够执行程序,可是不能访问操做系统资源、不能访问硬盘数据,也不能在敏感位置读取任何数据,例如你的文档和桌面。Chrome 把插件进程和渲染进程使用沙箱隔离起来,这样即便在渲染进程或者浏览器进程中执行了恶意代码,恶意代码也没法突破沙箱限制去获取系统权限。
沙箱隔离起来的进程必须使用 IPC 通道才能够与浏览器内核进程通讯,通讯进程就会进行安全的检查。
沙箱设计的目的是为了让不可信的代码运行在必定的环境中,从而限制这些代码访问隔离区以外的资源。若是由于某种缘由,确实须要访问隔离区外的资源,那么就必须经过的指定的通道,这些通道会进行严格的安全检查,来判断请求的合法性。通道会采起默认拒绝的策略,通常采用封装 API 的方式来实现。
1.3 目前多进程架构浏览器
Chrome 团队不断发展,目前架构有了较新变化,最新 Chrome 架构图以下所示
最新 Chrome 浏览器包括:1个网络进程、1个浏览器进程、1个 GPU 进程、多个渲染进程、多个插件进程。
- 浏览器进程:主要负责界面显示、用户交互、子进程管理,同时提供存储功能。
- 渲染进程:核心任务是将 HTML、CSS、Javascript 转换为用户能够与之的网页,排版引擎 Blink 和 Javascript 引擎 V8 都是运行在该进程中。默认状况下,Chrome 为每一个 tab 标签页建立一个新的渲染进程。出于安全考虑,渲染进程都是运行在沙箱机制下的。
- GPU 进程:最先 Chrome 刚发布的时候是没有 GPU 进程的,而 GPU 的使用初衷是实现 css 3D 效果。随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器广泛需求。最后 Chrome 多进程架构中也引入了 GPU 进程。
- 网络进程:主要负责页面的网络资源请求加载。早期是做为一个模块运行在浏览器进程里面的,最近才独立出来做为一个单独的进程。
- 插件进程:主要负责插件的运行。因插件代码由普通开发者书写,因此在 QA 方面可能不是那么完善,代码质量良莠不齐,插件容易奔溃,因此须要经过插件进程来隔离,以保证插件进程的奔溃不会对浏览器和页面形成影响。
因此,你会发现打开一个页面,查看进程发现有4个进程。凡事具备两面性,上面说了多进程架构带来浏览器稳定性、安全性、流畅性,可是也带来一些问题:
- 更高资源占用:每一个进程都会包含公共基础结构的副本(如 Javascript 运行环境),这意味着浏览器将会消耗更多的资源
- 更复杂的体系结构:浏览器各模块之间耦合度高、拓展性差,会致使如今的架构很难适应新需求。
Chrome 团队一直在寻求新的弹性方案,既能够解决资源占用较高问题吗,也能够解决复杂的体系架构问题。
1.4 将来面向服务的架构
2016年 Chrome 官方团队使用“面向服务的架构”(Services Oriented Architecture,简称 SOA)的思想设计了最新的 Chrome 架构。Chrome 总体架构会向现代操做系统所采用的“面向服务的架构”方向发展。
以前的各类模块会被重构成为单独的服务(Services),每一个服务均可以运行在独立的进程中,访问服务必须使用定义好的接口,经过 IPC 进行通讯。从而构建一个更内聚、低耦合、易于维护和拓展的系统。
Chrome 最终把 UI、数据库、文件、设备、网络等模块重构为基础服务。下图是 “Chrome 面向服务的架构”的进程模型图
目前 Chrome 正处于老架构向新架构的过分阶段,且比较漫长。
Chrome 提供灵活的弹性架构,在强大性能设备上会以多进程的方式运行基础服务,可是在设备资源受限的状况下,Chrome 会将不少服务整合到一个进程中,从而节省内存占用。
1.5 小实验
测试环境: MacBook Pro(macOS 10.15.3)、Chrome Version 81.0.4044.138 (Official Build) (64-bit)
操做步骤:
- 打开 Chrome 浏览器
- 在地址栏输入
https://github.com/FantasticLBP
- 点击 Chrome 浏览器右上角
...
,在下拉菜单中选择More Tools
,在对应的展开菜单中点击Task Manager
实验现象:
实验结论:
仅仅打开了 1 个页面,为何有 4 个进程?由于打开 1 个页面至少须要 1 个网络进程、1 个浏览器进程、1 个 GPU 进程以及 1 个渲染进程,共 4 个;若是打开的页面有运行插件的话,还须要再加上 1 个插件进程。
从而印证了上述观点。
1.6 特殊状况
现象:咱们在使用 Chrome 的时候仍是会出现因为单个页面卡死最终崩溃致使全部页面崩溃的状况,why?
一般状况下是一个页面使用一个进程,可是,有一种状况,叫"同一站点(same-site)",具体地讲,咱们将“同一站点”定义为根域名(例如,github.com)加上协议(例如,https:// 或者http://),还包含了该根域名下的全部子域名和不一样的端口,好比下面这三个:
https://developer.github.com https://www.github.com https://www.github.com:8080 都是属于同一站点,由于它们的协议都是 https,而根域名也都是 github.com。区别于浏览器同源策略。
Chrome 的默认策略是,每一个标签对应一个渲染进程。可是若是从一个页面打开了新页面,而新页面和当前页面属于同一站点时,那么新页面会复用父页面的渲染进程。官方把这个默认策略叫 process-per-site-instance
。
直白的讲,就是若是几个页面符合同一站点,那么他们将被分配到一个渲染进程里面去。
这种状况下,一个页面崩溃了,会致使同一站点的页面同时崩溃,由于他们使用了同一个渲染进程。
为何要让他们跑在一个进程里面呢?
由于在一个渲染进程里面,他们就会共享JS的执行环境,也就是说A页面能够直接在B页面中执行脚本。由于是同一家的站点,因此是有这个需求的
1.7 Chromium 架构
这张图是 chromium 多进程架构图。
多进程架构的浏览器解决了上述问题,至于如何解决的之后的文章会专门讲解,不是本文的主要内容。
简单描述下。
- 主进程中的
RenderProcessHost
和 render 进程中的RenderProcess
是用来处理进程间通讯的(IPC)。 - Render 进程中的 RenderView 内容基于 WebKit 排版展现出来的
- Render 进程中的
ResourceDispatcher
是用来处理资源请求的。Render 进程中若是有请求则建立一个请求 ID,转发到 IPC,由 Browser 进程中处理后返回 - Chromium 是多进程架构,包括一个主进程,多个渲染进程
对于 chromium 多进程架构感兴趣的能够点击这个连接查看更多资料-Multi-process Architecture。
2. Electron 架构
Electron 架构和 Chromium 架构相似,也是具备1个主进程和多个渲染进程。可是也有区别
- 在各个进行中暴露了 Native API ,提供了 Native 能力。
- 引入了 Node.js,因此可使用 Node 的能力
技术难点:因为 Electron 内部整合了 Chromium 和 Node.js,主线程在某个时刻只能够执行一个事件循环,可是2者的事件循环机制不同,Node.js 的事件循环基于 libuv,可是 Chromium 基于 message bump。
因此 Electron 原理的重点就是「如何整合事件循环」。2种思路
- Chromium 集成到 Node.js 中:用 libuv 实现 messagebump(Node-Webkit 就是这么干的,缺点挺多)
- Node.js 集成到 Chromium 中(Electron 所采用的方式)
后来随着 libuv 引入 backend_fd 的概念,至关因而 libuv 轮询事件的文件描述符。经过轮训 backend_fd 能够知道 libuv 的新事件。因此 Electron 采起的作法就是将 Node.js 集成到 Chromium 中。
上图描述了 Node.js 如何融入到 Chromium 中。描述下原理
- Electron 新起一个安全线程去轮训 backend_fd
- 当检测到一个新的 backend_fd,也就是一个新的 Node.js 事件以后,经过 PostTask 转发到 Chromium 的事件循环中
上述2个步骤完成了 Electron 的事件循环。
5、 如何调试
调试分为主进程调试和渲染进程调试。
1. 渲染进程调试
看到 Demo 工程中执行 npm start
以后能够看到主界面,Mac 端快捷键 comand + option + i
,唤出调试界面,相似于 chrome 下的 devtools。其实就是无头浏览器的那些东西。或者在代码里打开调试模式 mainWindow.webContents.openDevTools()
。
工程采用 Electron + Vue 技术,下面截图 Vue-devtools 很方便查看 Vue 组件层级等 Vue 相关的调试
2. 主进程调试方式
主进程调试有2种方法
方法一:利用 chrome inspect 功能进行调试
- 须要在启动的时候给
package.json
中的 scripts 节点下的 start 命令加上调试开关
--inspect=[port] // electrom --inspect=8001 yourApp
- 而后打开浏览器,在地址栏输入
chrome://inspect
- 点击
configure
,在弹出的面板中填写须要调试的端口信息 - 从新开启服务
npm start
,在 chrome inspect 面板的Target
节点中选择须要调试的页面 - 在面板中能够看到主进程执行的
main.js
。能够加断点进行调试
方法二:利用 VS Code 调试 Electron 主进程。
-
在 VS Code 的左侧菜单栏,第四个功能模块就是调试,点击调试,弹出对话框让你添加调试配置文件
launch.json
-
编辑 launch.json 的文件内容。以下
{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Debug main process", "cwd": "${workspaceRoot}", "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/Electron", "windows": { "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/Electron.cmd" }, "args": ["."], "outputCapture": "std" } ] }
-
在调试模点击绿色小三角,会运行程序,能够添加断点信息。总体界面以下所示。能够单步调试、能够暂停、鼠标移上去能够看到对象的各类信息。
3. 主进程调试之 hot reload
Electron 的渲染进程中的代码改变了,使用 Command + R 能够刷新,可是修改主进程中的代码则必须从新启动 yarn run dev
。效率低下,因此为了提高开发效率,有必要解决这个问题
Webpack 有一个 api: watch-run
,能够针对代码文件检测,有变化则 Restart
6、 软件更新方式
更新方式 | 手动更新 | 文件覆盖 | 自动更新 | 操做系统级别的应用商店 |
---|---|---|---|---|
优势 | 简单、稳定 | 下载过程快 | 稳定、快、打扰少 | 统1、稳定 |
缺点 | 过程繁琐、慢、影响使用、更新率低 | 慢、实现比较复杂、稳定性差、写文件失败 | 实现复杂 | 受应用商店局限 |
适用场景 | 低频更新、用户粘性高、做为 各类升级技术的降级方案 | 打补丁 | 高频更新软件、体验要求高 | 操做系统应用商店上架的软件 |
Electron 官方给出了解决方案 Squirrel,基于 Squirrel 框架完成的自动更新,这其实就是图上“自定更新”的类别。
6、 开发 tips及其优化手段
1. 开发 tips
-
或许会为网页添加事件代码,可是页面看到的内容是渲染进程,因此事件相关的逻辑代码应该写在 html 引入的
render.js
中。 -
在
render.js
中写 Node 代码的时候须要在main.js
初始化 BrowserWindow 的时候,在 webPreferences 节点下添加nodeIntegration: true
。否则会报错:renderer.js:9 Uncaught ReferenceError: process is not defined。 -
从 Chrome Extenstion V2 开始,不容许执行任何 inline javascript 代码在 html 中。不支持之内联方式写事件绑定代码。好比
<button onclick="handleCPU">查看</button>
Refused to execute inline event handler because it violates the following Content Security Policy directive:
-
利用 Electron 进行开发的时候,能够当作是 NodeJS + chromium + Web 前端开发技术。NodeJS 拥有文件访问等后端能力,chromium 提供展现功能,以及网络能力(Electron 网络能力不是 NodeJS 提供的,而是 chromium 的 net 模块提供的)。web 前端开发技术方案均可以应用在 Electron 中,好比 Vue、React、Bootstrap、sass 等。
-
在工程化角度看,使用 yarn 比 npm 好一些,由于 yarn 会缓存已经安装过的依赖,其余项目只要发现存在缓存,则读取本地的包依赖,会更加快速。
-
在使用 Vue、React 开发 Electron 应用时,可使用 npm 或 yarn install 包,也可使用 Electron-vue 脚手架工具。
vue init simulatedgreg/Electron-vue my-project cd my-project npm install npm run dev
-
开发完毕后须要设置应用程序的图标信息、版本号等,打包须要指定不一样的平台。
-
新开项目建立后会报错.
初始化工程后会报错 ERROR in Template execution failed: ReferenceError: process is not defined
。解决办法是使用 nvm 将 node 版本将为 10。
继续运行仍是报错,以下
┏ Electron ------------------- [11000:0615/095124.922:ERROR:CONSOLE(7574)] "Extension server error: Object not found: <top>", source: chrome-devtools://devtools/bundled/inspector.js (7574) ┗ ----------------------------
解决办法是在 main/index.dev.js 修改代码
- require('Electron-debug')({ showDevTools: true }); + // NB: Don't open dev tools with this, it is causing the error + require('Electron-debug')();
在 In main/index.js in the createWindow() function:
mainWindow.loadURL(winURL); + // Open dev tools initially when in development mode + if (process.env.NODE_ENV === "development") { + mainWindow.webContents.on("did-frame-finish-load", () => { + mainWindow.webContents.once("devtools-opened", () => { + mainWindow.focus(); + }); + mainWindow.webContents.openDevTools(); + }); + }
-
Electron 多窗口与单窗口应用区别
-
知道 Electron 开发原理,因此大部分时间是在写前端代码。因此根据团队技术沉淀、选择对应的前端框架,好比 Vue、React、Angular。
-
也许开发并不难,难在视觉和 UX。不少写过网页的开发者或者之前端的视觉去写 Electron App 不免会写出网页版的桌面应用程序,说难听点,就是四不像 😂。因此须要转变想法,这是在开发桌面应用程序。
-
Electron 和 Web 开发相比,各自有侧重点
2. 优化手段
2.1 性能分析
由于开发过程当中,Electron 体验就是在开发一个前端项目,因此咱们使用 Chrome 的 Performance 进行分析。
分析某段代码的执行过程,也能够经过下面命令生成分析文件,而后导入到 Chrome Performance 中分析:
# 输出 cpu 和 堆分析文件 node --cpu-prof --heap-prof -e "require('request’)”“
2.2 白屏优化
-
无论是 Native App 仍是 Web 网页,骨架屏都是比较常见的一些技术手段。好比弱网下简书 App 的效果
-
懒加载。优先加载第一屏(主界面)所需的依赖,其余的依赖延迟加载
-
代码分割。Webpack 工具支持代码分割,这在前端中是很常见的一种优化手段
-
Node 模块延迟加载或合并。Node 属于 CMD 规范,模块查找、文件读取都比较耗时,某些 Node 模块依赖模块较多、子模块较深这样首屏会很慢。因此延迟加载或者选择使用打包工具优化和合并 Node 模块。
-
打包优化。现代打包工具备很是多优化手段。 Webpack 支持代码压缩、预执行... 裁剪多余的代码, 减小运行时负担。模块合并后还能够减少 IO 。
-
和 Native App 中的 Hybrid 优化手段机制同样。能够对静态资源进行缓存(公司基础样式文件、基础组件等,设计资源更新策略)。使用 Service-Worker 进行静态资源拦截,或者在 Native 端作请求拦截,好比 Mac 端 NSURLProtocol
-
预加载机制。在登录界面或者启动动画阶段,初始化主界面,而后将主界面设置到不可见状态。等启动或者登录后再将主界面设置为可见状态
-
使用 Node API 尽可能避免同步操做。通常 Node API 都有同步和异步2种 API,好比
fs
文件读取模块 -
对内存影响较大的功能使用 Native 的能力,好比 Database、Image、Network、Animation 等。虽然 Web 都有对应的解决方案,可是原生对于这些功能的操做权限更大、内存控制更灵活、安全性更高、性能更佳
-
减少主进程压力。Electron 有1个主进程和多个渲染进程,主进程是全部窗口的父进程,它负责调度各类资源。若是主进程被阻塞,将影响整个应用响应性能。
7、 技术体系搭建:全景
其实一个技术自己的难易程度并非可否在本身企业、公司、团队内顺利使用的惟一标尺,其配套的 CI/CD、APM、埋点系统、发布更新、灰度测试等可否与现有的系统以较小成本融合才是很大的决定要素。由于某个技术并非很是难,要是大多数开发者以为很难,那它设计上就是失败的。
1. 构建
2. 工程解耦
3. 问题定位
Electron 提供的 crash 信息进行包装。
import { BrowserWindow, app, dialog} from 'Electron'; const mainWindow = BrowserWindow.fromId(global.mainId); mainWindow.webContents.on('crashed', const options = { type: 'error', title: '进程崩溃了', message: '这个进程已经崩溃.', buttons: ['重载', '退出'], }; recordCrash().then(() => { dialog.showMessageBox(options, (index) => { if (index === 0) reloadWindow(mainWindow); else app.quit(); }); }).catch((e) => { console.log('err', e); }); }) function recordCrash() { return new Promise(resolve => { // 崩溃日志请求成功.... resolve(); }) } function reloadWindow(mainWin) { if (mainWin.isDestroyed()) { app.relaunch(); app.exit(0); } else { BrowserWindow.getAllWindows().forEach((w) => { if (w.id !== mainWin.id) w.destroy(); }); mainWin.reload(); } }
// index.js app.on('will-finish-launching', () => { if(!isDev) { require('./updater.js') } require('./crash-reporter').init() }) // crash-reporter.js const {crashReporter} = require('Electron') function init() { crashReporter.start({ productName: 'ZanPandora', companyName: 'youzan', submitURL: 'http://127.0.0.1:33855/crash', }) } module.exports = {init} // server const Koa = require('koa') const app = new Koa() const Router = require('koa-router') const serve = require('koa-static-server') const router = new Router() const compareVersions = require('compare-versions') const multer = require('koa-multer') const uploadCrash = multer({dest: 'crash/'}) router.post('/crash', uploadCrash.single('upload_file_minidump'), (ctx, next) => { console.log(ctx.req.body) // 存DB })
4. 软件更新
// package.json "dependencies": { "Electron-is-dev": "^1.1.0", "Electron-squirrel-startup": "^1.0.0", // ... }, // index.js app.on('will-finish-launching', () => { if(!isDev) { require('./updater.js') } require('./crash-reporter').init() }) // updater.js const {autoUpdater, app, dialog} = require('Electron') if(process.platform == 'darwin') { autoUpdater.setFeedURL('http://127.0.0.1:33855/darwin?version=' + app.getVersion()) } else { autoUpdater.setFeedURL('http://127.0.0.1:33855/win32?version=' + app.getVersion()) } autoUpdater.checkForUpdates() // 定时轮训、服务端推送 autoUpdater.on('update-available', () => { console.log('update-available') }) autoUpdater.on('update-downloaded', (e, notes, version) => { // 提醒用户更新 app.whenReady().then(() => { let clickId = dialog.showMessageBoxSync({ type: 'info', title: '升级提示', message: '已为你升级到最新版,是否当即体验', buttons: ['立刻升级', '手动重启'], cancelId: 1, }) if(clickId === 0) { autoUpdater.quitAndInstall() app.quit() } }) }) autoUpdater.on('error', (err) => { console.log('error', err) }) // server // index.js const Koa = require('koa') const app = new Koa() const Router = require('koa-router') const serve = require('koa-static-server') const router = new Router() const compareVersions = require('compare-versions') const multer = require('koa-multer') const uploadCrash = multer({dest: 'crash/'}) router.post('/crash', uploadCrash.single('upload_file_minidump'), (ctx, next) => { console.log(ctx.req.body) // 存DB }) function getNewVersion(version) { if(!version) return null let maxVersion = { name: '1.0.1', pub_date: '2020-02-01T12:26:53+1:00', notes: '新增功能AAA', url: `http://127.0.0.1:33855/public/ZanPandora-1.0.1-mac.zip` } if(compareVersions.compare(maxVersion.name , version, '>')) { return maxVersion } return null } router.get('/win32/RELEASES', (ctx, next) => { let newVersion = getNewVersion(ctx.query.version) if(newVersion) { ctx.body='BBC6F98A5CD32C675AAB6737A5F67176248B900C ZanPandora-1.0.1-full.nupkg 62177782' } else { ctx.status = 204 } }) router.get('/win32/*.nupkg', (ctx, next) => { // redirect s3 静态文件服务 ctx.redirect(`/public/${ctx.params[0]}.nupkg`) }) router.get('/darwin', (ctx, next) => { // 处理Mac更新, ?version=1.0.0&uid=123 let {version} = ctx.query let newVersion = getNewVersion(version) if(newVersion) { ctx.body = newVersion } else { ctx.status = 204 } }) app.use(serve({rootDir: 'public', rootPath: '/public'})) app.use(router.routes()) .use(router.allowedMethods()) app.listen(33855)
8、 Electron 应用场景
业界拿 Electron 作了不少东西,好比你们都在用的 VSCode、Atom、一些工具软件的实现、不少大厂的面试软件、一些不是很是注重渲染效率的产品、一些集团内部的开发工具等。Electron 大有可为,有不少想象空间去作一些事情。举几个例子
1. 字节跳动
-
Electron作了一个工具,能直接查看线上包的函数耗时,无任何侵入
-
调试工具的合集
-
研发需求管理
-
代码合并工具(MR)。区别于 gitlab 的特性,没有多仓合代码的能力。
好比同时在主工程系修改了7个 pod 、1个主工程的代码,须要分批次提交,不具有同时将8个仓库的代码原子性合入
pod 的 changeLog、版本号等须要设计自动发版的流程
-
把性能调试和效率工具都整合到一块儿了。
2. 阿里
- 沙盒的查看与操做,好比在 PC 端查看移动沙盒内文件内容、数据库文件的查看与 SQL 执行等
- lint 功能、检测无用方法、在线日志解密查看、数据库文件的解密查看、jspatch 的 mock、
- 针对网络请求和响应的操做,好比自定义请求、延迟、mock response、
- Mock、查看
- 性能测试:cpu、load、fps、启动耗时(机房有高速摄像机解帧,模拟点击 icon、启动页启动、App 首页出现、首页图片出现、App 能够滚动交互了。不须要 hook 去监控耗时)
- pre-main、main、首页打点
- 数据板:埋点体系的数据,看坑位的点击效果
- 应用数据:沙盒、Cookie、数据
- 开发工具:CPU、内存、OOM
- 视觉:移动端元素的参考线
- 网络、开关数据、卡顿、内存泄漏
一些原则,一言以蔽之:该工具只作数据的查看、Mock 等工做,不作线上数据的干扰和生产。
主要实现方式:经过 deviceID、ip 地址等与设备产生链接,将一切能够标准化的流程都抽象、自动化、好比性能调试和效率工具都整合到一块儿。