This is my entry for the F# Advent Calendar 2018. I’ve been doing full-stack .Net web development for ~6 years now, and I learned F# ~3 years ago. In the second set of 3 years, I’ve also done more frontend web development than I did backend. In this time I’ve had the (mis)fortune of playing with countless JS SPA frameworks. This has given me the insight into what these various frameworks do, how they work, and how they differ from the other frameworks. I’ve also thought about what web developers should be paying more attention to, and what is nice and fun. In this post I want to explore a combination of what’s fun (Fable Elmish), and what I believe developers need to pay more attention to (bundle size). With that, I’d like to delve into two topics, namely, what I believe to be a big problem with the current state of web development, and a possible solution to this problem.
The world of web development these days is rather … interesting. I’m not going to go into finer details about what I mean beyond what I write here. Maybe that’s a blog post for another day. Essentially, there are countless frontend frameworks to build rich interactive websites, and countless helper libraries that do various things. Something that I’ve noticed, is that developers tend to elect to use numerous helper libraries, and then only use one or two functions out of those libraries instead of writing those functions themselves. The most egregious example of this is the NPM ecosystem, with the left-pad debacle, and more recently, the event-stream problem. Frontend web development may not see this problem to such an extreme degree as backend development with node.js, but a similar problem in a smaller scale still exists. This causes the problem of bundle size. All of these libraries need to be sent to the end client in full, even if only a small subset of the helper libraries are used. This means that the end client must download an awful lot of additional data that is completely unnecessary. Admittedly, there is tree-shaking, but in my experience this has never worked well at all. This also holds true for various CSS frameworks, such as bootstrap or material-ui. These are massive libraries where only a subset will be used, just to save a small amount of development time, but at the cost of end user load times.
It’s easy to overlook the final bundle size as developers, because we tend to sit at powerful computers with fast internet. But it becomes a problem when we’re trying to show Grandma what we’ve made, or when a client is using their smartphone in the middle of nowhere. These are all realistic use cases that are very common, especially when you look outside the traditional first world countries. Suddenly there are massive load times on the end clients’ devices, for absolutely no discernible advantage. There have been numerous studies done to show that even the slightest increase in load times drastically reduces conversion rates.
So that’s the reason I decided to explore the subject at hand. Fable Elmish React is nice, I don’t think I need to convince anyone of that, but React is a large(ish) library that needs to be sent to the client. What if we could have the same functionality, at a fraction of the final bundle size? Enter Preact. Preact advertises itself as a drop in replacement for React, but at a tiny size. It features similar performance, and an almost identical API to create React like applications from scratch, or with the addition of the Preact-Compat addon, to be able to replace React and React-Dom one-to-one with Preact and Preact-Compat in an existing application without any other changes. What I want to explore, is how simple is it to use Preact instead of React with Fable, and what kind of impact does it have on the final bundle size.
The sample application I’m using to conduct this experiment with is a bit more involved than just the standard Counter app you will find in any getting started tutorial. I wanted to make sure that replacing React with Preact in a “properly” architected application would work as expected. It includes routing, and a component that is created using React class syntax, with a lifecycle event. This proves that several common use cases are covered by this experiment. What I am not testing is server side rendering, or the use of third party components, although theoretically they should work according to the Preact documentation.
You can view the sample application using React here. Open the developer tools and take not of the size of vendors.js and app.js
You can also view the repository itself here: The master branch is the version with Preact (described below) and the react branch is, as you can guess, the version with React.
For React to work in the browser, it needs two libraries: React, and React-Dom. The reason for this separation is that React also supports alternative renderers, such as React Native, so the people over at Facebook decided to split the core logic of React, and its renderers into different libraries. When combined and minified in production mode, they have a total size of 108 KB. This is just the React framework, this doesn’t include application code, or the Fable and Elmish libraries. So now to see how to use Preact instead. According to the Preact documentation it’s as simple as installing Preact and Preact-Compat and including those in the final bundle instead of React and React-Dom. Additionally, an alias needs to be set in the webpack configuration that aliases both
preact-compat. See the config here to take a look. Now theoretically, when we build our bundle it should work just as before. And after building it … surprisingly, it does! All functionality is still the same, it still renders incredibly quickly, and if we take a look at the final bundle size, it’s 17 KB!. That means our entire application including Fable and Elmish libraries are smaller than just React itself. That’s a huge bonus for our end users, and we don’t even have to give up our nice frameworks. Well that was easy.
You can view the sample application using Preact here. Open the developer tools and take not of the size of vendors.js and app.js
Something to keep in mind though, is that we lose alternative renderers, such as React Native. However with that specific use case, bundle size isn’t an issue as everything is already loaded on the client. For a comprehensive list of differences, check this out.
In the Preact documentation, it mentions that Preact uses
hyperscript notation, which aims to be a simple implementation agnostic abstraction as to how to create DOM elements in code. Perhaps it might make sense to create a Fable Hyperscript package, and then have the ability to use any VDOM library that the developers wants. Perhaps this already exists, or maybe ts2fable is already good enough in this regard. Those are subjects I want to explore next.
So now the question stands: If I’m so concerned about bundle size, why am I using something like Fable Elmish, which brings its own library that the client needs? Well, I also mentioned that earlier, and the answer is that SOME fun still needs to be had :) However, for a more “real” answer, let’s explore the alternative. Most likely, in a React app, you would be using Redux as the state management solution. Additionally, you would need React-Router, and React-Redux. That sounds like numerous additional dependencies that the client needs. This causes a bundle size of about 380KB. That’s HUGE, and Fable Elmish only is about 40KB, for roughly the same functionality. So now we have a small bundle size, AND fun by using Fable Elmish.