electron技巧总结
这篇文章整理了一些在electron开发中经常使用到的一些技巧;这些技巧大多数是细枝末节的知识,将其收集起来方法自己和其他人使用。
1. 调试用日志
不同于web端,在electron上debug是比较麻烦的,所以使用日志就非常有必要了。下面是用winston第三库创建的一个日志系统及使用举例:
js
const { createLogger, transports, format } = require('winston');
const path = require('path');
// 创建一个 logger 实例
logger = createLogger({
level: 'info', // 设置日志输出级别
format: format.combine(
format.timestamp(), // 添加时间戳
format.simple(), // 使用简单的文本格式输出日志信息
),
transports: [
// new transports.Console(), // 输出到控制台
new transports.File({
// 输出到文件
filename: 'logs/app.log',
}),
],
});
logger.info(process.env.APP_URL);
2. 捕获异常
2.1 捕获全局异常
js
/**
* 监听未捕获的异常
*/
process.on('uncaughtException', (error) => {
logger?.error('error', error);
process.exit(1);
});
2.2 捕获溢出的promise的reject
js
// 监听未捕获的 Promise 拒绝(Rejection)
process.on('unhandledRejection', (reason, promise) => {
logger?.error('error', reason);
process.exit(1);
});
3. 获取当前的运行路径
js
const appPath = path.resolve(process.execPath);
4. 获取开发/运行环境下不同的资源基地址
js
const APP_URL = process.env.NODE_ENV === 'development' ? process.env.APP_URL : 'http://localhost/';
5. 获取当前所有的窗口
js
const { BrowserWindow } = require('electron');
const windows = () => BrowserWindow.getAllWindows();
6. 注册一些常见的快捷键
6.1 注册快捷键时候使用的方法
js
const registerShortcut = () => {
// 强制刷新
globalShortcut.register('ctrl+shift+r', () => BrowserWindow.getFocusedWindow()?.webContents?.reload());
// 在右侧打开devTool
globalShortcut.register('ctrl+shift+i', () => BrowserWindow.getFocusedWindow()?.webContents?.openDevTools({ mode: 'right' }));
globalShortcut.register('ctrl+shift+a', () => void 0);
globalShortcut.register('ctrl+shift+d', () => void 1);
};
6.2 注册方法的调用时机
对于窗口快捷键的注册时机应该在app.whenReady状态改变之后
js
app.whenReady().then(() => {
...
// 注册快捷键
registerShortcut();
})
6.3 释放快捷键的时机
取消快捷键注册的时机是app的quit钩子中
js
app.on('quit', () => {
globalShortcut.unregisterAll();
removeRegKey();
});
7. 渲染进程主动获取主进程状态
使用ipcMain.handle和ipcRenderer.invoke搭配获取主进程上的数据/状态
主进程响应:
js
function cb (e, params) {
return 'data';
}
ipcMain.handle('topic-x', cb);
渲染进程请求:
js
ipcRenderer.invoke('topic-x', params); // invoke的返回值是一个promise对象
8. 主进程接受渲染进程发送的消息
使用ipcMain.on接受渲染进程发送的消息
js
ipcMain.on('topic-y', (e, data) => {
// do something with data
e.reply('reply-topic-y', 'message get it');
});
9. 主进程主动向渲染进程广播消息
使用ipcMain.on接受渲染进程发送的消息
js
BrowserWindow.getAllWindows()?.forEach( win => void win.webContents.send('broadcast-message', 'wolf is coming!'));
10. 在主进程中发起get网络请求
使用axios发起网络请求
js
const axios = require('axios');
...
const request = (method, url) => {
return axios({
method,
url,
})
}
11. electron以单例运行--单例锁
js
const { app } = require('electron');
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
}
12. 新建窗口
- 传入初始化参数,实例化一个新的BrowserWindow对象
- 设置新打开窗口的图标
- 新窗口展示目标网址的内容
- 新建窗口监视自定义的事件--用来和主进程进行通信
js
const createWindow = (screen, prepareJS, url = APP_URL) => {
// 传入必要的参数,实例化出来一个新窗口
const win = new BW({
x: screen.bounds.x + 50 || 50,
y: screen.bounds.y + 50 || 50,
frame: false,
kiosk: true,
skipTaskbar: true,
fullscreen: true,
resizable: false,
minimizable: false,
transparent: true,
webPreferences: {
preload: path.join(__dirname, prepareJS),
},
});
// 将窗口设置为总是位于最顶层,并且在多个显示器时仅限于当前屏幕上显示
win.setAlwaysOnTop(true, 'screen-saver');
// 设置窗口的图标
win.setIcon(path.join(__dirname, '../icons/logo.png'));
// 在新建窗口上显示内容
win
.loadURL(url)
.then(() => {
win.webContents.send('new-window-established');
})
.catch(() => {});
// win监听主进程发送的消息,topic是established-fail
win.webContents.on('established-fail', (e) => void 0);
return win;
};
13. 根据pid关闭某个进程
根据进程的pid,使用child_process模块中的方法可以关闭此进程
js
const exec = require('child_process').execSync;
...
const closeProcessById = (pid) => {
if (!pid) return;
try {
// 关闭语音助手进程
exec(`taskkill /F /PID ${pid}`);
} catch (e: unknown) {
logger?.error(`进程${pid}关闭失败!`);
}
};
14. 根据path打开某个服务
如果知道了某个服务所在的地址,就可以通过child_process模块中的方法打开这个服务
js
const { spawn } = require('child_process');
...
let pid = '';
const openProcessByPath = (path) => {
try {
const childProcess = spawn(path []);
pid = childProcess.pid;
// 语音助手的消息会打印在日志中
childProcess.stdout.on('data', (data) => {
vLogger.log('info', `子进程输出: ${data}`);
});
// 处理错误
childProcess.on('error', (error) => {
logger?.error('error', `子进程错误: ${error}`);
throw new Error('launch service failed');
});
// // 断线通知
// childProcess.on('close', (code) => {
// logger?.log('info', `子进程退出,退出码: ${code}`);
// });
} catch (e: unknown) {
logger?.error('error', 'launch server failed');
}
};
15. 根据服务名称清除所有同名服务
同样是使用child_process中的exec方法
js
const exec = require('child_process').execSync;
...
const clearServerByName = (processName) => {
const command = 'wmic process where "name=\'' + processName + '\'" get ProcessId /VALUE';
try {
const output = exec(command).toString('utf8');
const pidLine = output
.trim()
.split('=')
.filter((v: string) => v !== 'ProcessId');
if (pidLine?.forEach) {
pidLine.forEach((pid: string) => exec(`taskkill /F /PID ${parseInt(pid, 10)}`));
}
} catch (e: unknown) {
return;
}
};
16. 获取全部屏幕
js
const { screen } = require('electron');
const getScreens = () => {
return screen.getAllDisplays();
}
17. 获取主屏幕
js
const { screen } = require('electron');
const getPrimaryDisplay = () => {
return screen.getPrimaryDisplay();
}
18. 获取辅屏幕
js
const { screen } = require('electron');
const getSideScreens = () => {
return screen.getAllDisplays().filter((s) => s !== screen.getPrimaryDisplay());
}