“This is the 28th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

preface

As we all know, the code will be compressed before going online, in order to reduce the volume of source code, improve the loading speed, so code compression is a very important step, so today I would like to introduce the principle of uglifyJS and compression rules.

AST (Abstract Syntax Tree)

To understand the compression principle of JS, you need to understand AST first. Because the first step in compression is to turn the code into an AST.

Abstract Syntax Tree: AST (Abstract Syntax Tree) is the Tree representation of the Abstract Syntax structure of the source code, especially the source code of the programming language. Each node in the tree represents a structure in the source code. The syntax is “abstract” because it does not represent every detail that occurs in real grammar.

Such as:

You can see that the AST is the source code abstracted into a tree representation based on its syntactical structure, leaving out some details (for example: parentheses don’t generate nodes). Abstract syntax trees have many applications in computer science, such as compilers, ides, compressed code, formatted code, and so on.

The compression principle

The compression code consists of three steps

  1. Convert code to AST
  2. The AST is optimized to produce a smaller AST
  3. Convert the newly generated AST into code

The rules

First let’s install uglifyJS. NPM isntall uglify – js. Then create a new file in the root directory, demo.js, and write the test code in it. Then run uglifyjs demo.js –mangle-props keep_quoted-c -m on the command line to compress. (Version 3.14.3 of Uglip-JS used here)

1. Expression compression

1.1 Expression prediction

Replace the precomputed expression with its computed result, comparing the size of the original expression with that of the generated result, taking the shorter one.

// -------------- before compression -------------
var expr1 = 1 + 1;
var expr2 = 1 / 3;

// -------------- after compression -------------
var expr1=2,expr2=1/3;
Copy the code

The reason why expr1 is only calculated and expr2 is not calculated is because the calculated value 0.33333333333 is longer than 1/3, so it is not expected to be calculated.

1.2 Optimize true and false

This normally changes: true to! 0, saving 2 characters false becomes! One might wonder: why not just change true to 1 and false to 0? Because this can turn a Boolean type into a numeric type and cause runtime chaos by participating in certain operations. But are there any special cases where true becomes 1 and false becomes 0? The answer is: re-engagement == and! Is equal to the operation.

// -------------- before compression -------------
var expr1 = true;
var expr2 = false;
true == A;
false == A;

// -------------- after compression -------------
var expr1=!0,expr2=!1; A,A;Copy the code

1.3 according to the && | | short circuit feature compression expression

// -------------- before compression -------------
true && A();
false && A();
true || A();
false || A();

// -------------- after compression -------------
A(),A();  // The effect is significant
Copy the code

2. Operator shortening

Shorten the assignment expression. For an assignment expression such as a = a + b, you can shorten it to a += b

It’s a bit convoluted here, but it’s easy to understand the details of this rule when you think about it:

  1. It must be an = assignment statement
  2. The left side of the = sign can only be a variable, not an expression, etc
  3. = number must be on the right side expression for binary operation, and symbols are for array [‘ + ‘, ‘-‘, ‘/’, ‘*’, ‘%’, ‘> >’, ‘< <‘, ‘> > >’, ‘|’, ‘^’, ‘&’] in the element
  4. The first operand of the binary expression to the right of the = sign must be identical to the variable to the left of the = sign
// -------------- before compression -------------
a = a + b;
c = c >>> d;
a = b + c;

// -------------- after compression -------------
a+=b,c>>>=d,a=b+c;
Copy the code

3. Eliminate useless declarations/references

3.1 Remove repeated indicative strings

// -------------- before compression -------------
function A(){
  "use strict";
  function B(){
    "use strict"; }}// -------------- after compression -------------
function A(){}
Copy the code

3.2 Remove unused function parameters

// -------------- before compression -------------
function A(a, b, c){
  b++;
}

// -------------- after compression -------------
function A(a, b){
  b++;
}
Copy the code

3.3 Removing function names that are redundant in function expressions

If a function expression is called recursively without a reference to its name, the function name can be removed to make it anonymous.

