Electron开发上手笔记1:概览

2,717 阅读12分钟

一、为什么要学习Electron,适合什么场景?

1、为什么要学?

  • 可以帮你快速构建一个桌面端应用(可用于快速试错)。

  • 可以用来造工具。

  • 提升技术广度和视野。

Electron是Web的超集,它基于JavaScript环境,能够让前端工程师快速上手,同时会接触到Node.js、客户端知识、甚至更底层的操作系统,能够让前端工程师的想象力从浏览器中释放出来,丰富自己的知识储备,提升技术广度和视野。

2、Electron适合用在什么场景?

  • 要快速试错,占领市场时

  • 开发工具,效率应用时

  • 要同时开发Web版和桌面版时

3、谁在用Electron

  • Atom
  • slack
  • VS Code
  • Whatsapp
  • WordPress

二、学习Electron的难点在哪

1、涉及的技术栈比较多

需要了解Electron知识、客户端知识、Node.js,甚至是集成C++或者Rust等第三方库,会涉及到多进程的概念等等

2、大部分公司Electron相关的基础建设比较匮乏

比如在Mac打包的时候,可以依赖iOS持续集成,而在Windows上则不行,类似场景还特别多。

3、如何充分利用好Electron的能力

前端工程师习惯于局限在基于浏览器的能力进行开发的思维中。

三、Electron是什么

1、它是由Github开发的开源框架,现在有OpenJS基金会维护的一个开源项目。

2、它允许开发者使用Web技术构建跨平台桌面应用。

3、Electron = Chromium + Node.js + Native API

其中,

Chromium提供强大的UI能力,让其可以在不考虑兼容性的情况下,利用强大的Web生态来开发界面;

Node.js让Electron有了底层的操作能力,比如文件读写,加解密,集成C++模块等,而且还可以使用大量的NPM包;

Electron内置的Native API解决了跨平台的问题,提供了统一的原生界面,比如像窗口、托盘,其次是系统能力,比如Notification,最后是应用的基础能力,比如像软件更新、崩溃监控等等。

四、Electron的历史

Electron的历史得从浏览器的历史讲起。

1989年,英国科学家伯纳斯-李(Berners-Lee)发明了万维网,然后在90年他写出了世界上第一个浏览器。就是Nexus,但当时这个浏览器有一个很大的缺陷就是不能显示图片。

然后到了1993年,NSCA(美国国家超级电脑应用中心)研发了第一款能看到图片的浏览器,这款浏览器叫做Mosaic(马赛克)。

1994年,Mosaic的核心成员Marc Andreessen,他跟辞职的硅谷制图(Silicon Graphics)创始人吉姆.克拉克一起创建了网景公司,他们的愿望是打破Mosaic的垄断地位,起初它们取了一个名字“Mozilla”(原因有二,Mosaic Killa, Godzilla eat the Mosaic),因为市场的原因最后还是用了Netscape。

随着网景浏览器的快速发展,在1995年微软发现浏览器的巨大市场之后,推出了IE1,也就是这一年,网景公司找来了天才程序员布兰登.艾奇,他用10天时间创造了JavaScript;但微软还是有钱,迭代速度很快,通过操作系统的垄断以及捆绑销售,逐步拿下了市场份额。

1998年网景被击败,受Linux开源项目的影响,网景公司决定开放源码以回击IE,转型为开源软件,名为Mozilla,在收购之前成立了Mozzila非盈利组织。

到了2002年在网景源码的基础上派生了火狐,它在安全、插件扩展、开发调试工具等方面都有着巨大的贡献,然后到了2008年,基于Firefox开发的Chrome诞生了,它是非常具有颠覆意义的,重新设计后带来了极致的简洁,然后引入了多进程带来的稳定,还有带来了10倍提速的V8引擎。对应Chrome也有一份开源项目就是Chromium。

