These pages are old. They apply to UglifyJS v2. Version 3 has evolved a lot in the mean time to support most of ES6. Please check the documentation in the official repository for up-to-date information. Big thanks to all contributors, especially to Alex Lam S.L., who has maintained this project for years!
UglifyJS — TreeTransformer
The tree transformer is a special case of a tree walker. In fact it even inherits from TreeWalker and you can use the same methods, but initialization and visitor protocol are a bit different.
SYNOPSIS
function before(node, descend) { ... };
function after(node) { ... };
var tt = new UglifyJS.TreeTransformer(before, after);
var new_ast = ast.transform(tt);
Both visitors are optional, but you should pass at least one of them (otherwise it'll just walk the tree pointlessly).
The “before” visitor
For each node in the tree, your before visitor is called before the children of that node are walked. It receives 2 arguments, the current node and the “descend” function.
- If your before visitor returns
undefined
:- if an after visitor exists, then the tree walker will clone the current node, descend into it (process children nodes) and thereafter call the after visitor, giving it a reference to the new node (the clone)
- if there is no after visitor, then it will just descend the current node without cloning it.
- If your before visitor returns a value, then the current node will be replaced by that value in the tree (destructive change!). The children of the current node will not be processed in this case (if you return a new node in the “before” visitor, it is expected that you did any processing for the child nodes yourself). Also, the “after” handler will not be called.
The “after” visitor
If you provide an after visitor, it will be called on any node
for which the before visitor returned undefined
,
after the node has been cloned and its children transformed. If your
after visitor returns a value !== undefined
, then
that value will replace the current node in the tree (that's
non-destructive, since the node was cloned first).
Examples
Cloning a tree
A quick first example is cloning a tree. The UglifyJS compressor is destructive—it will modify the existing tree—so if you'd like to use the compressor but to also keep the original AST, you should make a clone of it. Here's how:
// sample AST
var ast = UglifyJS.parse("a = 1 + 2");
// this is the transformer
var deep_clone = new UglifyJS.TreeTransformer(function(node, descend){
node = node.clone();
// the descend function expects two arguments:
// the node to dive into, and the tree walker
// `this` here is the tree walker (=== deep_clone).
// by descending into the *cloned* node, we keep the original intact
descend(node, this);
return node;
});
// in ast2 we'll get a deep copy of ast
var ast2 = ast.transform(deep_clone);
// let's change AST now:
ast.body[0].body.left.name = "CHANGED"; // CHANGED = 1 + 2
console.log(ast.print_to_string({ beautify: true }));
console.log(ast2.print_to_string({ beautify: true }));
A quick note, because the transformer itself will clone nodes if an “after” visitor is present, we could also clone a whole tree with the following (might even be a bit more efficient):
ast.transform(new UglifyJS.TreeTransformer(null, function(){}));
Consolidating strings
Let's say we'd like to collect all strings from a piece of code into a
single var
declaration, and use variables to access those
strings. This should reduce size of code using a lot of strings, but only
before Gzip (Gzip deals a lot better with string repetition than we can,
so that's why UglifyJS doesn't do a similar transformation).
// in this hash we will map string to a variable name
var strings = {};
// here's the transformer:
var consolidate = new UglifyJS.TreeTransformer(null, function(node){
if (node instanceof UglifyJS.AST_Toplevel) {
// since we get here after the toplevel node was processed,
// that means at the end, we'll just create the var definition,
// or better yet, "const", and insert it as the first statement.
var defs = new UglifyJS.AST_Const({
definitions: Object.keys(strings).map(function(key){
var x = strings[key];
return new UglifyJS.AST_VarDef({
name : new UglifyJS.AST_SymbolConst({ name: x.name }),
value : x.node, // the original AST_String
});
})
});
node.body.unshift(defs);
return node;
}
if (node instanceof UglifyJS.AST_String) {
// when we encounter a string, we give it an unique
// variable name (see the getStringName function below)
// and return a symbol reference instead.
return new UglifyJS.AST_SymbolRef({
start : node.start,
end : node.end,
name : getStringName(node).name,
});
}
});
var count = 0;
function getStringName(node) {
var str = node.getValue(); // node is AST_String
if (strings.hasOwnProperty(str)) return strings[str];
var name = "_" + (++count);
return strings[str] = { name: name, node: node };
}
// now let's try it.
var ast = UglifyJS.parse(function foo() {
console.log("This is a string");
console.log("Another string");
console.log("Now repeat");
var x = "This is a string", y = "Another string";
var x = x + y + "Now repeat";
alert("Now repeat".length);
alert("Another string".length);
alert("This is a string".length);
}.toString());
// transform and print
var ast2 = ast.transform(consolidate);
console.log(ast2.print_to_string({ beautify: true }));
// also, the change is non-destructive; the original AST remains the same:
console.log("Original:");
console.log(ast.print_to_string({ beautify: true }));
By not repeating the strings, this optimization could save quite a few bytes on code containing a lot of strings. However, as I said, it's not worthy because it ruins Gzip's own optimizations and will actually produce bigger code after Gzip.
UglifyJS
JS compressor of world fame.
Open demo- Home
- Parser
- Code generator
- Compressor
- Mangler
- The AST structure
- SpiderMonkey AST
- Scope analysis
- AST walker
- AST transformer
- UglifyJS on Github
Latest blog entries tagged "uglifyjs"
- Using UglifyJS for code refactoring
- Should you switch to UglifyJS2?
- UglifyJS online demo
- UglifyJS — why not switching to SpiderMonkey AST
/** May the source-map be with you! **/