使用Google Closure Compiler全力压缩代码(转)
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全力压缩代码(转)的更多相关文章
- 使用Google Closure Compiler高级压缩Javascript代码注意的几个地方
介绍 GCC(Google Closure Compiler)是由谷歌发布的Js代码压缩编译工具.它可以做到分析Js的代码,移除不需要的代码(dead code),并且去重写它,最后再进行压缩. 三种 ...
- Google Closure Compiler高级压缩混淆Javascript代码
一.背景 前端开发中,特别是移动端,Javascript代码压缩已经成为上线必备条件. 如今主流的Js代码压缩工具主要有: 1)Uglify http://lisperator.net/uglifyj ...
- 使用Google Closure Compiler高级压缩Javascript代码
背景 前端开发中,特别是移动端,Javascript代码压缩已经成为上线必备条件. 如今主流的Js代码压缩工具主要有: 1)Uglify http://lisperator.net/uglifyjs/ ...
- JavaScript代码压缩工具UglifyJS和Google Closure Compiler的基本用法
网上搜索了,目前主流的Js代码压缩工具主要有Uglify.YUI Compressor.Google Closure Compiler,简单试用了UglifyJS 和Google Closure Co ...
- Google Closure Compiler 高级模式及更多思考(转)
前言 Google Closure Compiler 是 Google Closure Tools 的一员,在 2009 年底被 Google 释出,早先,有 玉伯 的 Closure Compile ...
- bootstrap-datetimepicker在经过GC(Google Closure Compiler)压缩后无法使用的解决方案
将压缩级别由simple改成whitespace 问题就是这样之后压缩后的文件大了很多 <?xml version="1.0"?> <project name=& ...
- 使用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 ...
- Closure Compiler应用程序使用入门[译]
Hello World示例 Closure Compiler应用程序是一个Java 命令行工具,用来对JavaScript代码进行压缩.优化和排错.按照下面的步骤,用一个简单的JavaScript程序 ...
- Closure Compiler(封闭编辑器), Closure Inspector, Closure Templates, 封闭图书馆(Closure Library) Google- 摘自网络
谷歌日前宣布,将自己开发者使用的一系列工具对外开放.这些工具曾用来开发谷歌的主要产品,包括Gmail.谷歌文档(Google Docs)和谷歌地图(Google Maps). 第一个工具叫做Closu ...
随机推荐
- (十二)python3 迭代器
可以直接作用于 for 循环的对象统称为可迭代对象: Iterable .一类是集合数据类型,如 list . tuple . dict . set . str 等,一类是 generator ,包括 ...
- Python随笔day01
环境变量的配置: 配置Python的安装目录到path变量中,例如C:\Python37 标识符的命名规则: 变量名只能以数字,字母,下划线组成. 不能以数字开头,保留字不能被使用. 建议使用下划线分 ...
- nyoj 96 n-1位数(处理前导 0 的情况)(string)
n-1位数 时间限制:3000 ms | 内存限制:65535 KB 难度:1 描述 已知w是一个大于10但不大于1000000的无符号整数,若w是n(n≥2)位的整数,则 ...
- HDU 2897 经典巴什博弈
从n个石子中每次取p~q个,求先手能否获胜 可以先列举一部分数据,然后观察可得总是在p+q中循环,所以只要用n对p+q取模就好了 #include <cstdio> #include &l ...
- [luoguP1056] 排座椅(sort + 模拟)
传送门 nc题,一直sort就过了 代码 #include <cstdio> #include <iostream> #include <algorithm> #d ...
- MySQL:视图、触发器、存储过程、事务
视图: 视图,虚拟表 创建虚拟表: # 语法: # create view 虚拟表名称 as 虚拟表; create view course_and_teacher as select * from ...
- java中文乱码问题的处理方式
URL访问java时. 注意: URL: 编码二次 URLEncoder.encode(URLEncoder.encode(searchKey, "utf-8"),"ut ...
- 中国福利彩票,牛B,开奖和数据传输有什么关系?
昨天,由中国教育电视台直播的福利彩票“双色球”15011期开奖,在没有事先预告的情况下突然取消.晚上11点40分左右,中国福利彩票发行管理中心唯一指定网络信息发布媒体——中彩网官方微博出乎意料地在网上 ...
- CSU - 1115 最短的名字(字典树模板题)
Description 在一个奇怪的村子中,很多人的名字都很长,比如aaaaa, bbb and abababab. 名字这么长,叫全名显然起来很不方便.所以村民之间一般只叫名字的前缀.比如叫'aaa ...
- [bzoj2443][Usaco2011 Open]奇数度数_树形dp_生成树_并查集
奇数度数 bzoj-2443 Usaco-2011 Open 题目大意:给定一个n个点m条便有向图,问是否有一种选出一些边的方式使得所有点的度数都是奇数. 注释:$1\le n \le 5\cdot ...