蓝凌EKP产品:JSP 项目性能基于业务维度的 JS 压缩合并方案优化实战
一、业务背景
蓝凌的OA系统是一个典型的功能强大,业务使用广泛的系统,蓝凌的OA可以随便拖拉拽配置复杂的表单,比如在流程管理模块配置一个完整的合同审批流,整个合同流程可以通过第三方系统发起合同,有完整的接口提供。在合同审批中,可以通过机器人节点完成和第三方系统交互,推送数据,从第三方抓取数据审查合同数据。还能通过EKP的最新AI功能,在合同审批节点完成对合同的数据比对和分析,大量节省人力。但是为了满足复杂的功能,开发一个页面时也需要引用第三方库完成复杂的页面交互。有时经常需要加载几十甚至上百个 JavaScript 文件。此工具还提供扩展接口,方便项目做强大定制,只需要按照扩展点实现,引入扩展点即可,在自己的扩展业务里面做好替代的引入压缩js,对业务定制功能性能也有保障。
比如:
流程审批页面:需要引入公用的工具库、表单校验、流程图组件、权限控制脚本;
报表统计页面:需要引入图表库、数据处理脚本、导出功能;
门户首页:需要引入各种小部件 JS、消息中心脚本、公告模块脚本。
结果就是:
👉 一个页面加载时可能触发 几百个 JS 请求。
这带来了几个问题:
请求数过多,浏览器并发限制导致脚本队列排队;
TCP 连接和 HTTP 头开销严重;
页面白屏时间长,用户体验差。
于是我们决定:按业务功能维度合并压缩 JS,用一个请求替代几十个请求。
二、解决思路
1. 按业务功能分类
我们不是简单地把所有 JS 合并成一个“巨无霸”文件,而是按照业务场景来划分:
公共库 JS(所有页面必用,比如 jQuery、工具函数)
流程审批 JS(审批页面特有脚本)
自定义表单JS(拖拉拽的表单)
报表统计 JS(图表处理、导出相关脚本)
门户首页 JS(公告、消息、模块拼装脚本)
这样做的好处:
保证通用库只需加载一次;
业务模块按需加载,避免无关脚本浪费。
2. 实现原理
定义一个扩展点,EKP的扩展点功能强大,底层实现完全基于解耦合,方便各个业务模块扩展,可以做到代码的零耦合,业务要扩展直接在业务端增加配置即可实现业务的功能增强。
声明的扩展点
<extension-point id="com.landray.kmss.sys.ui.compressExecutor"><item name="executor"><param name="unid"required="true"kind="unid"><description>唯一标识</description></param><param name="bean"basedOn="com.landray.kmss.sys.ui.service.ISysUiCompressExecutor"required="true"kind="spring"><description>压缩合并js执行接口的具体实现类</description></param><description>js合并压缩扩展点,在门户维护参数>参数配置中开启压缩合并js时执行</description></item><description>js合并压缩执行配置</description></extension-point>
实现的扩展点
<extension point="com.landray.kmss.sys.ui.compressExecutor"model="com.landray.kmss.sys.ui.service.ISysUiCompressExecutor"><item name="executor"><param name="unid" value="sysUiCompressExecutor" /><param name="bean" value="sysUiCompressExecutor"/></item></extension>
sysUiCompressExecutor 这个就是业务实现的功能,合并业务的js,此处就是按业务功能维度合并整理的合并JS文件。

