Skip to content

Instantly share code, notes, and snippets.

@mgd216
Last active May 7, 2024 14:00
Show Gist options
  • Save mgd216/33d7805847f9bb1ef23a381fd76e22e6 to your computer and use it in GitHub Desktop.
Save mgd216/33d7805847f9bb1ef23a381fd76e22e6 to your computer and use it in GitHub Desktop.
Vue2 / Vuetify2 Migration to Vue3 / Nuxt3 / Vuetify3

Vue3 / Nuxt3 / Vuetify3 Migration Steps

  • The following notes are a 'checklist' for migrating a large Vue2 / Vuetify2 project to Vue3 / Nuxt3 / Vuetify3. It is NOT intended to be a step-by-step migration guide, they are rough notes our team used for migration
  • Our team decided to create a brand new Nuxt3 app, instead of tyring a 'bridge' or running Vue2/Vue3 side-by-side:
    • nuxi init our-new-app-v3
  • We also changed our store from Vuex to Pinia
  • We decided to use the experimental @vue/reactivity-transform. This provides the ability to use a ref in the script block without having to use .value everywhere
    • without reactivity-transform
      • let userName = ref<string>('')
      • userName.value = "John Doe"
    • with reactivity-transform
      • let userName = $ref<string>('')
      • userName = "John Doe"
    • to enable add the following to nuxt.config.ts
      • experimental: { reactivityTransform: true }
  • Props are defined using interfaces, this means you can access props without having to use props.myVar
    • without interface definition
      • const props = defineProps(['userName'])
      • console.log("NAME", props.userName)
    • with interface definition
      • interface Props { userName: string; }
      • const { userName = 'Joe Doe' } = defineProps<Props>();
      • console.log("NAME", userName)
  • Using reactivity-transform and defining props with interfaces keeps our resulting code very clean because you do not have to use myvar.value or props.myvar.

Structure Changes

  • Copy each Vue2 file into a new Vue3 file using proper Nuxt folders:
    • components/UserInfoCard.vue > components/User/InfoCard.vue
  • Move the Script block above the Template block

Script Block Changes

  • Add into the script tag: <script setup lang="ts">
  • Search and remove "this."
  • Delete all imports statements, most are auto-imported by Nuxt
  • Remove from the copied Vue2 export block {}:
    • name
    • components
  • Migrate Props
    • interface Props { sid: string;}
    • const { sid } = defineProps< Props >();
  • Migrate Data
    • let var = $ref< string >()
  • Migrate Computed
    • mapGetters > const { var } = $(useSomething())
    • const var = $computed(()=>{})
  • Migrate Created
    • onBeforeMount(() => {})
  • Migrate Watchers
    • watch(() => varToWatch,() => { functionToCall(); });
    • watch(() => varToWatch,() => { functionToCall(); }, { immediate: true });
    • watch(() => varToWatch,() => { functionToCall(); }, { deep: true, immediate: true });
    • watch([() => var1ToWatch, () => var2ToWatch],() => { functionToCall(); })
  • Migrate Methods
    • mapActions > const { function } = useStore()
    • copy functions directly out of method {} block
  • Change constants to enums
  • Paages
    • Add the Nuxt Page metadata
    • definePageMeta({key: (route) => route.fullPath, middleware: ['auth'], layout: 'page',});

Template Block Changes

  • Remove all Filters replace with functions or composables
    • <span>{{ updateDate | dateFormat}}</span> > <span>{{ formatDate(updateDate)}}</span>
  • Change $refs
    • $refs are no longer available for access to components in template as a ref
    • Include component with ref: <UserDialog ref="UserDialogRef" />
    • On UserDialog component expose function to open: defineExpose({showDialog})
    • In script of parent, create a ref: const UserDialogRef = $ref<InstanceType<typeof UserDialog> | null>(null);
    • from the parent call the exposed method: MyDialogRef.showDialog();

