Git Product home page Git Product logo

vue-codemod's Introduction

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
  • (WIP) 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

Included Transformations

Migrating from Vue 2 to Vue 3

The migration path (to be integrated in a new version of vue-migration-helper):

  1. Install eslint-plugin-vue@7, turn on the vue3-essential category
  2. Run eslint --fix to fix all auto-fixable issues; if there are any remaining errors, fix them manually
  3. Run the codemods below
  4. Install vue@3, vue-loader@16, etc.
  5. Make sure to use the compat build of vue@3
  6. 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. Please double check before deploying your Vue 3 app into production.

Legend of annotations:

Mark Description
๐Ÿ”ด work not started
๐ŸŸ  on-going work
๐ŸŸข implemented (may have uncovered edge cases)
๐Ÿ”ต needs to or can be implemented in the compat runtime

Fixable in ESLint

Codemods

  • ๐ŸŸข RFC01: New slot syntax and RFC06: Slots unification
    • ๐ŸŸข Can be detected and partially fixed by the vue/no-deprecated-slot-attribute and vue/no-deprecated-slot-scope-attribute
    • ๐ŸŸข During the transition period, with the 2 ESLint rules enabled, it will warn users when they use this.$slots, recommending this.$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 as scoped-slots-to-slots)
  • ๐ŸŸ  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 as vue-as-namespace-import)
    • ๐ŸŸข Vue.extend -> defineComponent (implemented as define-component)
    • ๐ŸŸข new Vue() -> Vue.createApp() (implemented as new-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() and import { h } from 'vue' (implemented as remove-contextual-h-from-render)
    • ๐ŸŸข Vue.config.productionTip -> removed (implemented as remove-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:
        1. Scan the entire project to collect all the usages of the abovementioned global properties / methods
        2. Depending on the result of the first scan:
          1. If there's only one entry file using these global APIs, then transform it;
          2. 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;
          3. 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
  • ๐ŸŸ  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 as import-composition-api-from-vue)
    • ๐Ÿ”ด Other subtle differences between @vue/composition-api and the Vue 3 implementation.
  • ๐ŸŸ  RFC16: Remove inline-template
  • ๐ŸŸ  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>.
  • ๐Ÿ”ด 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 dynamic import call.
    • 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 the emits option
  • ๐ŸŸข Vuex 3.x to 4
    • implemented as in combination of new-global-api and vuex-v4
    • ๐ŸŸข Vue.use(Vuex) & new Vue({ store }) -> app.use(store)
    • ๐ŸŸข new Store() -> createStore()
  • ๐ŸŸ  Vue Router 3.x to 4
  • ๐Ÿ”ด vue-class-component 7.x to 8
    • import { 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 to Vue besides extends Vue)
    • Component.registerHooks -> Vue.registerHooks

Breaking Changes that Can Only Be Manually Migrated

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 to defineComponent
    • For the changes in the form of Vue.*->app.*, it may be not easy to transform all apperances correctly, because there would be many cross references to the root app instance. So in the runtime, we can alias Vue.* to app.* if there's only one createApp call in the whole app lifecycle, and throws if there are more than one root app instance detected. This can greatly ease the migration cost for most single-root apps. The compat layer won't apply to multi-root apps because that would defeat the purpose of the API change.
  • ๐Ÿ”ต 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.

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
  • ๐Ÿ”ด RFC18: Transition class name adjustments
    • For <transition> components with custom enter-class or leave-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
  • ๐ŸŸ  RFC20: Events API Change
  • ๐ŸŸข 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 with is 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 for is usage on built-in HTML tags.

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 use App as the direct root
  • define-component
    • --param.useCompositionAPI: false by default. When set to true, it will import the defineComponent helper from @vue/composition-api instead of vue
    • this transformation adds defineComponent() wrapper to .vue file exports, and replaces Vue.extend calls to defineComponent

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

vue-codemod's People

Contributors

sodatea avatar

Stargazers

 avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.