Electron 企业级应用开发实战(二)

news2024/12/26 23:07:44

这一讲会重点介绍如何集成 Node.js、使用 preload 脚本、进程间双向通信、上下文隔离等,为大家揭开 Electron 更强大的能力。

集成 Node.js

企业级桌面应用的资源都是本地化的,离线也能使用,所以需要把 html、js、css 这些资源都打包进去,接下来我们就在 src/renderer 目录下创建 index.html 和 index.js 两个文件:

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>Electron Desktop</title></head><body><p id="platform">操作系统:</p><p id="release">版本号:</p><script src="./index.js"></script></body>

</html> 

然后在创建窗口函数里面把用 loadURL 加载网页的代码换成 loadFile 加载本地文件:

function createWindow() {mainWindow = new BrowserWindow({ width: 800, height: 600 })mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
} 

这样就可以加载本地 HTML 文件了,接下来要实现本节第一个需求:

  • 获取用户当前操作系统及其版本号并展示在页面上

传统的 Web 网页运行在浏览器沙箱环境里面,没有能力调用操作系统 API,但是 Electron 就不一样了,它支持在 Web 中执行 Node.js 代码。不过这个能力默认是不开启的,要想使用这个能力,必须在创建窗口的时候指定两个参数:

  • nodeIntegration: true:开启 node.js 环境集成
  • contextIsolation: false:关闭上下文隔离
function createWindow() {mainWindow = new BrowserWindow({width: 800,height: 600,webPreferences: {nodeIntegration: true,contextIsolation: false,},})mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
} 

然后就可以在 src/renderer/index.js 中调用 node.js 的方法:

const os = require('os')
const platform = os.platform()
const release = os.release()
document.getElementById('platform').append(platform)
document.getElementById('release').append(release) 

运行程序会发现操作系统和版本号已经显示出来了:

使用 preload 脚本

直接在网页上调用 node.js 的 API 虽然很爽,但是风险极大,尤其是加载一个第三方的 Web 页面的时候,可能会被植入恶意脚本(例如调用 fs 模块删除文件等)。因此,Electron 官方不推荐开启 nodeIntegration,而是建议大家使用加载 preload 脚本的方式:

