vite.config.ts 7.3 KB

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