Electron 在 Taro IDE 的开发实践

做者:凹凸曼-littly前端

背景

Taro IDE 是一款咱们正在精心打造的一站式移动端研发工做台。除了须要实现 Taro 从建立项目到预览、编译的所有能力,还须要打通用户测试、调试、监控等一系列流程。为了提高开发体验,仅仅一个命令行工具是远远不够的,咱们须要开发一款桌面客户端,并同时提供 Windows、MacOS 等不一样系统的版本。node

Electron 最初是 Github 为 Atom 编辑器开发的桌面应用框架。Electron 将 Chromium 与 Node 合并到同个运行时环境中,赋予了 Web 代码与底层操做系统进行交互的能力,并在打包时生成 Windows、MacOS、Linux 等平台的桌面应用。比起原生的桌面应用开发框架,Electron 在性能、应用体积方面会稍逊一筹,但 Electron 支持打包多个平台的桌面应用,在业界已经有 VSCode、Atom、Slack 等综合体验拔群的成功案例,咱们认为 Electron 彻底知足咱们的需求。react

介绍 Electron

若是只想体验一下 Electron,最快的方式是使用 Electron Fiddle,或者直接使用社区中提供的 脚手架webpack

最初接触 Electron,通常是被“使用前端技术栈生成多平台桌面应用”的特性吸引。但在后续的开发中,才会留意到 Electron 相比 NW.js 更为复杂的进程模型:git

Electron 的架构能够用下图来表示:github

img

Electron 项目中,运行 package.json 的 main 脚本的进程被称为主进程。主进程经过建立 web 页面来展现用户界面。这些用户界面都运行在彼此隔离的渲染进程中。web

Electron 主进程支持 Node API,而且可直接与操做系统进行底层交互,弹出系统通知、文件系统读写、调用硬件设备等。typescript

Electron 渲染进程默认只能与自身的 Web 内容进行交互。在打开 nodeIntegration 功能后,渲染进程也能够具有操做 Node 的能力。渲染进程也没法直接操做弹窗(Dialog)、系统通知(Notification)等,这些功能都须要经过 Electron 提供的 IPC/remote 机制在主进程中调用。json

而且在后续 Electron 的升级中,这些约束也可能由于安全、性能的缘由进行调整。能够说,Electron 的开发体验并不太美好,但正是这种开发体验与用户体验之间的博弈,保证了 Electron 应用在性能、安全方面的表现。浏览器

开发工做流

咱们使用社区提供的 electron-react-typescript 做为项目的初始脚手架。阅读 package.json 文件,咱们能够了解到,这个项目使用 webpack 进行主进程和渲染进程的打包,src/main/main.ts 文件就是主进程的入口。

Electron 的 BrowserWindow 类负责建立和控制浏览器窗口,app 对象则能够控制应用程序的各个事件与生命周期。 主进程的代码大体以下:

import { app, BrowserWindow } from 'electron'

let win
app.on('ready', () => {
  win = new BrowserWindow({ width: 800, height: 600 });
  win.loadURL(`http://localhost:2003`);
  // xxx
});

app.on('activate', () => {})
app.on('window-all-closed', () => {})

渲染进程 src/renderer/app.tsx 就一个普通的页面,这里再也不赘述。安装依赖后,使用 yarn start-dev ,便可启动项目的预览服务。

这个项目使用 webpack 来打包项目代码,这样处理有两个好处。一是经过 webpack 处理,咱们能够减小运行时的 require 调用,对 Electron 应用加载性能有必定帮助;二是借助 webpack 的 tree shaking 能力,未使用的代码也会被轻松移除,能够有效减小安装包体积。

为了打包 electron 项目,咱们须要至少两份 webpack 配置文件,一份打包主进程文件,指定 target 为 electron-main,另外一份打包渲染进程,target 设置为 electron-renderer

为了辅助 Electron 项目的调试工做,咱们能够安装 Devtron。Devtron 是 Electron 提供的开发调试插件。在开发者工具中加入 Devtron 后,项目中的 IPC 通讯、查看项目依赖、事件等信息,均可以在开发者工具中直接查看。

若有须要,咱们还能够安装其余的开发者工具扩展,例如 Redux、React 等,只须要在主进程中运行:

// main.js

const {
  default: installExtension,
  REACT_DEVELOPER_TOOLS,
  REACT_PERF,
  REDUX_DEVTOOLS
} = require('electron-devtools-installer')
const extensions = [REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS, REACT_PERF]

extensions.forEach(extension => {
  try {
    installExtension(extension)
  } catch (e) {
    console.error(e)
  }
})

至此,咱们的开发环境搭建完毕,能够开始进行业务代码的开发了。

优化

业务代码开发完毕后,就到了优化的环节了。这里主要从 Electron 应用的性能与体积两方面来说。

性能

Electron 在性能方面一直受到广大开发者的诟病。窗口打开慢,加载时间长都是老生畅谈的话题。这些问题该如何解决呢?

答案是预加载。在展现登陆窗口时,咱们能够提早将主窗口开启并设置隐藏,预加载主窗口的静态资源。用户登陆后,再经过 IPC 消息通知主窗口展现,达到秒开的效果。这个过程能够用下图表示:

img

除了窗口加载,在 Electron 中,require Node 模块也是至关昂贵的操做。若是在渲染进程中直接使用大量的原生模块,会严重拖慢页面的打开时间,形成窗口可交互时间的延后,这对于桌面应用来讲是灾难性的体验。Electron@5 以后的版本已经默认关闭了 BrowserWindow 的 nodeIntegration 功能,能够看出 Electron 团队也并不建议在渲染进程中直接使用原生模块。

