JavaScript压缩代码的重要性不言而喻,如今的压缩工具也有不少,例如YUI Compressor,Google Closure Compiler,以及现在比较红火的UglifyJS。UglifyJS的出名是由于它代替Closure Compiler成为jQuery项目的压缩工具。根据我的实测,jQuery Core的代码使用UglifyJS压缩后(节省62.5%)的确要比Closure Compiler压缩后(节省57.53%)更小一些。很显然,这是因为UglifyJS的压缩策略比Closure Compiler更“聪明”一些。我这里用了“聪明”而不是“激进”,是因为“激进”带上了一丝负面的意味——就好比Closure Compiler的“高级”优化方式。之前与UglifyJS相比的是Closure Compiler的“简单”优化方式,它们都是“安全”的,而Closure Compiler的“高级”优化几乎100%会破坏您的代码,因此它提出了各种“激进”的手段去“破坏”您的代码,以此达到压缩的目的。这种手段是把双刃剑,如果您能掌控它的压缩规则,则代码便可以压缩至极小。

我们先来看看的Closure Compiler的威力。例如我有这样一段代码:

var Jscex = (function () {
/**
* @constructor
*/
var CodeGenerator = function () {
this.normalMode = false;
}
CodeGenerator.prototype.generate = function () {
alert("Hello World");
} function compile() {
return new CodeGenerator();
}; return { compile: compile };
})();

猜猜看,如果使用Closure Compiler的高级优化方式来压缩代码,会是什么情况呢?结果如下:

(function(){function a(){this.a=!1}return{compile:function(){return new a}}})();

目标代码很短,硬着头皮看看也无妨。首先,Jscex对象消失了,因为Closure Compiler认为其他地方并没有使用这个对象。其次,CodeGenerator的normalMode字段也被改名为a,因为这个名字更省空间。最后,generate方法也不见了,理由同第一项。您瞅瞅,这样的代码还能执行吗?这就是Closure Compiler激进的地方,它把输入文件作为一个完整的单元,并不会考虑对外的“接口”是否会变化。我读了Closure Compiler的文档,发现了它支持对源代码做标记。但是经过实验,这些标记似乎并不能影响编译后的结果,只是让编译器工作时多做一些“静态检查”。

当然,理论上说Closure Compiler提供了保持成员名称的机制,例如exports和extern。假设我要保持之前的Jscex对象,那么就必须这么做:

window["Jscex"] = (function () { ... })();

这样Closure Compiler生成的代码就会变成:

window.Jscex=(function(){ ... })();

为了“节省”空间,它把“索引”的访问方式又切换回“字段”的访问方式,何等蛋疼!此外,原本我以为Closure Compiler强迫我依赖浏览器环境,后来发现其实window也可以替换为其他名称,例如:

my_root["Jscex"] = (function () {
/**
* @constructor
*/
var CodeGenerator = function () {
this["normalMode"] = false;
}
CodeGenerator.prototype["generate"] = function () {
alert("Hello World");
} function compile() {
return new CodeGenerator();
}; return { compile: compile };
})();

虽然从理论上说,使用这种方式可以告诉Closure Compiler哪些成员名称是可以压缩的而哪些不行,但我真心难以接受这种四处使用“索引”的写法。不过,其实这对我造成的影响其实不大,因为我很少使用那种“面向对象”的方式来对外公开接口,我一般也就是用“对象”加上“方法”的形式,例如上面的Jscex.compile方法,至于内部类型,如CodeGenerator,就随Closure Compiler压缩去吧。

话又说回来,其实如果您是从头开始编写JavaScript代码,并且遵守一定规则,那么Closure Compiler的确可以把您的代码压缩地很小。甚至您可以多写一点调试用的代码,但在最终压缩后的代码中去掉它们。这里最基本的原则可以归纳为:将压缩后不需要的代码抽取为独立的方法,然后在预处理阶段去除这些方法的调用代码,于是Closure Compiler便会将这些方法的定义一并删除,节省了相当多的空间。

Jscex项目为例加以说明:Jscex的核心之一是根据AST生成JavaScript代码,在“调试”版本的实现中,我希望生成的代码能够美观、易读;而在“发布”版本中,我希望需要代码的体积越少越好。于是,对于某个表达式“是否需要添加括号”这样的场景,便需要详细斟酌了。我的策略是,在“调试”代码中,将判断是否需要增加括号的逻辑放置到needBracket方法中,然后编写这样的代码:

"dot": function (ast) {
function needBracket() { /* ... */ } var nb = needBracket();
if (nb) {
this._write("(")
._visit(ast[1])
._write(").")
._write(ast[2]);
} else {
this._visit(ast[1])
._write(".")
._write(ast[2]);
}
},

