Skip to content

Migration Build

Overview

@vue/compat (aka "the migration build") is a build of Vue 3 that provides configurable Vue 2 compatible behavior.

The migration build runs in Vue 2 mode by default - most public APIs behave exactly like Vue 2, with only a few exceptions. Usage of features that have changed or been deprecated in Vue 3 will emit runtime warnings. A feature's compatibility can also be enabled/disabled on a per-component basis.

Intended Use Cases

  • Upgrading a Vue 2 application to Vue 3 (with limitations)
  • Migrating a library to support Vue 3
  • For experienced Vue 2 developers who have not tried Vue 3 yet, the migration build can be used in place of Vue 3 to help learn the difference between versions.

Known Limitations

While we've tried hard to make the migration build mimic Vue 2 behavior as much as possible, there are some limitations that may prevent your app from being eligible for upgrading:

  • Dependencies that rely on Vue 2 internal APIs or undocumented behavior. The most common case is usage of private properties on VNodes. If your project relies on component libraries like Vuetify, Quasar or ElementUI, it is best to wait for their Vue 3 compatible versions.

  • Internet Explorer 11 support: Vue 3 has officially dropped the plan for IE11 support. If you still need to support IE11 or below, you will have to stay on Vue 2.

  • Server-side rendering: the migration build can be used for SSR, but migrating a custom SSR setup is much more involved. The general idea is replacing vue-server-renderer with @vue/server-renderer. Vue 3 no longer provides a bundle renderer and it is recommended to use Vue 3 SSR with Vite. If you are using Nuxt.js, it is probably better to wait for Nuxt 3.

Expectations

Please note that the migration build aims to cover only publicly documented Vue 2 APIs and behavior. If your application fails to run under the migration build due to reliance on undocumented behavior, it is unlikely that we'll tweak the migration build to cater to your specific case. Consider refactoring to remove reliance on the behavior in question instead.

A word of caution: if your application is large and complex, migration will likely be a challenge even with the migration build. If your app is unfortunately not suitable for upgrade, do note that we are planning to backport Composition API and some other Vue 3 features to the 2.7 release (estimated late Q3 2021).

If you do get your app running on the migration build, you can ship it to production before the migration is complete. Although there is a small performance/size overhead, it should not noticeably affect production UX. You may have to do so when you have dependencies that rely on Vue 2 behavior, and cannot be upgraded/replaced.

The migration build will be provided starting with 3.1, and will continue to be published alongside the 3.2 release line. We do plan to eventually stop publishing the migration build in a future minor version (no earlier than EOY 2021), so you should still aim to switch to the standard build before then.

Upgrade Workflow

The following workflow walks through the steps of migrating an actual Vue 2 app (Vue HackerNews 2.0) to Vue 3. The full commits can be found here. Note that the actual steps required for your project may vary, and these steps should be treated as general guidance rather than strict instructions.

Preparations

