渲染进程 (Renderer)
概述
渲染进程是 Electron 应用的前端部分,负责页面渲染和用户交互。本项目使用 Vue 3 + Vite + Ant Design Vue 技术栈构建现代化的渲染进程应用。
技术栈
核心框架
| 技术 | 版本 | 说明 |
|---|---|---|
| Vue | ^3.3.4 | 渐进式 JavaScript 框架 |
| Vue Router | ^4.2.4 | Vue 官方路由管理器 |
| Vite | ^4.4.9 | 下一代前端构建工具 |
| Ant Design Vue | ^4.0.0 | 企业级 UI 组件库 |
开发工具
| 工具 | 用途 |
|---|---|
| ESLint | 代码质量检查 |
| Less | CSS 预处理器 |
| unplugin-vue-components | 组件自动导入 |
| rollup-plugin-visualizer | 打包分析 |
特殊库
| 库 | 用途 |
|---|---|
| OGL | WebGL 图形库(用于特效) |
| @ant-design/icons-vue | Ant Design 图标库 |
项目结构
packages/renderer/
├── src/
│ ├── app/ # 应用配置
│ ├── assets/ # 静态资源
│ ├── components/ # Vue 组件
│ ├── pages/ # 页面组件
│ └── utils/ # 工具函数
├── index.html # 主应用入口
├── ball.html # 悬浮球页面
├── plugin.html # 插件管理页面
├── error.html # 错误页面
├── package.json # 依赖配置
└── vite.config.js # Vite 配置多页面配置
入口文件
项目支持多个独立的 HTML 页面:
javascript
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'), // 主应用
error: resolve(__dirname, 'error.html'), // 错误页面
plugin: resolve(__dirname, 'plugin.html'), // 插件管理
ball: resolve(__dirname, 'ball.html'), // 悬浮球
},
},
},
});页面说明
| 页面 | 文件 | 用途 | 尺寸 |
|---|---|---|---|
| 主应用 | index.html | 应用主界面 | 1200x800 |
| 错误页面 | error.html | 加载失败提示 | 800x600 |
| 插件管理 | plugin.html | 插件管理界面 | 1200x800 |
| 悬浮球 | ball.html | 桌面悬浮球 | 75x75 |
Vite 配置
基础配置
javascript
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({
plugins: [
vue(),
// 自动按需引入 Ant Design Vue 组件
Components({
resolvers: [
AntDesignVueResolver({
importStyle: false, // css in js
}),
],
}),
],
// 基础路径
base: './',
// 构建配置
build: {
outDir: '../../dist/renderer',
emptyOutDir: true,
chunkSizeWarningLimit: 1000,
reportCompressedSize: true,
cssCodeSplit: true,
},
});代码分割策略
javascript
build: {
rollupOptions: {
output: {
// 优化分包策略
manualChunks: {
'vue-core': ['vue', 'vue-router'],
'antd': ['ant-design-vue', '@ant-design/icons-vue'],
'ogl': ['ogl'],
'shared': ['./src/utils/index.js', './src/components/common/index.js'],
},
entryFileNames: 'assets/[name]-[hash].js',
chunkFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]',
},
},
}分包说明:
vue-core: Vue 核心库和路由antd: Ant Design Vue 组件库ogl: WebGL 图形库shared: 共享的工具和组件
打包分析
javascript
import { visualizer } from 'rollup-plugin-visualizer';
plugins: [
process.env.ANALYZE === 'true'
? visualizer({
open: true,
gzipSize: true,
brotliSize: true,
})
: undefined,
].filter(Boolean);使用方式:
bash
# 打包并分析
ANALYZE=true pnpm build:renderer开发环境
启动开发服务器
bash
# 进入渲染进程目录
cd packages/renderer
# 启动开发服务器
pnpm dev服务器默认运行在 http://localhost:3000
自定义启动提示
javascript
{
name: 'server-start',
configureServer(server) {
server.httpServer?.once('listening', () => {
printBuildInfo('\n 🚀 renderer渲染进程工程启动成功... \n');
});
},
}热更新 (HMR)
Vite 默认支持热模块替换 (HMR):
- ✅ Vue 组件热更新
- ✅ CSS 样式热更新
- ✅ 状态保持
- ✅ 快速刷新
生产构建
构建命令
bash
# 构建渲染进程
pnpm build:renderer
# 输出目录
# dist/renderer/构建完成提示
javascript
{
name: 'build-complete',
closeBundle() {
printBuildInfo('\n ✅ renderer渲染进程工程构建完成... \n');
},
}构建产物
dist/renderer/
├── assets/
│ ├── main-[hash].js # 主应用 JS
│ ├── vue-core-[hash].js # Vue 核心
│ ├── antd-[hash].js # Ant Design
│ ├── ogl-[hash].js # OGL 库
│ ├── shared-[hash].js # 共享代码
│ └── index-[hash].css # 样式文件
├── index.html # 主页面
├── error.html # 错误页面
├── plugin.html # 插件页面
└── ball.html # 悬浮球页面组件自动导入
配置
javascript
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
Components({
resolvers: [
AntDesignVueResolver({
importStyle: false, // css in js
}),
],
});使用
vue
<template>
<!-- 无需手动导入,直接使用 -->
<a-button type="primary">按钮</a-button>
<a-input v-model:value="text" />
<a-modal v-model:visible="visible" />
</template>
<script setup>
// 无需 import,自动按需导入
const text = ref('');
const visible = ref(false);
</script>自动导入的组件:
- 所有 Ant Design Vue 组件
- 项目中的自定义组件(可配置)
页面开发
主应用页面 (index.html)
html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>应用名称</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/app/main.js"></script>
</body>
</html>Vue 应用入口
javascript
// src/app/main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router);
app.mount('#app');插件管理页面 (plugin.html)
html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>插件管理</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/pages/plugin/main.js"></script>
</body>
</html>悬浮球页面 (ball.html)
html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>悬浮球</title>
<style>
body {
-webkit-app-region: drag;
user-select: none;
}
</style>
</head>
<body>
<div class="floating-ball">⚡</div>
<script>
// 悬浮球交互逻辑
window.ballAPI.onClick();
</script>
</body>
</html>样式方案
Less 预处理器
vue
<style lang="less" scoped>
.container {
padding: 20px;
.title {
font-size: 24px;
color: #333;
}
}
</style>CSS-in-JS
Ant Design Vue 默认使用 CSS-in-JS:
javascript
Components({
resolvers: [
AntDesignVueResolver({
importStyle: false, // 使用 CSS-in-JS
}),
],
});全局样式
javascript
// src/app/main.js
import './styles/global.css';
import './styles/reset.css';路由配置
路由定义
javascript
// src/app/router.js
import { createRouter, createWebHashHistory } from 'vue-router';
const routes = [
{
path: '/',
name: 'Home',
component: () => import('../pages/Home.vue'),
},
{
path: '/about',
name: 'About',
component: () => import('../pages/About.vue'),
},
];
const router = createRouter({
history: createWebHashHistory(), // 使用 hash 模式
routes,
});
export default router;注意: Electron 应用推荐使用 createWebHashHistory() 而非 createWebHistory()
路由守卫
javascript
router.beforeEach((to, from, next) => {
// 路由跳转前的逻辑
console.log('导航到:', to.path);
next();
});
router.afterEach((to, from) => {
// 路由跳转后的逻辑
document.title = to.meta.title || '应用名称';
});与主进程通信
Preload API
通过 preload 脚本暴露的 API 与主进程通信:
javascript
// 发送消息到主进程
window.electronAPI.send('channel-name', data);
// 调用主进程方法
const result = await window.electronAPI.invoke('method-name', params);
// 监听主进程消息
const unsubscribe = window.electronAPI.on('event-name', (data) => {
console.log('收到消息:', data);
});
// 取消监听
unsubscribe();Vue 组合式函数封装
javascript
// src/utils/useElectron.js
import { onUnmounted } from 'vue';
export function useElectron() {
// 发送消息
const send = (channel, data) => {
window.electronAPI?.send(channel, data);
};
// 调用方法
const invoke = async (channel, data) => {
return await window.electronAPI?.invoke(channel, data);
};
// 监听事件(自动清理)
const on = (channel, callback) => {
const unsubscribe = window.electronAPI?.on(channel, callback);
onUnmounted(() => {
unsubscribe?.();
});
};
return { send, invoke, on };
}使用示例:
vue
<script setup>
import { useElectron } from '@/utils/useElectron';
const { send, invoke, on } = useElectron();
// 获取机器信息
const getMachineInfo = async () => {
const info = await invoke('get-machine-info');
console.log(info);
};
// 监听更新状态
on('update-status', (status) => {
console.log('更新状态:', status);
});
</script>状态管理
Pinia (推荐)
bash
pnpm add piniajavascript
// src/stores/app.js
import { defineStore } from 'pinia';
export const useAppStore = defineStore('app', {
state: () => ({
user: null,
settings: {},
}),
actions: {
setUser(user) {
this.user = user;
},
},
});Vue 3 Composition API
javascript
// src/composables/useState.js
import { ref, readonly } from 'vue';
const state = ref({
count: 0,
user: null,
});
export function useState() {
const increment = () => {
state.value.count++;
};
return {
state: readonly(state),
increment,
};
}性能优化
1. 组件懒加载
javascript
// 路由懒加载
{
path: '/about',
component: () => import('./pages/About.vue'),
}
// 组件懒加载
const HeavyComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
);2. 虚拟列表
大列表使用虚拟滚动:
vue
<template>
<a-list :data-source="items" :virtual="true" :height="600">
<template #renderItem="{ item }">
<a-list-item>{{ item }}</a-list-item>
</template>
</a-list>
</template>3. 图片懒加载
vue
<template>
<img v-lazy="imageUrl" alt="图片" />
</template>4. 代码分割
javascript
// 动态导入
const module = await import('./heavy-module.js');
// 条件导入
if (condition) {
const { feature } = await import('./feature.js');
}调试技巧
Vue DevTools
bash
# 安装 Vue DevTools
pnpm add -D @vue/devtoolsConsole 日志
javascript
// 开发环境日志
if (import.meta.env.DEV) {
console.log('开发环境信息');
}
// 生产环境禁用日志
if (import.meta.env.PROD) {
console.log = () => {};
}性能监控
javascript
// 组件渲染性能
import { onMounted, onUpdated } from 'vue';
onMounted(() => {
console.time('component-mount');
});
onUpdated(() => {
console.timeEnd('component-mount');
});环境变量
.env 文件
bash
# .env.development
VITE_API_URL=http://localhost:8080
VITE_APP_TITLE=开发环境
# .env.production
VITE_API_URL=https://api.production.com
VITE_APP_TITLE=生产环境使用环境变量
javascript
// 在代码中使用
const apiUrl = import.meta.env.VITE_API_URL;
const appTitle = import.meta.env.VITE_APP_TITLE;
// 判断环境
if (import.meta.env.DEV) {
// 开发环境
}
if (import.meta.env.PROD) {
// 生产环境
}最佳实践
1. 组件化开发
components/
├── common/ # 通用组件
│ ├── Button.vue
│ └── Input.vue
├── business/ # 业务组件
│ ├── UserCard.vue
│ └── OrderList.vue
└── layout/ # 布局组件
├── Header.vue
└── Sidebar.vue2. 组合式函数
javascript
// useCounter.js
export function useCounter() {
const count = ref(0);
const increment = () => count.value++;
return { count, increment };
}
// 使用
const { count, increment } = useCounter();3. TypeScript 支持
bash
pnpm add -D typescript @vue/tsconfigvue
<script setup lang="ts">
interface User {
id: number;
name: string;
}
const user = ref<User | null>(null);
</script>4. 错误边界
vue
<template>
<error-boundary>
<router-view />
</error-boundary>
</template>
<script setup>
import { onErrorCaptured } from 'vue';
onErrorCaptured((err) => {
console.error('捕获错误:', err);
return false; // 阻止错误继续传播
});
</script>常见问题
Q1: 开发环境无法访问 Electron API?
确保开发服务器地址与主进程加载的 URL 一致:
ini
# config.ini
loadMode="remote"
electronUrl="http://localhost:3000"Q2: 生产环境白屏?
检查构建输出路径和主进程加载路径:
javascript
// 主进程
const url = path.join(process.resourcesPath, 'renderer', 'index.html');
win.loadFile(url);Q3: 路由刷新 404?
使用 Hash 模式而非 History 模式:
javascript
const router = createRouter({
history: createWebHashHistory(), // ✅ 使用 hash 模式
routes,
});Q4: 样式不生效?
检查 scoped 作用域和深度选择器:
vue
<style scoped>
/* 普通样式 */
.container {
}
/* 深度选择器 */
:deep(.ant-btn) {
}
</style>相关文件
packages/renderer/vite.config.js- Vite 配置packages/renderer/package.json- 依赖配置packages/renderer/src/- 源代码目录packages/renderer/*.html- 页面入口文件