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 相关文章推荐
JAVASCRIPT  THIS详解 面向对象
Mar 25 Javascript
JavaScript 继承详解 第一篇
Aug 30 Javascript
javascript 模拟JQuery的Ready方法实现并出现的问题
Dec 06 Javascript
jQuery获得内容和属性方法及示例
Dec 02 Javascript
JS批量修改PS中图层名称的方法
Jan 26 Javascript
js调试工具console.log()方法查看js代码的执行情况
Aug 08 Javascript
AngularJS表达式讲解及示例代码
Aug 16 Javascript
JavaScript Uploadify文件上传实例
Feb 28 Javascript
vue2.0项目中使用Ueditor富文本编辑器示例代码
Aug 14 Javascript
JS实现字符串中去除指定子字符串方法分析
May 17 Javascript
VUE解决 v-html不能触发点击事件的问题
Oct 28 Javascript
用Golang运行JavaScript的实现示例
Nov 25 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中关于codeigniter的xmlrpc的类在进行数据交换时的类型问题
2011/07/03 PHP
php变量范围介绍
2012/10/15 PHP
php中字符串和正则表达式详解
2014/10/23 PHP
PHP利用func_get_args和func_num_args函数实现函数重载实例
2014/11/12 PHP
PHP实现字母数字混合验证码功能
2019/07/11 PHP
基于laravel-admin 后台 列表标签背景的使用方法
2019/10/03 PHP
JS获取url链接字符串 location.href
2013/12/23 Javascript
JavaScript中Math对象方法使用概述
2014/01/02 Javascript
js日期、星座的级联显示代码
2014/01/23 Javascript
javascript日期格式化方法汇总
2015/10/04 Javascript
bootstrap fileinput完整实例分享
2016/11/08 Javascript
详解vue2.0 使用动态组件实现 Tab 标签页切换效果(vue-cli)
2017/08/30 Javascript
js es6系列教程 - 基于new.target属性与es5改造es6的类语法
2017/09/02 Javascript
vue轮播图插件vue-concise-slider的使用
2018/03/13 Javascript
JavaScript分步实现一个出生日期的正则表达式
2018/03/22 Javascript
vue2.0+koa2+mongodb实现注册登录
2018/04/10 Javascript
详解基于DllPlugin和DllReferencePlugin的webpack构建优化
2018/06/28 Javascript
JavaScript模板引擎实现原理实例详解
2018/12/14 Javascript
详解微信小程序之一键复制到剪切板
2019/04/24 Javascript
Vuex的实战使用详解
2019/10/31 Javascript
Python中字典映射类型的学习教程
2015/08/20 Python
用python写个自动SSH登录远程服务器的小工具(实例)
2017/06/17 Python
PyCharm设置SSH远程调试的方法
2018/07/17 Python
python 在指定范围内随机生成不重复的n个数实例
2019/01/28 Python
浅谈sklearn中predict与predict_proba区别
2020/06/28 Python
Python 常用日期处理 -- calendar 与 dateutil 模块的使用
2020/09/02 Python
python与c语言的语法有哪些不一样的
2020/09/13 Python
html5+css3气泡组件的实现
2014/11/21 HTML / CSS
购买限量版收藏品、珠宝和礼品:Bradford Exchange
2016/09/23 全球购物
护理专科自荐书范文
2014/02/18 职场文书
《小鹰学飞》教学反思
2014/04/23 职场文书
运动会标语
2014/06/21 职场文书
教师师德师风个人整改方案
2014/09/18 职场文书
意外死亡赔偿协议书
2014/10/14 职场文书
担保书格式范文
2015/09/22 职场文书
消防安全培训工作总结
2015/10/23 职场文书