随后在2009年,Ryan Dahl基于V8引擎将非阻塞IO和JS整合,让JS延伸到了服务端领域。有了Chromium,V8 Node.js开源后,终于有人开始尝试JS来做桌面端的应用,在2011年英特尔工程师王文睿写了第一版node-webkit(NW基于Node.js和Chromium),当时还是一个默默无闻的项目,2012年他招来了一个实习生做这个项目,实习生一人兼顾了开发、维护、宣传,接近重写了NW,将NW做到了Github C++前五,这位实习生就是Electron的作者赵成。赵成在2012年年底加入Github的Atom项目组,他尝试用NW开发,发现问题太多,响应太慢,在2013年的时候终于决定要开发Atom-shelll,然后在2015年正式改名为Electron。

五、怎么判断一个项目是否是用Electron开发的?

以VS Code举例,通过如下方式就可得知。

六、开发Electron与开发Web开发有什么不同

主要体现在四个方面:

第一,主进程与渲染进程。

第二,进程间通信。

第三,需要用到很多原生能力。

第四,可以较大程度上释放前端的想象力,能做的事情更多。

具体来说:

1、主进程和渲染进程

1)主进程

Electron运行package.json的main脚本的进程称为主进程。

每一个应用只有一个主进程。

主进程管理着原生GUI,典型的窗口(BrowserWindow、Tray、Dock、Menu)。

主进程还负责创建渲染进程。

主进程还控制着应用的生命周期(app)。

主进程的模块有:

  • app

管理了应用的生命周期,比如说退出;同时它又可以设置app本身的一些属性,比如说Dock。例如app.on('ready', callback)

  • BrowserWindow

用来管理窗口

  • ipcMain

是和ipcRenderer进行IPC通信的。

  • Menu
  • Tray
  • MenuItem
  • dialog
  • Notification

用来做一些可交互的通知

  • webContents

用来加载具体的页面

  • autoUpdater

是更新模块

  • globalShortcut

用来设置全局的快捷键

  • systemPreferences

  • TouchBar

  • netLog

  • powerMonitor

  • inAppPurchase

  • net

  • contentTracing

  • BrowserView

  • session

  • protocol

  • Screen

  • clipboard

用来读写剪切板

  • crashReporter

用来监控主进程和渲染进程是否有崩溃

  • shell
  • nativeImage

这是一些原生的GUI

clipboard、crashReporter、shell、nativeImage这四个模块是主进程和渲染进程都可以调用。

2)渲染进程

展示Web页面的进程称为渲染进程。

通过Node.js以及Electron提供的API可以跟系统底层打交道。

一个Electron应用可以有多个渲染进程。

主进程的模块有:

  • ipcRender 是和ipcMain进行IPC通信的

  • remote

用来调用主进程的模块,但是建议大家不要使用这个模块

  • desktopCapture

用来捕获桌面流,通过它可以进行系统的截图和获取到屏幕的视频流等等。

  • webFrame
  • nativeImage
  • shell
  • crashReporter

用来监控主进程和渲染进程是否有崩溃

  • clipboard

用来读写剪切板

总结一下,主进程和渲染进程的模块如下:

2、进程间通信

1)Electron进程间通信的目的

  • 通知事件

比如,我们想在页面上创建一个原生菜单,但是Electron中只有主进程能够创建原生菜单,这时就需要用到进程间通信,通知主进程去创建原生菜单

  • 数据传输

比如,我想在页面中获得系统的内存情况,这时就需要将这些数据通过IPC来传输。

  • 共享数据

比如,我们想让用户信息在不同的进程中都能够看到,这个时候就需要用到IPC来共享数据。

2)IPC通信模块

Electron提供了IPC通信模块,主进程的ipcMain和渲染进程的ipcRenderer。

ipcMain和ipcRenderer都是EventEmitter对象。

3)三种类型的进程间通信

  • 从渲染进程到主进程

有两种写法:

第一种,callback写法:

ipcRenderer.send(channel, ...args)

