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

13 KiB
Raw Blame History

Electron + Vue3 + TypeScript 项目

基于 electron-vite 构建的现代化 Electron 应用模板。

技术栈

领域 技术
构建工具 electron-vite
前端框架 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)

// main.js - 窗口配置
webPreferences: {
    contextIsolation: true,   // 启用上下文隔离
    nodeIntegration: false,    // 禁用 Node.js 访问
    preload: 'preload.js'     // 加载预加载脚本
}
  • contextIsolation: true 防止渲染进程直接访问 Node.js,防止 XSS 攻击
  • 渲染进程只能通过 window.electronwindow.api 访问预暴露的 API

3. IPC 通信模式

Renderer                         Main
   │                               │
   │  ipcRenderer.invoke('xxx')    │
   │ ─────────────────────────────►│
   │                               │  ipcMain.handle('xxx', ...)
   │         Promise<Result>       │
   │ ◄─────────────────────────────│
   ▼                               ▼
// 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 组件生命周期

<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 - 组件卸载时清理定时器,防止内存泄漏

命令

# 开发模式(热重载)
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(根配置/入口)

{
  "references": [
    { "path": "./tsconfig.node.json" },
    { "path": "./tsconfig.web.json" }
  ]
}

作用:项目总入口,本身不编译代码,通过 references 引用其他两个配置。

tsconfig.node.jsonNode.js 环境)

对应src/main/ + src/preload/ + electron.vite.config.ts

{
  "lib": ["ES2022"],
  "include": ["src/main/**/*", "src/preload/**/*", "electron.vite/**/*"]
}

作用:为主进程和预加载脚本提供类型检查,这两个运行在 Node.js 环境,需要 Node.js 内置类型(如 processpathfs)。

tsconfig.web.json(浏览器环境)

对应src/renderer/

{
  "lib": ["ES2022", "DOM", "DOM.Iterable"],
  "jsx": "preserve",
  "jsxImportSource": "vue",
  "include": ["src/renderer/**/*"]
}

作用:为渲染进程(Vue 应用)提供类型检查,需要 DOM 类型(如 windowdocumentHTMLElement)和 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,但需要为三个进程分别配置:

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)        │
└──────────────────────────────────────────┘