Compare commits

...
Author SHA1 Message Date
zlwuandCursor d6769846f5 fix: CI web 构建堆提升至 5120MB,避免低内存优化后仍 OOM
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-24 15:40:39 +08:00
zlwuandCursor 9d93a373ae perf(web): 降低生产构建内存峰值,并启用 CI 低内存打包模式
关闭按需 Element Plus 样式与构建期 dts、限制 rollup 并行、CI 跳过 gzip;
新增 npm run build:ci,供 Drone 在小内存机器上打包。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-24 15:30:38 +08:00
zlwuandCursor df6ce133dd fix: CI web 构建堆改为 6144MB,匹配前端实际内存需求
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-24 14:24:30 +08:00
zlwuandCursor 8c33135f4c fix: CI web 构建堆改为 3584MB,配合测试机扩容后的 swap
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-24 13:21:47 +08:00
zlwuandCursor f067c95d93 fix: CI web 构建改用 2G 堆内存,适配测试机小内存
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-24 13:10:08 +08:00
zlwuandCursor 8a48b2cf02 fix: 统一 classMsg API 引用大小写,修复 Linux CI 构建找不到模块
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-24 12:23:24 +08:00
10 changed files with 60 additions and 34 deletions

No files matched your search

+6 -5
View File
@@ -36,18 +36,19 @@ steps:
- ls -lh "/hzdyjs-releases/${DRONE_TAG}/backend-${DRONE_TAG}.jar"
- name: package-web
# Node 20 + 无 lock 时 npm 易触发 edgesOut 空指针;18 + legacy-peer-deps 更稳
image: node:18-bookworm
# 无 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
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) (build:ci low-mem)"
- mkdir -p "/hzdyjs-releases/${DRONE_TAG}"
- cd web
# package-lock.json 被 gitignore,仓库内无 lock
- npm install --legacy-peer-deps --no-audit --no-fund
- npm run build:prod
# build:ci 内置 CI/VITE_LOW_MEM/关 gzip + 5G 堆(勿在 Drone environment 再声明自定义变量,会被预处理掏空)
- 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"
+3 -2
View File
@@ -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=5120 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",
@@ -535,9 +535,9 @@ import {
updateClassStatus,
updateClassStatusBatch,
updateClassCampusBatch
} from '@/api/xg/xsjcxx/classmsg/classMsg';
import { listColleges, collegeTree, listMajorsByCollegeId } from '@/api/xg/xsjcxx/classmsg/college';
import { ClassMsgVO, ClassMsgQuery, ClassMsgForm, SysDeptVo } from '@/api/xg/xsjcxx/classmsg/types';
} from '@/api/xg/xsjcxx/classMsg/classMsg';
import { listColleges, collegeTree, listMajorsByCollegeId } from '@/api/xg/xsjcxx/classMsg/college';
import { ClassMsgVO, ClassMsgQuery, ClassMsgForm, SysDeptVo } from '@/api/xg/xsjcxx/classMsg/types';
import { listDormCampus } from '@/api/xg/xgss/xqxx/dormCampus';
import type { DormCampusVO } from '@/api/xg/xgss/xqxx/dormCampus/types';
import { ComponentInternalInstance, ref, reactive, toRefs, onMounted, getCurrentInstance, watch } from 'vue';
@@ -239,8 +239,8 @@
</template>
<script setup name="MajorMsg" lang="ts">
import { listMajor, getMajor, delMajor, addMajor, updateMajor, getCollegeTree, previewMajorMerge, mergeMajors } from '@/api/xg/xsjcxx/classmsg/majormsg';
import { MajorVO, MajorQuery, MajorForm, CollegeTreeNode, MajorMergePreviewVO } from '@/api/xg/xsjcxx/classmsg/majormsg/types';
import { listMajor, getMajor, delMajor, addMajor, updateMajor, getCollegeTree, previewMajorMerge, mergeMajors } from '@/api/xg/xsjcxx/classMsg/majormsg';
import { MajorVO, MajorQuery, MajorForm, CollegeTreeNode, MajorMergePreviewVO } from '@/api/xg/xsjcxx/classMsg/majormsg/types';
import { ComponentInternalInstance, ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue';
import type { FormInstance as ElFormInstance } from 'element-plus';
import type { ElTree } from 'element-plus';
+22 -3
View File
@@ -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'
]
}
};
+5 -4
View File
@@ -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')
});
};
+5 -3
View File
@@ -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')
});
};
+8 -4
View File
@@ -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
})
);
}
+3 -3
View File
@@ -1,8 +1,8 @@
import Icons from 'unplugin-icons/vite';
export default () => {
export default (isBuild = false) => {
return Icons({
// 自动安装图标库
autoInstall: true
// 构建时禁止临时 npm install,避免打包过程访问外网卡住
autoInstall: !isBuild
});
};
+3 -5
View File
@@ -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;