vue-codemod
Current status: experimental
This repository contains a collection of codemod scripts for use with JSCodeshift that help update Vue.js APIs.
Inspired by react-codemod.
Command Line Usage
npx vue-codemod <path> -t <transformation> --params [transformation params] [...additional options]
transformation
(required) - name of transformation, see available transformations below; or you can provide a path to a custom transformation module.path
(required) - files or directory to transform.--params
(optional) - additional transformation specific args.
Programmatic API
runTransformation(fileInfo, transformation, params)
Roadmap
- Basic testing setup and a dummy CLI
- Support applying
jscodeshift
codemods to.vue
files - Provide a programmatic interface for usage in
vue-cli-plugin-vue-next
- Set up tests
- (WIP) Implement the transformations described below for migration usage
- Built-in transformations need to support TypeScript
- Built-in transformations need to support module systems other than ES module, and those without modules
- Define an interface for transformation of template blocks (may use
vue-eslint-parser
for this) - A playground for writing transformations -
yarn playground
and visit http://localhost:3000
Included Transformations
Migrating from Vue 2 to Vue 3
The migration path (to be integrated in a new version of vue-migration-helper
):
- Install eslint-plugin-vue@7, turn on the
vue3-essential
category (maybe a few exceptions likevue/no-deprecated-dollar-scopedslots-api
) - Run
eslint --fix
to fix all auto-fixable issues; if there are any remaining errors, fix them manually - Run the codemods below
- Install vue@3, vue-loader@16, etc.
- Make sure to use the compat build of vue@3
- Serve the app in development mode, fix the runtime deprecation warnings
Note: even though most of the migration process can be automated, please be aware there might still be subtle differences between Vue 3 and Vue 2 runtime, and the codemods may have uncovered edge cases. Please double check before deploying your Vue 3 app into production.
Legend of annotations:
Mark | Description |
---|---|
🔴 | work not started |
🔵 | needs to or can be implemented in the compat runtime |
Fixable in ESLint
- RFC05: Replace
v-bind
's.sync
with av-model
argument- Can be detected and fixed by the
vue/no-deprecated-v-bind-sync
ESLint rule
- Can be detected and fixed by the
- RFC14: Remove
keyCode
support inv-on
- Can be detected and fixed by the
vue/no-deprecated-v-on-number-modifiers
ESLint rule config.keyCode
can be supported in the compat build. It is also detectable with thevue/no-deprecated-vue-config-keycodes
ESLint rule
- Can be detected and fixed by the
- RFC19: Remove
data
object declaration- Can be detected and fixed by the
vue/no-shared-component-data
and thevue/no-deprecated-data-object-declaration
ESLint rules
- Can be detected and fixed by the
Codemods
- RFC01: New slot syntax and RFC06: Slots unification
- Can be detected and partially fixed by the
vue/no-deprecated-slot-attribute
andvue/no-deprecated-slot-scope-attribute
- During the transition period, with the 2 ESLint rules enabled, it will warn users when they use
this.$slots
, recommendingthis.$scopedSlots
as a replacement - When upgrading to Vue 3, replace all
.$scopedSlots
occurrences with.$slots
(should pass the abovementioned ESLint checks before running this codemod) (implemented asscoped-slots-to-slots
)
- Can be detected and partially fixed by the
- RFC04: Global API treeshaking & RFC09: Global mounting/configuration API change
- implemented as
new-global-api
import Vue from 'vue'
->import * as Vue from 'vue'
(implemented asvue-as-namespace-import
)Vue.extend
->defineComponent
(implemented asdefine-component
)new Vue()
->Vue.createApp()
(implemented asnew-vue-to-create-app
)new Vue({ el })
,new Vue().$mount
->Vue.createApp().mount
new HelloWorld({ el })
,new HelloWorld().$mount
->createApp(HelloWorld).mount
render(h)
->render()
andimport { h } from 'vue'
(implemented asremove-contextual-h-from-render
)Vue.config.productionTip
-> removed (implemented asremove-production-tip
)- 🔴 Some global APIs now can only be used on the app instances, while it's possible to support the legacy usage in a compat build, we will provide a codemod to help migration. (
global-to-per-app-api
)Vue.config
,Vue.use
,Vue.mixin
,Vue.component
,Vue.directive
, etc ->app.**
(It's possible to provide a runtime compatibility layer for single-root apps)Vue.prototype.customProperty
->app.config.globalProperties.customProperty
Vue.config.ignoredElements
->app.config.isCustomElement
- The migration path would be a two-pass approach:
- Scan the entire project to collect all the usages of the abovementioned global properties / methods
- Depending on the result of the first scan:
- If there's only one entry file using these global APIs, then transform it;
- If there's exactly one entry file and one root instance, but several other files are also using
Vue.*
, then transform the entry file to export the root instance, import it in other files and transform them with the imported root instance; - If there are more than one entry file or root instances, then the user needs to manually export the root instances, re-apply this codemod to those non-entry files with an argument designating the root instance.
- 🔵 Detect and warn on
optionMergeStrategies
behavior change
- implemented as
- RFC07: Functional and async components API change
- 🔵 a compatibility mode can be provided for functional components for one-at-a-time migration
- Can be detected by the
vue/no-deprecated-functional-template
ESLint rule - 🔴 SFCs using
<template functional>
should be converted to normal SFCs
- RFC08: Render function API change
- Template users won't be affected
- JSX plugin will be rewritten to cover most use cases (work-in-progress, available at https://github.com/vueComponent/jsx/)
- 🔴 For Users who manually write render functions using
h
- 🔵 It's possible to provide a compat plugin that patches render functions and make them expose a 2.x compatible arguments, and can be turned off in each component for a one-at-a-time migration process.
- 🔴 It's also possible to provide a codemod that auto-converts
h
calls to use the new VNode data format, since the mapping is pretty mechanical.
- 🔴 Functional components using context will likely have to be manually migrated, but a similar adaptor can be provided.
- RFC12: Custom directive API change
bind
->beforeMount
inserted
->mounted
- remove
update
hook and insert a comment to note the user about the change componentUpdated
->updated
unbind
->unmounted
- 🔴 VNode interface change (a runtime compat plugin is also possible, see the notes for RFC08)
- RFC13: Composition API
import ... from '@vue/composition-api'
->import ... from 'vue'
(implemented asimport-composition-api-from-vue
)- There are some subtle differences between the plugin and Vue 3 implementation, see https://github.com/vuejs/composition-api#limitations for more details.
- RFC16: Remove
inline-template
- Can be detected by the
vue/no-deprecated-inline-template
ESLint rule - 🔴 Possible alternatives are addressed in the RFC
- Can be detected by the
- RFC25: Built-in
<Teleport>
component- Can be detected by the
vue/no-reserved-component-names
ESLint rule - 🔴 A codemod can be implemented to rename all
<Teleport>
components to some other name like<TeleportComp>
.
- Can be detected by the
- 🔴 RFC26: New async component API
- 🔵 In the compat build, it is possible to check the return value of functional components and warn legacy async components usage. This should cover all Promise-based use cases.
- 🔴 The syntax conversion is mechanical and can be performed via a codemod. The challenge is in determining which plain functions should be considered async components. Some basic heuristics can be used (note this may not cover 100% of the existing usage):
- Arrow functions that returns dynamic
import
call to.vue
files - Arrow functions that returns an object with the
component
property being a dynamicimport
call.
- Arrow functions that returns dynamic
- The only case that cannot be easily detected is 2.x async components using manual
resolve/reject
instead of returning promises. Manual upgrade will be required for such cases but they should be relatively rare.
- 🔴 RFC30: Add
emits
component option- There could be potential naming conflicts with existing component-level
emits
options, so we need to scan and warn on such usages - To better utilize the new
emits
option, we can provide a codemod that automatically scans all instances of$emit
calls in a component and generate theemits
option
- There could be potential naming conflicts with existing component-level
- Vuex 3.x to 4
- implemented as in combination of
new-global-api
andvuex-v4
Vue.use(Vuex)
&new Vue({ store })
->app.use(store)
new Store()
->createStore()
- implemented as in combination of
- Vue Router 3.x to 4
- implemented as in combination of
new-global-api
andvue-router-v4
Vue.use(VueRouter)
&new Vue({ router })
->app.use(router)
new VueRouter()
->createRouter()
mode: 'history', base: BASE_URL
etc. ->history: createWebHistory(BASE_URL)
etc.- 🔴 RFC21: Scoped slot API for
router-link
- 🔴 RFC28: Change active and exact-active behavior for
router-link
- implemented as in combination of
vue-class-component
7.x to 8import { Component } from 'vue-class-component'
->import { Options as Component } from 'vue-class-component'
- 🔴
import Vue from 'vue'
->import { Vue } from 'vue-class-component'
(Need to avoid name collision if there's any reference toVue
besidesextends Vue
) - 🔴
Component.registerHooks
->Vue.registerHooks
Breaking Changes that Can Only Be Manually Migrated
- RFC17: Changed behavior when using
transition
as root- Can be detected by the
vue/require-toggle-inside-transition
ESLint rule
- Can be detected by the
- RFC22: Merge
meta
fields from parent to child inRouteLocation
- Seems no codemod or ESLint rule is applicable to this breaking change
- RFC24: Attribute coercion behavior change
- Codemod is not likely to help in this case
- RFC31: Attribute Fallthrough + Functional Component Updates
- Warn of every
$listeners
and.native
usage
- Warn of every
- Subtle differences between
@vue/composition-api
and the Vue 3 implementation are listed in the@vue/composition-api
README - The precedence of
v-if
andv-for
have been flipped when using both on the same element. - Warn for mixing
v-for
andref
. - Warn about deprecated instance methods and properties:
$destroy
,$children
RFCs that May Need Amendments to Simplify the Migration
Note: there are just rough ideas. Amendments may or may not be proposed, depending on the implementation progress of this repo.
- 🔵 RFC04: Global API treeshaking & RFC09: Global mounting/configuration API change
Vue.extend
can be supported in a compat runtime as an alias todefineComponent
- 🔵 RFC11: Component
v-model
API change- I don't have a clear idea on how to progressively migrate the
v-model
API because both the author and consumer of the components need to change their ways to use this API, according to the current RFC. So we might need a compatibility layer in the runtime.
- I don't have a clear idea on how to progressively migrate the
Other Opt-In Changes
These features are only deprecated but still supported in the compatiblity builds. There will be runtime warnings and ESLint rules to detect their usages. Some of them can be automatically migrated with the help of codemods.
- RFC15: Remove filters
- Can be detected by the
vue/no-deprecated-filter
ESLint rule
- Can be detected by the
- 🔴 RFC18: Transition class name adjustments
- For
<transition>
components with customenter-class
orleave-class
:enter-class
->enter-from-class
leave-class
->leave-from-class
- Otherwise, there are two possible solutions:
- Change every
.v-enter
and.v-leave
selector in the stylesheets to.v-enter-from
and.v-leave-from
- Or, attach
enter-from-class="v-enter v-enter-from" leave-from-class="v-leave v-leave-from"
to these<transition>
components. Users can delete these attributes after they updated the corresponding stylesheets
- Change every
- For
- RFC20: Events API Change
- Can be detected by the
vue/no-deprecated-events-api
ESLint rule - 🔴 A codemod can be implemented to use other libraries like tiny-emitter for the events API
- Can be detected by the
- RFC23-scoped-styles-changes
- The new behavior should be opt-in
- RFC27: Custom Elements Interop Improvements
- (Covered by the Global API RFCs):
Vue.config.ignoredElements
->app.config.isCustomElement
- 🔴 Vue 2 non-
<component>
tags withis
usage -><component is>
(for SFC templates).v-is
(for in-DOM templates).
- The
vue/no-deprecated-html-element-is
ESLint rule can be used to detect usage foris
usage on built-in HTML tags.
- (Covered by the Global API RFCs):
set
anddelete
instance or global methods will be supported only in IE compat builds.- implemented as
remove-vue-set-and-delete
- implemented as
Generic Transformations
Aside from migrating Vue 2 apps to Vue 3, this repository also includes some generic transformations that can help clean up your codebase.
remove-trivial-root
- this transformation removes trivial root components like
{ render: () => h(App) }
and useApp
as the direct root
- this transformation removes trivial root components like
define-component
--param.useCompositionAPI
:false
by default. When set totrue
, it will import thedefineComponent
helper from@vue/composition-api
instead ofvue
- this transformation adds
defineComponent()
wrapper to.vue
file exports, and replacesVue.extend
calls todefineComponent
Custom Transformation
See https://github.com/facebook/jscodeshift#transform-module
Post Transformation
- Running transformations will generally ruin the formatting of your files. A recommended way to solve that problem is by using Prettier or
eslint --fix
. - Even after running prettier its possible to have unnecessary new lines added/removed. This can be solved by ignoring white spaces while staging the changes in git.
git diff --ignore-blank-lines | git apply --cached