1. 教程前提
该教程依赖于这篇教程:Electron Forge + Vite + Vue3 + Ts 搭建 Electron 项目到应用打包
2. 参考资料
npm官网: rollup-plugin-copy
3. 静态资源处理机制差异
Vite
在打包应用时主要处理的是基于 Web 技术的项目,其工作方式和 Electron
在处理文件路径上有所不同。这种差异主要体现在以下几个方面:
1. 路径解析机制的差异
- Vite: 作为一个为现代 Web 应用设计的构建工具,
Vite
通常处理的是基于HTTP
服务器的资源路径。它假设所有资源都可以通过相对于应用的基本URL
或端口的路径来访问。这意味着,Vite
会扫描源代码中的导入语句,查找以HTTP URL
形式指定的资源。 - Electron:
Electron
应用通常会访问文件系统中的资源,使用的是文件系统的绝对路径或相对于当前文件的路径。这使得Electron
可以加载本地的文件和资源,而不依赖于Web
服务器。
2. 资源引用和打包
- 在
Vite
打包时,它只会包括那些被静态分析到的资源文件(例如 JavaScript、CSS、图片文件等),这些文件通常是通过相对路径或模块解析方式引用的。 Electron
中使用的大多都是系统路径(例如通过 __dirname 或 process.resourcesPath 生成的路径),这些路径在 Vite 的打包过程中是无法被正确识别的。
在 Vite
不支持指定将某个目录打包至指定编译目录的情况下,问题逐渐变得复杂化(Forge
也没找到相关的功能)。
4. 解决办法
-
public
公共目录
public
是一个特殊的目录,Vite
会将这个目录里的静态资源一起输出到web
编译目录与Electron
编译目录,资源双份,但不失为一种解决办法。
不是很推荐这个办法,所以我直接摸了。 -
rollup-plugin-copy 插件
这是一个比较推荐的方案,“复制文件和文件夹”。a. 安装
rollup-plugin-copy
npm install rollup-plugin-copy -D
b. 配置
vite.main.config.ts
文件import copy from 'rollup-plugin-copy' plugins: [ copy({ targets: [ { // 要进行复制的目录或文件 src: 'static', // 目标位置 dest: '.vite/build' }, ] }) ]
vite.main.config.ts
文件完整内容:import type { ConfigEnv, UserConfig } from 'vite'; import { defineConfig, mergeConfig } from 'vite'; import { getBuildConfig, getBuildDefine, external, pluginHotRestart } from './vite.base.config'; // 引入 rollup-plugin-copy 插件 import copy from 'rollup-plugin-copy' // https://vitejs.dev/config export default defineConfig((env) => { const forgeEnv = env as ConfigEnv<'build'>; const { forgeConfigSelf } = forgeEnv; const define = getBuildDefine(forgeEnv); const config: UserConfig = { build: { lib: { entry: forgeConfigSelf.entry!, fileName: () => '[name].js', formats: ['cjs'], }, rollupOptions: { external, } }, plugins: [ pluginHotRestart('restart'), copy({ targets: [ { // 要进行复制的目录或文件 src: 'static', // 目标位置 dest: '.vite/build' }, ] }) ], define, resolve: { // Load the Node.js entry. mainFields: ['module', 'jsnext:main', 'jsnext'], }, }; return mergeConfig(getBuildConfig(forgeEnv), config); });
这样
static
静态资源目录就会被打包到Electron
的编译目录内,也不会在web
编译目录打包它。c. 编写系统托盘,测试结果
在项目根目录创建一个electron
目录,在这个目录里创建一个Tray.ts
文件,用于编写系统托盘按钮逻辑,在src/main.ts
中引用托盘内容。Electron Forge
在编译后会将Tray.ts
合并到main.js
中,所以我们就以main.js
的编译位置为路径锚点,进入与其保持同级的static
目录里获取静态资源。在项目根目录创建一个
static
文件夹,在里面放置图片文件,例如electron.png
:Tray.ts
内容:import { app, BrowserWindow, Tray, Menu, nativeImage } from 'electron'; import path from 'path'; // 窗体对象 let win: BrowserWindow; // 托盘菜单 const contextMenu = Menu.buildFromTemplate([ { label: '显示主界面', click: () => { win.show() } }, { label: '退出应用', click: () => { app.quit() } } ]) // 创建系统托盘 function createTray(w: BrowserWindow) { // 获取窗体对象 win = w; // 获取 static 下的图标文件,创建图标对象 const icon = nativeImage.createFromPath(path.join(__dirname, 'static', 'electron.png')); // 系统托盘 const concrete = new Tray(icon); concrete.setContextMenu(contextMenu); concrete.setToolTip('工具组'); // 点击托盘图标显示主窗口 concrete.on('click', () => { win.show(); }); return concrete; } export default createTray;
main.ts
内容:
main.ts
位于src
目录下,Tray.ts
位于src
同级目录的electron
目录下。import { app, BrowserWindow, Tray } from 'electron'; import path from 'path'; // 这个用于存放 ipcMain 进程通信,在这篇教程中并没有进行描述 import ipceventmount from '../electron/Ipcevent' // 引入 Tray.ts 系统托盘 import createTray from '../electron/Tray' // 窗体对象 let win: BrowserWindow; // 系统托盘 let concrete: Tray; if (require('electron-squirrel-startup')) { app.quit(); } const createWindow = () => { // 创建并获取 BrowserWindow 对象 win = new BrowserWindow({ autoHideMenuBar: true, frame: false, width: 1000, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') } }) if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { win.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); } else { win.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)); } } app.whenReady().then(() => { createWindow(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); ipceventmount(win); } }) if (require('electron-squirrel-startup')) app.quit(); ipceventmount(win); }).then(() => { /* 在这里创建系统托盘 */ concrete = createTray(win); }) // 全部窗口关闭时,退出应用 app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } });
app.whenReady()
得到一个当Electron
已初始化后fulfill
的Promise
对象,
在第一个.then
里创建好BrowserWindow
对象,并将其赋值给win
对象,
在第二个.then
里执行concrete = createTray(win);
创建并获取到系统托盘对象。d. 启动测试
npm start
e. 目录结构
root L .vite # 编译目录 | L build | L main.js | L preload.js | L static | L electron.png L electron | L Tray.ts | L Ipcevent.ts L src | L main.ts | L preload.ts | L renderer.ts L static | L electron.png