Electron 初识和入门学习 - 风之涯技术博客

标签搜索

Electron 初识和入门学习

小峰
2022年08月30日 / 0 评论 / 46 阅读 / 正在检测是否收录...
Electron简介
  1. Electron是使用JavaScript,HTML和CSS构建跨平台的桌面应用程序框架。 Electron兼容Mac、Windows和Linux,可以构建出三个平台的应用程序,很大一部分插件都基于NodeJs环境运行。是当下最便捷的桌面端应用开发者福音。
  2. 最初被GitHub开发,2013年4月11日以Atom Shell为名起步,2014年5月16日开源,2015年4月17日改名为Electron。
  3. 若想开发一个兼容多平台的桌面应用,以往常用的技术框架有GTK、QT等,这些框架受语言限制,且学习成本较高,效率有限。目前一些基于前端技术的hybrid框架很流行,且已经在多个领域得到了广泛的应用和验证,比如利用前端技术+相应的打包工具可开发适配多平台的应用(PC、微信公众号、小程序、Android、IOS等)。Electron就是这样一款框架,为前端技术人员利用web前端技术开发桌面应用带来了可能,开发人员可利用已经掌握的前端技术如Html、CSS、JavaScript,以及结合一些前端技术框架:Vue、Angular、React、webpack,加之浏览器渲染引擎、Electron封装的系统API快速实现一款桌面应用的开发,Electron做了大部分复杂的工作,开发人员只需要专注在核心业务和前端技术本身。同时,通过一定的优化,Electron可以做到很好的体验。
  4. 目前有不少知名桌面应用采用Electron开发,如:开发人员熟知的Visual Studio Code、MongoDB桌面版管理工具、Skype桌面版、WhatsApp桌面版、HTTP网络测试工具Postman等。
Electron组成

  • Chromium : 为Electron提供了强大的UI能力,可以不考虑兼容性的情况下,利用强大的Web生态来开发界面。(本质上就是chromium(chrome开源版本)浏览器,有最新的东西都会在chromium测试,所以electron可以体验最新的api,这也是好处之一)
  • Chromium简介
     主进程中的RenderProcessHost和 render 进程中的RenderProcess是用来处理进程间通信的(IPC(Inter-Process Communication,进程间通信)。
     Render 进程中的 RenderView 内容基于 WebKit(浏览器引擎) 排版展示出来的。
     Render 进程中的ResourceDispatcher是用来处理资源请求的。Render 进程中如果有请求则创建一个请 求 ID,转发到 IPC,由 Browser 进程中处理后返回

Chromium 是多进程架构,包括一个主进程,多个渲染进程

  • Node.js :让Electron有了底层的操作能力,比如文件的读写,甚至是集成C++等等操作,并可以使用大量开源的npm包来完成开发需求,是非常方便的。
  • Native API : Native API让Electron有了跨平台和桌面端的原生能力,比如说它有统一的原生界面,窗口、托盘、消息通知这些,让它更具有桌面端的能力。
Electron架构

Electron 架构和 Chromium 架构类似,也是具有1个主进程和多个渲染进程。但是也有区别

  • 在各个进行中暴露了 Native API ,提供了 Native 能力。
  • 引入了 Node.js,所以可以使用 Node 的能力
  • 但是渲染进程使用node 需要配置,下文会有所提到

可以简单的理解为Electron为web项目套上了Node.js环境的壳,使得我们可以调用Node.js的丰富的API。这样我们可以用JavaScript来写桌面应用,拓展很多我们在web端不能做的事情。


示意图

Electron 进程

electron核心我们可以分成2个部分,主进程和渲染进程。

主进程

Electron 运行 package.json 的 main 脚本的进程被称为主进程 (只有一个)

主进程特点

  • 主进程连接着操作系统和渲染进程,可以把她看做页面和计算机沟通的桥梁。
  • 进程间通信、窗口管理
  • 全局通用服务。
  • 一些只能或适合在主进程做的事情。例如浏览器下载、全局快捷键处理、托盘、session。
  • 维护一些必要的全局状态
渲染进程

渲染进程就是我们所熟悉前端环境了。只是载体改变了,从浏览器变成了window.
注:出于安全考虑,渲染进程是不能直接访问本地资源的),因此都需要在主进程完成。

渲染进程特点

  • Electron 使用了 Chromium 来展示 web 页面,所以 Chromium 的多进程架构也被使用到。
  • 每个web页面运行在它自己的渲染进程中。每个渲染进程都是相互独立的,并且只关心他们自己的网页。
  • 使用BrowserWindow类开启一个渲染进程并将这个实例运行在该进程中,当一个BrowserWindow实例被销毁后,相应的渲染进程也会被终止。
  • 渲染进程中不能调用原生资源,但是渲染进程中同样包含Node.js环境,所以可以引入Node.js(下文会提到:nodeIntegration )

    主进程与渲染进程的区别
  • 主进程使用 BrowserWindow 实例创建网页。
  • 每个 BrowserWindow 实例都在自己的渲染进程里运行着一个网页。当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终止。
  • 主进程管理所有页面和与之对应的渲染进程。
  • 由于在网页里管理原生 GUI 资源是非常危险而且容易造成资源泄露,所以在网页面调用 GUI 相关的 APIs 是不被允许的。如果你想在网页里使用 GUI 操作,其对应的渲染进程必须与主进程进行通讯,请求主进程进行相关的 GUI 操作。

