前言
上一篇文章:Puppeteer基础入门、常见应用、利用谷歌插件编写Puppeteer脚本,简单介绍了Puppeteer的基本使用,以及如何编写一个脚本。
但是呢脚本的运行需要在node环境里,开发人员可能没什么问题。但是如果你写的这个脚本要给非开发人员使用呢?那么能不能做一个可视化的界面呢?
这时候想起了Electron,Electron可以用于构建桌面程序,而且嵌入了Chromeium
和 Node.js
,这不是刚刚好吗
Electron文档
https://www.electronjs.org/
Puppeter文档
https://puppeteer.bootcss.com/
准备
搭建一个Electron项目
现在搭建项目更加的方便了,可以直接使用Vite
提供的模板。看个人的选择,我这里选择
electron-vite-vue - Electron + Vite + Vue template.
模板比较简介,方便我这样刚入门的使用。当然也可以多下载几个模板,看看效果,把优点整合一下,开发一个适合子级的模板。
模板选择
项目下载下来后,安装依赖并运行,常用的依赖都已经安装好了,还是很不错的
# 安装依赖
npm install
# 运行
npm run dev
# 打包
npm run build
运行效果
安装依赖
安装一下element-plus
、axios
、vue-router
方便后续的开发使用
npm install element-plus vue-router axios pinia --save
修改配置文件
main.ts
import { createApp } from 'vue'
import "./style.css"
import App from './App.vue'
import './samples/node-api'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import { createPinia } from 'pinia'
import router from './router'
const app = createApp(App)
const store = createPinia()
app
.use(ElementPlus)
.use(store)
.use(router)
.mount('#app')
.$nextTick(() => {
postMessage({ payload: 'removeLoading' }, '*')
})
修改后
安装puppeteer
npm install puppeteer.11.1
通信
vue里面是无法直接调用脚本的,需要在vue组件里向Electron发送消息,Electron接收到消息后执行相应的命令。
现在我们看一下electron
这个目录,electon/main
这个目录存放的是主进程,electon/preload
这个目录放的是预加载脚本
主进程
主进程是Electron应用程序的主要进程,负责创建和控制所有的渲染进程和窗口。它是运行在Node.js环境中的一个进程,可以使用Electron提供的API来访问底层操作系统功能。主进程负责管理应用程序的生命周期、处理系统级的事件、创建和销毁渲染进程等。
主进程通常是通过一个JavaScript文件(通常命名为main.js)来定义和创建的。在主进程中,你可以使用Electron提供的API,如创建窗口、访问操作系统资源、处理系统级的事件等。
预加载脚本
预加载脚本是在渲染进程运行之前被加载和执行的脚本。它可以在渲染进程中访问Node.js的API,这样你就可以在渲染进程中使用一些Electron提供的API,而无需通过与主进程进行通信。预加载脚本在渲染进程创建之前被注入,可以在创建Web页面时通过preload选项指定。
预加载脚本通常用于在渲染进程中执行一些需要使用Node.js API的操作,例如访问文件系统、操作数据库等。它可以在渲染进程中提供一些额外的功能和权限。
需要注意的是,预加载脚本运行在渲染进程的沙箱环境中,它们与主进程是隔离的,因此应该谨慎处理涉及安全性和权限的操作。
vue与electron之间的通信
vue向electron通信
首先,在你的Vue组件中,使用Electron提供的remote模块获取当前渲染进程的BrowserWindow实例对象,然后使用这个对象来给主进程发送消息。示例代码如下:
// 导入ipcRenderer模块
const { ipcRenderer } = window.require('electron')
// 向Electron主进程发送消息
ipcRenderer.send('toMain', 'message content')
然后,在你的Electron主进程中监听toMain事件,当接收到该事件时执行相应的脚本。示例代码如下:
import { ipcMain } from 'electron'
// 监听toMain事件
ipcMain.on('toMain', (event, message) => {
// 执行相应的脚本
})
electron向vue通信
vue组件监听消息
// 导入ipcRenderer模块
const { ipcRenderer } = window.require('electron')
// 监听来自Electron主进程的消息
ipcRenderer.on('fromMain', (event, message) => {
// 处理从Electron主进程接收到的消息
})
electron发送消息
import { ipcMain } from 'electron'
// 监听来自Vue组件的消息
ipcMain.on('toMain', (event, message) => {
// 执行相应的脚本
// 获取所有打开的窗口
const windows = BrowserWindow.getAllWindows()
// 向所有窗口广播消息
windows.forEach(window => {
window.webContents.send('fromMain', 'response content')
})
})
electron与node脚本之间的通信
比如我要使用node脚本下载图片,需要electron向node脚本发消息,告诉下载后的这个图片叫什么名字吧。node脚本下载完图片是不是要向electron说一声我已经下载完图片了。
这里需要用到child_process
模块,child_process
模块提供了几个方法来创建子进程:exec
、spawn
和fork
。这些方法在以下方面有所不同:
-
exec
方法:exec
方法用于执行命令并获取其输出。它创建一个shell,并在该shell中执行指定的命令。当命令执行完成后,它提供了回调函数来获取命令的输出结果。这个方法适用于需要执行简单命令和获取其输出的情况。 -
spawn
方法:spawn
方法用于创建一个新的进程,并通过流的方式与其进行通信。它可以执行复杂的命令,并提供了标准输入、标准输出和标准错误流的实时数据传输。这个方法适用于需要与子进程进行实时交互的情况。 -
fork
方法:fork
方法是spawn
方法的一个特殊形式,用于创建一个新的Node.js进程。它可以在子进程中执行Node.js模块,并通过IPC(进程间通信)通道与父进程进行通信。这个方法适用于需要创建独立的Node.js进程,以便并行处理任务的情况。
总结一下:
exec
用于执行简单的命令并获取其输出结果。spawn
用于创建子进程并通过流与其进行实时通信。fork
用于创建独立的Node.js进程,并通过IPC与父进程进行通信。
前两个没有用到,因此没怎么研究,感兴趣的自行百度,这里主要说一下第三个fork
electron端
const { fork } = require('child_process');
// 创建子进程
const child = fork('child.js');
// 向子进程发送消息
child.send('Hello from main');
// 这是创建的窗口界面的对象
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true // 允许在渲染进程中使用 Node.js API
}
});
// 监听子进程的消息
child.on('message', (msg) => {
mainWindow.webContents.send('message-from-child', msg); // 向渲染进程发送消息
});
补充:
1、创建子进程的过程就会同时执行你的node脚本
2、 创建子进程时可以传递参数
// 创建子进程,并指定命令行参数和环境变量
const child = fork('child.js', ['arg1', 'arg2'], { env: { NODE_ENV: 'production' } });
子进程(你的脚本)
const { ipcMain } = require('electron');
// 监听主进程发送的消息
ipcMain.on('message-from-renderer', (event, msg) => {
console.log('Received message from renderer:', msg);
});
// 向主进程发送消息,也可以发送对象
process.send('Hello from child');
补充:
子进程可以通过 process.argv
属性获取到在启动子进程时传递的命令行参数。process.argv
是一个数组,包含了在启动 Node.js 进程时传递的所有命令行参数
console.log(process.argv); // 打印所有命令行参数
console.log(process.argv[0]); // 打印 Node.js 可执行文件路径
console.log(process.argv[1]); // 打印被执行的 JavaScript 文件路径
console.log(process.argv.slice(2)); // 打印用户传递的命令行参数
vue与node脚本间的通信
上面我们介绍的通信,都需要用到主进程,主进程相当于一个中转站的角色。那么能不能直接在vue组件里与node脚本进行通信,查了一下也是可以的,比如借助spawn
const { spawn } = require('child_process');
或
import { spawn } from 'child_process';
但是我不建议使用这种方式,因此也不打算深入研究了。
通过主进程的方式进行通信,可以保证所有的通信都能在主进程里找到,出现问题可以方便排查。可以设置公共的通信,通过传参来执行不同的脚本。
但是如果让vue组件直接与node脚本进行通信,如果组件少还可以。如果组件多了,一起开发的人多了很容易出现各种各样的问题。就比如我定义了一个a消息是用来下载图片的,另一个人定义了b来下载图片,可能还有c、d什么的,这样很容易导致项目变得越来越烂。
消息类型
以下是我结合gpt,已经自己应用总结的,不一定正确,如果有错误之处麻烦指出。
- 主进程与渲染进程之间的通信,消息类型类型可以自定义,只需要保证双方一致
主进程
import { app, BrowserWindow, shell, ipcMain } from 'electron'
// 监听toMain事件
ipcMain.on('toMain', (event, message) => {
console.log("toMain事件:", event, message)
// 获取所有打开的窗口
const windows = BrowserWindow.getAllWindows()
const date = new Date().toLocaleDateString()
// 向所有窗口广播消息
windows.forEach(window => {
window.webContents.send('fromMain', date)
})
})
渲染进程
const { ipcRenderer } = window.require('electron')
// 监听从Electron主进程接收到的消息
ipcRenderer.on('fromMain', (event, message) => {
console.log("fromMain", event, message)
date.value = message
})
// 向Electron主进程发送消息
ipcRenderer.send('toMain', 'message content')
- 主进程与子进程(node脚本)进行通信
主进程
const { fork } = require('child_process');
// 创建子进程,filePath是脚本路径
const child = fork(filePath,['screenshot_0.png']);
// 监听子进程的消息
child.on('message', (data) => {
console.log("success:",data)
})
message
是child_process
模块中的一个固定的事件名称,用于接收子进程发送的消息。它是一个内置的事件,用于监听子进程发送的任何类型的消息。子进程可以通过发送对象,主进程根据对象的属性值来区分消息类型
脚本
const message = {
type: "download",
status: "success",
path: path.join(
process.env.DIST_ELECTRON,
`../puppeteer-download/${process.argv[2]}`
),
};
process.send(message);
执行脚本
代码就不放了,效果图如下:
这里在打包时遇到了一些问题
问题1:如下图,原因是Nullish Coalescing Assignment (??=) 是在 ECMAScript 2021 中引入的,并且需要 Node.js 15.0.0 或更高版本才能支持。需要确保你的 Node.js 版本高于 15.0.0
问题2:如下图,原因是从github下载文件时出错了,很好理解,国内访问github经常访问不了。
除了这个文件还有其他几个文件,可以将下载地址复制到浏览器里下载下来。
我这里总共下载了三个文件,分别是:winCodeSign-2.6.0.7z
、nsis-3.0.4.1.7z
、nsis-resources-3.4.1.7z
需要将这3个文件解压到对应的文件目录里(路径不一定一样,但是基本上是c盘,找到electron-builder
文件夹)
分别是:
再次执行打包,打包成功如下:
打包需要的配置文件已经项目demo下载地址:
链接:https://pan.baidu.com/s/12EnVdQNB7bwqDACLISEUcw
提取码:1234