- 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 }
- without reactivity-transform
- 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)
- without interface definition
- 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.
- 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
- 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(()=>{})
- mapGetters >
- 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
- mapActions >
- Change constants to enums
- Paages
- Add the Nuxt Page metadata
definePageMeta({key: (route) => route.fullPath, middleware: ['auth'], layout: 'page',});
- 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();
- 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+)
- Use RecycleScroller (https://www.npmjs.com/package/vue-virtual-scroller)
- 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"
- 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>
- 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>