上面这个方法的作用是生成一个dot表达式的代码,其中定义了needBracket方法,我们可以在其中放置复杂而低效的逻辑,用来判断dot的左侧表达式是否需要添加括号。如果needBracket返回true,则生成括号,例如("abc" + "def").length;否则,便会生成更为简洁易读的代码,例如Jscex.Async.start,而不会是((Jscex).Async).start。但是在最终“发布”版本的代码中,nb变量被直接设置为true,于是Closure Compiler则会发现if的一个分支永远不会执行,则将其完全去除。在压缩后的代码中,以上方法只会是这样的:

dot:function(a){this.a("(").b(a[1]).a(").").a(a[2])},

可以看出,这段实现无论如何都会生成带括号的JavaScript代码,丑则丑矣,但对JavaScript引擎来说没有丝毫区别。目前jscex.js的压缩脚本其实是这样的:

# pre-processing for Closure Compiler
sed \
-e 's/var Jscex =/my_temp_root["Jscex"] =/' \
-e 's/\._writeLine(/._write(/g' \
-e 's/this\._write();//g' \
-e 's/\._write()//g' \
-e 's/this\._writeIndents();//g' \
-e 's/\._writeIndents()//g' \
-e 's/this\._indentLevel = 0;//g' \
-e 's/this\._indentLevel++;//g' \
-e 's/this\._indentLevel--;//g' \
-e 's/checkBindArgs([^;]*;//g' \
-e 's/needBracket([^;]*;/true;/g' \
-e 's/throwUnsupportedError();//g' \
-e 's/_log([^;]*;//g' \
../src/jscex.js > ../bin/jscex.tmp.js # use Closure Compiler to compress
java \
-jar ../tools/compiler.jar \
--js ../bin/jscex.tmp.js \
--js_output_file ../bin/jscex.tmp.min.js \
--compilation_level ADVANCED_OPTIMIZATIONS # post-processing
sed 's/my_temp_root\.Jscex=/var Jscex=/' ../bin/jscex.tmp.min.js > ../bin/jscex.min.js # remove temp files
rm ../bin/jscex.tmp*.js

我在使用Closure Compiler压缩代码之前,会先对脚本进行一下“预处理”,暂时为如下几项:

  • 为了避免Jscex对象丢失,先将var Jscex替换成my_temp_root["Jscex"],压缩之后再将其替换回来。
  • 将所有的writeLine方法调用替换成write,这样代码里便不会用到writeLine方法,Closure Compiler会去除该方法定义。
  • 去除空的write方法调用,这一般是由writeLine替换为write而引起的。
  • 去除与“缩进(indent)”相关的所有属性和方法,这样相关定义在压缩后也会一并消失。
  • 去除各种错误检查,如checkBindArgs,throwUnsupportedError方法的调用。
  • 去除日志输出,即_log方法调用,则_log方法本身也会消失不见。
  • 将needBracket方法调用直接替换为true,强制输出带括号的代码。

使用这样的做法,我们可以充分利用Closure Compiler在“高级”优化级别下的激进压缩方式,同时得到正确、高效、体积还很小的代码(补充:后来发现其实某些情况下可以使用定义常量的方式来简化预处理的步骤)。就拿jscex.js和jQuery Core进行比较(事先都去除注释及空白字符):

  • “简单”压缩的jQuery Core(安全压缩):减小30.83%体积(120.18KB => 83.13KB)。
  • “高级”压缩的jQuery Core(非安全压缩,不可用):减小37.91%体积(120.18KB => 74.62KB)。
  • “高级”压缩的Jscex.js(非安全压缩,可用):节省55.02%体积(12.14KB => 5.46KB)。

以上数据是从在线的Closure Compiler得出的结果,我也不知道为何效果不如本地明显。从本地压缩来看,jscex.js是25812字节,而jscex.min.js只有5585字节,有将近5倍的差距。

可惜的是,如果不是从代码编写及压缩一开始就考虑到Closure Compiler的诸多行为,我们只能使用“简单”的压缩方式来确保代码的正确性。如果要让一个现有的大段代码(例如jQuery)安全通过Closure Compiler的“高级”考验,这几乎是一件不可能的事情。

