Migrating a big old codebase to Vue 3: what I'm excited about

How big?

1289 .vue files

~230k rows of Vue code

502 Vue applications

How old?

Codebase is almost 8 years old

Vue first time used in 2016

Natalia Tepluhina

Staff Frontend Engineer

Core Team Member

Google Dev Expert

@N_Tepluhina

Performance

@N_Tepluhina

Functional components

return h('div', { class: 'js-line log-line' }, [
  h(LineNumber, {
    props: {
      lineNumber: line.lineNumber,
      path,
    },
  }),
  ...chars,
]

@N_Tepluhina

Functional components

functional: true

<template functional>

render functions

@N_Tepluhina

Functional components

"Performance gains from 2.x for functional components are now negligible in 3.x, so we recommend just using stateful components"

🎉

@N_Tepluhina

Mixins

@N_Tepluhina

Mixins

👎 Not really reusable

👎 Conflict-prone

👎 Unclear source

@N_Tepluhina

Mixins

import { formatDate, getTimeago } from '../../lib/utils/datetime_utility';

export default {
  methods: {
    timeFormatted(time) {
      const timeago = getTimeago();

      return timeago.format(time);
    },

    tooltipTitle(time) {
      return formatDate(time);
    },
  },
};

@N_Tepluhina

Composition API

import { formatDate, getTimeago } from '../../lib/utils/datetime_utility';

export default function useTimeAgo() {
  const timeFormatted = (time) => getTimeago.format(time)
  
  const tooltipTitle = (time) => formatDate(time)
  
  return { timeFormatted, tooltipTitle }
}

@N_Tepluhina

VueApollo

Options API

Apollo Components

Mixins for reuse

Composables

@N_Tepluhina

VueApollo - Options API

apollo: {
  permissions: {
    query: permissionsQuery,
      variables() {
      return {
        fullPath: this.projectPath,
        iid: this.issueIid,
      };
    },
    update: data => data.project.issue.userPermissions,
  },
},

@N_Tepluhina

VueApollo - Options API

import { useQuery, useResult } from '@vue/apollo-composable'
  
const { result, loading } = useQuery(permissionsQuery, 
{
  fullPath: this.projectPath,
  iid: this.issueIid,
})
const permissions = useResult(result, null, data => data.project.issue.userPermissions)

@N_Tepluhina

VueApollo - Components

  <apollo-mutation
    #default="{ mutate, loading, error }"
    :mutation="$options.destroyDesignMutation"
    :variables="{
      filenames,
      projectPath,
      iid,
    }"
    :update="updateStoreAfterDelete"
    v-on="$listeners"
  >
    <slot v-bind="{ mutate, loading, error }"></slot>
  </apollo-mutation>

@N_Tepluhina

VueApollo - Components

import { useMutation } from '@vue/apollo-composable'

const { mutate: deleteDesign } = useMutation(
  destroyDesignMutation,
  {
    variables: {
      filenames: this.filenames,
      projectPath: this.projectPath,
      iid: this.iid,
    },
	update: this.updateStoreAfterDelete
  }
)

@N_Tepluhina

Multiple v-model

<gl-table
  class="alert-management-table"
  :items="alerts ? alerts.list : []"
  :fields="$options.fields"
  :sort-direction="sortDirection"
  :sort-desc.sync="sortDesc"
  :sort-by.sync="sortBy"
  sort-icon-left
  fixed
  @row-clicked="navigateToAlertDetails"
  @sort-changed="fetchSortedData"
>

@N_Tepluhina

Multiple v-model

<gl-table
  class="alert-management-table"
  :items="alerts ? alerts.list : []"
  :fields="$options.fields"
  :sort-direction="sortDirection"
  v-model:sort-desc="sortDesc"
  v-model:sort-by="sortBy"
  sort-icon-left
  fixed
  @row-clicked="navigateToAlertDetails"
  @sort-changed="fetchSortedData"
>

@N_Tepluhina

Multiple v-model

  model: {
    prop: 'entities',
  },
  v-model:entities="smth"

@N_Tepluhina

TypeScript

@N_Tepluhina

TypeScript

2 years

321 comment

No clear decision

@N_Tepluhina

TypeScript

import { defineComponent } from 'vue'

const Component = defineComponent({
  // type inference enabled
})

@N_Tepluhina

TypeScript

const Component = defineComponent({
  data() {
    return {
      count: 0
    }
  },
  mounted() {
    const result = this.count.split('') 
    // => Property 'split' does not exist on type 'number'
  }
})

@N_Tepluhina

DevTools!

Thank you!