https://evmar.github.io/js-min-bench/
What I discovered there is that the best minification is one where you just produce an empty file -- it's 0 bytes of output and extremely fast.
And if you say "wait, I have have the additional requirement that you didn't break the code in the original file, obviously!", you then must define what exactly "break" means in that requirement-- for example, it could be that some user requires that someFunction().toString().charAt(10) == 'a', in which case even removing whitespace counts as breakage.
You might think the above is just being overly pedantic or pathological, but it demonstrates a principle that actually ends up mattering, which is that you can't evaluate a minifier unless you define what a successful minification even means. Particularly in the case of more advanced transformation.
I went into it more in this post:
http://neugierig.org/software/blog/2019/04/js-minifiers.html