Hello World
Spiga

通过定义常量控制Closure Compiler的行为

2011-04-11 01:12 by 老赵, 2940 visits

上一篇文章里我提到,在进行Closure Compiler压缩之前可以对代码进行一些预处理,这样可以得到更好的效果。在回复中有朋友提到可以使用一些Annotation(标记),例如加上@export,然后使用--generate_exports,便可以保留需要的那些变量名。不过经过实验还是没有得到预期的效果,所以使用标记来“指导”高级压缩行为依旧是一个不太可行的做法。不过有个标记与我的设想一直,那就是使用@define来“定义一个常量”,然后在编译(压缩)时对其进行覆盖。这为一些压缩需求提供一种更直接的控制方式。

还是先谈一下@export,当时看了文档的说明,我一度也觉得这能满足我的要求(即保留变量名不被压缩):

$ java -jar compiler.jar --help
...
--generate_exports         : Generates export code for those marked
                              with @export
...

但事实上,实验的结果令人大失所望。例如下面的代码:

/**
 * @export
 */
var Jscex = (function () {
    /**
     * @constructor
     */
    var CodeGenerator = function () {
        /**
         * @export
         */
        this.normalMode = false;
    }
    /**
     * @export
     */
    CodeGenerator.prototype.generate = function () {
        alert(this.normalMode);
    }

    function compile() { return new CodeGenerator(); };

    return { compile: compile };

})();

首先,Closure Compiler只允许在Global范围内使用@export标记,因此以上几个@export只有Jscex上的那个才能保留下来。其次,当去除其他@export标记之后,Closure Compiler生成的结果却是:

var b=function(){...};goog.a("Jscex",b);

Closure Compiler很神奇地为脚本添加了Closure Libraries这个依赖,这显然无法令人接受。

失望之余,我观察并尝试了其他一些标记,发现@define还是不错的。就拿前文来说,我希望Jscex能够在Debug模式下避免生成不必要的括号,原来的做法是编写这样的代码:

"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]);
    }
},

然后在使用Closure Compiler压缩之前将needBracket方法的调用替换成true,这样Closure Compiler就会发现if的第二个分支永远不会执行,因此直接从最后的结果中去除了;更近一步,由于needBracket方法没有其他地方调用过,因此它也会被完整地删除,这就得到了体积更小的代码。不过这个方法多少有点trick的意味,它需要代码编写者与压缩脚本之间进行约定,其实我们可以使用一种更直接的方式,那就是使用@define标记来定义一个常量:

/** @define {boolean} */
var JSCEX_DEBUG = true;
...

"dot": function (ast) {
    function needBracket() { /* ... */ }

    var nb = (!JSCEX_DEBUG) || needBracket();
    if (nb) {
        this._write("(")
            ._visit(ast[1])
            ._write(").")
            ._write(ast[2]);
    } else {
        this._visit(ast[1])
            ._write(".")
            ._write(ast[2]);
    }
},

在代码中JSCEX_DEBU为true,因此nb的值会是needBracket方法调用后的结果。但如果我们在使用Closure Compiler压缩代码添加--define参数,便可以将JSCEX_DEBUG的值修改为false:

java \
    -jar compiler.jar \
    --js jscex.js \
    --js_output_file jscex.min.js \
    --define 'JSCEX_DEBUG=false' \
    --compilation_level ADVANCED_OPTIMIZATIONS

于是乎——就不多做解释了。Closure Compiler提供的标记有很多,但实验下来行为大都和想象中不符,这倒也是件很奇怪的事情。可能还是要去邮件列表之类的地方问问吧。

广告时间:第四届nBazaar技术交流会将于2011年4月23日举行。根据部分用户反馈,第四届交流会的形式将略作改变:除了三场演讲之外,本次活动设有嘉宾互动环节,您将有机会和嘉宾就某些话题进行探讨。我们将在会前收集部分话题,也希望大家踊跃提问,具体方式详见 http://nbazaar.org/

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

11 条回复

  1. 老赵
    admin
    链接

    老赵 2011-04-11 10:48:51

    居然过了那么久还可以抢到沙发啊。

  2. Mien++
    222.128.67.*
    链接

    Mien++ 2011-04-11 12:50:16

    阅读有价值的个人博客一般都会通过RSS的,很少人会记住你的域名来访问,更不用说留言了.

  3. starlightliu
    210.13.114.*
    链接

    starlightliu 2011-04-11 13:07:43

    通常老赵的文章会点开看,因为评论也很重要~

  4. 老赵
    admin
    链接

    老赵 2011-04-11 13:10:17

    @Mien++

    通过RSS也能点进来留言哪,哈哈。

  5. baihmpgy
    117.22.228.*
    链接

    baihmpgy 2011-04-12 11:38:52

    呵呵,我就是使用RSS的人,留言来了,:)

  6. winter
    114.80.133.*
    链接

    winter 2011-04-12 23:53:46

    看赵老研究CC真开心,坐享其成ing

  7. @waynebaby
    210.22.108.*
    链接

    @waynebaby 2011-04-13 15:41:31

    js 话题插不上嘴 fufufufufu

  8. 老赵
    admin
    链接

    老赵 2011-04-13 22:24:08

    @winter

    越研究觉得越恶心啊,因为高级功能大都不安全……

  9. 桂林
    222.66.40.*
    链接

    桂林 2011-04-26 12:54:35

    文档没看好吧, 不要使用externs代替exports

    //这样可以exports generate 方法

    CodeGenerator.prototype['generate'] = function(){....}
    

    //这样可以exports CodeGenerator 类

    window['CodeGenerator'] = CodeGenerator
    

    我测试了一下,closure-compiler 完胜 uglifyjs dead code 移除测试

    另外有关 uglifyjs 压缩后比 closure-compiler 更小的说法, closure-compiler的目标是更快的执行速度和更小的gzipped尺寸。

    为什么inline所有代码,让文件变大了

  10. 老赵
    admin
    链接

    老赵 2011-04-26 20:52:56

    @桂林

    我看清文档里,你没看清我的文章。其实我文章里都写了:我不想到处用字符串的方式,这不是JS编程,这是Closure Compiler编程──再者说,凭什么要我依赖window对象?谁说我的JavaScript一定是在浏览器里执行的?

    至于UglifyJS的压缩,的确没有去删除Dead Code,这点不如Closure Compiler。

  11. KK小写
    114.247.50.*
    链接

    KK小写 2013-02-28 12:13:36

    老赵,想问你一下。使用closure compiler 压缩,他会将所有的字符串,函数名,对象类型给单独提取出来,这样就可以更狠的压缩。例如,压缩过后会变成这样。

    //对象类型
    var ba = void 0,
         j = !0,
         k = null,
         l = !1,
         ca = encodeURIComponent,
         m = window,
    
    //js中所有用到的字符串
    var Za = "appendChild",
         $a = "deviceXDPI",
         o = "trigger",
         q = "bindTo",
         ab = "shift",
         bb = "clearTimeout",
         cb = "exec",
         db = "fromLatLngToPoint",
         s = "width",
    
    //函数名
    var ma = function(a, b) {
            return a.onload = b
        },
        na = function(a, b) {
            return a.center_changed = b
        },
    

    请问这是怎么做到的呢?用什么命令来实现?

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我