const {ipcRenderer} = require('electron);
ipcRenderer.send('do-some-work', 1, 2);

ipcMain.on(channel, handler)

const {app, BrowserWindow, Notification, ipcMain} = require('electron');

let win;
app.on('ready', () => {
  win = new BrowserWindow({
    width: 300,
    height: 300,
    webPreferences: {
        nodeIntegration: true
     }
  });
  win.loadFile('./index.html');
  handleIPC();
});

function handleIPC() {
   ipcMain.on('do-some-work', function(e, a, b) {
     console.log('do-some-work', a ,b);
  })
}

第二种,Promise写法(Electron 7.0之后,处理请求 + 响应模式)。

ipcRenderer.invoke(channel, ...args)

ipcMain.handle(channel, handler)

  • 从主进程到渲染进程

ipcRenderer.on(channel, handler)

const {ipcRenderer} = require('electron');

ipcRenderer.on('do-some-work', () => {
    alert('do-some-work');
})

webContents.send(channel)

const {app, BrowserWindow. Notification, ipcMain} = require('electron');

let win;
app.on('ready', () => {
  win = new BrowserWindow({
    width: 300,
    height: 300,
    webPreferrences: {
      nodeIntegration: true
    }
  });

  win.loadFile('./index.html');
  setTimeout(handleIPC, 500);
})

function handleIPC(){
  win.webContents.send('do-some-work');
}
  • 页面间(渲染进程与渲染进程间)通信

通知事件:

通过主进程转发(Electron 5之前);

ipcRenderer.sendTo(Electron 5之后)。

数据共享:

Web技术(localStorage、sessionStorage、indexedDB);

使用remote:它会将数据挂在一个全局的过程中,但不太建议使用remote这个模块,因为用得不好的话会导致程序卡顿,影响性能

例子:

// main.js
const {app, BrowserWindow, Notification, ipcMain} = require('electron');

let win;
let win2;

app.on('ready', () => {
  win = new BrowserWindow({
    width: 300,
    height: 300,
    webPreferences: {
     nodeIntegation: true
    }
  });

  win2 =  new BrowserWindow({
    width: 300,
    height: 300,
    webPreferences: {
     nodeIntegation: true
    }
  });

  win2.loadFile('./index.html');
  gloabal.sharedObject = {
    win2WebContentsId: win2.webContents.id
  };

  setTimeout(handleIPC, 500);
})
// renderer.js
const {ipcRenderer, remote} = require('electron');

let sharedObject = remote.getGlobal('sharedObject');
let win2WebContentsId = sharedObject.win2WebContentsId;

ipcRenderer.sendTo(win2WebContentsId, 'do-some-work', 1);
// renderer2.js
const {ipdRenderer} = require('electron');
ipcRenderer.on('do-some-work', (e, a) => {
  alert('renderer2 handle some work' + a);
})
  • 进程间通信的一些经验

少用remote模块,每次会触发底层的同步IPC事件,特别影响性能,甚至如果处理不好的话,进程就会卡死;

不要用sync模式,一旦处理不好,整个应用就会卡死;

在请求+响应的通信模式下,需要自定义超时时长限制,当超时时要response一个超时异常事件,让业务去处理。

3、需要用到很多原生能力

1)Electron的原生能力主要是源于两个方面:Electron内置的Native API以及强大的Node.js。

Electron内置的API提供的原生能力:

一方面,是使用Electron API创建原生的客户端GUI,包括:

  • BrowserWindow应用窗口

  • Tray托盘

  • app设置dock.badge

  • Menu菜单

  • dialog原生弹框

  • TouchBar苹果触控栏

  • ……

基于它们可以设置右键菜单、窗口定制、系统托盘、在Dock上设置未读消息数等。

另一方面,是使用Electron API获得底层的能力,包括:

  • Notification

  • clipboard 读写剪切板

  • globalShortcut 注册全局快捷键

  • desktopCapture 捕获桌面流

  • shell 打开本地文件夹、本地文件、本地URL

  • ……

