在 Vue3 + Electron 中使用预加载脚本(preload)
文章目录
一、什么是预加载脚本(preload),为什么我们需要它
根据 Electron 官方提供的相关说明,我们可以将预加载脚本理解成是主进程和渲染进程间的桥梁。通常出于安全性的角度考虑,我们使用预加载脚本来安全地将 Node.js 模块或第三方库的 API 暴露至渲染进程中。
有时,我们可能为了在渲染进程中使用 Node.js 相关模块而关闭上下文隔离和为页面集成 Node.js 环境,但是这种方式官方并不推荐:
// 不推荐
const mainWindow = new BrowserWindow({
webPreferences: {
// 关闭上下文隔离
contextIsolation: false,
// 为页面集成 Node.js 环境
nodeIntegration: true,
}
})
mainWindow.loadURL('https://example.com')
官方推荐的使用预加载脚本(preload)暴露相关 API,以使用 Node.js 的模块功能:
// 推荐
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(app.getAppPath(), 'preload.js')
}
})
mainWindow.loadURL('https://example.com')
关于预加载脚本的更多介绍参见:https://www.electronjs.org/zh/docs/latest/tutorial/tutorial-preload
关于安全方面的更多介绍参见:https://www.electronjs.org/zh/docs/latest/tutorial/security
二、通过预加载脚本暴露相关 API 至渲染进程
实现目标:
- 获取系统默认桌面路径功能
- 向剪切板写入内容
- 使用系统默认浏览器访问目标 url
- 使用文件选择对话框
项目通过 electron-vite-vue 构建,详情可见:https://blog.csdn.net/qq_45897239/article/details/138490747
准备工作,在主进程 main.ts
文件中导入 preload
相关文件:
// electron/main.ts
function createWindow() {
win = new BrowserWindow({
width: 1200,
height: 700,
minWidth: 885,
minHeight: 580,
icon: path.join(process.env.VITE_PUBLIC, "electron-vite.svg"),
webPreferences: {
// 导入 preload 相关文件
preload: path.join(__dirname, "preload.mjs"),
},
// 隐藏菜单栏 按 Alt 键显示
autoHideMenuBar: true,
});
// 程序启动后开启 开发者工具
// win.webContents.openDevTools();
// 关闭菜单栏
// Menu.setApplicationMenu(null);
if (VITE_DEV_SERVER_URL) {
win?.loadURL(VITE_DEV_SERVER_URL);
} else {
win?.loadFile(path.join(RENDERER_DIST, "index.html"));
}
}
1、实现获取系统默认桌面路径功能
在 preload.ts
文件中通过 contextBridge
对外暴露自定义 API。
// electron/preload.ts
import { ipcRenderer, contextBridge } from "electron";
contextBridge.exposeInMainWorld("electronAPI", {
// 获取系统默认桌面路径
getDesktopPath: async () => {
try {
return await ipcRenderer.invoke("get-desktop-path");
} catch (error) {
console.error("Failed to get desktop path:", error.message);
}
},
...
});
ipcRenderer.invoke
允许渲染进程向主进程发送事件或消息,并且接收主进程返回的数据。
可以直接在主进程中使用 ipcMain.handle()
监听渲染进程发送来的消息:
// electron/main.ts
import { app, ipcMain } from "electron";
...
app.whenReady().then(async () => {
try {
...
createWindow();
// get-desktop-path => 获取系统桌面路径
ipcMain.handle("get-desktop-path", () => {
return app.getPath("desktop");
});
} catch (error) {
console.error("Failed to start server:", error);
}
});
调用 API 获取系统桌面路径:
<script setup lang="ts">
onMounted(async () => {
// 获取系统桌面路径
const res = await window.electronAPI.getDesktopPath();
console.log(res);
});
</script>
注意: 可能会出现 ts
报错
属性“electronAPI”在类型“Window & typeof globalThis”上不存在。你是否指的是“Electron”?ts-plugin(2551)
electron.d.ts(12, 19): 在此处声明了 "Electron"。
解决方案
在 vite-env.d.ts
中追加以下内容即可:
declare interface Window {
electronAPI: any
}
2、向剪切板写入内容
定义相关 API:
// electron/preload.ts
import { ipcRenderer, contextBridge } from "electron";
contextBridge.exposeInMainWorld("electronAPI", {
// 向剪切板写入内容
clipboardWriteText: async (text) => {
try {
await ipcRenderer.invoke("write-to-clipboard", text);
} catch (error) {
console.error("Failed to write to clipboard:", error.message);
}
},,
...
});
主进程中监听:
// electron/main.ts
import { app, ipcMain, clipboard } from "electron";
...
app.whenReady().then(async () => {
try {
...
// write-to-clipboard => 向剪切板写入内容
ipcMain.handle("write-to-clipboard", (event, text) => {
clipboard.writeText(text);
});
} catch (error) {
console.error("Failed to start server:", error);
}
});
使用:
<script setup lang="ts">
// 向剪切板写入内容
const copyLink = (text) => {
window.electronAPI.clipboardWriteText(text);
};
</script>
3、使用系统默认浏览器访问目标 url
定义相关 API:
// electron/preload.ts
import { ipcRenderer, contextBridge } from "electron";
contextBridge.exposeInMainWorld("electronAPI", {
// 使用系统默认浏览器访问目标 url
openBrowserByUrl: async (url) => {
try {
await ipcRenderer.invoke("open-browser-by-url", url);
} catch (error) {
console.error("Failed to open browser:", error.message);
}
},
...
});
主进程中监听:
// electron/main.ts
import { app, ipcMain, shell } from "electron";
...
app.whenReady().then(async () => {
try {
...
// open-browser-by-url => 使用系统默认浏览器访问目标 url
ipcMain.handle("open-browser-by-url", async (event, url) => {
await shell.openExternal(url);
});
} catch (error) {
console.error("Failed to start server:", error);
}
});
使用:
<script setup lang="ts">
// 使用系统默认浏览器访问目标 url
const goToLink = (url) => {
window.electronAPI.openBrowserByUrl(url);
};
</script>
4、使用文件选择对话框
定义相关 API:
// electron/preload.ts
import { ipcRenderer, contextBridge } from "electron";
contextBridge.exposeInMainWorld("electronAPI", {
// 打开文件保存对话框 返回文件保存路径
openFileSaveDialog: async (path) => {
try {
return await ipcRenderer.invoke("open-save-dialog",path);
} catch (error) {
console.error("Failed to open save dialog:", error.message);
}
},
...
});
此处需要将文件对话框设置为顶层窗口,否则用户关闭应用程序后,该窗口依然存在。由于将对话框设置为顶层对话框需要win实例,所以使用函数方式导出初始化。设置为顶层对话框后,防止对话框被多次打开和未关闭对话框时的其他窗口操作。
可以在 electron
文件夹下创建一个 ipcHandlers.ts
文件,该文件内放置主进程需要监听的渲染进程发送来的消息,后续再导入 main.ts
主进程文件中。
// electron/ipcHandlers.ts
import { app, ipcMain, dialog, BrowserWindow } from "electron";
// 可以在此文件内放置需要监听的来自渲染进程的消息
// get-desktop-path => 获取系统桌面路径
ipcMain.handle("get-desktop-path", () => {
...
});
export function initIpcHandlerDialog(win: BrowserWindow) {
// open-save-dialog => 打开文件保存对话框
ipcMain.handle("open-save-dialog", async (event, path) => {
try {
// 打开保存文件对话框
const result = await dialog.showOpenDialog(win, {
// 对话框标题
title: "选择文件保存目录",
// 确认按钮
buttonLabel: "选择目录",
// 默认文件路径
defaultPath: app.getPath("desktop"),
// 只允许选择文件夹
properties: ["openDirectory"],
// 文件过滤器,定义可以选择哪些类型的文件
filters: [{ name: "All Files", extensions: ["*"] }]
});
if (result) {
if (result.canceled) {
console.log("用户取消了保存操作");
// 如果用户取消了保存操作,则返回之前的保存路径,如若为空,则使用默认值
if (!path) {
path = app.getPath("desktop");
}
return path;
} else {
const filePath = result.filePaths[0];
console.log("用户选择了保存路径:", filePath);
return filePath;
}
}
} catch (error) {
console.log("文件对话框打开失败:" + error);
// 获取桌面默认路径并返回
return app.getPath("desktop");
}
});
}
在主进程中导入:
// electron/main.ts
import { app, BrowserWindow, ipcMain } from "electron";
...
// 导入相关功能模块
import "./ipcHandlers.ts";
import { initIpcHandlerDialog } from "./ipcHandlers.ts";
...
app.whenReady().then(async () => {
try {
...
createWindow();
// 初始化对话框,将弹出对话框设置为顶层状态
initIpcHandlerDialog(win);
} catch (error) {
console.error("Failed to start server:", error);
}
});
使用:
<script setup lang="ts">
// 打开文件保存对话框
const openSaveDialog = async () => {
const defaultPath = 'D:/Desktop'
const path = await window.electronAPI.openFileSaveDialog(defaultPath);
};
</script>
该方法传递了一个 defaultPath
参数,用于处理当用户点击了取消文件选择对话框时,使用的默认文件保存路径。
效果展示:
三、参考资料
更多推荐
所有评论(0)