Hi everyone, welcome to my talk on scaling your React app without micro-frontend. Very quick disclaimer first, this talk is about scalability of your codebase on a developer perspective and not on user-facing performances. That's it for the disclaimer.
Hello again, I'm Jonathan Wagner I'm an engineering manager at DataUK and I've been working for the past 4 years on a bit more than 10 projects in production and for the last 8 months I've been working with NX, which is a build system helping you build faster and do things faster. That's going to be the core of the talk here. Let's have a look at what we'll talk about.
First of all, discuss a bit of the problem, what happens when you scale up codebase and then my journey to fixing it and what I've learned along the way, what I've tried, what didn't work, what worked. So first of all, the problem. I mentioned, having a codebase that grows, that means your CI becomes a bit slower and when it becomes slower, it means you have a slower feedback loop, which means your developers are unhappy, it takes longer to develop features and in the end your users are unhappy, which we definitely don't want.
So when your codebase grows, you have type checking, you have ESLint, you have maybe some dead code tests, some unit tests, end-to-end tests, a lot of testing and the build time and then everything's taking a bit of time and it adds up and in my case it added up to more than 30 minutes and that's my trigger. Ideally I want my site to be 10 or 15 minutes, when it reaches 30 minutes it means something's gone terribly wrong and I want to address it. In this case, we cannot even see the buildable time, it's just skyrocketing, it's going to 3-4 hours, which means it's costing a lot of money to the company and just frustrating for everyone.
Let's try to look at a more precise example here, so we have a growing codebase which means we may have maybe 800 tests. Imagine you have a pull request where I do one line change, you push everything and the CI runs everything again, so that means you have to wait 20 minutes, maybe 30 minutes for the test to pass. Does it sound normal to you that a one line change triggers 20 minutes running the CI? I don't think so. And nx doesn't think so either. From the nx docs they actually say, the duration of the invoked operation should be proportional to the size of the change, and something very strong. It seems simple but it's quite tricky to put in place. It involves a bit of caching, a lot of caching and then putting all that together properly. But I didn't know that at first.
So obviously I tried to just over-engine my CI and that meant doing a lot of parallelisation, putting some tests up to skip frontend or backend tests, depending on what was changed. And that meant writing a lot of custom rules, which were tricky and introduced a few regressions. And even like switching TypeScript compilations to something based on Rust like swc. And doing all of this, I got some improvements, got down to 20 minutes each but it meant I had a tricky CI to manage. We had 27 different jobs and it meant understanding how they all played together, which ones were conditional on which, and it became just tricky to manage and to maintain. So that's the start of where I wanted to go afterwards. I think we spent hours and hours optimising the CI. Where do we go from there? How do we make it faster without overengineering it more? Here comes the journey. Discovering Annex, discovering a bit more about how to use Annex properly, what the secret trick is, and how the caching puts everything and stitching everything together. As I said before, the main idea we want to aim for is only rerunning things that have changed.
Comments