DEV Community

Nikhil Verma
Nikhil Verma

Posted on

From Vue 2 to 3: A long journey

TL;DR

This is a long post detailing my journey of migrating a project from Vue 2 to 3. It contains the setbacks and the victories I faced and providing breakdown of the issues as well.


1. Learning Vue

I joined my current team on May 2021, the project I was working with used Vue 2.6 And Vuex 3.4 and plain Javascript.

Vue 3.0 was already out at that point yet it was not the "default" Vue version. It won't be the case until Feb 7, 2022

2. The need for types

I was new to Vue back then and came from a React and Typescript codebase.

My first commit was to add a tsconfig.json just to have some intellisense. The pain point at that time was that Vue.extend for Vue 2 had poor support for Typescript. I wanted to fix it.

The solution came with the usage of vue-class-component with a combination of vue-property-decorator. Using a combination of the two allowed us to define components in a way that provide a reasonable amount of type safety.

The Vue component code would look something like this

import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Prop(Number) readonly propA: number | undefined
  @Prop({ default: 'default value' }) readonly propB!: string
  @Prop([String, Boolean]) readonly propC: string | boolean | undefined
}
Enter fullscreen mode Exit fullscreen mode

The migration to class components was done incrementally, any new features or components written using the class syntax. Older components were refactored out whenever we needed to change them.

We also switched our VSCode plugin from Vetur to Volar which had better Typescript support.

3. Using Vuex wrong

The Vuex stores were in the "traditional" redux style. They had a file structure like so:

store/
    actions.js
    getters.js
    index.js
    mutations.js
    state.js
    types.js
Enter fullscreen mode Exit fullscreen mode

Good luck finding the right store by name! Or landing on the right action/mutation because there was no support for intellisense.

This got solved by migrating the stores to use vuex-module-decorators which came with a much better support for Typescript. Plus you get all actions/mutations/getters etc in a single file so it's easier to understand. This was also done incrementally.

Here is how the syntax for the decorator stores will look like.

import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'

@Module
export default class Counter extends VuexModule {
  count = 0

  @Mutation
  increment(delta: number) {
    this.count += delta
  }

  @Action({ commit: 'increment' })
  incr() {
    return 5
  }
}
Enter fullscreen mode Exit fullscreen mode

4. First setback

There was a spike done into the feasibility of migrating to Vue 3. It didn't last long because of the following reasons:

  1. We used an internal UI library which depended on components that weren't compatible or were unstable with Vue 3 at the time
    1. vee-validate
    2. v-tooltip
    3. vue-showdown
    4. vue-multiselect
    5. vue-grid-layout
  2. We couldn't figure out a reasonable strategy to migrate the UI library to Vue 3. Because then we would need to manage two versions of the same library.
  3. We did not want to stop shipping our product while the migration was happening, it either had to be incremental or quick.

5. vee-validate and the $slots problem

The biggest hurdle that we faced was vee-validate. It is a form validation library built for Vue. The pain was that the library API for Vue 2 and 3 were entirely different.

You would almost call it another library, this was due to the new breaking $slots implementation in Vue 3 (more on that later).

I thought about that initially, but aside from how hard is it to find a new name, I think it is better to build on the existing popularity of vee-validate especially not everyone who used vee-validate will switch to the new package and I would be gettings tons of requests for Vue 3 support.

Our UI library had used vee-validate to create a "form builder" that was used over 30 times in our application. We couldn't find an incremental upgrade path here. Either we migrate them all or we migrate none.

6. $slots between Vue 2 and Vue 3

The official migration documentation for slots unification goes over the API differences. It omits a major point: In Vue 3 it's not possible to get the DOM elements of the slots directly from the reference to the slot.

You would need to render your parent, then use the parent's HTML node to get the child slot selector. Or use some kind of inject/provide workaround.

This is the main reason why the vee-validate library could not use it's earlier, simpler API.

Here is a link to the problematic part of the code.

Code example of where slots don't work

This wouldn't work anymore in Vue 3 because you can't directly extract the DOM nodes of children from the $slots property.

7. A new hope

On 1st July 2022 Vue 2.7 was released. It finally ported many features from Vue 3 back to Vue 2. This was a pivotal moment in our development because we it came with a solid Typescript support out of the box using defineComponent. This was better than vue-class-component because it needed some hacks to get inter-component type inference to work.

