vite.config.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import vue from '@vitejs/plugin-vue';
  2. import { resolve } from 'path';
  3. import { defineConfig, loadEnv, ConfigEnv } from 'vite';
  4. import vueSetupExtend from 'vite-plugin-vue-setup-extend';
  5. import compression from 'vite-plugin-compression2';
  6. import { buildConfig } from './src/utils/build';
  7. import vueJsx from '@vitejs/plugin-vue-jsx';
  8. import { CodeInspectorPlugin } from 'code-inspector-plugin';
  9. import fs from 'fs';
  10. import { visualizer } from 'rollup-plugin-visualizer';
  11. import { webUpdateNotice } from '@plugin-web-update-notification/vite';
  12. import vitePluginsAutoI18n, { EmptyTranslator } from 'vite-auto-i18n-plugin';
  13. const pathResolve = (dir: string) => {
  14. return resolve(__dirname, '.', dir);
  15. };
  16. const alias: Record<string, string> = {
  17. '/@': pathResolve('./src/'),
  18. 'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js',
  19. 'ezuikit-js': pathResolve('node_modules/ezuikit-js/ezuikit.js'),
  20. };
  21. const viteConfig = defineConfig((mode: ConfigEnv) => {
  22. const env = loadEnv(mode.mode, process.cwd());
  23. /** dev 代理目标:始终本机后端。勿用浏览器用的公网 VITE_API_URL,避免绕一圈且易配错 */
  24. const apiProxyTarget = env.VITE_PROXY_TARGET || 'http://127.0.0.1:5005';
  25. const devPort = Number(env.VITE_PORT) || 8888;
  26. /** 浏览器访问用公网 IP/域名;勿写入 server.hmr.host,否则 WS 会 bind 该地址,云主机 EIP 常不在网卡上 → EADDRNOTAVAIL */
  27. const devPublicHost = String(env.VITE_DEV_PUBLIC_HOST || '').trim();
  28. const runtimeConfigJs = `window.__env__ = ${JSON.stringify(env, null, 2)} `;
  29. // dev(serve)才写 public/config.js,避免 build 把生产 env 写回本地开发态文件(如 VITE_API_URL=/prod-api 会让 dev 端口 404)。
  30. // build 产物中的 dist/config.js 由下面的 emit-runtime-config-js 插件在打包完成后直接写入 dist/,不经过 public/。
  31. if (mode.command === 'serve') {
  32. fs.writeFileSync('./public/config.js', runtimeConfigJs);
  33. }
  34. return {
  35. plugins: [
  36. visualizer({ open: false }),
  37. CodeInspectorPlugin({
  38. bundler: 'vite',
  39. hotKeys: ['shiftKey'],
  40. }),
  41. vue(),
  42. vueJsx(),
  43. webUpdateNotice({
  44. versionType: 'build_timestamp',
  45. notificationConfig: {
  46. placement: 'topLeft',
  47. },
  48. notificationProps: {
  49. title: '📢 系统更新',
  50. description: '系统更新啦,请刷新页面!',
  51. buttonText: '刷新',
  52. dismissButtonText: '忽略',
  53. },
  54. }),
  55. vueSetupExtend(),
  56. ...(mode.command === 'build'
  57. ? [
  58. compression({
  59. deleteOriginalAssets: false,
  60. threshold: 5120,
  61. skipIfLargerOrEqual: true,
  62. }),
  63. {
  64. name: 'emit-runtime-config-js',
  65. apply: 'build' as const,
  66. writeBundle(options: { dir?: string }) {
  67. const outDir = options.dir || resolve(__dirname, 'dist');
  68. if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
  69. fs.writeFileSync(resolve(outDir, 'config.js'), runtimeConfigJs);
  70. },
  71. },
  72. ]
  73. : []),
  74. JSON.parse(env.VITE_OPEN_CDN) ? buildConfig.cdn() : null,
  75. vitePluginsAutoI18n({
  76. enabled: false,
  77. originLang: 'zh-cn',
  78. targetLangList: ['zh-hk', 'zh-tw', 'en', 'it'],
  79. translator: new EmptyTranslator(),
  80. }),
  81. ],
  82. root: process.cwd(),
  83. resolve: { alias },
  84. base: mode.command === 'serve' ? './' : env.VITE_PUBLIC_PATH,
  85. optimizeDeps: { exclude: ['vue-demi'] },
  86. server: {
  87. host: '0.0.0.0',
  88. port: env.VITE_PORT as unknown as number,
  89. open: JSON.parse(env.VITE_OPEN),
  90. ...(mode.command === 'serve' && devPublicHost
  91. ? { origin: `http://${devPublicHost}:${devPort}` }
  92. : {}),
  93. // VITE_HMR=false 可关热更新。公网访问时不要给 hmr.host 填 EIP(见 devPublicHost 注释)
  94. // 勿设 hmr.port === server.port:Vite 8 会先把 WS 占住该端口,HTTP 再监听会冲突并退到 8889,浏览器仍访问 8888 会 426
  95. hmr: env.VITE_HMR === 'false' ? false : true,
  96. // 文件监视过多时 inotify 会 ENOSPC;设 VITE_WATCH_POLLING=true 可改用轮询(略耗 CPU)
  97. watch:
  98. env.VITE_WATCH_POLLING === 'true'
  99. ? { usePolling: true, interval: 1500 }
  100. : undefined,
  101. proxy: {
  102. // 勿对 /api 开 ws:true,在部分环境下会导致普通 GET 挂起无响应
  103. '/api': {
  104. target: apiProxyTarget,
  105. changeOrigin: true,
  106. },
  107. '/upload': {
  108. target: apiProxyTarget,
  109. changeOrigin: true,
  110. },
  111. '/Upload': {
  112. target: apiProxyTarget,
  113. changeOrigin: true,
  114. },
  115. '/hubs': {
  116. target: apiProxyTarget,
  117. changeOrigin: true,
  118. ws: true,
  119. },
  120. '/schedule': {
  121. target: apiProxyTarget,
  122. changeOrigin: true,
  123. },
  124. },
  125. },
  126. // 生产包用 vite preview 时,浏览器仍请求 VITE_API_URL=/prod-api;无 Nginx 时需在此反代到后端(与线上 nginx 行为一致)
  127. preview: {
  128. host: '0.0.0.0',
  129. port: devPort,
  130. proxy: {
  131. '/prod-api': {
  132. target: apiProxyTarget,
  133. changeOrigin: true,
  134. rewrite: (path) => path.replace(/^\/prod-api/, ''),
  135. },
  136. '/api': {
  137. target: apiProxyTarget,
  138. changeOrigin: true,
  139. },
  140. '/upload': {
  141. target: apiProxyTarget,
  142. changeOrigin: true,
  143. },
  144. '/Upload': {
  145. target: apiProxyTarget,
  146. changeOrigin: true,
  147. },
  148. '/hubs': {
  149. target: apiProxyTarget,
  150. changeOrigin: true,
  151. ws: true,
  152. },
  153. '/schedule': {
  154. target: apiProxyTarget,
  155. changeOrigin: true,
  156. },
  157. },
  158. },
  159. build: {
  160. outDir: 'dist',
  161. chunkSizeWarningLimit: 1500,
  162. assetsInlineLimit: 5000, // 小于此阈值的导入或引用资源将内联为 base64 编码
  163. sourcemap: false, // 构建后是否生成 source map 文件
  164. extractComments: false, // 移除注释
  165. // esbuild 压缩内存占用远低于 terser;maxParallelFileOps 降低并发,便于小内存环境完成构建
  166. minify: 'esbuild',
  167. esbuild: {
  168. drop: mode.mode === 'production' ? ['console', 'debugger'] : [],
  169. },
  170. rollupOptions: {
  171. output: {
  172. chunkFileNames: 'assets/js/[name]-[hash].js', // 引入文件名的名称
  173. entryFileNames: 'assets/js/[name]-[hash].js', // 包的入口文件名称
  174. assetFileNames: 'assets/[ext]/[name]-[hash].[ext]', // 资源文件像 字体,图片等
  175. // 小内存环境构建时可去掉 manualChunks,降低 Rollup 分析内存(大机器可恢复分包策略)
  176. ...(process.env.VITE_LOW_MEM_BUILD === '1'
  177. ? {}
  178. : {
  179. manualChunks(id: string) {
  180. if (id.includes('node_modules')) {
  181. return id.toString().match(/\/node_modules\/(?!.pnpm)(?<moduleName>[^\/]*)\//)?.groups!.moduleName ?? 'vender';
  182. }
  183. },
  184. }),
  185. },
  186. ...(JSON.parse(env.VITE_OPEN_CDN) ? { external: buildConfig.external } : {}),
  187. },
  188. },
  189. css: { preprocessorOptions: { css: { charset: false }, scss: { silenceDeprecations: ['legacy-js-api', 'global-builtin', 'fs-importer-cwd', 'import'] } } },
  190. define: {
  191. __VUE_I18N_LEGACY_API__: JSON.stringify(false),
  192. __VUE_I18N_FULL_INSTALL__: JSON.stringify(false),
  193. __INTLIFY_PROD_DEVTOOLS__: JSON.stringify(false),
  194. __NEXT_VERSION__: JSON.stringify(process.env.npm_package_version),
  195. __NEXT_NAME__: JSON.stringify(process.env.npm_package_name),
  196. },
  197. };
  198. });
  199. export default viteConfig;