Installation

  1. Upgrade tooling if applicable.

    • If using custom webpack setup: Upgrade vue-loader to ^16.0.0.
    • If using vue-cli: upgrade to the latest @vue/cli-service with vue upgrade
    • (Alternative) migrate to Vite + vite-plugin-vue2. [Example commit]
  2. In package.json, update vue to 3.1, install @vue/compat of the same version, and replace vue-template-compiler (if present) with @vue/compiler-sfc:

    diff
    "dependencies": {
    -  "vue": "^2.6.12",
    +  "vue": "^3.1.0",
    +  "@vue/compat": "^3.1.0"
       ...
    },
    "devDependencies": {
    -  "vue-template-compiler": "^2.6.12"
    +  "@vue/compiler-sfc": "^3.1.0"
    }

    Example commit

  3. In the build setup, alias vue to @vue/compat and enable compat mode via Vue compiler options.

    Example Configs

    vue-cli
    js
    // vue.config.js
    module.exports = {
      chainWebpack: (config) => {
        config.resolve.alias.set('vue', '@vue/compat')
    
        config.module
          .rule('vue')
          .use('vue-loader')
          .tap((options) => {
            return {
              ...options,
              compilerOptions: {
                compatConfig: {
                  MODE: 2
                }
              }
            }
          })
      }
    }
    Plain webpack
    js
    // webpack.config.js
    module.exports = {
      resolve: {
        alias: {
          vue: '@vue/compat'
        }
      },
      module: {
        rules: [
          {
            test: /\.vue$/,
            loader: 'vue-loader',
            options: {
              compilerOptions: {
                compatConfig: {
                  MODE: 2
                }
              }
            }
          }
        ]
      }
    }
    Vite
    js
    // vite.config.js
    export default {
      resolve: {
        alias: {
          vue: '@vue/compat'
        }
      },
      plugins: [
        vue({
          template: {
            compilerOptions: {
              compatConfig: {
                MODE: 2
              }
            }
          }
        })
      ]
    }
  4. If you are using TypeScript, you will also need to modify vue's typing to expose the default export (which is no longer present in Vue 3) by adding a *.d.ts file with the following:

    ts
    declare module 'vue' {
      import { CompatVue } from '@vue/runtime-dom'
      const Vue: CompatVue
      export default Vue
      export * from '@vue/runtime-dom'
      const { configureCompat } = Vue
      export { configureCompat }
    }

    Note that this module declaration must be placed in a *.d.ts file which contains at least one other top-level import or export (export {} is enough) in order for these types to augment the module as opposed to overwriting it.

  5. At this point, your application may encounter some compile-time errors / warnings (e.g. use of filters). Fix them first. If all compiler warnings are gone, you can also set the compiler to Vue 3 mode.

    Example commit

  6. After fixing the errors, the app should be able to run if it is not subject to the limitations mentioned above.

    You will likely see a LOT of warnings from both the command line and the browser console. Here are some general tips:

    • You can filter for specific warnings in the browser console. It's a good idea to use the filter and focus on fixing one item at a time. You can also use negated filters like -GLOBAL_MOUNT.

    • You can suppress specific deprecations via compat configuration.

    • Some warnings may be caused by a dependency that you use (e.g. vue-router). You can check this from the warning's component trace or stack trace (expanded on click). Focus on fixing the warnings that originate from your own source code first.

    • If you are using vue-router, note <transition> and <keep-alive> will not work with <router-view> until you upgrade to vue-router v4.

  7. Update <transition> class names. This is the only feature that does not have a runtime warning. You can do a project-wide search for .*-enter and .*-leave CSS class names.

    Example commit

  8. Update app entry to use new global mounting API.

    Example commit

  9. Upgrade vuex to v4.

    Example commit

  10. Upgrade vue-router to v4. If you also use vuex-router-sync, you can replace it with a store getter.

    After the upgrade, to use <transition> and <keep-alive> with <router-view> requires using the new scoped-slot based syntax.

    Example commit

  11. Pick off individual warnings. Note some features have conflicting behavior between Vue 2 and Vue 3 - for example, the render function API, or the functional component vs. async component change. To migrate to Vue 3 API without affecting the rest of the application, you can opt-in to Vue 3 behavior on a per-component basis with the compatConfig option.

    Example commit

  12. When all warnings are fixed, you can remove the migration build and switch to Vue 3 proper. Note you may not be able to do so if you still have dependencies that rely on Vue 2 behavior.

    Example commit

Compat Configuration

Global Config

Compat features can be disabled individually:

js
import { configureCompat } from 'vue'

// disable compat for certain features
configureCompat({
  FEATURE_ID_A: false,
  FEATURE_ID_B: false
})

Alternatively, the entire application can default to Vue 3 behavior, with only certain compat features enabled:

js
import { configureCompat } from 'vue'

// default everything to Vue 3 behavior, and only enable compat
// for certain features
configureCompat({
  MODE: 3,
  FEATURE_ID_A: true,
  FEATURE_ID_B: true
})

Per-Component Config

A component can use the compatConfig option, which expects the same options as the global configureCompat method:

js
export default {
  compatConfig: {
    MODE: 3, // opt-in to Vue 3 behavior for this component only
    FEATURE_ID_A: true // features can also be toggled at component level
  }
  // ...
}

Compiler-specific Config

Features that start with COMPILER_ are compiler-specific: if you are using the full build (with in-browser compiler), they can be configured at runtime. However if using a build setup, they must be configured via the compilerOptions in the build config instead (see example configs above).

Feature Reference

Compatibility Types

  • ✔ Fully compatible
  • ◐ Partially Compatible with caveats
  • ⨂ Incompatible (warning only)
  • ⭘ Compat only (no warning)

Incompatible

Should be fixed upfront or will likely lead to errors

