Skip to content

Getting started

1. Analyzing Vue 2 Features Used

Vue 3 introduces several breaking changes and new paradigms that might affect your existing codebase. Therefore, it is a good idea to review the features used in your codebase as a starting point. This analysis will help define the scope of changes required to make your code compatible with Vue 3 and serve as a useful reference for future steps.

Options API

If your components primarily use the Options Api, consider refactoring to the Composition Api, especially for larger applications. This refactoring is optional but can offer more flexibility and maintainability. However, it can consume a lot of resources and might not always be the best choice (see Options API vs. Composition API). This step can be done later, component by component if needed, or as a first step by utilizing script setup, which has been available since Vue 2.7.

v-model

Vue 3 changed the default prop/event combination for v-model from :value/@input to modelValue/@update:modelValue. It is required to update all occurrences on custom components that uses v-model. <input> and other form elements still use the native prop and event combination, e.g., :value and @input.

Vue 2 components use value and emit input events. Other synced props are possible with the sync modifier:

vue2-component.vue
<template>
<button @click="emit('input', value + 1)">Click me ({{ value }})</button>
<button @click="emit('update:otherValue', otherValue + 1)">
Click me ({{ otherValue }})
</button>
</template>
<script>
default export {
props: ['value', 'otherValue'],
emits: ['input', 'update:otherValue'],
}
</script>

Vue 3 components use modelValue and emit update:modelValue instead and allow multiple v-models

vue3-component.vue
<template>
<button @click="emit('update:modelValue', value + 1)">
Click me ({{ value }})
</button>
<button @click="emit('update:otherValue', otherValue + 1)">
Click me ({{ otherValue }})
</button>
</template>
<script setup>
defineProps({
modelValue: Number,
otherValue: Number,
});
defineEmits(["update:modelValue", "update:otherValue"]);
</script>

As you can see, Vue 3 streamlined the usage of v-model. Just think as v-model being the short form of v-model:modelValue and this being the short form of @update:modelValue and :modelValue.

app.vue
<template>
<vue2Component v-model="value" :otherValue.sync="otherValue" />
<vue3Component v-model="value" v-model:otherValue="otherValue" />
</template>

Vue.set() / this.$set()

Vue 2 was based on getters and setters and could not track changes in an object directly. Therefore, it was necessary to explicitly “tell” Vue by using Vue.set() or this.$set(). However, Vue 3 uses proxies, so this is no longer required. These methods can be safely replaced with a normal assignment, e.g., obj.foo = 'bar'.

Deprecated slot / slot-scope syntax

In Vue 2.6, a new syntax for slots and scoped slots was introduced, and slot / slot-scope was deprecated. If you still use the old syntax in your code, you must update it to the new syntax while migrating to Vue 3. This is also required for the vue-compat build to work (see Setting Up the Vue-Compat Build and ESLint).

OldSyntax.vue
<template>
<base-layout>
<template slot="header" slot-scope="slotProps">
<h1>{{ slotProps.title }}</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template slot="footer">
<p>Here's some contact info</p>
</template>
</base-layout>
</template>
NewSyntax.vue
<template>
<base-layout>
<template #header="slotProps">
<h1>{{ slotProps.title }}</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
</template>

Filters

Filters in Vue 2 were a convenient way to format text and data in templates, but they have been removed in Vue 3. If your codebase heavily relies on filters, you will need to refactor them into computed properties or methods.

Before.vue
<template>
<div>
<h1>{{ msg | capitalize }}</h1>
</div>
</template>
AfterCompositionApi.vue
<script setup>
import { computed } from "vue";
const msg = ref("this is a message");
const capitalizedMsg = computed(() => capitalize(msg.value));
</script>
AfterOptionsApi.vue
<script>
export default {
data: () => ({
msg: "this is a message",
}),
computed: {
capitalizedMsg() {
return capitalize(this.msg);
},
},
};
</script>

Custom Directives

Custom directives are still supported in Vue 3. However, some changes might be required depending on how you use them. Vue 3 introduces a new lifecycle hook, beforeUnmount, and changes the signature for directive hooks. If your application relies on custom directives for significant DOM manipulations, you’ll need to update these hooks to match the new API. While you’re at it, you might want to consider removing directives altogether. There are some rumors about custom directives being deprecated in later versions of Vue 3. Most of the time, a component can accomplish the same task more effectively.

If you are writing your own directives, you surely can figure out the new syntax yourself ;).

Mixins

