时隔3个半月的日志——这段时间新入手了新车,折腾车去了,导致近期的技术积累都没有及时沉淀下来。

回到技术上来,最近在做SVN和迁移代码优化时,决定放弃之前用了N久的JS压缩工具,改用比较流行的YUICompressor,主要原因是:

1、YUICompressor可以进一步对代码进行优化,包括省略不必要的分号和添加必要的分号等,而之前的压缩工具不会管这些,没分号就保留换行,或者强制压缩就会脚本报错;
2、YUICompressor强大的混淆功能,进一步压缩代码量,即提高被分析的门槛,也减少了代码的体积。要知道我们有一部分代码是通过IDC网络来输出的;
3、基于java,开源,方便修改,而且在Mac上也可以用。

试用了一下,发现YUICompressor有个不和我心意的地方:常用的 if (xxx) { } else if (ooo) { } 会被压缩为 if(xxx){}else{if(ooo){}},看上去很不习惯。在强迫症的趋势下,决定改之!

之前看过Rhino的源代码并参考做了一套基于C#的JS词法和语法分析,而YUICompressor覆盖重写了Rhino中的部分类,因此代码阅读上基本没啥门槛,当然不想看完成的逻辑,最直接的就是调用堆栈走查加代码搜索,我们要找的是if语句,自然搜索 Token.IF 了。在 org.mozilla.javascript.Parser 类(YUICompressor重写的,不是Rhino原生的)statementHelper方法,找到了对if语句的解析和代码队列的构建:  

            decompiler.addToken(Token.IF);
            int lineno = ts.getLineno();
            Node cond = condition();
            decompiler.addEOL(Token.LC);
            Node ifTrue = statement();
            Node ifFalse = null;
            if (matchToken(Token.ELSE)) {
                decompiler.addToken(Token.RC);
                decompiler.addToken(Token.ELSE);
                decompiler.addEOL(Token.LC);
                ifFalse = statement();
            }
            decompiler.addEOL(Token.RC);

其中,decompiler可以理解为Token队列。可以看出,if语句总共有以下队列:

关键字if——判断条件condition——左大括号{——条件成立语句statement——
如果下一个关键字为else,则——右大括号}——关键字else——左大括号{——条件不成立语句statement——
无论是否有else——右大括号}

可以看到,这里并没有针对else if做特殊处理,而是将else if中的if作为else中的statement。修改的思路也很容易想到:判断到else之后再继续判断下一个关键字是否为if,如果为是则按照else if来处理,此时else后面的statement不再使用大括号包围,而是用空格分隔else与statement,而下一条语句已经知道是if,因此就变成了我们常见的else if写法。基于递归的处理,如果其后还有else if,则依然会走这里的方法,与之前的处理逻辑一致。

思路有了,开始动手,首先要记录这段if是否有else if,此外还要增加一个“空格”Token,因此我们来到Token的定义位置 org.mozilla.javascript.Token,在 LAST_TOKEN 之前增加一个常量 SPACE,因为 LAST_TOKEN 之前已经没有空的数值,SPACE 需要取值为原来的 LAST_TOKEN,而 LAST_TOKEN 则在原有基础上加一。LAST_TOKEN 用于判断合法的Token值的范围,因此一定要小于它。此外,在 org.mozilla.javascript.Token.name 方法中还要增加Token对应的文字类型,按前面已有的Token处理就好啦。

Token的问题解决之后就是添加判断逻辑了,在 Parser.statementHelper 中修改 case Token.IF 段,其中取下一个Token的方法为 peekToken(),该方法不会改变当前Token指针的位置,因此无需做指针回退的处理。

            decompiler.addToken(Token.IF);
            int lineno = ts.getLineno();
            Node cond = condition();
            decompiler.addEOL(Token.LC);
            Node ifTrue = statement();
            Node ifFalse = null;
            boolean elseIf = false;
            if (matchToken(Token.ELSE)) {
                decompiler.addToken(Token.RC);
                decompiler.addToken(Token.ELSE);
                if (peekToken() == Token.IF) {
                    elseIf = true;
                    decompiler.addToken(Token.SPACE);
                } else {
                    decompiler.addEOL(Token.LC);
                }
                ifFalse = statement();
            }
            if (!elseIf) {
                decompiler.addEOL(Token.RC);
            }

以上代码中加粗加斜的文字为新增的代码,主要为了保证上下文的一致。

最后要在Token队列转换为实际代码时处理 Token.SPACE,搜索代码,找到了 com.yahoo.platform.yui.compressor.JavaScriptCompressor 的静态构造函数,在最后将 Token.SPACE 对应的空格字符添加到集合中。接下来就是构建测试,通过~

posted on 2012-06-15 16:00  费神  阅读(425)  评论(0编辑  收藏  举报