Electron Vite MPA 项目搭建
概括
环境准备
- Node.js: 建议 v20.10.0+ (LTS)。
- 包管理器: pnpm (推荐 v9+)。
初始化 Vue 项目
脚手架快速创建
使用 Vite 最新脚手架创建 Vue + TS 项目。
- 创建 electron-app 项目
pnpm create vite electron-app --template vue-ts
➜ electron-vue3 pnpm create vite electron-app --template vue-ts
│
◇ Use rolldown-vite (Experimental)?:
│ No
│
◇ Install with pnpm and start now?
│ Yes
│
◇ Scaffolding project in /Volumes/Code/Code/Electron/electron-vue3/electron-app-vite...
│
◇ Installing dependencies with pnpm...
Packages: +48
++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 94, reused 48, downloaded 0, added 48, done
dependencies:
+ vue 3.5.25
devDependencies:
+ @types/node 24.10.1
+ @vitejs/plugin-vue 6.0.2
+ @vue/tsconfig 0.8.1
+ typescript 5.9.3
+ vite 7.2.6
+ vue-tsc 3.1.5
╭ Warning ───────────────────────────────────────────────────────────────────────────────────╮
│ │
│ Ignored build scripts: esbuild. │
│ Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts. │
│ │
╰────────────────────────────────────────────────────────────────────────────────────────────╯
Done in 7.4s using pnpm v10.11.1
│
◇ Starting dev server...
> electron-app-vite@0.0.0 dev /Volumes/Code/Code/Electron/electron-vue3/electron-app-vite
> vite
You are using Node.js 21.7.1. Vite requires Node.js version 20.19+ or 22.12+. Please upgrade your Node.js version.
VITE v7.2.6 ready in 589 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
- 进入目录
cd electron-app
- 安装基础依赖
pnpm install
删除 Vue 页面
index.html
src/App.vue
src/main.ts
src/style.css
src/assets/vue.svg
src/components/HelloWorld.vue
搭建多页面
由于创建的是大型项目,需要搭建多窗口的页面,每个窗口内容相对独立。
electron-app/
├── src/
│ ├── renderer/
│ │ ├── app/ # 🟢 主窗口页面
│ │ │ ├── index.html # 主窗口 HTML (必须包含 type="module" 指向 main.ts)
│ │ │ ├── main.ts # 主窗口入口
│ │ │ └── App.vue
│ │ └── settings/ # 🔵 第二个窗口 (例如设置页)
│ │ │ ├── index.html
│ │ │ ├── main.ts
│ │ │ └── App.vue
│ └── assets/ # 公共资源
├── vite.config.ts
vite.config.ts
/**
* 当前 Vite 项目的根目录(渲染进程根路径)
* Electron 的渲染进程通常单独放在 /src/renderer
*/
root: resolve(__dirname, './src/renderer'),
/**
* Vite 缓存目录
*/
cacheDir: resolve(__dirname, './dist'),
/**
* 静态资源目录
* 例如 public/icon.png 将直接复制到构建输出
*/
publicDir: resolve(__dirname, './public'),
/**
* 插件部分
*/
plugins: [
vue(),
/**
* 开发服务器启动后自动本地重定向:
* 当访问 "/" 时跳转到 "/app/"
*/
{
name: 'vite-plugin-dev-redirect',
configureServer(server) {
server.middlewares.use((req, res, next) => {
if (req.url === '/') {
res.writeHead(302, { Location: '/app/' })
res.end()
} else {
next()
}
})
},
},
],
打包配置
vite.config.ts
/**
* 渲染进程构建配置
*/
build: {
// 输出目录
outDir: resolve('./dist_electron/dist'),
assetsDir: 'assets',
// 是否生成 JS sourcemap(这里关闭)
sourcemap: false,
// 使用 esbuild 进行压缩
minify: 'esbuild',
// 输出体积不打印 gzip 大小
reportCompressedSize: false,
// 开启清空输出目录
emptyOutDir: true,
// Rollup 打包配置
rollupOptions: {
// 多页面入口
input: getMultiPageInput(),
output: {
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
// vendor 分包(把 vue 单独拿出来)
manualChunks: {
vendor: ['vue'],
},
},
},
},
/**
* 依赖预构建优化
* 避免项目启动时卡顿
*/
optimizeDeps: {
force: true,
include: ['vue', 'vue-router'],
},
路径别名
vite.config.ts
/**
* 路径别名设置
*/
resolve: {
alias: {
'@/app': join(srcDir, 'renderer/app'),
'@/settings': join(srcDir, 'renderer/settings'),
},
},
tsconfig.app.json
"baseUrl": ".",
"paths": {
"@/app/*": ["src/renderer/app/*"],
"@/settings/*": ["src/renderer/settings/*"]
},
注入全局常量
vite.config.ts
/**
* 向应用注入全局常量(版本号、应用名)
*/
define: {
ViteConst: JSON.stringify({
AppVersion: pkg.version,
AppName: pkg.name,
}),
},
src/types/global.d.ts
export {};
declare global {
var ViteConst: {
AppName: string;
Version: string;
};
}
vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve, join } from "node:path";
import { getMultiPageInput } from "./configs/pages.ts";
import pkg from "./package.json";
const srcDir = resolve(__dirname, "src");
// https://vite.dev/config/
export default defineConfig({
envDir: "./env",
root: resolve(__dirname, "./src/renderer"),
cacheDir: resolve(__dirname, "./dist"),
publicDir: resolve(__dirname, "./public"),
resolve: {
alias: {
// '@': join(srcDir, ''),
// '@/renderer': join(srcDir, 'renderer'),
"@/app": join(srcDir, "renderer/app"),
"@/settings": join(srcDir, "renderer/settings"),
},
},
define: {
ViteConst: JSON.stringify({
AppVersion: pkg.version,
AppName: pkg.name,
}),
},
plugins: [
vue(),
{
name: "vite-plugin-dev-redirect",
configureServer(server) {
server.middlewares.use((req, res, next) => {
if (req.url === "/") {
res.writeHead(302, { Location: "/app/" });
res.end();
} else {
next();
}
});
},
},
],
build: {
outDir: resolve("./dist_electron/dist"),
assetsDir: "assets",
sourcemap: false,
minify: "esbuild",
reportCompressedSize: false,
emptyOutDir: true,
rollupOptions: {
input: getMultiPageInput(),
output: {
chunkFileNames: "static/js/[name]-[hash].js",
entryFileNames: "static/js/[name]-[hash].js",
assetFileNames: "static/[ext]/[name]-[hash].[ext]",
manualChunks: {
vendor: ["vue"],
},
},
},
},
// 预打包
optimizeDeps: {
force: true,
include: ["vue", "vue-router"],
},
server: {
host: true,
port: 5173,
open: true,
proxy: {
"/api": {
target: "http://localhost:3000",
changeOrigin: true,
rewrite: (p) => p.replace(/^\/api/, ""),
},
},
},
});
安装 Electron 全家桶
electron 目录结构规划
electron-app/
├── electron/ # 主进程 (保持不变)
│ └── main/ # 公共资源
│ └── preload/ # 公共资
├── vite.config.ts
└── package.json
安装 Electron 和 适配新版 Vite 的插件。
pnpm add -D electron vite-plugin-electron vite-plugin-electron-renderer
➜ electron-vite-vue (vue-ts) pnpm add -D electron vite-plugin-electron vite-plugin-electron-renderer
WARN 1 deprecated subdependencies found: boolean@3.2.0
Packages: +303
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 397, reused 290, downloaded 60, added 302
Progress: resolved 397, reused 290, downloaded 61, added 302
Progress: resolved 397, reused 290, downloaded 61, added 303, done
devDependencies:
+ @typescript-eslint/eslint-plugin 8.48.1
+ @typescript-eslint/parser 8.48.1
+ @vue/eslint-config-prettier 10.2.0
+ @vue/eslint-config-typescript 14.6.0
+ @vue/test-utils 2.4.6
+ electron 39.2.5
+ eslint 9.39.1
+ eslint-plugin-vue 10.6.2
+ jsdom 27.2.0
+ vite-plugin-electron 0.29.0
+ vite-plugin-electron-renderer 0.14.6
+ vitest 4.0.15
╭ Warning ─────────────────────────────────────────────────────────────────────╮
│ │
│ Ignored build scripts: electron. │
│ Run "pnpm approve-builds" to pick which dependencies should be allowed │
│ to run scripts. │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
Done in 21.4s using pnpm v10.11.1
修改 package
配置 electron 执行的入口
"main": "dist_electron/main/index.js",
vite.config.ts
/**
* 插件部分
*/
plugins: [
vue(),
// 处理 Electron 主进程 / preload 自动打包
electron(electronConfig(electronOptions)),
// 处理渲染进程与主进程 IPC 通信支持
renderer(),
],
编写主进程
这里使用更现代的写法,适配 ESM 和 CJS。
import { app, shell, BrowserWindow, ipcMain } from "electron";
// import { join } from 'path'
import { electronApp, optimizer, is } from "@electron-toolkit/utils";
// import icon from '../../resources/icon.png?asset'
import { nativeImage } from "electron";
// import { join } from 'node:path';
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
// 当前文件路径
const __filename = fileURLToPath(import.meta.url);
// 当前目录
const __dirname = dirname(__filename);
const iconPath = join(__dirname, "../resources/icon.png");
const trayIcon = nativeImage.createFromPath(iconPath);
console.log("icon", process.env["VITE_DEV_SERVER_URL"]);
function createWindow(): void {
console.log("createWindow");
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
show: false,
autoHideMenuBar: true,
...(process.platform === "linux" ? { trayIcon } : {}),
webPreferences: {
preload: join(__dirname, "../preload/index.js"),
sandbox: false,
},
});
mainWindow.on("ready-to-show", () => {
mainWindow.show();
});
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url);
return { action: "deny" };
});
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env["VITE_DEV_SERVER_URL"]) {
mainWindow.loadURL(process.env["VITE_DEV_SERVER_URL"] + "settings/");
mainWindow.webContents.openDevTools();
// mainWindow.loadFile(join(__dirname, `../src/renderer/app/index.html`))
} else {
mainWindow.loadFile(join(__dirname, "../renderer/app/index.html"));
}
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId("com.electron");
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on("browser-window-created", (_, window) => {
optimizer.watchWindowShortcuts(window);
});
// IPC test
ipcMain.on("ping", () => console.log("pong"));
createWindow();
app.on("activate", function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
编写预加载脚本
即使暂时不用,也建议放一个空文件,防止报错。
import { contextBridge } from "electron";
// 暴露 API 给渲染进程 (如果有需要)
// contextBridge.exposeInMainWorld('myAPI', { ... })
console.log("Preload script loaded");
修改 package.json (关键)
新版配置下,我们不需要删除 "type": "module",Vite 项目应当保持为 Module。
修改点如下:
main: 指向编译后的主进程文件(插件通常默认输出到dist-electron/main.js)。scripts: 依然建议加上chcp 65001。
{
"name": "electron-app",
"private": true,
"version": "0.0.0",
"type": "module",
"main": "dist-electron/main.js",
"scripts": {
"dev": "chcp 65001 && vite",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"electron": "^33.0.0",
"typescript": "^5.6.0",
"vite": "^6.0.0",
"vite-plugin-electron": "^0.28.0",
"vite-plugin-electron-renderer": "^0.14.0",
"vue-tsc": "^2.1.0"
}
}
配置 TypeScript
为了让 TS 识别 Electron 的类型,需要将 electron 目录加入包含路径。
在 include 数组中添加 "electron":
{
"compilerOptions": {
// ... 其他配置
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "electron"]
}
启动与验证
- 启动开发环境:
pnpm dev
你应该能看到 Vite 启动了 Web 服务,随后弹出了 Electron 窗口。
- 打包:
pnpm build
Vite 会先构建 Vue 页面到 dist,然后插件会构建 Electron 主进程到 dist-electron。
##:新旧版本核心区别
| 特性 | 旧版 (你的参考文档) | 新版 (Vite 6/7 + Electron 33) |
|---|---|---|
| 模块规范 | 需要删除 "type": "module" 强制切回 CJS | 保留 "type": "module",全面拥抱 ESM |
| 插件配置 | 手动配置 vite-plugin-electron 路径 | 使用 vite-plugin-electron/simple 极简配置 |
| 文件加载 | 容易出现 ESM/CJS 混用报错 | 插件自动处理转译,开发体验平滑 |
| 预加载脚本 | 往往忽略,直接开启 Node 集成 | 默认支持 preload,更符合安全标准 |
兼容问题
从安装步骤上看,我们默认都是安装的最新的版本,本次测试 electron 39。但是主流基本为 27 版本,但是大型项目需要使用的库的兼容性测试没有实现到最新版本的。所以默认安装最新版本可能会出现问题,所以需要客观的判断这个问题。
🔥 总结:为什么主流项目都卡在 Electron 27?
因为:
- 生态兼容性最佳
- 原生模块 ABI 最稳定
- 打包工具链支持最成熟
- 成本最低、风险最小
- 大厂项目的长期基线版本
对于需要稳定生产环境的应用(你这种 Electron + Vue 桌面级产品),v27 是目前最稳妥的选择。
✔️ 你应该升级到 v39 吗?
给你个实际判断:
如果满足以下条件,不建议升级:
- 使用 sqlite、ffi、robotjs、node-pty 等 native 模块 ✔️
- 你的应用要跨平台打包(Windows + macOS) ✔️
- 对稳定性要求高 ✔️
- 没有需要 Chromium 130+ 的新特性 ✔️
- 使用 electron-builder,而不是 forge ✔️
→ 最好继续使用 v27 或 v30 左右的版本。
✔️ 什么时候可以考虑升级到 v39?
如果:
- 完全切成 WASM(sql.js / WASM-FFI)
- 不使用原生模块
- 使用 electron-forge
- 打包基建已经准备好 macOS 新一套签名
- 有强需求(如要用最新 Web API)
注意
🔴 第一步:彻底解决 Electron 安装失败问题 (最严重)
你的报错 Error: Electron failed to install correctly 说明之前建议的镜像源配置可能没有生效,或者下载过程被中断了,导致 node_modules 里只有空壳,没有真正的 .exe / .app 程序。
请在终端中依次执行以下命令(不要跳过):
-
设置镜像源(确保环境配置正确)
# 设置 Electron 淘宝镜像
pnpm config set electron_mirror https://npmmirror.com/mirrors/electron/
pnpm config set electron_builder_binaries_mirror https://npmmirror.com/mirrors/electron-builder-binaries/ -
暴力重装 Electron
# 1. 删除依赖文件夹 (确保清理干净)
rm -rf node_modules pnpm-lock.yaml
# 2. 重新安装
pnpm install
# 3. ⭐️ 关键步骤:手动触发 Electron 二进制下载(验证是否成功)
# 如果这一步报错,说明网络依然有问题
node node_modules/electron/install.js如果第 3 步没有报错,说明 Electron 安装好了。
Node.js 版本不兼容 (警告)
报错信息:
You are using Node.js 21.7.1. Vite requires Node.js version 20.19+ or 22.12+.
原因: Node.js v21 是一个奇数版本的非长期支持版本 (Non-LTS),Vite 7 明确表示不支持。虽然它可能能跑,但容易遇到奇怪的 Bug。
解决方案: 建议切换到 v20 (LTS) 或 v22 (LTS)。
➜ electron-vite-vue (vue-ts) pnpm exec electron . ✗ ✭ ✱
/Volumes/Code/Code/Electron/electron-vue3/electron-vite-vue/node_modules/.pnpm/electron@39.2.4/node_modules/electron/index.js:17
throw new Error('Electron failed to install correctly, please delete node_modules/electron and try installing again');
^
Error: Electron failed to install correctly, please delete node_modules/electron and try installing again
at getElectronPath (/Volumes/Code/Code/Electron/electron-vue3/electron-vite-vue/node_modules/.pnpm/electron@39.2.4/node_modules/electron/index.js:17:11)
at Object.<anonymous> (/Volumes/Code/Code/Electron/electron-vue3/electron-vite-vue/node_modules/.pnpm/electron@39.2.4/node_modules/electron/index.js:21:18)
at Module._compile (node:internal/modules/cjs/loader:1368:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1426:10)
at Module.load (node:internal/modules/cjs/loader:1205:32)
at Module._load (node:internal/modules/cjs/loader:1021:12)
at Module.require (node:internal/modules/cjs/loader:1230:19)
at require (node:internal/modules/helpers:179:18)
at Object.<anonymous> (/Volumes/Code/Code/Electron/electron-vue3/electron-vite-vue/node_modules/.pnpm/electron@39.2.4/node_modules/electron/cli.js:5:18)
at Module._compile (node:internal/modules/cjs/loader:1368:14)
Node.js v21.7.1
➜ electron-vite-vue (vue-ts) rm -rf node_modules
rm -f pnpm-lock.yaml
pnpm store prune
pnpm install --ignore-scripts=false
pnpm approve-builds
Removed all cached metadata files
Removed 177 files
Removed 2 packages
WARN 1 deprecated subdependencies found: boolean@3.2.0
Packages: +120
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 166, reused 118, downloaded 2, added 120, done
dependencies:
+ vue 3.5.25
devDependencies:
+ @types/node 24.10.1
+ @vitejs/plugin-vue 6.0.2
+ @vue/tsconfig 0.8.1
+ electron 39.2.4
+ typescript 5.9.3
+ vite 7.2.6
+ vite-plugin-electron 0.29.0
+ vite-plugin-electron-renderer 0.14.6
+ vue-tsc 3.1.5
╭ Warning ─────────────────────────────────────────────────────────────────────╮
│ │
│ Ignored build scripts: electron, esbuild. │
│ Run "pnpm approve-builds" to pick which dependencies should be allowed │
│ to run scripts. │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
Done in 23.8s using pnpm v10.11.1
? Choose which packages to build (Press <space> to select, <a> to toggle all, <i> to invert selection) …
● electron
❯ ● esbuild