在 Vue3 + Electron 中使用预加载脚本(preload)

07-19 969阅读

文章目录

      • 一、什么是预加载脚本(preload),为什么我们需要它
      • 二、通过预加载脚本暴露相关 API 至渲染进程
        • 1、实现获取系统默认桌面路径功能
        • 2、向剪切板写入内容
        • 3、使用系统默认浏览器访问目标 url
        • 4、使用文件选择对话框
        • 三、参考资料

          一、什么是预加载脚本(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 获取系统桌面路径:

            onMounted(async () => {
                // 获取系统桌面路径
                const res = await window.electronAPI.getDesktopPath();
                console.log(res);
            });
            
            

            注意: 可能会出现 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);
                }
            });
            

            使用:

            // 向剪切板写入内容
            const copyLink = (text) => {
                window.electronAPI.clipboardWriteText(text);
            };
            
            
            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);
                }
            });
            

            使用:

            // 使用系统默认浏览器访问目标 url
            const goToLink = (url) => {
                window.electronAPI.openBrowserByUrl(url);
            };
            
            
            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);
                }
            });
            

            使用:

            // 打开文件保存对话框
            const openSaveDialog = async () => {
                const defaultPath = 'D:/Desktop'
                const path = await window.electronAPI.openFileSaveDialog(defaultPath);
            };
            
            

            该方法传递了一个 defaultPath 参数,用于处理当用户点击了取消文件选择对话框时,使用的默认文件保存路径。

            效果展示:

            在 Vue3 + Electron 中使用预加载脚本(preload)

            三、参考资料

            • electron - ipcRenderer
            • electron - preload
            • electron - security
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]