在桌面应用中,等待是很是难以忍受的,性能上的些许欠缺都会让用户以为这是个套壳的网页。如需使用原生模块,咱们更建议使用异步的方式加载模块,或是使用异步 IPC 在主进程中调用。另外,为了优化用户的体验,咱们还须要在小动画等方面下功夫,例如骨架屏等等。

Atom 团队经过使用 V8 snapshot 能力,在生产环境中去掉了低性能的 require 调用,将 Electron 应用的加载性能提高了 30%,同时还提高了应用的安全性能,这篇文章 How Atom Uses Chromium Snapshots 对他们的作法作了详细介绍。

启用骨架屏先后对比:

img

性能优化的方式并不局限于上面的方式。例如开启 electron-builder 的 asar 功能,在打包时将源码生成二进制的 asar 文件,下降 require 操做的代价的同时,也能稍许减小空间占用,代价是没法对 asar 内的文件使用 child_process.spawn ;须要密集计算的功能,能够开多一个渲染进程来跑,或是使用 require('child_process').spawn 开子进程来跑,避免阻塞主进程,形成应用卡死。

体积

一样受到开发者诟病的,还有 Electron 应用的体积 。一个空 Electron 项目,在打包后就会占据近上百兆空间。Electron 的应用体积之因此大,除了自带的 Chromium 内核,还有大部分体积是来自用户安装的 node_modules。

使用 electron-builder 打包 Electron 应用时,若是不加处理,会将 node_modules 内的内容全数打包,致使应用体积偏大。针对这种状况,咱们能够进行一系列优化:

  1. 使用 yarn autoclean 命令进行清理。node_modules 目录中,包含着大量的 README 文件、文档等内容,这部分文件在生产环境中并不是必要。若是项目中使用 yarn 进行依赖管理,则可使用 yarn autoclean 命令。这个命令会初始化一份默认的配置文件 .yarncleanyarn 在安装依赖后,将会自动根据 .yarnclean 进行依赖清理。

    # 默认的 .yarnclean 文件大体以下:
     
     # test directories
     __tests__
     test
     tests
     powered-test
     
     # asset directories
     docs
     doc
     website
     assets
     
     # examples
     example
     examples
     
     ...
  2. 使用双 package.json 架构。node_modules 目录中,除了生产环境须要用到的依赖,还存在着不少 devDependencies,这部分依赖是不该该被打包的。为了解决这个问题,electron-builder 提供了双 package.json 架构。具体来讲,electron-builder 推荐用户将 Electron 应用依赖划分为两部分:开发依赖以及生产依赖。用户使用项目根目录的 package.json 来管理开发依赖,而使用项目的应用文件夹下的 package.json 管理生产依赖。electron-builder 仅会打包应用文件夹下的依赖。

    在这个改动后,安装依赖时还须要经过 electron-builder install-app-deps 命令安装应用依赖。这个操做推荐放在 package.json 内的 post-install 脚本中。

    electron-builder@8 后,并不会打包 devDependencies 内的依赖。这意味着咱们能够经过这个途径来避免开发依赖被打包的问题。若是项目使用了 webpack 之类的工具进行打包,则须要注意将 webpack 已经打包过的资源从 dependencies 中排除,避免重复打包。

将来

能力 Web 化

目前,项目的大部分能力依然是基于 Electron 提供的能力实现的。这至关于与 Electron 严重耦合,不利于项目中个别能力的复用。将来,咱们但愿对项目的架构进行调整,对核心能力进行插件化改造,方便能力的移植与复用,甚至将来的研发上云,这有赖于项目核心能力的 Web 化。固然,Web 化也会带来额外的性能损耗,这会对咱们项目的性能提出新的要求。

img

崩溃处理

项目的稳定性也是将来须要努力的方向。咱们有时会收到用户关于应用闪退、卡死等现象的反馈,却苦于没法复现,不少时候难以解决用户反馈的问题。将来,咱们须要在项目中加入异常监控上报的机制,收集操做系统信息、内存使用量等关键信息,在 Crash 时进行上报,甚至推送告警消息。这有利于开发人员进一步了解用户的使用过程,方便问题的复现。

小结

在开发桌面应用时,Electron 在效率上有很大的优点。几行 JS 代码就能够启动桌面客户端,大大下降了开发门槛。但 Electron 在性能、体积等方面也存在着软肋。若是在前期开发时没有通过充分思考,颇有可能会在后期优化时付出惨痛的代价。在这个项目中,咱们的优化工做还远远不够,后续有更多突破会分享给你们。


参考资料

[1]Electron: https://www.electronjs.org/
[2]Electron Fiddle: https://www.electronjs.org/fi...
[3]脚手架: https://github.com/search?q=e...
[4]NW.js: https://nwjs.io/
[5]electron-react-typescript: https://github.com/Robinfr/el...
[6]src/main/main.ts: https://github.com/Robinfr/el...
[7]src/renderer/app.tsx: https://github.com/Robinfr/el...
[8]Devtron: https://www.electronjs.org/de...
[9]How Atom Uses Chromium Snapshots: https://flight-manual.atom.io...


欢迎关注凹凸实验室博客:aotu.io

或者关注凹凸实验室公众号(AOTULabs),不定时推送文章:

欢迎关注凹凸实验室公众号

相关文章
相关标签/搜索