There and back again: our journey from Vue 2 to Vue 3

Kees van Lierop · Engineering Team Lead
    There and Back Again: Our Journey from Vue 2 to Vue 3

    In the final quarter of 2023, we completed one of our most challenging engineering projects to date: the infamous Vue 3 migration. Many platforms using Vue as their component library know the complexity of this project—it escalated to the point where even the creator of Vue admitted that the experience could have been much smoother.

    Here’s our journey through the process, how we approached the migration from both a technical and tactical perspective, and finally, what we learned along the way.


    What is the Vue 3 migration?

    Vue is a JavaScript-based framework used for building interfaces. We relied comfortably on Vue 2 for years to scale our prototype into the platform it is today. Then, in September 2020, Vue 3 was officially released (already over three years ago!).

    It’s reasonable to say that Vue 3 is a reaction to—and influenced by—changes in the React ecosystem at the time of its development. One of the biggest improvements has been the introduction of the Composition API, allowing engineers to embed more scalable and flexible patterns. Upgrading to Vue 3 seemed like a good move because it promised to make things smoother for developers and improve our platform’s performance, both of which lead to delivering more customer value.


    Why didn’t we migrate earlier?

    For starters, the challenge seemed overwhelming, especially considering our product was still in its early stages at the time. We knew that as our product continued to grow in size, usage, and complexity, tackling the migration would only become more daunting. Meanwhile, the Vue team kept adding more layers to the Vue 3 migration process, making it seem like a moving target. 

    But there was another hurdle: the Vue ecosystem wasn’t ready. Apart from using Vue as our component library, we relied on various open-source, third-party Vue extensions, such as a date picker, a chart library, our emoji picker, tooltips, form elements, and many other utilities. Unfortunately, these extensions weren’t compatible with the changes in Vue 3. This meant we’d have to rebuild or reimplement them, adding even more complexity to an already challenging task. It took time for the ecosystem to catch up and for Vue to become the default for similar platforms.


    Preparing for the Vue 3 migration

    In 2023, we learned that Vue 2 would reach its end of life at the close of the calendar year, meaning no more updates or support from the Vue team. This announcement sparked a realization across our engineering team and the wider company that we needed to allocate time to tackle this project in 2023. However, we still needed to figure out how much time we would need. 

    To prepare, we began researching and gathering information, using similar articles and migration guides as inspiration. As every project is unique and uses its own patterns and flavors, we quickly realized that we needed to develop our own approach tailored to our specific needs.

    Typically (preferably), projects like this are done gradually, with tasks broken down into smaller, manageable steps to reduce risks. There are many concepts like the strangler fig pattern that are used to facilitate this. However, the nature of the Vue 3 migration made it difficult to adopt these patterns. Many Vue 2 patterns had changed, or been removed, requiring us to update and refactor everything, before being able to flip the switch.

    Recognizing this challenge, the Vue team introduced @vue/compat, which served as a bridge between Vue 2 and Vue 3—consider it as a wrapper around the Vue 3 core that ‘allows’ (a significant amount of) those incompatible Vue 2 patterns, therefore aiding large projects such as ours in this transition to Vue 3.

    Regardless, we had to do what product engineers try to prevent at all cost: a feature freeze. We estimated that with the entire engineering team (~15 engineers at the time) focusing solely on this project (and avoiding all other product work), it would take at least two weeks to complete. After careful consideration of our roadmap and other product priorities, we blocked off two weeks in November and fully committed to the migration project.

    Our approach

    We set up a fairly detailed and chronological script to define the chapters and tasks of this project, using the official migration guide from the compat website as inspiration. Having been ‘late to the game’, we can definitely recommend that others still looking to transition to Vue 3 use this guide as a source of truth. While some of these steps may not have any impact, others could be significant.

    Ironically, the biggest ‘dragons’ that we faced within Theydo were hidden in a small text in the migration guide that said:

    At this point, your application may encounter some compile-time errors / warnings (e.g. use of filters). Fix them first.

    When we reached this point, our compiler reported hundreds of errors across 900 Vue files due to incompatible breaking changes. Before we could even boot the application in the browser to test the changes, we needed to resolve those compiler errors. This meant that, for a long time, the compiler was our only way of getting feedback; it was a painful process.


    AI to the rescue?

    Using AI to automate this process looked really promising. However, there were two issues which prevented us from actually taking this approach:

    1. The quality of output from ChatGPT just wasn’t good enough. While it knew how to transform some Vue 2 patterns into Vue 3 compatible code, it (inconsistently) produced faulty code—incorrectly implementing Vue 3 patterns, creating TypeScript errors, removing comments, and making stylistic inconsistencies and simple syntax errors.

    2. While Theydo mostly uses conventional patterns, like other larger scale applications, there are always certain unique flavors or custom patterns that a generalized AI tool is not aware of and cannot automatically correctly improve.

    We probably could have made it work with lots of prompt-engineering, corrections, and blue-penciling, but we figured that the process wouldn’t have necessarily been faster, smoother or risk-averse.


    DIY

    So we bit the bullet and did it ourselves, manually (although to be fair, the shared experiences within the Vue community and initiatives such as vue-codemod definitely helped speed things up).

    It took some coordination and alignment, consistently working with ±15 engineers in the same directories, sometimes on the same lines of code. To keep track of the distribution and statuses of all the individual tasks, we resorted to using a good old spreadsheet.

    Here are some of the technical details, in a somewhat chronological order:

    • Refactoring all Vue 2 patterns that weren’t supported with the @vue/compat build, such as the scoped slot syntax, app bootstrap, template filters, and event bubbling, among others.

    • Updating TypeScript prop definitions in 500 components.

    • Fixing the breaking changes from vue-router.

    • Upgrading, replacing, or reimplementing all our Vue vendor libraries.

    After changing roughly 29k (!!) lines of code within two weeks, we were able to deploy stable versions of the application once again.

    Our QA team is working tirelessly to increase our automated test coverage. Since this project touched every part of the front end, we launched an extensive QA regression phase, encouraging everyone to compare the production environment (still running on Vue 2) with the Vue 3 release candidate for any oddities. In the final week, we managed to fix those run-time issues and finally released the project to production.


    Lessons learned

    Our engineering community is proud to have managed this project within the expected timeframe, especially considering how uncertain the journey was.

    Looking back, we aim to be better prepared for similar developments in the future. While it’s impossible to completely eliminate risk, it’s crucial to be as ready as possible. Here are some steps that we think can help:

    • Investing in more automated test coverage: Did we ship a bug-free Vue 3 application? Unfortunately not. We learned that Theydo has grown so much in terms of size and complexity that we can’t rely completely on manual functional testing. We’ve already made great progress in increasing the test coverage in our test suites.

    • Exploring micro frontends: Right now, Theydo is a centralized, single-page application. We’re researching the feasibility of splitting up into smaller independent applications which can be managed separately. This could make us more resilient to future changes and bring other benefits (though it also comes with some interesting challenges).

    • Promote fearless engineering initiatives: Most of us found this project ‘daunting’. However, it showed us the importance of establishing a culture that promotes creativity, ingenuity, resilience, and persistence, as this is what enables engineers to tackle tough problems and drive meaningful innovation. Even so, we’ve already managed to pursue the next steps of this project, switching over to Vite, which will boost our velocity even further.

    Kees van Lierop · Engineering Team Lead