electron-vite 完整示例教程
目标:从零开始,一步步构建一个可运行的 Electron + electron-vite + Vue + TypeScript 桌面应用,并打包成 macOS 应用。每一步都有预期结果,当前机器(Node 24 / npm 11 / macOS 15)已实测通过。
工作原理见 README 与 electron-vite,上下文隔离/IPC 见 03-ipc。
先决条件
- Node.js ≥ 18
- npm / pnpm / yarn 任一
- 常见国内环境建议先配置镜像(见 07-environment-variables),避免 postinstall 卡住
Step 0:脚手架
命令
npm create @quick-start/electron@latest electron-vite-demo -- --template vue-ts交互选项取默认值即可:是否加 Electron updater 插件选 No,是否安装依赖选 Yes,是否初始化 git 视情况。完成后进入项目:
cd electron-vite-demo
npm install # 若上一步跳过了安装;postinstall 会自动重建原生模块预期结果
Scaffolding project in .../electron-vite-demo...
Done. Now run: cd electron-vite-demo && npm install && npm run dev
生成结构:
src/
├── main/index.ts # 主进程入口(Electron + electron-vite)
├── preload/index.ts # 预加载脚本(contextBridge)
└── renderer/
├── index.html # 渲染进程入口
└── src/App.vue # Vue 根组件
electron.vite.config.ts # 三段式配置:main / preload / renderer
electron-builder.yml # 打包配置
模板依赖约 electron@39、electron-vite@5、vite@7、vue@3 + TS,版本以脚手架实际生成为准。
Step 1:项目结构理解
electron.vite.config.ts:
import { resolve } from 'path'
import { defineConfig } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
main: {}, // 主进程:Node 环境,产物 out/main/index.js
preload: {}, // 预加载:受限环境,产物 out/preload/index.js
renderer: { // 渲染进程:浏览器环境,标准 Vite + HMR
resolve: { alias: { '@renderer': resolve('src/renderer/src') } },
plugins: [vue()]
}
})Step 2:运行开发模式
命令
npm run dev预期结果
控制台依次出现「main 构建成功 → preload 构建成功 → dev server running at localhost:5173 → starting electron app」,并弹出 Electron 窗口。此时:
- 渲染进程有热更新:改
App.vue模板保存,窗口不刷新即更新 - 主进程/preload 改动会自动重启应用
HMR 的关键在主进程模板里:开发模式加载 process.env.ELECTRON_RENDERER_URL,生产模式加载 ../renderer/index.html 文件。
Step 3:加一个真实跨进程功能
目标:页面显示系统信息,数据经 渲染进程 → preload → 主进程 的 IPC invoke 返回。
3.1 主进程(src/main/index.ts)导入 node:os,在 app.whenReady() 里注册:
import { cpus, release, totalmem } from 'node:os'
import { ipcMain } from 'electron'
app.whenReady().then(() => {
ipcMain.handle('system:info', () => ({
platform: process.platform,
release: release(),
cores: cpus().length,
totalMemory: totalmem()
}))
})3.2 preload(src/preload/index.ts)暴露 API:
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
const api = {
getSystemInfo: () => ipcRenderer.invoke('system:info')
}
if (process.contextIsolated) {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} else {
// @ts-ignore (define in dts)
window.electron = electronAPI
// @ts-ignore (define in dts)
window.api = api
}3.3 渲染进程(src/renderer/src/App.vue):
<script setup lang="ts">
import { onMounted, ref } from 'vue'
interface SystemInfo {
platform: string
release: string
cores: number
totalMemory: number
}
const info = ref<SystemInfo | null>(null)
onMounted(async () => {
info.value = await window.api.getSystemInfo()
})
const formatMemory = (bytes: number) => `${(bytes / 1024 ** 3).toFixed(1)} GB`
</script>
<template>
<main class="panel">
<h1>系统信息</h1>
<section v-if="info" class="grid">
<div><span>平台</span><strong>{{ info.platform }}</strong></div>
<div><span>系统版本</span><strong>{{ info.release }}</strong></div>
<div><span>CPU 核心</span><strong>{{ info.cores }}</strong></div>
<div><span>内存</span><strong>{{ formatMemory(info.totalMemory) }}</strong></div>
</section>
<p v-else>读取中…</p>
</main>
</template>Step 4:TypeScript 类型检查
3.1 中 window.api 是 unknown,无法直接调用 getSystemInfo()。 在 src/preload/index.d.ts 补充类型:
import { ElectronAPI } from '@electron-toolkit/preload'
interface SystemInfo {
platform: string
release: string
cores: number
totalMemory: number
}
declare global {
interface Window {
electron: ElectronAPI
api: {
getSystemInfo: () => Promise<SystemInfo>
}
}
}命令
npm run typecheck # 分 node 和 web 两条,tsc + vue-tsc预期结果
无输出退出码 0。若不补 d.ts,Step 3 的 window.api.getSystemInfo 会被 vue-tsc 判错。
Step 5:生产构建
命令
npm run build # 实际是 typecheck && electron-vite build预期结果
产物集中在 out/:
out/
├── main/index.js # 主进程单文件
├── preload/index.js # 预加载单文件
└── renderer/ # 渲染进程:index.html + 压缩过的 assets
三步都显示 ✓ built,渲染层压缩后的 JS/CSS 在 out/renderer/assets/。
Step 6:electron-builder 打包
先验证整个应用能用生产产物运行(不打包):
npm run start # electron-vite preview,加载 out/ 里的产物然后产出未压缩应用目录(macOS 得到 .app,比 DMG 快,无签名也能跑):
npx electron-builder --dir预期产物:
dist/mac/evite-demo.app
- 首次会额外下载 Electron 发行版,命中 07-environment-variables 的 electron-builder 缓存后再次打包很快
- 未签名会有
skipped macOS application code signing提示,本地调试可忽略
也可以跑完整安装包(macOS 生成 .dmg):
npm run build:mac打包脚本定义在
package.json,build:mac/build:win/build:linux分别对应;出 DMG 比--dir慢。
关键坑回顾
| 坑 | 解决 |
|---|---|
渲染进程 window.api 是 unknown | 在 src/preload/index.d.ts 补全局类型(Step 4) |
| 开发模式正常、打包后窗口空白 | 检查主进程是否按 ELECTRON_RENDERER_URL 分流加载 URL / 本地文件 |
require('electron-updater') 报 CJS 错误 | 模板生产依赖按需升级 ESM 用法,快速升级请查阅该模块 README |
| 打包下载慢/失败 | 配置 ELECTRON_BUILDER_BINARIES_MIRROR + ELECTRON_MIRROR,见 07-environment-variables |
| 打包产物不可运行 | 先 npm run start 验证生产产物,再排查签名/资源路径 |
| 打包配置不生效 | 独立配置文件名前缀固定为 electron-builder,优先级 .yml > .ts;electron-builder.config.ts 不是自动发现名(详见 electron-builder) |
下一步
- 03-ipc — 了解更多安全 IPC 模式
- 06-security-performance — 加上安全清单与发布前检查
- electron-builder — 配置应用 ID、图标、自动更新
- README — 工具链全景