Lessons learned upgrading our frontend component library
How to ensure your next upgrade is 10x less expensive
January 11, 2022
Frontend tech debt
Early stage startups bring a lot of challenges.
One of them is making a trade-off between long-term code quality and the short-term speed of executing experiments that may not last. When a startup is just a few months old, speed of executing experiments often wins by a large margin. As it becomes clear which experiments have turned into a product with product-market fit and further resources have been acquired (in terms of money and people), we begin to pay for those fast-to-release experiments by spending time on fixing the tech debt they have accrued.
Tech debt therefore isn’t necessarily a bad thing at first, so long as it’s managed over time. Additionally from a personal learning perspective it’s an exceptional opportunity for growth. Adding a new feature from scratch is much easier than diving into someone’s old code, extending and then refactoring it — that’s much harder.
One of our biggest debts on the frontend was our component library — Vuetify. Even though v3.x alpha was released a few months ago, we were still sitting on the ancient v1.x. As a consequence, a few odd Vuetify bugs appeared over time and we hadn’t yet prioritised fixing them properly. Rather than hack a temporary solution or even replace the component with something else, we decided to prioritise the upgrade of Vuetify. We began scoping that work and paused almost all frontend work for a few days until the upgrade was released.
The problem with our implementation
To successfully upgrade from v1.x to v2.x, we had to address and fix all the breaking changes. Vuetify’s update was huge, and there were many changes to make.
When we wanted to use any of Vuetify’s components, we simply rendered them in the template. Let’s take a look at the example:
<template> <v-btn flat color="primary" :loading="loading" @click="onUpdateClick"> Update </v-btn> ... </template>
This syntax was valid for v1.x, however, in v2.x the
flat prop wasn’t used anymore; it was replaced with
text. Since we had 400+
v-btn components across the app, we had to manually go through all of them and update
text wherever used. This is just the simplest example to change but in reality some breaking changes were much harder to fix.
Updating breaking changes isn’t the only downside of directly using Vuetify’s components. At the time of writing this article,
v-btn’s API has 50+ different props to pass in. That’s a lot of unwanted combinations for a simple button. Our UI would look fairly inconsistent with all those button variations. Our goal should be to restrict
v-btn’s API and create our own which gives only a few button types as a result.
In total, we had 1,600+ usages of Vuetify components across our app. Not all of them needed refactoring, but unfortunately, many did.
When refactoring literally the whole application, you don’t want to temporarily pause the implementation of any new features. However, doing both at the same time could result in conflicts and lead to excessive amount of time on aligning two different codebases. Thus we chose to spend the majority of the time working on the frontend upgrade, as well as spending some time on non-frontend related tasks. The upgrade, although it was time-consuming, went well without any major complications.
After we upgraded everything, updated the tests and properly code-reviewed and tested it, we asked for company-wide help to manually test the app more comprehensively. Some small cosmetic bugs were found as a result, and after fixing those, the new changes were ready for a release to production.
We had zero customer complaints following the release so it was safe to say that the upgrade went well!
This long process taught us a lot about being tightly coupled to an external library, and how that wasn’t such a great idea.
Thus our next step is to wrap all the Vuetify components with our own implementation. How would that work? Let’s imagine you have a custom component, called something like
core-button, and inside you will render
v-btn. That component will reveal a limited
v-btn API by exposing only a certain number of props. Then, we will replace all the
v-btn occurrences with
That means that instead of 400+
v-btns across the app, we’ll have only one! If Vuetify decides to add any breaking changes, we will need to update in only one place. The same goes for all the other components we use. Our plan is to wrap everything, and eventually create our own component library.
Decoupling our implementation from Vuetify also means that in the future, we can more easily replace the framework if we don’t find it as useful anymore.
There’s always more work to be done but at present we’re excited about all the benefits we’ll enjoy:
- Future upgrades will be much faster
- Future upgrades will be significantly less bug-prone
- The app’s UI is more consistent because of a restricted custom API for every component
- Developers’ experience is better because we can align the library to our own needs
- Any future plans for replacing Vuetify are easy to accomplish because of how we’ve decoupled the implementation
- Creating a component library improves the creation of future apps we decide to build