Vuetify Changes

  • Change Icons
    • change fa-icon > v-icon icon="fa:fas fa-home"
    • check text colors green--text > color="green"
  • Change Text Typogrpahy
    • title > text-h5
    • caption > text-caption
    • success--text > text-success
    • warning--text > text-warning
    • error--text > text-error
    • white--text > text-white
  • v-list Changes
    • remove v-list-item-content > It is now the default slot on v-list-item
  • v-chip Changes
    • sizes are now size="small" not attributes
    • colors are now color="primary" not a class
    • default is a translucent background, need to use variant="flat"
  • v-select and v-autocomplete
    • @input= > @update:model-value=
    • item-text > item-title
  • v-tabs
    • v-tabs-items > v-window
    • v-tab-item > v-window-item
  • v-simple-table
    • v-simple-table > v-table
    • dense > density="compact"
  • v-virtual-scroll (Not available until Vuetify 3.1+)
  • v-textfield
    • filled > variant="plain"
    • append-icon="mdi-magnify" > prepend-inner-icon="fa:fas fa-search"
    • @input= > @update:model-value=
  • Activators (v-tooltip)
    • #activator="{ on }" > #activator="{ props }"
    • v-on="on" > v-bind="props"

Code Example

Original Vue2 / Vuetify2 file

  • components/PersonSoftwareCard.vue
<template>
  <BaseCard :title="title">
    <template #body>
      <LoadingSpinner :loading="softwaresLoading" title="Software" :size="30">
        <v-row>
          <v-col cols="6">
            <div class="title grey darken-2 pl-2"> Owned: </div>
            <div v-if="softwareOwned.length > 0">
              <v-virtual-scroll
                :bench="10"
                :items="softwareOwned"
                :item-height="30"
                :height="200"
                class="scroller-blue"
              >
                <template #default="{ item, index }">
                  <v-row
                    dense
                    no-gutters
                    style="height: 30px"
                    :style="AppService.getAltRowColor(index, '#121212')"
                    align="center"
                  >
                    <v-col cols="8" class="text-truncate">
                      <span class="ml-2 font-weight-bold">{{ item.softwareName }}</span>
                    </v-col>
                    <v-col cols="1">
                      {{ item.quantity | numFormat('0,0') }}
                    </v-col>
                    <v-col cols="3" class="text-center">
                      <SoftwareStatusLabel :software="item" />
                    </v-col>
                  </v-row>
                </template>
              </v-virtual-scroll>
            </div>
            <div v-else>
              <div class="text-center headline my-5">
                There is no Software assigned to {{ userId }} as the Owned.
              </div>
            </div>
          </v-col>
          <v-col cols="6">
            <div class="title grey darken-2 pl-2"> Subscribed: </div>
            <div v-if="softwareSubscribed.length > 0">
              <v-virtual-scroll :bench="10" :items="softwareSubscribed" :item-height="30" :height="200" class="scroller-blue">
                <template #default="{ item, index }">
                  <v-row
                    dense
                    no-gutters
                    style="height: 30px"
                    :style="AppService.getAltRowColor(index, '#121212')"
                    align="center"
                  >
                    <v-col cols="8" class="text-truncate">
                      <span class="ml-2 font-weight-bold">{{ item.softwareName }}</span>
                    </v-col>
                    <v-col cols="1">
                      {{ item.quantity | numFormat('0,0') }}
                    </v-col>
                    <v-col cols="3" class="text-center">
                      <SoftwareStatusLabel :software="item" />
                    </v-col>
                  </v-row>
                </template>
              </v-virtual-scroll>
            </div>
            <div v-else>
              <div class="text-center headline my-5">
                There is no Software assigned to {{ userId }} as the Subscribed.
              </div>
            </div>
          </v-col>
        </v-row>
      </LoadingSpinner>
    </template>
  </BaseCard>
</template>

<script>
import { mapActions, mapGetters } from 'vuex'
import AppService from '@/services/app.service.js'
import BaseCard from '@/components/layout/BaseCard.vue'
import LoadingSpinner from '@/components/common/LoadingSpinner.vue'
import SoftwareStatusLabel from '@/components/software/SoftwareStatusLabel.vue'

