terser icon indicating copy to clipboard operation
terser copied to clipboard

Make private class methods inlineable

Open TimvdLippe opened this issue 2 years ago • 3 comments

Bug report or Feature request?

Feature

Version (complete output of terser -V or specific git commit) 5.10.0

Complete CLI command or minify() options used

{
  module: true,
  compress: {},
  mangle: {},
  output: {},
  parse: {},
  rename: {},
}

terser input

export class Minified {
  someField;
  constructor() {
    this.someField = (() => {
      return 42;
    })();
  }
}

export class NotMinified {
  someField;
  constructor() {
    this.someField = this.#privateMethod();
  }
  #privateMethod() {
    return 42;
  }
}

terser output or error

export class Minified{someField;constructor(){this.someField=42}}export class NotMinified{someField;constructor(){this.someField=this.#e()}#e(){return 42}}

Expected result

export class Minified{someField;constructor(){this.someField=42}}export class NotMinified{someField;constructor(){this.someField=42}}

Since there is only 1 call to the private method, it can be inlined, saving a bunch of indirection. Further optimizations can then be applied as well, completely removing the call and setting it to the value.

This is also helpful with event listeners:

export class NotMinified {
  #someBoundedListener = this.#someListener.bind(this);
  #someListener() {
    console.log('listener called');
  }
  constructor(div) {
    div.addEventListener('click', this.#someBoundedListener);
  }
}

Which would then be optimized to

export class NotMinified {
  constructor(div) {
    div.addEventListener('click', () => {console.log('listener called')});
  }
}

TimvdLippe avatar Dec 15 '21 11:12 TimvdLippe

In addition, I think this:

class X {
  constructor(){
    if(false){
      this.#a();
    }
  }
  #a(){
  }
}

var x = new X();
var y = new X();

console.log({x,y})

Could be optimized to:

class o{constructor(){0}}var c=new o,n=new o;console.log({x:c,y:n});

Instead of:

class o{constructor(){0}#o(){}}var c=new o,n=new o;console.log({x:c,y:n});

VictorQueiroz avatar Dec 22 '21 17:12 VictorQueiroz

@jridgewell Could you maybe give an indication how difficult this feature would be? I am interested in contributing this if it isn't too difficult (hopefully!).

TimvdLippe avatar Jan 12 '22 22:01 TimvdLippe

This is pretty difficult @TimvdLippe. You would need to create a new compression option, that calls a method that keeps track of which private methods exist in a class (and nested classes), and how many times they're used.

Then you'd need to traverse the class looking for references to the single-use ones to be replaced with the original, and for the original for removal. Interestingly, you would almost accidentally implement inlining for values as well.

Basically you wouldn't be using any of the facilities for scope, variable reference tracking, etc, that exist in the rest of Terser, because private properties aren't kept track of at all.

fabiosantoscode avatar Jan 20 '22 21:01 fabiosantoscode