IDTypeDescriptionDocs
GLOBAL_MOUNT_CONTAINERMounted application does not replace the element it's mounted tolink
CONFIG_DEVTOOLSproduction devtools is now a build-time flaglink
COMPILER_V_IF_V_FOR_PRECEDENCEv-if and v-for precedence when used on the same element has changedlink
COMPILER_V_IF_SAME_KEYv-if branches can no longer have the same keylink
COMPILER_V_FOR_TEMPLATE_KEY_PLACEMENT<template v-for> key should now be placed on <template>link
COMPILER_SFC_FUNCTIONAL<template functional> is no longer supported in SFCslink

Partially Compatible with Caveats

IDTypeDescriptionDocs
CONFIG_IGNORED_ELEMENTSconfig.ignoredElements is now config.compilerOptions.isCustomElement (only in browser compiler build). If using build setup, isCustomElement must be passed via build configuration.link
COMPILER_INLINE_TEMPLATEinline-template removed (compat only supported in browser compiler build)link
PROPS_DEFAULT_THISprops default factory no longer have access to this (in compat mode, this is not a real instance - it only exposes props, $options and injections)link
INSTANCE_DESTROY$destroy instance method removed (in compat mode, only supported on root instance)
GLOBAL_PRIVATE_UTILVue.util is private and no longer available
CONFIG_PRODUCTION_TIPconfig.productionTip no longer necessarylink
CONFIG_SILENTconfig.silent removed

Compat only (no warning)

IDTypeDescriptionDocs
TRANSITION_CLASSESTransition enter/leave classes changedlink

Fully Compatible

IDTypeDescriptionDocs
GLOBAL_MOUNTnew Vue() -> createApplink
GLOBAL_EXTENDVue.extend removed (use defineComponent or extends option)link
GLOBAL_PROTOTYPEVue.prototype -> app.config.globalPropertieslink
GLOBAL_SETVue.set removed (no longer needed)
GLOBAL_DELETEVue.delete removed (no longer needed)
GLOBAL_OBSERVABLEVue.observable removed (use reactive)link
CONFIG_KEY_CODESconfig.keyCodes removedlink
CONFIG_WHITESPACEIn Vue 3 whitespace defaults to "condense"
INSTANCE_SETvm.$set removed (no longer needed)
INSTANCE_DELETEvm.$delete removed (no longer needed)
INSTANCE_EVENT_EMITTERvm.$on, vm.$off, vm.$once removedlink
INSTANCE_EVENT_HOOKSInstance no longer emits hook:x eventslink
INSTANCE_CHILDRENvm.$children removedlink
INSTANCE_LISTENERSvm.$listeners removedlink
INSTANCE_SCOPED_SLOTSvm.$scopedSlots removed; vm.$slots now exposes functionslink
INSTANCE_ATTRS_CLASS_STYLE$attrs now includes class and stylelink
OPTIONS_DATA_FNdata must be a function in all caseslink
OPTIONS_DATA_MERGEdata from mixin or extension is now shallow mergedlink
OPTIONS_BEFORE_DESTROYbeforeDestroy -> beforeUnmount
OPTIONS_DESTROYEDdestroyed -> unmounted
WATCH_ARRAYwatching an array no longer triggers on mutation unless deeplink
V_ON_KEYCODE_MODIFIERv-on no longer supports keyCode modifierslink
CUSTOM_DIRCustom directive hook names changedlink
ATTR_FALSE_VALUENo longer removes attribute if binding value is boolean falselink
ATTR_ENUMERATED_COERCIONNo longer special case enumerated attributeslink
TRANSITION_GROUP_ROOT<transition-group> no longer renders a root element by defaultlink
COMPONENT_ASYNCAsync component API changed (now requires defineAsyncComponent)link
COMPONENT_FUNCTIONALFunctional component API changed (now must be plain functions)link
COMPONENT_V_MODELComponent v-model reworkedlink
RENDER_FUNCTIONRender function API changedlink
FILTERSFilters removed (this option affects only runtime filter APIs)link
COMPILER_IS_ON_ELEMENTis usage is now restricted to <component> onlylink
COMPILER_V_BIND_SYNCv-bind.sync replaced by v-model with argumentslink
COMPILER_V_BIND_PROPv-bind.prop modifier removed
COMPILER_V_BIND_OBJECT_ORDERv-bind="object" is now order sensitivelink
COMPILER_V_ON_NATIVEv-on.native modifier removedlink
COMPILER_V_FOR_REFref in v-for (compiler support)
COMPILER_NATIVE_TEMPLATE<template> with no special directives now renders as native element
COMPILER_FILTERSfilters (compiler support)