WEB高性能开发之疯狂的HTML压缩


Posted in Javascript onJune 19, 2010

一般我们启动gzip都比较少对html启动gzip,因为现在的html都是动态的,不会使用浏览器缓存,而启用gzip的话每次请求都需要压缩,会比较消耗服务器资源,对js,css启动gzip比较好是因为js,css都会使用缓存。我个人觉得的压缩html的最大好处就是一本万利,只要写好了一次,以后所有程序都可以使用,不会增加任何额外的开发工作。
在“JS、CSS的合并、压缩、缓存管理”一文中说到自己写过的1个自动合并、压缩JS,CSS,并添加版本号的组件。这次把压缩html的功能也加入到该组件中,流程很简单,就是在程序启动(contextInitialized or Application_Start)的时候扫描所有html,jsp(aspx)进行压缩。
压缩的注意事项:
实现的方式主要是用正则表达式去查找,替换。在html压缩的时候,主要要注意下面几点:
1. pre,textarea 标签里面的内容格式需要保留,不能压缩。
2. 去掉html注释的时候,有些注释是不能去掉的,比如:<!--[if IE 6]> ..... <![endif]-->
3. 压缩嵌入式js中的注释要注意,因为可能注释符号会出现在字符串中,比如: var url = "http://www.cnblogs.com"; // 前面的//不是注释
去掉JS换行符的时候,不能直接跟一下行动内容,需要有空格,考虑下面的代码:
else
return;
如果不带空格,则变成elsereturn。
4. jsp(aspx) 中很有可能会使用<% %>嵌入一些服务器代码,这个时候也需要单独处理,里面注释的处理方法跟js的一样。
源代码:
下面是java实现的源代码,也可以 猛击此处 下载该代码,相信大家都看的懂,也很容易改成net代码:

