Lessons Learned from Upgrading Bayanat to Vue 3 and Vuetify 3

Lessons Learned from Upgrading Bayanat to Vue 3 and Vuetify 3

At Bayanat, we're always looking to keep our technology stack current. Recently, we took on the challenge of upgrading our application to Vue 3 and Vuetify 3. This wasn't a simple task, especially given our extensive use of custom components and third-party Vue 2 and Vuetify 2 components.

One key aspect of our approach is our commitment to a no-build environment. This decision simplifies our development and deployment processes, reduces dependencies, and leverages modern browser capabilities. However, it also presented unique challenges during the upgrade process.

In this post, we'll share some of the biggest hurdles we faced during this upgrade and how we overcame them. If you're considering a similar upgrade, our experience might provide some helpful insights.

App Initialization and Creation

The way Vue initializes apps changed quite a bit in version 3. We had to adapt our app structure to fit the new paradigm.

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
  const { createApp } = Vue
  const app = createApp({
    data() {
      return {
        message: 'Hello Vue 3!'
      }
    }
  })
  app.mount('#app')

We ran into some issues with Vuetify not finding the layout, but after some trial and error, we landed on a structure that worked:

<div id="app">
    <v-app v-cloak class="h-screen">
        <v-layout>
            <!-- App content -->
        </v-layout>
    </v-app>
</div>

This setup allowed us to maintain our desired layout while conforming to Vuetify 3's expectations.

Component Registration

In Vue 2, components were often automatically registered globally. Vue 3 requires a more explicit approach. We had to use app.component to register our custom components globally.

Given the complex dependencies between our components, we found it more practical to continue with global registration rather than manually defining dependency trees for each component. This approach saved us time and reduced the likelihood of overlooking critical dependencies.

Routing Challenges

Bayanat is a hybrid multi-page/single-page application, which presented unique routing challenges during the upgrade.

We came up with a solution that looks like this:

const { createRouter, createWebHistory, createWebHashHistory } = VueRouter;
const routes = [
    { path: '/', name: 'home', component: Vue.defineComponent({ template: '' }) },
    { path: '/admin/bulletins/:id', name: 'bulletin', component: Vue.defineComponent({}) },
    { path: '/admin/bulletins/', name: 'bulletins', component: Vue.defineComponent({}) },
    // ... other routes
];

The key was defining default routes with empty components. This approach allowed us to maintain our routing structure while adhering to Vue-router’'s routing paradigm.

Adapting to a No-Build-Tool Environment

Bayanat doesn't use build tools, which posed significant challenges when it came to using certain Vue 3 and Vuetify 3 compatible libraries. This approach, while simplifying our development and deployment processes, required creative solutions for integration.

One of the most significant challenges we faced was integrating Leaflet with Vue 3. The popular vue-leaflet library requires a build tool, which didn't align with our no-build approach. This situation exemplified a broader issue we encountered with several third-party libraries.

To overcome these challenges, we had to write our own integrations and components for certain functionalities. In the case of Leaflet, we decided to create our own geomap component from the ground up. This decision, while time-consuming, offered several benefits:

  1. Full control over functionality and integration with our Vue 3 application.
  2. Seamless operation in our no-build environment.
  3. Implementation of only the features we needed, potentially improving performance.
  4. Deepened understanding of both Leaflet and Vue 3's component system.

Updating Custom Components

Migrating our custom components to Vue 3 and Vuetify 3 involved several key changes:

  1. We moved from the Vue.component syntax to Vue.defineComponent.
  2. We had to explicitly define emit methods for our components.
  3. We updated our v-model implementations to use model-value instead of value.
  4. We removed filters and replaced them with methods or computed properties.

These changes allowed us to take advantage of Vue 3's improved performance and new features while maintaining compatibility with Vuetify 3.

Rebuilding Date Components for Vuetify 3

A significant change we encountered was with our custom date components. Vuetify 3 handles date models as actual Date objects instead of strings. This meant we had to rebuild our date components from the ground up.

After some research, we found a great solution: Vuetify Labs Date Input component. We used this as a foundation and built our own component on top of it. This new approach gives us more flexibility and aligns better with modern JavaScript practices.

Sticking with the Options API

While Vue 3 introduced the Composition API, we made the decision to stick with the Options API for our existing components. This choice was driven by a few factors:

  1. Backward compatibility: The Options API is fully supported in Vue 3, making our transition smoother.
  2. Familiarity: Our team was already well-versed in the Options API, which helped maintain productivity during the upgrade.
  3. Code clarity: We find the Options API clear and readable, especially for components with straightforward logic.

This decision allowed us to focus on other critical aspects of the upgrade without introducing a new learning curve for our entire codebase.

Data Table Migration in Vuetify 3

One of the most significant changes in our Vuetify 3 upgrade was migrating our data tables. We replaced the Vuetify 2 data table with the new data-table-server component in Vuetify 3.

A key change in this migration was how we handle data loading and pagination. In Vuetify 2, we used to watch the options prop for changes. With Vuetify 3, we simplified this by using the @update:options event. Here's a snippet of how we implemented this:

<v-data-table-server
  :items="items"
  :items-length="totalItems"
  :items-per-page="itemsPerPage"
  @update:options="refresh"
>
  <!-- Table content -->
</v-data-table-server>
methods: {
  refresh(options) {
    // Here we handle pagination, sorting, and filtering
    // based on the options object
  }
}

This approach is more straightforward and reactive, aligning well with Vue 3's improved reactivity system.

Wrapping Up

Upgrading Bayanat to Vue 3 and Vuetify 3 was a significant undertaking, but one that has positioned us well for the future. The process taught us valuable lessons about adaptability, problem-solving, and the intricacies of both Vue 3 and Vuetify 3.

While the journey had its challenges, the end result is a more robust, performant application that's ready to evolve with our users' needs. For teams considering a similar upgrade, we hope our experiences provide useful insights and potential solutions to common obstacles.