// -------------- before compression -------------
(function A(){ A(); }) (); (function B(){ c++; }) ();// -------------- after compression -------------
(function A(){ A(); }) (); (function(){ c++; }) ();Copy the code

4. While compression

Remove a while loop that never executes: while(false){}

While (true) into for (;;) Can be shortened by 4 characters

// -------------- before compression -------------
while(false){
  A();
  B();
}
while(true){
  C();
  D();
}

// -------------- after compression -------------
// While (false) is ignored by compression
for(;;) { C(); D(); }Copy the code

5.Conditional expression

Conditional expressions that use ternary operators? For example cond? yes() : no()

5.1 If cond is preceded by a non-operation, consider removing the non-operation and reversing the positions of yes() and no()

// -------------- before compression -------------! cond ? yes() : no();// -------------- after compression -------------(cond? no:yes)();Copy the code

5.2 If cond is a constant value, it can be shortened directly to yes() or no();

// -------------- before compression -------------
true ? yes() : no();
false ? yes() : no();

// -------------- after compression -------------
yes(),no();
Copy the code

6.Block compression

6.1 Consecutive expression statements can be combined into a comma expression

// -------------- before compression -------------
function A(){
  B();
  C();
  d = 1;
}

// -------------- after compression -------------
function A(){B(),C(),d=1}
Copy the code

6.2 Multiple VAR declarations can be compressed into one VAR declaration

6.3 Non-variable declaration and non-function declaration statements after return can be removed

// -------------- before compression -------------
function A(){
  return false;
  var a = 1;
  function B(){}
  expr += 1;
  a = 3;
}

// -------------- after compression -------------
function A(){return!1}
Copy the code

6.4 Merge a return statement at the end of a block and multiple expression statements before it.

This rule doesn’t seem to shrink the resulting code.

// -------------- before compression -------------
function A(){
  B();
  C();
  return D();
}

// -------------- after compression -------------
function A(){
  return B(), C(), D();
}
Copy the code

7.IF branch optimization

7.1 Get rid of useless if/else branches

If the if condition is a predictable constant result, then you can ignore the useless if/else branch

// -------------- before compression -------------
if (true){
  A();
}else{
  B();
}
if (false){
  C();
}else{
  D();
}

// -------------- after compression -------------
A(),D();
Copy the code

7.2 Remove empty if/else branches

If the if branch is empty, the condition is not, the else branch is reversed into the if branch

// -------------- before compression -------------
if (A){
  B();
}else{}if (C){
}else{
  D();
}

// -------------- after compression -------------
if (A){
  B();
}
if(! C){ D(); }Copy the code

7.3 Turning into an Expression

// -------------- before compression -------------
if(! c){ A(); }else{
  B();
}

// -------------- after compression -------------(c? B:A)();Copy the code

7.4 If there is only one if statement in the if block and the else block is empty, you can merge the two ifs

// -------------- before compression -------------
if (A){
  if(B){ C(); }}else{}// -------------- after compression -------------
if (A && B){
  C();
}
Copy the code

7.5 If the last statement of the if is an exit control statement, we can refer to the else block outside the else, and then remove the else

// -------------- before compression -------------
if (A){
  B();
  return;
}else{
  C();
}

// -------------- after compression -------------
if (A){
  B();
  return;
}
C();
Copy the code

7.6 If both if and else contain only one return statement, you can combine the two returns

// -------------- before compression -------------
function name() {
  if (A){
    return B();
  }else{
    returnC(); }}// -------------- after compression -------------
function name(){return(A? B:C)()}Copy the code

7.7 If there is only one expression statement in both if and else, it can be converted into a conditional expression, and then it can be further compressed according to Rules 5.1 and 5.2

7.8 if the if/else one of the pieces is empty, another block only one statement, you can turn into | | or && expression.

// -------------- before compression -------------
if (A){
  B();
}else{}if (C){
}else{
  D();
}

// -------------- after compression -------------
A&&B(),C||D();
Copy the code