import java.io.StringReader; 
import java.io.StringWriter; 
import java.util.*; 
import java.util.regex.*; 
/******************************************* 
* 压缩jsp,html中的代码,去掉所有空白符、换行符 
* @author bearrui(ak-47) 
* @version 0.1 
* @date 2010-5-13 
*******************************************/ 
public class HtmlCompressor { 
private static String tempPreBlock = "%%%HTMLCOMPRESS~PRE&&&"; 
private static String tempTextAreaBlock = "%%%HTMLCOMPRESS~TEXTAREA&&&"; 
private static String tempScriptBlock = "%%%HTMLCOMPRESS~SCRIPT&&&"; 
private static String tempStyleBlock = "%%%HTMLCOMPRESS~STYLE&&&"; 
private static String tempJspBlock = "%%%HTMLCOMPRESS~JSP&&&"; 
private static Pattern commentPattern = Pattern.compile("<!--\\s*[^\\[].*?-->", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); 
private static Pattern itsPattern = Pattern.compile(">\\s+?<", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); 
private static Pattern prePattern = Pattern.compile("<pre[^>]*?>.*?</pre>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); 
private static Pattern taPattern = Pattern.compile("<textarea[^>]*?>.*?</textarea>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); 
private static Pattern jspPattern = Pattern.compile("<%([^-@][\\w\\W]*?)%>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); 
// <script></script> 
private static Pattern scriptPattern = Pattern.compile("(?:<script\\s*>|<script type=['\"]text/javascript['\"]\\s*>)(.*?)</script>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); 
private static Pattern stylePattern = Pattern.compile("<style[^>()]*?>(.+)</style>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); 
// 单行注释, 
private static Pattern signleCommentPattern = Pattern.compile("//.*"); 
// 字符串匹配 
private static Pattern stringPattern = Pattern.compile("(\"[^\"\\n]*?\"|'[^'\\n]*?')"); 
// trim去空格和换行符 
private static Pattern trimPattern = Pattern.compile("\\n\\s*",Pattern.MULTILINE); 
private static Pattern trimPattern2 = Pattern.compile("\\s*\\r",Pattern.MULTILINE); 
// 多行注释 
private static Pattern multiCommentPattern = Pattern.compile("/\\*.*?\\*/", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); 
private static String tempSingleCommentBlock = "%%%HTMLCOMPRESS~SINGLECOMMENT&&&"; // //占位符 
private static String tempMulitCommentBlock1 = "%%%HTMLCOMPRESS~MULITCOMMENT1&&&"; // /*占位符 
private static String tempMulitCommentBlock2 = "%%%HTMLCOMPRESS~MULITCOMMENT2&&&"; // */占位符 public static String compress(String html) throws Exception { 
if(html == null || html.length() == 0) { 
return html; 
} 
List<String> preBlocks = new ArrayList<String>(); 
List<String> taBlocks = new ArrayList<String>(); 
List<String> scriptBlocks = new ArrayList<String>(); 
List<String> styleBlocks = new ArrayList<String>(); 
List<String> jspBlocks = new ArrayList<String>(); 
String result = html; 
//preserve inline java code 
Matcher jspMatcher = jspPattern.matcher(result); 
while(jspMatcher.find()) { 
jspBlocks.add(jspMatcher.group(0)); 
} 
result = jspMatcher.replaceAll(tempJspBlock); 
//preserve PRE tags 
Matcher preMatcher = prePattern.matcher(result); 
while(preMatcher.find()) { 
preBlocks.add(preMatcher.group(0)); 
} 
result = preMatcher.replaceAll(tempPreBlock); 
//preserve TEXTAREA tags 
Matcher taMatcher = taPattern.matcher(result); 
while(taMatcher.find()) { 
taBlocks.add(taMatcher.group(0)); 
} 
result = taMatcher.replaceAll(tempTextAreaBlock); 
//preserve SCRIPT tags 
Matcher scriptMatcher = scriptPattern.matcher(result); 
while(scriptMatcher.find()) { 
scriptBlocks.add(scriptMatcher.group(0)); 
} 
result = scriptMatcher.replaceAll(tempScriptBlock); 
// don't process inline css 
Matcher styleMatcher = stylePattern.matcher(result); 
while(styleMatcher.find()) { 
styleBlocks.add(styleMatcher.group(0)); 
} 
result = styleMatcher.replaceAll(tempStyleBlock); 
//process pure html 
result = processHtml(result); 
//process preserved blocks 
result = processPreBlocks(result, preBlocks); 
result = processTextareaBlocks(result, taBlocks); 
result = processScriptBlocks(result, scriptBlocks); 
result = processStyleBlocks(result, styleBlocks); 
result = processJspBlocks(result, jspBlocks); 
preBlocks = taBlocks = scriptBlocks = styleBlocks = jspBlocks = null; 
return result.trim(); 
} 
private static String processHtml(String html) { 
String result = html; 
//remove comments 
// if(removeComments) { 
result = commentPattern.matcher(result).replaceAll(""); 
// } 
//remove inter-tag spaces 
// if(removeIntertagSpaces) { 
result = itsPattern.matcher(result).replaceAll("><"); 
// } 
//remove multi whitespace characters 
// if(removeMultiSpaces) { 
result = result.replaceAll("\\s{2,}"," "); 
// } 
return result; 
} 
private static String processJspBlocks(String html, List<String> blocks){ 
String result = html; 
for(int i = 0; i < blocks.size(); i++) { 
blocks.set(i, compressJsp(blocks.get(i))); 
} 
//put preserved blocks back 
while(result.contains(tempJspBlock)) { 
result = result.replaceFirst(tempJspBlock, Matcher.quoteReplacement(blocks.remove(0))); 
} 
return result; 
} 
private static String processPreBlocks(String html, List<String> blocks) throws Exception { 
String result = html; 
//put preserved blocks back 
while(result.contains(tempPreBlock)) { 
result = result.replaceFirst(tempPreBlock, Matcher.quoteReplacement(blocks.remove(0))); 
} 
return result; 
} 
private static String processTextareaBlocks(String html, List<String> blocks) throws Exception { 
String result = html; 
//put preserved blocks back 
while(result.contains(tempTextAreaBlock)) { 
result = result.replaceFirst(tempTextAreaBlock, Matcher.quoteReplacement(blocks.remove(0))); 
} 
return result; 
} 
private static String processScriptBlocks(String html, List<String> blocks) throws Exception { 
String result = html; 
// if(compressJavaScript) { 
for(int i = 0; i < blocks.size(); i++) { 
blocks.set(i, compressJavaScript(blocks.get(i))); 
} 
// } 
//put preserved blocks back 
while(result.contains(tempScriptBlock)) { 
result = result.replaceFirst(tempScriptBlock, Matcher.quoteReplacement(blocks.remove(0))); 
} 
return result; 
} 
private static String processStyleBlocks(String html, List<String> blocks) throws Exception { 
String result = html; 
// if(compressCss) { 
for(int i = 0; i < blocks.size(); i++) { 
blocks.set(i, compressCssStyles(blocks.get(i))); 
} 
// } 
//put preserved blocks back 
while(result.contains(tempStyleBlock)) { 
result = result.replaceFirst(tempStyleBlock, Matcher.quoteReplacement(blocks.remove(0))); 
} 
return result; 
} 
private static String compressJsp(String source) { 
//check if block is not empty 
Matcher jspMatcher = jspPattern.matcher(source); 
if(jspMatcher.find()) { 
String result = compressJspJs(jspMatcher.group(1)); 
return (new StringBuilder(source.substring(0, jspMatcher.start(1))).append(result).append(source.substring(jspMatcher.end(1)))).toString(); 
} else { 
return source; 
} 
} 
private static String compressJavaScript(String source) { 
//check if block is not empty 
Matcher scriptMatcher = scriptPattern.matcher(source); 
if(scriptMatcher.find()) { 
String result = compressJspJs(scriptMatcher.group(1)); 
return (new StringBuilder(source.substring(0, scriptMatcher.start(1))).append(result).append(source.substring(scriptMatcher.end(1)))).toString(); 
} else { 
return source; 
} 
} 
private static String compressCssStyles(String source) { 
//check if block is not empty 
Matcher styleMatcher = stylePattern.matcher(source); 
if(styleMatcher.find()) { 
// 去掉注释,换行 
String result= multiCommentPattern.matcher(styleMatcher.group(1)).replaceAll(""); 
result = trimPattern.matcher(result).replaceAll(""); 
result = trimPattern2.matcher(result).replaceAll(""); 
return (new StringBuilder(source.substring(0, styleMatcher.start(1))).append(result).append(source.substring(styleMatcher.end(1)))).toString(); 
} else { 
return source; 
} 
} 
private static String compressJspJs(String source){ 
String result = source; 
// 因注释符合有可能出现在字符串中,所以要先把字符串中的特殊符好去掉 
Matcher stringMatcher = stringPattern.matcher(result); 
while(stringMatcher.find()){ 
String tmpStr = stringMatcher.group(0); 
if(tmpStr.indexOf("//") != -1 || tmpStr.indexOf("/*") != -1 || tmpStr.indexOf("*/") != -1){ 
String blockStr = tmpStr.replaceAll("//", tempSingleCommentBlock).replaceAll("/\\*", tempMulitCommentBlock1) 
.replaceAll("\\*/", tempMulitCommentBlock2); 
result = result.replace(tmpStr, blockStr); 
} 
} 
// 去掉注释 
result = signleCommentPattern.matcher(result).replaceAll(""); 
result = multiCommentPattern.matcher(result).replaceAll(""); 
result = trimPattern2.matcher(result).replaceAll(""); 
result = trimPattern.matcher(result).replaceAll(" "); 
// 恢复替换掉的字符串 
result = result.replaceAll(tempSingleCommentBlock, "//").replaceAll(tempMulitCommentBlock1, "/*") 
.replaceAll(tempMulitCommentBlock2, "*/"); 
return result; 
} 
}

使用注意事项:

使用了上面方法后,再运行程序,是不是发现每个页面查看源代码的时候都变成1行啦,还不错吧,但是在使用的时候还是要注意一些问题:
1. 嵌入js本来想调用yuicompressor来压缩,yuicompressor压缩JS前,会先编译js是否合法,因我们嵌入的js中可能很多会用到一些服务器端代码,比如 var now = <%=DateTime.now %> ,这样的代码会编译不通过,所以无法使用yuicompressor。
最后只能自己写压缩JS代码,自己写的比较粗燥,所以有个问题还解决,就是如果开发人员在一句js代码后面没有加分号的话,压缩成1行就很有可能出问题。所以使用这个需要保证每条语句结束后都必须带分号。

2. 因为是在程序启动的时候压缩所有jsp(aspx),所以如果是用户请求的时候动态产生的html就无法压缩。

Javascript 相关文章推荐
拖动一个HTML元素
Dec 22 Javascript
EasyUI中的tree用法介绍
Nov 01 Javascript
使用javascript实现简单的选项卡切换
Jan 09 Javascript
JS实现动画兼容性的transition和transform实例分析
Dec 13 Javascript
浅谈Vue响应式(数组变异方法)
May 07 Javascript
JS实现统计字符串中字符出现个数及最大个数功能示例
Jun 04 Javascript
js嵌套的数组扁平化:将多维数组变成一维数组以及push()与concat()区别的讲解
Jan 19 Javascript
谈谈JavaScript中super(props)的重要性
Feb 12 Javascript
详解服务端预渲染之Nuxt(介绍篇)
Apr 07 Javascript
微信小程序在text文本实现多种字体样式
Nov 08 Javascript
JS前端面试必备——基本排序算法原理与实现方法详解【插入/选择/归并/冒泡/快速排序】
Feb 24 Javascript
原生js+css实现tab切换功能
Sep 17 Javascript
Html中JS脚本执行顺序简单举例说明
Jun 19 #Javascript
js parseInt(&quot;08&quot;)未指定进位制问题
Jun 19 #Javascript
ExtJs grid行 右键菜单的两种方法
Jun 19 #Javascript
JavaScript中也使用$美元符号来代替document.getElementById
Jun 19 #Javascript
javascript,jquery闭包概念分析
Jun 19 #Javascript
基于jquery的滚动新闻列表
Jun 19 #Javascript
基于Jquery的温度计动画效果
Jun 18 #Javascript
You might like
php实现有序数组旋转后寻找最小值方法
2018/09/27 PHP
javascript 自定义事件初探
2009/08/21 Javascript
NodeJS 模块开发及发布详解分享
2012/03/07 NodeJs
瀑布流布局并自动加载实现代码
2013/03/12 Javascript
js点击更换背景颜色或图片的实例代码
2013/06/25 Javascript
在Firefox下js select标签点击无法弹出
2014/03/06 Javascript
JavaScript实现的一个日期格式化函数分享
2014/12/06 Javascript
js实现类似于add(1)(2)(3)调用方式的方法
2015/03/04 Javascript
微信小程序 MINA文件结构
2016/10/17 Javascript
Vue 2.0+Vue-router构建一个简单的单页应用(附源码)
2017/03/14 Javascript
基于easyui checkbox 的一些操作处理方法
2017/07/10 Javascript
使用 Javascript 实现浏览器推送提醒功能的示例
2017/11/03 Javascript
JavaScript变量作用域及内存问题实例分析
2019/06/10 Javascript
node express使用HTML模板的方法示例
2019/08/22 Javascript
layui+SSM的数据表的增删改实例(利用弹框添加、修改)
2019/09/27 Javascript
layui清除radio的选中状态实例
2019/11/14 Javascript
微信小程序吸底区域适配iPhoneX的实现
2020/04/09 Javascript
微信小程序仿抖音视频之整屏上下切换功能的实现代码
2020/05/24 Javascript
[00:34]拔城逐梦,热血永恒!2020(秋)完美世界城市挑战赛报名开启
2020/10/09 DOTA
使用python的chardet库获得文件编码并修改编码
2014/01/22 Python
Python open()文件处理使用介绍
2014/11/30 Python
探究Python中isalnum()方法的使用
2015/05/18 Python
python制作最美应用的爬虫
2015/10/28 Python
实例讲解Python中global语句下全局变量的值的修改
2016/06/16 Python
Python使用matplotlib绘制正弦和余弦曲线的方法示例
2018/01/06 Python
Python开发的十个小贴士和技巧及长常犯错误
2018/09/27 Python
Windows下安装Scrapy
2018/10/17 Python
Python中使用遍历在列表中添加字典遇到的坑
2019/02/27 Python
python 动态调用函数实例解析
2019/10/21 Python
德国baby-markt婴儿用品瑞士网站:baby-markt.ch
2017/06/09 全球购物
瑞典网上购买现代和复古家具:Reforma
2019/10/21 全球购物
Jar包的作用是什么
2014/03/30 面试题
中专毕业自我鉴定
2013/10/16 职场文书
中层干部培训方案
2014/06/16 职场文书
公共艺术专业自荐信
2014/09/01 职场文书
交通事故案件代理词
2015/05/23 职场文书