使用Google Closure Compiler全力压缩代码(转)的更多相关文章

  1. 使用Google Closure Compiler高级压缩Javascript代码注意的几个地方

    介绍 GCC(Google Closure Compiler)是由谷歌发布的Js代码压缩编译工具.它可以做到分析Js的代码,移除不需要的代码(dead code),并且去重写它,最后再进行压缩. 三种 ...

  2. Google Closure Compiler高级压缩混淆Javascript代码

    一.背景 前端开发中,特别是移动端,Javascript代码压缩已经成为上线必备条件. 如今主流的Js代码压缩工具主要有: 1)Uglify http://lisperator.net/uglifyj ...

  3. 使用Google Closure Compiler高级压缩Javascript代码

    背景 前端开发中,特别是移动端,Javascript代码压缩已经成为上线必备条件. 如今主流的Js代码压缩工具主要有: 1)Uglify http://lisperator.net/uglifyjs/ ...

  4. JavaScript代码压缩工具UglifyJS和Google Closure Compiler的基本用法

    网上搜索了,目前主流的Js代码压缩工具主要有Uglify.YUI Compressor.Google Closure Compiler,简单试用了UglifyJS 和Google Closure Co ...

  5. Google Closure Compiler 高级模式及更多思考(转)

    前言 Google Closure Compiler 是 Google Closure Tools 的一员,在 2009 年底被 Google 释出,早先,有 玉伯 的 Closure Compile ...

  6. bootstrap-datetimepicker在经过GC(Google Closure Compiler)压缩后无法使用的解决方案

    将压缩级别由simple改成whitespace 问题就是这样之后压缩后的文件大了很多 <?xml version="1.0"?> <project name=& ...

  7. 使用Google的Closure Compiler,在本机上压缩javascript

    2011-12-05 13:47:39   1.JAVA JDK下载地址: http://download.oracle.com/otn-pub/java/jdk/7u1-b08/jdk-7u1-wi ...

  8. Closure Compiler应用程序使用入门[译]

    Hello World示例 Closure Compiler应用程序是一个Java 命令行工具,用来对JavaScript代码进行压缩.优化和排错.按照下面的步骤,用一个简单的JavaScript程序 ...

  9. Closure Compiler(封闭编辑器), Closure Inspector, Closure Templates, 封闭图书馆(Closure Library) Google- 摘自网络

    谷歌日前宣布,将自己开发者使用的一系列工具对外开放.这些工具曾用来开发谷歌的主要产品,包括Gmail.谷歌文档(Google Docs)和谷歌地图(Google Maps). 第一个工具叫做Closu ...

随机推荐

  1. 使用JS对form的内容验证失败后阻止提交

    1.form的两个事件 submit,提交表单,如果直接调用该函数,则直接提交表单 onSubmit,提交按钮点击时先触发,然后触发submit事件.如果不加控制的话,默认返回true,因此表单总能提 ...

  2. Python接口测试之unittest框架(五)

    Test-driven development(TDD)开发模式在今天已经不是什么新奇的事了,它的开发思维是在开发一个产品功能的时候,先 编写好该功能的测试代码,在编写开发比如,比如要写二个数相除的函 ...

  3. python gdal库安装

    yum安装了postgis之后,会安装依赖gdal centos7.5的repo中gdal为1.11.4-3版本

  4. Maxscale安装-读写分离(1)

    前言 关于MySQL中间件的产品也很多,之前用过了360的Atlas.玩过MyCat.这边我选择 Maxscale的原因就是功能能满足需求,也看好他的未来发展. 其实有关于如何安装 Maxscale的 ...

  5. Archive log restore using RMAN for Logminer (http://www.dba-village.com/village/dvp_forum.OpenThread?ThreadIdA=26816)

    Subject: Archive log restore using RMAN for Logminer Author: Edwin Weele van der, Netherlands Date: ...

  6. [POJ3463] Sightseeing(次短路 Heap + Dijkstra)

    传送门 用dijkstra比较好,spfa可能有的重复 dis[x][2]:dis[x][0]表示起点到x的最短路.dis[x][1]表示起点到x的次短路: tot[x][2]:tot[x][0]表示 ...

  7. bzoj3545 Peaks 线段树合并

    离线乱搞... 也就是一个线段树合并没什么 #include<algorithm> #include<iostream> #include<cstring> #in ...

  8. set(集合)数据类型【七】

    一.概述:(类似于Java的Set,不允许有重复元素) 在Redis中,我们可以将Set类型看作为没有排序的字符集合,和List类型一样,我们也可以在该类型的数据值上执行添加.删除或判断某一元素是否存 ...

  9. 封装HttpURLConnection

    package com.pingyijinren.test; import java.io.BufferedReader; import java.io.InputStream; import jav ...

  10. 无权二分图最大匹配 HDU2063 匈牙利算法 || Hopcroft-Karp

    参考两篇比较好的博客 http://www.renfei.org/blog/bipartite-matching.html http://blog.csdn.net/thundermrbird/art ...