可以理解为Electron是一个浏览器容器

 Chrome(或其他浏览器)的每个标签页(tab)及其页面,就好比 Electron 中的一个单独渲染进程。即使关闭所有标签页,Chrome 依然存在。这好比 Electron 的主进程,能打开新的窗口或关闭这个应用。

Electron 优缺点
优点
  • 上手简单
    • HTML、CSS、JS、Node 。npm包、UI框架 ,方便高效,能很轻松的实现很好看的UI
  • 多端运行
    • 快速构建“跨平台”(Windows、MacOs、Linux)的桌面级应用
  • 开发时间短
    • 相对其他跨平台方案(如 QT GTK+ 等),更稳定,bug少, 毕竟只要浏览器外壳跑起 来了就可以了,当然坑是少不了的
  • 再也不用兼容多浏览器
    • 只针对谷歌 但要兼容mac、Linux
缺点
  • 安装包体积略大(打包了Chromium) 至少包含了一个浏览器的体积 ,每装一个 app 就相当于装一个 chrome
  • 性能不如原生应用,mac下丝滑一些,window就有点丢帧
  • 卡、启动慢、新开一个进程,起步价就是一个nodejs的内存开销
  • loadURL加载远程页面白屏事件长,优化可采用 vscode 骨架屏
创建一个最简单的项目

目录结构

  • index.html
  • main.js
  • package.json

生成package.json文件并修改

npm init
 
// 修改package.json
{
  "name": "firstApp",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "devDependencies": {
    "electron": "^13.6.9"
  }
}

注意:控制台中文乱码可以使用

"start": "chcp 65001 && electron ."
// utf8的值是65001

安装 Electron

npm install electron --save-dev
// npm 安装会十分的慢,甚至是失败,可以先使用cnpm 淘宝镜像去下载,速度快

创建main.js文件

const { app, BrowserWindow } = require('electron') 
 
let mainWindow = null ;
app.on('ready',()=>{
    mainWindow = new BrowserWindow({ 
        width:500,
        height:500,
        webPreferences:{ 
          nodeIntegration:true //设置为true就可以在这个渲染进程中调用Node.js
        }
    });
 
    mainWindow.loadFile('index.html'); // 加载本地文件
    // mainWindow.loadURL('https://zhuiyi.ai/'); // 加载远程文件
 
    mainWindow.webContents.openDevTools({ mode: 'bottom' }); // 控制台开关
  
    mainWindow.on('close',(e)=>{ 
        // 在窗口要关闭的时候触发
                e.preventDefault(); // 避免进程意外关闭导致进程销毁
    });
 
    mainWindow.on('closed',()=>{ 
       // 当窗口已经关闭的时候触发
    });
 
});

app 模块是为了控制整个应用的生命周期而设计。

BrowserWindow参数: BrowserWindow | Electron 中文文档

创建html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello World!</title>
</head>
<body>
    <h1>Hello World!</h1>
</body>
</html>

启动项目

electron . //或者用npm start 

这个过程对于初学者而言,可能不好理解,但是你需要记住这个流程,只有我们记住这个流程后,在以后程序出现问题时,才可以很快的定位问题.

自定义菜单

需要使用的模块

  • menu 模块
  • menu类可以用来创建原生菜单,它可用作应用菜单和 context 菜单,这个模块是一个主进程的模块,并且可以通过 remote 模块给渲染进程调用,每个菜单有一个或几个菜单项 menu items,并且每个菜单项可以有子菜单.
  • Tray 模块
  • 用一个 Tray 来表示一个图标,这个图标处于正在运行的系统的通知区 ,通常被添加到一个 context menu 上.
  • remote 模块新版本已经取消该模块
  • 提供了一种在渲染进程(网页)和主进程之间进行进程间通讯(IPC)的简便途径。

窗口菜单

// 创建菜单模板
const template = [{
      label: '文章',
      submenu: [
        {
          label: '精品文章',
          click:()=>{
            console.log('精品文章');
          }
        },
        {label: '技术文档'}
      ]
    },
    {
      label: '分类',
      submenu: [
        {label: '短文'},
        {label: '文案'}
      ]
    }
  ];
// 一定是在ready生命周期中 创建进程时设置菜单
app.on('ready', () => {
  const m = Menu.buildFromTemplate(template); // 创建菜单模板
  Menu.setApplicationMenu(m);
});
// Menu属于是主线程下的模块,要在渲染进程中使用的话需要借助 remote模块

