JavaScript Minification Benchmarks
github.com
github.com
The reason I don't really like doing it is that it obfuscates the code. Happened to discover a bug in production (which may not be easy to repro)? A lot easier to examine that if your files aren't minified. It also allows users to examine the scripts if they encounter bugs, which I've done on a number of occasions. It also simplifies the entire build process.
I know about source maps, but many people don't ship them and it all seems quite complicated for very little savings.
When I do minify, I usually just have a little script to remove the comments, which is often the biggest win with the smallest obfuscation.
I suppose that if you have a lot of JavaScript (>1M) it has more value, but in a lot of cases I see people minifying 100k scripts for little benefit "because that's just what you do".
My application ships a small 3K script people can add to their website which isn't minified on purpose (imagine being able to inspect what 3rd-party scripts do exactly!); IIRC minifying it would save 500 bytes or something, but I've had quite literally dozens of people ask me "why isn't it minified?!"
I couldn't find any decent benchmarks to say exactly how much though. Of course for your 3kb script it's irrelevant, but a large web app may load a few mb of JS in which case it could make a big difference, especially on mobile devices.
Also, I've also had minification introduce bugs; and while it's uncommon I'd rather not take the risk as these things are not easy to figure out.
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
someFunction.toString().charAt(10) == 'a'
so you're getting the function's string representation, not its return valueAlso I've never even considered that minifiers can break your code.
To be safe it seems you either need to run minification during test & dev or setup some linting to ban features that could break during minification.
My blog post linked above goes more into the consequences -- ideally you'd formally define what the allowable language subset is.
So if you split your code into modules, some of which are loaded later, the compiler can figure out which code in the initial download is used, but not yet and move it into the late loaded modules, whereas ordinarily this code gets retained in the initial load, because it looks like it is referenced.
A long time ago Malte Ubl demonstrated this with the Splittable add on for Babel.
https://medium.com/@cramforce/introducing-splittable-1c882ba...
For snowpack, it’s just a natural consequence of runtime imports.
Could you provide a link where I can read more? I use Rollup a lot but was not aware it could do this
If you're mostly worried about how quickly you can minify when you do minify, then you're worrying about the wrong thing. You want as small results as possible that can be executed the fastest, how long time it takes doesn't really matter as you only minify on delivery to end-users, not every time you make a change locally.
So instead it seems Google Closure is the best, in the cases the author got it to work. Otherwise it's UglifyJS/Terser, depending on your needs.
It might be a bit of a red herring but build (and thus deploy) times absolutely do matter.
Going from a 30 second deploy to a 2 minute deploy to a 30 minute deploy has severe impact on your workflow, how atomic your changes can be and how immediate your feedback loop is.
I think (without data but from my own experience) it's one of the most massive underlooked productivity-sinks especially in web.
Totally agree re: deploy being very important to optimize.
I don't see a reason, but maybe there is one.
Different people may weight those things differently, but it is unlikely that someone would assign a weight of zero to one or the other (so people are unlikely to just throw up their hands and say "no minification").
You don't need to be 'mostly' worried about how quickly you can minify for esbuild to be the top choice- if you're only, say, 10% worried about build times and still 90% ('mostly') worried about size + execution speed, esbuild still comes out on top from these benchmarks.
For the rest of us that are 100% focused on the best experience for our users, we stick with the tools that does the best minification while being a bit slower, and throw more hardware at it if needed for the deploys.
And pay more. ESBuild is so much faster that I imagine it could have a significant impact on costs.
Besides, yes you make a good point about the size savings scaling with users, but development speed compounds changes over time.
The choice is a bit more nuanced than "smaller build always === 100% focused on the best experience"
Correction: 10x faster, not 10%. The 60-minute deploy is shortened to 6 minutes, not 54 minutes. This is a significant difference in impact on a deployment workflow.
> we stick with the tools that does the best minification while being a bit slower
Correction: not 'a bit slower', _10 times_ slower, which is significant.
For various reasons, I end up debugging a disproportionate number of these cases, and I gotta say that the combination of unsafe optimizations plus long slow compile times can make for some unfun times.
IMO the best option is to try very hard not to have a closure-compiler shaped problem. Keep your client-side code small. If you can't do that, compose it out of small, largely independent, lazily loadable modules. And if you can't do _that_, come to terms with it as early as possible and start using Closure Compiler from the beginning.
Because as much trouble as Closure Compiler can be, there's a scale of application where nothing else comes close. It has top tier dead code elimination (not just tree shaking, but proper DCE), can split an application into lazily loadable chunks, will move code between modules, and will perform pervasive type-aware optimizations that AFAIK no other minifier comes close to.
2% sounds small. And it is, if your traffic is small. It's not small when you have millions of users.
But for an established product with substantial traffic, swapping out a js minifier for one that achieves even single digit % compression improvement seems worthwhile to me - if the only downside is adding a few extra seconds to build time.
Of course you could use esbuild for dev and terser for prod, but maintaining two may be a support headache.
If someone were only interested in the smallest possible minified size, the appropriate solution would be to use all of these minifiers every time and choose the smallest result.
My biggest frustration is that I am not nearly as skilled in Go as I am in other languages, and don't have the confidence jumping into a large existing Go codebase than I would with JS/TS/etc. I have the same issue with Flow, which is written in OCaml.
On the other hand, with Node.js supporting native import/export, I also find myself bundling+minifying the code less and less. Just wish Create-React-App started using it, since I cannot be bothered to dig there enough to manually set it up (the goal of me using CRA is to avoid manual config).
Not that it makes fast minification (which I don't care that much about, I minify only on deploy, and it's a small % of the time it takes), but it does allow for a very fast workflow in dev mode: auto reload seems almost instant even with heavy transpiling.
I only mention it because I assume people look at this articles and think about their dev process speed as well.
If you haven't, give it a try: it's from the Vue author, so it's really clean, but works with react as well.
Because transpilation is done by esbuild
A comment reads "google-closure-compiler is no longer maintained" but that's completely misleading. Maybe that's why the author didn't bother to fix their config for Google Closure?
I'm most excited about esbuild though and I'm curious how it'll fit into a larger codebase & ecosystem. The author is aggressive about keeping it simple (for good reason!!)- the focus on speed is awesome.
You're right, the author probably didn't provide externs and/or exports, so the tests fails. It's not really that much of an effort, especially if you're supposed to be testing the tool itself. Considering that the author is already testing libraries that have externs/exports written for them already, just shows the lack of care of the tests.
https://www.github.com/sebbekarlsson/fjb/tree/master/benchma...
And as understand all these tools use a lexing and ast parsing stage. It would be interesting if you could build a very simple minifier (at least a whitespace one) that could skip that step through just working on input buffer?
There is also the fact that more code means longer minification time. You can praise esbuild for building a CRA in 2 seconds but there are workflows/frameworks that compile sub second by using less runtime code.
By contrast, minification is mechanical semantic-preserving syntax transformations done to a completed program after it written.