LeoYuan / leoyuan.github.io

This is my blog repo.
http://leoyuan.github.io
17 stars 2 forks source link

UglifyJS AST(Abstract Syntax Tree)之浅要分析 #2

Open LeoYuan opened 11 years ago

LeoYuan commented 11 years ago

之前一直使用Closure.jar来压缩JS代码,不过似乎在Node上大家更习惯用UglifyJS来完成压缩、扰码的工作。 何为UglifyJS,官方介绍是

UglifyJS is a JavaScript compressor/minifier written in JavaScript. It also contains tools that allow one to automate working with JavaScript code:

  • A parser which produces an abstract syntax tree (AST) from JavaScript code.
  • A code generator which outputs JavaScript code from an AST, also providing the option to get a source map.
  • A compressor (optimizer) — it uses the transformer API to optimize an AST into a smaller one.
  • A mangler — reduce names of local variables to (usually) single-letters.
  • A scope analyzer, which is a tool that augments the AST with information about where variables are defined/referenced etc.
  • A tree walker — a simple API allowing you to do something on every node in the AST.
  • A tree transformer — another API intended to transform the tree.

一般使用UglifyJS来压缩代码的似乎不关心整个minify的流程到底是怎样的? 直接使用命令行或者如下代码来完成压缩

var result = UglifyJS.minify("/path/to/file.js");
console.log(result.code); // minified output
// if you need to pass code instead of file name
var result = UglifyJS.minify("var b = function () {};", {fromString: true});

不过对于高级玩家,还有另一番玩法。UglifyJS提供了parse功能,主要作用就是将一段代码解析成一颗语法树ast,另外UglifyJS给你提供了

  1. ast.walk(new UglifyJS.TreeWalker())
  2. ast.transform(new UglifyJS.TreeTransformer())

两个函数,这样一来,要对这颗树遍历一遍还是转换一遍,都随你了。(亲,不要太暴力哦~)

完整介绍可以猛击上边的两个链接,另外下边列出了在研读cmd-util/lib/ast.js时用到的几个点,

  1. 如何只遍历ast第一层节点?只需要在walk时返回true就行
var code = "function foo() {\n\
  function x(){}\n\
  function y(){}\n\
}\n\
function bar() {}";
var toplevel = UglifyJS.parse(code);
var walker = new UglifyJS.TreeWalker(function(node){
    if (node instanceof UglifyJS.AST_Defun) {
        // string_template is a cute little function that UglifyJS uses for warnings
        console.log(UglifyJS.string_template("Found function {name} at {line},{col}", {
            name: node.name.name,
            line: node.start.line,
            col: node.start.col
        }));
        return true; //  That's it!
    }
});
toplevel.walk(walker);
  1. 在walk函数参数里,node的类型是AST_Node(所有类型的基类,实际上node的类型通常是AST_Call/AST_String等类型),判断方式如下
if (node instanceof UglifyJS.AST_Call) {}  // 判断该node是不是一个函数调用
  1. 如何判断node的开始行和列?详见UglifyJS ast
console.log('row ->', node.start.line);
console.log('column ->', node.start.col);
  1. 如何判断node是不是一个函数调用,并且是define或者require开头,如何获知definerequire的参数?
if (node instanceof UglifyJS.AST_Call && node.start.value === 'define') {
  // node.args                        // define函数的参数
}

注:以上介绍还属皮毛知识,详细内容还请移步官网一探究竟UglifyJS

LeoYuan commented 11 years ago

@h63542 @varfunction 才知道UglifyJS还有语法树,惭愧惭愧。