前言

有时候我们需要将前端项目打包成一个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 -D
  • electron: 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中将生产的文件也加上。

操作步骤:

  1. 创建 .env.development.env.production 文件。
  2. main.ts 的最顶部,通过判断 isDev 变量来确定当前环境。
  3. 使用 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 开发之旅!

分类: vue 项目实战 标签: vue3viteElectronexe桌面程序原生

评论

暂无评论数据

暂无评论数据

目录