vite.config.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import { defineConfig } from 'vite';
  2. import vue from '@vitejs/plugin-vue';
  3. import electron from 'vite-plugin-electron';
  4. import renderer from 'vite-plugin-electron-renderer';
  5. import AutoImport from 'unplugin-auto-import/vite';
  6. import Components from 'unplugin-vue-components/vite';
  7. import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
  8. import { resolve } from 'path';
  9. import { existsSync, readFileSync } from 'fs';
  10. function readServerEnv() {
  11. const envPath = resolve(__dirname, '../server/.env');
  12. const env: Record<string, string> = {};
  13. if (!existsSync(envPath)) return env;
  14. const lines = readFileSync(envPath, 'utf8').split(/\r?\n/);
  15. for (const line of lines) {
  16. const trimmed = line.trim();
  17. if (!trimmed || trimmed.startsWith('#')) continue;
  18. const index = trimmed.indexOf('=');
  19. if (index <= 0) continue;
  20. const key = trimmed.slice(0, index).trim();
  21. const value = trimmed.slice(index + 1).trim().replace(/^['"]|['"]$/g, '');
  22. env[key] = value;
  23. }
  24. return env;
  25. }
  26. function getDevApiTarget() {
  27. const serverEnv = readServerEnv();
  28. const rawHost = process.env.VITE_DEV_API_HOST || serverEnv.HOST || '127.0.0.1';
  29. const host = rawHost === '0.0.0.0' || rawHost === '::' ? '127.0.0.1' : rawHost;
  30. const port = process.env.VITE_DEV_API_PORT || serverEnv.PORT || '3000';
  31. return `http://${host}:${port}`;
  32. }
  33. export default defineConfig(({ command }) => {
  34. const isServe = command === 'serve';
  35. const isBuild = command === 'build';
  36. const devApiTarget = getDevApiTarget();
  37. return {
  38. // Electron 打包后从 file:// 加载,需使用相对路径
  39. base: isBuild ? './' : '/',
  40. resolve: {
  41. alias: {
  42. '@': resolve(__dirname, 'src'),
  43. },
  44. },
  45. plugins: [
  46. vue({
  47. template: {
  48. compilerOptions: {
  49. // 将 webview 标记为自定义元素(Electron 特有标签)
  50. isCustomElement: (tag) => tag === 'webview',
  51. },
  52. },
  53. }),
  54. AutoImport({
  55. imports: ['vue', 'vue-router', 'pinia'],
  56. resolvers: [ElementPlusResolver()],
  57. dts: 'src/auto-imports.d.ts',
  58. }),
  59. Components({
  60. resolvers: [
  61. ElementPlusResolver(),
  62. // 自动解析 Element Plus 图标组件
  63. {
  64. type: 'component',
  65. resolve: (name: string) => {
  66. // Element Plus 图标组件通常是 PascalCase 且不以 El 开头
  67. const iconNames = [
  68. 'Fold', 'Expand', 'Loading', 'DocumentCopy', 'Message', 'Close',
  69. 'Refresh', 'Delete', 'Right', 'FolderDelete', 'DataAnalysis',
  70. 'User', 'Film', 'Upload', 'Clock', 'Setting', 'Document',
  71. 'Monitor', 'TrendCharts', 'ChatDotRound', 'ArrowDown', 'ArrowLeft',
  72. 'ArrowRight', 'VideoPlay', 'CircleCheck', 'CircleClose', 'Lock',
  73. 'Picture', 'Plus', 'Search', 'Edit', 'Download', 'MoreFilled',
  74. 'MagicStick',
  75. ];
  76. if (iconNames.includes(name)) {
  77. return { name, from: '@element-plus/icons-vue' };
  78. }
  79. },
  80. },
  81. ],
  82. dts: 'src/components.d.ts',
  83. }),
  84. electron([
  85. {
  86. entry: 'electron/main.ts',
  87. onstart(options) {
  88. if (process.env.VSCODE_DEBUG) {
  89. console.log('[startup] Electron App');
  90. } else {
  91. options.startup();
  92. }
  93. },
  94. vite: {
  95. build: {
  96. sourcemap: isServe,
  97. minify: isBuild,
  98. outDir: 'dist-electron',
  99. rollupOptions: {
  100. external: ['electron'],
  101. output: {
  102. format: 'cjs',
  103. },
  104. },
  105. },
  106. },
  107. },
  108. {
  109. entry: 'electron/preload.ts',
  110. onstart(options) {
  111. options.reload();
  112. },
  113. vite: {
  114. build: {
  115. sourcemap: isServe ? 'inline' : undefined,
  116. minify: isBuild,
  117. outDir: 'dist-electron',
  118. rollupOptions: {
  119. external: ['electron'],
  120. output: {
  121. format: 'cjs',
  122. },
  123. },
  124. },
  125. },
  126. },
  127. ]),
  128. renderer(),
  129. ],
  130. css: {
  131. preprocessorOptions: {
  132. scss: {
  133. additionalData: `@use "@/styles/variables.scss" as *;`,
  134. },
  135. },
  136. },
  137. server: {
  138. host: '127.0.0.1',
  139. port: 15173,
  140. strictPort: true,
  141. proxy: {
  142. '/api': {
  143. target: devApiTarget,
  144. changeOrigin: true,
  145. },
  146. '/ws': {
  147. target: devApiTarget.replace(/^http/, 'ws'),
  148. ws: true,
  149. },
  150. },
  151. },
  152. build: {
  153. outDir: 'dist',
  154. emptyOutDir: true,
  155. rollupOptions: {
  156. output: {
  157. // manualChunks 在 Electron file:// 协议下会导致跨 chunk 的 ES module
  158. // 静态 import 失败,引发白屏。如需分包优化,改用自定义协议加载。
  159. },
  160. },
  161. },
  162. };
  163. });