自定义右键菜单

 const {remote} = require('electron')
 const rigthTemplate = [{
          label: '粘贴',
          click: ()=>{/*点击事件*/}
        },
        {
          label: '复制'
        },
        {
          label: '保存'
        }
      ]
const m = remote.Menu.buildFromTemplate(rigthTemplate);
window.addEventListener('contextmenu', function (e) {
    //阻止当前窗口默认事件
  e.preventDefault();
  //把菜单模板添加到右键菜单
  m.popup({
      window: remote.getCurrentWindow()
  });
});

渲染进程使用remote模块的前提需要打开允许在remote中使用

 webPreferences: {
    enableRemoteModule: true // 在初始化窗口的时候 允许渲染进程使用Remote模块 否则报错
 }

托盘图标菜单

let appIcon = null;
app.on('ready', function(){
  appIcon = new Tray(require('path').join(__dirname,'./assets/a.png')); // 最好用path.join绝对位置方式应用图片
    cosnt contextMenu = Menu.buildFromTemplate([ //菜单配置
        {
            label: '退出',
            click: function () {
                app.quit();
            }
        }
    ]);
    // 设置托盘悬浮提示
    appIcon.setToolTip('提示内容');
    // 设置托盘菜单
    appIcon.setContextMenu(contextMenu);
    // 单击托盘小图标显示应用
    appIcon.on('click', function () {
        // 显示主程序
      if(currentWin.isVisible()){
         currentWin.hide();
      }else{
         currentWin.show();
      }
    });
});

注意:appIcon 必须声明在app.on('ready)之外,防止被垃圾回收机制回收,导致托盘图标消失let appIcon = null;

进程间通信

主进程和渲染进程之间可以通过ipcRenderer 模块ipcMain 模块通信。

通信使用到的模块

  • ipcMain 模块
    • 是类 EventEmitter 的实例.当在主进程中使用它的时候,它控制着由渲染进程(web page)发送过来的异步或同步消息.从渲染进程发送过来的消息将触发事件.
  • ipcRenderer 模块
    • 是一个 EventEmitter 类的实例. 它提供了有限的方法,你可以从渲染进程向主进程发送同步或异步消息. 也可以收到主进程的相应.

主进程 => 渲染进程

// 入口文件 main.js中 例如菜单中触发
 {
     label: '文章',
     click:()=>{
         console.log('精品文章 ');
         currentWin.webContents.send('message','我是主进程发送的参数');
    }
}
 
// 渲染进程 / web页面中 html (第一种方式)
 <script>
        const {ipcRenderer} = require('electron')
        ipcRenderer.on('message', (event, data) => {
           console.log('>>>>>>>>params',data) //我是主进程发送的参数
        });
</script>
 
//在创建窗口时  (第二种方式)  现阶段已经不推荐这样使用 容易报错坑多
currentWin.webContents.executeJavaScript(`
    const {ipcRenderer} = require('electron')
    ipcRenderer.on('message', (evt, data) => {
       console.log('>>>>>>>>params',data) //我是主进程发送的参数
        });
`)

进程 => 渲染进程

// 渲染进程1   web 页面中
<script>
  const {ipcRenderer} = require('electron')
   ipcRenderer.send('params','9453913')
</script>
 
// 渲染进程2   web 页面中
<script>
    const {ipcRenderer} = require('electron')
    ipcRenderer.on('params','9453913')
</script>
// 主进程作为中转
// 渲染进程1 -> 主进程 -> 渲染进程2
// 渲染进程2 -> 主进程 -> 渲染进程1
项目打包

通过electron-packager及electron-builder两种方式,将已有的electron应用打包成msi格式和exe可执行文件

npm install electron-builder --save-dev

在package.json中做如下配置

"scripts": {
      "start": "electron .",
      "pack": "electron-builder --win --x64"
},
"build": {
    "appId": "test.app",
    "productName": "测试项目",
    "mac": {
      "target": ["dmg","zip"]
    },
    "win": {
      "target": ["nsis","zip"]
    }
}

注意:mac的dmg包 需要macos签名,所以最好mac打dmg的包

常见的报错

require is not defined

远程渲染页面(loadURL),页面使用require('electron'),容易报require 错误 undefined等,以下方法可解决

// 配置窗口时 添加 preload
webPreferences: {
    enableRemoteModule: true // 在初始化窗口的时候 允许渲染进程使用Remote模块 否则报错
    nodeIntegration: true, //渲染进程允许使用node
    preload: path.join(__dirname, '../static/preload.js'), // electron 与 独立vue项目使用
}     

preload.js

/**
 * 挂在electron 在全局 在远程页面中可以读取
 */
window.electron = require('electron');
currentWin.webContents.executeJavaScript(不推荐使用)

executeJavaScript 有一些项目可能会使用到,但是基本不会用到,除非植入js代码,但是这效率很低,不如直接用Puppeteer插件

集成VUE

在下一篇相关文章介绍如何集成VUE,使用Vue更方便,可以直接引用现有的已经集成好的,我个人更喜欢直接上js。

0

评论 (0)

QQ
昵称
邮箱
取消