搭建基于 Vite + TypeScript + Vue 3 + Electron的小项目
前言
有时候我们需要将前端项目打包成一个exe文件供人使用,在不考虑性能或者包体大小的情况下,使用Electron来打包项目是一个不错的选择,那么有没有办法从现有的前端项目中加入Electron呢?
答案,当然是可以的,这里以我自己的项目为例,我的项目基于Vue3 + TypeScript + Vite,目的是为了实现一个excel文件的格式化处理功能,打包成exe安装包也只是为了减少运维的工作,用户自己安装本地运行就可以了,没必要部署到线上。
核心概念:主进程与渲染进程
在开始之前,理解 Electron 的两个核心概念至关重要:
- 主进程 (Main Process):每个 Electron 应用有且只有一个主进程。它相当于应用的“大脑”,是整个应用的入口点。主进程在 Node.js 环境中运行,拥有操作原生 GUI(如图形用户界面)、管理所有渲染进程以及与操作系统底层交互的能力。我们将在
electron/main.ts文件中编写主进程代码。 - 渲染进程 (Renderer Process):每个 Electron 应用可以有一个或多个渲染进程。每个
BrowserWindow实例都运行着一个独立的渲染进程,负责渲染 Web 页面,也就是我们用 Vue、React 等框架编写的用户界面。它运行在 Chromium 环境中,出于安全考虑,默认无法直接访问 Node.js API 或操作系统资源。
环境准备
在开始之前,请确保您的开发环境中已安装以下软件:
- Node.js: 建议使用
LTS版本。 - Yarn: 本教程推荐使用 Yarn 作为包管理器。
pnpm 我在测试中发现有很多问题,electron相关的文档都是推荐npm或者yarn。
初始化vue3项目
这里就使用官方的cli命令:
yarn create vue这里我建议使用yarn来管理项目依赖,因为Electron使用pnpm会出现依赖问题。
大家根据自己需求来配置项目,这里我配置的是:
[##] 2/2┌ Vue.js - The Progressive JavaScript Framework
│
◇ 请输入项目名称:
│ test
│
◆ 请选择要包含的功能: (↑/↓ 切换,空格选择,a 全选,回车确认)
│ ◼ TypeScript
│ ◻ JSX 支持
│ ◼ Router(单页面应用开发)
│ ◼ Pinia(状态管理)
│ ◻ Vitest(单元测试)
│ ◻ 端到端测试
│ ◼ ESLint(错误预防)
│ ◼ Prettier(代码格式化)
└回车后,等待项目初始化完成。
集成 Electron
为了将我们的 Web 应用打包成桌面应用,需要引入 Electron 相关依赖。
yarn add electron dotenv
yarn add electron-builder vite-plugin-electron -Delectron: Electron 的核心框架。dotenv: 用于从.env文件加载环境变量到process.env中,方便主进程使用。electron-builder: 功能强大且广泛使用的 Electron 应用打包和分发工具。vite-plugin-electron: 一个 Vite 插件,它简化了在开发模式下同时运行 Vite 开发服务器和 Electron 应用的过程。
创建主进程入口文件
安装完成后我们需要创建一个electron的入口文件:
在项目根目录,也就是与src同级目录下创建electron文件夹,并创建main.js文件,注意必须是js文件,不能是ts,内容如下:
import { app, BrowserWindow, globalShortcut } from "electron";
// import { Menu } from "electron";
import path from "path";
import { fileURLToPath } from "url";
import dotenv from "dotenv";
// 常量
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const isDev = Boolean(process.env.VITE_DEV_SERVER_URL ?? false);
// 加载环境变量
{
const envPath = isDev
? path.resolve(__dirname, "../.env.development")
: path.resolve(__dirname, "../.env.production");
dotenv.config({ path: envPath });
}
// 屏蔽安全警告
// ectron Security Warning (Insecure Content-Security-Policy)
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
// 创建浏览器窗口时,调用这个函数。
const createWindow = async () => {
const win = new BrowserWindow({
width: 800,
height: 600,
maximizable: true,
autoHideMenuBar: isDev ? false : true, // 生产关闭菜单栏
icon: fileURLToPath(new URL("../public/favicon.ico", import.meta.url)),
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
});
// 禁用右键菜单(如果非调试模式)
if (process.env.VITE_APP_DEBUG !== "true") {
win.webContents.on("context-menu", (e) => {
e.preventDefault();
});
}
// 最大化窗口
win.maximize();
// 开启调试台
if (process.env.VITE_APP_DEBUG === "true") {
win.webContents.openDevTools({ mode: "bottom" });
}
// development模式
if (isDev) {
await win.loadURL(process.env.VITE_DEV_SERVER_URL);
} else {
await win.loadFile(path.join(__dirname, "../dist/index.html"));
}
// NOTE: 调试用的,别删了,调试记得把autoHideMenuBar=false
// 创建菜单显示环境信息
// const menu = Menu.buildFromTemplate([
// {
// label: "Debug",
// submenu: [
// {
// label: `NODE_ENV: ${process.env.NODE_ENV || "undefined"}`,
// enabled: false
// },
// {
// label: `App Packaged: ${app.isPackaged}`,
// enabled: false
// },
// {
// label: `${process.env.VITE_APP_TITLE}`,
// enabled: false
// }
// ]
// }
// ]);
// Menu.setApplicationMenu(menu);
};
const registerShortcuts = () => {
// 常见快捷键全封
const shortcuts = [
"F12",
"Ctrl+Shift+I",
"Cmd+Option+I",
"Ctrl+Shift+J",
"Cmd+Option+J",
"Ctrl+U", // 查看源代码
"Cmd+U"
];
shortcuts.forEach((shortcut) => {
globalShortcut.register(shortcut, () => {
console.log(`Blocked development shortcut: ${shortcut}`);
// 啥也不干,进行拦截
});
});
};
// Electron 会在初始化后并准备
app.whenReady().then(() => {
createWindow();
// 在应用准备好后,根据条件注册全局快捷键
if (process.env.VITE_APP_DEBUG !== "true") {
registerShortcuts();
}
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
if (process.env.VITE_APP_DEBUG !== "true") globalShortcut.unregisterAll();
app.quit();
}
});这里的逻辑稍后再分别解释,反正这是一份完整的代码,大家可以先看看。
Vite 配置
打开 vite.config.ts 文件,引入并配置 vite-plugin-electron。
import electron from "vite-plugin-electron";
export default defineConfig({
plugins: [
electron({
entry: "./electron/main.js"
}),
],
});Vue Router配置
在 Electron 的生产环境中,应用是通过 file:// 协议加载的,标准的 createWebHistory 模式无法正常工作。因此,我们必须使用哈希模式 (createWebHashHistory)。
import { createRouter, createWebHashHistory } from "vue-router";
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [],
scrollBehavior: () => ({ left: 0, top: 0 })
});
export default router;package.json配置
这是整合的最后一步,也是非常关键的一步。我们需要配置 main 入口、scripts 命令以及 electron-builder 的打包选项。
{
"name": "qyx-excel-tool",
"version": "0.0.1",
"main": "./electron/main.js",
"author": {
"name": "作者名称",
"url": "作者主页链接,例如:公司网站",
"email": "联系邮箱"
},
"scripts": {
"electron:build": "vite build && electron-builder"
},
"build": {
"productName": "打包后的包名",
"appId": "com.xxx.xx",
"copyright": "版权说明。例:Copyright © 2025 xxx. All rights reserved.",
"compression": "maximum",
"asar": true,
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"perMachine": true,
"allowElevation": true,
"deleteAppDataOnUninstall": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true
},
"files": [
"dist/**/*",
"dist-electron/**/*",
"electron/**/*",
"public/**/*",
".env.production"
],
"directories": {
"output": "output/"
},
"win": {
"icon": "./public/favicon.ico",
"artifactName": "${productName}-v${version}-${platform}-setup.${ext}",
"target": [
{
"target": "nsis"
}
]
}
}
}关键配置项解释:
main: 指定 Electron 应用的主进程入口文件。构建后,它位于dist-electron/main.js。scripts.electron:build: 定义了打包命令,它会先执行vite build构建 Web 内容,然后执行electron-builder进行桌面应用打包。build:electron-builder的所有配置都写在这里。productName: 安装包和应用显示的名字。appId: 应用的唯一标识符,通常采用反向域名格式。directories.output: 指定打包文件的输出目录,这里设置为release/。files: 一个数组,指定哪些文件和目录需要被打包进最终的应用中。dist存放 Vue 应用构建产物,dist-electron存放主进程构建产物。我们还需要将生产环境变量文件.env.production打包进去。win: 针对 Windows 平台的特定配置。nsis:NSIS安装程序制作工具的配置项。oneClick:false表示创建传统的多步安装向导,而不是一键安装。allowToChangeInstallationDirectory:true允许用户在安装时选择安装路径。
运行与构建
开发模式
执行 dev 命令,vite-plugin-electron 会自动启动 Vite 开发服务器和 Electron 应用窗口。
yarn dev在开发模式下,对 Vue 组件或 Electron 主进程代码的任何修改都会触发热重载,极大地提升了开发效率。
打包构建
当应用开发完成后,执行 electron:build 命令来进行打包。
yarn run electron:build命令执行成功后,会在 output 目录下找到生成的安装包(例如 .exe 文件)和相关文件。
高级:环境变量
Vite 的环境变量(import.meta.env)和主进程的 Node.js 环境变量(process.env)是隔离的。主进程无法直接访问 Vite 的 VITE_ 前缀变量。而且需要注意的是,这里的node进程不是运行打包命令时的命令运行进程,而是运行electron的进程。
由于我们需要使用环境变量来控制一些配置,但是灵活性暂时没有vite那边方便,vite可以通过mode参数来自由指定环境变量文件,但是在electron中,我们需要自己实现这种功能,索性就简单点,就用两个环境变量文件:.env.development、.env.production来做。
我们通过Boolean(process.env.VITE_DEV_SERVER_URL ?? false)判断是不是dev环境,如果不是就是生产环境。
然后固定加载指定的环境变量文件,使用dotenv库将环境变量自动挂载到process.env中。这个命令必须在使用之前执行,所以一般都是写在最开头的位置。
但是需要注意,在build的时候,我们还需要将这个文件也打进去,所以需要在package.json中的files中将生产的文件也加上。
操作步骤:
- 创建
.env.development和.env.production文件。 - 在
main.ts的最顶部,通过判断isDev变量来确定当前环境。 - 使用
dotenv库加载对应的.env文件,将其中的变量注入到process.env中。
// electron/main.ts 开头部分
const isDev = Boolean(process.env.VITE_DEV_SERVER_URL);
const envPath = isDev
? path.resolve(__dirname, "../.env.development")
: path.resolve(__dirname, "../.env.production");
dotenv.config({ path: envPath });
// 现在可以在 main.ts 的任何地方使用 process.env.VITE_APP_...切记:如 package.json 中配置所示,必须将 .env.production 文件包含在 build.files 数组中,这样打包时它才会被复制到应用资源目录中,dotenv 在生产环境才能找到并加载它。
{
"build": {
"files": [".env.production"]
}
}高级:Prettier和ESLint配置忽略项
electron的一些文件是不需要格式化的,比如打包后的文件。
Prettier
在 .prettierignore 文件中添加以下内容(没有自己手动创建):
dist-electron/*
output/*ESLint
现在的都是v9版本ESLint,在 eslint.config.ts文件中添加以下内容:
export default defineConfigWithVueTs(
{
name: "app/files-to-ignore",
ignores: [
"**/dist-electron/**",
"**/output/**"
]
}
)总结
这个项目只是一个起点。接下来,你还可以探索更多高级功能,例如:
- 创建自定义的原生菜单栏。
- 使用
electron-store实现应用配置的本地持久化存储。 - 集成
electron-updater实现应用的热更新功能。 - 调用更多系统原生 API,如系统通知、剪贴板等。
希望这篇更详尽的指南能帮助你顺利开启 Electron 开发之旅!
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据