June 3, 2023

New build infrastructure (a love letter to esbuild)

Imagine that instead of building campaigns and widgets, we were building boats.

All kind of boats, sailing boats, Research Vessels, cargos, and canoes. Of course you can have them in different colors, and with special features to comply with local regulations.

Now, while we’re crafting your masterpiece and try to build the sleek and swift sailing boat you want, we have two options: build it exactly how you ordered it, putting only the equipment you ordered so it’s ready to sail at peak performance without any waiting time, or wait until each cruise to decide what kind of beverage you want in the bar and what is your favourite sail’s color today.

Of course, on a real boat, you have limited options or what you can change for every trip, but for software, you can decide for every passenger to build a red canoe or a yellow submarine. And so we have two main options: Pre-compute during the build time or wait until the visitor comes to know what to prepare.

Green and fast, we love build time

We aim at being the fastest campaign tool for your supporters, and we heavily invested in pre-computing everything we could at build time for the following reasons:

  1. Reduced Latency: By precomputing and storing the results of intensive calculations during the build phase, you eliminate the need to perform those calculations at run time. This significantly reduces the latency or time delay experienced by the supporters when they interact with the application or system.
  2. Improved Performance: With the heavy lifting done during the build phase, the run-time execution becomes faster and more efficient. It contains only the language and features you want and the application can respond more quickly to user requests, leading to an overall improvement in performance.
  3. Greener Resource Utilisation: Complex calculations often require significant computational resources. At run time, the application can then operate on the precomputed results using fewer resources, resulting in greener solutions and better resource management.
  4. Consistent Results: Performing calculations at build time ensures consistent and deterministic results. Even if something changes on our main code (and we add new features or improvements pretty much every day), your widget won’t be impacted until it’s built again.
  5. Ease of Maintenance: By separating intensive calculations into the build phase, it becomes easier to maintain and update the application or system. Your widget can be regenerated or updated during the build process whenever necessary, without affecting the core functionality or introducing potential bugs during run time.

Overall, performing intensive calculations at build time offers a range of advantages. However, it’s worth noting that not all calculations can be precomputed during the build phase, as some may rely on dynamic or real-time data. 

For most campaigns, we have one single prebuild widget file that contains everything from the needed icons to the translations to the code itself, and have two or three extra requests:

  • Fetch the counter (for the number of supporters)
  • Get the country of the visitor (to save their time and pre-select it)
  • Get the list of targets (if twitterstorm or mail to target campaigns)
  • Get a personalised letter (the snowflake engine, we’ll discuss it more another time)

But it had limitations too, and we just fixed a big one

While way faster than building a boat, building a widget was painfully slow for campaigners, sometimes taking nearly a minute to get the server to build a new version. We tried workarounds and only rebuild the widget if needed, but it added complexity and made the system more brittle. When we started three years ago, the only tool that was smart enough to build the optimised widget we wanted (being able to only embed the language of the widget, removing unused code…) was webpack. It is smart, but slow. Fortunately, new build tools came to the scene and we found one that was as good as webpack but faster: esbuild

We have quite a few niche requirements, like no code splitting, dynamic imports based on a config file, using a single language resource, putting everything under a top level object (proca) and migrating all of that from webpack to esbuilt was smoother than hoped. the only element that needed a bit of change was replacing webpack auto converting from svg to react components, and even that was fairly trivial change.

All our code is opensource and you can see the details of what we did, but the net result is that the build time went from more than 30 seconds to less than 0.3! Here’s a graph from their benchmark, matching pretty much our experience.

It might not sound a lot, but we have over 3000 widgets, most of them are rebuilt multiple times over the duration of the campaign, it quickly adds up.

And I hope our campaigners will enjoy an even smoother experience and get even faster to a running campaign!