Mixins are a powerful feature in Vue 2 for reusing code across components, but they have been increasingly discouraged in favor of the Composition API in Vue 3. The Composition API provides a more explicit and organized way to reuse code without the “implicit magic” that can make mixins hard to debug and maintain. If your application relies on mixins, plan to refactor this shared logic into composables. Composables are functions that encapsulate reactive state and logic and can be imported and reused across components, providing a clearer and more modular approach to code reuse.

Event Bus ($on, $off, $emit)

In Vue 2, it is common to use an event bus and component events to pass data around. However, in Vue 3, component events were replaced by $emit and binding to events via v-on. A global event bus can still be used but often leads to hard-to-maintain, spaghetti code. Therefore, you should consider using a different state management solution like Pinia for more complex scenarios.

If your codebase heavily relies on an event bus, it might seem easier to leave it as is, but future developers will appreciate you untangling the mess that an event bus introduces.

2. Checking Dependencies

We have covered all of Vue’s core features; however, Vue plugins, component libraries, state management solutions, etc., often need to be updated alongside Vue. You need to check which dependencies are incompatible with Vue 3 and update them accordingly. Many libraries have their own migration paths that you should follow.

Here is a list of common dependencies used in Vue projects:

  • Vue: The core of the framework. You should definitely read their migration guide even if some information might be redundant. To help with migration, Vue comes with a compatibility build (also see Setting Up the Vue-Compat Build and ESLint).
  • Vue Router: Almost every project uses it. Follow the migration guide.
  • Vuex: Version 4 brings support for Vue 3. Refer to its migration guide.
  • Vuetify: Officially supported in Vue 3 after a long wait. It also has its own migration guide and an extra eslint-plugin to ease the transition.
  • Element UI: Supported via Element Plus. A guide can be found here.
  • Quasar: Version 2 supports Vue 3 and has a dedicated guide here.
  • Bootstrap Vue: Tips for migrating can be found here.
  • Bundler: Consider switching to Vite if you still use vue-cli or webpack (see Migrate to Vite).

3. Reviewing Component Libraries

Component libraries are a crucial part of many Vue applications, and their compatibility with Vue 3 can significantly impact your migration strategy. In some cases, older libraries are no longer maintained and need to be replaced. Sometimes a migration path exists.

Assessing Compatibility and Migration Paths

Not all libraries have full support for Vue 3 yet. Check the documentation or repositories of each library to see if they provide a Vue 3-compatible version or an official migration guide.

If a component library is no longer maintained or does not offer a clear migration path to Vue 3, consider alternatives. Look for libraries with similar aesthetics or functionality that are compatible with Vue 3.

Also, consider whether the components can be replaced with your own components instead.

Deciding Whether to Replace or Rewrite

In cases where a library is not migratable, you have a choice: replace the library entirely or rewrite the necessary components. Replacing a library might be straightforward if it mostly provides simple, styled components or utilities like grids, containers, or basic forms. In many cases, a simple <div> with some styling is all that’s required.

However, more interactive components such as inputs, buttons, or modals might require more effort to replace. Rewriting these components or finding direct replacements that mimic their API and style can be time-consuming and could introduce bugs or require additional testing.

Considering the Effort and Benefits of a New Library

When considering a switch to a different library, factor in the effort involved in re-styling and re-testing your application. Even if the new library offers a similar look and feel, minor differences in component behavior or style could require additional adjustments across your codebase. This can often result in significant effort to make your codebase “work again” and can tie up a lot of resources.

Making an Informed Decision

The decision to replace a component library should be made carefully. Consider the effort required for refactoring against the long-term maintainability and benefits of using a supported and actively maintained library. Sometimes, the effort spent upfront to switch to a better-supported library will save you time and headaches in the future.

4. TypeScript

TypeScript can significantly aid in migrating a larger codebase, especially when moving to Vue 3. Most component libraries now support TypeScript, providing immediate feedback if props are incorrect or missing, which helps catch many bugs early that might otherwise go unnoticed. Additionally, TypeScript is valuable in everyday development, and many developers prefer it over plain JavaScript today.

However, transitioning an entire codebase to TypeScript can be challenging. If you’re not already using TypeScript and don’t intend to, the migration effort might not be justified. But if TypeScript should become part of your stack, now is an opportune time to fully integrate it.

For more details, refer to Migrate to TypeScript.

Need Assistance?

If you need help with your migration, feel free to contact me via email or visit the contact page. I offer professional assistance and guidance for migrations at fair rates.