最近借着项目对 Electron 又进行了一波实践
前言
然而早期是有接触过的,当时在做一个桌宠,但是后来烂尾了 🤣
原因则是因为编译 & 安装后产生的文件太多了
在我看来,桌宠应该是一个插件,即编译产生的文件应该是免安装的单个 .exe 文件
所以用 Electron 写桌宠是不可取的
Start
原本准备采用以往的 Vue2 + Electron8 进行开发,但是在一阵纠结之后选择了 Vue3 + TypeScript + Electron 13 🤗
project init
0️⃣ 全局安装 yarn
npm i yarn -g
1️⃣ 全局安装 Vue 脚手架
npm i @vue/cli -g
2️⃣ 构建 Vue 项目
vue create vue-electron-app
这里我选择第三个,因为默认不会加入TypeScript & Router
3️⃣ 加入 Electron
cd vue-electron-app
vue add electron-builder
4️⃣ 关于 package.json
- electron:serve - 用于 dev 环境,即启动项目
- electron:build - 用于项目编译
5️⃣ 关于报错
需要安装指定的 ts-loader
yarn add ts-loader@8.2.0
这里必须指定版本,假如不带版本号直接安装 latest 则依然无法启动项目 😴
不使用 ts 则无此问题
6️⃣ End
Another
也可以参考此处进行构建,命令少且更加简单 🥳
https://github.com/electron-vite/electron-vite-vue
开发时碰到的一些问题
关于 Vuex
使用 Vuex 的目的自然是用来存放一些公共数据,典型的例子则是 Token & UserProfile
但是这里没办法使用 Vuex,原因则是因为各个窗口之间是相互独立的,比如在窗口A中修改了Vuex中的数据,在窗口B中是没有办法拿到的
- 主进程
- 各个渲染进程
最终我选择在主进程中对这部分数据进行维护,通过渲染进程 & 主进程之间的通信进行数据读取及修改
我维护了一个对象 windows,用于记录渲染进程产生的每个窗口
(路由的 Name 作为 key , BrowserWindow 作为 value
let windows: any = {};
// ...
let anotherWin: any = new BrowserWindow({
width: res.width,
height: res.height,
...
})
if (winURL) {
await anotherWin.loadURL(winURL + res.link);
} else {
createProtocol("app");
anotherWin.loadURL(`app://./index.html/#/${res.link}`);
}
windows[res.link] = anotherWin;
先通过 ipcRenderer.send 向主进程发送消息,再通过 ipcRenderer.on 接收来自主进程的消息,并使用 Promise 进行返回
import { ipcRenderer } from "electron";
import router from "@/router";
export const get = (key: string) => {
ipcRenderer.send("getObjectFromMain", {
key: key,
link: router.currentRoute.value.name,
});
return new Promise((resolve) => {
ipcRenderer.on("getObjectCallback", (_, res) => {
resolve(res);
});
});
};
利用路由的 name 向指定的渲染进程发送消息
ipcMain.on("getObjectFromMain", (_, res) => {
let thisWin: any;
if (res.link == "main") thisWin = win;
else thisWin = windows[res.link];
thisWin.webContents.send(
"getObjectCallback",
data[res.key as keyof typeof data]
);
});
然而 npm 上是有 vuex-electron 存在的,但是一眼 4 years ago 不是很敢用 (〜 ̄△ ̄)〜
不过这里会产生另一个问题,即窗口创建时,Vue生命周期钩子函数 onMounted 部分情况下无法使用
如页面加载时需要从主进程中获取数据,此时在 onMounted 内进行将会产生报错
webContents is undefined
即执行 onMounted 内代码片段时,窗体并未完成创建
Then
可以通过在主线程中监听窗体创建完毕(' did-finish-load '),再由进程间通信通知窗体执行初始化... ,以此来代替 onMounted
# 1.主进程
/**
* window-init
*
* ipcRenderer.on("window-init", () => {})
* 用于处理渲染进程在创建完成时对主进程缓存数据的获取
*/
anotherWin.webContents.on("did-finish-load", () => {
let result: any = {
windowId: res.id,
};
// 用于创建时的传值
result.params = res.params;
anotherWin.webContents.send("window-init", result);
});
# 2.渲染进程
/**
* window init
*/
ipcRenderer.on("window-init", (_, res: any) => {
setWindowId(res.windowId);
});
electron:build
“白屏,开发时正常”
归根结底是路由导致的,编译时需要修改为 hash 模式
- 开发时 - history: createWebHistory(process.env.BASE_URL)
- 编译时 - history: createWebHashHistory(process.env.BASE_URL)
// new BrowserWindow
if (winURL) {
await anotherWin.loadURL(winURL + res.link);
} else {
createProtocol("app");
anotherWin.loadURL(`app://./index.html/#/${res.link}`);
}
// ./router/index.ts
const routerSetting = () => {
if (process.env.IS_ELECTRON) {
return createWebHashHistory(process.env.BASE_URL);
} else {
return createWebHistory(process.env.BASE_URL);
}
};
const router = createRouter({
history: routerSetting(),
routes,
});
End
由 Electron 开发的桌面应用其实还挺多的,最有名的自然是 VSCode,其它的比如说还在内测中的 QQ频道,以及雷神加速器(其中弹框消息用到了 ElementUI v2 的消息组件,还是挺明显的,然而确是个减速器 😕
总之,在技术栈有限,且熟悉 js 的情况下选择 Electron 来开发桌面应用无疑是最好的选择
当然,开发上文中所提到的桌宠之类的项目自然要被排除在外,因项目而异
Comments | NOTHING