The Elm version is quite small! (Smaller is better. Smaller assets means faster downloads!) Note that the React library itself is 32kb. Just the library without any application code. The entire Elm RealWorld App is 29kb, so no amount of code splitting can make the React version smaller than the Elm version!
I was really excited when I first saw these results, but the RealWorld App is not that big. The Vue implementation is only about 2000 lines. So what about larger projects? We had a community member working on a 49,315 line application try the new version and he got 114kb after compilation, minification, and gzip. That is in the ballpark of the 100kb produced by the 2000 lines in the Vue implementation! So it looks like your projects need to get exceptionally large before you start running into the baseline issues you see in JavaScript.
The best part is that it is easy to get these results. No need to write your code in special ways or do tons of configuration. You just add the --optimize
flag when you compile. The compiler takes care of everything else!
The rest of this post will explore (1) the new optimizations that make these results possible, (2) how the new compiler became extremely fast for large projects, and (3) some nice improvements and simplifications.
Note: Check out the full release notes to see everything that is new in Elm 0.19. The installers are available in the freshly updated official guide!
Dead Code Elimination
The primary optimization in Elm 0.19 that gets us such small assets is function-level dead code elimination.
If you use a package with hundreds of functions, like elm/html
, it automatically gets trimmed down to the 10 you actually use. The compiler can just figure it out. This works across the entire ecosystem. Using one function from a massive package with tons of dependencies? No problem. You just get that one function in the generated code.
This level of granularity is possible because:
- Elm functions cannot be redefined or removed at runtime. This makes it easy to tell if something is needed. Is the code reachable from
main
? If so, keep it. If not, drop it. Whereas in JavaScript, any module might modify window.Array
or Array.prototype
, changing how code elsewhere evaluates. So the fact that you do not call it directly does not prove it can be dropped.
- Every package on
package.elm-lang.org
is written entirely in Elm. That means we have a 100% guarantee that there is nothing weird going on in any of your dependencies, so we can cut it up just as easily as your application. If packages contained arbitrary JavaScript code, we would inherit all the same optimization challenges and have to be more conservative.
In JavaScript, the equivalent of dead code elimination is commonly called tree shaking. As that link says, tree shaking generally works with a granularity of modules (not functions) due to the potential redefinition and removal of functions. The larger granularity means you are more likely to get modules you do not use. And those modules can bring in more modules you do not use. Even if you do not use any of their functions directly, you might rely on the functions they mutate. This leads to a cascading increase in asset size as projects get more complicated:

This is further compounded by the fact that npm
allows many versions of the same package in a single project. To work around all this, some JavaScript packages go so far as to chop themselves into individual functions, like lodash-modularized, so application developers have the option to manually approximate what Elm does automatically. So instead of sacrificing development time and code clarity to shave bits, Elm lets you just focus on making great packages and applications. The compiler will take it from there!
Record Field Renaming
When you use the --optimize
flag, you get a couple extra optimizations. One interesting one is record field renaming across your whole codebase. This optimization turns long names like student.mostRecentGrade
into s.m
instead. It tends to give a 5% to 10% reduction in asset size.
Again, this works across the entire Elm ecosystem because every single package on package.elm-lang.org
written completely in Elm. To achieve similar results in JavaScript, you must avoid student['mostRecent' + info]
and statically figure out the difference between student[field]
and student[index]
. If you have ever tried to use ADVANCED_OPTIMIZATIONS
in Google Closure Compiler, you know that this is extremely difficult even when you write all the code yourself, but it would have to work across the 700k npm
packages out there to give comparable results.
Compile Times
Now some readers may be worried that these optimizations come at the cost of slower compile times. Just the opposite! The new compiler is quite fast!
Earlier we mentioned a community member with 49,315 lines of code spread across 227 modules. The application is called SchoolHouse. The author compiled from scratch on his desktop and got the following output:
$ time elm make src/Main.elm --optimize
Dependencies loaded from local cache.
Dependencies ready!
Success! Compiled 227 modules.
real 0m1.959s
user 0m1.129s
sys 0m0.066s
Under two seconds for 50k lines of code. Pretty good, especially considering that this is the absolute worst case. The incremental compiles you do in development will only compile the files with potential changes, which should bring his times under 500ms for most changes.
I suspect this compares favorably with 50k line projects that use Babel, TypeScript, Flow, etc. but I would love to see data to do a concrete comparison. Please share on Discourse if you have the data!
It took a lot of unglamorous work within the compiler to get this kind of performance (e.g. rewriting the parser) and I am particularly excited to hear how it helps companies like NoRedInk and Featurespace which both have more than 200k lines of Elm!
Fun Stuff
I feel like a robot talking about all those numbers. That stuff is nice and all, but I also made improvements to packages, documentation, and error messages! My personal favorites include:
- Nicer parse errors. Should help a lot with the first few weeks of Elm!
- Better docs on types, interop, and “single page apps” in the official guide.
- A simpler way to think about time and time zones in
elm/time
.
- The
getViewport
family of functions for getting scroll positions.
These examples showcase the effort that has gone into continually making Elm simpler and friendlier, but there are a bunch of other improvements listed in the release notes in the same spirit!
Conclusion
I am excited to finally share this release publicly! I hope it will help you out, whether you are learning your first programming language or on your way to 300k lines of code at work.
As folks with 50k+ lines upgrade to Elm 0.19, I encourage you to share your new asset sizes and compile times. You can send feedback to the core team directly to help explore further optimizations like code splitting as described here. And if you run that script and are excited about the results, please blog about it!
And finally, if you decide to give Elm a try, start with the official guide and ask questions on Discourse and Slack if you need help with anything. We will do our best to help you out, but everyone has different projects and preferences. So even if you find it is not the right fit for you, I hope you will come away with a positive learning experience!
Thank You
This was quite a tough release cycle, so thank you to everyone who was supportive and patient over the past 18 months. Thank you in particular to the community members who felt emotional strain alluded to in the beginning of What is Success? I wish it never got like that, but it was also very encouraging to see so many people take time out of their day to be supportive online and in person. I learned a lot in this time!
Thank you to all the folks who helped quietly test Elm 0.19 over the past couple months. Your bug reports were extremely helpful. Some were quite fun to resolve! And thank you to the folks who updated packages and editors. Not everything is done, but we have a pretty solid start. Remember that people do free work because it is fun, not to get stressed by strangers. Based on my personal experiences and discussions with other authors, being patient online and supportive in person is the most productive path.