3 April 2026

Optimisation is an art, micro-optimisation is an obsession

Sometimes, optimisation is an art, and micro-optimisation is an obsession. I have been experimenting with a new tech stack for the past 7 months, and I have been trying to optimise my build times as much as possible.

Preface

As any developer, I want to explore as much as I can and make tests and experiments whenever possible. I think we can all agree that optimisation is a must in this field but micro-optimisation is a bit of an obsession, you either have it or you don’t and listen to me, that’s ok.

I have experimenting for the past 7 months with a new tech stack that I can use for my projects, passion projects or even work projects. I stumbled upon a few pieces of tech that I really liked and I started building with them.

First of all, typescript, it was a tough choice, I had to put my over-optimiser ego aside and decide to use the language that is without a doubt the most recommended for web development.

However, I found the perfect occasion to do some experiments, try to push the limits of my knowledge, and learn a beautiful language like typescript. What kind of experiments? Mainly, monorepos. Now, monorepos are not new nor are they special. They are more of a concept and a way to structure your code and your projects, but I had never used them before and I wanted to give it a try.

You need to know quite a lot about typescript and mainly tsconfigs. Let’s say that I developed quite a keen eye for configuration files.

Building in a monorepo

For those who don’t know, a monorepo is a collection of apps and packages, they sit in the same repository and can be installed from the same place. So if App A needs Package B, well it can just install it, and import it as if it was a normal package. The great thing is that you don’t need to publish anything and all your apps, not just App A will receive any update to Package B.

For my experiments, I didn’t want to go full hardcore mode, so I kind of cheated and used a tool, a clever piece of tech that really simplified my setup. Enter Turborepo

I picked it but there are other solutions out there and this post doesn’t really want to highlight the particular strengths or weaknesses of Turborepo. I will, perhaps, write a post about the pros and cons and other solutions but for now, let’s just say that it was a great choice for me and it really helped me to get started with monorepos.

In particular, it has thought me one thing, how do I serve my packages?

First micro-optimisation: how to serve my packages?

There are 2 main school of thought, and there really isn’t a right or wrong answer, it really depends on your needs and your preferences.

I went with option number 2, why? Because it makes sense, the type checker is much faster, all the packages are already compiled and when I code I don’t need to wait for it to load, everything is just there. In big monorepos, with lots of packages and apps, and with large folders this is absolutely the way I wanted things to be.

This might be considered a micro-optimisation because at the end of the day, both approaches just work, and actually, the second one is a bit more complicated to set up, and this was a great learning experience, especially for configuring everything correctly, because when you compile you typescript package, you need to know how to write a tsconfig.

The second and probably more useless micro-optimisation was just me obsessing about build times. How can I make it faster?

How can I build it faster?

If you have ever used typescript, you usually use tsc to compile your code into javascript. According to your tsconfing.json you can generate source maps, very useful in some cases (when you bundle or extremely minify your code), and declaration files, which are those .d.ts files that basically declare the types to your javascript code.

tsc is a formidable tool, widely used and extremely efficient when it comes to declaration files, but it might become a bit slow when it’s time to build, basically compile your code into javascript. It is quite known, after a quick research online, that it really isn’t the best solution for very large typescript codebases. Other tools have come out and their numbers really got my attention, I know I am a simple creature. I see it builds in 20ms instead of 200ms? I am sold.

One of these tools, probably the most popular and fast is esbuild.

The problem with esbuild is that it’s a very powerful bundler and minifier, but what if you don’t want to bundle your code? You just want to go from a typescript file to a javascript file as quickly as possible. This is where tsup comes into play.

I won’t spend time explaining what tsup is, but it is based on esbuild and gives you the option not to bundle your code, and also generate declaration files. It could be considered a very good alternative to tsc and it really sped up the build time of my packages, but not immediately, there was something that still wasn’t working, and this brought to another micro-optimisation, which was to configure tsup optimally and combine the fast build time powered by esbuild and the fast declaration files coming from tsc.

Configuring tsup optimally

The logic behind is pretty simple: tsup is very fast at building, but when it come to produce declaration files, given its internal algorithm to do that, is definitely not the best, and overall it actually made the build time worse.

However, you can combine 2 tools to make a very powerful one. tsup configuration offers a pretty cool api, a method or a property called onSuccess and you can pass a string, which is a command that will be run after a successful build, or a function. I pass a simple string command tsc --emitDeclarationOnly --declaration -p tsconfig.build.json which basically tells tsc to only generate declaration files, and it does it in a very efficient way, much faster than tsup.

What are the other commands? Well, tsc has a quite efficient way to incrementally build your code, when nothing has changed, it won’t do anything. So any time the build would run, tsup would clean the previous build, deleting the artifacts basically, and generate new ones but tsc didn’t really feel need to do that, not consistently. So, the solution, and I won’t take credit for something that wasn’t mine, came from Codex, which is to create a tsconfig.build.json file which extends the main configuration file, but it adds a very important piece of this puzzle incremental: false. This forces tsc to always run the command in a non conservative way, and there you go, it worked.

Conclusion

It took me a few hours to set this up, and quite a few chats with Codex and eventually how much time have I saved on my builds? Hard to say, maybe a couple hundreds of milliseconds, but I still thing it was worth it, as I said at the beginning, micro-optimisation is an obsession, and it’s hard to explain why I am very satisfied with this new setup, but I am. But the truth of the matter is that there is a small piece of my mind which still thinks about ways to optimise this even more. I am sure there are and I will find them and maybe use’em in my projects, who knows.

Thanks for reading