全局配置
1. 为什么选择Electron+ Vite +Vue3这套组合?
简单来说,Electron让你能用前端技术写桌面应用,Vite负责极速的开发体验,Vue3提供了现代、响应式的UI框架,TypeScript帮你提前发现代码里的“坑”。这几者结合在一起,就像给开发流程装上了涡轮增压。
2. 环境准备与项目初始化
2.1 确保你的开发环境就绪
在开始之前,你得先检查一下自己的“装备”。Node.js版本很关键,我强烈推荐使用Node.js 20.x LTS或更高版本。为什么不是最新的22.x?因为很多Electron相关的库对20.x的支持最稳定,我在22.x上遇到过一些奇怪的兼容性问题。你可以用node -v命令检查版本。
包管理器方面,pnpm是我的首选。它比npm和yarn快得多,而且磁盘空间占用小,对于Electron这种依赖多的项目特别友好。如果你还没装pnpm,用这个命令安装就行:npm install -g pnpm。
另外,我建议装个代码编辑器,VS Code是绝大多数Electron开发者的选择。装上Volar(Vue3官方扩展)、TypeScript扩展和Electron相关的插件,开发体验会提升一个档次。
2.2 一键创建你的第一个项目
准备工作做完,咱们就可以创建项目了。electron-vite官方提供了一个超级方便的脚手架工具,让你不用再手动配置那些繁琐的Vite和Electron集成。
打开你的终端,运行下面这个命令(我用的是pnpm,你用npm或yarn也行):
1
pnpm create @quick-start/electron@latest mytemplate
运行后,命令行会问你几个问题,就像下面这样:
1
2
3
4
5
✔ Project name: … mytemplate
✔ Select a framework: › vue
✔ Add TypeScript? … Yes
✔ Add Electron updater plugin? … Yes
✔ Enable Electron download mirror proxy? … No(可选)
这里有几个选择我解释一下。框架肯定选Vue,TypeScript强烈建议选Yes,虽然刚开始可能有点不习惯,但用久了你会发现它帮你避免了很多低级错误。Electron updater插件是用来做自动更新的,如果你打算发布正式版本,最好选上。最后一个镜像代理,如果你在国内网络环境不太好,可以选Yes加速下载。
选完之后,脚手架会自动创建项目结构并安装依赖。整个过程大概一两分钟,取决于你的网速。我第一次用的时候,看到那一堆依赖自动安装,感觉特别省心,以前手动配置至少要折腾半小时。
2.3 理解生成的项目结构
项目创建好后,用VS Code打开,你会看到这样的目录结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mytemplate/ # 项目根目录
├── resources/ # 应用静态资源目录(开发/打包均需用到)
│ ├── icon.icns # Mac 系统应用图标
│ └── icon.ico # Windows 系统应用图标
├── src/ # 项目源码核心目录
│ ├── main/ # Electron 主进程目录(Node.js 环境)
│ │ └── index.ts # 主进程入口文件(创建窗口、IPC 通信等核心逻辑)
│ ├── preload/ # 预加载脚本目录(桥接主进程和渲染进程)
│ │ └── index.ts # 预加载入口文件(暴露安全的 API 给渲染进程)
│ └── renderer/ # 渲染进程目录(Vue3 + Web 环境)
│ ├── src/ # 渲染进程源码子目录(符合 Vue 项目通用规范)
│ │ ├── assets/ # Vue 静态资源(图片、样式、字体等)
│ │ ├── components/ # Vue 公共组件(可复用的 UI 组件)
│ │ ├── App.vue # Vue 根组件(渲染进程页面入口)
│ │ └── main.ts # 渲染进程入口文件(创建 Vue 实例、挂载 DOM)
│ └── index.html # 渲染进程入口 HTML(Vue 挂载的根容器)
├── electron.vite.config.ts # Electron + Vite 核心配置文件(配置别名、打包规则等)
├── package.json # 项目依赖/脚本配置文件
├── tsconfig.json # TypeScript 根级配置(公共编译规则)
├── tsconfig.node.json # 主进程/预加载脚本的 TS 配置(Node.js 环境)
└── tsconfig.web.json # 渲染进程的 TS 配置(Web 环境,适配 Vue)
这个结构比传统的Electron项目清晰多了。以前我经常搞混主进程和渲染进程的文件该放哪,现在electron-vite帮你分得清清楚楚。main目录下放Electron主进程的逻辑,比如创建窗口、处理系统菜单这些;preload是预加载脚本,负责在渲染进程和主进程之间安全地通信;renderer就是你的Vue3前端代码了。
我特别喜欢这种分离的设计,因为它强制你遵循Electron的安全最佳实践。很多新手容易犯的错误就是把Node.js的模块直接在前端页面里引入,这会有安全风险。现在有了预加载脚本这个明确的边界,你就知道哪些API该通过预加载暴露给前端。
3. 核心配置详解与优化
3.1 electron.vite.config.ts深度解析
这个配置文件是整个项目的“大脑”,理解了它,你就掌握了electron-vite的精髓。默认生成的配置已经能工作了,但根据我的经验,有几个地方你最好调整一下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { resolve } from 'path'
import { defineConfig } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
main: {
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/main/index.ts')
},
external: [] // 如需外部化特定依赖,可在这里配置,例如 ['electron']
}
}
},
preload: {
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/preload/index.ts')
},
external: []
}
}
},
renderer: {
root: resolve(__dirname, 'src/renderer/'),
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src/renderer/src')
}
},
server: {
port: 5173,
strictPort: true
}
}
})
我重点说几个关键点。
resolve.alias配置了@指向renderer目录,这样你在Vue组件里就可以用@/components/Button.vue这样的路径了,比相对路径清晰很多。
server.port我固定为了5173,strictPort: true确保端口被占用时会报错而不是自动换端口。为什么这么做?因为Electron启动时需要连接这个开发服务器,如果端口变来变去,Electron就找不到你的前端页面了。这个坑我踩过,调试了半天才发现是端口问题。
3.2 TypeScript配置的实战技巧
TypeScript的配置在tsconfig.json、tsconfig.web.json和tsconfig.node.json里。默认配置能用,但如果你想获得更好的开发体验,我建议加几个选项。
在tsconfig.web.json里,加上这些:
1
2
3
4
5
6
7
8
9
{
"compilerOptions": {
"strict": true,
"types": ["vite/client"],
"paths": {
"@/*": ["src/renderer/*"]
}
}
}
strict: true开启所有严格检查,刚开始可能会有点痛苦,报很多错,但坚持下来,你的代码质量会高很多。types: ["vite/client"]让TypeScript认识Vite特有的导入语法,比如导入.vue文件。
对于主进程的TypeScript配置,有个特别的地方。Electron主进程运行在Node.js环境,所以你需要安装Node.js的类型定义:pnpm add -D @types/node。然后在主进程的代码里,你可能会遇到Electron API的类型问题,这时候可以这样处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/main/index.ts
import { app, BrowserWindow, type BrowserWindowConstructorOptions } from 'electron'
import path from 'path'
// 这样就有完整的类型提示了
const createWindow = (): void => {
const options: BrowserWindowConstructorOptions = {
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
contextIsolation: true,
nodeIntegration: false
}
}
const mainWindow = new BrowserWindow(options)
// ... 其他代码
}
我刚开始用的时候,总觉得TypeScript麻烦,一个简单的窗口创建都要写类型。但用久了发现,它真的能帮你避免很多运行时错误。比如有一次我写window.width(应该是window.getSize()[0]),TypeScript直接报错,省了我调试的时间。
3.3 开发脚本与工作流优化
package.json里的scripts是每天都要用的,配置好了能极大提升效率。除了默认的,我一般还会加几个:
1
2
3
4
5
6
7
8
{
"scripts": {
"preview": "electron-vite preview",
"dev:debug": "electron-vite dev --debug",
"dev:inspect": "electron-vite dev --inspect",
"type-check": "vue-tsc --noEmit"
}
}
dev:debug和dev:inspect是我强烈推荐的。Electron调试有时候挺头疼的,特别是主进程的bug。加上--debug参数,你可以用Chrome DevTools调试主进程;--inspect则开启Node.js调试器,可以在VS Code里打断点。
type-check命令单独做类型检查,有时候开发时Vite的TypeScript检查不够全面,用这个命令做一次全面检查,确保没有类型问题。
lint用ESLint统一代码风格,团队协作时特别重要。我建议在提交代码前跑一下这个命令,保持代码库整洁。
实际开发中,我的工作流是这样的:开一个终端跑pnpm dev,另一个终端跑pnpm type-check --watch(实时类型检查),VS Code里开着ESLint插件。这样写代码的时候,类型错误、语法问题都能实时看到,基本不用等到编译时才发现问题。
Vue配置
第一步:用 pnpm 安装依赖
在项目根目录执行(不要进 render 文件夹):
1
2
3
4
5
# 安装核心依赖(对应 npm install)
pnpm add pinia vue-router axios
# 安装 TypeScript 类型支持(开发依赖,对应 npm install -D)
pnpm add -D @types/vue-router
第二步:配置 Vue Router
在 render/src 下创建 router 目录,新建 index.ts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// render/src/router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../views/Home.vue' // 如果你有这个页面
const routes = [
{ path: '/', name: 'Home', component: Home },
{ path: '/about', name: 'About', component: () => import('../views/About.vue') } // 懒加载
]
const router = createRouter({
history: createWebHashHistory(), // Electron 推荐用 Hash 模式,避免打包后路径问题
routes
})
export default router
第三步:配置 Pinia
在 render/src 下创建 stores 目录,新建 counter.ts(示例 Store):
1
2
3
4
5
6
7
8
9
10
11
// render/src/stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
}
}
})
第四步:封装 Axios
封装 Axios
在 render/src 下创建 utils 目录,新建 request.ts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// render/src/utils/request.ts
import axios from 'axios'
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
})
// 请求拦截器(比如添加 Token)
service.interceptors.request.use(
(config) => {
// 示例:从 Pinia 或 localStorage 取 Token
// const token = localStorage.getItem('token')
// if (token) config.headers.Authorization = `Bearer ${token}`
return config
},
(error) => Promise.reject(error)
)
// 响应拦截器(处理错误)
service.interceptors.response.use(
(response) => response.data,
(error) => {
console.error('请求失败:', error)
return Promise.reject(error)
}
)
export default service
创建 API 接口
在 src 目录下创建 api/index.ts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import request from '../utils/request'
// 示例接口
export function getExampleData(params) {
return request({
url: '/example',
method: 'get',
params
})
}
export function postExampleData(data) {
return request({
url: '/example',
method: 'post',
data
})
}
配置环境变量
在项目根目录创建 .env.development:
1
2
# 开发环境配置
VITE_API_BASE_URL=http://localhost:3000/api
创建 .env.production:
1
2
# 生产环境配置
VITE_API_BASE_URL=https://api.example.com
第五步:在页面中挂载
修改main.ts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// render/src/main.ts
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
app.mount('#app')
第六步:创建示例页面并使用
1. 创建 render/src/views/Home.vue:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!-- render/src/views/Home.vue -->
<template>
<div>
<h1>首页</h1>
<p>Pinia 计数: </p>
<button @click="counterStore.increment">点我 +1</button>
<button @click="goToAbout">跳转到 About 页</button>
<button @click="testAxios">测试 Axios 请求</button>
</div>
</template>
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter' // 用 @ 别名导入
import { useRouter } from 'vue-router'
import request from '@/utils/request' // 导入封装的 Axios
const counterStore = useCounterStore()
const router = useRouter()
// 路由跳转
const goToAbout = () => {
router.push('/about')
}
// 测试 Axios
const testAxios = async () => {
try {
const res = await request.get('/api/hello') // 替换成你的测试接口
console.log('请求成功:', res)
} catch (err) {
console.error('请求失败:', err)
}
}
</script>
2. 创建 render/src/views/About.vue:
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- render/src/views/About.vue -->
<template>
<div>
<h1>关于页</h1>
<button @click="router.push('/')">返回首页</button>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
</script>
3. 修改 render/src/App.vue:
1
2
3
4
<!-- render/src/App.vue -->
<template>
<router-view /> <!-- 路由出口 -->
</template>
第七步(可选):安装vue devtools
步骤 1:获取 Vue Devtools 扩展文件
- GitHub 仓库下载源码压缩包。
- 解压进入项目目录,并安装依赖
- 依赖安装成功后,进行打包:
1
2
pnpm install
pnpm build:chrome-extension
步骤 2:集成到electron 项目
在 Electron 的主进程文件(如 electron/main/index.ts)中添加:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import { app, shell, BrowserWindow, ipcMain, session } from 'electron' // 新增 session,用于加载 Chrome 扩展
import { join, resolve } from 'path' // 新增 resolve,用于精准解析扩展路径
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
function createWindow(): void {
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
show: false,
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
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' }
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
// 仅开发环境加载 Vue DevTools 的函数
async function loadVueDevTools(): Promise<void> {
if (!is.dev) return // 仅开发环境执行,生产环境直接返回
try {
// 根据实际路径改动
const devtoolsPath = resolve(__dirname, '../../devtools/7.7.7_0')
// 使用 session 加载本地 Chrome 扩展,loadExtension会显示已弃用,但仍然可用
const extension = await session.defaultSession.loadExtension(devtoolsPath)
console.log(`Vue DevTools 加载成功: ${extension.name}`)
} catch (err) {
console.error('Vue DevTools 加载失败:', err)
}
}
// 将回调函数改为 async,以支持 await 加载扩展
app.whenReady().then(async () => {
electronApp.setAppUserModelId('com.electron')
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
// 在创建窗口前加载 Vue DevTools(仅开发环境)
await loadVueDevTools()
ipcMain.on('ping', () => console.log('pong'))
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
最后:启动项目
在根目录执行:
1
pnpm dev
全局配置节选自CSDN 黑虾电影并稍作改动