Electron学习笔记
前言
这段时间在整理今年的资料。这篇文章记录于2023年初,当时的情况是要将网页端的内容移动到服务端,最省事省力的方案自然是用Electron解决(至于臃肿的问题,那就交给消费者自己去烦恼吧😂)
以下部分仅作记录,今天暂且就水一篇
安装
修改.yarnrc
registry "https://registry.npmmirror.com"
ELECTRON_MIRROR "https://npmmirror.com/mirrors/electron/"
ELECTRON_BUILDER_BINARIES_MIRROR "https://npmmirror.com/mirrors/electron-builder-binaries/"
Vue启动
npm create electron-vite
or
git clone https://github.com/electron-vite/electron-vite-vue
然后yarn
Quick start
mkdir my-electron-app && cd my-electron-app
yarn init
yarn add --dev electron
解决CSP问题
<!-- https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
预加载
你不能直接在主进程中编辑DOM,因为它无法访问渲染器 文档
上下文。 它们存在于完全不同的进程!
这是将 预加载 脚本连接到渲染器时派上用场的地方。 预加载脚本在渲染器进程加载之前加载,并有权访问两个 渲染器全局 (例如 window
和 document
) 和 Node.js 环境。
创建一个名为 preload.js
的新脚本如下:
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
选项。
// 需在当前文件内开头引入 Node.js 的 'path' 模块
const path = require('path')
// modify your existing createWindow() function
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只能有一个主进程
- 主进程指的是,你在执行 electron . 命令后,对应的当前目录下面的package.json文件下面的main的指定文件将会被执行,这里指的是
渲染进程(Renderer Process)
- 由主进程调用GUI接口创建的页面,都有自己的进程,叫做渲染进程。主进程通过实例化BrowserWindow,每个BrowserWindow实例都会渲染一个web页面,相互独立,当一个BrowserWindow被销毁,相应的渲染进程也会被终止。
- 渲染进程被主进程管理,每个渲染进程相互独立,只管理他们自己的web页面
两者需要通信,就需要用ipc通信
Example
单向
主进程.ts(可以写NodeJS代码,访问本地存储)
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
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
搭配使用
//render.js
const { ipcRenderer } = require('electron');
async function invokeMessageToMain() {
const replyMessage = await ipcRenderer.invoke('render-invoke-to-main', '我是渲染进程通过 invoke 发送的消息');
console.log('replyMessage', replyMessage);
}
//main.js
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 之间的交互
双向
//main.ts
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())
});
})
}
//render.js
const replyMessage = await ipcRenderer.invoke('render-invoke-to-main', 'render invoke');
data = JSON.parse(replyMessage)
单向
//main.js
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);
}
})
});
//render.js
const { ipcRenderer } = require('electron');
ipcRenderer.on("asynchronous-reply", function(event, arg) {
// 这里的arg是从主线程请求的数据
console.log(arg)
data = JSON.parse(arg)
console.log(data);
});
ipcRenderer.send("asynchronous-message", "ping");
使用预加载
修改DOM树
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();
})
})
全屏
mainWindow = new BrowserWindow({
//...
fullscreenable:true,
fullscreen: true,
simpleFullscreen:true,
});
取消菜单栏
import { Menu } from 'electron'
Menu.setApplicationMenu(null) // null值取消顶部菜单栏
使用系统摄像头
Electron 从入门到实践07之实战 相机应用(还是mediaDevices.getUserMedia,但是保存到了本地)
Esc退出全屏
https://www.electronjs.org/zh/docs/latest/api/global-shortcut
const { app, globalShortcut } = require('electron')
在CreateWindow()中添加相关
globalShortcut.register('ESC', function () {
win.close()
})
流程模型
https://www.electronjs.org/zh/docs/latest/tutorial/process-model
Electron 应用程序的结构非常相似。 作为应用开发者,你将控制两种类型的进程:主进程 和 渲染器进程。 这类似于上文所述的 Chrome 的浏览器和渲染器进程。
窗口管理
主进程的主要目的是使用 BrowserWindow
模块创建和管理应用程序窗口。
BrowserWindow
类的每个实例创建一个应用程序窗口,且在单独的渲染器进程中加载一个网页。 您可从主进程用 window 的 webContent
对象与网页内容进行交互。
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
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
electron-builder
资料
官方
https://www.electronjs.org/zh/docs/latest/
blogs
Bugs
Electron+Vite+Vue3中
public中可能会有几张无法打包
在App.vue中引用,会无法显示
应急解决方案:将public的打进入assets
After build, assets
can not resolved correct. / 打包后,assets下资源路径不正确 #287