这些工具可以:
自动合并多个 JS;
去掉空格、注释;
变量名缩短,减少体积;
输出一个新的
.min.js
文件。
3. 页面引用替换
原来:页面引用几十个 JS 文件;
优化后:页面只引用一个合并压缩后的 JS 文件。
三、实现步骤
1. 业务模块归类
示例项目目录:
"sys/ui/js/parser.js", "sys/ui/js/dialog.js", "sys/ui/js/util/str.js","sys/ui/js/topic.js", "sys/ui/js/Evented.js", "sys/ui/js/view/layout.js","sys/ui/js/base.js", "sys/ui/js/util/env.js", "sys/ui/js/overlay.js","sys/ui/js/dialog/actor.js", "sys/ui/js/dialog/trigger.js", "sys/ui/js/dialog/content.js","sys/ui/js/dragdrop.js", "sys/ui/js/util/crypto.js", "sys/ui/js/Class.js","sys/ui/js/view/Template.js", "sys/ui/js/util/loader.js", "sys/ui/js/toolbar.js","sys/ui/js/element.js", "sys/ui/js/suspend.js", "sys/ui/js/spa/directions/ifDirection.js","sys/ui/js/spa/directions/authDirection.js", "sys/ui/js/spa/directions/mapDirection.js", "sys/ui/js/spa/const.js","sys/ui/js/spa/Spa.js", "sys/ui/js/spa/router.js", "sys/ui/js/spa/values.js","sys/ui/js/spa/direction.js", "sys/ui/js/spa/router/hash.js", "sys/ui/js/framework/router/router-utils.js","sys/ui/js/framework/router/router.js", "sys/ui/js/framework/router/const.js", "sys/ui/js/framework/router/utils/create-route-map.js",//增加一些新公共js的提取jsHead.jsp"sys/ui/js/imageP/preview.js", "sys/ui/js/imageP/ImageP.js", "sys/ui/js/imageP/Play.js", "sys/ui/js/imageP/Panel.js","sys/ui/js/imageP/Thumb.js", "sys/ui/js/imageP/Value.js", "sys/ui/js/imageP/Path.js", "sys/ui/js/imageP/Toolbar.js","sys/ui/js/menu.js", "sys/ui/js/view/render.js", "sys/ui/js/switch.js", "sys/ui/js/listview/columntable.js","sys/ui/js/listview/paging.js", "sys/ui/js/popup.js", "sys/ui/js/spa/adapters/menuSourceAdapter.js", "sys/ui/js/spa/adapters/menuItemAdapter.js","sys/ui/js/spa/adapters/menuAdapter.js", "sys/authentication/identity/js/auth.js", "sys/ui/js/qrcode/qrcode.js"
原理一个页面针对某一个业务要请求几十个js 文件。
规划合并后:
common.min.js
→ 公共库flow.min.js
→ 流程审批模块report.min.js
→ 报表模块portal.min.js
→ 门户首页模块
2. 使用 Java 压缩工具
以 YUI Compressor 为例:
public static void compressOneJS(final JSContext ctx) throws EvaluatorException, IOException {final Reader in = ctx.getIn();final Writer out = ctx.getOut();try {JavaScriptCompressor compressor = new JavaScriptCompressor(in, new ErrorReporter() {@Overridepublic void warning(String message, String sourceName, int line, String lineSource, int lineOffset) {if (line < 0) {logger.debug(message);} else {logger.debug("" + line + ':' + lineOffset + ':' + message);}}@Overridepublic void error(String message, String sourceName, int line, String lineSource, int lineOffset) {String file = "[ERROR: " + ctx.getFile() + "]";if (line < 0) {logger.error(file + ", " + message);throw new Error("\n[ERROR] " + message);} else {logger.error(file + ", " + "第" + line + "行出错:" + message + "\n" + lineSource);throw new Error("\n[ERROR] 第" + line + "行出错:" + message + "\n" + lineSource);}}@Overridepublic EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource,int lineOffset) {error(message, sourceName, line, lineSource, lineOffset);return new EvaluatorException(message);}});boolean munge = ctx.isMunge();boolean preserveAllSemiColons = ctx.isPreserveAllSemiColons();boolean disableOptimizations = ctx.isDisableOptimizations();boolean verbose = ctx.isVerbose();// 关闭详细信息(因为可能有大量警告信息)int linebreakpos = ctx.getLinebreakpos();// 无断行compressor.compress(out, linebreakpos, munge, verbose, preserveAllSemiColons, disableOptimizations);} catch (Throwable t) {logger.error("JS资源压缩错误", t);throw new RuntimeException(t);} finally {IOUtils.closeQuietly(in);IOUtils.closeQuietly(out);}}
执行后,dist/flow.min.js
就是压缩合并好的文件。
然后在业务模块使用即可。
3. 页面替换引用
原始页面:
<script type="text/javascript" src='${LUI_ContextPath}/resource/js/domain.js?s_cache=${ LUI_Cache }'></script><script type="text/javascript" src='${LUI_ContextPath}/sys/ui/js/LUI.js?s_cache=${ LUI_Cache }'></script><script type="text/javascript" src="${LUI_ContextPath}/resource/js/common.js?s_cache=${ LUI_Cache }"></script><script type="text/javascript" src="${LUI_ContextPath}/resource/js/sea.js?s_cache=${ LUI_Cache }"></script>
优化后:主要增加了压缩合并开关功能,提供用户多重选择。
<c:choose><c:when test="${compressSwitch eq 'true' && lfn:jsCompressEnabled('sysUiCompressExecutor', 'jsHead_com_combined')}"><script src="<%= PcJsOptimizeUtil.getScriptSrcByExtension("sysUiCompressExecutor","jsHead_com_combined") %>?s_cache=${ LUI_Cache }"></script></c:when><c:otherwise><script type="text/javascript" src='${LUI_ContextPath}/resource/js/domain.js?s_cache=${ LUI_Cache }'></script><script type="text/javascript" src='${LUI_ContextPath}/sys/ui/js/LUI.js?s_cache=${ LUI_Cache }'></script><script type="text/javascript" src="${LUI_ContextPath}/resource/js/common.js?s_cache=${ LUI_Cache }"></script><script type="text/javascript" src="${LUI_ContextPath}/resource/js/sea.js?s_cache=${ LUI_Cache }"></script></c:otherwise>
</c:choose>
四、优化效果
请求数对比
优化前:一个页面 100+ 请求
优化后:一个页面 < 10 请求
加载速度
首屏渲染加快 40%+
移动端用户体验提升明显
维护性
仍然保持模块化,开发时引用原始 JS,打包时生成压缩文件;
不会出现一个大文件难以维护的情况。
五、总结
这次优化的核心思路是:
从 业务场景 出发划分 JS 模块;
使用 Java 后台压缩工具 合并压缩,避免引入前端复杂构建链路;
页面只需引用少量合并后的 JS 文件,大幅减少请求数。
最终,我们把“一个页面几百个 JS 请求”优化成了“按业务划分的几个请求”,极大改善了用户体验。
这套方案特别适合 传统 OA项目,既能结合现有业务结构,又能保证性能提升,值得在类似项目里推广。