5

Vue2 / Vuetify2 Migration to Vue3 / Nuxt3 / Vuetify3

 1 year ago
source link: https://gist.github.com/mgd216/33d7805847f9bb1ef23a381fd76e22e6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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 lang="ts" setup>
  • Search and remove "this."
  • Delete all imports statements, most are auto-imported by Nuxt
  • Remove from the copied Vue2 export block {}:
    • 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>

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK