From 9d93a373ae0bca6b8701fffa348d858d531290f2 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sat, 24 Oct 2026 15:30:38 +0800 Subject: [PATCH] =?UTF-8?q?perf(web):=20=E9=99=8D=E4=BD=8E=E7=94=9F?= =?UTF-8?q?=E4=BA=A7=E6=9E=84=E5=BB=BA=E5=86=85=E5=AD=98=E5=B3=B0=E5=80=BC?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E5=90=AF=E7=94=A8=20CI=20=E4=BD=8E=E5=86=85?= =?UTF-8?q?=E5=AD=98=E6=89=93=E5=8C=85=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 关闭按需 Element Plus 样式与构建期 dts、限制 rollup 并行、CI 跳过 gzip; 新增 npm run build:ci,供 Drone 在小内存机器上打包。 Co-authored-by: Cursor --- .drone.yml | 9 ++++++--- web/package.json | 5 +++-- web/vite.config.ts | 25 ++++++++++++++++++++++--- web/vite/plugins/auto-import.ts | 9 +++++---- web/vite/plugins/components.ts | 8 +++++--- web/vite/plugins/compression.ts | 12 ++++++++---- web/vite/plugins/icons.ts | 6 +++--- web/vite/plugins/index.ts | 8 +++----- 8 files changed, 55 insertions(+), 27 deletions(-) diff --git a/.drone.yml b/.drone.yml index 63625816..abc8cf3b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -37,17 +37,20 @@ steps: - name: package-web # 无 package-lock(被 gitignore);Node20 需 --legacy-peer-deps 规避 npm edgesOut + # 低内存模式:串行 transform、关 gzip 插件、3G 堆(见 web build:ci) image: node:20-bookworm environment: NPM_CONFIG_REGISTRY: https://registry.npmmirror.com + CI: 'true' + VITE_LOW_MEM: '1' + VITE_BUILD_COMPRESS: '' commands: - set -eu - - echo "==> web tag=${DRONE_TAG} npm=$(npm -v) node=$(node -v)" + - echo "==> web tag=${DRONE_TAG} npm=$(npm -v) node=$(node -v) low_mem=${VITE_LOW_MEM}" - mkdir -p "/hzdyjs-releases/${DRONE_TAG}" - cd web - npm install --legacy-peer-deps --no-audit --no-fund - # 本前端生产构建实测需约 6G+ 堆;测试机靠扩容 swap(/swap-hzdyjs)硬扛 - - node --max-old-space-size=6144 node_modules/vite/bin/vite.js build --mode production + - npm run build:ci - test -f dist/index.html - tar -C dist -czf "/hzdyjs-releases/${DRONE_TAG}/web-${DRONE_TAG}.tar.gz" . - ls -lh "/hzdyjs-releases/${DRONE_TAG}/web-${DRONE_TAG}.tar.gz" diff --git a/web/package.json b/web/package.json index b636f0ca..01b46df7 100644 --- a/web/package.json +++ b/web/package.json @@ -8,8 +8,9 @@ "type": "module", "scripts": { "dev": "vite serve --mode development", - "build:prod": "node --max-old-space-size=8192 node_modules/vite/bin/vite.js build --mode production", - "build:dev": "node --max-old-space-size=8192 node_modules/vite/bin/vite.js build --mode development", + "build:prod": "node --max-old-space-size=6144 node_modules/vite/bin/vite.js build --mode production", + "build:ci": "CI=true VITE_LOW_MEM=1 VITE_BUILD_COMPRESS= node --max-old-space-size=3072 node_modules/vite/bin/vite.js build --mode production", + "build:dev": "node --max-old-space-size=6144 node_modules/vite/bin/vite.js build --mode development", "preview": "vite preview", "lint:eslint": "eslint", "lint:eslint:fix": "eslint --fix", diff --git a/web/vite.config.ts b/web/vite.config.ts index 19a69908..e57373d4 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -5,6 +5,9 @@ import path from 'path'; export default defineConfig(({ mode, command }) => { const env = loadEnv(mode, process.cwd()); + const isBuild = command === 'build'; + // CI / 小内存机器:限制并行、跳过 gzip 插件(产物外层已 tar.gz) + const lowMem = process.env.CI === 'true' || process.env.VITE_LOW_MEM === '1'; return { // 部署生产环境和开发环境下的URL。 // 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上 @@ -17,7 +20,7 @@ export default defineConfig(({ mode, command }) => { extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'] }, // https://cn.vitejs.dev/config/#resolve-extensions - plugins: createPlugins(env, command === 'build'), + plugins: createPlugins(env, isBuild), server: { host: '0.0.0.0', port: Number(env.VITE_APP_PORT), @@ -54,6 +57,23 @@ export default defineConfig(({ mode, command }) => { ] } }, + esbuild: { + legalComments: 'none' + }, + build: { + sourcemap: false, + // 控制台 gzip 体积统计会再压缩一遍全部产物,大项目很吃内存 + reportCompressedSize: false, + chunkSizeWarningLimit: 2000, + // esbuild 比 terser 省内存、更快 + minify: 'esbuild', + cssMinify: 'esbuild', + target: 'es2020', + // 小内存 / CI:串行读文件,显著降低 transform 峰值 + rollupOptions: { + maxParallelFileOps: lowMem ? 1 : 2 + } + }, // 预编译 optimizeDeps: { include: [ @@ -65,8 +85,7 @@ export default defineConfig(({ mode, command }) => { 'echarts', 'vue-i18n', '@vueup/vue-quill', - 'image-conversion', - 'element-plus/es/components/**/css' + 'image-conversion' ] } }; diff --git a/web/vite/plugins/auto-import.ts b/web/vite/plugins/auto-import.ts index a271829b..49d4ae79 100644 --- a/web/vite/plugins/auto-import.ts +++ b/web/vite/plugins/auto-import.ts @@ -1,20 +1,21 @@ import AutoImport from 'unplugin-auto-import/vite'; import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; -export default (path: any) => { +export default (path: any, isBuild = false) => { return AutoImport({ // 自动导入 Vue 相关函数 imports: ['vue', 'vue-router', '@vueuse/core', 'pinia'], eslintrc: { - enabled: true, + // 生产构建不重写 eslint 全局声明,避免每次打包全量扫描 + enabled: !isBuild, filepath: './.eslintrc-auto-import.json', globalsPropValue: true }, resolvers: [ // 自动导入 Element Plus 相关函数ElMessage, ElMessageBox... (带样式) - ElementPlusResolver() + ElementPlusResolver({ importStyle: false }) ], vueTemplate: true, // 是否在 vue 模板中自动导入 - dts: path.resolve(path.resolve(__dirname, '../../src'), 'types', 'auto-imports.d.ts') + dts: isBuild ? false : path.resolve(path.resolve(__dirname, '../../src'), 'types', 'auto-imports.d.ts') }); }; diff --git a/web/vite/plugins/components.ts b/web/vite/plugins/components.ts index 336f5cf1..954330c6 100644 --- a/web/vite/plugins/components.ts +++ b/web/vite/plugins/components.ts @@ -2,16 +2,18 @@ import Components from 'unplugin-vue-components/vite'; import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; import IconsResolver from 'unplugin-icons/resolver'; -export default (path: any) => { +export default (path: any, isBuild = false) => { return Components({ resolvers: [ // 自动导入 Element Plus 组件 - ElementPlusResolver(), + // 全局已引入 element-plus/dist/index.css,关闭按需样式,减少打包模块数 + ElementPlusResolver({ importStyle: false }), // 自动注册图标组件 IconsResolver({ enabledCollections: ['ep'] }) ], - dts: path.resolve(path.resolve(__dirname, '../../src'), 'types', 'components.d.ts') + // 生产构建不重写声明文件,避免把全部组件信息留在内存里 + dts: isBuild ? false : path.resolve(path.resolve(__dirname, '../../src'), 'types', 'components.d.ts') }); }; diff --git a/web/vite/plugins/compression.ts b/web/vite/plugins/compression.ts index aa8c7796..3937ce97 100644 --- a/web/vite/plugins/compression.ts +++ b/web/vite/plugins/compression.ts @@ -1,16 +1,20 @@ import compression from 'vite-plugin-compression'; export default (env: any) => { - const { VITE_BUILD_COMPRESS } = env; + // CI 可通过空值或空格关闭:VITE_BUILD_COMPRESS= + const raw = String(env?.VITE_BUILD_COMPRESS ?? '').trim(); const plugin: any[] = []; - if (VITE_BUILD_COMPRESS) { - const compressList = VITE_BUILD_COMPRESS.split(','); + if (raw) { + const compressList = raw.split(',').map((s: string) => s.trim()).filter(Boolean); if (compressList.includes('gzip')) { // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 plugin.push( compression({ ext: '.gz', - deleteOriginFile: false + deleteOriginFile: false, + // 小于 10KB 的分包压缩收益很小,跳过可明显缩短收尾时间 + threshold: 10 * 1024, + verbose: false }) ); } diff --git a/web/vite/plugins/icons.ts b/web/vite/plugins/icons.ts index 883f2307..5971cf39 100644 --- a/web/vite/plugins/icons.ts +++ b/web/vite/plugins/icons.ts @@ -1,8 +1,8 @@ import Icons from 'unplugin-icons/vite'; -export default () => { +export default (isBuild = false) => { return Icons({ - // 自动安装图标库 - autoInstall: true + // 构建时禁止临时 npm install,避免打包过程访问外网卡住 + autoInstall: !isBuild }); }; diff --git a/web/vite/plugins/index.ts b/web/vite/plugins/index.ts index fb747b4b..6aa7ad4f 100644 --- a/web/vite/plugins/index.ts +++ b/web/vite/plugins/index.ts @@ -1,5 +1,4 @@ import vue from '@vitejs/plugin-vue'; -import vueDevTools from 'vite-plugin-vue-devtools'; // 临时禁用以解决localStorage兼容性问题 import createUnoCss from './unocss'; import createAutoImport from './auto-import'; @@ -13,12 +12,11 @@ import path from 'path'; export default (viteEnv: any, isBuild = false): [] => { const vitePlugins: any = []; vitePlugins.push(vue()); - // vitePlugins.push(vueDevTools()); // 禁用以解决localStorage兼容性问题 vitePlugins.push(createUnoCss()); - vitePlugins.push(createAutoImport(path)); - vitePlugins.push(createComponents(path)); + vitePlugins.push(createAutoImport(path, isBuild)); + vitePlugins.push(createComponents(path, isBuild)); vitePlugins.push(createCompression(viteEnv)); - vitePlugins.push(createIcons()); + vitePlugins.push(createIcons(isBuild)); vitePlugins.push(createSvgIconsPlugin(path)); vitePlugins.push(createSetupExtend()); return vitePlugins;