@matrunchyk had done a fantastic job creating a codemod to automatically migrate Vue class components to the earlier syntax. It was not perfect but workable.

I made some further modifications to it to get it to work on our project. It's available here if you want to try it for yourself: NikhilVerma/vue-codemod. It's still not polished enough to consider an upstream pull request but you can hack around to migrate your project with it.

We finally migrated to Vue 2.7 and defineComponent using the codemod in a very short time. Also used this to migrate most of the codebase to Typescript. Here is the timeline recorded in our JIRA ticket.

Date JS Files
5th December 2021 244
22nd Feb 2022 173
23nd Feb 2022 138
24th Feb 2022 109
10th Mar 2022 103
7 Apr 2022 88
26 Apr 2022 54
9 May 2023 24
17 Aug 2023 22

At the same time, we noticed several library dependencies migrated to support Vue 3 with minimal API changes. This was exciting!

8. The solution to vee-validate

Now the only thing preventing a smooth migration was the vee-validate library. We were in luck because our UI team was working on a second version of the Flow UI library which used Lit. It also offered a replacement Form builder which could take over the vee-validate one.

Since the new library used Lit, it was agnostic of Vue 2 or 3 so incremental migration was easy.

9. Let's do this!

The final plan was in place:

  1. Migrate all old form and vee-validate instances with the new Lit ones
  2. Migrate remaining UI components in the library to support Vue 3
  3. Migrate the application to Vue 3 in a single PR

The first step took around a month to complete, as the instances has to be migrated incrementally. The second step was faster, around a week to migrate because most of the other libraries had good backwards compatibility.

The third was easy too. Vue codemod helped a lot, and ESLint + Typescript caught most issues. The lifesaver was our automation suite which made sure we didn't break any critical functionality in the application.

Here is a screenshot of the pull request which migrated our app to Vue 3. (edited out the internal bits)

PR overview of the Vue migration

After almost two years. We migrated to Vue 3. Because we prepared so well the final merge was uneventful. We had a few bugs discovered afterwards, mostly due to how boolean attributes have changed in Vue 3.

10. Aftermath

We are happily using Vue 3 since then. And I hope that future Vue upgrades will be less eventful and not need writing posts like these.

I hope this was helpful to you, I am sure that there are others who are still using Vue 2 on larger codebase and having a tough time with it. And I hope by sharing their experiences it will encourage others to start their migration journey.

Top comments (4)

Collapse
 
cmcnicholas profile image
Craig McNicholas

Nice, we faced a similar scenario almost 2 years ago now switching directly from class components to Vue 3 composition. We bit the bullet on a ~1.5m line codebase of Vue 2 and just went for a brute force 2-3 which we managed in 7 weeks.

Two reasons it worked is because we have such a comprehensive suite of unit (10k+), component (3k+) and e2e (1.5k+) tests and we heavily lean into a lot of strict TypeScript. Without the test suite and build time failures we would have failed I'm absolutely sure.

Best advice I can give others is make sure you have the test coverage, it's the only way you will have the confidence to pull the trigger.

Collapse
 
giovannimazzuoccolo profile image
Giovanni Mazzuoccolo • Edited

I can totally relate to the frustration. Currently, I'm in the process of migrating a Nuxt 2 app to version 3, but I eventually decided to go for a complete rewrite. Since it's a side project developed during my free time, progress has been a bit slow :(

Congratulations for the upgrade!

Collapse
 
ozzythegiant profile image
Oziel Perez

I just decided to stop supporting Vue altogether. Came for the SFC, stayed for Class Components. I did every project with them. Once Vue 3 was in beta and Evan You decided to just straight up eliminate class components and not support them, I realized not only was this going to fracture the entire ecosystem but also destroy many libraries, which it did. All because they wanted to force Composition API, a React ripoff that tries to copy that disgusting hooks concept. That's my take but just the fact that they did not care about backwards compatibility was a huge turn off. If they did it for 3, what makes you think they wont do it for 4? Nah, Im doing all projects with Svelte and Angular now

Collapse
 
itswadesh profile image
Swadesh Behera

Hi Nikhil. Thanks for the post. I completely agree with the associated frustration. We completely avoided the long path by migrating our app from Vue2 to Svelte. It was easier than migrating to Vue3 and the developer experience was a lot better.