It’s not unusual for engineering departments to find themselves deeply invested in old technology, all too aware that there are beautiful, shinier, newer frameworks or languages out there. Technologies that could make impossible or hard things — possible and manageable.
This scenario is usually solved by a complete rewrite. A start-over in an entirely different framework, language and technology stack. While this can solve the problem, it requires a lot of effort, can lead to delays in development work and have unpredictable ETAs. Moreover, this situation can repeat itself with rewrites required every 5–6 years when new front-end technologies appear.
This was something we wanted to avoid at Zava. We wanted to find an approach that would enable us to continuously and discreetly switch from one tech stack to another. Something that made it easier to predict when new technology would be implemented and remove the usual risk associated with implementing and investing in something new.
Why did we start looking for a new front-end framework?
Zava is growing fast, and to support this growth, our engineering team needs to develop new applications, scale them for multiple brands and implement experiments and new features as efficiently as possible.
However, at the end of 2018, almost all of our front-end was powered by Angular.js including customer-facing and internal applications as well as lots of supplementary tools and components. It wasn’t possible to make necessary changes at the speed required because our apps used outdated approaches and accumulated problems like:
- Big bundle sizes, no code splitting was used
- No shared components were used between apps. Only CSS styles were shared
- Code wasn’t modularised enough. Global objects were being referenced everywhere
As a framework, Angular.js is no longer supported for development. Its creators have abandoned it, no new libraries are being created, and the job market of Angular.js developers is slowly shrinking. It was clear we needed to try something new.
The review process — which framework should we use?
Whether to migrate to the new Angular framework (which is entirely different) or choose framework like React wasn’t clear at all. At that point of time React was an established option, that seemed like the go-to solution to all our problems, but we decided to consider all the available options.
To organise our migration to new technology we created a forum for front-end devs from independent teams — the “next-web” guild. Everyone was asked to propose a framework of their choice. The main candidates were:
Angular is an entirely new framework, and we knew that we wouldn’t be able to reuse anything we’d already written in Angular.js. During the decision meeting, Angular was discarded straight away as it relies on TypeScript and RxJS library. RxJS, for example, forces you to think in reactive programming paradigm and structure your app accordingly, while React and Vue.js are less opinionated in how to handle asynchronous logic.
No one in the team was comfortable with TypeScript of RxJS library, so it seemed too risky to invest not only in a new framework but also into these new technologies as well.
React has a very strong set of advantages:
- Major internet companies support it: Facebook, Airbnb, Netflix.
- Rich ecosystem of open-source libraries, discussions, courses.
- Multiple libraries exist for the same problem — for example for state management Redux, Mobx, Unstated. You can pick up the most appropriate for your problem.
But on the other side of the coin there were some disadvantages:
- Fast turnover of libraries, it’s harder to keep up with the fast pace of changes (state management, routing libraries).
- Often React libraries try to be super generic so that they can be used both for web and native development, react-router v3 was good for web, but react-router 4 was focused on both web and native platforms, so at first, it had a lot of things missing for the web.
- Has a steeper learning curve, developers have to understand new concepts of functional programming, such as higher-order components.
React has its specific use cases, it is especially well-suited for making apps, but it shouldn’t be used for everything, for example, for static pages.
The positives for Vue.js are as follows:
- Focused mostly on solving problems arising in the web.
- Easy to get started.
- Has default libraries for main problems which are supported by core contributors: vue-router, vuex for state management, vue-server-renderer, vue-cli.
- More natural understanding by backend developers who had limited exposure to front-end before, because Vue uses plain templates.
- It’s opinionated in a good sense. It has ready patterns for solving many problems, fewer choices to make.
- It is aligned with the web-components standard.
Still, Vue is younger technology than React, and it has its downsides:
- Less choice of open source libraries, fewer people that have experience in Vue.
- Fewer companies are using Vue, though this seems to be changing — for example, Apple and Netflix have recently started using Vue.
- Not so many examples of large applications written with Vue, with the notable exception of Gitlab. For Gitlab transition to Vue went quite well and supported their scale.
To make our final decision, we had an open vote for developers, which helped everyone to align. We identified two main aspects of how we can differentiate between Vue and React:
JSX vs. HTML templates. React implies using JSX to write templates and while it’s also possible with Vue, it’s more idiomatic to use HTML templates in Vue. So we had a choice between using HTML and JSX templates.
Opinionated vs non-opinionated framework. This choice has a great impact on the codebase. Choosing a less opinionated solution provides lots of freedom and flexibility to architect your app. However it comes with a cost — teams need to spend more time on investigations and in numerous meetings to align and agree on the approach. In contrast, with an opinionated solution, a lot of decisions are already made by the framework authors so aligning the team requires way less effort.
We voted on which approach our team members preferred:
Final voting was about choosing between Vue and React:
Vue won voting with a slight advantage. After the voting, no one questioned the outcome because the decision was owned by the team.
After we chose Vue, we started to build solid foundations for our new front-end tech stack. The first step was the configuration of Verdaccio — the registry for storing private node packages. Next, we analysed the leading solutions for displaying the shared component and were choosing between Storybook and vue-styleguidist. We built proof-of-concept style guides with both solutions, and we chose Storybook as it has a more flexible configuration and better community support.
However, initial development progress stalled for a while, as we didn’t have a clear migration path for big applications. The situation would remain like this until we figured out how to write apps in Vue whilst still delivering value to the business:
- Using Micro Frontends — an approach with splitting frontend application into smaller and independent “micro” applications. It’s great for migrating old apps incrementally.
- Creating new apps completely in Vue — this was easier if the app was completely independent of old apps.
For micro front-ends, we chose two approaches:
- Use single-spa to migrate current applications gradually. Single-spa library provides utilities to initialise and destroy app automatically depending on the active URL. Another great thing is that it allows the sharing of dependencies between micro-frontend apps.
- Writing widget apps with Vue so that we can use it anywhere. We have just two files: script and style files which we can embed whenever we need to show the widget. In the future, we would want to make web components out of the widget to isolate its styles and make it more robust.
We can show how we use both of these approaches in our Assessment application. The primary purpose of our Assessment app is to obtain information regarding a patient’s medical condition, including all details that allow us to fulfil the order safely. It consists of these sections:
- Questionnaire to make a diagnosis for a patient
- Product selection
- Login and registration
This is how it looks (displaying a questionnaire section):
Our general approach is to use single-spa library to split the Assessment application into independent micro apps and migrate them independently. Single-spa provides a handful of utilities for loading/destroying new apps depending on the active URL. It also allows sharing dependencies between them.
However, in some cases, we need more flexibility, so we decided to extract the Questionnaires section into a widget. This way we can reuse it in any context (e.g. an app for a user account, static pages), just by loading script and style files from the CDN. On top of that we have the additional benefit of being able to deploy widget independently from all applications where it’s located.
This hybrid approach looks like this:
We’re already using single-spa in other customer-facing applications, and it’s greatly simplified our migration process.
What we’ve learned so far?
- vue-cli is excellent for scaffolding projects, keeping build process up to date and in sync between projects.
- It’s hard to organise work on shared components if you have cross-functional independent teams.
- White-labelling is particularly hard, as there are varying degrees of customisation you can provide. Narrowing down the requirements helped to get the ball rolling.
- With Vue and Jest testing became way easier that with Angular.js
Looking back at the last nine months, we can say that the crucial part in migrating to a new tech stack is organising it incrementally. This makes it easier to convince stakeholders and to implement step-by-step migration without disrupting business processes.
As the next steps we’re planning to:
- Join efforts of independent teams to merge multiple components implementations in the shared library
- Add visual regression testing of shared components
- Adopt standard tools — tracking, A/B testing for use with Vue apps
- Start using server-side rendering of Vue components
This will allow us to easily scale Zava for multiple brands, have a consistent design and implementation and quickly experiment with ideas.