export default {
  name: 'PersonSoftwareCard',
  components: {
    BaseCard,
    LoadingSpinner,
    SoftwareStatusLabel,
  },
  props: {
    userId: {
      type: String,
      default: null,
    },
  },
  data() {
    return {
      AppService,
      softwareOwned: [],
      softwareSubscribed: [],
    }
  },
  computed: {
    ...mapGetters(['softwares', 'softwaresLoading', 'userIdAuth']),
    title() {
      return this.userId === this.userIdAuth ? 'My Assigned Software' : `Software Assigned to: ${this.userId}`
    },
  },
  created() {
    this.initialize()
  },
  methods: {
    ...mapActions(['getSoftwares']),
    async initialize() {
      if (this.softwares.length === 0) {
        await this.getSoftwares()
      }
      this.softwareOwned = this.softwares.filter((s) => {
        if (s.ownedUserId?._id === this.userId) return true
        return false
      })
      this.softwareSubscribed = this.softwares.filter((s) => {
        if (s.subscribeUserId?._id === this.userId) return true
        return false
      })
    },
  },
}
</script>

New Vue3 / Nuxt3 / Vuetify3 file

  • components/Person/SoftwareCard.vue
<script lang="ts" setup>
interface Props {
  userId?: string;
}
const { userId } = defineProps<Props>();

const NumSvc = useNumeral();

let softwareOwned = $ref<any[]>([]);
let softwareSubscribed = $ref<any[]>([]);

const { userIdAuth } = useAuthStore();
const { getAltRowColor } = useAppStore();
const { getSoftwares, softwares, softwaresLoading } = useSoftwareStore();

const title = computed(() => {
  return userId === userIdAuth ? 'My Assigned Software' : `Software Assigned to: ${userId}`;
});

onBeforeMount(async () => {
  if (softwares.length === 0) {
    await getSoftwares();
  }
  softwareOwned = softwares.filter((s) => {
    if (s.ownedUserId?._id === userId) return true;
    return false;
  });
  softwareSubscribed = softwares.filter((s) => {
    if (s.subscribeUserId?._id === userId) return true;
    return false;
  });
});
</script>

<template>
  <BaseCard :title="title">
    <template #body>
      <LoadingSpinner :loading="softwaresLoading" title="Software" :size="30">
        <v-row>
          <v-col cols="6">
            <div class="text-h5 grey-darken-2 pl-2">Owned:</div>
            <div v-if="softwareOwned.length > 0">
              <RecycleScroller
                v-slot="{ item, index }"
                :items="softwareOwned"
                :item-size="30"
                class="scroller-200"
                key-field="_id"
              >
                <v-row no-gutters style="height: 30px" :style="getAltRowColor(index, '#121212')" align="center">
                  <v-col cols="8" class="text-truncate">
                    <span class="ml-2 font-weight-bold">{{ item.softwareName }}</span>
                  </v-col>
                  <v-col cols="1">
                    {{ NumSvc.numberFormat(item.quantity, '0,0') }}
                  </v-col>
                  <v-col cols="3" class="text-center">
                    <SoftwareStatusLabel :software="item" />
                  </v-col>
                </v-row>
              </RecycleScroller>
            </div>
            <div v-else>
              <div class="text-center text-h5 my-5">
                There is no Software assigned to <b>{{ userId }}</b> as the Owned.
              </div>
            </div>
          </v-col>
          <v-col cols="6">
            <div class="text-h5 grey-darken-2 pl-2">Subscribed:</div>
            <div v-if="softwareSubscribed.length > 0">
              <RecycleScroller
                v-slot="{ item, index }"
                :items="softwareSubscribed"
                :item-size="30"
                class="scroller-200"
                key-field="_id"
              >
                <v-row no-gutters style="height: 30px" :style="getAltRowColor(index, '#121212')" align="center">
                  <v-col cols="8" class="text-truncate">
                    <span class="ml-2 font-weight-bold">{{ item.softwareName }}</span>
                  </v-col>
                  <v-col cols="1">
                    {{ NumSvc.numberFormat(item.quantity, '0,0') }}
                  </v-col>
                  <v-col cols="3" class="text-center">
                    <SoftwareStatusLabel :software="item" />
                  </v-col>
                </v-row>
              </RecycleScroller>
            </div>
            <div v-else>
              <div class="text-center text-h5 my-5">
                There is no Software assigned to <b>{{ userId }}</b> as the Subscribed.
              </div>
            </div>
          </v-col>
        </v-row>
      </LoadingSpinner>
    </template>
  </BaseCard>
</template>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment