346 lines
13 KiB
Markdown
346 lines
13 KiB
Markdown
# 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.json(Node.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) │
|
||
└──────────────────────────────────────────┘
|
||
``` |