引言
如果开发跨桌面端的应用开发的话,我相信,electron目前绝对是不可避免的技术方案。
web应用大家都知道,通过浏览器访问的应用就是web应用,那什么是桌面端?
桌面端有两个重要特点:
- 具备独立运行于操作系统上的能力(通常指的是windows、MacOS、linux这这些主流PC操作系统)
- 具有自己的GUI(用户图形界面 graphical user interface)
有人会问?web应用也有自己的GUI,但是它必须在浏览器中执行,因此不是桌面客户端。
浏览器能直接运行在操作系统上,而且有自己的GUI,因此浏览器是桌面客户端。
这样大家就理解web应用和桌面端的区别了。
Electron是什么
先简单介绍一下electron。electron是由Github开发的开源框架,现在由OpenJS基金会维护的一个开源项目。它允许开发者使用 HTML、CSS 和 JavaScript 来构建跨平台的桌面应用,而且构建出来的应用,可以很好地兼容 Mac、Windows 和 Linux 这些主流的操作系统。
相对于web开发和传统的桌面端,它集合了一些自己的特点:
- 自动更新 —— 支持应用自动更新,相对于传统的桌面端更加快捷
- 原生菜单和通知 —— 可以创建原生应用菜单和上下文菜单,还有原生的交互通知 Windows
- installer ——可以快速便捷地创建安装包,兼容Mac、windows
- 调试和分析 —— Chrominum的内容模块可以发现性能瓶颈和缓慢的操作。你也可以在应用中使用自己喜欢的 Chrome 开发者工具
- 应用崩溃报告 —— 可以将崩溃报告提交到远程服务器
所以目前来看,虽然是使用了web技术进行开发,但是和web开发稍许有些不同的。
为什么选择Electron
- 首先,为了跨端,兼容多个平台,不仅仅是操作系统,还有浏览器的内核;
- 其次,可以快速构建一个桌面应用,可用于快速试错;
- 然后,也适用于一些效率工具应用,比如有道云笔记,vscode,截屏软件;
- 最重要的是,省钱,因为原生开发效率低,可以降本增效;
其实,大部分选择 Electron 框架的动机也都是差不多的,无非就是省钱,尤其是在夹缝中生存的微小型企业。
🈶️ 很多优秀的应用都是使用electron开发的,比如,vscode,atom,github桌面版,语雀桌面版,postman桌面版,支付宝小程序开发工具等等。
Electron架构
Electron的架构可以理解为由三部分构成, 即chromium + nodejs + 原生api
其中,
Chromium提供强大的UI能力,允许我们在不考虑浏览器兼容性的前提下,利用完整的web生态开发页面。
NodeJS让Electron具备了底层操作能力,比如文件读写、加解密、集成C++模块,支持引用npm包等。
Native API 解决了跨平台的问题,提供了统一的原生界面,比如窗口、托盘,其次是系统能力,比如
Notification
,最后是应用的基础能力,比如软件更新、系统监控等。
Chromium架构
这张图来源于chromium官网,比较清晰的表达了Chromium的多进程架构。
我们从几个方面,分析一下Chromium的多进程模式
- 浏览器端(Browser)
- 渲染器端(Render)
- 浏览器和渲染器间的通信方式(IPC)
从图上可知,有Browser,有Render。
Browser浏览器端直观上来说,就是打开Chrome后,浏览器的主体(顶级浏览器窗口)。它对应一个单独的进程,Browser进程。该进程一旦挂掉,整个浏览器就会崩溃。
而每一个Render进程,可以看成一个标签页。每个标签页,都对应着自己的Render进程。即使一个标签页挂掉,其他标签页也可以正常运作。
1、Browser
Browser 进程可以看成一个指挥调度中心。它负责运行UI和管理标签页(Render)。如消息的派发,IO处理等等。
Browser 进程为每个Render的每个
RenderView
,维护对应的
RenderViewHost
,管理浏览器状态、渲染器交互。
Browser 进程为每个Render维护一个
RenderProcessHost
,用其来管理对应的
RenderViewHost
。
2、Render
每个Render进程,持有一个Webkit(现在为Blink)内核对象。持有一个
RenderProcess
对象。这个对象与Browser端的
RenderProcessHost
对应。同样,每个Render持有一个或多个
RenderView
,它于Browser端的
RenderViewHost
对应。
RenderProcess
管理与浏览器进程间的通信,维护标签页状态。
RenderView
代表网页的内容。它的唯一标识为
RenderProcessHost
+ViewID。
3、Browser与Render间的通信
Browser与Render间通过IPC通信。
Browser端通过
RenderViewHost
与特定标签页中的特定内容交互。
RenderViewHost -> RenderProcessHost -> IPC -> RenderProcess -> RenderView
Native API
Electron 分别在主进程和渲染进程提供了大量 API,这里先不用管什么是进程。关于Native API我们需要知道的是,可以通过require语句方便的把这些 API 包含在当前模块使用。
对于NodeJS 上提供的 API,同样也只需要简单的require一下。但是 Electron 提供的 API 只能用于指定进程类型,即某些 API 只能用于渲染进程,而某些只能用于主进程。
例如,
app
,
BrowserWindow
就只能用于主进程,
ipcRenderer
、
remote
这些只能用于渲染进程。
另外,图中间的4个 Native api在两个进程中都可以使用,其中深色模块是工作中常用的模块:
● app 模块: 管理应用的生命周期,同时也可以设置app本身的一些属性,如Dock;
● BrowserWindow模块:管理我们的窗口;
● ipcMain 模块:与 ipcRender 进行IPC通信;
● Notification 模块:进行可交互的通知;
● webContents 模块:用来加载具体的页面;
● autoUpdater 模块:更新模块;
● globalShortcut 模块:设置全局的一个快捷键;
● clipboard 模块:读写剪切板;
● cashReporter 模块:监控主进程和渲染进程是否有崩溃;
● remote 模块:可以调用主进程的模块,但不建议使用(注:remote本质上是在基于IPC的一个同步的进程间消息,同步IPC会影响UI渲染。每次get和set操作都会触发,写得不好性能会卡,甚至UI hang掉);
● desktopCapture 模块:捕获桌面流,通过这个模块可以拿到像系统的截图、屏幕的视频流;
Electron进程
在electron中,进程区分为主进程和渲染进程。
electron 运行 package.json 中的
main
指定的入口脚本的进程称为主进程,一个应用只能有一个主进程。主进程负责的工作主要有:
负责创建、管理渲染进程、控制整个应用的生命周期、启动一个串口、加载index.html等本地文件、应用程序关闭后回收资源、退出程序等工作。
展示 Web 页面的进程被称为渲染进程,一个应用可以有多个渲染进程。渲染进程负责的工作主要有:
负责渲染显示界面、控制用户的交互逻辑、响应用户的交互等工作。
也就是说,一个
BrowserWindow
实例就代表一个渲染进程。如果使用electron做普通的单页应用,你可以理解为就一个渲染进程。而且,当
BrowserWindow
实例被销毁后,该渲染进程也会跟着销毁。
另外,需要知道的是,在浏览器中 Web 页面是运行在沙盒环境中,无法访问操作系统的原生资源。而 Electron 可以让我们在每一个渲染进程中,使用 Node.js来去访问系统底层。
主进程
一个常见的主进程文件例子,
// main.js文件const{ app, BrowserWindow }=require("electron");//引入 electron的app和BrowserWindow模块 let mainWindow;
app.on("ready",()=>{
mainWindow =newBrowserWindow({width:800,height:500});// 设置屏幕宽高
mainWindow.setMenu(null);// 隐藏Chromium菜单
mainWindow.loadFile("index.html");// 加载index.html文件
mainWindow.on("closed",()=>{
mainWindow =null;});});
app.on("window-all-closed",()=>{/* 在Mac系统用户通过Cmd+Q显式退出之前,保持应用程序和菜单栏处于激活状态。*/if(process.platform !=="darwin"){
app.quit();}});
app.on("activate",()=>{/* 当dock图标被点击并且不会有其它窗口被打开的时候,在Mac系统上重新建立一个应用内的window。*/if(mainWindow ===null){createWindow();}});
electron app 中常用的事件时机:
- ready:即当electron初始化时被触发;
- window-all-closed:所有窗口被关闭时触发;
- before-quit:应用程序开始关闭窗口之前触发;
- will-quit:当所有窗口都已关闭并且应用程序将退出时触发;
- quit:当应用程序退出时触发;
- active:当应用被激活时发出;
进程通信:主进程和渲染进程
在Electron中,主进程和渲染进程的通讯可以说是互相关联,也可以说不关联,是处于分支型。非常类似
react
组件之间的通信。
主进程和渲染进程的通信主要通过
ipcMain
和
ipcRenderer
这两个Native API:
// 主进程中
ipcMain.on('message',(event,arg)=>{
event.sender.send('getMessage','发送给渲染线程的消息')})// 渲染线程中
ipcRenderer.on(getMessage,(event, arg)=>{
console.log(arg);})
进程通信:渲染进程和渲染进程
渲染进程之间是无法直接进行通信的,不过可以通过主进程来间接发送,是不是很像react的子组件之间的状态传递?
// 渲染进程 setting windowconst{ ipcRenderer }=require('electron')// 引入ipc
ipcRenderer.send('setting window message','发送给主线程的消息')// 主线程中
ipcMain.on('setting window message',(event, arg)=>{
console.log(arg);// 指定另一个渲染进程接收 ,这里的目标进程:mainWindow
mainWindow.webContents.send('main window message', arg)})// 渲染进程 main window const{ ipcRenderer }=require('electron')// 引入ipc
ipcRenderer.on('main window message',(event, arg)=>{// 接收到Main进程返回的消息
console.log(arg);})
Electron 安装
在项目中,我们可以直接通过yarn或者npm等包管理工具来安装electron,然后可以通过 -v 查看是否安装成功。
打包的话,我们需要用到另外一个包,
electron-builder
。
yarnadd electron -dev
electron -v // 查看是否安装成功
yarnadd electron-builder -dev
一般,我们可以指定 arch=ia32 来安装32位的electron,这样在 Windows 平台打包都应该要基于32位来打,这样打出来的包在32位和64位都可以用,只需维护一套即可。
yarnadd--arch=ia32 --platform=win32 electron
React + Electron
我使用的前端脚手架是基于umi来搭建的,所以我在此基础之上进行了拓展。
umi 脚手架似乎没有官方内置的 electron 插件,因此使用了umi-plugin-electron-builder来快捷配置 electron。这个插件会自动帮你生成模板配置,十分方便。
⚠️需要注意的是,这个插件仅支持umi3版本。
$ yarnadd umi-plugin-electron-builder --dev
安装插件之后,执行 umi dev electron 会生成相关文件,主进程文件 src/main/main.ts,然后会在package.json中增加入口配置。
打包配置,则需要我们手动在 .umirc.ts文件中配置,
electronBuilder:{routerMode:'hash',// 路由 只能是hash或memoryoutputDir:'dist_electron',// 默认打包目录externals:[],// 不配置的无法使用rendererTarget:'electron-renderer',// 构建目标electron-renderer 或 webbuilderOptions:{productName:'electron-react-demo',// 项目名,也是生成的安装文件名,即electron-react-demo.exeappId:'cc.yichen.electron-react-demo',// 包名copyright:'Copyright © 2022-present 逸尘',// 版权信息nsis:{// https://www.electron.build/configuration/nsisoneClick:false,// 是否一键安装allowElevation:true,// 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。allowToChangeInstallationDirectory:true,// 允许修改安装目录installerIcon:'./build/icons/icon.ico',// 安装图标uninstallerIcon:'./build/icons/icon.ico',// 卸载图标installerHeaderIcon:'./build/icons/icon.ico',// 安装时头部图标createDesktopShortcut:true,// 创建桌面图标createStartMenuShortcut:true,// 创建开始菜单图标shortcutName:'electron-react-demo',// 图标名称},win:{// win exe 相关配置,icon:'./build/icons/icon.ico',artifactName:'${productName}-v${version}-win32-setup.${ext}',target:[{target:'nsis',// 利用nsis制作安装程序arch:[// 这个意思是打出来32 bit + 64 bit的包,但是要注意:这样打包出来的安装包体积比较大,所以建议直接打32的安装包。// "x64",'ia32',],},],},mac:{// mac pkg 配置项icon:'./build/icons/icon.icns',artifactName:'${productName}-v${version}-mac.${ext}',},dmg:{// macOS dmg镜像 配置项,https://www.electron.build/configuration/dmgcontents:[// 自定义图标位置。x 和 y坐标是指图标中心的位置(1x比例),不考虑标签{x:410,y:150,type:'link',path:'/Applications',},{x:130,y:150,type:'file',},],},linux:{icon:'./build/icons',artifactName:'${productName}-v${version}-linux.${ext}',},publish:[{provider:'generic',url:'',},],},},
关于项目打包,可以配置如下命令:
# windows
umi build electron --win# mac
umi build electron --mac# linux
umi build electron --linux# windows 按平台打包
umi build electron --win--ia32 //32位
umi build electron --win--x64 //64位
umi build electron --win--armv7l //arm32位
umi build electron --win--arm64 //arm64位
Vue + Electron
首先需要下载安装electron 和 vue-cli-plugin-electron-builder
yarnadd electron --devyarnadd vue-cli-plugin-electron-builder --dev
然后,这个插件我们需要手动去配置修改package.json,加入主进程文件background.js。以及,在 package.json 中配置启动项命令和打包命令,
"electron:build":"vue-cli-service electron:build","electron:build:mac":"vue-cli-service electron:build --macos","electron:build:win64":"vue-cli-service electron:build --windows --x64","electron:build:win32":"vue-cli-service electron:build --windows --ia32","electron:serve":"vue-cli-service electron:serve",
打包配置,则需要我们手动在 vue.config.js文件中配置,
pluginOptions:{electronBuilder:{// electron 构建配置builderOptions:{productName:'electron-vue-demo',// 项目名,也是生成的安装文件名,即electron-vue-demo.exeappId:'cc.yichen.electron-vue-demo',// 包名copyright:'Copyright © 2022-present Yichen',// 版权nsis:{oneClick:false,// 是否一键安装allowElevation:true,// 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。allowToChangeInstallationDirectory:true,// 允许修改安装目录installerIcon:'./build/icons/icon.ico',// 安装图标uninstallerIcon:'./build/icons/icon.ico',// 卸载图标installerHeaderIcon:'./build/icons/icon.ico',// 安装时头部图标createDesktopShortcut:true,// 创建桌面图标createStartMenuShortcut:true,// 创建开始菜单图标shortcutName:'electron-vue-demo'// 图标名称},dmg:{// macOSdmgcontents:[{x:410,y:150,type:'link',path:'/Applications'},{x:130,y:150,type:'file'}]},mac:{// macicon:'./build/icons/icon.icns',artifactName:'${productName}-v${version}-mac.${ext}'},win:{// win 相关配置icon:'./build/icons/icon.ico',artifactName:'${productName}-v${version}-win32-setup.${ext}',target:[{target:'nsis',// 利用nsis制作安装程序arch:[// 这个意思是打出来32 bit + 64 bit的包,但是要注意:这样打包出来的安装包体积比较大,所以建议直接打32的安装包。// 'x64', // 64位'ia32'// 32位]}]}}}}
Electron & web 开发
ok,到这里,稍微总结一下,Electron相对于web开发的区别,主要体现在四个方面:
- 第一,主进程与渲染进程;
- 第二,进程间的通信;
- 第三,需要用到很多原生能力;
- 第四,可以较大程度上释放前端的想象里,跳出浏览器的一些限制,能做的事情更多;
解决跨域问题的三种方式:
- 关闭web安全性,
webSecurity
; - 使用NodeJS发送请求;
- 使用Electron net API发送请求;
这些都不会被浏览器的跨域请求所影响,而且可以做到请求的时候少一个option请求,也不会被浏览器同域名6个请求所限制住。
问题与挑战
除了上述的一些优势,electron也带来了一些问题和挑战。
- 应用端开发有一个很重要特点,就是它的发布频率更低,我们又有非常多的 Web 业务要集成,对代码质量要求会更高;
- 其次,要面对渲染进程和主进程的交互,以及非常多的系统交互,排查问题的链路更长;
- 最后,因为包是在用户的系统上,我们也会面临更多的安全性要求。
参考链接
Electron 官方文档
Electron Playground
umi-plugin-electron-builder
…
版权归原作者 逸尘️ 所有, 如有侵权,请联系我们删除。