基于它们在桌面环境集成系统通知、剪切板、系统快捷键、文件拖放等,也可以做电源监视、内存、CPU、屏幕获取等。

强大的Node.js提供的原生能力,这一点不言而喻,在此不再赘述。

2)Electron可获得的原生能力

第一,基于原生API,可获得:

  • 文件读写(通过fs)
  • 操作系统(通过child_process调用系统的Shell命令)
  • 加解密(通过crypto)

Electron同时在主进程和渲染进程中对Node.js暴露了所有的接口。

第二,基于Node.js可集成第三方模块:

  • 通过npm安装即可引入社区上所有的Node.js库

包括在渲染进程中,也是可以直接使用Node.js模块的

  • Node.js add-on:用Node.js的插件机制可以去集成C++写的模块
  • node-ffi(Foreign Function Interface):通过node-ffi可以去集成C++写好的动态库,比如对于打印这一动态库的集成。

第三,使用Node.js调用系统集成好的一些能力:

  • 调用Windows RT(通过:github.com/NodeRT/Node… APIs的Node.js模块生成器),比如通过它可以调用蓝牙、USB、预览文件等。

备注:Windows RT是Windows家族的一个新成员,新系统画面与操作方式变化极大,采用全新的Modern UI风格用户界面,各种应用程序、快捷方式等能以动态方块的样式呈现在屏幕上,用户可自行将常用的浏览器、社交网络、游戏、操作界面融入。Windows RT专注于ARM平台,并不会单独零售,仅采用预装的方式发行。Windows RT 中将包含针对触摸操作进行优化的微软 Word、Excel、PowerPoint 和 OneNote 的桌面版,但与旧版Windows应用不兼容,可通过WinRT开发环境为其创建Modern UI应用。

Windows RT是ARM平台下的独立版本,无法单独购买,只能预装在采用ARM架构处理器的PC和平板电脑中。Windows RT无法兼容X86软件,但将附带专为触摸屏设计的微软Word、Excel、PowerPoint和OneNote。

  • 在MacOS上调用AppleScript(通过github.com/TooTallNate… OS X上轻松执行任意AppleScript代码的模块,通过它可以调用系统的原生应用,做一些跟系统的原生应用交互的事情。

总的来说,Electron的原生能力可以总结成下表:

4、释放前端想象力:基于Electron能做什么?

1)无兼容问题

  • 不用担心IE、Safari上的表现差异了

  • 大量使用Chrome已支持的新的feature

比如,在Chrome77版本开始,就支持了原生的lazy-loading(developers.google.cn/web/updates…

<img src="image.jpg" loading="lazy" width="400" height="250" alt="...">

mathiasbynens.be/demo/img-lo…

怎么知道Chrome有什么新特性呢?看这个网址就好了,一般每个月会更新一个版本:developers.google.cn/web/updates

  • Babel中设置targets为Electron对应的Chrome版本,包的体积也会被缩减。

2)可以使用ES6/7/8/9/10等的最新语法

  • Async、Await、Promise

  • String、Array、Object等的一些高级用法

例如,'123'.padStart(5, '0')

  • BigInt数据类型

例如,BigInt(12345678901234567890123456789)

3)无跨域问题

  • 可以使用Node.js发送请求
  • 也可以使用Electron net发送请求

都不会被浏览器的跨域请求所影响,而且可以做到请求的时候少一个option请求,也不会被浏览器同域名6个请求所限制住。

4)更多精彩

  • 操作本地文件
  • 会有一份本地日志,方便我们排查问题
  • 引入一些更好用的本地DB,如Lowdb、Leveldb甚至是SQLite等等,而不局限于IndexDB
  • 可以通过Node.js的worker_threads和child_process将任务拆成多线程、多进程并行,充分利用计算机的多核

七、其它

1、如何运行Electron项目

npx electron main.js

2、怎么打开调试面板

方法有二:

1)通过代码:someWindow.webContents.openDevTools()

2)通过顶部菜单:View——>Toggle Developer Tools