Skip to content

渲染进程 (Renderer)

概述

渲染进程是 Electron 应用的前端部分,负责页面渲染和用户交互。本项目使用 Vue 3 + Vite + Ant Design Vue 技术栈构建现代化的渲染进程应用。

技术栈

核心框架

技术版本说明
Vue^3.3.4渐进式 JavaScript 框架
Vue Router^4.2.4Vue 官方路由管理器
Vite^4.4.9下一代前端构建工具
Ant Design Vue^4.0.0企业级 UI 组件库

开发工具

工具用途
ESLint代码质量检查
LessCSS 预处理器
unplugin-vue-components组件自动导入
rollup-plugin-visualizer打包分析

特殊库

用途
OGLWebGL 图形库(用于特效)
@ant-design/icons-vueAnt 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 pinia
javascript
// 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/devtools

Console 日志

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.vue

2. 组合式函数

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/tsconfig
vue
<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 - 页面入口文件

相关文档

基于 MIT 许可发布