Files
electron-vue/README.md
T
2026-05-21 00:35:43 +08:00

346 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Electron + Vue3 + TypeScript 项目
基于 [electron-vite](https://electron-vite.org/) 构建的现代化 Electron 应用模板。
## 技术栈
| 领域 | 技术 |
|------|------|
| 构建工具 | [electron-vite](https://electron-vite.org/) |
| 前端框架 | Vue 3 (Composition API) |
| 语言 | TypeScript |
| UI | 原生 HTML/CSS + Vue 组件 |
## 项目结构
```
├── electron.vite.config.ts # Vite 配置(electron-vite 专用)
├── tsconfig*.json # TypeScript 配置
├── src/
│ ├── main/ # 主进程(Node.js 环境)
│ │ └── index.ts
│ ├── preload/ # 预加载脚本(桥接层)
│ │ ├── index.ts
│ │ └── index.d.ts # 类型声明
│ └── renderer/ # 渲染进程(浏览器环境)
│ ├── index.html
│ └── src/
│ ├── main.ts # Vue 入口
│ ├── App.vue
│ └── components/
│ └── Counter.vue
├── out/ # 构建输出目录
└── package.json
```
## 核心概念
### 1. 三进程架构
Electron 有三个独立的进程:
```
┌─────────────────────────────────────────────────┐
│ Main Process │
│ (Node.js) - 创建窗口、管理应用生命周期 │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────┐ │
│ │ Preload Script │ │
│ │ (上下文桥接,安全暴露 API 到渲染进程) │ │
│ └───────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────┐ │
│ │ Renderer Process │ │
│ │ (Chromium) - Vue 应用运行,DOM 操作 │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
```
**主进程 (main)**:运行 Node.js,负责窗口管理、应用生命周期、系统 API 调用
**预加载脚本 (preload)**:运行在渲染进程中,但能访问 Node.js API,通过 `contextBridge` 安全暴露有限 API
**渲染进程 (renderer)**:运行 Web 内容,Vue/React 应用在此执行,只能通过 IPC 与主进程通信
### 2. 上下文隔离 (Context Isolation)
```javascript
// main.js - 窗口配置
webPreferences: {
contextIsolation: true, // 启用上下文隔离
nodeIntegration: false, // 禁用 Node.js 访问
preload: 'preload.js' // 加载预加载脚本
}
```
- `contextIsolation: true` 防止渲染进程直接访问 Node.js,防止 XSS 攻击
- 渲染进程只能通过 `window.electron``window.api` 访问预暴露的 API
### 3. IPC 通信模式
```
Renderer Main
│ │
│ ipcRenderer.invoke('xxx') │
│ ─────────────────────────────►│
│ │ ipcMain.handle('xxx', ...)
│ Promise<Result> │
│ ◄─────────────────────────────│
▼ ▼
```
```typescript
// preload - 暴露 IPC 通道
contextBridge.exposeInMainWorld('api', {
getData: () => ipcRenderer.invoke('get-data')
})
// renderer - 调用
const data = await window.api.getData()
// main - 处理
ipcMain.handle('get-data', () => {
return { message: 'Hello from main!' }
})
```
### 4. electron-vite 构建流程
```
┌─────────────────────────────────────────────────────────┐
│ electron-vite dev │
├─────────────────────────────────────────────────────────┤
│ main │ Vite (SSR) → out/main/index.js │
│ preload │ Vite (ESM) → out/preload/index.mjs │
│ renderer│ Vite + Vue → http://localhost:5173 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ electron-vite build │
├─────────────────────────────────────────────────────────┤
│ main │ Vite (SSR) → out/main/index.js │
│ preload │ Vite (ESM) → out/preload/index.mjs │
│ renderer│ Vite + Vue → out/renderer/ │
└─────────────────────────────────────────────────────────┘
```
```
┌─────────────┬─────────────────────────────────────────┐
│ 旧文件 │ → 新文件 │
├─────────────┼─────────────────────────────────────────┤
│ main.js │ src/main/index.ts │
├─────────────┼─────────────────────────────────────────┤
│ preload.js │ src/preload/index.ts │
├─────────────┼─────────────────────────────────────────┤
│ index.html │ src/renderer/index.html │
├─────────────┼─────────────────────────────────────────┤
│ renderer.js │ src/renderer/src/components/Counter.vue │
└─────────────┴─────────────────────────────────────────┘
```
electron-vite 自动处理:
- 主进程和预加载脚本的 CommonJS/ESM 兼容
- 渲染进程的热模块替换 (HMR)
- 路径别名、依赖打包等
### 5. Vue 组件生命周期
```typescript
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const count = ref(0)
let timer: ReturnType<typeof setTimeout> | null = null
function updateCounter(): void {
count.value++
timer = setTimeout(updateCounter, 1000)
}
onMounted(() => {
updateCounter()
})
onUnmounted(() => {
if (timer) clearTimeout(timer)
})
</script>
```
- `onMounted` - 组件挂载时启动定时器
- `onUnmounted` - 组件卸载时清理定时器,防止内存泄漏
## 命令
```bash
# 开发模式(热重载)
npm run dev
# 生产构建
npm run build
# 运行已构建的应用
npm start
# 打包为可执行文件
npm run package
```
## 开发建议
1. **IPC 优先**:渲染进程不直接操作系统 API,通过 IPC 委托给主进程
2. **类型安全**preload 的 API 声明在 `.d.ts` 文件中,renderer 可获得智能提示
3. **组件化**:UI 逻辑封装在 Vue 组件中,主进程只负责业务无关的系统操作
4. **清理资源**:组件卸载时务必清理定时器、事件监听器等
## TypeScript 配置
项目有 3 个 TypeScript 配置文件:
### tsconfig.json(根配置/入口)
```json
{
"references": [
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.web.json" }
]
}
```
**作用**:项目总入口,本身不编译代码,通过 `references` 引用其他两个配置。
### tsconfig.node.jsonNode.js 环境)
**对应**`src/main/` + `src/preload/` + `electron.vite.config.ts`
```json
{
"lib": ["ES2022"],
"include": ["src/main/**/*", "src/preload/**/*", "electron.vite/**/*"]
}
```
**作用**:为主进程和预加载脚本提供类型检查,这两个运行在 Node.js 环境,需要 Node.js 内置类型(如 `process``path``fs`)。
### tsconfig.web.json(浏览器环境)
**对应**`src/renderer/`
```json
{
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "preserve",
"jsxImportSource": "vue",
"include": ["src/renderer/**/*"]
}
```
**作用**:为渲染进程(Vue 应用)提供类型检查,需要 DOM 类型(如 `window``document``HTMLElement`)和 Vue JSX 支持。
### 为什么分开?
```
┌─────────────────┐
│ tsconfig.json │ ← 根配置,定义整体规则
└────────┬────────┘
references
┌────┴────┐
▼ ▼
┌───────┐ ┌───────┐
│ node │ │ web │ ← 分别针对不同运行环境
│ .json │ │ .json │
└───┬───┘ └───┬───┘
│ │
Node.js 浏览器
API 类型 DOM 类型
```
不同运行环境有不同的全局 API,分开配置可以:
- 避免类型冲突(Node.js 的 `global` vs 浏览器的 `window`
- 按需引入类型库,减小检查范围
- IDE 根据当前文件路径自动选择对应配置
## 全部文件清单
| 文件 | 作用 |
|------|------|
| `package.json` | 项目配置:依赖、脚本、electron-builder 设置 |
| **`electron.vite.config.ts`** | **构建入口**:定义三个进程的入口路径、插件、别名 |
| `tsconfig.json` | TS 总入口,引用其他两个配置 |
| `tsconfig.node.json` | Node.js 环境配置(主进程、预加载) |
| `tsconfig.web.json` | 浏览器环境配置(渲染进程、Vue) |
| `src/main/index.ts` | Electron 主进程:创建窗口、管理应用生命周期、处理 IPC |
| `src/preload/index.ts` | 预加载脚本:通过 contextBridge 暴露安全 API |
| `src/preload/index.d.ts` | 预加载 API 的 TypeScript 类型声明 |
| `src/renderer/index.html` | 渲染进程 HTML 入口 |
| `src/renderer/src/main.ts` | Vue 应用入口 |
| `src/renderer/src/App.vue` | Vue 根组件 |
| `src/renderer/src/components/Counter.vue` | 计数器业务组件 |
| `out/` | 构建输出目录(`.gitignore` 忽略) |
## electron.vite.config.ts 详解
这是 electron-vite 的核心配置文件,作用类似普通 Vite 项目的 `vite.config.ts`,但需要为三个进程分别配置:
```typescript
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
// ── 主进程配置 ──
main: {
plugins: [externalizeDepsPlugin()], // 依赖外置,不打包进输出
build: {
rollupOptions: {
input: { index: 'src/main/index.ts' }
}
}
},
// ── 预加载脚本配置 ──
preload: {
plugins: [externalizeDepsPlugin()],
build: {
rollupOptions: {
input: { index: 'src/preload/index.ts' }
}
}
},
// ── 渲染进程配置 ──
renderer: {
root: 'src/renderer', // HTML 所在目录
plugins: [vue()], // Vue 插件
resolve: {
alias: { '@': 'src/renderer/src' } // 路径别名
},
build: {
rollupOptions: {
input: 'src/renderer/index.html' // HTML 入口
}
}
}
})
```
### 关键点
| 配置项 | 作用 |
|--------|------|
| `externalizeDepsPlugin()` | 将 `node_modules` 中的依赖转为 `require()` 引用,减小包体积 |
| `root: 'src/renderer'` | 告诉 Vite 渲染进程的 HTML 在哪个目录 |
| `plugins: [vue()]` | 在渲染进程启用 Vue 插件,处理 `.vue` 文件 |
| `resolve.alias` | 设置 `@` 指向 `src/renderer/src`,简化导入路径 |
| `build.rollupOptions.input` | 指定每个进程的入口文件 |
### 构建后输出
```
electron-vite build
┌──────────────────────────────────────────┐
│ out/main/index.js (主进程) │
│ out/preload/index.mjs (预加载) │
│ out/renderer/index.html (渲染进程) │
│ out/renderer/assets/* (JS/CSS) │
└──────────────────────────────────────────┘
```