function createWindow() {mainWindow = new BrowserWindow({width: 800,height: 600,webPreferences: {nodeIntegration: false, // 不开启 node 集成preload: path.join(__dirname, '../preload/index.js'), // 在 preload 脚本中访问 node 的 API},})mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
} 

preload 脚本是特殊的 JS 脚本,由 Electron 注入到 index.html 当中,会早于 index.html 文件中引入的其他脚本,而且它有权限访问 node.js 的 API,无论用户是否开启了 nodeIntegration。我们把 src/renderer/index.js 的内容删除,改成仅打印一行文字:

console.log('renderer index.js') 

然后在 src 目录下新增 preload/index.js 文件,代码为:

console.log('preload index.js')
console.log('platform', require('os').platform()) 

运行之后观察一下控制台输出,可以发现 preload/index.js 代码先执行,renderer/index.js 代码后执行,而且 preload 中可以直接调用 node.js 的 API:

如果你在运行的时候报错了,提示 module not found:

这是因为从 Electron 20 版本开始,渲染进程默认开启沙箱模式,需要指定 sandbox: false才行,具体细节可参与官方文档。

有一点需要特别注意的是:preload.js 脚本注入的时机非常之早,执行该脚本的时候,index.html 还没有开始解析,所以不能立即操作 DOM,需要在 DOMContentLoaded 事件之后再操作:

const os = require('os')
const platform = os.platform()
const release = os.release()

document.addEventListener('DOMContentLoaded', () => {document.getElementById('platform').append(platform)document.getElementById('release').append(release)
}) 

主进程和渲染进程

主进程运行在一个完整的 Node.js 环境中,负责控制整个应用的生命周期,并管理渲染进程。Electron 依据 package.json 的 main 字段作为主进程的入口文件,在我们的项目中就是 src/main/index.js 文件,先注释掉 createWindow 那行代码,然后启动应用:

app.whenReady().then(() => {// createWindow()
}) 

去活动监视器中查看,发会现启动了三个进程:

pid 为 8188,名称为 Electron 的进程就是主进程,那多出来的两个进程是做什么的呢?打开活动监视器,强制退出 Electron Helper 进程,然后观察控制台输出:

通过错误信息中我们知道 Electron Helper 进程是负责网络服务的,而且有保活机制,如果 crash 的话会被重新拉起,Electron Helper (GPU) 从名称上就能知道是负责 GPU 渲染的,也被主进程保活了,所以一个 Electron 应用至少会存在上述三个进程。实际上,一个 Electron 应用最多有五类进程:

  • Electron
  • Electron Helper
  • Electron Helper (GPU)
  • Electron Helper (Plugin)
  • Electron Helper (Renderer)

Electron 进程就是主进程,剩下的四个进程都是主进程创建出来的,用于辅助主进程完成对应任务。在 macOS 系统下右键进入到 Electron.app 里面,在 Contents/Frameworks 目录下可以看到它们的身影:

它们的作用分别是:

进程名中文名作用
Electron主进程负责界面显示、用户交互、子进程管理,控制应用程序的地址栏、书签,前进/后退按钮等,同时提供存储等功能
Electron Helper网络进程负责页面的网络资源加载
Electron Helper (Renderer)渲染进程负责网页排版和交互(排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中)
Electron Helper (GPU)GPU 进程负责 GPU 渲染
Electron Helper (Plugin)插件进程负责插件的运行

如果我们修改代码,调用三次 createWindow 函数:

app.whenReady().then(() => {createWindow()createWindow()createWindow()
}) 

运行之后会创建三个窗口:

活动监视器中也会出现三个 Electron Helper (Renderer) 渲染进程:

但是如果你以为每个窗口就对应一个渲染进程,那就大错特错了!真正产生渲染进程的不是 createWindow,而是里面的 loadFile/loadURL 函数,不信你把 loadFile 那一行注释掉:

function createWindow() {mainWindow = new BrowserWindow({ /* 代码省略 */ })
	// 下面的代码才是产生渲染进程的原因// mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
} 

会发现窗口了三个空白窗口,里面什么内容都没有,而且活动监视器中没有找到任何 Electron Helper (Renderer) 进程。

进程间通信

Electron 为主进程和渲染进程分别提供了对应的 API:

由于渲染进程中无法直接调用主进程的 API,但有些场景需要在渲染进程知道主进程 API 的结果,例如操作系统会允许用户设置外观的主题色:

如果现在要做一个功能,实现两个需求:

  • 判断系统是否是深色模式
  • 可切换应用主题色为深色或浅色

首先把页面布局搭好:

<body><p id="isDarkMode">当前系统是采用暗黑模式:</p><button onclick="setTheme('light')">设置浅色</button><button onclick="setTheme('dark')">设置深色</button>
</body> 

判断系统是否为深色模式,需要用到主进程提供的 nativeTheme API,把下面的代码加到 src/main/index.js 里面可以打印出调用结果:

const { nativeTheme } = require('electron')
console.log('isDarkMode', nativeTheme.shouldUseDarkColors) 

那渲染进程如何拿到这个结果呢?这就用到了进程间通信的能力了,Electron 为开发者封装了三种通信的方式:

  • sendSync & returnValue
  • send & reply
  • invoke & handle

sendSync & returnValue

这是同步调用的方式,渲染进程的代码为:

const value = ipcRenderer.sendSync('isDarkMode')
console.log('sendSync reply', value) 

主进程代码:

ipcMain.on('isDarkMode', (event, args) => {event.returnValue = nativeTheme.shouldUseDarkColors
}) 

send & reply

异步回调的方式,渲染进程代码:

ipcRenderer.send('isDarkMode')
ipcRenderer.on('isDarkMode', (event, value) => {console.log('on reply', value)
}) 

主进程代码:

ipcMain.on('isDarkMode', (event, args) => {event.reply('isDarkMode', nativeTheme.shouldUseDarkColors)
}) 

invoke & handle

异步 Promise 方式,渲染进程代码:

ipcRenderer.invoke('isDarkMode').then((value) => console.log('invoke reply', value)) 

主进程代码:

ipcMain.handle('isDarkMode', (event, args) => {return nativeTheme.shouldUseDarkColors
}) 

这里推荐大家使用 invoke & handle 组合来进行通信,不过需要注意:相同的事件名称,on 方法可以注册多次,但是 handle 方法只能注册一次,否则会报错:

App threw an error during load
Error: Attempted to register a second handler for 'isDarkMode'at IpcMainImpl.handle (node:electron/js2c/browser_init:193:325) 

最后用一张图总结一下进程间通信:

上下文隔离

上面讲到,preload.js 脚本中可以访问 node.js 的 全部API 和 Electron 提供的渲染进程 API,这个脚本最终也是会注入到 index.html 页面里面的,在 webPreferences 的选项当中有个 contextIsolation 配置,表示是否开启上下文隔离(默认开启),它的具体含义为:

preload.js 脚本和 index.html 是否共享相同的 document 和 window 对象

为了让大家更直观的理解这个概念,我们来完成上一节的第二个需求:

  • 可切换应用主题色为深色或浅色

在 preload.js 中增加以下代码:

window.setTheme = (theme) => {ipcRenderer.invoke('setTheme', theme)
} 

当点击设置浅色/深色按钮的时候,发现报错了:

原因就在于默认开启了 contextIsolation 导致的,preload.js 中的 window 和 index.html 中的 window 不是同一个对象,我们把它给关掉再试试:

mainWindow = new BrowserWindow({width: 800,height: 600,webPreferences: {nodeIntegration: false,contextIsolation: false, // 关闭上下文隔离sandbox: false,preload: path.join(__dirname, '../preload/index.js'),},
}) 

再点击报错就消失了。这是怎么回事呢?在开启 contextIsolation 选项之后,点击控制台 top 箭头,发现有两个上下文:

  • top:网页 index.html 的上下文
  • Electron Isolated Context:preload.js 的隔离上下文

这两个上下文之间不同享全局变量,例如 document、window 等。

上下文(context)其实是 V8 中的全局作用域的概念,每个上下文中都会有一个独立的 window 对象,它们彼此之间是隔离开来的,有各自的全局作用域、全局变量和原型链。

所以在 preload.js 中给 window 变量添加属性,外面是拿不到的,只能通过另外一个 contextBridge API 来暴露:

const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('preloadApi', {setTheme: (theme) => {ipcRenderer.invoke('setTheme', theme)},
}) 

调用方式为 window.preloadApi.setTheme,因此需要把 index.html 改成这样:

<body><p id="isDarkMode">当前系统是采用暗黑模式:</p><button onclick="preloadApi.setTheme('light')">设置浅色</button><button onclick="preloadApi.setTheme('dark')">设置深色</button><style> @media (prefers-color-scheme: dark) {body {background-color: black;color: white;}} </style>
</body> 

然后在主进程中响应 setTheme 调用,设置应用的主题色:

ipcMain.handle('setTheme', (event, theme) => {nativeTheme.themeSource = theme
}) 

最终实现的效果如下:

总结

Electron 与浏览器最大的区别就是集成了 Node.js 的能力,不过能力越强风险也越大,如何在其中找到一个平衡点呢?

  • Electron 允许开发者指定本地 preload 脚本,让其拥有无条件访问 Node.js 的权利,并且在页面开始加载之前就注入进去
  • Electron 为了避免 preload 脚本污染页面全局变量,提供了上下文隔离的能力
  • Electron 三种进程间通信的方式,极大地方便了开发者在渲染进程中调用主进程 API

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/140348.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

独立光伏-电池-柴油发电机组的能源管理系统的主干网研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

ESPI3接收机

18320918653 ESPI3 ESPI3|R&S ESPI3|二手EMI接收机|EMI预认证测试接收机|罗德与施瓦茨|EMC接收机|9KHz至3GHz品 牌&#xff1a;德国罗德与施瓦茨 | R&S | Rohde&Schwarz处于预认证级别的 R&S ESPI测试接收机有两种型号, 集成了罗德与施瓦茨公司认证级EMI测试…

springboot:除了OpenOffice还可以用它轻松实现文档在线预览功能【附带源码】

0. 引言 我们在项目中常常需要实现文档在线预览的功能&#xff0c;而文档种类繁多&#xff0c;除了pdf&#xff0c;还有word、text、excel、甚至还有mp3,mp4等多媒体文件。常用的手段是通过OpenOffice来将文档转换为pdf实现预览&#xff0c;本期我们就来看如何通过kkFileView实…

rabbitmq基础10——消息追踪、Shovel插件的web端使用和命令使用

文章目录一、消息追踪1.1 Firehose功能1.1.1 开启与关闭1.1.2 测试1.1.3 总结1.2 rabbitmq_tracing 插件1.2.1 定义trace规则1.2.2 测试1.2.2.1 与Firehose之间的优先级二、Shovel插件2.1 实现原理2.1.1 从队列到交换器2.1.2 从队列到队列2.1.3 交换器到交换器2.2 Shovel 插件使…

大小端转换

一、名词解释首先解释一下大端模式和小端模式。小端模式&#xff0c;也叫小端存储&#xff1a;Little-Endian就是低位字节排放在内存的低地址端&#xff0c;高位字节排放在内存的高地址端。大端模式&#xff0c;也叫大端存储&#xff1a;Big-Endian就是高位字节排放在内存的低地…

2022年度技术总结

2022 年度总结 本年收获 计算机网络 2022年2月&#xff0c;系统学习巩固了计算机网络课程&#xff08;本科&#xff09;&#xff0c;基本的七层模式&#xff0c;四层模式&#xff0c;重点是与前端开发相关的 TCP UDP HTTP HTTPS 等协议。 现在有一个整体的认识&#xff1a;…

@Transactional事务处理解决方案的看法

Transactional事务处理解决方案的看法前言一.声明式事务二.编程式事务三.事务粒度优化方法四.缓存和事务的一致性五.介绍--延时双删总结前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 本文就是了解一下声明式事务和编程式事务的优缺点和事务一致性的一…

怎样阅读NLP论文

经典的论文也是需要读的。并不是所有的论文都值得细读。论文不是从头赶着朝下读。 目录收集和组织论文收集组织1.通过会议的方式分类2.是否是arXiv上的文章分类&#xff08;preprint or not&#xff09;3.根据问题&#xff08;推荐&#xff09;&#xff0c;方法和数据集分类选择…

Ka波段卫星通信小尺寸无线电设计

传统Ka波段地面站卫星通信系统依赖于室内到室外配置。室外单元包含天线和块下变频接收机&#xff0c;接收机输出L波段的模拟信号。该信号随后被传送到室内单元&#xff0c;室内单元包含滤波、数字化和处理系统。Ka波段的干扰信号通常较少&#xff0c;因此室外单元的主要任务是以…

微信小程序开发整体过程整理

目录1微信开发相关介绍1.1微信公众平台1.2微信开放平台1.3注意事项2微信小程序开发整体介绍2.1微信小程序简介2.2小程序接入流程3框架简介3.1uni-app简介3.2学习使用uni-app3.3学习微信小程序开发4开发规范5开发示例5.1开发工具5.2开发调试5.2.1导入代码5.2.2项目运行5.2.3在微…

第三篇 - 对象的单层劫持

一&#xff0c;前言 上篇&#xff0c;介绍了 Vue 使用及数据初始化的流程 回顾一下&#xff0c;主要涉及以下几个核心点&#xff1a; initMixin 方法&#xff1a; 原型方法 Vue.prototype._initvm.$options&#xff1a;使 options 选项在 vm 实例上被共享initState 方法&…

c#入门-匿名函数,多播委托

匿名函数 如果一个函数的参数是一个委托类型。而你此刻没有合适的方法组使用&#xff0c;也不想为他专门声明一个局部函数。 则可以使用匿名函数。匿名函数的创建更为简单&#xff0c;语法为&#xff1a;返回类型 参数列表 > 函数主体 Func<int, string> func str…

人工智能OCR文字识别研究

1 研究背景 人工智能是研究开发能够模拟、延伸和扩展人类智能的理论、方法、技术及应用系统的一门新的技术科学&#xff0c;研究目的是促使智能机器会听&#xff08;语音识别、机器翻译等&#xff09;、会看&#xff08;图像识别、文字识别等&#xff09;、会说&#xff08;语音…

使用Jiralert实现AlertManager告警对接Jira

简介 Alertmanager 处理由客户端应用程序&#xff08;如 Prometheus server&#xff09;发送的警报。它负责去重(deduplicating)&#xff0c;分组(grouping)&#xff0c;并将它们路由(routing)到正确的接收器(receiver)集成&#xff0c;如电子邮件&#xff0c;微信&#xff0c…

MMYOLO 自定义数据集从标注到部署保姆级教程

theme: juejin 来自社区 PeterH0323 投稿 AI 已经被应用到各行各业&#xff0c;现如今任何人都可以轻松基于开源框架快速搭建符合自身需求的 AI 应用。本文将基于 MMYOLO 开源框架&#xff0c;基于生活中收集的猫猫数据集&#xff0c;教你如何从零开始训练一个可部署检测模型…

TiCDC 源码阅读(二)TiKV CDC 模块介绍

内容概要 TiCDC 是一款 TiDB 增量数据同步工具&#xff0c;通过拉取上游 TiKV 的数据变更日志&#xff0c;TiCDC 可以将数据解析为有序的行级变更数据输出到下游。 本文是 TiCDC 源码解读的第二篇&#xff0c;将于大家介绍 TiCDC 的重要组成部分&#xff0c;TiKV 中的 CDC 模…

【C++】命名空间(namespace) 以及理解using namespace std

命名空间1.命名空间使用的背景1.背景2.命名空间的定义&#xff08;namespace&#xff09;2.1正常的定义2.2 命名空间可以嵌套定义2.3允许命名空间相同3.域作用限定符&#xff08;&#xff1a;&#xff1a;&#xff09;和命名空间的使用3.1域作用限定符&#xff08;&#xff1a;…

【nodejs】模块化

一、概念 1、模块化 编程领域中的模块化&#xff0c;就是遵守固定的规则&#xff0c;把一个大文件拆成独立并相互依赖的多个小模块 把代码进行模块化拆分的好处&#xff1a; 1、提高代码的复用性 2、提高代码的可维护性 3、可以实现按需加载 2、模块化规范 对代码进行模块化…

《CSS新世界》读书笔记

前言 本文为《CSS新世界》的读书笔记。推荐去读原著。 《CSS新世界》微信读书APP链接&#xff1a;CSS新世界-张鑫旭-微信读书 (qq.com) 1. 尺寸属性值&#xff1a;fit-content 描述 fit-content 不是一个属性&#xff0c;它是 css 尺寸系列属性的一个新属性值。可用在 wid…

lammps教程:旋转模型的技巧

大家好&#xff0c;我是小马老师。 本文介绍lammps翻转模型的方法。 在进行分子动力学模拟时&#xff0c;可能需要特定的面位于设定的方向。 如Al2O3的力学性能模拟中&#xff0c;需要分别对A、B、C面进行压痕或者摩擦模拟。 按照研究界面垂直z轴&#xff0c;并且面法线沿着z轴…