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

一般我们启动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就无法压缩。

(0)

相关推荐

  • 高性能WEB开发 flush让页面分块,逐步呈现 flush让页面分块,逐步呈现

    一般大家在处理这种情况,都使用ajax,先把html输出到客户端,然后再用ajax取加载比较耗时的资源.用ajax麻烦的地方是增加了请求数,而且需要写额外的js代码.和js调用的请求接口. 正对这种情况,还有一种处理方法,就是让response分块编码进行传输.response分块编码,可以先传输一部分不需要处理的html代码到客户端,等其他耗时代码执行完毕后再传输另外的html代码. 分块编码(chunked encoding) chunked encoding 是http1.1 才支持编码格

  • 高性能web开发 如何加载JS,JS应该放在什么位置?

    外部JS的阻塞下载 所有浏览器在下载JS的时候,会阻止一切其他活动,比如其他资源的下载,内容的呈现等等.至到JS下载.解析.执行完毕后才开始继续并行下载其他资源并呈现内容. 有人会问:为什么JS不能像CSS.image一样并行下载了?这里需要简单介绍一下浏览器构造页面的原理, 当浏览器从服务器接收到了HTML文档,并把HTML在内存中转换成DOM树,在转换的过程中如果发现某个节点(node)上引用了CSS或者IMAGE,就会再发1个request去请求CSS或image,然后继续执行下面的转换,

  • 高性能WEB开发 页面呈现、重绘、回流。

    页面呈现流程 在讨论页面重绘.回流之前.需要对页面的呈现流程有些了解,页面是怎么把html结合css等显示到浏览器上的,下面的流程图显示了浏览器对页面的呈现的处理流程.可能不同的浏览器略微会有些不同.但基本上都是类似的. 1. 浏览器把获取到的html代码解析成1个Dom树,html中的每个tag都是Dom树中的1个节点,根节点就是我们常用的document对象(<html> tag).dom树就是我们用firebug或者IE Developer Toolbar等工具看到的html结构,里面包

  • web高性能开发系列随笔 BearRui(AK-47)版

    1. HTTP服务器.2.性能测试工具推荐 3. 图片篇. 4. 如何加载JS,JS应该放在什么位置. 5. 为什么要减少请求数,如何减少请求数.6. 减少请求,响应的数据量.7.JS.CSS的合并.压缩.缓存管理 8.页面呈现.重绘.回流. 9.该如何加载google-analytics(或其他第三方)的JS. [声明] 转载请注明出处:http://www.cnblogs.com/BearsTaR/. 禁止商用!

  • 高性能WEB开发 nginx HTTP服务器篇

    第一篇:HTTP服务器 因tomcat处理静态资源的速度比较慢,所以首先想到的就是把所有静态资源(JS,CSS,image,swf) 提到单独的服务器,用更加快速的HTTP服务器,这里选择了nginx了,nginx相比apache,更加轻量级, 配置更加简单,而且nginx不仅仅是高性能的HTTP服务器,还是高性能的反向代理服务器. 目前很多大型网站都使用了nginx,新浪.网易.QQ等都使用了nginx,说明nginx的稳定性和性能还是非常不错的. 1. nginx 安装(linux) htt

  • 编写高性能的JavaScript 脚本的加载与执行

    脚本可以放在html页面的head里面,也可以放在body里面. 把脚本放在body中,当浏览器遇见<script>标签时, 浏览器不知道脚本会插入文本还是html标签,因此浏览器会停止分析html页面而去执行脚本.当使用src的方式添加脚本时,浏览器也会做同样的动作.在脚本处理的时候,页面呈现和用户交互将被完全阻止.脚本下载和执行阻塞了其他资源的下载,比如呈现页面使用的图片.(虽然很多浏览器实现了脚本并行下载的技术,但是这个问题依然没有解决) 脚本的位置 鉴于上面的理由,脚本应该始终放在页面

  • 高性能WEB开发 为什么要减少请求数,如何减少请求数!

    http请求头的数据量 我们先分析下请求头,看看每次请求都带了那些额外的数据.下面是监控的google的请求头 Host www.google.com.hk User-Agent Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTBDFff GTB7.0 Accept text/html,application/xhtml+xml,application/xml;q

  • 高性能WEB开发(5) 减少请求,响应的数据量

    GZIP压缩    gzip是目前所有浏览器都支持的一种压缩格式,IE6需要SP1及以上才支持(别说你还在用IE5,~_~).gzip可以说是最方便而且也是最大减少响应数据量的1种方法. 说它方便,是因为你不需要为它写任何额外的代码,只需要在http服务器上加上配置都行了,现在主流的http服务器都支持gzip,各种服务器的配置这里就不一一介绍(其实是我不知道怎么配), nginx的配置可以参考我这篇文章:www.blogjava.net/BearRui/archive/2010/01/29/w

  • 高性能WEB开发 图片压缩篇

    一.缩小图片大小 当图片很多的时候,减少图片大小是提高下载速度最直接的方法. 1. 使用PNG8代替GIF(非动画图片),因为PNG8在效果一样的情况,图片大小比GIF要小. 2. 用fireworks处理PNG图片,在我们产品中很多PNG图片是美工直接用photoshop导出的, 后来让美工用fireworks处理PNG(大概的方式是选择保存为PNG8,删除背景色). 处理后100K的图片大小基本减少了3/4,但图片质量也会有少许降低,要看自己是否能接受. 3. 使用Smush.it(http

  • 高性能WEB开发 JS、CSS的合并、压缩、缓存管理

    存在的问题: 合并.压缩文件主要有2方面的问题: 1. 每次发布的时候需要运行一下自己写的bat文件或者其他程序把文件按照自己的配置合并和压缩. 2. 因生产环境和开发环境需要加载的文件不一样,生产环境为了需要加载合并.压缩后的文件,而开发环境为了修改.调试方便,需要加载非合并.压缩的文件,所以我们常常需要在JSP中类似与下面的判断代码: 复制代码 代码如下: <c:if test="${env=='prod'}"> <script type="text/j

  • 了解CSS的查找匹配原理,让CSS更简洁、高效

    看1个简单的CSS: DIV#divBox p span.red{color:red;},按习惯我们对这个CSS 的理解是,浏览器先查找id为divBox的DIV元素,当找到后,再找其下的所有p元素,然后再查找所有span元素,当发现有span的class为red的时候,就应用该style.多么简单易懂的原理,可是这个理解却是完完全全相反.错误的. 匹配原理: 浏览器CSS匹配不是从左到右进行查找,而是从右到左进行查找.比如之前说的 DIV#divBox p span.red{color:red

  • 高性能WEB开发 web性能测试工具推荐

    Firebug:    Firebug 是firefox中最为经典的开发工具,可以监控请求头,响应头,显示资源加载瀑布图: HttpWatch :   httpwatch 功能类似firebug,可以监控请求头,响应头,显示资源加载瀑布图.但是httpwatch还能显示GZIP压缩信息,DNS查询,TCP链接信息,个人在监控http请求比较喜欢使用httpwatch, httpwatch包含IE和firefox插件.不过httpwatch专业版本是收费的,免费版本有些功能限制. DynaTrac

随机推荐