前言
这段时间在整理今年的资料。这篇文章记录于2023年初,当时的情况是要将网页端的内容移动到服务端,最省事省力的方案自然是用Electron解决(至于臃肿的问题,那就交给消费者自己去烦恼吧😂)
以下部分仅作记录,今天暂且就水一篇
安装
修改.yarnrc
1 2 3
| registry "https://registry.npmmirror.com" ELECTRON_MIRROR "https://npmmirror.com/mirrors/electron/" ELECTRON_BUILDER_BINARIES_MIRROR "https://npmmirror.com/mirrors/electron-builder-binaries/"
|
Vue启动
1 2 3 4
| npm create electron-vite or git clone https://github.com/electron-vite/electron-vite-vue 然后yarn
|
Quick start
1 2 3
| mkdir my-electron-app && cd my-electron-app yarn init yarn add --dev electron
|
解决CSP问题
1 2
| <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
预加载
你不能直接在主进程中编辑DOM,因为它无法访问渲染器 文档
上下文。 它们存在于完全不同的进程!
这是将 预加载 脚本连接到渲染器时派上用场的地方。 预加载脚本在渲染器进程加载之前加载,并有权访问两个 渲染器全局 (例如 window
和 document
) 和 Node.js 环境。
创建一个名为 preload.js
的新脚本如下:
1 2 3 4 5 6 7 8 9 10
| window.addEventListener('DOMContentLoaded', () => { const replaceText = (selector, text) => { const element = document.getElementById(selector) if (element) element.innerText = text }
for (const dependency of ['chrome', 'node', 'electron']) { replaceText(`${dependency}-version`, process.versions[dependency]) } })
|
要将此脚本附加到渲染器流程,请在你现有的 BrowserWindow
构造器中将路径中的预加载脚本传入 webPreferences.preload
选项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const path = require('path')
const createWindow = () => { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') } })
win.loadFile('index.html') }
|
进程通信
https://www.electronjs.org/zh/docs/latest/tutorial/ipc
主进程的主要目的是使用 BrowserWindow
模块创建和管理应用程序窗口
electron中的ipc通信(二)
大概是全网最详细的Electron ipc 讲解(一)——主进程与渲染进程的两情相悦
主进程与渲染进程通信
主进程
- 一个electron只能有一个主进程
- 主进程指的是,你在执行 electron . 命令后,对应的当前目录下面的package.json文件下面的main的指定文件将会被执行,这里指的是
渲染进程(Renderer Process)
- 由主进程调用GUI接口创建的页面,都有自己的进程,叫做渲染进程。主进程通过实例化BrowserWindow,每个BrowserWindow实例都会渲染一个web页面,相互独立,当一个BrowserWindow被销毁,相应的渲染进程也会被终止。
- 渲染进程被主进程管理,每个渲染进程相互独立,只管理他们自己的web页面
两者需要通信,就需要用ipc通信
Example
单向
主进程.ts(可以写NodeJS代码,访问本地存储)
1 2 3 4 5 6 7 8 9 10 11
| ipcMain.on("async-message", (event, arg) => { console.log(`async-message:我接收到了异步消息`, arg); event.reply("async-reply", "哥们我收到消息了-来自异步"); });
ipcMain.on("sync-message", (event, arg) => { console.log(`async-message:我接收到了同步消息`, arg); event.returnValue = "哥们我收到消息了-来自同步"; });
|
渲染进程.ts
1 2 3 4 5 6 7 8
| const { ipcRenderer } = require('electron'); ipcRenderer.on("async-reply", (event, arg) => { console.log("异步消息:", arg); data = JSON.parse(arg) console.log(data); });
ipcRenderer.send("async-message", "发个异步消息");
|
ipcRenderer.send进行触发,ipcRenderer.on对返回进行相应
双向
ipcRenderer.invoke
与 ipcMain.handle
搭配使用
1 2 3 4 5 6 7
| const { ipcRenderer } = require('electron');
async function invokeMessageToMain() { const replyMessage = await ipcRenderer.invoke('render-invoke-to-main', '我是渲染进程通过 invoke 发送的消息'); console.log('replyMessage', replyMessage); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ipcMain.handle('render-invoke-to-main', async (event, message) => { console.log(`receive message from render: ${message}`) const result = await asyncWork(); return result; })
const asyncWork = async () => { return new Promise(resolve => { setTimeout(() => { resolve('延迟 2 秒获取到主进程的返回结果') }, 2000) }) }
|
功能
读取文件
使用icp
Electron教程(五)读取本地文件内容, icpMain icpRenderer 之间的交互
双向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ipcMain.handle('render-invoke-to-main', async (event, message) => { console.log(`receive message from render: ${message}`) const result = await asyncWork(); return result; }) async function asyncWork(){ return new Promise(resolve => { let fs = require("fs"); fs.readFile('E:\\code\\test_node\\a.json', function (err, data) { if (err) { console.log(err) resolve(err) } resolve(data.toString()) }); }) }
|
1 2 3
| const replyMessage = await ipcRenderer.invoke('render-invoke-to-main', 'render invoke'); data = JSON.parse(replyMessage)
|
单向
1 2 3 4 5 6 7 8 9 10 11
| ipcMain.on('asynchronous-message', function(event, arg) { let fs = require("fs"); fs.readFile("E:\\code\\test_node\\a.json","utf8",(err,data)=>{ if(err){ event.sender.send('asynchronous-reply', "读取失败"); }else{ event.sender.send('asynchronous-reply', data); } }) });
|
1 2 3 4 5 6 7 8 9
| const { ipcRenderer } = require('electron'); ipcRenderer.on("asynchronous-reply", function(event, arg) {
console.log(arg) data = JSON.parse(arg) console.log(data); }); ipcRenderer.send("asynchronous-message", "ping");
|
使用预加载
修改DOM树
1 2 3 4 5 6 7 8 9 10 11 12 13
| window.addEventListener('DOMContentLoaded', () => { let readAndDisplay = () => { let fs = require("fs"); fs.readFile("E:\\code\\test_node\\input.txt","utf8",(err, data) => { if (err) return console.error(err); console.log(data.toString()); }); }
document.querySelector('#btn1').addEventListener("click", event => { readAndDisplay(); }) })
|
全屏
1 2 3 4 5 6
| mainWindow = new BrowserWindow({ //... fullscreenable:true, fullscreen: true, simpleFullscreen:true, });
|
取消菜单栏
1 2
| import { Menu } from 'electron' Menu.setApplicationMenu(null) // null值取消顶部菜单栏
|
使用系统摄像头
未解决的issue
Electron加载HTTP页面打开本地摄像头
纯HTML5打开
Electron 从入门到实践07之实战 相机应用(还是mediaDevices.getUserMedia,但是保存到了本地)
Esc退出全屏
https://www.electronjs.org/zh/docs/latest/api/global-shortcut
1
| const { app, globalShortcut } = require('electron')
|
在CreateWindow()中添加相关
1 2 3
| globalShortcut.register('ESC', function () { win.close() })
|
流程模型
https://www.electronjs.org/zh/docs/latest/tutorial/process-model
Electron 应用程序的结构非常相似。 作为应用开发者,你将控制两种类型的进程:主进程 和 渲染器进程。 这类似于上文所述的 Chrome 的浏览器和渲染器进程。
窗口管理
主进程的主要目的是使用 BrowserWindow
模块创建和管理应用程序窗口。
BrowserWindow
类的每个实例创建一个应用程序窗口,且在单独的渲染器进程中加载一个网页。 您可从主进程用 window 的 webContent
对象与网页内容进行交互。
1 2 3 4 5 6 7
| const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 1500 }) win.loadURL('https://github.com')
const contents = win.webContents console.log(contents)
|
预加载(Preload 脚本)
预加载(preload)脚本包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。 这些脚本虽运行于渲染器的环境中,却因能访问 Node.js API 而拥有了更多的权限。
预加载脚本可以在 BrowserWindow
构造方法中的 webPreferences
选项里被附加到主进程。
main.js
1 2 3 4 5 6 7 8
| const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ webPreferences: { preload: 'path/to/preload.js', }, })
|
因为预加载脚本与浏览器共享同一个全局 Window
接口,并且可以访问 Node.js API,所以它通过在全局 window
中暴露任意 API 来增强渲染器,以便你的网页内容使用。
进程间通信
https://www.electronjs.org/zh/docs/latest/tutorial/ipc
打包
electron-forge
https://www.electronforge.io/
electron-builder
资料
官方
https://www.electronjs.org/zh/docs/latest/
https://www.electronjs.org/
electron/electron-quick-start
blogs
electron 应用开发优秀实践
Bugs
Electron+Vite+Vue3中
public中可能会有几张无法打包
在App.vue中引用,会无法显示
应急解决方案:将public的打进入assets
After build, assets
can not resolved correct. / 打包后,assets下资源路径不正确 #287